From 7f7b1228faddac08354356396c5ce5563a0257dc Mon Sep 17 00:00:00 2001 From: Shubert Munthali Date: Fri, 1 Aug 2025 14:09:24 +0200 Subject: [PATCH 1/3] Enhance code style and linting --- .editorconfig | 34 +++ .github/workflows/build.yml | 245 +++++++++--------- .../dev/arkbuilders/arkmemo/graphics/SVG.kt | 10 +- .../preferences/MemoPreferencesImpl.kt | 6 +- .../arkmemo/repo/graphics/GraphicNotesRepo.kt | 12 +- .../arkmemo/ui/adapters/NotesListAdapter.kt | 28 +- .../arkmemo/ui/dialogs/FilePickerDialog.kt | 8 +- .../arkmemo/ui/fragments/AboutFragment.kt | 6 +- .../ui/fragments/ArkRecorderFragment.kt | 15 +- .../ui/fragments/BaseEditNoteFragment.kt | 6 +- .../ui/fragments/EditTextNotesFragment.kt | 4 +- .../arkmemo/ui/fragments/NotesFragment.kt | 24 +- .../ui/viewmodels/ArkMediaPlayerViewModel.kt | 9 +- .../ui/views/GraphicControlTextView.kt | 6 +- .../arkmemo/ui/views/SettingTextView.kt | 6 +- 15 files changed, 272 insertions(+), 147 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..015d3bc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,34 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +tab_width = 4 +no-unused-imports = true +no-wildcard-import = true +max_line_length = 100 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{kt,kts}] +ktlint_code_style = ktlint_official +ktlint_function_naming_ignore_when_annotated_with=Composable +ktlint_standard_annotation = disabled +ktlint_ignore_back_ticked_identifier = true +ktlint_standard = enabled +ktlint_standard_multiline-if-else = disabled + +# Don't allow any wildcard imports +ij_kotlin_packages_to_use_import_on_demand = unset + +# Prevent wildcard imports +ij_kotlin_name_count_to_use_star_import = 99 +ij_kotlin_name_count_to_use_star_import_for_members = 99 + +[*.md] +trim_trailing_whitespace = false + +[**/test/**.kt] +max_line_length=off diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c05150d..ffe9eca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,131 +1,130 @@ name: Build the app on: - push: - branches: [ main ] - pull_request: - branches: [ main ] + push: + branches: [ main ] + pull_request: + branches: [ main ] env: - BRANCH_NAME: ${{ github.ref_name }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: ${{ github.ref_name }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: - check: - if: ${{ startsWith(github.actor, 'dependabot') }} - environment: Development - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'adopt' - cache: gradle - - - name: Validate Gradle wrapper - uses: gradle/actions/wrapper-validation@v3 - - - name: Build debug APK - run: ./gradlew assembleDebug - - build: - if: ${{ ! startsWith(github.actor, 'dependabot') }} - environment: Development - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'adopt' - cache: gradle - - - name: Validate Gradle wrapper - uses: gradle/actions/wrapper-validation@v3 - - - name: Decrypt the keystore for signing - run: | - echo "${{ secrets.KEYSTORE_ENCRYPTED }}" > keystore.asc - gpg -d --passphrase "${{ secrets.KEYSTORE_PASSWORD }}" --batch keystore.asc > keystore.jks - - - name: Build release APK - run: ./gradlew assembleRelease - - - name: Upload release Github arm64-v8a APK - uses: actions/upload-artifact@v4 - with: - name: release-arm64-v8a-apk-github - path: ./app/build/outputs/apk/github/release/app-github-arm64-v8a-release.apk - - - name: Upload release Github armeabi-v7a APK - uses: actions/upload-artifact@v4 - with: - name: release-armeabi-v7a-apk-github - path: ./app/build/outputs/apk/github/release/app-github-armeabi-v7a-release.apk - - - name: Upload release Github universal APK - uses: actions/upload-artifact@v4 - with: - name: release-universal-apk-github - path: ./app/build/outputs/apk/github/release/app-github-universal-release.apk - - - name: Upload release GooglePlay arm64-v8a APK - uses: actions/upload-artifact@v4 - with: - name: release-arm64-v8a-apk-googleplay - path: ./app/build/outputs/apk/googleplay/release/app-googleplay-arm64-v8a-release.apk - - - name: Upload release GooglePlay armeabi-v7a APK - uses: actions/upload-artifact@v4 - with: - name: release-armeabi-v7a-apk-googleplay - path: ./app/build/outputs/apk/googleplay/release/app-googleplay-armeabi-v7a-release.apk - - - name: Upload release GooglePlay universal APK - uses: actions/upload-artifact@v4 - with: - name: release-universal-apk-googleplay - path: ./app/build/outputs/apk/googleplay/release/app-googleplay-universal-release.apk - - lint: - needs: build - environment: Development - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'adopt' - - - name: Run linter - run: ./gradlew lint - - - uses: actions/upload-artifact@v4 - with: - name: lint-results - path: ./app/build/reports/*.html - - ktlint: - needs: build - environment: Development - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'adopt' - - - name: Kotlin linter - run: ./gradlew ktlintCheck + check: + if: ${{ startsWith(github.actor, 'dependabot') }} + environment: Development + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'adopt' + cache: gradle + + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v3 + + - name: Build debug APK + run: ./gradlew assembleDebug + + build: + needs: [ lint, ktlint ] + if: ${{ ! startsWith(github.actor, 'dependabot') }} + environment: Development + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'adopt' + cache: gradle + + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v3 + + - name: Decrypt the keystore for signing + run: | + echo "${{ secrets.KEYSTORE_ENCRYPTED }}" > keystore.asc + gpg -d --passphrase "${{ secrets.KEYSTORE_PASSWORD }}" --batch keystore.asc > keystore.jks + + - name: Build release APK + run: ./gradlew assembleRelease + + - name: Upload release Github arm64-v8a APK + uses: actions/upload-artifact@v4 + with: + name: release-arm64-v8a-apk-github + path: ./app/build/outputs/apk/github/release/app-github-arm64-v8a-release.apk + + - name: Upload release Github armeabi-v7a APK + uses: actions/upload-artifact@v4 + with: + name: release-armeabi-v7a-apk-github + path: ./app/build/outputs/apk/github/release/app-github-armeabi-v7a-release.apk + + - name: Upload release Github universal APK + uses: actions/upload-artifact@v4 + with: + name: release-universal-apk-github + path: ./app/build/outputs/apk/github/release/app-github-universal-release.apk + + - name: Upload release GooglePlay arm64-v8a APK + uses: actions/upload-artifact@v4 + with: + name: release-arm64-v8a-apk-googleplay + path: ./app/build/outputs/apk/googleplay/release/app-googleplay-arm64-v8a-release.apk + + - name: Upload release GooglePlay armeabi-v7a APK + uses: actions/upload-artifact@v4 + with: + name: release-armeabi-v7a-apk-googleplay + path: ./app/build/outputs/apk/googleplay/release/app-googleplay-armeabi-v7a-release.apk + + - name: Upload release GooglePlay universal APK + uses: actions/upload-artifact@v4 + with: + name: release-universal-apk-googleplay + path: ./app/build/outputs/apk/googleplay/release/app-googleplay-universal-release.apk + + lint: + environment: Development + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'adopt' + + - name: Run linter + run: ./gradlew lint + + - uses: actions/upload-artifact@v4 + with: + name: lint-results + path: ./app/build/reports/*.html + + ktlint: + environment: Development + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'adopt' + + - name: Kotlin linter + run: ./gradlew ktlintCheck diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/SVG.kt b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/SVG.kt index 859d06c..99bc25d 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/SVG.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/SVG.kt @@ -128,9 +128,14 @@ class SVG { getAttributeValue("", Attributes.VIEW_BOX), ) } + PATH_TAG -> { pathCount += 1 - strokeColor = getAttributeValue("", Attributes.Path.STROKE) + strokeColor = + getAttributeValue( + "", + Attributes.Path.STROKE, + ) fill = getAttributeValue("", Attributes.Path.FILL) pathData = getAttributeValue("", Attributes.Path.DATA) } @@ -164,6 +169,7 @@ class SVG { }, ) } + SVGCommand.AbsLineTo.CODE -> { if (commandElements.size > 3) { strokeColor = commandElements[3] @@ -178,6 +184,7 @@ class SVG { }, ) } + SVGCommand.AbsQuadTo.CODE -> { if (commandElements.size > 5) { strokeColor = commandElements[5] @@ -192,6 +199,7 @@ class SVG { }, ) } + else -> {} } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt index 8fd12f3..3c74a99 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt @@ -34,7 +34,11 @@ class MemoPreferencesImpl prefEditor.putBoolean(CRASH_REPORT_ENABLE, enabled).apply() } - override fun getCrashReportEnabled(): Boolean = sharedPreferences.getBoolean(CRASH_REPORT_ENABLE, true) + override fun getCrashReportEnabled(): Boolean = + sharedPreferences.getBoolean( + CRASH_REPORT_ENABLE, + true, + ) override fun storageNotAvailable(): Boolean { return getPath().isEmpty() || !getNotesStorage().exists() diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt index 21f908c..29edc34 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt @@ -47,9 +47,17 @@ class GraphicNotesRepo private val displayMetrics by lazy { Resources.getSystem().displayMetrics } private val screenWidth by lazy { displayMetrics.widthPixels } private val screenHeight by lazy { displayMetrics.heightPixels - 150.dpToPx() } - private val thumbViewWidth by lazy { context.resources.getDimension(R.dimen.graphic_thumb_width) } + private val thumbViewWidth by lazy { + context.resources.getDimension( + R.dimen.graphic_thumb_width, + ) + } - private val thumbDirectory by lazy { context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) } + private val thumbDirectory by lazy { + context.getExternalFilesDir( + Environment.DIRECTORY_PICTURES, + ) + } override suspend fun init(root: String) { helper.init(root) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/NotesListAdapter.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/NotesListAdapter.kt index 9a6036f..c94edd4 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/NotesListAdapter.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/NotesListAdapter.kt @@ -35,7 +35,11 @@ import dev.arkbuilders.arkmemo.utils.visible class NotesListAdapter( private var notes: MutableList, - private val onPlayPauseClick: (path: String, pos: Int?, stopCallback: ((pos: Int) -> Unit)?) -> Unit, + private val onPlayPauseClick: ( + path: String, + pos: Int?, + stopCallback: ((pos: Int) -> Unit)?, + ) -> Unit, ) : RecyclerView.Adapter() { private lateinit var activity: MainActivity private var mActionMode = false @@ -66,7 +70,12 @@ class NotesListAdapter( parent: ViewGroup, viewType: Int, ): NoteViewHolder { - val binding = AdapterTextNoteBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val binding = + AdapterTextNoteBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) binding.root.clipToOutline = true return NoteViewHolder(binding.root) } @@ -178,13 +187,16 @@ class NotesListAdapter( is ArkMediaPlayerSideEffect.StartPlaying -> { showPlayingState(holder) } + is ArkMediaPlayerSideEffect.PausePlaying -> { showPlaybackIdleState(holder, isPaused = true) } + is ArkMediaPlayerSideEffect.StopPlaying -> { showPlaybackIdleState(holder) holder.tvPlayingPosition.gone() } + is ArkMediaPlayerSideEffect.ResumePlaying -> { showPlayingState(holder) } @@ -296,7 +308,10 @@ class NotesListAdapter( private val clickNoteToEditListener = View.OnClickListener { - val storageFolderExist = (activity.fragment as? NotesFragment)?.checkForStorageExistence() ?: true + val storageFolderExist = + ( + activity.fragment as? NotesFragment + )?.checkForStorageExistence() ?: true if (!storageFolderExist) { return@OnClickListener } @@ -308,11 +323,16 @@ class NotesListAdapter( } var tag = EditTextNotesFragment.TAG when (val selectedNote = notes[bindingAdapterPosition]) { - is TextNote -> activity.fragment = EditTextNotesFragment.newInstance(selectedNote) + is TextNote -> + activity.fragment = + EditTextNotesFragment + .newInstance(selectedNote) + is GraphicNote -> { activity.fragment = EditGraphicNotesFragment.newInstance(selectedNote) tag = EditGraphicNotesFragment.TAG } + is VoiceNote -> { activity.fragment = ArkRecorderFragment.newInstance(selectedNote) tag = ArkRecorderFragment.TAG diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt index 01d0098..ce0fe5a 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt @@ -20,7 +20,8 @@ import javax.inject.Inject @AndroidEntryPoint class FilePickerDialog : ArkFilePickerFragment() { - @Inject lateinit var memoPreferences: MemoPreferences + @Inject + lateinit var memoPreferences: MemoPreferences override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -67,7 +68,10 @@ class FilePickerDialog : ArkFilePickerFragment() { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { Environment.isExternalStorageManager() } else { - ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == + ContextCompat.checkSelfPermission( + activity, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + ) == PackageManager.PERMISSION_GRANTED } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/AboutFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/AboutFragment.kt index 7fd5ef6..d679ef2 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/AboutFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/AboutFragment.kt @@ -31,7 +31,11 @@ open class AboutFragment : Fragment(R.layout.fragment_about) { binding.toolbarCustom.tvRightActionText.gone() binding.toolbarCustom.ivRightActionIcon.gone() - binding.tvAppVersion.text = getString(R.string.setting_app_version, BuildConfig.VERSION_NAME) + binding.tvAppVersion.text = + getString( + R.string.setting_app_version, + BuildConfig.VERSION_NAME, + ) initSettingActions() } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt index 25372a0..026c6b1 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt @@ -154,7 +154,8 @@ class ArkRecorderFragment : BaseEditNoteFragment() { start: Int, count: Int, after: Int, - ) {} + ) { + } override fun onTextChanged( s: CharSequence?, @@ -271,6 +272,7 @@ class ArkRecorderFragment : BaseEditNoteFragment() { binding.layoutAudioView.root.gone() binding.layoutAudioRecord.tvRecordGuide.gone() } + is RecorderSideEffect.StopRecording -> { val recordIcon = ResourcesCompat.getDrawable( @@ -297,6 +299,7 @@ class ArkRecorderFragment : BaseEditNoteFragment() { getString(R.string.audio_record_guide_text_replace) binding.layoutAudioRecord.tvDuration.setText(R.string.ark_memo_duration_default) } + RecorderSideEffect.PauseRecording -> { val resumeIcon = ResourcesCompat.getDrawable( @@ -307,6 +310,7 @@ class ArkRecorderFragment : BaseEditNoteFragment() { ivPauseResume.setImageDrawable(resumeIcon) pauseOrResumeRecordingAnimation(false) } + RecorderSideEffect.ResumeRecording -> { showPauseIcon() pauseOrResumeRecordingAnimation(true) @@ -320,6 +324,7 @@ class ArkRecorderFragment : BaseEditNoteFragment() { binding.layoutAudioView.ivPlayAudio.setImageResource(R.drawable.ic_pause_circle) binding.layoutAudioView.animAudioPlaying.background = null } + ArkMediaPlayerSideEffect.StopPlaying -> { binding.layoutAudioView.ivPlayAudio.setImageResource(R.drawable.ic_play_circle) mediaPlayViewModel.getDurationString { duration -> @@ -328,11 +333,17 @@ class ArkRecorderFragment : BaseEditNoteFragment() { binding.layoutAudioView.tvPlayingPosition.gone() binding.layoutAudioView.animAudioPlaying.resetWave() binding.layoutAudioView.animAudioPlaying.invalidateWave(0) - binding.layoutAudioView.animAudioPlaying.background = ContextCompat.getDrawable(activity, R.drawable.audio_wave_thumb) + binding.layoutAudioView.animAudioPlaying.background = + ContextCompat.getDrawable( + activity, + R.drawable.audio_wave_thumb, + ) } + ArkMediaPlayerSideEffect.PausePlaying -> { binding.layoutAudioView.ivPlayAudio.setImageResource(R.drawable.ic_play_circle) } + ArkMediaPlayerSideEffect.ResumePlaying -> { binding.layoutAudioView.ivPlayAudio.setImageResource(R.drawable.ic_pause_circle) } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt index f5842a0..1aea731 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt @@ -105,7 +105,11 @@ abstract class BaseEditNoteFragment : BaseFragment() { "dd MMM yyyy', 'hh:mm aa", calendar, ).toString() - binding.tvLastModified.text = getString(R.string.note_last_modified_time, lastModifiedTime) + binding.tvLastModified.text = + getString( + R.string.note_last_modified_time, + lastModifiedTime, + ) } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt index d6d61d6..426550d 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt @@ -42,7 +42,9 @@ class EditTextNotesFragment : BaseEditNoteFragment() { clipBoardText.length + cursorPos } try { - newTextBuilder.append(noteContent.insertStringAtPosition(clipBoardText, cursorPos)) + newTextBuilder.append( + noteContent.insertStringAtPosition(clipBoardText, cursorPos), + ) } catch (e: IndexOutOfBoundsException) { Log.e(TAG, "pasteNoteClickListener exception: ${e.message}") newTextBuilder.append(noteContent).append(clipBoardText) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt index 4e4eb23..3743fe7 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt @@ -288,7 +288,8 @@ class NotesFragment : BaseFragment() { binding.pbLoading.visible() binding.groupSearchResultEmpty.gone() - if (!binding.edtSearch.isFocused && text.isNullOrEmpty()) return@addTextChangedListener + if (!binding.edtSearch.isFocused && text.isNullOrEmpty()) + return@addTextChangedListener notesViewModel.searchNote(keyword = text.toString()) { notes -> binding.pbLoading.gone() @@ -365,8 +366,13 @@ class NotesFragment : BaseFragment() { this.adapter = notesAdapter this.itemAnimator = object : DefaultItemAnimator() { - override fun canReuseUpdatedViewHolder(viewHolder: RecyclerView.ViewHolder): Boolean { - val isSwiping = (viewHolder as? NotesListAdapter.NoteViewHolder)?.isSwiping ?: false + override fun canReuseUpdatedViewHolder( + viewHolder: RecyclerView.ViewHolder, + ): Boolean { + val isSwiping = + ( + viewHolder as? NotesListAdapter.NoteViewHolder + )?.isSwiping ?: false return !isSwiping } } @@ -438,7 +444,11 @@ class NotesFragment : BaseFragment() { if (arkMediaPlayerViewModel.isPlaying()) { playingAudioPath?.let { arkMediaPlayerViewModel.onPlayOrPauseClick(it) - (notesAdapter?.getNotes()?.getOrNull(playingAudioPosition) as? VoiceNote)?.isPlaying = false + ( + notesAdapter?.getNotes()?.getOrNull( + playingAudioPosition, + ) as? VoiceNote + )?.isPlaying = false notesAdapter?.notifyItemChanged(playingAudioPosition) } } @@ -585,7 +595,11 @@ class NotesFragment : BaseFragment() { selectedCountForDelete, selectedCountForDelete, ), - message = resources.getQuantityString(R.plurals.delete_batch_note_message, selectedCountForDelete), + message = + resources.getQuantityString( + R.plurals.delete_batch_note_message, + selectedCountForDelete, + ), positiveText = R.string.action_delete, negativeText = R.string.ark_memo_cancel, isAlert = true, diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkMediaPlayerViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkMediaPlayerViewModel.kt index 53321c4..d977579 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkMediaPlayerViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkMediaPlayerViewModel.kt @@ -94,7 +94,9 @@ class ArkMediaPlayerViewModel viewModelScope.launch(Dispatchers.IO) { val intensity = computeFftMagnitude(fft) withContext(Dispatchers.Main) { - arkMediaPlayer.setMaxAmplitude((intensity * WaveView.MAX_AMPLITUDE).toInt()) + arkMediaPlayer.setMaxAmplitude( + (intensity * WaveView.MAX_AMPLITUDE).toInt(), + ) } } } @@ -169,7 +171,10 @@ class ArkMediaPlayerViewModel val duration = millisToString(arkMediaPlayer.duration().toLong()) progressJob = - viewModelScope.launchPeriodicAsync(repeatMillis = 100L, repeatCondition = isPlaying()) { + viewModelScope.launchPeriodicAsync( + repeatMillis = 100L, + repeatCondition = isPlaying(), + ) { val curPosInMillis = arkMediaPlayer.currentPosition() val curPos = curPosInMillis / 1000 diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/GraphicControlTextView.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/GraphicControlTextView.kt index bc3bb38..4ec0001 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/GraphicControlTextView.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/GraphicControlTextView.kt @@ -24,7 +24,11 @@ class GraphicControlTextView typedArray.getResourceId(R.styleable.GraphicControlTextView_gct_drawable, 0) val isSelected = typedArray.getBoolean(R.styleable.GraphicControlTextView_gct_selected, false) - iconTintColor = typedArray.getColor(R.styleable.GraphicControlTextView_gct_icon_tint, -1) + iconTintColor = + typedArray.getColor( + R.styleable.GraphicControlTextView_gct_icon_tint, + -1, + ) drawableResId.let { this.setCompoundDrawablesWithIntrinsicBounds(drawableResId, 0, 0, 0) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SettingTextView.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SettingTextView.kt index 77bfc3c..6406d4e 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SettingTextView.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SettingTextView.kt @@ -25,7 +25,11 @@ class SettingTextView val iconResId = typedArray.getResourceId(R.styleable.SettingTextView_stv_icon, 0) val enableSwitch = typedArray.getBoolean(R.styleable.SettingTextView_stv_switch_on, false) - val switchChecked = typedArray.getBoolean(R.styleable.SettingTextView_stv_switch_checked, false) + val switchChecked = + typedArray.getBoolean( + R.styleable.SettingTextView_stv_switch_checked, + false, + ) textResId?.let { binding.tvText.text = textResId } From a54b90cd4a5c8b68fdff9973a9cbb2763872348c Mon Sep 17 00:00:00 2001 From: Shubert Munthali Date: Fri, 1 Aug 2025 15:36:17 +0200 Subject: [PATCH 2/3] Fix lint --- .../arkmemo/ui/viewmodels/NotesViewModel.kt | 274 +++++++++--------- 1 file changed, 138 insertions(+), 136 deletions(-) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt index 1787b74..3ff21ed 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt @@ -29,171 +29,173 @@ import kotlin.io.path.pathString @HiltViewModel class NotesViewModel - @Inject - constructor( - @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, - private val textNotesRepo: NotesRepo, - private val graphicNotesRepo: NotesRepo, - private val voiceNotesRepo: NotesRepo, - ) : ViewModel() { - private val notes = MutableStateFlow(listOf()) - private val mSaveNoteResultLiveData = MutableLiveData() - private var searchJob: Job? = null - - @set:Inject - internal lateinit var memoPreferences: MemoPreferences - - fun init(extraBlock: () -> Unit) { - val root = memoPreferences.getPath() - val initJob = - viewModelScope.launch(iODispatcher) { - textNotesRepo.init(root) - graphicNotesRepo.init(root) - voiceNotesRepo.init(root) - } - viewModelScope.launch { - initJob.join() - extraBlock() - } - } +@Inject +constructor( + @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, + private val textNotesRepo: NotesRepo, + private val graphicNotesRepo: NotesRepo, + private val voiceNotesRepo: NotesRepo, +) : ViewModel() { + private val notes = MutableStateFlow(listOf()) + private val mSaveNoteResultLiveData = MutableLiveData() + private var searchJob: Job? = null - fun readAllNotes(onSuccess: (notes: List) -> Unit) { + @set:Inject + internal lateinit var memoPreferences: MemoPreferences + + fun init(extraBlock: () -> Unit) { + val root = memoPreferences.getPath() + val initJob = viewModelScope.launch(iODispatcher) { - notes.value = textNotesRepo.read() + graphicNotesRepo.read() + voiceNotesRepo.read() - notes.value.let { - withContext(Dispatchers.Main) { - notes.value = it.sortedByDescending { note -> note.resource?.modified } - onSuccess(notes.value) - } - } + textNotesRepo.init(root) + graphicNotesRepo.init(root) + voiceNotesRepo.init(root) } + viewModelScope.launch { + initJob.join() + extraBlock() } + } - fun searchNote( - keyword: String, - onSuccess: (notes: List) -> Unit, - ) { - searchJob?.cancel() - searchJob = - viewModelScope.launch(iODispatcher) { - // Add a delay to restart the search job if there are 2 consecutive search events - // triggered within 0.5 second window. - delay(500) - notes.collectLatest { - val filteredNotes = - it - .filter { note -> - note.title.contains(keyword, true) - } - // Keep the search result ordered chronologically - .sortedByDescending { note -> note.resource?.modified } - withContext(Dispatchers.Main) { - onSuccess(filteredNotes) - } - } + fun readAllNotes(onSuccess: (notes: List) -> Unit) { + viewModelScope.launch(iODispatcher) { + notes.value = textNotesRepo.read() + graphicNotesRepo.read() + voiceNotesRepo.read() + notes.value.let { + withContext(Dispatchers.Main) { + notes.value = it.sortedByDescending { note -> note.resource?.modified } + onSuccess(notes.value) } + } } + } - fun onSaveClick( - note: Note, - parentNote: Note? = null, - showProgress: (Boolean) -> Unit, - ) { - val noteResId = note.resource?.id + fun searchNote( + keyword: String, + onSuccess: (notes: List) -> Unit, + ) { + searchJob?.cancel() + searchJob = viewModelScope.launch(iODispatcher) { - withContext(Dispatchers.Main) { - showProgress(true) + // Add a delay to restart the search job if there are 2 consecutive search + // events triggered within 0.5 second window. + delay(500) + notes.collectLatest { + val filteredNotes = + it + .filter { note -> + note.title.contains(keyword, true) + } + // Keep the search result ordered chronologically + .sortedByDescending { note -> note.resource?.modified } + withContext(Dispatchers.Main) { + onSuccess(filteredNotes) + } } + } + } + + fun onSaveClick( + note: Note, + parentNote: Note? = null, + showProgress: (Boolean) -> Unit, + ) { + val noteResId = note.resource?.id + viewModelScope.launch(iODispatcher) { + withContext(Dispatchers.Main) { + showProgress(true) + } - fun handleResult(result: SaveNoteResult) { - if (result == SaveNoteResult.SUCCESS_NEW || - result == SaveNoteResult.SUCCESS_UPDATED - ) { - if (result == SaveNoteResult.SUCCESS_NEW) { - parentNote?.let { onDeleteConfirmed(listOf(parentNote)) {} } - } - add(note, noteResId) + fun handleResult(result: SaveNoteResult) { + if (result == SaveNoteResult.SUCCESS_NEW || + result == SaveNoteResult.SUCCESS_UPDATED + ) { + if (result == SaveNoteResult.SUCCESS_NEW) { + parentNote?.let { onDeleteConfirmed(listOf(parentNote)) {} } } - mSaveNoteResultLiveData.postValue(result) + add(note, noteResId) } - when (note) { - is TextNote -> { - textNotesRepo.save(note) { result -> - handleResult(result) - } - } - is GraphicNote -> { - graphicNotesRepo.save(note) { result -> - handleResult(result) - } - } - is VoiceNote -> { - voiceNotesRepo.save(note) { result -> - handleResult(result) - } + mSaveNoteResultLiveData.postValue(result) + } + when (note) { + is TextNote -> { + textNotesRepo.save(note) { result -> + handleResult(result) } } - withContext(Dispatchers.Main) { - showProgress(false) - } - } - } - fun onDeleteConfirmed( - notes: List, - onSuccess: () -> Unit, - ) { - viewModelScope.launch(iODispatcher) { - notes.forEach { note -> - when (note) { - is TextNote -> textNotesRepo.delete(note) - is GraphicNote -> graphicNotesRepo.delete(note) - is VoiceNote -> voiceNotesRepo.delete(note) + is GraphicNote -> { + graphicNotesRepo.save(note) { result -> + handleResult(result) } } - this@NotesViewModel.notes.value = - this@NotesViewModel.notes.value.toMutableList() - .apply { removeAll(notes) } - withContext(Dispatchers.Main) { - onSuccess.invoke() + + is VoiceNote -> { + voiceNotesRepo.save(note) { result -> + handleResult(result) + } } } + withContext(Dispatchers.Main) { + showProgress(false) + } } + } - private fun add( - note: Note, - parentResId: ResourceId? = null, - ) { - val notes = this.notes.value.toMutableList() - note.resource?.let { - notes.removeIf { it.resource?.id == parentResId ?: note.resource?.id } + fun onDeleteConfirmed( + notes: List, + onSuccess: () -> Unit, + ) { + viewModelScope.launch(iODispatcher) { + notes.forEach { note -> + when (note) { + is TextNote -> textNotesRepo.delete(note) + is GraphicNote -> graphicNotesRepo.delete(note) + is VoiceNote -> voiceNotesRepo.delete(note) + } } - if (note is VoiceNote) { - note.duration = extractDuration(note.path.pathString) + this@NotesViewModel.notes.value = + this@NotesViewModel.notes.value.toMutableList() + .apply { removeAll(notes) } + withContext(Dispatchers.Main) { + onSuccess.invoke() } - notes.add(note) - this.notes.value = notes } + } - private fun remove(note: Note) { - val notes = this.notes.value.toMutableList() - notes.remove(note) - this.notes.value = notes + private fun add( + note: Note, + parentResId: ResourceId? = null, + ) { + val notes = this.notes.value.toMutableList() + note.resource?.let { + notes.removeIf { it.resource?.id == parentResId ?: note.resource?.id } } - - fun getSaveNoteResultLiveData(): LiveData { - return mSaveNoteResultLiveData + if (note is VoiceNote) { + note.duration = extractDuration(note.path.pathString) } + notes.add(note) + this.notes.value = notes + } - fun storageFolderNotAvailable(): Boolean { - return memoPreferences.storageNotAvailable() - } + private fun remove(note: Note) { + val notes = this.notes.value.toMutableList() + notes.remove(note) + this.notes.value = notes + } - fun getStorageFolderPath(): String { - return memoPreferences.getPath() - } + fun getSaveNoteResultLiveData(): LiveData { + return mSaveNoteResultLiveData + } - fun setLastLaunchSuccess(success: Boolean) { - memoPreferences.setLastLaunchSuccess(success) - } + fun storageFolderNotAvailable(): Boolean { + return memoPreferences.storageNotAvailable() + } + + fun getStorageFolderPath(): String { + return memoPreferences.getPath() + } + + fun setLastLaunchSuccess(success: Boolean) { + memoPreferences.setLastLaunchSuccess(success) } +} From c0f2b5270bc46d6b0ad07b85c3fb2f4fde08f9b1 Mon Sep 17 00:00:00 2001 From: Shubert Munthali Date: Fri, 1 Aug 2025 15:51:12 +0200 Subject: [PATCH 3/3] Fix lint --- .../arkmemo/ui/viewmodels/NotesViewModel.kt | 283 +++++++++--------- 1 file changed, 144 insertions(+), 139 deletions(-) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt index 8e4a654..7e71d28 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt @@ -30,181 +30,186 @@ import kotlin.io.path.pathString @HiltViewModel class NotesViewModel -@Inject -constructor( - @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, - private val textNotesRepo: NotesRepo, - private val graphicNotesRepo: NotesRepo, - private val voiceNotesRepo: NotesRepo, -) : ViewModel() { - private val notes = MutableStateFlow(listOf()) - private val mSaveNoteResultLiveData = MutableLiveData() - private var searchJob: Job? = null - - @set:Inject - internal lateinit var memoPreferences: MemoPreferences - - companion object { + @Inject + constructor( + @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, + private val textNotesRepo: NotesRepo, + private val graphicNotesRepo: NotesRepo, + private val voiceNotesRepo: NotesRepo, + ) : ViewModel() { + private val notes = MutableStateFlow(listOf()) + private val mSaveNoteResultLiveData = MutableLiveData() + private var searchJob: Job? = null + + @set:Inject + internal lateinit var memoPreferences: MemoPreferences + + companion object { private const val TAG = "NotesViewModel" } fun init(extraBlock: () -> Unit) { Log.d(TAG, "init") - val root = memoPreferences.getPath() - val initJob = - viewModelScope.launch(iODispatcher) { - textNotesRepo.init(root) - graphicNotesRepo.init(root) - voiceNotesRepo.init(root) - } - viewModelScope.launch { - initJob.join() - extraBlock() - } - } - - fun readAllNotes(onSuccess: (notes: List) -> Unit) { - Log.d(TAG, "readAllNotes")viewModelScope.launch(iODispatcher) { - notes.value = textNotesRepo.read() + graphicNotesRepo.read() + voiceNotesRepo.read() - notes.value.let { - withContext(Dispatchers.Main) { - notes.value = it.sortedByDescending { note -> note.resource?.modified } - onSuccess(notes.value) + val root = memoPreferences.getPath() + val initJob = + viewModelScope.launch(iODispatcher) { + textNotesRepo.init(root) + graphicNotesRepo.init(root) + voiceNotesRepo.init(root) } + viewModelScope.launch { + initJob.join() + extraBlock() } } - } - fun searchNote( - keyword: String, - onSuccess: (notes: List) -> Unit, - ) { - Log.d(TAG, "searchNote")searchJob?.cancel() - searchJob = + fun readAllNotes(onSuccess: (notes: List) -> Unit) { + Log.d(TAG, "readAllNotes") viewModelScope.launch(iODispatcher) { - // Add a delay to restart the search job if there are 2 consecutive search - // events triggered within 0.5 second window. - delay(500) - notes.collectLatest { - val filteredNotes = - it - .filter { note -> - note.title.contains(keyword, true) - } - // Keep the search result ordered chronologically - .sortedByDescending { note -> note.resource?.modified } + notes.value = textNotesRepo.read() + graphicNotesRepo.read() + voiceNotesRepo.read() + notes.value.let { withContext(Dispatchers.Main) { - onSuccess(filteredNotes) + notes.value = it.sortedByDescending { note -> note.resource?.modified } + onSuccess(notes.value) } } } - } - - fun onSaveClick( - note: Note, - parentNote: Note? = null, - showProgress: (Boolean) -> Unit, - ) { - val noteResId = note.resource?.id - viewModelScope.launch(iODispatcher) { - withContext(Dispatchers.Main) { - showProgress(true) - } + } - fun handleResult(result: SaveNoteResult) { - Log.d(TAG, "handleResult: ${result.name}")if (result == SaveNoteResult.SUCCESS_NEW || - result == SaveNoteResult.SUCCESS_UPDATED - ) { - if (result == SaveNoteResult.SUCCESS_NEW) { - parentNote?.let { onDeleteConfirmed(listOf(parentNote)) {} } + fun searchNote( + keyword: String, + onSuccess: (notes: List) -> Unit, + ) { + Log.d(TAG, "searchNote") + searchJob?.cancel() + searchJob = + viewModelScope.launch(iODispatcher) { + // Add a delay to restart the search job if there are 2 consecutive search + // events triggered within 0.5 second window. + delay(500) + notes.collectLatest { + val filteredNotes = + it + .filter { note -> + note.title.contains(keyword, true) + } + // Keep the search result ordered chronologically + .sortedByDescending { note -> note.resource?.modified } + withContext(Dispatchers.Main) { + onSuccess(filteredNotes) + } } - add(note, noteResId) } - mSaveNoteResultLiveData.postValue(result) - } - when (note) { - is TextNote -> { - textNotesRepo.save(note) { result -> - handleResult(result) - } + } + + fun onSaveClick( + note: Note, + parentNote: Note? = null, + showProgress: (Boolean) -> Unit, + ) { + val noteResId = note.resource?.id + viewModelScope.launch(iODispatcher) { + withContext(Dispatchers.Main) { + showProgress(true) } - is GraphicNote -> { - graphicNotesRepo.save(note) { result -> - handleResult(result) + fun handleResult(result: SaveNoteResult) { + Log.d(TAG, "handleResult: ${result.name}") + if (result == SaveNoteResult.SUCCESS_NEW || + result == SaveNoteResult.SUCCESS_UPDATED + ) { + if (result == SaveNoteResult.SUCCESS_NEW) { + parentNote?.let { onDeleteConfirmed(listOf(parentNote)) {} } + } + add(note, noteResId) } + mSaveNoteResultLiveData.postValue(result) } + when (note) { + is TextNote -> { + textNotesRepo.save(note) { result -> + handleResult(result) + } + } + + is GraphicNote -> { + graphicNotesRepo.save(note) { result -> + handleResult(result) + } + } - is VoiceNote -> { - voiceNotesRepo.save(note) { result -> - handleResult(result) + is VoiceNote -> { + voiceNotesRepo.save(note) { result -> + handleResult(result) + } } } - } - withContext(Dispatchers.Main) { - showProgress(false) + withContext(Dispatchers.Main) { + showProgress(false) + } } } - } - fun onDeleteConfirmed( - notes: List, - onSuccess: () -> Unit, - ) { - viewModelScope.launch(iODispatcher) { - notes.forEach { note -> - when (note) { - is TextNote -> textNotesRepo.delete(note) - is GraphicNote -> graphicNotesRepo.delete(note) - is VoiceNote -> voiceNotesRepo.delete(note) + fun onDeleteConfirmed( + notes: List, + onSuccess: () -> Unit, + ) { + viewModelScope.launch(iODispatcher) { + notes.forEach { note -> + when (note) { + is TextNote -> textNotesRepo.delete(note) + is GraphicNote -> graphicNotesRepo.delete(note) + is VoiceNote -> voiceNotesRepo.delete(note) + } + } + this@NotesViewModel.notes.value = + this@NotesViewModel.notes.value.toMutableList() + .apply { removeAll(notes) } + withContext(Dispatchers.Main) { + onSuccess.invoke() } - } - this@NotesViewModel.notes.value = - this@NotesViewModel.notes.value.toMutableList() - .apply { removeAll(notes) } - withContext(Dispatchers.Main) { - onSuccess.invoke() } } - } - private fun add( - note: Note, - parentResId: ResourceId? = null, - ) { - Log.d( + private fun add( + note: Note, + parentResId: ResourceId? = null, + ) { + Log.d( TAG, - "add note with title: ${note.title} resId: ${note.resource?.id} resName: ${note.resource?.name}", - )val notes = this.notes.value.toMutableList() - note.resource?.let { - notes.removeIf { it.resource?.id == (parentResId ?: note.resource?.id) } - } - if (note is VoiceNote) { - note.duration = extractDuration(note.path.pathString) + "add note with title: ${note.title} resId: ${note.resource?.id} " + + "resName: ${note.resource?.name}", + ) + val notes = this.notes.value.toMutableList() + note.resource?.let { + notes.removeIf { it.resource?.id == (parentResId ?: note.resource?.id) } + } + if (note is VoiceNote) { + note.duration = extractDuration(note.path.pathString) + } + notes.add(note) + this.notes.value = notes } - notes.add(note) - this.notes.value = notes - } - private fun remove(note: Note) { - val notes = this.notes.value.toMutableList() - notes.remove(note) - this.notes.value = notes - } + private fun remove(note: Note) { + val notes = this.notes.value.toMutableList() + notes.remove(note) + this.notes.value = notes + } - fun getSaveNoteResultLiveData(): LiveData { - return mSaveNoteResultLiveData - } + fun getSaveNoteResultLiveData(): LiveData { + return mSaveNoteResultLiveData + } - fun storageFolderNotAvailable(): Boolean { - return memoPreferences.storageNotAvailable() - } + fun storageFolderNotAvailable(): Boolean { + return memoPreferences.storageNotAvailable() + } - fun getStorageFolderPath(): String { - return memoPreferences.getPath() - } + fun getStorageFolderPath(): String { + return memoPreferences.getPath() + } - fun setLastLaunchSuccess(success: Boolean) { - memoPreferences.setLastLaunchSuccess(success) + fun setLastLaunchSuccess(success: Boolean) { + memoPreferences.setLastLaunchSuccess(success) + } } -}