From 21043d0918af0810f0f5e11885478d316482c24e Mon Sep 17 00:00:00 2001 From: Paul Klauser Date: Tue, 11 Jun 2024 16:28:21 -0400 Subject: [PATCH] Use Flow of phrases from Room rather than a one shot This ensures that as phrases are updated, we are constantly observing them. Needed to update Koin so that we could get the nav arg directly in the ViewModel. No safe args integration with Koin unfortunately. --- app/build.gradle | 3 +- .../EditCategoryPhrasesViewModelTest.kt | 83 ------------------- .../com/willowtree/vocable/AppKoinModule.kt | 2 +- .../com/willowtree/vocable/IPhrasesUseCase.kt | 3 + .../com/willowtree/vocable/MainActivity.kt | 5 +- .../com/willowtree/vocable/PhrasesUseCase.kt | 47 ++++++++--- .../vocable/presets/CategoriesFragment.kt | 7 +- .../vocable/presets/PhrasesFragment.kt | 7 +- .../vocable/presets/PresetsFragment.kt | 7 +- .../com/willowtree/vocable/room/PhraseDao.kt | 14 +++- .../vocable/room/PresetPhrasesDao.kt | 7 ++ .../vocable/room/PresetPhrasesRepository.kt | 3 + .../room/RoomPresetPhrasesRepository.kt | 16 +++- .../room/RoomStoredPhrasesRepository.kt | 12 +++ .../vocable/room/StoredPhrasesRepository.kt | 3 + .../settings/EditCategoriesFragment.kt | 7 +- .../settings/EditCategoriesListFragment.kt | 7 +- .../settings/EditCategoryPhrasesFragment.kt | 9 +- .../settings/EditCategoryPhrasesViewModel.kt | 25 ++---- .../CustomCategoryPhraseListFragment.kt | 7 +- .../willowtree/vocable/FakePhrasesUseCase.kt | 5 ++ gradle/libs.versions.toml | 2 + 22 files changed, 123 insertions(+), 158 deletions(-) delete mode 100644 app/src/androidTest/java/com/willowtree/vocable/settings/EditCategoryPhrasesViewModelTest.kt diff --git a/app/build.gradle b/app/build.gradle index 5bfa5629..e7193747 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -79,8 +79,7 @@ dependencies { implementation 'android.arch.lifecycle:extensions:1.1.1' implementation 'android.arch.lifecycle:viewmodel:1.1.1' - implementation 'io.insert-koin:koin-core:3.1.5' - implementation 'io.insert-koin:koin-android:3.1.5' + implementation(libs.koin.android) implementation "androidx.room:room-runtime:2.5.2" kapt "androidx.room:room-compiler:2.5.2" diff --git a/app/src/androidTest/java/com/willowtree/vocable/settings/EditCategoryPhrasesViewModelTest.kt b/app/src/androidTest/java/com/willowtree/vocable/settings/EditCategoryPhrasesViewModelTest.kt deleted file mode 100644 index 953c140e..00000000 --- a/app/src/androidTest/java/com/willowtree/vocable/settings/EditCategoryPhrasesViewModelTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.willowtree.vocable.settings - -import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import com.willowtree.vocable.MainDispatcherRule -import com.willowtree.vocable.presets.Category -import com.willowtree.vocable.presets.PresetCategories -import com.willowtree.vocable.presets.PresetPhrase -import com.willowtree.vocable.room.PresetPhrasesRepository -import com.willowtree.vocable.utility.VocableKoinTestRule -import junit.framework.TestCase.assertEquals -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -class EditCategoryPhrasesViewModelTest: KoinComponent { - - @get:Rule - val koinTestRule = VocableKoinTestRule() - - @get:Rule - val mainDispatcherRule = MainDispatcherRule() - - @get:Rule - val instantTaskExecutorRule = InstantTaskExecutorRule() - - private lateinit var viewModel: EditCategoryPhrasesViewModel - - @Before - fun setUp() { - val presetPhrasesRepository: PresetPhrasesRepository = get() - runBlocking { - presetPhrasesRepository.populateDatabase() - } - viewModel = get() - } - - @Test - fun getCategoryName_withRecents_returnsRecents() { - val result = viewModel.getCategoryName(Category.Recents(false, 0)) - assertEquals("Recents", result) - } - - @Test - fun getCategoryName_withPresetCategory_returnsCategoryName() { - val result = viewModel.getCategoryName(Category.PresetCategory(PresetCategories.GENERAL.id, 0, false)) - assertEquals("General", result) - } - - @Test - fun fetchCategoryPhrases_withGeneral_returnsCorrectPhraseList() { - viewModel.fetchCategoryPhrases(Category.PresetCategory(PresetCategories.GENERAL.id, 0, false)) - viewModel.categoryPhraseList.observeForever { - assertEquals(9, it.size) - assertEquals("preset_please", it[0].phraseId) - } - } - - @Test - fun deletePhraseFromCategory_withPlease_deletesPleaseCorrectly() { - viewModel.deletePhraseFromCategory( - phrase = PresetPhrase( - phraseId = "preset_please", - sortOrder = 0, - lastSpokenDate = 0, - deleted = false, - parentCategoryId = PresetCategories.GENERAL.id - ), - category = Category.PresetCategory( - categoryId = PresetCategories.GENERAL.id, - sortOrder = 0, - hidden = false - ) - ) - - viewModel.categoryPhraseList.observeForever { - assertEquals(8, it.size) - assertEquals("preset_thank_you", it[0].phraseId) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/willowtree/vocable/AppKoinModule.kt b/app/src/main/java/com/willowtree/vocable/AppKoinModule.kt index ddd74202..e0224cb7 100644 --- a/app/src/main/java/com/willowtree/vocable/AppKoinModule.kt +++ b/app/src/main/java/com/willowtree/vocable/AppKoinModule.kt @@ -103,7 +103,7 @@ val vocableKoinModule = module { single { VocableEnvironmentImpl() } viewModel { PresetsViewModel(get(), get(), get(named())) } viewModel { EditCategoriesViewModel(get()) } - viewModel { EditCategoryPhrasesViewModel(get(), get()) } + viewModel { EditCategoryPhrasesViewModel(get(), get(), get()) } viewModel { AddUpdateCategoryViewModel(get(), get(), get()) } viewModel { EditCategoryMenuViewModel(get()) } } \ No newline at end of file diff --git a/app/src/main/java/com/willowtree/vocable/IPhrasesUseCase.kt b/app/src/main/java/com/willowtree/vocable/IPhrasesUseCase.kt index 695a4a7a..b61338c8 100644 --- a/app/src/main/java/com/willowtree/vocable/IPhrasesUseCase.kt +++ b/app/src/main/java/com/willowtree/vocable/IPhrasesUseCase.kt @@ -2,11 +2,14 @@ package com.willowtree.vocable import com.willowtree.vocable.presets.Phrase import com.willowtree.vocable.utils.locale.LocalesWithText +import kotlinx.coroutines.flow.Flow interface IPhrasesUseCase { suspend fun getPhrasesForCategory(categoryId: String): List + fun getPhrasesForCategoryFlow(categoryId: String): Flow> + suspend fun updatePhraseLastSpokenTime(phraseId: String) suspend fun deletePhrase(phraseId: String) diff --git a/app/src/main/java/com/willowtree/vocable/MainActivity.kt b/app/src/main/java/com/willowtree/vocable/MainActivity.kt index 5d904909..81047930 100644 --- a/app/src/main/java/com/willowtree/vocable/MainActivity.kt +++ b/app/src/main/java/com/willowtree/vocable/MainActivity.kt @@ -22,8 +22,7 @@ import io.github.inflationx.viewpump.ViewPumpContextWrapper import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koin.androidx.scope.ScopeActivity -import org.koin.androidx.viewmodel.ViewModelOwner -import org.koin.androidx.viewmodel.scope.getViewModel +import org.koin.androidx.viewmodel.ext.android.getViewModel class MainActivity : ScopeActivity() { @@ -41,7 +40,7 @@ class MainActivity : ScopeActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - faceTrackingViewModel = scope.getViewModel(owner = { ViewModelOwner.from(this) }) + faceTrackingViewModel = getViewModel() binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) diff --git a/app/src/main/java/com/willowtree/vocable/PhrasesUseCase.kt b/app/src/main/java/com/willowtree/vocable/PhrasesUseCase.kt index ff54cfcb..39c189ee 100644 --- a/app/src/main/java/com/willowtree/vocable/PhrasesUseCase.kt +++ b/app/src/main/java/com/willowtree/vocable/PhrasesUseCase.kt @@ -12,6 +12,8 @@ import com.willowtree.vocable.utils.DateProvider import com.willowtree.vocable.utils.UUIDProvider import com.willowtree.vocable.utils.locale.LocaleProvider import com.willowtree.vocable.utils.locale.LocalesWithText +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine class PhrasesUseCase( private val legacyPhrasesRepository: ILegacyCategoriesAndPhrasesRepository, @@ -31,6 +33,23 @@ class PhrasesUseCase( presetPhrasesRepository.getPhrasesForCategory(categoryId) } + override fun getPhrasesForCategoryFlow(categoryId: String): Flow> { + return combine( + presetPhrasesRepository.getRecentPhrasesFlow(), + storedPhrasesRepository.getRecentPhrasesFlow(), + storedPhrasesRepository.getPhrasesForCategoryFlow(categoryId), + presetPhrasesRepository.getPhrasesForCategoryFlow(categoryId) + ) { recentPresets, recentStored, stored, presets -> + if (categoryId == PresetCategories.RECENTS.id) { + (recentPresets + recentStored) + .sortedByDescending { it.lastSpokenDate } + .take(8) + } else { + stored + presets + } + } + } + override suspend fun updatePhraseLastSpokenTime(phraseId: String) { storedPhrasesRepository.updatePhraseLastSpokenTime(phraseId) presetPhrasesRepository.updatePhraseLastSpokenTime(phraseId) @@ -54,6 +73,7 @@ class PhrasesUseCase( localizedUtterance = localizedUtterance, ) } + is PresetPhrase -> { presetPhrasesRepository.deletePhrase(phraseId = phraseId) // add a custom phrase to "shadow" over the preset @@ -69,24 +89,29 @@ class PhrasesUseCase( ) } + null -> throw IllegalArgumentException("Phrase with id $phraseId not found") } } override suspend fun addPhrase(localizedUtterance: LocalesWithText, parentCategoryId: String) { if (parentCategoryId != PresetCategories.RECENTS.id) { - storedPhrasesRepository.addPhrase(PhraseDto( - phraseId = uuidProvider.randomUUIDString(), - parentCategoryId = parentCategoryId, - creationDate = dateProvider.currentTimeMillis(), - lastSpokenDate = null, - localizedUtterance = localizedUtterance, - sortOrder = legacyPhrasesRepository.getPhrasesForCategory(parentCategoryId).size - )) + storedPhrasesRepository.addPhrase( + PhraseDto( + phraseId = uuidProvider.randomUUIDString(), + parentCategoryId = parentCategoryId, + creationDate = dateProvider.currentTimeMillis(), + lastSpokenDate = null, + localizedUtterance = localizedUtterance, + sortOrder = legacyPhrasesRepository.getPhrasesForCategory(parentCategoryId).size + ) + ) } else { - throw Exception("The 'Recents' category is not a true category -" + - " it is a filter applied to true categories. Therefore, saving phrases from " + - "the Recents 'category' is not supported.") + throw Exception( + "The 'Recents' category is not a true category -" + + " it is a filter applied to true categories. Therefore, saving phrases from " + + "the Recents 'category' is not supported." + ) } } } \ No newline at end of file diff --git a/app/src/main/java/com/willowtree/vocable/presets/CategoriesFragment.kt b/app/src/main/java/com/willowtree/vocable/presets/CategoriesFragment.kt index 71be5d1f..fd0185ed 100644 --- a/app/src/main/java/com/willowtree/vocable/presets/CategoriesFragment.kt +++ b/app/src/main/java/com/willowtree/vocable/presets/CategoriesFragment.kt @@ -15,8 +15,7 @@ import com.willowtree.vocable.customviews.CategoryButton import com.willowtree.vocable.customviews.PointerListener import com.willowtree.vocable.databinding.CategoriesFragmentBinding import com.willowtree.vocable.databinding.CategoryButtonBinding -import org.koin.androidx.viewmodel.ViewModelOwner -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.androidx.viewmodel.ext.android.activityViewModel class CategoriesFragment : BaseFragment() { @@ -35,9 +34,7 @@ class CategoriesFragment : BaseFragment() { override val bindingInflater: BindingInflater = CategoriesFragmentBinding::inflate - private val viewModel: PresetsViewModel by viewModel(owner = { - ViewModelOwner.from(requireActivity()) - }) + private val viewModel: PresetsViewModel by activityViewModel() private val allViews = mutableListOf() private var maxCategories = 1 diff --git a/app/src/main/java/com/willowtree/vocable/presets/PhrasesFragment.kt b/app/src/main/java/com/willowtree/vocable/presets/PhrasesFragment.kt index 32c61d7d..fa6454b5 100644 --- a/app/src/main/java/com/willowtree/vocable/presets/PhrasesFragment.kt +++ b/app/src/main/java/com/willowtree/vocable/presets/PhrasesFragment.kt @@ -11,13 +11,10 @@ import com.willowtree.vocable.R import com.willowtree.vocable.databinding.FragmentPhrasesBinding import com.willowtree.vocable.presets.adapter.PhraseAdapter import com.willowtree.vocable.utils.ItemOffsetDecoration -import org.koin.androidx.viewmodel.ViewModelOwner -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.androidx.viewmodel.ext.android.activityViewModel class PhrasesFragment : BaseFragment() { - private val presetsViewModel: PresetsViewModel by viewModel(owner = { - ViewModelOwner.from(requireActivity()) - }) + private val presetsViewModel: PresetsViewModel by activityViewModel() companion object { private const val KEY_PHRASES = "KEY_PHRASES" diff --git a/app/src/main/java/com/willowtree/vocable/presets/PresetsFragment.kt b/app/src/main/java/com/willowtree/vocable/presets/PresetsFragment.kt index ba7ad8c4..92885266 100644 --- a/app/src/main/java/com/willowtree/vocable/presets/PresetsFragment.kt +++ b/app/src/main/java/com/willowtree/vocable/presets/PresetsFragment.kt @@ -20,8 +20,7 @@ import com.willowtree.vocable.databinding.FragmentPresetsBinding import com.willowtree.vocable.utils.SpokenText import com.willowtree.vocable.utils.VocableFragmentStateAdapter import com.willowtree.vocable.utils.VocableTextToSpeech -import org.koin.androidx.viewmodel.ViewModelOwner -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.androidx.viewmodel.ext.android.activityViewModel class PresetsFragment : BaseFragment() { @@ -34,9 +33,7 @@ class PresetsFragment : BaseFragment() { private var isPortraitMode = true private var isTabletMode = false - private val presetsViewModel: PresetsViewModel by viewModel(owner = { - ViewModelOwner.from(requireActivity()) - }) + private val presetsViewModel: PresetsViewModel by activityViewModel() private lateinit var categoriesAdapter: CategoriesPagerAdapter private lateinit var phrasesAdapter: PhrasesPagerAdapter diff --git a/app/src/main/java/com/willowtree/vocable/room/PhraseDao.kt b/app/src/main/java/com/willowtree/vocable/room/PhraseDao.kt index 0abea269..11be3b63 100644 --- a/app/src/main/java/com/willowtree/vocable/room/PhraseDao.kt +++ b/app/src/main/java/com/willowtree/vocable/room/PhraseDao.kt @@ -1,6 +1,12 @@ package com.willowtree.vocable.room -import androidx.room.* +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import kotlinx.coroutines.flow.Flow @Dao interface PhraseDao { @@ -26,9 +32,15 @@ interface PhraseDao { @Query("SELECT * FROM Phrase WHERE last_spoken_date IS NOT NULL ORDER BY last_spoken_date DESC LIMIT 8") suspend fun getRecentPhrases(): List + @Query("SELECT * FROM Phrase WHERE last_spoken_date IS NOT NULL ORDER BY last_spoken_date DESC LIMIT 8") + fun getRecentPhrasesFlow(): Flow> + @Query("SELECT * FROM Phrase WHERE parent_category_id == :categoryId") suspend fun getPhrasesForCategory(categoryId: String): List + @Query("SELECT * FROM Phrase WHERE parent_category_id == :categoryId") + fun getPhrasesForCategoryFlow(categoryId: String): Flow> + @Query("SELECT * FROM Phrase WHERE phrase_id == :phraseId") suspend fun getPhrase(phraseId: String): PhraseDto? } diff --git a/app/src/main/java/com/willowtree/vocable/room/PresetPhrasesDao.kt b/app/src/main/java/com/willowtree/vocable/room/PresetPhrasesDao.kt index 80e7fc7a..72f13288 100644 --- a/app/src/main/java/com/willowtree/vocable/room/PresetPhrasesDao.kt +++ b/app/src/main/java/com/willowtree/vocable/room/PresetPhrasesDao.kt @@ -5,6 +5,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update +import kotlinx.coroutines.flow.Flow @Dao interface PresetPhrasesDao { @@ -21,9 +22,15 @@ interface PresetPhrasesDao { @Query("SELECT * FROM PresetPhrase WHERE last_spoken_date IS NOT NULL ORDER BY last_spoken_date DESC LIMIT 8") suspend fun getRecentPhrases(): List + @Query("SELECT * FROM PresetPhrase WHERE last_spoken_date IS NOT NULL ORDER BY last_spoken_date DESC LIMIT 8") + fun getRecentPhrasesFlow(): Flow> + @Query("SELECT * FROM PresetPhrase WHERE parent_category_id = :categoryId") suspend fun getPhrasesForCategory(categoryId: String): List + @Query("SELECT * FROM PresetPhrase WHERE parent_category_id = :categoryId") + fun getPhrasesForCategoryFlow(categoryId: String): Flow> + @Query("SELECT * FROM PresetPhrase WHERE phrase_id = :phraseId") suspend fun getPhrase(phraseId: String): PresetPhraseDto? diff --git a/app/src/main/java/com/willowtree/vocable/room/PresetPhrasesRepository.kt b/app/src/main/java/com/willowtree/vocable/room/PresetPhrasesRepository.kt index d86937ae..d688a5f7 100644 --- a/app/src/main/java/com/willowtree/vocable/room/PresetPhrasesRepository.kt +++ b/app/src/main/java/com/willowtree/vocable/room/PresetPhrasesRepository.kt @@ -1,13 +1,16 @@ package com.willowtree.vocable.room import com.willowtree.vocable.presets.PresetPhrase +import kotlinx.coroutines.flow.Flow interface PresetPhrasesRepository { suspend fun populateDatabase() suspend fun getAllPresetPhrases(): List suspend fun updatePhraseLastSpokenTime(phraseId: String) suspend fun getRecentPhrases() : List + fun getRecentPhrasesFlow(): Flow> suspend fun getPhrasesForCategory(categoryId: String): List + fun getPhrasesForCategoryFlow(categoryId: String): Flow> suspend fun getPhrase(phraseId: String): PresetPhrase? suspend fun deletePhrase(phraseId: String) } \ No newline at end of file diff --git a/app/src/main/java/com/willowtree/vocable/room/RoomPresetPhrasesRepository.kt b/app/src/main/java/com/willowtree/vocable/room/RoomPresetPhrasesRepository.kt index dc8e33d0..cdca34ea 100644 --- a/app/src/main/java/com/willowtree/vocable/room/RoomPresetPhrasesRepository.kt +++ b/app/src/main/java/com/willowtree/vocable/room/RoomPresetPhrasesRepository.kt @@ -5,6 +5,8 @@ import com.willowtree.vocable.presets.PresetCategories import com.willowtree.vocable.presets.PresetPhrase import com.willowtree.vocable.presets.asPhrase import com.willowtree.vocable.utils.DateProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.koin.core.context.GlobalContext.get @@ -37,7 +39,12 @@ class RoomPresetPhrasesRepository( override suspend fun getRecentPhrases(): List { return presetPhrasesDao.getRecentPhrases() .filterDeletedPresets() - .map { it.asPhrase()} + .map { it.asPhrase() } + } + + override fun getRecentPhrasesFlow(): Flow> { + return presetPhrasesDao.getRecentPhrasesFlow() + .map { phraseList -> phraseList.filterDeletedPresets().map { it.asPhrase() } } } override suspend fun getPhrasesForCategory(categoryId: String): List { @@ -46,6 +53,11 @@ class RoomPresetPhrasesRepository( .map { it.asPhrase() } } + override fun getPhrasesForCategoryFlow(categoryId: String): Flow> { + return presetPhrasesDao.getPhrasesForCategoryFlow(categoryId) + .map { phraseList -> phraseList.filterDeletedPresets().map { it.asPhrase() } } + } + override suspend fun getPhrase(phraseId: String): PresetPhrase? { return presetPhrasesDao.getPhrase(phraseId)?.asPhrase() } @@ -54,7 +66,7 @@ class RoomPresetPhrasesRepository( presetPhrasesDao.deletePhrase(phraseId, deleted = true) } - private fun List.filterDeletedPresets() : List { + private fun List.filterDeletedPresets(): List { return filterNot { it.deleted } } diff --git a/app/src/main/java/com/willowtree/vocable/room/RoomStoredPhrasesRepository.kt b/app/src/main/java/com/willowtree/vocable/room/RoomStoredPhrasesRepository.kt index 1e326cce..ff5628fb 100644 --- a/app/src/main/java/com/willowtree/vocable/room/RoomStoredPhrasesRepository.kt +++ b/app/src/main/java/com/willowtree/vocable/room/RoomStoredPhrasesRepository.kt @@ -4,6 +4,8 @@ import com.willowtree.vocable.presets.Phrase import com.willowtree.vocable.presets.asPhrase import com.willowtree.vocable.utils.DateProvider import com.willowtree.vocable.utils.locale.LocalesWithText +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map class RoomStoredPhrasesRepository( private val database: VocableDatabase, @@ -26,10 +28,20 @@ class RoomStoredPhrasesRepository( return database.phraseDao().getRecentPhrases().map { it.asPhrase() } } + override fun getRecentPhrasesFlow(): Flow> { + return database.phraseDao().getRecentPhrasesFlow() + .map { phraseList -> phraseList.map { it.asPhrase() } } + } + override suspend fun getPhrasesForCategory(categoryId: String): List { return database.phraseDao().getPhrasesForCategory(categoryId).map { it.asPhrase() } } + override fun getPhrasesForCategoryFlow(categoryId: String): Flow> { + return database.phraseDao().getPhrasesForCategoryFlow(categoryId) + .map { phraseList -> phraseList.map { it.asPhrase() } } + } + override suspend fun getPhrase(phraseId: String): Phrase? { return database.phraseDao().getPhrase(phraseId)?.asPhrase() } diff --git a/app/src/main/java/com/willowtree/vocable/room/StoredPhrasesRepository.kt b/app/src/main/java/com/willowtree/vocable/room/StoredPhrasesRepository.kt index aaec6384..66c27f43 100644 --- a/app/src/main/java/com/willowtree/vocable/room/StoredPhrasesRepository.kt +++ b/app/src/main/java/com/willowtree/vocable/room/StoredPhrasesRepository.kt @@ -2,12 +2,15 @@ package com.willowtree.vocable.room import com.willowtree.vocable.presets.Phrase import com.willowtree.vocable.utils.locale.LocalesWithText +import kotlinx.coroutines.flow.Flow interface StoredPhrasesRepository { suspend fun addPhrase(phrase: PhraseDto) suspend fun updatePhraseLastSpokenTime(phraseId: String) suspend fun getRecentPhrases(): List + fun getRecentPhrasesFlow(): Flow> suspend fun getPhrasesForCategory(categoryId: String): List + fun getPhrasesForCategoryFlow(categoryId: String): Flow> suspend fun getPhrase(phraseId: String): Phrase? suspend fun updatePhrase(phrase: PhraseDto) suspend fun updatePhraseLocalizedUtterance( diff --git a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesFragment.kt b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesFragment.kt index c840c491..7ca2af7c 100644 --- a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesFragment.kt +++ b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesFragment.kt @@ -17,8 +17,7 @@ import com.willowtree.vocable.databinding.FragmentEditCategoriesBinding import com.willowtree.vocable.presets.Category import com.willowtree.vocable.utils.VocableFragmentStateAdapter import kotlinx.coroutines.launch -import org.koin.androidx.viewmodel.ViewModelOwner -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.androidx.viewmodel.ext.android.activityViewModel import kotlin.math.min class EditCategoriesFragment : BaseFragment() { @@ -27,9 +26,7 @@ class EditCategoriesFragment : BaseFragment() { FragmentEditCategoriesBinding::inflate private lateinit var categoriesAdapter: CategoriesPagerAdapter - private val editCategoriesViewModel: EditCategoriesViewModel by viewModel(owner = { - ViewModelOwner.from(requireActivity()) - }) + private val editCategoriesViewModel: EditCategoriesViewModel by activityViewModel() private val allViews = mutableListOf() private var maxEditCategories = 1 diff --git a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesListFragment.kt b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesListFragment.kt index 155df677..e056caf2 100644 --- a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesListFragment.kt +++ b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesListFragment.kt @@ -17,8 +17,7 @@ import com.willowtree.vocable.presets.Category import com.willowtree.vocable.utils.locale.LocalizedResourceUtility import kotlinx.coroutines.launch import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ViewModelOwner -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.androidx.viewmodel.ext.android.activityViewModel class EditCategoriesListFragment : BaseFragment() { @@ -41,9 +40,7 @@ class EditCategoriesListFragment : BaseFragment = FragmentEditCategoriesListBinding::inflate - private val editCategoriesViewModel: EditCategoriesViewModel by viewModel(owner = { - ViewModelOwner.from(requireActivity()) - }) + private val editCategoriesViewModel: EditCategoriesViewModel by activityViewModel() private var maxEditCategories = 1 private var startPosition = 0 diff --git a/app/src/main/java/com/willowtree/vocable/settings/EditCategoryPhrasesFragment.kt b/app/src/main/java/com/willowtree/vocable/settings/EditCategoryPhrasesFragment.kt index fe855c28..00aafafe 100644 --- a/app/src/main/java/com/willowtree/vocable/settings/EditCategoryPhrasesFragment.kt +++ b/app/src/main/java/com/willowtree/vocable/settings/EditCategoryPhrasesFragment.kt @@ -18,7 +18,6 @@ import com.willowtree.vocable.presets.Phrase import com.willowtree.vocable.presets.PresetCategories import com.willowtree.vocable.settings.customcategories.CustomCategoryPhraseListFragment import com.willowtree.vocable.utils.VocableFragmentStateAdapter -import org.koin.androidx.viewmodel.ViewModelOwner import org.koin.androidx.viewmodel.ext.android.viewModel class EditCategoryPhrasesFragment : BaseFragment() { @@ -27,9 +26,7 @@ class EditCategoryPhrasesFragment : BaseFragment = FragmentEditCategoryPhrasesBinding::inflate - private val editCategoriesViewModel: EditCategoryPhrasesViewModel by viewModel(owner = { - ViewModelOwner.from(requireActivity()) - }) + private val editCategoriesViewModel: EditCategoryPhrasesViewModel by viewModel() private var maxPhrases = 1 private lateinit var phrasesAdapter: PhrasesPagerAdapter @@ -116,10 +113,6 @@ class EditCategoryPhrasesFragment : BaseFragment>() - val categoryPhraseList: LiveData> = liveCategoryPhraseList +) : ViewModel() { + + val categoryPhraseList: LiveData> = phrasesUseCase.getPhrasesForCategoryFlow(savedStateHandle.get("category")!!.categoryId) + .asLiveData() fun getCategoryName(category: Category): String { return localizedResourceUtility.getTextFromCategory(category) } - fun fetchCategoryPhrases(category: Category) { + fun deletePhraseFromCategory(phrase: Phrase) { viewModelScope.launch { - val phrasesForCategory = phrasesUseCase.getPhrasesForCategory(category.categoryId) - .sortedBy { it.sortOrder } - liveCategoryPhraseList.postValue(phrasesForCategory) - } - } - - fun deletePhraseFromCategory(phrase: Phrase, category: Category) { - viewModelScope.launch { - phrasesUseCase.deletePhrase(phrase.phraseId) - - // Refresh phrase list - fetchCategoryPhrases(category) } } } \ No newline at end of file diff --git a/app/src/main/java/com/willowtree/vocable/settings/customcategories/CustomCategoryPhraseListFragment.kt b/app/src/main/java/com/willowtree/vocable/settings/customcategories/CustomCategoryPhraseListFragment.kt index d20f7141..00bd5231 100644 --- a/app/src/main/java/com/willowtree/vocable/settings/customcategories/CustomCategoryPhraseListFragment.kt +++ b/app/src/main/java/com/willowtree/vocable/settings/customcategories/CustomCategoryPhraseListFragment.kt @@ -16,7 +16,6 @@ import com.willowtree.vocable.settings.EditCategoryPhrasesFragmentDirections import com.willowtree.vocable.settings.EditCategoryPhrasesViewModel import com.willowtree.vocable.settings.customcategories.adapter.CustomCategoryPhraseAdapter import com.willowtree.vocable.utils.ItemOffsetDecoration -import org.koin.androidx.viewmodel.ViewModelOwner import org.koin.androidx.viewmodel.ext.android.viewModel class CustomCategoryPhraseListFragment : BaseFragment() { @@ -35,9 +34,7 @@ class CustomCategoryPhraseListFragment : BaseFragment @@ -91,7 +88,7 @@ class CustomCategoryPhraseListFragment : BaseFragment> { + TODO("Not yet implemented") + } + override suspend fun updatePhraseLastSpokenTime(phraseId: String) { error("Not implemented") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff301ff2..68139c9b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,12 +6,14 @@ safe-args = "2.6.0" google-services = "4.3.15" crashlytics = "2.8.1" androidx-test = "1.5.0" +koin = "3.5.6" [libraries] turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidx-test"} +koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }