From bfd9a9661f2defb897967c0ee8752bb3243f022d Mon Sep 17 00:00:00 2001 From: tuancoltech Date: Mon, 2 Dec 2024 13:59:03 +0700 Subject: [PATCH 1/3] [ux] Save folder choice only if it loads successfully --- .../arkmemo/di/RepositoryModule.kt | 21 ++++++++++++------- .../arkmemo/repo/NotesRepoHelper.kt | 19 ++++++++++++++--- .../arkmemo/ui/viewmodels/NotesViewModel.kt | 13 +++++++++++- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/di/RepositoryModule.kt b/app/src/main/java/dev/arkbuilders/arkmemo/di/RepositoryModule.kt index cdffa14c..9d0dd312 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/di/RepositoryModule.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/di/RepositoryModule.kt @@ -15,6 +15,9 @@ import dev.arkbuilders.arkmemo.repo.NotesRepoHelper import dev.arkbuilders.arkmemo.repo.graphics.GraphicNotesRepo import dev.arkbuilders.arkmemo.repo.text.TextNotesRepo import dev.arkbuilders.arkmemo.repo.voices.VoiceNotesRepo +import kotlinx.coroutines.CoroutineDispatcher +import javax.inject.Named +import javax.inject.Singleton @InstallIn(SingletonComponent::class) @Module @@ -27,12 +30,16 @@ abstract class RepositoryModule { @Binds abstract fun bindVoiceNotesRepo(impl: VoiceNotesRepo): NotesRepo +} - companion object { - @Provides - fun provideNotesRepoHelper( - memoPreferences: MemoPreferences, - propertiesStorageRepo: PropertiesStorageRepo, - ) = NotesRepoHelper(memoPreferences, propertiesStorageRepo) - } +@InstallIn(SingletonComponent::class) +@Module +object RepoHelperModule { + @Singleton + @Provides + fun provideNotesRepoHelper( + memoPreferences: MemoPreferences, + propertiesStorageRepo: PropertiesStorageRepo, + @Named(IO_DISPATCHER) coroutineDispatcher: CoroutineDispatcher, + ) = NotesRepoHelper(memoPreferences, propertiesStorageRepo, coroutineDispatcher) } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt index 63d4c426..d457f780 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt @@ -7,13 +7,18 @@ import dev.arkbuilders.arklib.data.index.RootIndex import dev.arkbuilders.arklib.user.properties.Properties import dev.arkbuilders.arklib.user.properties.PropertiesStorage import dev.arkbuilders.arklib.user.properties.PropertiesStorageRepo +import dev.arkbuilders.arkmemo.di.IO_DISPATCHER import dev.arkbuilders.arkmemo.models.Note import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.utils.isEqual +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.withContext import java.nio.file.Path import javax.inject.Inject +import javax.inject.Named import kotlin.NullPointerException import kotlin.io.path.deleteIfExists import kotlin.io.path.extension @@ -26,13 +31,21 @@ class NotesRepoHelper constructor( private val memoPreferences: MemoPreferences, private val propertiesStorageRepo: PropertiesStorageRepo, + @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, ) { - private lateinit var root: Path + private val root by lazy { + memoPreferences.getNotesStorage() + } + private lateinit var propertiesStorage: PropertiesStorage + private val lazyPropertiesStorage by lazy { + CoroutineScope(iODispatcher).async { + propertiesStorageRepo.provide(RootIndex.provide(root)) + } + } suspend fun init() { - root = memoPreferences.getNotesStorage() - propertiesStorage = propertiesStorageRepo.provide(RootIndex.provide(root)) + propertiesStorage = lazyPropertiesStorage.await() } suspend fun persistNoteProperties( 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 e3cf5436..5c73878e 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 @@ -16,6 +16,7 @@ import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.utils.extractDuration import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -43,9 +44,19 @@ class NotesViewModel @Inject lateinit var memoPreferences: MemoPreferences + private val initExceptionHandler by lazy { + CoroutineExceptionHandler { _, throwable -> + throwable.printStackTrace() + + // If the initialization fails with an exception for current storage path, + // clear the path for users to be able to re-select it next time. + memoPreferences.storePath("") + } + } + fun init(extraBlock: () -> Unit) { val initJob = - viewModelScope.launch(iODispatcher) { + viewModelScope.launch(iODispatcher + initExceptionHandler) { textNotesRepo.init() graphicNotesRepo.init() voiceNotesRepo.init() From 5e54af1a2b3f8953e8ba8977c0c384d38ef8c6ac Mon Sep 17 00:00:00 2001 From: tuancoltech Date: Sun, 8 Dec 2024 12:20:40 +0700 Subject: [PATCH 2/3] * Clear storage path before initiating the Root storage folder, and store it once load is successfully * Remove redundant initializations of NotesRepoHelper across different note type repositories --- .../dev/arkbuilders/arkmemo/repo/NotesRepo.kt | 2 +- .../arkmemo/repo/NotesRepoHelper.kt | 12 +-- .../arkmemo/repo/graphics/GraphicNotesRepo.kt | 9 +-- .../arkmemo/repo/text/TextNotesRepo.kt | 9 +-- .../arkmemo/repo/voices/VoiceNotesRepo.kt | 9 +-- .../arkmemo/ui/activities/MainActivity.kt | 73 +++++++++++-------- .../ui/fragments/ArkRecorderFragment.kt | 1 - .../ui/fragments/EditGraphicNotesFragment.kt | 1 - .../ui/fragments/EditTextNotesFragment.kt | 5 +- .../arkmemo/ui/fragments/NotesFragment.kt | 5 +- .../arkmemo/ui/viewmodels/NotesViewModel.kt | 32 ++++---- 11 files changed, 81 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt index ab92ae34..2f3b60d1 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt @@ -3,7 +3,7 @@ package dev.arkbuilders.arkmemo.repo import dev.arkbuilders.arkmemo.models.SaveNoteResult interface NotesRepo { - suspend fun init() + suspend fun init(root: String) suspend fun save( note: Note, diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt index d457f780..d4018db0 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt @@ -20,6 +20,7 @@ import java.nio.file.Path import javax.inject.Inject import javax.inject.Named import kotlin.NullPointerException +import kotlin.io.path.Path import kotlin.io.path.deleteIfExists import kotlin.io.path.extension import kotlin.io.path.getLastModifiedTime @@ -33,18 +34,19 @@ class NotesRepoHelper private val propertiesStorageRepo: PropertiesStorageRepo, @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, ) { - private val root by lazy { - memoPreferences.getNotesStorage() - } + lateinit var root: Path private lateinit var propertiesStorage: PropertiesStorage private val lazyPropertiesStorage by lazy { CoroutineScope(iODispatcher).async { - propertiesStorageRepo.provide(RootIndex.provide(root)) + val propertyStorage = propertiesStorageRepo.provide(RootIndex.provide(root)) + memoPreferences.storePath(root.toString()) + propertyStorage } } - suspend fun init() { + suspend fun init(root: String) { + this.root = Path(root) propertiesStorage = lazyPropertiesStorage.await() } 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 055773da..21f908cd 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 @@ -16,7 +16,6 @@ import dev.arkbuilders.arkmemo.graphics.ColorCode import dev.arkbuilders.arkmemo.graphics.SVG import dev.arkbuilders.arkmemo.models.GraphicNote import dev.arkbuilders.arkmemo.models.SaveNoteResult -import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.repo.NotesRepoHelper import dev.arkbuilders.arkmemo.utils.dpToPx @@ -39,12 +38,11 @@ import kotlin.io.path.name class GraphicNotesRepo @Inject constructor( - private val memoPreferences: MemoPreferences, @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, private val helper: NotesRepoHelper, @ApplicationContext private val context: Context, ) : NotesRepo { - private lateinit var root: Path + private val root: Path by lazy { helper.root } private val displayMetrics by lazy { Resources.getSystem().displayMetrics } private val screenWidth by lazy { displayMetrics.widthPixels } @@ -53,9 +51,8 @@ class GraphicNotesRepo private val thumbDirectory by lazy { context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) } - override suspend fun init() { - helper.init() - root = memoPreferences.getNotesStorage() + override suspend fun init(root: String) { + helper.init(root) } override suspend fun save( diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt index 93521be0..67ba6b80 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt @@ -6,7 +6,6 @@ import dev.arkbuilders.arklib.data.index.Resource import dev.arkbuilders.arkmemo.di.IO_DISPATCHER import dev.arkbuilders.arkmemo.models.SaveNoteResult import dev.arkbuilders.arkmemo.models.TextNote -import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.repo.NotesRepoHelper import dev.arkbuilders.arkmemo.utils.listFiles @@ -27,16 +26,14 @@ import kotlin.io.path.writeLines class TextNotesRepo @Inject constructor( - private val memoPreferences: MemoPreferences, @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, private val helper: NotesRepoHelper, ) : NotesRepo { - private lateinit var root: Path + private val root: Path by lazy { helper.root } - override suspend fun init() { - root = memoPreferences.getNotesStorage() - helper.init() + override suspend fun init(root: String) { + helper.init(root) } override suspend fun save( diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt index e83a4e30..1f7de8aa 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt @@ -6,7 +6,6 @@ import dev.arkbuilders.arklib.data.index.Resource import dev.arkbuilders.arkmemo.di.IO_DISPATCHER import dev.arkbuilders.arkmemo.models.SaveNoteResult import dev.arkbuilders.arkmemo.models.VoiceNote -import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.repo.NotesRepoHelper import dev.arkbuilders.arkmemo.utils.extractDuration @@ -26,15 +25,13 @@ import kotlin.io.path.pathString class VoiceNotesRepo @Inject constructor( - private val memoPreferences: MemoPreferences, @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, private val helper: NotesRepoHelper, ) : NotesRepo { - private lateinit var root: Path + private val root: Path by lazy { helper.root } - override suspend fun init() { - root = memoPreferences.getNotesStorage() - helper.init() + override suspend fun init(root: String) { + helper.init(root) } override suspend fun read(): List = 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 1491bd0b..338581f7 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 @@ -67,34 +67,6 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { onBackPressedDispatcher.onBackPressed() } - fun showFragment() { - val textDataFromIntent = intent?.getStringExtra(Intent.EXTRA_TEXT) - if (textDataFromIntent != null) { - fragment = EditTextNotesFragment.newInstance(textDataFromIntent) - supportFragmentManager.beginTransaction().apply { - replace(fragContainer, fragment, EditTextNotesFragment.TAG) - commit() - } - } else { - if (savedInstanceState == null) { - supportFragmentManager.beginTransaction().apply { - add(fragContainer, fragment, NotesFragment.TAG) - commit() - } - } else { - supportFragmentManager.apply { - val tag = savedInstanceState.getString(CURRENT_FRAGMENT_TAG) - findFragmentByTag(tag)?.let { - fragment = it - if (!fragment.isInLayout) { - resumeFragment(fragment) - } - } - } - } - } - } - val storageFolderExisting = memoPreferences.getNotesStorage().exists() if (memoPreferences.storageNotAvailable()) { if (!storageFolderExisting) { @@ -104,11 +76,49 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { } supportFragmentManager.onArkPathPicked(this) { - memoPreferences.storePath(it.toString()) - showFragment() + showFragment(savedInstanceState, it.toString()) + } + } else { + showFragment(savedInstanceState, memoPreferences.getPath()) + } + } + + private fun showFragment( + savedInstanceState: Bundle?, + storagePath: String, + ) { + val textDataFromIntent = intent?.getStringExtra(Intent.EXTRA_TEXT) + if (textDataFromIntent != null) { + fragment = EditTextNotesFragment.newInstance(textDataFromIntent) + fragment.arguments = + Bundle().apply { + putString(BUNDLE_KEY_STORAGE_PATH, storagePath) + } + supportFragmentManager.beginTransaction().apply { + replace(fragContainer, fragment, EditTextNotesFragment.TAG) + commit() } } else { - showFragment() + fragment.arguments = + Bundle().apply { + putString(BUNDLE_KEY_STORAGE_PATH, storagePath) + } + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction().apply { + add(fragContainer, fragment, NotesFragment.TAG) + commit() + } + } else { + supportFragmentManager.apply { + val tag = savedInstanceState.getString(CURRENT_FRAGMENT_TAG) + findFragmentByTag(tag)?.let { + fragment = it + if (!fragment.isInLayout) { + resumeFragment(fragment) + } + } + } + } } } @@ -166,6 +176,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { companion object { private const val CURRENT_FRAGMENT_TAG = "current fragment tag" + const val BUNDLE_KEY_STORAGE_PATH = "bundle_key_storage_path" } } 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 641d4874..25372a07 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 @@ -117,7 +117,6 @@ class ArkRecorderFragment : BaseEditNoteFragment() { } private fun observeDataStates() { - notesViewModel.init {} observeRecordingState() observeSaveResult(notesViewModel.getSaveNoteResultLiveData()) } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt index 45d22798..394b891b 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt @@ -66,7 +66,6 @@ class EditGraphicNotesFragment : BaseEditNoteFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - notesViewModel.init {} arguments?.getParcelableCompat(GRAPHICAL_NOTE_KEY, GraphicNote::class.java)?.let { note = it graphicNotesViewModel.onNoteOpened(note) 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 fba1b95f..17e4aa1c 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 @@ -15,6 +15,7 @@ import dagger.hilt.android.AndroidEntryPoint import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.models.Note import dev.arkbuilders.arkmemo.models.TextNote +import dev.arkbuilders.arkmemo.ui.activities.MainActivity.Companion.BUNDLE_KEY_STORAGE_PATH import dev.arkbuilders.arkmemo.utils.getParcelableCompat import dev.arkbuilders.arkmemo.utils.getTextFromClipBoard import dev.arkbuilders.arkmemo.utils.gone @@ -69,13 +70,15 @@ class EditTextNotesFragment : BaseEditNoteFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - notesViewModel.init {} if (arguments != null) { requireArguments().getParcelableCompat(NOTE_KEY, TextNote::class.java)?.let { note = it } noteStr = requireArguments().getString(NOTE_STRING_KEY) + arguments?.getString(BUNDLE_KEY_STORAGE_PATH)?.let { + notesViewModel.init(it) {} + } } } 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 3b98ab16..946661a2 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 @@ -168,10 +168,13 @@ class NotesFragment : BaseFragment() { initEmptyStateViews() binding.pbLoading.visible() + val root = arguments?.getString(MainActivity.BUNDLE_KEY_STORAGE_PATH) ?: "" notesViewModel.apply { - init { + storePath("") + init(root) { readAllNotes { onNotesLoaded(it) + storePath(root) } } } 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 5c73878e..b26f5a7f 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 @@ -16,7 +16,6 @@ import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.utils.extractDuration import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -41,25 +40,18 @@ class NotesViewModel private val mSaveNoteResultLiveData = MutableLiveData() private var searchJob: Job? = null - @Inject - lateinit var memoPreferences: MemoPreferences + @set:Inject + internal lateinit var memoPreferences: MemoPreferences - private val initExceptionHandler by lazy { - CoroutineExceptionHandler { _, throwable -> - throwable.printStackTrace() - - // If the initialization fails with an exception for current storage path, - // clear the path for users to be able to re-select it next time. - memoPreferences.storePath("") - } - } - - fun init(extraBlock: () -> Unit) { + fun init( + root: String, + extraBlock: () -> Unit, + ) { val initJob = - viewModelScope.launch(iODispatcher + initExceptionHandler) { - textNotesRepo.init() - graphicNotesRepo.init() - voiceNotesRepo.init() + viewModelScope.launch(iODispatcher) { + textNotesRepo.init(root) + graphicNotesRepo.init(root) + voiceNotesRepo.init(root) } viewModelScope.launch { initJob.join() @@ -202,4 +194,8 @@ class NotesViewModel fun getStorageFolderPath(): String { return memoPreferences.getPath() } + + fun storePath(path: String) { + memoPreferences.storePath(path) + } } From 24239f7640be31ca50fbca17bafd2668309e8af1 Mon Sep 17 00:00:00 2001 From: tuancoltech Date: Fri, 13 Dec 2024 14:09:39 +0700 Subject: [PATCH 3/3] Show dialog for Retry/Select root storage path again in case of load crash --- .../arkmemo/di/RepositoryModule.kt | 4 +- .../arkmemo/preferences/MemoPreferences.kt | 4 ++ .../preferences/MemoPreferencesImpl.kt | 9 +++ .../arkmemo/repo/NotesRepoHelper.kt | 3 - .../arkmemo/ui/activities/MainActivity.kt | 61 +++++++++++++------ .../arkmemo/ui/dialogs/CommonActionDialog.kt | 14 +++++ .../ui/fragments/EditTextNotesFragment.kt | 5 +- .../arkmemo/ui/fragments/NotesFragment.kt | 7 +-- .../arkmemo/ui/viewmodels/NotesViewModel.kt | 10 ++- .../main/res/layout/dialog_common_action.xml | 17 +++++- app/src/main/res/values/strings.xml | 3 + 11 files changed, 98 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/di/RepositoryModule.kt b/app/src/main/java/dev/arkbuilders/arkmemo/di/RepositoryModule.kt index 9d0dd312..b2adff2b 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/di/RepositoryModule.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/di/RepositoryModule.kt @@ -9,7 +9,6 @@ import dev.arkbuilders.arklib.user.properties.PropertiesStorageRepo import dev.arkbuilders.arkmemo.models.GraphicNote 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.repo.NotesRepoHelper import dev.arkbuilders.arkmemo.repo.graphics.GraphicNotesRepo @@ -38,8 +37,7 @@ object RepoHelperModule { @Singleton @Provides fun provideNotesRepoHelper( - memoPreferences: MemoPreferences, propertiesStorageRepo: PropertiesStorageRepo, @Named(IO_DISPATCHER) coroutineDispatcher: CoroutineDispatcher, - ) = NotesRepoHelper(memoPreferences, propertiesStorageRepo, coroutineDispatcher) + ) = NotesRepoHelper(propertiesStorageRepo, coroutineDispatcher) } 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 da9238e9..404807b7 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt @@ -14,4 +14,8 @@ interface MemoPreferences { fun getCrashReportEnabled(): Boolean fun storageNotAvailable(): Boolean + + fun isLastLaunchSuccess(): Boolean + + fun setLastLaunchSuccess(success: 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 4472a7fe..8fd12f31 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt @@ -11,6 +11,7 @@ import kotlin.io.path.exists private const val NAME = "memo_prefs" private const val CURRENT_NOTES_PATH = "current_notes_path" +private const val PREF_LAST_LAUNCH_SUCCESS = "pref_last_launch_success" class MemoPreferencesImpl @Inject @@ -38,4 +39,12 @@ class MemoPreferencesImpl override fun storageNotAvailable(): Boolean { return getPath().isEmpty() || !getNotesStorage().exists() } + + override fun isLastLaunchSuccess(): Boolean { + return sharedPreferences.getBoolean(PREF_LAST_LAUNCH_SUCCESS, true) + } + + override fun setLastLaunchSuccess(success: Boolean) { + prefEditor.putBoolean(PREF_LAST_LAUNCH_SUCCESS, success).apply() + } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt index d4018db0..46acac4b 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt @@ -9,7 +9,6 @@ import dev.arkbuilders.arklib.user.properties.PropertiesStorage import dev.arkbuilders.arklib.user.properties.PropertiesStorageRepo import dev.arkbuilders.arkmemo.di.IO_DISPATCHER import dev.arkbuilders.arkmemo.models.Note -import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.utils.isEqual import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -30,7 +29,6 @@ import kotlin.io.path.name class NotesRepoHelper @Inject constructor( - private val memoPreferences: MemoPreferences, private val propertiesStorageRepo: PropertiesStorageRepo, @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, ) { @@ -40,7 +38,6 @@ class NotesRepoHelper private val lazyPropertiesStorage by lazy { CoroutineScope(iODispatcher).async { val propertyStorage = propertiesStorageRepo.provide(RootIndex.provide(root)) - memoPreferences.storePath(root.toString()) propertyStorage } } 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 338581f7..e9a3a151 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 @@ -67,6 +67,11 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { onBackPressedDispatcher.onBackPressed() } + supportFragmentManager.onArkPathPicked(this) { + memoPreferences.storePath(it.toString()) + showFragment(savedInstanceState) + } + val storageFolderExisting = memoPreferences.getNotesStorage().exists() if (memoPreferences.storageNotAvailable()) { if (!storageFolderExisting) { @@ -74,35 +79,27 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { } else { FilePickerDialog.show(this, supportFragmentManager) } - - supportFragmentManager.onArkPathPicked(this) { - showFragment(savedInstanceState, it.toString()) - } } else { - showFragment(savedInstanceState, memoPreferences.getPath()) + if (memoPreferences.isLastLaunchSuccess()) { + showFragment(savedInstanceState) + } else { + showRetrySelectRootDialog( + rootPath = memoPreferences.getPath(), + savedInstanceState = savedInstanceState, + ) + } } } - private fun showFragment( - savedInstanceState: Bundle?, - storagePath: String, - ) { + private fun showFragment(savedInstanceState: Bundle?) { val textDataFromIntent = intent?.getStringExtra(Intent.EXTRA_TEXT) if (textDataFromIntent != null) { fragment = EditTextNotesFragment.newInstance(textDataFromIntent) - fragment.arguments = - Bundle().apply { - putString(BUNDLE_KEY_STORAGE_PATH, storagePath) - } supportFragmentManager.beginTransaction().apply { replace(fragContainer, fragment, EditTextNotesFragment.TAG) commit() } } else { - fragment.arguments = - Bundle().apply { - putString(BUNDLE_KEY_STORAGE_PATH, storagePath) - } if (savedInstanceState == null) { supportFragmentManager.beginTransaction().apply { add(fragContainer, fragment, NotesFragment.TAG) @@ -143,6 +140,35 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { loadFailDialog.show(supportFragmentManager, CommonActionDialog.TAG) } + private fun showRetrySelectRootDialog( + rootPath: String, + savedInstanceState: Bundle?, + ) { + val loadFailDialog = + CommonActionDialog( + title = getString(R.string.error_load_notes_crash_title), + message = getString(R.string.error_load_notes_crash_description, rootPath), + positiveText = R.string.error_load_notes_failed_retry_action, + negativeText = R.string.error_load_notes_failed_negative_action, + neutralText = R.string.error_load_notes_failed_positive_action, + isAlert = false, + enableNeutralOption = true, + onPositiveClick = { + showFragment(savedInstanceState) + }, + onNegativeClicked = { + finish() + }, + onNeutralClicked = { + FilePickerDialog.show(this, supportFragmentManager) + }, + onCloseClicked = { + finish() + }, + ) + loadFailDialog.show(supportFragmentManager, CommonActionDialog.TAG) + } + override fun onSaveInstanceState(outState: Bundle) { outState.putString(CURRENT_FRAGMENT_TAG, fragment.tag) super.onSaveInstanceState(outState) @@ -176,7 +202,6 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { companion object { private const val CURRENT_FRAGMENT_TAG = "current fragment tag" - const val BUNDLE_KEY_STORAGE_PATH = "bundle_key_storage_path" } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/CommonActionDialog.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/CommonActionDialog.kt index 06813c9c..b5c46574 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/CommonActionDialog.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/CommonActionDialog.kt @@ -8,6 +8,7 @@ import androidx.annotation.StringRes import androidx.fragment.app.DialogFragment import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.databinding.DialogCommonActionBinding +import dev.arkbuilders.arkmemo.utils.visible /** * This is a common action dialog that can be used inside app. @@ -18,9 +19,12 @@ class CommonActionDialog( private val message: String, @StringRes private val positiveText: Int, @StringRes private val negativeText: Int, + @StringRes private val neutralText: Int? = null, private val isAlert: Boolean = false, + private val enableNeutralOption: Boolean = false, private val onPositiveClick: (() -> Unit)? = null, private val onNegativeClicked: (() -> Unit)? = null, + private val onNeutralClicked: (() -> Unit)? = null, private val onCloseClicked: (() -> Unit)? = null, ) : DialogFragment() { companion object { @@ -48,10 +52,15 @@ class CommonActionDialog( mBinding.tvPositive.setBackgroundResource(R.drawable.bg_red_button) } + if (enableNeutralOption) { + mBinding.tvNeutral.visible() + } + mBinding.tvTitle.text = title mBinding.tvMessage.text = message mBinding.tvPositive.setText(positiveText) mBinding.tvNegative.setText(negativeText) + neutralText?.let { mBinding.tvNeutral.setText(neutralText) } mBinding.ivClose.setOnClickListener { onCloseClicked?.invoke() dismiss() @@ -66,6 +75,11 @@ class CommonActionDialog( onNegativeClicked?.invoke() dismiss() } + + mBinding.tvNeutral.setOnClickListener { + onNeutralClicked?.invoke() + dismiss() + } } override fun onResume() { 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 17e4aa1c..d6d61d63 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 @@ -15,7 +15,6 @@ import dagger.hilt.android.AndroidEntryPoint import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.models.Note import dev.arkbuilders.arkmemo.models.TextNote -import dev.arkbuilders.arkmemo.ui.activities.MainActivity.Companion.BUNDLE_KEY_STORAGE_PATH import dev.arkbuilders.arkmemo.utils.getParcelableCompat import dev.arkbuilders.arkmemo.utils.getTextFromClipBoard import dev.arkbuilders.arkmemo.utils.gone @@ -76,9 +75,7 @@ class EditTextNotesFragment : BaseEditNoteFragment() { note = it } noteStr = requireArguments().getString(NOTE_STRING_KEY) - arguments?.getString(BUNDLE_KEY_STORAGE_PATH)?.let { - notesViewModel.init(it) {} - } + notesViewModel.init {} } } 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 946661a2..7944af35 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 @@ -168,13 +168,12 @@ class NotesFragment : BaseFragment() { initEmptyStateViews() binding.pbLoading.visible() - val root = arguments?.getString(MainActivity.BUNDLE_KEY_STORAGE_PATH) ?: "" notesViewModel.apply { - storePath("") - init(root) { + setLastLaunchSuccess(false) + init { readAllNotes { onNotesLoaded(it) - storePath(root) + setLastLaunchSuccess(true) } } } 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 b26f5a7f..1787b741 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 @@ -43,10 +43,8 @@ class NotesViewModel @set:Inject internal lateinit var memoPreferences: MemoPreferences - fun init( - root: String, - extraBlock: () -> Unit, - ) { + fun init(extraBlock: () -> Unit) { + val root = memoPreferences.getPath() val initJob = viewModelScope.launch(iODispatcher) { textNotesRepo.init(root) @@ -195,7 +193,7 @@ class NotesViewModel return memoPreferences.getPath() } - fun storePath(path: String) { - memoPreferences.storePath(path) + fun setLastLaunchSuccess(success: Boolean) { + memoPreferences.setLastLaunchSuccess(success) } } diff --git a/app/src/main/res/layout/dialog_common_action.xml b/app/src/main/res/layout/dialog_common_action.xml index 045ec13e..d027b60a 100644 --- a/app/src/main/res/layout/dialog_common_action.xml +++ b/app/src/main/res/layout/dialog_common_action.xml @@ -55,6 +55,21 @@ android:layout_marginTop="24dp" android:id="@+id/tv_positive"/> + + Select All Deselect All Cannot find notes + Could not load notes The folder %s with notes data cannot be located.\nPlease select a new folder. Select Leave + Retry + There\'s a crash while loading the folder %s.\nPlease retry or select a new folder. \ No newline at end of file