Skip to content

Commit

Permalink
Merge pull request #571 from willowtreeapps/bugfix/568-phrase-added-f…
Browse files Browse the repository at this point in the history
…rom-home-screen-not-shown

Bugfix/568 phrase added from home screen not shown
  • Loading branch information
PaulKlauser authored Jun 12, 2024
2 parents c6da557 + 8e72692 commit f9e97fb
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.willowtree.vocable.presets.PresetCategories
import com.willowtree.vocable.utility.VocableKoinTestRule
import com.willowtree.vocable.utils.VocableSharedPreferences
import com.willowtree.vocable.utils.locale.LocalesWithText
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Assert.assertEquals
Expand Down Expand Up @@ -341,8 +342,8 @@ class MigrationTest {
), categories
)

val customPhrases = db.phraseDao().getPhrasesForCategory("custom")
val recentPhrases = db.phraseDao().getPhrasesForCategory("recents")
val customPhrases = db.phraseDao().getPhrasesForCategory("custom").first()
val recentPhrases = db.phraseDao().getPhrasesForCategory("recents").first()
assertEquals(
listOf(
PhraseDto(
Expand Down
9 changes: 2 additions & 7 deletions app/src/main/java/com/willowtree/vocable/PhrasesUseCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.willowtree.vocable.utils.locale.LocaleProvider
import com.willowtree.vocable.utils.locale.LocalesWithText
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first

class PhrasesUseCase(
private val legacyPhrasesRepository: ILegacyCategoriesAndPhrasesRepository,
Expand All @@ -24,13 +25,7 @@ class PhrasesUseCase(
private val localeProvider: LocaleProvider
) : IPhrasesUseCase {
override suspend fun getPhrasesForCategory(categoryId: String): List<Phrase> {
if (categoryId == PresetCategories.RECENTS.id) {
val presets = presetPhrasesRepository.getRecentPhrases()
val stored = storedPhrasesRepository.getRecentPhrases()
return (presets + stored).sortedByDescending { it.lastSpokenDate }.take(8)
}
return storedPhrasesRepository.getPhrasesForCategory(categoryId) +
presetPhrasesRepository.getPhrasesForCategory(categoryId)
return getPhrasesForCategoryFlow(categoryId).first()
}

override fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<Phrase>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.willowtree.vocable.room.PhraseDto
import com.willowtree.vocable.room.VocableDatabase
import com.willowtree.vocable.utils.locale.LocalesWithText
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import org.koin.core.component.KoinComponent

class LegacyCategoriesAndPhrasesRepository(
Expand All @@ -24,11 +25,11 @@ class LegacyCategoriesAndPhrasesRepository(
}

override suspend fun getPhrasesForCategory(categoryId: String): List<PhraseDto> {
return database.phraseDao().getPhrasesForCategory(categoryId)
return database.phraseDao().getPhrasesForCategory(categoryId).first()
}

override suspend fun getRecentPhrases(): List<PhraseDto> =
database.phraseDao().getRecentPhrases()
database.phraseDao().getRecentPhrases().first()

override suspend fun updateCategorySortOrders(categorySortOrders: List<CategorySortOrder>) {
database.categoryDao().updateCategorySortOrders(categorySortOrders)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
Expand All @@ -29,20 +31,20 @@ class PresetsViewModel(
.asLiveData()

// Will only ever be null immediately on init
private val liveSelectedCategoryId = MutableStateFlow<String?>(null)
private val selectedCategoryId = MutableStateFlow<String?>(null)
val selectedCategory: StateFlow<Category?> = combine(
categoriesUseCase.categories(),
liveSelectedCategoryId
selectedCategoryId
) { categories, selectedId ->
val currentCategory = categories.find { it.categoryId == selectedId }
if (currentCategory?.hidden == true) {
val newSortOrder = (currentCategory.sortOrder + 1)
val newCategory = categories.find { it.sortOrder == newSortOrder}
val newCategory = categories.find { it.sortOrder == newSortOrder }
if (newCategory != null) {
liveSelectedCategoryId.update { newCategory.categoryId }
categories.find { it.sortOrder == newSortOrder}
selectedCategoryId.update { newCategory.categoryId }
categories.find { it.sortOrder == newSortOrder }
} else {
liveSelectedCategoryId.update { categories.first().categoryId }
selectedCategoryId.update { categories.first().categoryId }
categories.first()
}
} else {
Expand All @@ -51,45 +53,41 @@ class PresetsViewModel(
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), null)
val selectedCategoryLiveData: LiveData<Category?> = selectedCategory.asLiveData()

val currentPhrases: LiveData<List<Phrase?>> = liveSelectedCategoryId
.map(::mapCategoryIdToPhrases)
.asLiveData()

private suspend fun mapCategoryIdToPhrases(categoryId: String?): List<Phrase?> {
if (categoryId == null) return emptyList()
return idlingResourceContainer.run {
val phrases: MutableList<Phrase?> = phrasesUseCase.getPhrasesForCategory(categoryId)
.run {
val currentPhrases: LiveData<List<Phrase?>> = selectedCategoryId
.filterNotNull()
.flatMapLatest { categoryId ->
phrasesUseCase.getPhrasesForCategoryFlow(categoryId).map { phrases ->
val phrasesToReturn = phrases.run {
if (categoryId != PresetCategories.RECENTS.id) {
sortedBy { it.sortOrder }
} else {
this
}
}.toMutableList<Phrase?>()
if (categoryId != PresetCategories.RECENTS.id && categoryId != PresetCategories.USER_KEYPAD.id && phrases.isNotEmpty()) {
//Add null to end of normal non empty category phrase list for the "+ Add Phrase" button
phrasesToReturn.add(null)
}
.toMutableList()
//Add null to end of normal non empty category phrase list for the "+ Add Phrase" button
if (categoryId != PresetCategories.RECENTS.id && categoryId != PresetCategories.USER_KEYPAD.id && phrases.isNotEmpty()) {
phrases.add(null)
phrasesToReturn
}
phrases
}
}
.asLiveData()

private val liveNavToAddPhrase = MutableLiveData<Boolean>()
val navToAddPhrase: LiveData<Boolean> = liveNavToAddPhrase

init {
viewModelScope.launch {
idlingResourceContainer.run {
liveSelectedCategoryId.update {
selectedCategoryId.update {
categoriesUseCase.categories().first().first().categoryId
}
}
}
}

fun onCategorySelected(categoryId: String) {
liveSelectedCategoryId.update { categoryId }
selectedCategoryId.update { categoryId }
}

fun addToRecents(phrase: Phrase) {
Expand Down
10 changes: 2 additions & 8 deletions app/src/main/java/com/willowtree/vocable/room/PhraseDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,10 @@ interface PhraseDao {
suspend fun updatePhraseLocalizedUtterance(phraseLocalizedUtterance: PhraseLocalizedUtterance)

@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>
fun getRecentPhrases(): Flow<List<PhraseDto>>

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

@Query("SELECT * FROM Phrase WHERE phrase_id == :phraseId")
suspend fun getPhrase(phraseId: String): PhraseDto?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,13 @@ class RoomStoredPhrasesRepository(
)
}

override suspend fun getRecentPhrases(): List<Phrase> {
return database.phraseDao().getRecentPhrases().map { it.asPhrase() }
}

override fun getRecentPhrasesFlow(): Flow<List<Phrase>> {
return database.phraseDao().getRecentPhrasesFlow()
return database.phraseDao().getRecentPhrases()
.map { phraseList -> phraseList.map { it.asPhrase() } }
}

override suspend fun getPhrasesForCategory(categoryId: String): List<Phrase> {
return database.phraseDao().getPhrasesForCategory(categoryId).map { it.asPhrase() }
}

override fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<Phrase>> {
return database.phraseDao().getPhrasesForCategoryFlow(categoryId)
return database.phraseDao().getPhrasesForCategory(categoryId)
.map { phraseList -> phraseList.map { it.asPhrase() } }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import kotlinx.coroutines.flow.Flow
interface StoredPhrasesRepository {
suspend fun addPhrase(phrase: PhraseDto)
suspend fun updatePhraseLastSpokenTime(phraseId: String)
suspend fun getRecentPhrases(): List<Phrase>
fun getRecentPhrasesFlow(): Flow<List<Phrase>>
suspend fun getPhrasesForCategory(categoryId: String): List<Phrase>
fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<Phrase>>
suspend fun getPhrase(phraseId: String): Phrase?
suspend fun updatePhrase(phrase: PhraseDto)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.willowtree.vocable.room.PhraseDto
import com.willowtree.vocable.utils.locale.LocalesWithText
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf

class FakePhrasesUseCase : IPhrasesUseCase {

Expand Down Expand Up @@ -41,7 +42,7 @@ class FakePhrasesUseCase : IPhrasesUseCase {
}

override fun getPhrasesForCategoryFlow(categoryId: String): Flow<List<Phrase>> {
TODO("Not yet implemented")
return flowOf(_categoriesToPhrases[categoryId]!!.map { it.asPhrase() })
}

override suspend fun updatePhraseLastSpokenTime(phraseId: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.willowtree.vocable.presets

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import app.cash.turbine.test
import com.willowtree.vocable.FakeCategoriesUseCase
import com.willowtree.vocable.FakePhrasesUseCase
import com.willowtree.vocable.MainDispatcherRule
Expand All @@ -11,7 +12,6 @@ import com.willowtree.vocable.utils.IdlingResourceContainerImpl
import com.willowtree.vocable.utils.locale.LocalesWithText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
Expand Down Expand Up @@ -94,24 +94,17 @@ class PresetsViewModelTest {
val vm = createViewModel()
vm.onCategorySelected("1")

//TODO: PK - Turbine may make this less painful, punting for now
var category: Category? = null
val job = launch {
vm.selectedCategory.collect {
category = it
}
vm.selectedCategory.test {
assertEquals(
Category.StoredCategory(
categoryId = "1",
localizedName = LocalesWithText(mapOf("en_US" to "category")),
hidden = false,
sortOrder = 0
),
awaitItem()
)
}
job.cancel()

assertEquals(
Category.StoredCategory(
categoryId = "1",
localizedName = LocalesWithText(mapOf("en_US" to "category")),
hidden = false,
sortOrder = 0
),
category
)
}

@OptIn(ExperimentalCoroutinesApi::class)
Expand All @@ -138,24 +131,17 @@ class PresetsViewModelTest {

vm.onCategorySelected("1")

var category: Category? = null
val job = launch {
vm.selectedCategory.collect {
category = it
}
vm.selectedCategory.test {
assertEquals(
Category.StoredCategory(
categoryId = "2",
localizedName = LocalesWithText(mapOf("en_US" to "second category")),
hidden = false,
sortOrder = 1
),
awaitItem()
)
}
job.cancel()

assertEquals(
Category.StoredCategory(
categoryId = "2",
localizedName = LocalesWithText(mapOf("en_US" to "second category")),
hidden = false,
sortOrder = 1
),
category
)

}

@OptIn(ExperimentalCoroutinesApi::class)
Expand Down Expand Up @@ -188,23 +174,17 @@ class PresetsViewModelTest {

vm.onCategorySelected("3")

var category: Category? = null
val job = launch {
vm.selectedCategory.collect {
category = it
}
vm.selectedCategory.test {
assertEquals(
Category.StoredCategory(
categoryId = "1",
localizedName = LocalesWithText(mapOf("en_US" to "category")),
hidden = false,
sortOrder = 0
),
awaitItem()
)
}
job.cancel()

assertEquals(
Category.StoredCategory(
categoryId = "1",
localizedName = LocalesWithText(mapOf("en_US" to "category")),
hidden = false,
sortOrder = 0
),
category
)
}

@OptIn(ExperimentalCoroutinesApi::class)
Expand Down Expand Up @@ -237,23 +217,17 @@ class PresetsViewModelTest {

vm.onCategorySelected("3")

var category: Category? = null
val job = launch {
vm.selectedCategory.collect {
category = it
}
vm.selectedCategory.test {
assertEquals(
Category.StoredCategory(
categoryId = "2",
localizedName = LocalesWithText(mapOf("en_US" to "second category")),
hidden = false,
sortOrder = 1
),
awaitItem()
)
}
job.cancel()

assertEquals(
Category.StoredCategory(
categoryId = "2",
localizedName = LocalesWithText(mapOf("en_US" to "second category")),
hidden = false,
sortOrder = 1
),
category
)
}

@Test
Expand Down

0 comments on commit f9e97fb

Please sign in to comment.