Skip to content

Commit 1c0b7e4

Browse files
committed
update LessonsLayout to render based upon LessonsScreen.UiState
1 parent e02ac94 commit 1c0b7e4

8 files changed

Lines changed: 141 additions & 29 deletions

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

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import androidx.compose.material3.Text
1111
import androidx.compose.runtime.Composable
1212
import androidx.compose.runtime.LaunchedEffect
1313
import androidx.compose.runtime.collectAsState
14-
import androidx.compose.runtime.getValue
14+
import androidx.compose.runtime.key
1515
import androidx.compose.runtime.mutableStateOf
16-
import androidx.compose.runtime.rememberUpdatedState
1716
import androidx.compose.runtime.saveable.rememberSaveable
1817
import androidx.compose.ui.Modifier
1918
import androidx.compose.ui.res.stringResource
@@ -22,6 +21,7 @@ import androidx.compose.ui.unit.dp
2221
import androidx.lifecycle.viewmodel.compose.viewModel
2322
import java.util.Locale
2423
import kotlinx.collections.immutable.persistentListOf
24+
import kotlinx.collections.immutable.toImmutableList
2525
import org.cru.godtools.R
2626
import org.cru.godtools.ui.dashboard.filters.FilterMenu
2727
import org.cru.godtools.ui.tools.LessonToolCard
@@ -34,45 +34,56 @@ internal sealed interface DashboardLessonsEvent {
3434

3535
@Composable
3636
internal fun LessonsLayout(viewModel: LessonsViewModel = viewModel(), onEvent: (DashboardLessonsEvent) -> Unit = {}) {
37+
val languageFilter = FilterMenu.UiState(
38+
menuExpanded = rememberSaveable { mutableStateOf(false) },
39+
query = rememberSaveable { mutableStateOf("") },
40+
items = viewModel.filteredLanguages.collectAsState(persistentListOf()).value,
41+
selectedItem = viewModel.selectedLanguage.collectAsState().value,
42+
eventSink = {
43+
when (it) {
44+
is FilterMenu.Event.SelectItem -> it.item?.let { viewModel.updateSelectedLanguage(it) }
45+
}
46+
}
47+
)
48+
LaunchedEffect(languageFilter.query.value) { viewModel.query.value = languageFilter.query.value }
49+
3750
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) }
51+
languageFilter = languageFilter,
52+
lessons = viewModel.lessons.collectAsState().value
53+
.map { lesson ->
54+
key(lesson) {
55+
lateinit var state: ToolCard.State
56+
state = toolViewModels[lesson].toState(language = languageFilter.selectedItem) {
57+
when (it) {
58+
ToolCard.Event.Click -> {
59+
viewModel.recordOpenLessonInAnalytics(lesson)
60+
onEvent(DashboardLessonsEvent.OpenLesson(lesson, state.translation?.languageCode))
61+
}
62+
63+
else -> TODO()
64+
}
65+
}
66+
state
4667
}
4768
}
48-
)
69+
.toImmutableList()
4970
)
50-
LaunchedEffect(state.languageFilter.query.value) { viewModel.query.value = state.languageFilter.query.value }
5171

52-
val lessons by viewModel.lessons.collectAsState(emptyList())
53-
val selectedLanguage by rememberUpdatedState(state.languageFilter.selectedItem)
72+
LessonsLayout(state)
73+
}
5474

55-
LazyColumn(contentPadding = PaddingValues(16.dp)) {
75+
@Composable
76+
internal fun LessonsLayout(state: LessonsScreen.UiState, modifier: Modifier = Modifier) {
77+
LazyColumn(contentPadding = PaddingValues(16.dp), modifier = modifier) {
5678
item("header", "header") {
5779
LessonsHeader()
5880
HorizontalDivider(modifier = Modifier.padding(vertical = 12.dp))
5981
LessonFilters(state)
6082
}
6183

62-
items(lessons, { it }, { "lesson" }) { lesson ->
63-
lateinit var state: ToolCard.State
64-
state = toolViewModels[lesson].toState(language = selectedLanguage) {
65-
when (it) {
66-
ToolCard.Event.Click -> {
67-
viewModel.recordOpenLessonInAnalytics(lesson)
68-
onEvent(DashboardLessonsEvent.OpenLesson(lesson, state.translation?.languageCode))
69-
}
70-
else -> TODO()
71-
}
72-
}
73-
84+
items(state.lessons, { it.toolCode.orEmpty() }, { "lesson" }) { toolState ->
7485
LessonToolCard(
75-
state,
86+
toolState,
7687
modifier = Modifier
7788
.animateItem()
7889
.padding(top = 16.dp)

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ package org.cru.godtools.ui.dashboard.lessons
22

33
import com.slack.circuit.runtime.CircuitUiState
44
import com.slack.circuit.runtime.screen.Screen
5+
import kotlinx.collections.immutable.ImmutableList
6+
import kotlinx.collections.immutable.persistentListOf
57
import kotlinx.parcelize.Parcelize
68
import org.cru.godtools.model.Language
79
import org.cru.godtools.ui.dashboard.filters.FilterMenu
10+
import org.cru.godtools.ui.tools.ToolCard
811

912
@Parcelize
1013
data object LessonsScreen : Screen {
11-
data class UiState(val languageFilter: FilterMenu.UiState<Language> = FilterMenu.UiState()) : CircuitUiState
14+
data class UiState(
15+
val languageFilter: FilterMenu.UiState<Language> = FilterMenu.UiState(),
16+
val lessons: ImmutableList<ToolCard.State> = persistentListOf(),
17+
) : CircuitUiState
1218
}
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.cru.godtools.ui.dashboard.lessons
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.material3.MaterialTheme
5+
import androidx.compose.ui.Modifier
6+
import app.cash.paparazzi.DeviceConfig
7+
import coil.Coil
8+
import coil.ImageLoader
9+
import coil.annotation.ExperimentalCoilApi
10+
import coil.test.FakeImageLoaderEngine
11+
import com.android.resources.NightMode
12+
import com.google.testing.junit.testparameterinjector.TestParameter
13+
import com.google.testing.junit.testparameterinjector.TestParameterInjector
14+
import java.util.Locale
15+
import kotlin.test.AfterTest
16+
import kotlin.test.BeforeTest
17+
import kotlin.test.Test
18+
import kotlinx.collections.immutable.persistentListOf
19+
import kotlinx.coroutines.Dispatchers
20+
import kotlinx.coroutines.ExperimentalCoroutinesApi
21+
import kotlinx.coroutines.test.UnconfinedTestDispatcher
22+
import kotlinx.coroutines.test.resetMain
23+
import kotlinx.coroutines.test.setMain
24+
import org.cru.godtools.model.Language
25+
import org.cru.godtools.ui.BasePaparazziTest
26+
import org.cru.godtools.ui.dashboard.filters.FilterMenu
27+
import org.cru.godtools.ui.dashboard.lessons.LessonsScreen.UiState
28+
import org.cru.godtools.ui.tools.ToolCardStateTestData
29+
import org.junit.runner.RunWith
30+
31+
@RunWith(TestParameterInjector::class)
32+
class LessonsLayoutPaparazziTest(
33+
@TestParameter(valuesProvider = DeviceConfigProvider::class) deviceConfig: DeviceConfig,
34+
@TestParameter nightMode: NightMode,
35+
@TestParameter accessibilityMode: AccessibilityMode,
36+
) : BasePaparazziTest(deviceConfig = deviceConfig, nightMode = nightMode, accessibilityMode = accessibilityMode) {
37+
private val state = UiState(
38+
languageFilter = FilterMenu.UiState(
39+
selectedItem = Language(Locale.ENGLISH)
40+
),
41+
lessons = persistentListOf(
42+
ToolCardStateTestData.tool.copy(toolCode = "lesson1", translation = null),
43+
ToolCardStateTestData.tool.copy(toolCode = "lesson2", translation = null),
44+
ToolCardStateTestData.tool.copy(toolCode = "lesson3", translation = null),
45+
ToolCardStateTestData.tool.copy(toolCode = "lesson4", translation = null),
46+
ToolCardStateTestData.tool.copy(toolCode = "lesson5", translation = null),
47+
)
48+
)
49+
50+
@BeforeTest
51+
@OptIn(ExperimentalCoilApi::class, ExperimentalCoroutinesApi::class)
52+
fun setup() {
53+
Dispatchers.setMain(UnconfinedTestDispatcher())
54+
Coil.setImageLoader(
55+
ImageLoader.Builder(paparazzi.context)
56+
.components {
57+
add(
58+
FakeImageLoaderEngine.Builder()
59+
.intercept(ToolCardStateTestData.banner, ToolCardStateTestData.bannerDrawable)
60+
.build()
61+
)
62+
}
63+
.build()
64+
)
65+
}
66+
67+
@AfterTest
68+
@OptIn(ExperimentalCoroutinesApi::class)
69+
fun cleanup() {
70+
Coil.reset()
71+
Dispatchers.resetMain()
72+
}
73+
74+
@Test
75+
fun `LessonsLayout()`() {
76+
snapshot {
77+
LessonsLayout(state, modifier = Modifier.background(MaterialTheme.colorScheme.background))
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)