Skip to content

Commit e02ac94

Browse files
committed
update the Lesson LanguageFilter to render using LazyFilterMenu
1 parent 29f5e98 commit e02ac94

6 files changed

Lines changed: 54 additions & 116 deletions

File tree

Lines changed: 11 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,40 @@
11
package org.cru.godtools.ui.dashboard.lessons
22

3-
import androidx.compose.foundation.ExperimentalFoundationApi
43
import androidx.compose.foundation.layout.Row
5-
import androidx.compose.foundation.layout.fillMaxWidth
64
import androidx.compose.foundation.layout.padding
7-
import androidx.compose.foundation.layout.sizeIn
8-
import androidx.compose.foundation.layout.wrapContentWidth
9-
import androidx.compose.foundation.lazy.itemsIndexed
10-
import androidx.compose.material3.ElevatedButton
11-
import androidx.compose.material3.ExperimentalMaterial3Api
12-
import androidx.compose.material3.ExposedDropdownMenuDefaults
13-
import androidx.compose.material3.HorizontalDivider
14-
import androidx.compose.material3.MenuDefaults
15-
import androidx.compose.material3.SearchBar
16-
import androidx.compose.material3.Surface
175
import androidx.compose.material3.Text
186
import androidx.compose.runtime.Composable
19-
import androidx.compose.runtime.collectAsState
20-
import androidx.compose.runtime.getValue
21-
import androidx.compose.runtime.mutableStateOf
22-
import androidx.compose.runtime.remember
23-
import androidx.compose.runtime.setValue
247
import androidx.compose.ui.Modifier
258
import androidx.compose.ui.platform.LocalContext
269
import androidx.compose.ui.res.pluralStringResource
2710
import androidx.compose.ui.res.stringResource
28-
import androidx.compose.ui.semantics.Role
29-
import androidx.compose.ui.semantics.role
30-
import androidx.compose.ui.semantics.semantics
31-
import androidx.compose.ui.text.style.TextOverflow
3211
import androidx.compose.ui.unit.dp
33-
import androidx.lifecycle.viewmodel.compose.viewModel
34-
import java.util.Locale
35-
import org.ccci.gto.android.common.androidx.compose.material3.ui.list.ListItemStartPadding
36-
import org.ccci.gto.android.common.androidx.compose.material3.ui.menu.LazyDropdownMenu
3712
import org.cru.godtools.R
3813
import org.cru.godtools.base.LocalAppLanguage
39-
import org.cru.godtools.base.ui.theme.GodToolsTheme
40-
import org.cru.godtools.model.Language
41-
import org.cru.godtools.ui.dashboard.filters.FilterMenuItem
14+
import org.cru.godtools.ui.dashboard.filters.LazyFilterMenu
4215
import org.cru.godtools.ui.languages.LanguageName
4316

44-
private val DROPDOWN_LESSON_MAX_HEIGHT = 700.dp
45-
private val DROPDOWN_LESSON_MAX_WIDTH = 350.dp
46-
4717
@Composable
48-
fun LessonFilters(modifier: Modifier = Modifier) {
18+
internal fun LessonFilters(state: LessonsScreen.UiState, modifier: Modifier = Modifier) {
4919
Row(modifier = modifier) {
5020
Text(
5121
stringResource(R.string.dashboard_lessons_section_filter_label),
5222
modifier = Modifier
5323
.alignByBaseline()
5424
.padding(end = 36.dp)
5525
)
56-
LessonLanguageFilter(
26+
LazyFilterMenu(
27+
state.languageFilter,
28+
buttonLabelText = state.languageFilter.selectedItem
29+
?.getDisplayName(LocalContext.current, LocalAppLanguage.current).orEmpty(),
30+
itemKey = { (it) -> it.code },
31+
itemLabel = { LanguageName(it.item.code) },
32+
itemSupportingText = { (_, count) ->
33+
pluralStringResource(R.plurals.dashboard_lessons_section_filter_available_lessons, count, count)
34+
},
5735
modifier = Modifier
5836
.weight(1f)
5937
.alignByBaseline()
6038
)
6139
}
6240
}
63-
64-
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
65-
@Composable
66-
internal fun LessonLanguageFilter(modifier: Modifier = Modifier, viewModel: LessonsViewModel = viewModel()) {
67-
val context = LocalContext.current
68-
val selectedLanguage by viewModel.selectedLanguage.collectAsState(initial = Language(Locale.getDefault()))
69-
70-
val lessonLanguages by viewModel.filteredLanguages.collectAsState(emptyList())
71-
var expanded by remember { mutableStateOf(false) }
72-
73-
ElevatedButton(
74-
onClick = { expanded = true },
75-
modifier = modifier.semantics { role = Role.DropdownList }
76-
) {
77-
Text(
78-
selectedLanguage.getDisplayName(context, LocalAppLanguage.current),
79-
maxLines = 1,
80-
overflow = TextOverflow.Ellipsis,
81-
modifier = Modifier.weight(1f),
82-
)
83-
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
84-
LazyDropdownMenu(
85-
expanded = expanded,
86-
onDismissRequest = { expanded = false },
87-
modifier = Modifier.sizeIn(maxHeight = DROPDOWN_LESSON_MAX_HEIGHT, maxWidth = DROPDOWN_LESSON_MAX_WIDTH)
88-
) {
89-
stickyHeader {
90-
Surface(color = MenuDefaults.containerColor) {
91-
val query by viewModel.query.collectAsState("")
92-
SearchBar(
93-
query = query,
94-
onQueryChange = { change ->
95-
viewModel.query.value = change
96-
},
97-
onSearch = {},
98-
active = false,
99-
onActiveChange = {},
100-
colors = GodToolsTheme.searchBarColors,
101-
placeholder = {
102-
Text(stringResource(R.string.language_settings_downloadable_languages_search))
103-
},
104-
content = {},
105-
modifier = Modifier
106-
.padding(horizontal = 8.dp)
107-
.fillMaxWidth()
108-
.wrapContentWidth()
109-
)
110-
}
111-
}
112-
113-
itemsIndexed(lessonLanguages, key = { _, (language) -> language.code }) { index, (lang, count) ->
114-
if (index > 0) {
115-
HorizontalDivider(Modifier.padding(start = ListItemStartPadding, end = ListItemStartPadding))
116-
}
117-
FilterMenuItem(
118-
label = { LanguageName(lang) },
119-
supportingText = pluralStringResource(
120-
R.plurals.dashboard_lessons_section_filter_available_lessons,
121-
count,
122-
count,
123-
),
124-
onClick = {
125-
expanded = false
126-
viewModel.updateSelectedLanguage(lang)
127-
viewModel.query.value = ""
128-
},
129-
// TODO: Animate item placement - Add back when Compose version 1.7 is released
130-
// modifier = Modifier.animateItemPlacement()
131-
)
132-
}
133-
}
134-
}
135-
}

app/src/main/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsLayout.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,21 @@ import androidx.compose.material3.HorizontalDivider
99
import androidx.compose.material3.MaterialTheme
1010
import androidx.compose.material3.Text
1111
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.LaunchedEffect
1213
import androidx.compose.runtime.collectAsState
1314
import androidx.compose.runtime.getValue
15+
import androidx.compose.runtime.mutableStateOf
16+
import androidx.compose.runtime.rememberUpdatedState
17+
import androidx.compose.runtime.saveable.rememberSaveable
1418
import androidx.compose.ui.Modifier
1519
import androidx.compose.ui.res.stringResource
1620
import androidx.compose.ui.tooling.preview.Preview
1721
import androidx.compose.ui.unit.dp
1822
import androidx.lifecycle.viewmodel.compose.viewModel
1923
import java.util.Locale
24+
import kotlinx.collections.immutable.persistentListOf
2025
import org.cru.godtools.R
26+
import org.cru.godtools.ui.dashboard.filters.FilterMenu
2127
import org.cru.godtools.ui.tools.LessonToolCard
2228
import org.cru.godtools.ui.tools.ToolCard
2329
import org.cru.godtools.ui.tools.toolViewModels
@@ -28,14 +34,29 @@ internal sealed interface DashboardLessonsEvent {
2834

2935
@Composable
3036
internal fun LessonsLayout(viewModel: LessonsViewModel = viewModel(), onEvent: (DashboardLessonsEvent) -> Unit = {}) {
37+
val state = LessonsScreen.UiState(
38+
languageFilter = FilterMenu.UiState(
39+
menuExpanded = rememberSaveable { mutableStateOf(false) },
40+
query = rememberSaveable { mutableStateOf("") },
41+
items = viewModel.filteredLanguages.collectAsState(persistentListOf()).value,
42+
selectedItem = viewModel.selectedLanguage.collectAsState().value,
43+
eventSink = {
44+
when (it) {
45+
is FilterMenu.Event.SelectItem -> it.item?.let { viewModel.updateSelectedLanguage(it) }
46+
}
47+
}
48+
)
49+
)
50+
LaunchedEffect(state.languageFilter.query.value) { viewModel.query.value = state.languageFilter.query.value }
51+
3152
val lessons by viewModel.lessons.collectAsState(emptyList())
32-
val selectedLanguage by viewModel.selectedLanguage.collectAsState()
53+
val selectedLanguage by rememberUpdatedState(state.languageFilter.selectedItem)
3354

3455
LazyColumn(contentPadding = PaddingValues(16.dp)) {
3556
item("header", "header") {
3657
LessonsHeader()
3758
HorizontalDivider(modifier = Modifier.padding(vertical = 12.dp))
38-
LessonFilters()
59+
LessonFilters(state)
3960
}
4061

4162
items(lessons, { it }, { "lesson" }) { lesson ->
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.cru.godtools.ui.dashboard.lessons
2+
3+
import com.slack.circuit.runtime.CircuitUiState
4+
import com.slack.circuit.runtime.screen.Screen
5+
import kotlinx.parcelize.Parcelize
6+
import org.cru.godtools.model.Language
7+
import org.cru.godtools.ui.dashboard.filters.FilterMenu
8+
9+
@Parcelize
10+
data object LessonsScreen : Screen {
11+
data class UiState(val languageFilter: FilterMenu.UiState<Language> = FilterMenu.UiState()) : CircuitUiState
12+
}

app/src/main/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsViewModel.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
77
import dagger.hilt.android.lifecycle.HiltViewModel
88
import dagger.hilt.android.qualifiers.ApplicationContext
99
import javax.inject.Inject
10+
import kotlinx.collections.immutable.toImmutableList
1011
import kotlinx.coroutines.ExperimentalCoroutinesApi
1112
import kotlinx.coroutines.flow.MutableStateFlow
1213
import kotlinx.coroutines.flow.SharingStarted
@@ -26,7 +27,7 @@ import org.cru.godtools.db.repository.ToolsRepository
2627
import org.cru.godtools.db.repository.TranslationsRepository
2728
import org.cru.godtools.model.Language
2829
import org.cru.godtools.model.Language.Companion.filterByDisplayAndNativeName
29-
import org.cru.godtools.ui.dashboard.tools.ToolsScreen.Filters.Filter
30+
import org.cru.godtools.ui.dashboard.filters.FilterMenu
3031
import org.greenrobot.eventbus.EventBus
3132

3233
const val KEY_SAVED_LESSON_LANGUAGE_LOCALE = "savedLessonLanguageLocale"
@@ -89,8 +90,9 @@ class LessonsViewModel @Inject constructor(
8990
) { languages, appLanguage, query, toolCounts ->
9091
languages
9192
.filterByDisplayAndNativeName(query, context, appLanguage)
92-
.map { Filter(it, toolCounts[it.code] ?: 0) }
93+
.map { FilterMenu.UiState.Item(it, toolCounts[it.code] ?: 0) }
9394
.filter { it.count > 0 }
95+
.toImmutableList()
9496
}
9597

9698
// region Analytics

app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsScreen.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ data object ToolsScreen : Screen {
2323
data class Filters(
2424
val categoryFilter: FilterMenu.UiState<String?> = FilterMenu.UiState(),
2525
val languageFilter: FilterMenu.UiState<Language?> = FilterMenu.UiState(),
26-
) : CircuitUiState {
27-
data class Filter<T>(val item: T, val count: Int)
28-
}
26+
) : CircuitUiState
2927

3028
sealed interface Event : CircuitUiEvent {
3129
data class OpenToolDetails(val tool: String, val source: String? = null) : Event

app/src/test/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsViewModelTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import org.cru.godtools.model.Tool
2828
import org.cru.godtools.model.Translation
2929
import org.cru.godtools.model.randomTool
3030
import org.cru.godtools.model.randomTranslation
31-
import org.cru.godtools.ui.dashboard.tools.ToolsScreen.Filters.Filter
31+
import org.cru.godtools.ui.dashboard.filters.FilterMenu
3232
import org.hamcrest.MatcherAssert.assertThat
3333
import org.hamcrest.Matchers.contains
3434

@@ -146,7 +146,7 @@ class LessonsViewModelTest {
146146

147147
viewModel.filteredLanguages.test {
148148
coVerify { translationsRepository.getTranslationsFlowForTools(setOf("lesson")) }
149-
assertEquals(listOf(Filter(first, 1), Filter(second, 1)), awaitItem())
149+
assertEquals(listOf(FilterMenu.UiState.Item(first, 1), FilterMenu.UiState.Item(second, 1)), awaitItem())
150150
}
151151
}
152152

@@ -166,7 +166,7 @@ class LessonsViewModelTest {
166166
viewModel.query.value = "Eng"
167167
runCurrent()
168168
coVerify { languagesRepository.getLanguagesFlow() }
169-
assertEquals(listOf(Filter(first, 1)), expectMostRecentItem())
169+
assertEquals(listOf(FilterMenu.UiState.Item(first, 1)), expectMostRecentItem())
170170
}
171171
}
172172

0 commit comments

Comments
 (0)