Skip to content

Commit

Permalink
Use Flow of phrases from Room rather than a one shot
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
PaulKlauser committed Jun 11, 2024
1 parent ae185f3 commit 21043d0
Show file tree
Hide file tree
Showing 22 changed files with 123 additions and 158 deletions.
3 changes: 1 addition & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion app/src/main/java/com/willowtree/vocable/AppKoinModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ val vocableKoinModule = module {
single<VocableEnvironment> { VocableEnvironmentImpl() }
viewModel { PresetsViewModel(get(), get(), get(named<PresetsViewModel>())) }
viewModel { EditCategoriesViewModel(get()) }
viewModel { EditCategoryPhrasesViewModel(get(), get()) }
viewModel { EditCategoryPhrasesViewModel(get(), get(), get()) }
viewModel { AddUpdateCategoryViewModel(get(), get(), get()) }
viewModel { EditCategoryMenuViewModel(get()) }
}
3 changes: 3 additions & 0 deletions app/src/main/java/com/willowtree/vocable/IPhrasesUseCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Phrase>

fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<Phrase>>

suspend fun updatePhraseLastSpokenTime(phraseId: String)

suspend fun deletePhrase(phraseId: String)
Expand Down
5 changes: 2 additions & 3 deletions app/src/main/java/com/willowtree/vocable/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

Expand All @@ -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)
Expand Down
47 changes: 36 additions & 11 deletions app/src/main/java/com/willowtree/vocable/PhrasesUseCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -31,6 +33,23 @@ class PhrasesUseCase(
presetPhrasesRepository.getPhrasesForCategory(categoryId)
}

override fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<Phrase>> {
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)
Expand All @@ -54,6 +73,7 @@ class PhrasesUseCase(
localizedUtterance = localizedUtterance,
)
}

is PresetPhrase -> {
presetPhrasesRepository.deletePhrase(phraseId = phraseId)
// add a custom phrase to "shadow" over the preset
Expand All @@ -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."
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<CategoriesFragmentBinding>() {

Expand All @@ -35,9 +34,7 @@ class CategoriesFragment : BaseFragment<CategoriesFragmentBinding>() {
override val bindingInflater: BindingInflater<CategoriesFragmentBinding> =
CategoriesFragmentBinding::inflate

private val viewModel: PresetsViewModel by viewModel(owner = {
ViewModelOwner.from(requireActivity())
})
private val viewModel: PresetsViewModel by activityViewModel()
private val allViews = mutableListOf<View>()
private var maxCategories = 1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<FragmentPhrasesBinding>() {
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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<FragmentPresetsBinding>() {

Expand All @@ -34,9 +33,7 @@ class PresetsFragment : BaseFragment<FragmentPresetsBinding>() {
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

Expand Down
14 changes: 13 additions & 1 deletion app/src/main/java/com/willowtree/vocable/room/PhraseDao.kt
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<PhraseDto>

@Query("SELECT * FROM Phrase WHERE last_spoken_date IS NOT NULL ORDER BY last_spoken_date DESC LIMIT 8")
fun getRecentPhrasesFlow(): Flow<List<PhraseDto>>

@Query("SELECT * FROM Phrase WHERE parent_category_id == :categoryId")
suspend fun getPhrasesForCategory(categoryId: String): List<PhraseDto>

@Query("SELECT * FROM Phrase WHERE parent_category_id == :categoryId")
fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<PhraseDto>>

@Query("SELECT * FROM Phrase WHERE phrase_id == :phraseId")
suspend fun getPhrase(phraseId: String): PhraseDto?
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<PresetPhraseDto>

@Query("SELECT * FROM PresetPhrase WHERE last_spoken_date IS NOT NULL ORDER BY last_spoken_date DESC LIMIT 8")
fun getRecentPhrasesFlow(): Flow<List<PresetPhraseDto>>

@Query("SELECT * FROM PresetPhrase WHERE parent_category_id = :categoryId")
suspend fun getPhrasesForCategory(categoryId: String): List<PresetPhraseDto>

@Query("SELECT * FROM PresetPhrase WHERE parent_category_id = :categoryId")
fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<PresetPhraseDto>>

@Query("SELECT * FROM PresetPhrase WHERE phrase_id = :phraseId")
suspend fun getPhrase(phraseId: String): PresetPhraseDto?

Expand Down
Original file line number Diff line number Diff line change
@@ -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<PresetPhrase>
suspend fun updatePhraseLastSpokenTime(phraseId: String)
suspend fun getRecentPhrases() : List<PresetPhrase>
fun getRecentPhrasesFlow(): Flow<List<PresetPhrase>>
suspend fun getPhrasesForCategory(categoryId: String): List<PresetPhrase>
fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<PresetPhrase>>
suspend fun getPhrase(phraseId: String): PresetPhrase?
suspend fun deletePhrase(phraseId: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -37,7 +39,12 @@ class RoomPresetPhrasesRepository(
override suspend fun getRecentPhrases(): List<PresetPhrase> {
return presetPhrasesDao.getRecentPhrases()
.filterDeletedPresets()
.map { it.asPhrase()}
.map { it.asPhrase() }
}

override fun getRecentPhrasesFlow(): Flow<List<PresetPhrase>> {
return presetPhrasesDao.getRecentPhrasesFlow()
.map { phraseList -> phraseList.filterDeletedPresets().map { it.asPhrase() } }
}

override suspend fun getPhrasesForCategory(categoryId: String): List<PresetPhrase> {
Expand All @@ -46,6 +53,11 @@ class RoomPresetPhrasesRepository(
.map { it.asPhrase() }
}

override fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<PresetPhrase>> {
return presetPhrasesDao.getPhrasesForCategoryFlow(categoryId)
.map { phraseList -> phraseList.filterDeletedPresets().map { it.asPhrase() } }
}

override suspend fun getPhrase(phraseId: String): PresetPhrase? {
return presetPhrasesDao.getPhrase(phraseId)?.asPhrase()
}
Expand All @@ -54,7 +66,7 @@ class RoomPresetPhrasesRepository(
presetPhrasesDao.deletePhrase(phraseId, deleted = true)
}

private fun List<PresetPhraseDto>.filterDeletedPresets() : List<PresetPhraseDto> {
private fun List<PresetPhraseDto>.filterDeletedPresets(): List<PresetPhraseDto> {
return filterNot { it.deleted }
}

Expand Down
Loading

0 comments on commit 21043d0

Please sign in to comment.