Skip to content

Commit dfc83c4

Browse files
frettclaude
andcommitted
Wire up Banner presenters in Home/Tools presenters via Circuit pattern
Replace direct Settings flow observation with injected BannerPresenter dependencies in HomePresenter and ToolsPresenter, enabling proper Circuit-based composition and simplifying testing via FakeBannerPresenter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 09eb1e8 commit dfc83c4

17 files changed

Lines changed: 95 additions & 139 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.cru.godtools.ui.banner
2+
3+
import dagger.Binds
4+
import dagger.Module
5+
import dagger.hilt.InstallIn
6+
import dagger.hilt.components.SingletonComponent
7+
import org.cru.godtools.ui.banner.favoritetools.FavoriteToolsBannerPresenter
8+
import org.cru.godtools.ui.banner.tutorial.TutorialFeaturesBannerPresenter
9+
10+
@Module
11+
@InstallIn(SingletonComponent::class)
12+
interface BannerModule {
13+
@Binds
14+
fun favoriteToolsBannerPresenter(
15+
presenter: FavoriteToolsBannerPresenter,
16+
): BannerPresenter<FavoriteToolsBannerPresenter.UiState>
17+
18+
@Binds
19+
fun tutorialFeaturesBannerPresenter(
20+
presenter: TutorialFeaturesBannerPresenter,
21+
): BannerPresenter<TutorialFeaturesBannerPresenter.UiState>
22+
}

app/src/main/kotlin/org/cru/godtools/ui/banner/Banners.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,24 @@ import androidx.compose.foundation.layout.heightIn
99
import androidx.compose.runtime.Composable
1010
import androidx.compose.ui.Modifier
1111
import androidx.compose.ui.unit.dp
12+
import org.cru.godtools.ui.banner.favoritetools.FavoriteToolsBannerLayout
13+
import org.cru.godtools.ui.banner.favoritetools.FavoriteToolsBannerPresenter
14+
import org.cru.godtools.ui.banner.tutorial.TutorialFeaturesBannerLayout
15+
import org.cru.godtools.ui.banner.tutorial.TutorialFeaturesBannerPresenter
1216

1317
@Composable
14-
internal fun Banners(banner: () -> Banner.Type?, modifier: Modifier = Modifier) = Box(modifier.heightIn(min = 1.dp)) {
18+
internal fun Banners(state: Banner.UiState?, modifier: Modifier = Modifier) = Box(modifier.heightIn(min = 1.dp)) {
1519
AnimatedContent(
16-
targetState = banner(),
20+
targetState = state,
1721
transitionSpec = {
1822
slideInVertically(initialOffsetY = { -it }) togetherWith slideOutVertically(targetOffsetY = { -it })
1923
},
2024
label = "Banner Visibility",
25+
contentKey = { it?.type },
2126
) {
2227
when (it) {
23-
Banner.Type.TOOL_LIST_FAVORITES -> FavoriteToolsBanner()
24-
Banner.Type.TUTORIAL_FEATURES -> TutorialFeaturesBanner()
28+
is FavoriteToolsBannerPresenter.UiState -> FavoriteToolsBannerLayout(it)
29+
is TutorialFeaturesBannerPresenter.UiState -> TutorialFeaturesBannerLayout(it)
2530
else -> Unit
2631
}
2732
}

app/src/main/kotlin/org/cru/godtools/ui/banner/FavoriteToolsBanner.kt

Lines changed: 0 additions & 27 deletions
This file was deleted.

app/src/main/kotlin/org/cru/godtools/ui/banner/TutorialFeaturesBanner.kt

Lines changed: 0 additions & 44 deletions
This file was deleted.

app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/HomeLayout.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,14 @@ private val PADDING_HORIZONTAL = 16.dp
4545
internal fun HomeLayout(state: UiState, modifier: Modifier = Modifier) {
4646
val columnState = rememberLazyListState()
4747

48-
val banner by rememberUpdatedState(state.banner)
49-
LaunchedEffect(banner) { if (banner != null) columnState.animateScrollToItem(0) }
48+
LaunchedEffect(state.banner?.type) { if (state.banner != null) columnState.animateScrollToItem(0) }
5049

5150
LazyColumn(state = columnState, contentPadding = PaddingValues(bottom = 16.dp), modifier = modifier) {
5251
if (!state.dataLoaded) return@LazyColumn
5352

5453
item("banners", "banners") {
5554
Banners(
56-
{ banner },
55+
state.banner,
5756
modifier = Modifier
5857
.animateItem()
5958
.fillMaxWidth()

app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenter.kt

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import dagger.assisted.AssistedFactory
1717
import dagger.assisted.AssistedInject
1818
import dagger.hilt.android.qualifiers.ApplicationContext
1919
import dagger.hilt.components.SingletonComponent
20-
import kotlinx.coroutines.flow.combine
2120
import kotlinx.coroutines.flow.map
2221
import org.cru.godtools.BuildConfig
2322
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent
@@ -27,9 +26,10 @@ import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.ACTIO
2726
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.SOURCE_FAVORITE
2827
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.SOURCE_FEATURED
2928
import org.cru.godtools.base.CONFIG_UI_DASHBOARD_HOME_FAVORITE_TOOLS
30-
import org.cru.godtools.base.Settings
3129
import org.cru.godtools.db.repository.ToolsRepository
3230
import org.cru.godtools.ui.banner.Banner
31+
import org.cru.godtools.ui.banner.BannerPresenter
32+
import org.cru.godtools.ui.banner.tutorial.TutorialFeaturesBannerPresenter
3333
import org.cru.godtools.ui.dashboard.home.HomePresenter.UiState
3434
import org.cru.godtools.ui.dashboard.tools.ToolsScreen
3535
import org.cru.godtools.ui.tooldetails.ToolDetailsScreen
@@ -43,16 +43,15 @@ class HomePresenter @AssistedInject constructor(
4343
private val context: Context,
4444
private val eventBus: EventBus,
4545
private val remoteConfig: FirebaseRemoteConfig,
46-
private val settings: Settings,
4746
private val toolCardPresenter: ToolCardPresenter,
4847
private val toolsRepository: ToolsRepository,
49-
@Assisted
50-
private val navigator: Navigator,
48+
private val tutorialFeaturesBannerPresenter: BannerPresenter<TutorialFeaturesBannerPresenter.UiState>,
49+
@Assisted private val navigator: Navigator,
5150
) : Presenter<UiState> {
5251
// region UiState / UiEvent
5352
data class UiState(
5453
val dataLoaded: Boolean = true,
55-
val banner: Banner.Type? = null,
54+
val banner: Banner.UiState? = null,
5655
val spotlightLessons: List<ToolCard.State> = emptyList(),
5756
val favoriteTools: List<ToolCard.State> = emptyList(),
5857
val eventSink: (UiEvent) -> Unit = {},
@@ -71,7 +70,7 @@ class HomePresenter @AssistedInject constructor(
7170

7271
return UiState(
7372
dataLoaded = spotlightLessons != null && favoriteTools != null,
74-
banner = rememberBanner(),
73+
banner = tutorialFeaturesBannerPresenter.present(),
7574
spotlightLessons = spotlightLessons.orEmpty(),
7675
favoriteTools = favoriteTools.orEmpty(),
7776
) {
@@ -82,17 +81,6 @@ class HomePresenter @AssistedInject constructor(
8281
}
8382
}
8483

85-
@Composable
86-
private fun rememberBanner() = remember {
87-
settings.isFeatureDiscoveredFlow(Settings.FEATURE_TUTORIAL_FEATURES)
88-
.combine(settings.appLanguageFlow) { discovered, language ->
89-
when {
90-
!discovered -> Banner.Type.TUTORIAL_FEATURES
91-
else -> null
92-
}
93-
}
94-
}.collectAsState(null).value
95-
9684
@Composable
9785
private fun rememberSpotlightLessons() =
9886
remember { toolsRepository.getLessonsFlow().map { it.filter { !it.isHidden && it.isSpotlight } } }

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,19 @@ internal val MARGIN_TOOLS_LAYOUT_HORIZONTAL = 16.dp
3333
@Composable
3434
@CircuitInject(ToolsScreen::class, SingletonComponent::class)
3535
internal fun ToolsLayout(state: UiState, modifier: Modifier = Modifier) {
36-
val banner by rememberUpdatedState(state.banner)
3736
val spotlightTools by rememberUpdatedState(state.spotlightTools)
3837
val filters by rememberUpdatedState(state.filters)
3938
val tools by rememberUpdatedState(state.tools)
4039

4140
val columnState = rememberLazyListState()
42-
LaunchedEffect(banner) { if (banner != null) columnState.animateScrollToItem(0) }
41+
LaunchedEffect(state.banner?.type) { if (state.banner != null) columnState.animateScrollToItem(0) }
4342

4443
LazyColumn(state = columnState, modifier = modifier) {
4544
if (!state.dataLoaded) return@LazyColumn
4645

4746
item("banners", "banners") {
4847
Banners(
49-
{ banner },
48+
state.banner,
5049
modifier = Modifier
5150
.animateItem()
5251
.fillMaxWidth()

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent
2020
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.ACTION_OPEN_TOOL_DETAILS
2121
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.SOURCE_ALL_TOOLS
2222
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.SOURCE_SPOTLIGHT
23-
import org.cru.godtools.base.Settings
2423
import org.cru.godtools.db.repository.ToolsRepository
2524
import org.cru.godtools.model.Language
2625
import org.cru.godtools.model.Tool
2726
import org.cru.godtools.ui.banner.Banner
27+
import org.cru.godtools.ui.banner.BannerPresenter
28+
import org.cru.godtools.ui.banner.favoritetools.FavoriteToolsBannerPresenter
2829
import org.cru.godtools.ui.dashboard.tools.ToolFiltersStateProducer.Filters
2930
import org.cru.godtools.ui.dashboard.tools.ToolsPresenter.UiState
3031
import org.cru.godtools.ui.tooldetails.ToolDetailsScreen
@@ -34,16 +35,16 @@ import org.greenrobot.eventbus.EventBus
3435

3536
class ToolsPresenter @AssistedInject internal constructor(
3637
private val eventBus: EventBus,
37-
private val settings: Settings,
3838
private val toolCardPresenter: ToolCardPresenter,
3939
private val toolsRepository: ToolsRepository,
40+
private val favoriteToolsBannerPresenter: BannerPresenter<FavoriteToolsBannerPresenter.UiState>,
4041
private val filteredToolsFlowProducer: FilteredToolsFlowProducer,
4142
private val toolFiltersStateProducer: ToolFiltersStateProducer,
4243
@Assisted private val navigator: Navigator,
4344
) : Presenter<UiState> {
4445
// region UiState / UiEvent
4546
data class UiState(
46-
val banner: Banner.Type? = null,
47+
val banner: Banner.UiState? = null,
4748
val dataLoaded: Boolean = true,
4849
val spotlightTools: List<ToolCard.State> = emptyList(),
4950
val filters: Filters = Filters(),
@@ -85,7 +86,7 @@ class ToolsPresenter @AssistedInject internal constructor(
8586
)
8687

8788
return UiState(
88-
banner = rememberBanner(),
89+
banner = favoriteToolsBannerPresenter.present(),
8990
dataLoaded = spotlightTools != null && tools != null,
9091
spotlightTools = spotlightTools.orEmpty(),
9192
filters = filters,
@@ -94,12 +95,6 @@ class ToolsPresenter @AssistedInject internal constructor(
9495
)
9596
}
9697

97-
@Composable
98-
private fun rememberBanner() = remember {
99-
settings.isFeatureDiscoveredFlow(Settings.FEATURE_TOOL_FAVORITE)
100-
.map { if (!it) Banner.Type.TOOL_LIST_FAVORITES else null }
101-
}.collectAsState(null).value
102-
10398
@Composable
10499
private fun rememberSpotlightTools(
105100
secondLanguage: Language?,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.cru.godtools.ui.banner
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.collectAsState
5+
import kotlinx.coroutines.flow.MutableStateFlow
6+
7+
class FakeBannerPresenter<T : Banner.UiState>(initialState: T? = null) : BannerPresenter<T> {
8+
private val uiState = MutableStateFlow(initialState)
9+
10+
fun updateState(state: T?) {
11+
uiState.value = state
12+
}
13+
14+
@Composable
15+
override fun present(): T? = uiState.collectAsState().value
16+
}
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)