Skip to content

Commit aab98bb

Browse files
committed
add support for specifying a custom locale to the ToolCardPresenter
1 parent 1c0b7e4 commit aab98bb

5 files changed

Lines changed: 106 additions & 30 deletions

File tree

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

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@ import androidx.compose.runtime.rememberCoroutineScope
1010
import androidx.compose.runtime.rememberUpdatedState
1111
import androidx.compose.runtime.setValue
1212
import androidx.compose.runtime.snapshotFlow
13+
import java.util.Locale
1314
import javax.inject.Inject
1415
import javax.inject.Singleton
1516
import kotlinx.coroutines.ExperimentalCoroutinesApi
1617
import kotlinx.coroutines.NonCancellable
18+
import kotlinx.coroutines.flow.MutableStateFlow
19+
import kotlinx.coroutines.flow.SharingStarted
1720
import kotlinx.coroutines.flow.combine
1821
import kotlinx.coroutines.flow.distinctUntilChanged
1922
import kotlinx.coroutines.flow.flatMapLatest
2023
import kotlinx.coroutines.flow.map
2124
import kotlinx.coroutines.flow.onEach
2225
import kotlinx.coroutines.flow.onStart
26+
import kotlinx.coroutines.flow.shareIn
2327
import kotlinx.coroutines.launch
2428
import org.cru.godtools.base.Settings
2529
import org.cru.godtools.base.ToolFileSystem
@@ -32,7 +36,6 @@ import org.cru.godtools.db.repository.rememberAttachmentFile
3236
import org.cru.godtools.db.repository.rememberLanguage
3337
import org.cru.godtools.model.Language
3438
import org.cru.godtools.model.Tool
35-
import org.cru.godtools.model.Translation
3639

3740
@Singleton
3841
class ToolCardPresenter @Inject constructor(
@@ -47,37 +50,59 @@ class ToolCardPresenter @Inject constructor(
4750
@OptIn(ExperimentalCoroutinesApi::class)
4851
fun present(
4952
tool: Tool,
53+
customLocale: Locale? = null,
5054
loadAppLanguage: Boolean = false,
5155
secondLanguage: Language? = null,
5256
loadAvailableLanguages: Boolean = false,
5357
eventSink: (ToolCard.Event) -> Unit = {},
5458
): ToolCard.State {
5559
val coroutineScope = rememberCoroutineScope()
5660
val toolCode by rememberUpdatedState(tool.code)
57-
val appLocale by settings.produceAppLocaleState()
58-
val defaultLocale by rememberUpdatedState(tool.defaultLocale)
5961

6062
// App Translation
61-
var appTranslation: Translation? by remember { mutableStateOf(null) }
63+
val appLocale by settings.produceAppLocaleState()
64+
val appTranslationFlow = remember {
65+
combine(
66+
snapshotFlow { toolCode },
67+
snapshotFlow { appLocale },
68+
) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) }
69+
.flatMapLatest { it }
70+
.shareIn(coroutineScope, SharingStarted.WhileSubscribed(5_000), 1)
71+
}
72+
val appTranslation by appTranslationFlow.collectAsState(null)
6273
val appLanguage = if (loadAppLanguage) languagesRepository.rememberLanguage(appLocale) else null
63-
val appLanguageAvailable by remember { derivedStateOf { appTranslation != null } }
6474

65-
// Translation
75+
// Custom Translation
76+
val customLocaleFlow = remember { MutableStateFlow(customLocale) }.apply { value = customLocale }
77+
val customLanguage = languagesRepository.rememberLanguage(customLocale)
78+
val customTranslationFlow = remember {
79+
combine(
80+
snapshotFlow { toolCode },
81+
customLocaleFlow
82+
) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) }
83+
.flatMapLatest { it }
84+
.shareIn(coroutineScope, SharingStarted.WhileSubscribed(5_000), 1)
85+
}
86+
val customTranslation by customTranslationFlow.collectAsState(null)
87+
88+
// Default Translation
89+
val defaultLocale by rememberUpdatedState(tool.defaultLocale)
90+
val defaultTranslationFlow = remember {
91+
combine(
92+
snapshotFlow { toolCode },
93+
snapshotFlow { defaultLocale }
94+
) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) }
95+
.flatMapLatest { it }
96+
.onStart { emit(null) }
97+
}
98+
99+
// Primary Translation
66100
var isLoaded by remember { mutableStateOf(false) }
67101
val translation by remember {
68-
val toolCodeFlow = snapshotFlow { toolCode }
69-
toolCodeFlow
70-
.combine(snapshotFlow { appLocale }) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) }
71-
.flatMapLatest { it }
72-
.onEach { appTranslation = it }
73-
.combine(
74-
toolCodeFlow
75-
.combine(snapshotFlow { defaultLocale }) { t, l ->
76-
translationsRepository.findLatestTranslationFlow(t, l)
77-
}
78-
.flatMapLatest { it }
79-
.onStart { emit(null) }
80-
) { t1, t2 -> t1 ?: t2 }
102+
combine(
103+
customLocaleFlow.flatMapLatest { if (it != null) customTranslationFlow else appTranslationFlow },
104+
defaultTranslationFlow
105+
) { t1, t2 -> t1 ?: t2 }
81106
.onEach { isLoaded = true }
82107
}.collectAsState(null)
83108

@@ -103,13 +128,17 @@ class ToolCardPresenter @Inject constructor(
103128
tool = tool,
104129
isLoaded = isLoaded,
105130
banner = attachmentsRepository.rememberAttachmentFile(fileSystem, tool.bannerId),
106-
// TODO: add support for specifying a locale for the ToolCardPresenter instead of just defaulting to the
107-
// appLocale
108-
language = appLanguage,
109-
languageAvailable = appLanguageAvailable,
131+
language = when (customLocale) {
132+
null -> appLanguage
133+
else -> customLanguage
134+
},
135+
languageAvailable = when (customLocale) {
136+
null -> appTranslation != null
137+
else -> customTranslation != null
138+
},
110139
translation = translation,
111140
appLanguage = appLanguage,
112-
appLanguageAvailable = appLanguageAvailable,
141+
appLanguageAvailable = appTranslation != null,
113142
secondLanguage = secondLanguage,
114143
secondLanguageAvailable = secondLanguageAvailable,
115144
availableLanguages = when {

app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/AllFavoritesPresenterTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class AllFavoritesPresenterTest {
5757
private val toolCardPresenter: ToolCardPresenter = mockk {
5858
everyComposable {
5959
present(tool = any(), eventSink = any())
60-
}.answers { ToolCard.State(toolCode = firstArg<Tool>().code, eventSink = arg(4)) }
60+
}.answers { ToolCard.State(toolCode = firstArg<Tool>().code, eventSink = arg(5)) }
6161
}
6262

6363
private val navigator = FakeNavigator(AllFavoritesScreen)
@@ -150,7 +150,7 @@ class AllFavoritesPresenterTest {
150150
ToolCard.State(
151151
toolCode = firstArg<Tool>().code,
152152
translation = randomTranslation(languageCode = Locale.ENGLISH),
153-
eventSink = arg(4)
153+
eventSink = arg(5)
154154
)
155155
}
156156

@@ -176,7 +176,7 @@ class AllFavoritesPresenterTest {
176176
ToolCard.State(
177177
toolCode = firstArg<Tool>().code,
178178
translation = randomTranslation(languageCode = Locale.ENGLISH),
179-
eventSink = arg(4)
179+
eventSink = arg(5)
180180
)
181181
}
182182

app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenterTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class HomePresenterTest {
7070
ToolCard.State(
7171
toolCode = firstArg<Tool>().code,
7272
translation = randomTranslation(languageCode = Locale.ENGLISH),
73-
eventSink = arg(4)
73+
eventSink = arg(5)
7474
)
7575
}
7676
}
@@ -174,7 +174,7 @@ class HomePresenterTest {
174174
val lesson = randomTool(type = Tool.Type.LESSON, isHidden = false, isSpotlight = true)
175175
val translation = randomTranslation(lesson.code, languageCode = Locale.FRENCH)
176176
everyComposable { toolCardPresenter.present(tool = lesson, eventSink = any()) }.answers {
177-
ToolCard.State(toolCode = lesson.code, translation = translation, eventSink = arg(4))
177+
ToolCard.State(toolCode = lesson.code, translation = translation, eventSink = arg(5))
178178
}
179179
lessonsFlow.value = listOf(lesson)
180180

app/src/testDebug/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsPresenterTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ class ToolDetailsPresenterTest {
131131
loadAvailableLanguages = true,
132132
eventSink = any()
133133
)
134-
}.answers { ToolCard.State(toolCode = firstArg<Tool>().code, eventSink = arg(4)) }
134+
}.answers { ToolCard.State(toolCode = firstArg<Tool>().code, eventSink = arg(5)) }
135135
}
136136

137137
private fun createPresenter(screen: ToolDetailsScreen = ToolDetailsScreen(TOOL)) = ToolDetailsPresenter(

app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardPresenterTest.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,53 @@ class ToolCardPresenterTest {
243243
assertNull(expectMostRecentItem().translation)
244244
}
245245
}
246+
247+
@Test
248+
fun `ToolCardState - translation - custom locale`() = runTest {
249+
toolFlow.value = randomTool(TOOL)
250+
appLocaleState.value = Locale.ENGLISH
251+
val translation = randomTranslation(TOOL, Locale.FRENCH)
252+
253+
presenterTestOf(
254+
presentFunction = {
255+
presenter.present(tool = toolFlow.collectAsState().value, customLocale = Locale.FRENCH)
256+
}
257+
) {
258+
frTranslationFlow.emit(translation)
259+
260+
val state = expectMostRecentItem()
261+
assertTrue(state.isLoaded)
262+
assertEquals(translation, state.translation)
263+
}
264+
}
265+
266+
@Test
267+
fun `ToolCardState - translation - custom locale - fallback to default language`() = runTest {
268+
toolFlow.value = randomTool(TOOL, defaultLocale = Locale.ENGLISH)
269+
appLocaleState.value = Locale.GERMAN
270+
val translation = randomTranslation(TOOL, Locale.ENGLISH)
271+
272+
presenterTestOf(
273+
presentFunction = {
274+
presenter.present(tool = toolFlow.collectAsState().value, customLocale = Locale.FRENCH)
275+
}
276+
) {
277+
enTranslationFlow.emit(translation)
278+
assertNotNull(expectMostRecentItem()) {
279+
assertNull(
280+
it.translation,
281+
"Translation should not be returned until the custom translation has attempted to load"
282+
)
283+
assertFalse(it.isLoaded, "isLoaded should be false until the custom translation has attempted to load")
284+
}
285+
286+
frTranslationFlow.emit(null)
287+
assertNotNull(expectMostRecentItem()) {
288+
assertTrue(it.isLoaded)
289+
assertEquals(translation, it.translation)
290+
}
291+
}
292+
}
246293
// endregion ToolCard.State.translation
247294

248295
// region ToolCard.State.appLanguage

0 commit comments

Comments
 (0)