Skip to content

Commit

Permalink
Merge pull request #3311 from CruGlobal/GT-2195-add-tools-available-t…
Browse files Browse the repository at this point in the history
…o-categories

GT-2194 GT-2195 Display tool counts for filter dropdowns
  • Loading branch information
frett authored Jan 12, 2024
2 parents 8822015 + 9fe9ddb commit 71bee8a
Show file tree
Hide file tree
Showing 15 changed files with 292 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
)
Expand Down Expand Up @@ -163,18 +174,24 @@ 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
}
)
}

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
Expand All @@ -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)
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<ToolsScreen.State> {
@Composable
Expand Down Expand Up @@ -114,19 +119,23 @@ class ToolsPresenter @AssistedInject constructor(
}

@Composable
private fun rememberFilterCategories(selectedLanguage: Locale?): List<String> {
private fun rememberFilterCategories(selectedLanguage: Locale?): List<Filter<String>> {
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<Language> {
private fun rememberFilterLanguages(category: String?, query: String): List<Filter<Language>> {
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
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ data object ToolsScreen : Screen {
) : CircuitUiState

data class Filters(
val categories: List<String> = emptyList(),
val categories: List<Filter<String>> = emptyList(),
val selectedCategory: String? = null,
val languages: List<Language> = emptyList(),
val languages: List<Filter<Language>> = emptyList(),
val languageQuery: String = "",
val selectedLanguage: Language? = null,
val eventSink: (FiltersEvent) -> Unit = {},
) : CircuitUiState
) : CircuitUiState {
data class Filter<T>(val item: T, val count: Int)
}

sealed interface Event : CircuitUiEvent {
data class OpenToolDetails(val tool: String, val source: String? = null) : Event
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings_dashboard.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ An online version can be found at https://knowgod.com/</string>
<string name="dashboard_tools_section_filter_label">Filter</string>
<string name="dashboard_tools_section_filter_category_any">Any category</string>
<string name="dashboard_tools_section_filter_language_any">Any language</string>
<plurals name="dashboard_tools_section_filter_available_tools">
<item quantity="one">%1$d Tool available</item>
<item quantity="other">%1$d Tools available</item>
</plurals>
<string name="dashboard_tools_section_filter_available_tools_all">All Tools available</string>
<string name="dashboard_tools_section_categories_label">Categories</string>
<string name="dashboard_tools_section_categories_all">All Tools</string>
<string name="dashboard_tools_section_spotlight_label">Tool Spotlight</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 71bee8a

Please sign in to comment.