Skip to content

Commit 9b8087e

Browse files
committed
route architecture done
1 parent 86664db commit 9b8087e

File tree

13 files changed

+153
-135
lines changed

13 files changed

+153
-135
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package br.com.mob1st.core.design.molecules.transitions
2+
3+
import androidx.compose.animation.AnimatedContentScope
4+
import androidx.compose.animation.AnimatedContentTransitionScope
5+
import androidx.compose.runtime.Composable
6+
import androidx.core.os.bundleOf
7+
import androidx.navigation.NavBackStackEntry
8+
import androidx.navigation.NavGraphBuilder
9+
import androidx.navigation.NavType
10+
import androidx.navigation.compose.composable
11+
import kotlin.reflect.KType
12+
import kotlin.reflect.typeOf
13+
14+
/**
15+
* Add a [composable] route to the [NavGraphBuilder] ensuring that the standard [TransitionPattern]s are applied.
16+
* @param T the type of the route to be added.
17+
* @param typeMap the map of [NavType]s to be used by the route. The [PatternKeyNavType] is added by default.
18+
* @param content the content to be displayed when the route is navigated to.
19+
*/
20+
inline fun <reified T : NavRoute> NavGraphBuilder.route(
21+
typeMap: Map<KType, @JvmSuppressWildcards NavType<*>> = emptyMap(),
22+
noinline content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
23+
) {
24+
composable<T>(
25+
typeMap = typeMap + mapOf(typeOf<PatternKey>() to PatternKeyNavType),
26+
enterTransition = {
27+
transition(entry = targetState, isForward = true)?.enter()
28+
},
29+
exitTransition = {
30+
transition(entry = targetState, isForward = true)?.exit()
31+
},
32+
popEnterTransition = {
33+
transition(entry = initialState, isForward = false)?.enter()
34+
},
35+
popExitTransition = {
36+
transition(entry = initialState, isForward = false)?.exit()
37+
},
38+
content = content,
39+
)
40+
}
41+
42+
/**
43+
* Provides the transition pattern to be applied during a navigation event.
44+
* @param entry the [NavBackStackEntry] to be transitioned.
45+
* @param isForward whether the navigation event is moving forward or backwards. It can be useful in cases where the
46+
* transition [BackAndForward] is used.
47+
* @return the [TransitionPattern] to be applied during the navigation event.
48+
*/
49+
fun AnimatedContentTransitionScope<NavBackStackEntry>.transition(
50+
entry: NavBackStackEntry,
51+
isForward: Boolean,
52+
): TransitionPattern? {
53+
val args = entry.arguments ?: bundleOf()
54+
val patternKey = PatternKeyNavType[args, NavRoute::enteringPatternKey.name]
55+
return when (patternKey) {
56+
PatternKey.TopLevel -> TopLevel
57+
PatternKey.BackAndForward -> {
58+
val towards = if (isForward) {
59+
AnimatedContentTransitionScope.SlideDirection.Start
60+
} else {
61+
AnimatedContentTransitionScope.SlideDirection.End
62+
}
63+
BackAndForward(this, towards)
64+
}
65+
66+
null -> null
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package br.com.mob1st.core.design.molecules.transitions
22

33
interface NavRoute {
4-
fun enteringPattern(): PatternKey
4+
val enteringPatternKey: PatternKey
55
}
66

7-
sealed interface PatternKey {
8-
data object BackAndForward : PatternKey
9-
10-
data object TopLevel : PatternKey
7+
enum class PatternKey {
8+
BackAndForward,
9+
TopLevel,
1110
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package br.com.mob1st.core.design.molecules.transitions
2+
3+
import android.os.Bundle
4+
import androidx.navigation.NavType
5+
6+
data object PatternKeyNavType : NavType<PatternKey>(isNullableAllowed = true) {
7+
override fun get(bundle: Bundle, key: String): PatternKey? {
8+
val ordinal = bundle.getInt(key, -1)
9+
return if (ordinal != -1) {
10+
PatternKey.entries[ordinal]
11+
} else {
12+
null
13+
}
14+
}
15+
16+
override fun parseValue(value: String): PatternKey {
17+
return PatternKey.valueOf(value)
18+
}
19+
20+
override fun put(bundle: Bundle, key: String, value: PatternKey) {
21+
bundle.putInt(key, value.ordinal)
22+
}
23+
24+
override fun serializeAsValue(value: PatternKey): String {
25+
return value.name
26+
}
27+
}

Diff for: features/finances/impl/src/main/kotlin/br/com/mob1st/features/finances/impl/domain/usecases/ProceedBuilderUseCase.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal class ProceedBuilderUseCase(
2323
*/
2424
suspend operator fun invoke(builder: BudgetBuilder) {
2525
val remainingInputs = builder.calculateRemainingInputs()
26-
if (remainingInputs != 100) {
26+
if (remainingInputs == 0) {
2727
if (builder.next is BuilderNextAction.Step) {
2828
startBuilderStepUseCase(builder.next)
2929
}

Diff for: features/finances/impl/src/main/kotlin/br/com/mob1st/features/finances/impl/ui/builder/navigation/BuilderNavRoute.kt

+7-8
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ sealed interface BuilderNavRoute : NavRoute {
1414
* The intro screen for the builder.
1515
*/
1616
@Serializable
17-
data object Intro : BuilderNavRoute {
18-
override fun enteringPattern(): PatternKey = PatternKey.TopLevel
19-
}
17+
data class Intro(
18+
override val enteringPatternKey: PatternKey = PatternKey.TopLevel,
19+
) : BuilderNavRoute
2020

2121
/**
2222
* All the screen steps during the builder flow.
@@ -25,9 +25,8 @@ sealed interface BuilderNavRoute : NavRoute {
2525
@Serializable
2626
data class Step(
2727
val id: Id,
28+
override val enteringPatternKey: PatternKey = PatternKey.BackAndForward,
2829
) : BuilderNavRoute {
29-
override fun enteringPattern() = PatternKey.BackAndForward
30-
3130
enum class Id {
3231
FixedExpenses,
3332
VariableExpenses,
@@ -40,9 +39,9 @@ sealed interface BuilderNavRoute : NavRoute {
4039
* The completion screen for the builder.
4140
*/
4241
@Serializable
43-
data object Completion : BuilderNavRoute {
44-
override fun enteringPattern(): PatternKey = PatternKey.TopLevel
45-
}
42+
data class Completion(
43+
override val enteringPatternKey: PatternKey = PatternKey.BackAndForward,
44+
) : BuilderNavRoute
4645

4746
/**
4847
* The factory to create the routes.

Diff for: features/finances/impl/src/main/kotlin/br/com/mob1st/features/finances/impl/ui/builder/navigation/BuilderRouter.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ internal interface BuilderRouter {
2929
companion object : BuilderRouter {
3030
override fun to(action: BuilderNextAction): BuilderNavRoute {
3131
return when (action) {
32-
BuilderNextAction.Complete -> Completion
32+
BuilderNextAction.Complete -> Completion()
3333
FixedExpensesStep -> Step(Step.Id.FixedExpenses)
3434
FixedIncomesStep -> Step(Step.Id.FixedIncomes)
3535
SeasonalExpensesStep -> Step(Step.Id.SeasonalExpenses)

Diff for: features/finances/impl/src/main/kotlin/br/com/mob1st/features/finances/impl/ui/builder/steps/BudgetBuilderStepUiState.kt

-5
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,6 @@ internal sealed interface BudgetBuilderStepUiState {
3838
builder = BudgetBuilder(step, emptyList()),
3939
)
4040

41-
/**
42-
* Indicates if the categories have been loaded.
43-
*/
44-
val hasLoaded: Boolean = builder.categories.isNotEmpty()
45-
4641
val header: Header = when (builder.id) {
4742
FixedExpensesStep -> Header(
4843
title = R.string.finances_builder_fixed_expenses_header,

Diff for: features/finances/impl/src/main/kotlin/br/com/mob1st/features/finances/impl/ui/navgraph/BudgetBuilderNavGraphImpl.kt

+24-84
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package br.com.mob1st.features.finances.impl.ui.navgraph
22

3-
import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection
43
import androidx.compose.foundation.layout.Box
54
import androidx.compose.foundation.layout.fillMaxSize
65
import androidx.compose.material3.Button
@@ -10,19 +9,15 @@ import androidx.compose.ui.Alignment
109
import androidx.compose.ui.Modifier
1110
import androidx.navigation.NavController
1211
import androidx.navigation.NavGraphBuilder
13-
import androidx.navigation.compose.composable
1412
import androidx.navigation.navigation
1513
import androidx.navigation.toRoute
16-
import br.com.mob1st.core.design.molecules.transitions.BackAndForward
17-
import br.com.mob1st.core.design.molecules.transitions.TopLevel
18-
import br.com.mob1st.features.finances.impl.domain.entities.BuilderNextAction
14+
import br.com.mob1st.core.design.molecules.transitions.route
1915
import br.com.mob1st.features.finances.impl.ui.builder.intro.BuilderIntroPage
2016
import br.com.mob1st.features.finances.impl.ui.builder.navigation.BuilderNavRoute
2117
import br.com.mob1st.features.finances.impl.ui.builder.navigation.BuilderRouter
2218
import br.com.mob1st.features.finances.impl.ui.builder.navigation.BuilderStepNavType
2319
import br.com.mob1st.features.finances.impl.ui.builder.steps.BudgetBuilderStepPage
2420
import br.com.mob1st.features.finances.publicapi.domain.ui.BudgetBuilderNavGraph
25-
import org.koin.compose.koinInject
2621
import kotlin.reflect.typeOf
2722

2823
internal class BudgetBuilderNavGraphImpl(
@@ -34,94 +29,39 @@ internal class BudgetBuilderNavGraphImpl(
3429
onComplete: () -> Unit,
3530
) {
3631
navigation<BudgetBuilderNavGraph.Root>(
37-
startDestination = BuilderNavRoute.Intro,
32+
startDestination = BuilderNavRoute.Intro(),
3833
) {
39-
intro {
40-
val route = router.to(it)
41-
navController.navigate(route)
34+
route<BuilderNavRoute.Intro> {
35+
BuilderIntroPage(
36+
onNext = {
37+
val route = router.to(it)
38+
navController.navigate(route)
39+
},
40+
)
4241
}
43-
step(
44-
onNext = {
45-
val route = router.to(it)
46-
navController.navigate(route)
47-
},
48-
onBack = navController::navigateUp,
49-
)
50-
completion {
51-
navController.navigate(BuilderNavRoute.Intro)
42+
route<BuilderNavRoute.Step>(
43+
typeMap = mapOf(typeOf<BuilderNavRoute.Step.Id>() to BuilderStepNavType),
44+
) {
45+
val route = it.toRoute<BuilderNavRoute.Step>()
46+
BudgetBuilderStepPage(
47+
step = router.from(route),
48+
onNext = { action ->
49+
navController.navigate(router.to(action))
50+
},
51+
onBack = navController::navigateUp,
52+
)
5253
}
5354
}
54-
}
55-
}
56-
57-
private fun NavGraphBuilder.intro(
58-
onNext: (BuilderNextAction.Step) -> Unit,
59-
) {
60-
composable<BuilderNavRoute.Intro>(
61-
enterTransition = {
62-
TopLevel.enter()
63-
},
64-
exitTransition = {
65-
BackAndForward(this, SlideDirection.Start).exit()
66-
},
67-
popEnterTransition = {
68-
BackAndForward(this, SlideDirection.End).enter()
69-
},
70-
popExitTransition = {
71-
TopLevel.exit()
72-
},
73-
) {
74-
BuilderIntroPage(onNext = onNext)
75-
}
76-
}
77-
78-
private fun NavGraphBuilder.step(
79-
onNext: (BuilderNextAction) -> Unit,
80-
onBack: () -> Unit,
81-
) {
82-
composable<BuilderNavRoute.Step>(
83-
typeMap = mapOf(typeOf<BuilderNavRoute.Step.Id>() to BuilderStepNavType),
84-
enterTransition = {
85-
BackAndForward(this, SlideDirection.Start).enter()
86-
},
87-
exitTransition = {
88-
BackAndForward(this, SlideDirection.Start).exit()
89-
},
90-
popExitTransition = {
91-
BackAndForward(this, SlideDirection.End).exit()
92-
},
93-
popEnterTransition = {
94-
BackAndForward(this, SlideDirection.End).enter()
95-
},
96-
) { navBackStackEntry ->
97-
val router = koinInject<BuilderRouter>()
98-
val route = navBackStackEntry.toRoute<BuilderNavRoute.Step>()
99-
val step = router.from(route)
100-
BudgetBuilderStepPage(
101-
step = step,
102-
onNext = onNext,
103-
onBack = onBack,
104-
)
55+
completion {
56+
navController.navigate(BuilderNavRoute.Intro())
57+
}
10558
}
10659
}
10760

10861
private fun NavGraphBuilder.completion(
10962
onComplete: () -> Unit,
11063
) {
111-
composable<BuilderNavRoute.Completion>(
112-
enterTransition = {
113-
BackAndForward(this, SlideDirection.Start).enter()
114-
},
115-
exitTransition = {
116-
TopLevel.exit()
117-
},
118-
popEnterTransition = {
119-
TopLevel.enter()
120-
},
121-
popExitTransition = {
122-
BackAndForward(this, SlideDirection.End).exit()
123-
},
124-
) {
64+
route<BuilderNavRoute.Completion> {
12565
Surface(modifier = Modifier.fillMaxSize()) {
12666
Box(
12767
modifier = Modifier.fillMaxSize(),

Diff for: features/finances/impl/src/test/kotlin/br/com/mob1st/features/finances/impl/domain/usecases/StartBuilderStepUseCaseTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class StartBuilderStepUseCaseTest {
4747
step.isExpense,
4848
step.type,
4949
)
50-
} returns 1L
50+
} returns flowOf(1L)
5151

5252
// When
5353
useCase(step)
@@ -68,7 +68,7 @@ class StartBuilderStepUseCaseTest {
6868
step.isExpense,
6969
step.type,
7070
)
71-
} returns 0L
71+
} returns flowOf(0L)
7272
every { categorySuggestionRepository.getByStep(step) } returns flowOf(suggestions)
7373
every { categoryFactory.create(eq(step), any()) } answers {
7474
Arb.category().next()

Diff for: features/finances/impl/src/test/kotlin/br/com/mob1st/features/finances/impl/infra/data/repositories/categories/CategoryRepositoryImplTest.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import io.mockk.every
2424
import io.mockk.mockk
2525
import kotlinx.coroutines.ExperimentalCoroutinesApi
2626
import kotlinx.coroutines.flow.collect
27+
import kotlinx.coroutines.flow.first
2728
import kotlinx.coroutines.flow.take
2829
import kotlinx.coroutines.test.TestScope
2930
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -122,7 +123,7 @@ internal class CategoryRepositoryImplTest {
122123
val actual = repository.countByIsExpenseAndRecurrencesType(
123124
isExpense = isExpense,
124125
recurrenceType = type,
125-
)
126+
).first()
126127
assertEquals(1, actual)
127128
}
128129

0 commit comments

Comments
 (0)