From 26fc90cc51d6a9c2b070e3aea8dd69bf9cf831b9 Mon Sep 17 00:00:00 2001 From: tuancoltech Date: Sat, 16 Nov 2024 16:24:32 +0700 Subject: [PATCH] Handle crash upon app launch when storage folder is removed externally --- .../arkbuilders/arkmemo/models/LoadError.kt | 5 +++ .../arkmemo/preferences/MemoPreferences.kt | 2 + .../preferences/MemoPreferencesImpl.kt | 5 +++ .../arkmemo/ui/activities/MainActivity.kt | 33 ++++++++++++++- .../arkmemo/ui/adapters/NotesListAdapter.kt | 6 +++ .../arkmemo/ui/dialogs/FilePickerDialog.kt | 6 +-- .../arkmemo/ui/fragments/NotesFragment.kt | 42 +++++++++++++++++++ .../arkmemo/ui/viewmodels/NotesViewModel.kt | 12 ++++++ app/src/main/res/values/strings.xml | 4 ++ 9 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/dev/arkbuilders/arkmemo/models/LoadError.kt diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/models/LoadError.kt b/app/src/main/java/dev/arkbuilders/arkmemo/models/LoadError.kt new file mode 100644 index 0000000..194053b --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/arkmemo/models/LoadError.kt @@ -0,0 +1,5 @@ +package dev.arkbuilders.arkmemo.models + +sealed interface LoadError + +data class RootNotFound(val rootPath: String) : LoadError diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt index 36b4dd8..da9238e 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt @@ -12,4 +12,6 @@ interface MemoPreferences { fun storeCrashReportEnabled(enabled: Boolean) fun getCrashReportEnabled(): Boolean + + fun storageNotAvailable(): Boolean } 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 253dd2b..4472a7f 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt @@ -7,6 +7,7 @@ import dev.arkbuilders.arkmemo.utils.CRASH_REPORT_ENABLE import java.nio.file.Path import javax.inject.Inject import kotlin.io.path.Path +import kotlin.io.path.exists private const val NAME = "memo_prefs" private const val CURRENT_NOTES_PATH = "current_notes_path" @@ -33,4 +34,8 @@ class MemoPreferencesImpl } 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/ui/activities/MainActivity.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt index f8d3852..1491bd0 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt @@ -15,13 +15,16 @@ import dagger.hilt.android.AndroidEntryPoint import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.contracts.PermissionContract import dev.arkbuilders.arkmemo.databinding.ActivityMainBinding +import dev.arkbuilders.arkmemo.models.RootNotFound import dev.arkbuilders.arkmemo.preferences.MemoPreferences +import dev.arkbuilders.arkmemo.ui.dialogs.CommonActionDialog import dev.arkbuilders.arkmemo.ui.dialogs.FilePickerDialog import dev.arkbuilders.arkmemo.ui.fragments.BaseFragment import dev.arkbuilders.arkmemo.ui.fragments.EditTextNotesFragment import dev.arkbuilders.arkmemo.ui.fragments.NotesFragment import dev.arkbuilders.components.filepicker.onArkPathPicked import javax.inject.Inject +import kotlin.io.path.exists @AndroidEntryPoint class MainActivity : AppCompatActivity(R.layout.activity_main) { @@ -92,8 +95,13 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { } } - if (memoPreferences.getPath().isEmpty()) { - FilePickerDialog.show(this, supportFragmentManager) + val storageFolderExisting = memoPreferences.getNotesStorage().exists() + if (memoPreferences.storageNotAvailable()) { + if (!storageFolderExisting) { + showNoNoteStorageDialog(RootNotFound(rootPath = memoPreferences.getPath())) + } else { + FilePickerDialog.show(this, supportFragmentManager) + } supportFragmentManager.onArkPathPicked(this) { memoPreferences.storePath(it.toString()) @@ -104,6 +112,27 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { } } + private fun showNoNoteStorageDialog(error: RootNotFound) { + val loadFailDialog = + CommonActionDialog( + title = getString(R.string.error_load_notes_failed_title), + message = getString(R.string.error_load_notes_failed_description, error.rootPath), + positiveText = R.string.error_load_notes_failed_positive_action, + negativeText = R.string.error_load_notes_failed_negative_action, + isAlert = false, + onPositiveClick = { + FilePickerDialog.show(this, supportFragmentManager) + }, + onNegativeClicked = { + finish() + }, + onCloseClicked = { + finish() + }, + ) + loadFailDialog.show(supportFragmentManager, CommonActionDialog.TAG) + } + override fun onSaveInstanceState(outState: Bundle) { outState.putString(CURRENT_FRAGMENT_TAG, fragment.tag) super.onSaveInstanceState(outState) 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 02439ef..9a6036f 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 @@ -23,6 +23,7 @@ import dev.arkbuilders.arkmemo.ui.activities.MainActivity import dev.arkbuilders.arkmemo.ui.fragments.ArkRecorderFragment import dev.arkbuilders.arkmemo.ui.fragments.EditGraphicNotesFragment import dev.arkbuilders.arkmemo.ui.fragments.EditTextNotesFragment +import dev.arkbuilders.arkmemo.ui.fragments.NotesFragment import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerSideEffect import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerState import dev.arkbuilders.arkmemo.utils.getAutoTitle @@ -295,6 +296,11 @@ class NotesListAdapter( private val clickNoteToEditListener = View.OnClickListener { + val storageFolderExist = (activity.fragment as? NotesFragment)?.checkForStorageExistence() ?: true + if (!storageFolderExist) { + return@OnClickListener + } + if (mActionMode) { checkedByItemClick = true binding.cbDelete.toggle() 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 a0c5f8a..eb65bc2 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 @@ -24,20 +24,18 @@ class FilePickerDialog : ArkFilePickerFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (storageNotAvailable()) { + if (memoPreferences.storageNotAvailable()) { isCancelable = false } } override fun dismiss() { super.dismiss() - if (storageNotAvailable()) { + if (memoPreferences.storageNotAvailable()) { activity?.finish() } } - private fun storageNotAvailable(): Boolean = memoPreferences.getPath().isEmpty() - companion object { private const val TAG = "file_picker" private lateinit var fragmentManager: FragmentManager 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 c69c1d9..3b98ab1 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 @@ -20,10 +20,12 @@ import dagger.hilt.android.AndroidEntryPoint import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.databinding.FragmentHomeBinding import dev.arkbuilders.arkmemo.models.Note +import dev.arkbuilders.arkmemo.models.RootNotFound import dev.arkbuilders.arkmemo.models.VoiceNote import dev.arkbuilders.arkmemo.ui.activities.MainActivity import dev.arkbuilders.arkmemo.ui.adapters.NotesListAdapter import dev.arkbuilders.arkmemo.ui.dialogs.CommonActionDialog +import dev.arkbuilders.arkmemo.ui.dialogs.FilePickerDialog import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerSideEffect import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerViewModel import dev.arkbuilders.arkmemo.ui.viewmodels.NotesViewModel @@ -62,6 +64,10 @@ class NotesFragment : BaseFragment() { private val pasteNoteClickListener = View.OnClickListener { + if (!checkForStorageExistence()) { + return@OnClickListener + } + requireContext().getTextFromClipBoard(view) { clipBoardText -> if (clipBoardText != null) { activity.fragment = EditTextNotesFragment.newInstance(clipBoardText) @@ -151,6 +157,9 @@ class NotesFragment : BaseFragment() { activity.title = getString(R.string.app_name) activity.supportActionBar?.setDisplayHomeAsUpEnabled(false) binding.ivSettings.setOnClickListener { + if (!checkForStorageExistence()) { + return@setOnClickListener + } activity.fragment = SettingsFragment() activity.replaceFragment(activity.fragment, SettingsFragment::class.java.name) } @@ -419,6 +428,10 @@ class NotesFragment : BaseFragment() { binding.groupFabActions.gone() showingFloatingButtons = false } else { + if (!checkForStorageExistence()) { + return + } + binding.fabNewAction.extend() binding.fabNewAction.icon = ContextCompat.getDrawable(activity, R.drawable.ic_close) binding.fabNewAction.backgroundTintList = @@ -539,6 +552,35 @@ class NotesFragment : BaseFragment() { } } + private fun showNoNoteStorageDialog(error: RootNotFound) { + val loadFailDialog = + CommonActionDialog( + title = getString(R.string.error_load_notes_failed_title), + message = getString(R.string.error_load_notes_failed_description, error.rootPath), + positiveText = R.string.error_load_notes_failed_positive_action, + negativeText = R.string.error_load_notes_failed_negative_action, + isAlert = false, + onPositiveClick = { + FilePickerDialog.show(activity, fragmentManager = parentFragmentManager) + }, + onNegativeClicked = { + activity.finish() + }, + onCloseClicked = { + activity.finish() + }, + ) + loadFailDialog.show(parentFragmentManager, CommonActionDialog.TAG) + } + + fun checkForStorageExistence(): Boolean { + if (notesViewModel.storageFolderNotAvailable()) { + showNoNoteStorageDialog(RootNotFound(rootPath = notesViewModel.getStorageFolderPath())) + return false + } + return true + } + companion object { const val TAG = "NotesFragment" } 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 57374c4..e3cf543 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 @@ -12,6 +12,7 @@ import dev.arkbuilders.arkmemo.models.Note import dev.arkbuilders.arkmemo.models.SaveNoteResult import dev.arkbuilders.arkmemo.models.TextNote import dev.arkbuilders.arkmemo.models.VoiceNote +import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.utils.extractDuration import kotlinx.coroutines.CoroutineDispatcher @@ -39,6 +40,9 @@ class NotesViewModel private val mSaveNoteResultLiveData = MutableLiveData() private var searchJob: Job? = null + @Inject + lateinit var memoPreferences: MemoPreferences + fun init(extraBlock: () -> Unit) { val initJob = viewModelScope.launch(iODispatcher) { @@ -179,4 +183,12 @@ class NotesViewModel fun getSaveNoteResultLiveData(): LiveData { return mSaveNoteResultLiveData } + + fun storageFolderNotAvailable(): Boolean { + return memoPreferences.storageNotAvailable() + } + + fun getStorageFolderPath(): String { + return memoPreferences.getPath() + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e68d460..e4fb86c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -117,5 +117,9 @@ Select All Deselect All + Cannot find notes + The folder %s with notes data cannot be located.\nPlease select a new folder. + Select + Leave \ No newline at end of file