diff --git a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolFilters.kt b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolFilters.kt index 32e0061872..8402dffa73 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolFilters.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolFilters.kt @@ -1,6 +1,7 @@ package org.cru.godtools.ui.dashboard.tools import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -12,11 +13,11 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Search import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ElevatedButton import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SearchBar import androidx.compose.material3.Text @@ -29,6 +30,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -69,8 +71,9 @@ internal fun ToolFilters( } @Composable +@VisibleForTesting @OptIn(ExperimentalMaterial3Api::class) -private fun CategoryFilter(filters: ToolsScreen.Filters, modifier: Modifier = Modifier) { +internal fun CategoryFilter(filters: ToolsScreen.Filters, modifier: Modifier = Modifier) { val categories by rememberUpdatedState(filters.categories) val selectedCategory by rememberUpdatedState(filters.selectedCategory) val eventSink by rememberUpdatedState(filters.eventSink) @@ -93,20 +96,28 @@ private fun CategoryFilter(filters: ToolsScreen.Filters, modifier: Modifier = Mo DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, - modifier = Modifier.heightIn(max = DROPDOWN_MAX_HEIGHT), + modifier = Modifier + .heightIn(max = DROPDOWN_MAX_HEIGHT) + .testTag(TEST_TAG_FILTER_DROPDOWN) ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.dashboard_tools_section_filter_category_any)) }, + FilterMenuItem( + label = stringResource(R.string.dashboard_tools_section_filter_category_any), + supportingText = stringResource(R.string.dashboard_tools_section_filter_available_tools_all), onClick = { eventSink(ToolsScreen.FiltersEvent.SelectCategory(null)) expanded = false } ) - categories.forEach { - DropdownMenuItem( - text = { Text(getToolCategoryName(it, LocalContext.current)) }, + categories.forEach { (category, count) -> + FilterMenuItem( + label = getToolCategoryName(category, LocalContext.current), + supportingText = pluralStringResource( + R.plurals.dashboard_tools_section_filter_available_tools, + count, + count, + ), onClick = { - eventSink(ToolsScreen.FiltersEvent.SelectCategory(it)) + eventSink(ToolsScreen.FiltersEvent.SelectCategory(category)) expanded = false } ) @@ -163,8 +174,9 @@ internal fun LanguageFilter(filters: ToolsScreen.Filters, modifier: Modifier = M content = {}, modifier = Modifier.padding(horizontal = 12.dp) ) - DropdownMenuItem( - text = { Text(stringResource(R.string.dashboard_tools_section_filter_language_any)) }, + FilterMenuItem( + label = stringResource(R.string.dashboard_tools_section_filter_language_any), + supportingText = stringResource(R.string.dashboard_tools_section_filter_available_tools_all), onClick = { eventSink(ToolsScreen.FiltersEvent.SelectLanguage(null)) expanded = false @@ -172,9 +184,14 @@ internal fun LanguageFilter(filters: ToolsScreen.Filters, modifier: Modifier = M ) } - items(languages, key = { it.code }) { - DropdownMenuItem( - text = { LanguageName(it) }, + items(languages, key = { (it) -> it.code }) { (it, count) -> + FilterMenuItem( + label = { LanguageName(it) }, + supportingText = pluralStringResource( + R.plurals.dashboard_tools_section_filter_available_tools, + count, + count, + ), onClick = { eventSink(ToolsScreen.FiltersEvent.SelectLanguage(it.code)) expanded = false @@ -185,3 +202,28 @@ internal fun LanguageFilter(filters: ToolsScreen.Filters, modifier: Modifier = M } } } + +@Composable +private fun FilterMenuItem( + label: String, + supportingText: String?, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) = FilterMenuItem( + label = { Text(label) }, + supportingText = supportingText, + onClick = onClick, + modifier = modifier, +) + +@Composable +private fun FilterMenuItem( + label: @Composable () -> Unit, + supportingText: String?, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) = ListItem( + headlineContent = label, + supportingContent = supportingText?.let { { Text(it) } }, + modifier = modifier.clickable(onClick = onClick) +) diff --git a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenter.kt b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenter.kt index 4b3e527d1a..e640f43801 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenter.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenter.kt @@ -21,19 +21,23 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.ACTION_OPEN_TOOL_DETAILS import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.SOURCE_SPOTLIGHT import org.cru.godtools.base.Settings import org.cru.godtools.db.repository.LanguagesRepository import org.cru.godtools.db.repository.ToolsRepository +import org.cru.godtools.db.repository.TranslationsRepository import org.cru.godtools.model.Language import org.cru.godtools.model.Language.Companion.filterByDisplayAndNativeName import org.cru.godtools.model.Tool import org.cru.godtools.ui.banner.BannerType +import org.cru.godtools.ui.dashboard.tools.ToolsScreen.Filters.Filter import org.cru.godtools.ui.tooldetails.ToolDetailsScreen import org.cru.godtools.ui.tools.ToolCard import org.cru.godtools.ui.tools.ToolCardPresenter @@ -47,6 +51,7 @@ class ToolsPresenter @AssistedInject constructor( private val toolCardPresenter: ToolCardPresenter, private val languagesRepository: LanguagesRepository, private val toolsRepository: ToolsRepository, + private val translationsRepository: TranslationsRepository, @Assisted private val navigator: Navigator, ) : Presenter { @Composable @@ -114,19 +119,23 @@ class ToolsPresenter @AssistedInject constructor( } @Composable - private fun rememberFilterCategories(selectedLanguage: Locale?): List { + private fun rememberFilterCategories(selectedLanguage: Locale?): List> { val filteredToolsFlow = rememberFilteredToolsFlow(language = selectedLanguage) return remember(filteredToolsFlow) { - filteredToolsFlow.map { it.mapNotNull { it.category }.distinct() } + filteredToolsFlow.map { + it.groupBy { it.category } + .mapNotNull { (category, tools) -> category?.let { Filter(category, tools.size) } } + } }.collectAsState(emptyList()).value } @Composable @OptIn(ExperimentalCoroutinesApi::class) - private fun rememberFilterLanguages(category: String?, query: String): List { + private fun rememberFilterLanguages(category: String?, query: String): List> { val categoryFlow = remember { MutableStateFlow(category) }.apply { value = category } val queryFlow = remember { MutableStateFlow(query) }.apply { value = query } + val toolsFlow = rememberFilteredToolsFlow(category = category) return remember { val languagesFlow = categoryFlow @@ -140,8 +149,25 @@ class ToolsPresenter @AssistedInject constructor( languages.sortedWith(Language.displayNameComparator(context, appLang)) } - combine(languagesFlow, settings.appLanguageFlow, queryFlow) { languages, appLang, query -> - languages.filterByDisplayAndNativeName(query, context, appLang) + val toolCountsFlow = toolsFlow + .map { it.mapNotNullTo(mutableSetOf()) { it.code } } + .distinctUntilChanged() + .flatMapLatest { translationsRepository.getTranslationsFlowForTools(it) } + .map { translations -> + translations + .groupBy { it.languageCode } + .mapValues { it.value.distinctBy { it.toolCode }.count() } + } + + combine( + languagesFlow, + settings.appLanguageFlow, + queryFlow, + toolCountsFlow, + ) { languages, appLang, query, toolCounts -> + languages + .filterByDisplayAndNativeName(query, context, appLang) + .map { Filter(it, toolCounts[it.code] ?: 0) } } }.collectAsState(emptyList()).value } diff --git a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsScreen.kt b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsScreen.kt index 26c056f5c2..bb48999973 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsScreen.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsScreen.kt @@ -21,13 +21,15 @@ data object ToolsScreen : Screen { ) : CircuitUiState data class Filters( - val categories: List = emptyList(), + val categories: List> = emptyList(), val selectedCategory: String? = null, - val languages: List = emptyList(), + val languages: List> = emptyList(), val languageQuery: String = "", val selectedLanguage: Language? = null, val eventSink: (FiltersEvent) -> Unit = {}, - ) : CircuitUiState + ) : CircuitUiState { + data class Filter(val item: T, val count: Int) + } sealed interface Event : CircuitUiEvent { data class OpenToolDetails(val tool: String, val source: String? = null) : Event diff --git a/app/src/main/res/values/strings_dashboard.xml b/app/src/main/res/values/strings_dashboard.xml index 8800628b07..0e8d16c80a 100644 --- a/app/src/main/res/values/strings_dashboard.xml +++ b/app/src/main/res/values/strings_dashboard.xml @@ -51,6 +51,11 @@ An online version can be found at https://knowgod.com/ Filter Any category Any language + + %1$d Tool available + %1$d Tools available + + All Tools available Categories All Tools Tool Spotlight diff --git a/app/src/test/kotlin/org/cru/godtools/ExternalSingletonsModule.kt b/app/src/test/kotlin/org/cru/godtools/ExternalSingletonsModule.kt index bda7e0e6cb..4ca8b7e0c2 100644 --- a/app/src/test/kotlin/org/cru/godtools/ExternalSingletonsModule.kt +++ b/app/src/test/kotlin/org/cru/godtools/ExternalSingletonsModule.kt @@ -102,7 +102,11 @@ class ExternalSingletonsModule { @get:Provides val trainingTipsRepository: TrainingTipsRepository by lazy { mockk() } @get:Provides - val translationsRepository: TranslationsRepository by lazy { mockk() } + val translationsRepository: TranslationsRepository by lazy { + mockk { + every { getTranslationsFlowForTools(any()) } returns flowOf(emptyList()) + } + } @get:Provides val userRepository: UserRepository by lazy { mockk() } @get:Provides diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolFiltersTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolFiltersTest.kt index 2a5989f44f..10b691192f 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolFiltersTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolFiltersTest.kt @@ -10,8 +10,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.slack.circuit.test.TestEventSink import java.util.Locale import org.cru.godtools.model.Language +import org.cru.godtools.model.Tool +import org.cru.godtools.ui.dashboard.tools.ToolsScreen.Filters.Filter import org.junit.Rule import org.junit.Test +import org.junit.experimental.categories.Categories.CategoryFilter import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -23,9 +26,118 @@ class ToolFiltersTest { private val events = TestEventSink() - // region: LanguagesFilter + // region CategoriesFilter @Test - fun `LanguagesFilter() - Shows selectedLanguage`() { + fun `CategoryFilter() - Shows selected category`() { + composeTestRule.setContent { + CategoryFilter( + ToolsScreen.Filters( + selectedCategory = Tool.CATEGORY_GOSPEL, + eventSink = events, + ), + ) + } + + composeTestRule.onNodeWithText("Gospel", substring = true, ignoreCase = true).assertExists() + events.assertNoEvents() + } + + @Test + fun `CategoryFilter() - Shows Any Category when no category is specified`() { + composeTestRule.setContent { + CategoryFilter( + ToolsScreen.Filters( + selectedCategory = null, + eventSink = events, + ), + ) + } + + composeTestRule.onNodeWithText("Any category", substring = true, ignoreCase = true).assertExists() + events.assertNoEvents() + } + + @Test + fun `CategoryFilter() - Dropdown Menu - Show when button is clicked`() { + composeTestRule.setContent { + CategoryFilter(ToolsScreen.Filters(eventSink = events)) + } + + // dropdown menu not shown + composeTestRule.onNodeWithTag(TEST_TAG_FILTER_DROPDOWN).assertDoesNotExist() + + // click button to show dropdown + composeTestRule.onNode(hasClickAction()).performClick() + composeTestRule.onNodeWithTag(TEST_TAG_FILTER_DROPDOWN).assertExists() + events.assertNoEvents() + } + + @Test + fun `CategoryFilter() - Dropdown Menu - Show categories`() { + composeTestRule.setContent { + CategoryFilter( + filters = ToolsScreen.Filters( + categories = listOf( + Filter(Tool.CATEGORY_GOSPEL, 1), + Filter(Tool.CATEGORY_ARTICLES, 1) + ), + eventSink = events, + ), + ) + } + composeTestRule.onNode(hasClickAction()).performClick() + + composeTestRule.onNodeWithText("Growth", substring = true, ignoreCase = true).assertDoesNotExist() + composeTestRule.onNodeWithText("Articles", substring = true, ignoreCase = true).assertExists() + composeTestRule.onNodeWithText("Gospel", substring = true, ignoreCase = true).assertExists() + events.assertNoEvents() + } + + @Test + fun `CategoryFilter() - Dropdown Menu - Select 'Any category' option`() { + composeTestRule.setContent { + CategoryFilter( + filters = ToolsScreen.Filters( + selectedCategory = Tool.CATEGORY_GOSPEL, + categories = listOf( + Filter(Tool.CATEGORY_GOSPEL, 1), + Filter(Tool.CATEGORY_ARTICLES, 1) + ), + eventSink = events, + ), + ) + } + composeTestRule.onNode(hasClickAction()).performClick() + + composeTestRule.onNodeWithText("Any category", substring = true, ignoreCase = true).performClick() + composeTestRule.onNodeWithTag(TEST_TAG_FILTER_DROPDOWN).assertDoesNotExist() + events.assertEvent(ToolsScreen.FiltersEvent.SelectCategory(null)) + } + + @Test + fun `CategoryFilter() - Dropdown Menu - Select a category`() { + composeTestRule.setContent { + CategoryFilter( + filters = ToolsScreen.Filters( + categories = listOf( + Filter(Tool.CATEGORY_GOSPEL, 1), + Filter(Tool.CATEGORY_ARTICLES, 1) + ), + eventSink = events, + ), + ) + } + composeTestRule.onNode(hasClickAction()).performClick() + + composeTestRule.onNodeWithText("Gospel", substring = true, ignoreCase = true).performClick() + composeTestRule.onNodeWithTag(TEST_TAG_FILTER_DROPDOWN).assertDoesNotExist() + events.assertEvent(ToolsScreen.FiltersEvent.SelectCategory(Tool.CATEGORY_GOSPEL)) + } + // endregion CategoryFilter + + // region LanguageFilter + @Test + fun `LanguageFilter() - Shows selectedLanguage`() { composeTestRule.setContent { LanguageFilter( ToolsScreen.Filters( @@ -40,7 +152,7 @@ class ToolFiltersTest { } @Test - fun `LanguagesFilter() - Shows Any Language when no language is specified`() { + fun `LanguageFilter() - Shows Any Language when no language is specified`() { composeTestRule.setContent { LanguageFilter( ToolsScreen.Filters( @@ -55,7 +167,7 @@ class ToolFiltersTest { } @Test - fun `LanguagesFilter() - Dropdown Menu - Show when button is clicked`() { + fun `LanguageFilter() - Dropdown Menu - Show when button is clicked`() { composeTestRule.setContent { LanguageFilter(ToolsScreen.Filters(eventSink = events)) } @@ -70,13 +182,13 @@ class ToolFiltersTest { } @Test - fun `LanguagesFilter() - Dropdown Menu - Show languages`() { + fun `LanguageFilter() - Dropdown Menu - Show languages`() { composeTestRule.setContent { LanguageFilter( filters = ToolsScreen.Filters( languages = listOf( - Language(Locale.FRENCH), - Language(Locale.GERMAN), + Filter(Language(Locale.FRENCH), 1), + Filter(Language(Locale.GERMAN), 1), ), eventSink = events, ), @@ -91,14 +203,14 @@ class ToolFiltersTest { } @Test - fun `LanguagesFilter() - Dropdown Menu - Select "Any language" option`() { + fun `LanguageFilter() - Dropdown Menu - Select 'Any language' option`() { composeTestRule.setContent { LanguageFilter( filters = ToolsScreen.Filters( selectedLanguage = Language(Locale.FRENCH), languages = listOf( - Language(Locale.FRENCH), - Language(Locale.GERMAN) + Filter(Language(Locale.FRENCH), 1), + Filter(Language(Locale.GERMAN), 1), ), eventSink = events, ), @@ -115,13 +227,13 @@ class ToolFiltersTest { } @Test - fun `LanguagesFilter() - Dropdown Menu - Select a language`() { + fun `LanguageFilter() - Dropdown Menu - Select a language`() { composeTestRule.setContent { LanguageFilter( filters = ToolsScreen.Filters( languages = listOf( - Language(Locale.FRENCH), - Language(Locale.GERMAN) + Filter(Language(Locale.FRENCH), 1), + Filter(Language(Locale.GERMAN), 1), ), eventSink = events, ), @@ -136,5 +248,5 @@ class ToolFiltersTest { ToolsScreen.FiltersEvent.SelectLanguage(Locale.FRENCH) ) } - // endregion: LanguagesFilter + // endregion LanguageFilter } diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenterTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenterTest.kt index e1839f1db7..697172ae13 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenterTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenterTest.kt @@ -22,10 +22,14 @@ import org.cru.godtools.TestUtils.clearAndroidUiDispatcher import org.cru.godtools.base.Settings import org.cru.godtools.db.repository.LanguagesRepository import org.cru.godtools.db.repository.ToolsRepository +import org.cru.godtools.db.repository.TranslationsRepository import org.cru.godtools.model.Language import org.cru.godtools.model.Tool +import org.cru.godtools.model.Translation import org.cru.godtools.model.randomTool +import org.cru.godtools.model.randomTranslation import org.cru.godtools.ui.banner.BannerType +import org.cru.godtools.ui.dashboard.tools.ToolsScreen.Filters.Filter import org.cru.godtools.ui.tools.ToolCardPresenter import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -56,6 +60,9 @@ class ToolsPresenterTest { every { getNormalToolsFlowByLanguage(any()) } returns flowOf(emptyList()) every { getMetaToolsFlow() } returns metatoolsFlow } + private val translationsRepository: TranslationsRepository = mockk { + every { getTranslationsFlowForTools(any()) } returns flowOf(emptyList()) + } // TODO: figure out how to mock ToolCardPresenter private val toolCardPresenter = ToolCardPresenter( @@ -77,6 +84,7 @@ class ToolsPresenterTest { toolCardPresenter = toolCardPresenter, languagesRepository = languagesRepository, toolsRepository = toolsRepository, + translationsRepository = translationsRepository, navigator = navigator, ) } @@ -148,7 +156,7 @@ class ToolsPresenterTest { presenter.test { assertEquals( - listOf(Tool.CATEGORY_GOSPEL, Tool.CATEGORY_ARTICLES), + listOf(Filter(Tool.CATEGORY_GOSPEL, 1), Filter(Tool.CATEGORY_ARTICLES, 1)), expectMostRecentItem().filters.categories ) } @@ -162,7 +170,7 @@ class ToolsPresenterTest { ) presenter.test { - assertEquals(listOf(Tool.CATEGORY_GOSPEL), expectMostRecentItem().filters.categories) + assertEquals(listOf(Filter(Tool.CATEGORY_GOSPEL, 2)), expectMostRecentItem().filters.categories) } } @@ -175,7 +183,7 @@ class ToolsPresenterTest { presenter.test { assertEquals( - listOf(Tool.CATEGORY_ARTICLES, Tool.CATEGORY_GOSPEL), + listOf(Filter(Tool.CATEGORY_ARTICLES, 1), Filter(Tool.CATEGORY_GOSPEL, 1)), expectMostRecentItem().filters.categories ) } @@ -191,7 +199,7 @@ class ToolsPresenterTest { presenter.test { metatoolsFlow.value = listOf(meta) - assertEquals(listOf(Tool.CATEGORY_GOSPEL), expectMostRecentItem().filters.categories) + assertEquals(listOf(Filter(Tool.CATEGORY_GOSPEL, 1)), expectMostRecentItem().filters.categories) } } @@ -203,7 +211,7 @@ class ToolsPresenterTest { ) presenter.test { - assertEquals(listOf(Tool.CATEGORY_GOSPEL), expectMostRecentItem().filters.categories) + assertEquals(listOf(Filter(Tool.CATEGORY_GOSPEL, 1)), expectMostRecentItem().filters.categories) } } // endregion State.filters.categories @@ -215,7 +223,7 @@ class ToolsPresenterTest { presenter.test { languagesFlow.value = languages - assertEquals(languages, expectMostRecentItem().filters.languages) + assertEquals(languages.map { Filter(it, 0) }, expectMostRecentItem().filters.languages) } verifyAll { @@ -231,7 +239,32 @@ class ToolsPresenterTest { awaitItem().filters.eventSink(ToolsScreen.FiltersEvent.SelectCategory(Tool.CATEGORY_GOSPEL)) gospelLanguagesFlow.value = languages - assertEquals(languages, expectMostRecentItem().filters.languages) + assertEquals(languages.map { Filter(it, 0) }, expectMostRecentItem().filters.languages) + } + } + + @Test + fun `State - filters - languages - include tool count`() = runTest { + val translationsFlow = MutableStateFlow(emptyList()) + every { translationsRepository.getTranslationsFlowForTools(setOf("tool1", "tool2")) } returns translationsFlow + + presenter.test { + toolsFlow.value = listOf( + randomTool("tool1", metatoolCode = null, isHidden = false), + randomTool("tool2", metatoolCode = null, isHidden = false), + ) + translationsFlow.value = listOf( + randomTranslation("tool1", Locale.ENGLISH), + randomTranslation("tool1", Locale.FRENCH), + randomTranslation("tool2", Locale.ENGLISH, version = 1), + randomTranslation("tool2", Locale.ENGLISH, version = 2), + ) + languagesFlow.value = listOf(Language(Locale.ENGLISH), Language(Locale.FRENCH)) + + assertEquals( + listOf(Filter(Language(Locale.ENGLISH), 2), Filter(Language(Locale.FRENCH), 1)), + expectMostRecentItem().filters.languages + ) } } // endregion State.filters.languages diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/repository/TranslationsRepository.kt b/library/db/src/main/kotlin/org/cru/godtools/db/repository/TranslationsRepository.kt index 5ce7d3d84f..0098993ae1 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/repository/TranslationsRepository.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/repository/TranslationsRepository.kt @@ -14,9 +14,9 @@ interface TranslationsRepository { suspend fun getTranslationsForLanguages(languages: Collection): List fun getTranslationsFlow(): Flow> - fun getTranslationsFlowForTool(tool: String) = getTranslationsForToolsFlow(listOf(tool)) - fun getTranslationsForToolsFlow(tools: Collection): Flow> - fun getTranslationsForToolsAndLocalesFlow( + fun getTranslationsFlowForTool(tool: String) = getTranslationsFlowForTools(listOf(tool)) + fun getTranslationsFlowForTools(tools: Collection): Flow> + fun getTranslationsFlowForToolsAndLocales( tools: Collection, locales: Collection, ): Flow> diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/TranslationsDao.kt b/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/TranslationsDao.kt index bd14b9d604..183f576db6 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/TranslationsDao.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/TranslationsDao.kt @@ -27,9 +27,9 @@ internal interface TranslationsDao { @Query("SELECT * FROM translations") fun getTranslationsFlow(): Flow> @Query("SELECT * FROM translations WHERE tool IN (:tools)") - fun getTranslationsForToolsFlow(tools: Collection): Flow> + fun getTranslationsFlowForTools(tools: Collection): Flow> @Query("SELECT * FROM translations WHERE tool IN (:tools) AND locale IN (:locales)") - fun getTranslationsForToolsAndLocalesFlow( + fun getTranslationsFlowForToolsAndLocales( tools: Collection, locales: Collection, ): Flow> diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/TranslationsRoomRepository.kt b/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/TranslationsRoomRepository.kt index b9fc524e78..0b75dd5fb6 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/TranslationsRoomRepository.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/TranslationsRoomRepository.kt @@ -42,10 +42,10 @@ internal abstract class TranslationsRoomRepository(private val db: GodToolsRoomD dao.getTranslationsForLanguages(languages).map { it.toModel() } override fun getTranslationsFlow() = dao.getTranslationsFlow().map { it.map { it.toModel() } } - override fun getTranslationsForToolsFlow(tools: Collection) = - dao.getTranslationsForToolsFlow(tools).map { it.map { it.toModel() } } - override fun getTranslationsForToolsAndLocalesFlow(tools: Collection, locales: Collection) = - dao.getTranslationsForToolsAndLocalesFlow(tools, locales).map { it.map { it.toModel() } } + override fun getTranslationsFlowForTools(tools: Collection) = + dao.getTranslationsFlowForTools(tools).map { it.map { it.toModel() } } + override fun getTranslationsFlowForToolsAndLocales(tools: Collection, locales: Collection) = + dao.getTranslationsFlowForToolsAndLocales(tools, locales).map { it.map { it.toModel() } } override fun translationsChangeFlow(): Flow = db.changeFlow("translations") diff --git a/library/db/src/test/kotlin/org/cru/godtools/db/repository/TranslationsRepositoryIT.kt b/library/db/src/test/kotlin/org/cru/godtools/db/repository/TranslationsRepositoryIT.kt index 12abb34bec..416f740fe8 100644 --- a/library/db/src/test/kotlin/org/cru/godtools/db/repository/TranslationsRepositoryIT.kt +++ b/library/db/src/test/kotlin/org/cru/godtools/db/repository/TranslationsRepositoryIT.kt @@ -201,7 +201,7 @@ abstract class TranslationsRepositoryIT { } // endregion getTranslationsFlow() - // region getTranslationsForToolFlow() + // region getTranslationsFlowForTool() @Test fun `getTranslationsForToolFlow()`() = testScope.runTest { val trans1 = randomTranslation(TOOL, Locale.ENGLISH) @@ -229,16 +229,16 @@ abstract class TranslationsRepositoryIT { } } } - // endregion getTranslationsForToolFlow() + // endregion getTranslationsFlowForTool() - // region getTranslationsForToolsAndLocalesFlow() + // region getTranslationsFlowForToolsAndLocales() @Test fun `getTranslationsForToolsAndLocalesFlow()`() = testScope.runTest { val trans1 = randomTranslation(TOOL, Locale.ENGLISH) val trans2 = randomTranslation(TOOL, Locale.FRENCH) val trans3 = randomTranslation(TOOL, Locale.GERMAN) - repository.getTranslationsForToolsAndLocalesFlow(setOf(TOOL), setOf(Locale.ENGLISH, Locale.GERMAN)).test { + repository.getTranslationsFlowForToolsAndLocales(setOf(TOOL), setOf(Locale.ENGLISH, Locale.GERMAN)).test { repository.storeInitialTranslations(listOf(randomTranslation(TOOL2))) runCurrent() assertTrue(expectMostRecentItem().isEmpty()) @@ -268,7 +268,7 @@ abstract class TranslationsRepositoryIT { } } } - // endregion getTranslationsForToolsAndLocalesFlow() + // endregion getTranslationsFlowForToolsAndLocales() // region translationsChangeFlow() @Test diff --git a/library/download-manager/src/main/kotlin/org/cru/godtools/downloadmanager/GodToolsDownloadManager.kt b/library/download-manager/src/main/kotlin/org/cru/godtools/downloadmanager/GodToolsDownloadManager.kt index 236af4a7dc..934b4a1199 100644 --- a/library/download-manager/src/main/kotlin/org/cru/godtools/downloadmanager/GodToolsDownloadManager.kt +++ b/library/download-manager/src/main/kotlin/org/cru/godtools/downloadmanager/GodToolsDownloadManager.kt @@ -521,7 +521,7 @@ class GodToolsDownloadManager @VisibleForTesting internal constructor( .map { it.mapNotNullTo(mutableSetOf()) { it.code } } .distinctUntilChanged() .combineTransformLatest(languages) { t, l -> - emitAll(translationsRepository.getTranslationsForToolsAndLocalesFlow(t, l)) + emitAll(translationsRepository.getTranslationsFlowForToolsAndLocales(t, l)) } .map { it.filterNot { it.isDownloaded } diff --git a/library/download-manager/src/test/kotlin/org/cru/godtools/downloadmanager/GodToolsDownloadManagerDispatcherTest.kt b/library/download-manager/src/test/kotlin/org/cru/godtools/downloadmanager/GodToolsDownloadManagerDispatcherTest.kt index 8dc14b7c31..f453d71ea2 100644 --- a/library/download-manager/src/test/kotlin/org/cru/godtools/downloadmanager/GodToolsDownloadManagerDispatcherTest.kt +++ b/library/download-manager/src/test/kotlin/org/cru/godtools/downloadmanager/GodToolsDownloadManagerDispatcherTest.kt @@ -67,7 +67,7 @@ class GodToolsDownloadManagerDispatcherTest { } private val translationsRepository: TranslationsRepository by lazy { mockk { - every { getTranslationsForToolsAndLocalesFlow(any(), any()) } returns flowOf(emptyList()) + every { getTranslationsFlowForToolsAndLocales(any(), any()) } returns flowOf(emptyList()) } } private val testScope = TestScope() @@ -94,7 +94,7 @@ class GodToolsDownloadManagerDispatcherTest { val translationsFlow = MutableSharedFlow>(replay = 1) every { - translationsRepository.getTranslationsForToolsAndLocalesFlow( + translationsRepository.getTranslationsFlowForToolsAndLocales( tools = match { it.toSet() == setOf("tool1", "tool2") }, locales = match { it.toSet() == setOf(Locale.FRENCH) } ) @@ -105,7 +105,7 @@ class GodToolsDownloadManagerDispatcherTest { appLanguageFlow.emit(Locale.FRENCH) runCurrent() verifyAll { - translationsRepository.getTranslationsForToolsAndLocalesFlow( + translationsRepository.getTranslationsFlowForToolsAndLocales( tools = match { it.toSet() == setOf("tool1", "tool2") }, locales = match { it.toSet() == setOf(Locale.FRENCH) } ) @@ -125,7 +125,7 @@ class GodToolsDownloadManagerDispatcherTest { fun `Favorite Tools downloadLatestPublishedTranslation() - default language`() = testScope.runTest { val translationsFlow = MutableSharedFlow>(replay = 1) every { - translationsRepository.getTranslationsForToolsAndLocalesFlow( + translationsRepository.getTranslationsFlowForToolsAndLocales( tools = match { it.toSet() == setOf("tool1", "tool2") }, locales = match { it.toSet() == setOf(Settings.defaultLanguage) } ) @@ -135,7 +135,7 @@ class GodToolsDownloadManagerDispatcherTest { favoriteToolsFlow.emit(listOf(Tool("tool1"), Tool("tool2"))) runCurrent() verifyAll { - translationsRepository.getTranslationsForToolsAndLocalesFlow( + translationsRepository.getTranslationsFlowForToolsAndLocales( tools = match { it.toSet() == setOf("tool1", "tool2") }, locales = match { it.toSet() == setOf(Settings.defaultLanguage) } ) @@ -157,7 +157,7 @@ class GodToolsDownloadManagerDispatcherTest { val translationsFlow = MutableSharedFlow>(replay = 1) every { - translationsRepository.getTranslationsForToolsAndLocalesFlow( + translationsRepository.getTranslationsFlowForToolsAndLocales( tools = match { it.toSet() == setOf("tool1", "tool2") }, locales = match { it.toSet() == setOf(Locale.FRENCH, Locale.GERMAN) } ) @@ -168,7 +168,7 @@ class GodToolsDownloadManagerDispatcherTest { pinnedLanguagesFlow.emit(listOf(Language(Locale.FRENCH), Language(Locale.GERMAN))) runCurrent() verifyAll { - translationsRepository.getTranslationsForToolsAndLocalesFlow( + translationsRepository.getTranslationsFlowForToolsAndLocales( tools = match { it.toSet() == setOf("tool1", "tool2") }, locales = match { it.toSet() == setOf(Locale.FRENCH, Locale.GERMAN) } ) diff --git a/ui/article-aem-renderer/src/main/kotlin/org/cru/godtools/article/aem/service/AemArticleManager.kt b/ui/article-aem-renderer/src/main/kotlin/org/cru/godtools/article/aem/service/AemArticleManager.kt index 7b6ef4848b..8bc33a4153 100644 --- a/ui/article-aem-renderer/src/main/kotlin/org/cru/godtools/article/aem/service/AemArticleManager.kt +++ b/ui/article-aem-renderer/src/main/kotlin/org/cru/godtools/article/aem/service/AemArticleManager.kt @@ -236,7 +236,7 @@ class AemArticleManager @VisibleForTesting internal constructor( .toSet() } .distinctUntilChanged() - .flatMapLatest { translationsRepository.getTranslationsForToolsFlow(it) } + .flatMapLatest { translationsRepository.getTranslationsFlowForTools(it) } .map { it.filter { it.isDownloaded } } .conflate() .onEach { aemArticleManager.processDownloadedTranslations(it) } diff --git a/ui/article-aem-renderer/src/test/kotlin/org/cru/godtools/article/aem/service/AemArticleManagerDispatcherTest.kt b/ui/article-aem-renderer/src/test/kotlin/org/cru/godtools/article/aem/service/AemArticleManagerDispatcherTest.kt index fbab81218c..a61efdb19e 100644 --- a/ui/article-aem-renderer/src/test/kotlin/org/cru/godtools/article/aem/service/AemArticleManagerDispatcherTest.kt +++ b/ui/article-aem-renderer/src/test/kotlin/org/cru/godtools/article/aem/service/AemArticleManagerDispatcherTest.kt @@ -51,7 +51,7 @@ class AemArticleManagerDispatcherTest { every { getNormalToolsFlow() } returns flowOf(emptyList()) } private val translationsRepository: TranslationsRepository = mockk { - every { getTranslationsForToolsFlow(any()) } returns translationsFlow + every { getTranslationsFlowForTools(any()) } returns translationsFlow } @Before