Skip to content

Commit

Permalink
generate the list of filter languages in the ToolsPresenter
Browse files Browse the repository at this point in the history
  • Loading branch information
frett committed Jan 3, 2024
1 parent 55f9115 commit dcd4daa
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.cru.godtools.ui.dashboard.tools

import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
Expand All @@ -13,9 +15,13 @@ import com.slack.circuit.runtime.presenter.Presenter
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.ACTION_OPEN_TOOL_DETAILS
Expand All @@ -24,6 +30,7 @@ import org.cru.godtools.base.Settings
import org.cru.godtools.db.repository.LanguagesRepository
import org.cru.godtools.db.repository.ToolsRepository
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.tooldetails.ToolDetailsScreen
Expand All @@ -32,6 +39,8 @@ import org.cru.godtools.ui.tools.ToolCardPresenter
import org.greenrobot.eventbus.EventBus

class ToolsPresenter @AssistedInject constructor(
@ApplicationContext
private val context: Context,
private val eventBus: EventBus,
private val settings: Settings,
private val toolCardPresenter: ToolCardPresenter,
Expand All @@ -43,9 +52,13 @@ class ToolsPresenter @AssistedInject constructor(
override fun present(): ToolsScreen.State {
val viewModel: ToolsViewModel = viewModel()

// selected category
val selectedCategory by viewModel.selectedCategory.collectAsState()

// selected language
val selectedLocale by viewModel.selectedLocale.collectAsState()
val selectedLanguage = rememberLanguage(selectedLocale)
val languageQuery by viewModel.languageQuery.collectAsState()

val eventSink: (ToolsScreen.Event) -> Unit = remember {
{
Expand All @@ -68,9 +81,9 @@ class ToolsPresenter @AssistedInject constructor(
spotlightTools = rememberSpotlightTools(secondLanguage = selectedLanguage, eventSink = eventSink),
filters = ToolsScreen.State.Filters(
categories = viewModel.categories.collectAsState().value,
selectedCategory = viewModel.selectedCategory.collectAsState().value,
languages = viewModel.languages.collectAsState().value,
languageQuery = viewModel.languageQuery.collectAsState().value,
selectedCategory = selectedCategory,
languages = rememberFilterLanguages(selectedCategory, languageQuery),
languageQuery = languageQuery,
selectedLanguage = selectedLanguage,
),
tools = viewModel.tools.collectAsState().value,
Expand All @@ -85,6 +98,27 @@ class ToolsPresenter @AssistedInject constructor(
.map { if (!it) BannerType.TOOL_LIST_FAVORITES else null }
}.collectAsState(null).value

@Composable
@VisibleForTesting
internal fun rememberFilterLanguages(selectedCategory: String?, query: String): List<Language> {
val appLanguage by settings.appLanguageFlow.collectAsState(settings.appLanguage)

val rawLanguages by remember(context, selectedCategory) {
combine(
when (selectedCategory) {
null -> languagesRepository.getLanguagesFlow()
else -> languagesRepository.getLanguagesFlowForToolCategory(selectedCategory)
},
settings.appLanguageFlow,
) { langs, appLang -> langs.sortedWith(Language.displayNameComparator(context, appLang)) }
.flowOn(Dispatchers.Default)
}.collectAsState(emptyList())

return remember(context, query) {
derivedStateOf { rawLanguages.filterByDisplayAndNativeName(query, context, appLanguage) }
}.value
}

@Composable
@VisibleForTesting
internal fun rememberLanguage(locale: Locale?) = remember(locale) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
package org.cru.godtools.ui.dashboard.tools

import android.content.Context
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.Locale
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import org.cru.godtools.base.Settings
import org.cru.godtools.db.repository.LanguagesRepository
import org.cru.godtools.db.repository.ToolsRepository
import org.cru.godtools.model.Language
import org.cru.godtools.model.Language.Companion.filterByDisplayAndNativeName

private const val KEY_SELECTED_CATEGORY = "selectedCategory"
private const val KEY_SELECTED_LANGUAGE = "selectedLanguage"
Expand All @@ -30,10 +22,7 @@ private const val KEY_LANGUAGE_QUERY = "languageQuery"
@HiltViewModel
@OptIn(ExperimentalCoroutinesApi::class)
class ToolsViewModel @Inject constructor(
@ApplicationContext context: Context,
settings: Settings,
toolsRepository: ToolsRepository,
languagesRepository: LanguagesRepository,
private val savedState: SavedStateHandle,
) : ViewModel() {
// region Tools
Expand Down Expand Up @@ -61,20 +50,6 @@ class ToolsViewModel @Inject constructor(
val categories = toolsForLocale.mapLatest { it.mapNotNull { it.category }.distinct() }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

val languages = selectedCategory
.flatMapLatest {
when {
it != null -> languagesRepository.getLanguagesFlowForToolCategory(it)
else -> languagesRepository.getLanguagesFlow()
}
}
.combine(settings.appLanguageFlow) { langs, appLang ->
langs.sortedWith(Language.displayNameComparator(context, appLang)) to appLang
}
.combine(languageQuery) { (langs, appLang), q -> langs.filterByDisplayAndNativeName(q, context, appLang) }
.flowOn(Dispatchers.Default)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

val tools = toolsForLocale
.combine(selectedCategory) { tools, category -> tools.filter { category == null || it.category == category } }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import io.mockk.every
import io.mockk.mockk
import java.util.Locale
import kotlin.test.assertEquals
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.cru.godtools.base.Settings
import org.cru.godtools.db.repository.ToolsRepository
import org.cru.godtools.model.Tool
import org.cru.godtools.model.randomTool
Expand All @@ -30,10 +27,6 @@ class ToolsViewModelTest {
private val toolsFlow = MutableStateFlow(emptyList<Tool>())
private val metaToolsFlow = MutableStateFlow(emptyList<Tool>())

private val settings: Settings = mockk {
every { appLanguageFlow } returns flowOf(Locale.ENGLISH)
every { isFeatureDiscoveredFlow(any()) } returns flowOf(true)
}
private val testScope = TestScope()
private val toolsRepository: ToolsRepository = mockk {
every { getNormalToolsFlow() } returns toolsFlow
Expand All @@ -46,9 +39,6 @@ class ToolsViewModelTest {
fun setup() {
Dispatchers.setMain(UnconfinedTestDispatcher(testScope.testScheduler))
viewModel = ToolsViewModel(
context = mockk(),
settings = settings,
languagesRepository = mockk(),
toolsRepository = toolsRepository,
savedState = SavedStateHandle(),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.cru.godtools.ui.dashboard.tools

import android.app.Application
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.slack.circuit.test.FakeNavigator
import com.slack.circuit.test.presenterTestOf
Expand All @@ -10,6 +11,7 @@ import io.mockk.mockk
import io.mockk.verifyAll
import java.util.Locale
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
Expand All @@ -32,14 +34,20 @@ import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
@Config(application = Application::class)
class ToolsPresenterTest {
private val appLanguage = MutableStateFlow(Locale.ENGLISH)
private val isFavoritesFeatureDiscovered = MutableStateFlow(true)
private val toolsFlow = MutableSharedFlow<List<Tool>>(extraBufferCapacity = 1)
private val languagesFlow = MutableSharedFlow<List<Language>>(extraBufferCapacity = 1)

private val languagesRepository: LanguagesRepository = mockk {
every { findLanguageFlow(any()) } returns flowOf(null)
every { getLanguagesFlow() } returns languagesFlow
every { getLanguagesFlowForToolCategory(any()) } returns languagesFlow
}
private val navigator = FakeNavigator()
private val settings: Settings = mockk {
every { appLanguage } returns this@ToolsPresenterTest.appLanguage.value
every { appLanguageFlow } returns this@ToolsPresenterTest.appLanguage
every { isFeatureDiscoveredFlow(Settings.FEATURE_TOOL_FAVORITE) } returns isFavoritesFeatureDiscovered
}
private val toolsRepository: ToolsRepository = mockk {
Expand All @@ -55,14 +63,20 @@ class ToolsPresenterTest {
translationsRepository = mockk(relaxed = true),
)

private val presenter = ToolsPresenter(
eventBus = mockk(),
settings = settings,
toolCardPresenter = toolCardPresenter,
languagesRepository = languagesRepository,
toolsRepository = toolsRepository,
navigator = navigator,
)
private lateinit var presenter: ToolsPresenter

@BeforeTest
fun setup() {
presenter = ToolsPresenter(
context = ApplicationProvider.getApplicationContext(),
eventBus = mockk(),
settings = settings,
toolCardPresenter = toolCardPresenter,
languagesRepository = languagesRepository,
toolsRepository = toolsRepository,
navigator = navigator,
)
}

@AfterTest
fun cleanup() = clearAndroidUiDispatcher()
Expand Down Expand Up @@ -144,6 +158,58 @@ class ToolsPresenterTest {
}
// endregion State.spotlightTools

// region State.filters.languages
@Test
fun `State - filters - languages - no category`() = runTest {
val languages = listOf(Language(Locale.ENGLISH), Language(Locale.FRENCH))

presenterTestOf(
presentFunction = {
ToolsScreen.State(
filters = ToolsScreen.State.Filters(
languages = presenter.rememberFilterLanguages(null, ""),
),
eventSink = {}
)
}
) {
expectMostRecentItem()

languagesFlow.emit(languages)
assertEquals(languages, awaitItem().filters.languages)
}

verifyAll {
languagesRepository.getLanguagesFlow()
}
}

@Test
fun `State - filters - languages - for category`() = runTest {
val languages = listOf(Language(Locale.ENGLISH), Language(Locale.FRENCH))

presenterTestOf(
presentFunction = {
ToolsScreen.State(
filters = ToolsScreen.State.Filters(
languages = presenter.rememberFilterLanguages("gospel", ""),
),
eventSink = {}
)
}
) {
expectMostRecentItem()

languagesFlow.emit(languages)
assertEquals(languages, awaitItem().filters.languages)
}

verifyAll {
languagesRepository.getLanguagesFlowForToolCategory("gospel")
}
}
// endregion State.filters.languages

// region State.filters.selectedLanguage
@Test
fun `State - filters - selectedLanguage - no language selected`() = runTest {
Expand Down

0 comments on commit dcd4daa

Please sign in to comment.