From acc5ff1299534e3eb1c350fa1df590edfe0981ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sat, 20 Jun 2026 23:58:04 -0500 Subject: [PATCH 01/10] feat(wearos:onboarding): add main onboarding screen for wearos app --- features/wearos/onboarding/build.gradle.kts | 17 ++++++++ .../wearos/onboarding/OnboardingScreen.kt | 39 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 features/wearos/onboarding/build.gradle.kts create mode 100644 features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt diff --git a/features/wearos/onboarding/build.gradle.kts b/features/wearos/onboarding/build.gradle.kts new file mode 100644 index 00000000..01ae2d8b --- /dev/null +++ b/features/wearos/onboarding/build.gradle.kts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 Marlonlom + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + id("mocca.compose.lib") + id("mocca.android.lib.wearos") +} + +android { + namespace = "dev.marlonlom.mocca.wearos.onboarding" +} + +dependencies { + implementation(project(":features:wearos:ui")) +} diff --git a/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt new file mode 100644 index 00000000..67cb5ac7 --- /dev/null +++ b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2026 Marlonlom + * SPDX-License-Identifier: Apache-2.0 + */ + +package dev.marlonlom.mocca.wearos.onboarding + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState +import androidx.wear.compose.material3.MaterialTheme + +@Composable +fun OnboardingScreen() { + val listState = rememberScalingLazyListState() + ScalingLazyColumn( + state = listState, + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), + contentPadding = PaddingValues(horizontal = 10.dp, vertical = 20.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + item { + Spacer(Modifier.height(20.dp)) + } + item { + + } + } +} From 64dc11675d138eb555c4901a537cdc201f66ff87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sat, 20 Jun 2026 23:58:33 -0500 Subject: [PATCH 02/10] build: include wearos:onboarding module in project build --- settings.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index c44800d6..2e5b8a77 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -43,6 +43,7 @@ include( ":features:wearos:calculator-input", ":features:wearos:calculator-fees", ":features:wearos:calculator-output", + ":features:wearos:onboarding", ":features:wearos:ui" ) include(":benchmarks:macro:mobile") From 0dd4ddcf5eb135173f20130952f0f9b9c69c64c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sun, 21 Jun 2026 01:04:52 -0500 Subject: [PATCH 03/10] feat(wearos:onboarding): Implement action buttons and add UI tests. - Update OnboardingScreen to accept click actions for calculating and viewing fees. - Extract StartCalculationButton and ViewFeesButton into dedicated Wear OS components. - Add string resources for both English and Spanish localizations. - Implement comprehensive UI tests for the onboarding screen and individual components using Compose HTML/JUnit4 rules. --- .../onboarding/OnboardingScreenUiTest.kt | 57 +++++++++++++++++ .../component/StartCalculationButtonUiTest.kt | 35 +++++++++++ .../component/ViewFeesButtonUiTest.kt | 35 +++++++++++ .../wearos/onboarding/OnboardingScreen.kt | 27 +++++--- .../component/StartCalculationButton.kt | 62 +++++++++++++++++++ .../onboarding/component/ViewFeesButton.kt | 56 +++++++++++++++++ .../src/main/res/values-es/strings.xml | 10 +++ .../src/main/res/values/strings.xml | 10 +++ 8 files changed, 284 insertions(+), 8 deletions(-) create mode 100644 features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreenUiTest.kt create mode 100644 features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/StartCalculationButtonUiTest.kt create mode 100644 features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/ViewFeesButtonUiTest.kt create mode 100644 features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/StartCalculationButton.kt create mode 100644 features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/ViewFeesButton.kt create mode 100644 features/wearos/onboarding/src/main/res/values-es/strings.xml create mode 100644 features/wearos/onboarding/src/main/res/values/strings.xml diff --git a/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreenUiTest.kt b/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreenUiTest.kt new file mode 100644 index 00000000..1263ebaa --- /dev/null +++ b/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreenUiTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2024 Marlonlom + * SPDX-License-Identifier: Apache-2.0 + */ +package dev.marlonlom.mocca.wearos.onboarding + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.v2.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import com.google.common.truth.Truth.assertThat +import org.junit.FixMethodOrder +import org.junit.Rule +import org.junit.Test +import org.junit.runners.MethodSorters + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +internal class OnboardingScreenUiTest { + + @get:Rule + var rule = createComposeRule() + + @Test + fun shouldDisplayScreenThenClickedStartCalculationButton() { + with(rule) { + var clicked = false + setContent { + OnboardingScreen( + onCalculateClick = { clicked = true }, + onViewFeesClick = { }, + ) + } + onNodeWithTag("start_calculation_action_button").assertIsDisplayed() + onNodeWithTag("view_fees_action_button").assertIsDisplayed() + onNodeWithText("Start calculation").assertIsDisplayed().performClick() + onNodeWithText("View fees").assertIsDisplayed() + assertThat(clicked).isTrue() + } + } + + @Test + fun shouldDisplayScreenThenClickedViewFeesButton() { + with(rule) { + var clicked = false + setContent { + OnboardingScreen( + onCalculateClick = { }, + onViewFeesClick = { clicked = true }, + ) + } + onNodeWithText("Start calculation").assertIsDisplayed() + onNodeWithText("View fees").assertIsDisplayed().performClick() + assertThat(clicked).isTrue() + } + } +} diff --git a/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/StartCalculationButtonUiTest.kt b/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/StartCalculationButtonUiTest.kt new file mode 100644 index 00000000..f1f0b6a9 --- /dev/null +++ b/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/StartCalculationButtonUiTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 Marlonlom + * SPDX-License-Identifier: Apache-2.0 + */ +package dev.marlonlom.mocca.wearos.onboarding.component + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.v2.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test + +internal class StartCalculationButtonUiTest { + + @get:Rule + var rule = createComposeRule() + + @Test + fun shouldDisplayButtonThenCheckClicked() { + with(rule) { + var clicked = false + setContent { + StartCalculationButton( + onClicked = { clicked = true }, + ) + } + onNodeWithTag("start_calculation_action_button").assertIsDisplayed() + onNodeWithText("Start calculation").assertIsDisplayed().performClick() + assertThat(clicked).isTrue() + } + } +} diff --git a/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/ViewFeesButtonUiTest.kt b/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/ViewFeesButtonUiTest.kt new file mode 100644 index 00000000..5a2ffd79 --- /dev/null +++ b/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/ViewFeesButtonUiTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 Marlonlom + * SPDX-License-Identifier: Apache-2.0 + */ +package dev.marlonlom.mocca.wearos.onboarding.component + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.v2.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test + +internal class ViewFeesButtonUiTest { + + @get:Rule + var rule = createComposeRule() + + @Test + fun shouldDisplayButtonThenCheckClicked() { + with(rule) { + var clicked = false + setContent { + ViewFeesButton( + onClicked = { clicked = true }, + ) + } + onNodeWithTag("view_fees_action_button").assertIsDisplayed() + onNodeWithText("View fees").assertIsDisplayed().performClick() + assertThat(clicked).isTrue() + } + } +} diff --git a/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt index 67cb5ac7..2556ded4 100644 --- a/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt +++ b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt @@ -1,13 +1,11 @@ /* - * Copyright 2026 Marlonlom + * Copyright 2024 Marlonlom * SPDX-License-Identifier: Apache-2.0 */ - package dev.marlonlom.mocca.wearos.onboarding import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height @@ -15,25 +13,38 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.foundation.lazy.ScalingLazyListState import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState import androidx.wear.compose.material3.MaterialTheme +import dev.marlonlom.mocca.wearos.onboarding.component.StartCalculationButton +import dev.marlonlom.mocca.wearos.onboarding.component.ViewFeesButton +/** + * Displays the onboarding screen with actions to proceed to calculation or view fees. + * + * @author marlonlom + * + * @param onCalculateClick Invoked when the user selects the calculate action. + * @param onViewFeesClick Invoked when the user selects the view fees action. + */ @Composable -fun OnboardingScreen() { - val listState = rememberScalingLazyListState() +fun OnboardingScreen(onCalculateClick: () -> Unit, onViewFeesClick: () -> Unit) { + val listState: ScalingLazyListState = rememberScalingLazyListState() ScalingLazyColumn( state = listState, modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background), - contentPadding = PaddingValues(horizontal = 10.dp, vertical = 20.dp), - verticalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), ) { item { Spacer(Modifier.height(20.dp)) } item { - + StartCalculationButton(onClicked = onCalculateClick) + } + item { + ViewFeesButton(onClicked = onViewFeesClick) } } } diff --git a/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/StartCalculationButton.kt b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/StartCalculationButton.kt new file mode 100644 index 00000000..29b266e0 --- /dev/null +++ b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/StartCalculationButton.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Marlonlom + * SPDX-License-Identifier: Apache-2.0 + */ +package dev.marlonlom.mocca.wearos.onboarding.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.AttachMoney +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material3.Button +import androidx.wear.compose.material3.ButtonDefaults +import androidx.wear.compose.material3.Icon +import androidx.wear.compose.material3.MaterialTheme +import androidx.wear.compose.material3.Text +import dev.marlonlom.mocca.wearos.onboarding.R + +/** + * Displays a button that navigates to the calculation screen. + * + * @author marlonlom + * + * @param onClicked Action invoked when the button is clicked. + */ +@Composable +internal fun StartCalculationButton(onClicked: () -> Unit) = Button( + modifier = Modifier.testTag("start_calculation_action_button") + .fillMaxWidth(), + onClick = onClicked, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.primary, + ), + border = BorderStroke( + width = 1.dp, + color = MaterialTheme.colorScheme.primary, + ), + icon = { + Icon( + imageVector = Icons.Rounded.AttachMoney, + contentDescription = stringResource(R.string.text_start_calculation), + tint = MaterialTheme.colorScheme.primary, + ) + }, + label = { + Text( + text = stringResource(R.string.text_start_calculation), + color = MaterialTheme.colorScheme.primary, + maxLines = 1, + style = MaterialTheme.typography.bodySmall, + overflow = TextOverflow.Ellipsis, + ) + }, +) diff --git a/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/ViewFeesButton.kt b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/ViewFeesButton.kt new file mode 100644 index 00000000..547fa074 --- /dev/null +++ b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/component/ViewFeesButton.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2024 Marlonlom + * SPDX-License-Identifier: Apache-2.0 + */ +package dev.marlonlom.mocca.wearos.onboarding.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.TableChart +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.wear.compose.material3.ButtonDefaults +import androidx.wear.compose.material3.CompactButton +import androidx.wear.compose.material3.Icon +import androidx.wear.compose.material3.MaterialTheme +import androidx.wear.compose.material3.Text +import dev.marlonlom.mocca.wearos.onboarding.R + +/** + * Displays a compact button that navigates to the fees screen. + * + * @author marlonlom + * + * @param onClicked Action invoked when the button is clicked. + */ +@Composable +internal fun ViewFeesButton(onClicked: () -> Unit) = CompactButton( + modifier = Modifier + .testTag("view_fees_action_button") + .fillMaxWidth(), + onClick = onClicked, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onSurface, + ), + icon = { + Icon( + imageVector = Icons.Rounded.TableChart, + contentDescription = stringResource(R.string.text_view_fees), + tint = MaterialTheme.colorScheme.onSurface, + ) + }, + label = { + Text( + text = stringResource(R.string.text_view_fees), + maxLines = 1, + style = MaterialTheme.typography.bodySmall, + overflow = TextOverflow.Ellipsis, + ) + }, +) diff --git a/features/wearos/onboarding/src/main/res/values-es/strings.xml b/features/wearos/onboarding/src/main/res/values-es/strings.xml new file mode 100644 index 00000000..0478230d --- /dev/null +++ b/features/wearos/onboarding/src/main/res/values-es/strings.xml @@ -0,0 +1,10 @@ + + + + + Iniciar cálculo + Ver tarifas + diff --git a/features/wearos/onboarding/src/main/res/values/strings.xml b/features/wearos/onboarding/src/main/res/values/strings.xml new file mode 100644 index 00000000..668af454 --- /dev/null +++ b/features/wearos/onboarding/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ + + + + + Start calculation + View fees + From 0b3065cef3ccbb0e8ca0294348a001249fc264de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sun, 21 Jun 2026 01:29:28 -0500 Subject: [PATCH 04/10] refactor(wearos:calculator-fees): hoist listState in CalculatorFeesListScreen Hoists the ScalingLazyListState to CalculatorFeesListScreen parameters to improve testability and state management from the parent level. Updates the associated UI test to pass a remembered state. --- .../calculator/fees/CalculatorFeesListScreenUiTest.kt | 2 ++ .../wearos/calculator/fees/CalculatorFeesListScreen.kt | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/features/wearos/calculator-fees/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/calculator/fees/CalculatorFeesListScreenUiTest.kt b/features/wearos/calculator-fees/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/calculator/fees/CalculatorFeesListScreenUiTest.kt index a21835fe..2a98ceea 100644 --- a/features/wearos/calculator-fees/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/calculator/fees/CalculatorFeesListScreenUiTest.kt +++ b/features/wearos/calculator-fees/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/calculator/fees/CalculatorFeesListScreenUiTest.kt @@ -6,6 +6,7 @@ package dev.marlonlom.mocca.wearos.calculator.fees import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithText +import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState import org.junit.Rule import org.junit.Test @@ -19,6 +20,7 @@ internal class CalculatorFeesListScreenUiTest { with(rule) { setContent { CalculatorFeesListScreen( + listState = rememberScalingLazyListState(), onBackNavigationAction = {}, ) } diff --git a/features/wearos/calculator-fees/src/main/kotlin/dev/marlonlom/mocca/wearos/calculator/fees/CalculatorFeesListScreen.kt b/features/wearos/calculator-fees/src/main/kotlin/dev/marlonlom/mocca/wearos/calculator/fees/CalculatorFeesListScreen.kt index 7a38c3a7..e09a210c 100644 --- a/features/wearos/calculator-fees/src/main/kotlin/dev/marlonlom/mocca/wearos/calculator/fees/CalculatorFeesListScreen.kt +++ b/features/wearos/calculator-fees/src/main/kotlin/dev/marlonlom/mocca/wearos/calculator/fees/CalculatorFeesListScreen.kt @@ -20,8 +20,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.foundation.lazy.ScalingLazyListState import androidx.wear.compose.foundation.lazy.itemsIndexed -import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState import androidx.wear.compose.material3.MaterialTheme import androidx.wear.compose.material3.Text import dev.marlonlom.mocca.wearos.calculator.fees.component.CalculationFeeListItem @@ -33,18 +33,18 @@ import dev.marlonlom.mocca.wearos.calculator.fees.domain.CalculatorFeesProvider * * @author marlonlom * + * @param listState Lazy column state. * @param onBackNavigationAction Callback invoked when the user requests to navigate * back to the previous screen. */ @Composable -fun CalculatorFeesListScreen(onBackNavigationAction: () -> Unit) { +fun CalculatorFeesListScreen(listState: ScalingLazyListState, onBackNavigationAction: () -> Unit) { BackHandler { onBackNavigationAction() } val feesListingsState: List = remember { CalculatorFeesProvider.provideFees() } - val listState = rememberScalingLazyListState() ScalingLazyColumn( state = listState, modifier = Modifier From f671926208388152a9e01b3d0fcdb527176b2b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sun, 21 Jun 2026 01:30:31 -0500 Subject: [PATCH 05/10] refactor(wearos:onboarding): hoist listState in OnboardingScreen Hoists the ScalingLazyListState parameter to OnboardingScreen to match the project's state-hoisting patterns and align with recent UI changes. Updates the corresponding instrumentation tests to provide a remembered list state. --- .../mocca/wearos/onboarding/OnboardingScreenUiTest.kt | 3 +++ .../marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreenUiTest.kt b/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreenUiTest.kt index 1263ebaa..0a1c516c 100644 --- a/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreenUiTest.kt +++ b/features/wearos/onboarding/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreenUiTest.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState import com.google.common.truth.Truth.assertThat import org.junit.FixMethodOrder import org.junit.Rule @@ -27,6 +28,7 @@ internal class OnboardingScreenUiTest { var clicked = false setContent { OnboardingScreen( + listState = rememberScalingLazyListState(), onCalculateClick = { clicked = true }, onViewFeesClick = { }, ) @@ -45,6 +47,7 @@ internal class OnboardingScreenUiTest { var clicked = false setContent { OnboardingScreen( + listState = rememberScalingLazyListState(), onCalculateClick = { }, onViewFeesClick = { clicked = true }, ) diff --git a/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt index 2556ded4..3a1929ea 100644 --- a/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt +++ b/features/wearos/onboarding/src/main/kotlin/dev/marlonlom/mocca/wearos/onboarding/OnboardingScreen.kt @@ -14,7 +14,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.ScalingLazyColumn import androidx.wear.compose.foundation.lazy.ScalingLazyListState -import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState import androidx.wear.compose.material3.MaterialTheme import dev.marlonlom.mocca.wearos.onboarding.component.StartCalculationButton import dev.marlonlom.mocca.wearos.onboarding.component.ViewFeesButton @@ -24,12 +23,12 @@ import dev.marlonlom.mocca.wearos.onboarding.component.ViewFeesButton * * @author marlonlom * + * @param listState Lazy column state. * @param onCalculateClick Invoked when the user selects the calculate action. * @param onViewFeesClick Invoked when the user selects the view fees action. */ @Composable -fun OnboardingScreen(onCalculateClick: () -> Unit, onViewFeesClick: () -> Unit) { - val listState: ScalingLazyListState = rememberScalingLazyListState() +fun OnboardingScreen(listState: ScalingLazyListState, onCalculateClick: () -> Unit, onViewFeesClick: () -> Unit) = ScalingLazyColumn( state = listState, modifier = Modifier @@ -47,4 +46,3 @@ fun OnboardingScreen(onCalculateClick: () -> Unit, onViewFeesClick: () -> Unit) ViewFeesButton(onClicked = onViewFeesClick) } } -} From 397595d611945fb3efee74b364f3add9a1c5391e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sun, 21 Jun 2026 01:31:08 -0500 Subject: [PATCH 06/10] feat(wearos:ui): implement home and view fees navigation destinations Refactors NavigationHost to incorporate the new onboarding user flow. Reassigns the Home route destination to a new welcome screen and introduces explicit destinations and route definitions for Calculator and ViewFees. --- .../wearos/ui/navigation/NavigationHost.kt | 19 +++++++++++++++++++ .../wearos/ui/navigation/NavigationRoutes.kt | 16 +++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationHost.kt b/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationHost.kt index 55e23fa4..c1144f4d 100644 --- a/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationHost.kt +++ b/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationHost.kt @@ -9,8 +9,10 @@ import androidx.navigation.NavHostController import androidx.wear.compose.navigation.SwipeDismissableNavHost import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.rememberSwipeDismissableNavController +import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.Calculator import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.Home import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.Result +import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.ViewFees /** * Application navigation host composable. @@ -23,14 +25,31 @@ import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.Result */ @Composable fun NavigationHost( + home: @Composable (() -> Unit, () -> Unit) -> Unit, calculatorInput: @Composable ((String) -> Unit) -> Unit, calculatorOutput: @Composable (String, () -> Unit) -> Unit, + viewFees: @Composable (() -> Unit) -> Unit, navController: NavHostController = rememberSwipeDismissableNavController(), ) = SwipeDismissableNavHost( navController = navController, startDestination = Home.route, ) { composable(route = Home.route) { + home( + { + navController.navigate(Calculator.route) + }, + { + navController.navigate(ViewFees.route) + }, + ) + } + + composable(route = ViewFees.route) { + viewFees(navController::popBackStack) + } + + composable(route = Calculator.route) { calculatorInput { amountText -> navController.navigate(Result.makeRoute(amountText)) } diff --git a/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationRoutes.kt b/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationRoutes.kt index 390477d6..bc61386f 100644 --- a/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationRoutes.kt +++ b/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationRoutes.kt @@ -20,7 +20,21 @@ sealed class NavigationRoutes(val route: String) { * * @author marlonlom */ - data object Home : NavigationRoutes(route = "calculator_input") + data object Home : NavigationRoutes(route = "welcome") + + /** + * Calculation fees listing navigation route single object. + * + * @author marlonlom + */ + data object ViewFees : NavigationRoutes(route = "calculator_fees") + + /** + * Calculation input navigation route single object. + * + * @author marlonlom + */ + data object Calculator : NavigationRoutes(route = "calculator_input") /** * Calculation result navigation route single object. From 24a9e3e6431969c23827c0315bef9c60273efc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sun, 21 Jun 2026 01:31:58 -0500 Subject: [PATCH 07/10] feat(apps:wearos): integrate onboarding and calculator fees screens into app navigation Connects the :features:wearos:onboarding module to the application build file. Updates WearAppContent to wire up the new OnboardingScreen and CalculatorFeesListScreen destinations within the NavigationHost, utilizing a shared scalingLazyListState. --- apps/wearos/build.gradle.kts | 1 + .../mocca/wearos/ui/main/WearAppContent.kt | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/apps/wearos/build.gradle.kts b/apps/wearos/build.gradle.kts index 38c7afea..162fe24a 100644 --- a/apps/wearos/build.gradle.kts +++ b/apps/wearos/build.gradle.kts @@ -14,5 +14,6 @@ dependencies { implementation(project(":features:wearos:calculator-fees")) implementation(project(":features:wearos:calculator-input")) implementation(project(":features:wearos:calculator-output")) + implementation(project(":features:wearos:onboarding")) implementation(project(":features:wearos:ui")) } diff --git a/apps/wearos/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/main/WearAppContent.kt b/apps/wearos/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/main/WearAppContent.kt index 825ab9b0..a839b3ed 100644 --- a/apps/wearos/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/main/WearAppContent.kt +++ b/apps/wearos/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/main/WearAppContent.kt @@ -17,8 +17,10 @@ import androidx.wear.compose.material3.Text import androidx.wear.compose.material3.TimeText import androidx.wear.compose.material3.TimeTextDefaults import androidx.wear.compose.material3.timeTextCurvedText +import dev.marlonlom.mocca.wearos.calculator.fees.CalculatorFeesListScreen import dev.marlonlom.mocca.wearos.calculator.input.CalculatorInputScreen import dev.marlonlom.mocca.wearos.calculator.output.CalculatorOutputScreen +import dev.marlonlom.mocca.wearos.onboarding.OnboardingScreen import dev.marlonlom.mocca.wearos.ui.navigation.NavigationHost /** @@ -56,6 +58,19 @@ fun WearAppContent() { }, content = { NavigationHost( + home = { onCalculateClick, onViewFeesClick -> + OnboardingScreen( + listState = scalingLazyListState, + onCalculateClick = onCalculateClick, + onViewFeesClick = onViewFeesClick, + ) + }, + viewFees = { onBackNavigationAction -> + CalculatorFeesListScreen( + listState = scalingLazyListState, + onBackNavigationAction = onBackNavigationAction, + ) + }, calculatorInput = { onCalculationReadyAction -> CalculatorInputScreen(onCalculationReadyAction = onCalculationReadyAction) }, From ab8302ab7a1590bec7fd2e4e10afa97e5d4c66e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sun, 21 Jun 2026 01:48:46 -0500 Subject: [PATCH 08/10] docs(wearos:ui): update NavigationHost KDoc parameters and description Updates the documentation for the NavigationHost composable to accurately reflect the current parameters (home, calculatorInput, calculatorOutput, viewFees, and navController) and clarify its overall purpose. --- .../mocca/wearos/ui/navigation/NavigationHost.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationHost.kt b/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationHost.kt index c1144f4d..b751683c 100644 --- a/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationHost.kt +++ b/features/wearos/ui/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationHost.kt @@ -15,13 +15,15 @@ import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.Result import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.ViewFees /** - * Application navigation host composable. + * Navigation host that wires together app screens using a NavHostController. * * @author marlonlom * - * @param calculatorInput Calculator input composable. - * @param calculatorOutput Calculator output composable. - * @param navController Navigation controller. + * @param home Home screen with primary and secondary navigation actions. + * @param calculatorInput Screen for entering calculation input. + * @param calculatorOutput Screen for displaying results and handling navigation. + * @param viewFees Screen showing fee information. + * @param navController Navigation controller (defaults to swipe-dismissable controller). */ @Composable fun NavigationHost( From a5fd163b5ffef0d9351d2dea732c30049a4865f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sun, 21 Jun 2026 01:49:42 -0500 Subject: [PATCH 09/10] refactor(apps:wearos): expose state parameters in WearAppContent and fix UI tests Refactors WearAppContent to accept isRound and scalingLazyListState as composable parameters with default values, improving testability and separation of concerns. Updates the corresponding KDoc documentation. Fixes SmartwatchActivityTest UI tests by ensuring the initial onboarding interaction (start_calculation_action_button click) is explicitly performed before validating the input screen contents. --- .../wearos/ui/main/SmartwatchActivityTest.kt | 5 + .../mocca/wearos/ui/main/WearAppContent.kt | 109 +++++++++--------- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/apps/wearos/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/ui/main/SmartwatchActivityTest.kt b/apps/wearos/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/ui/main/SmartwatchActivityTest.kt index db876764..aebad79e 100644 --- a/apps/wearos/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/ui/main/SmartwatchActivityTest.kt +++ b/apps/wearos/src/androidTest/kotlin/dev/marlonlom/mocca/wearos/ui/main/SmartwatchActivityTest.kt @@ -28,6 +28,7 @@ internal class SmartwatchActivityTest { @Test fun shouldDisplayActivityContents() { with(androidComposeTestRule) { + onNodeWithTag("start_calculation_action_button").assertIsDisplayed().performClick() onNodeWithText("Money amount").assertIsDisplayed() } } @@ -35,6 +36,7 @@ internal class SmartwatchActivityTest { @Test fun shouldShowCalculationBelowRangeFailure() { with(androidComposeTestRule) { + onNodeWithTag("start_calculation_action_button").assertIsDisplayed().performClick() onNodeWithText("Money amount").assertIsDisplayed() onNodeWithTag("calculatorButton_9").performClick().performClick().performClick() onNodeWithText("✔").performClick() @@ -46,6 +48,7 @@ internal class SmartwatchActivityTest { @Test fun shouldShowCalculationAboveRangeFailure() { with(androidComposeTestRule) { + onNodeWithTag("start_calculation_action_button").assertIsDisplayed().performClick() onNodeWithText("Money amount").assertIsDisplayed() onNodeWithTag("calculatorButton_4").performClick() onNodeWithTag("calculatorButton_5").performClick() @@ -60,6 +63,7 @@ internal class SmartwatchActivityTest { @Test fun shouldShowCalculationSuccess() { with(androidComposeTestRule) { + onNodeWithTag("start_calculation_action_button").assertIsDisplayed().performClick() onNodeWithText("Money amount").assertIsDisplayed() onNodeWithTag("calculatorButton_4").performClick() onNodeWithTag("calculatorButton_5").performClick() @@ -76,6 +80,7 @@ internal class SmartwatchActivityTest { @Test fun shouldShowFailureThenSuccessMakingCalculation() { with(androidComposeTestRule) { + onNodeWithTag("start_calculation_action_button").assertIsDisplayed().performClick() onNodeWithText("Money amount").assertIsDisplayed() onNodeWithTag("calculatorButton_4").performClick() diff --git a/apps/wearos/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/main/WearAppContent.kt b/apps/wearos/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/main/WearAppContent.kt index a839b3ed..daee342c 100644 --- a/apps/wearos/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/main/WearAppContent.kt +++ b/apps/wearos/src/main/kotlin/dev/marlonlom/mocca/wearos/ui/main/WearAppContent.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.text.style.TextAlign import androidx.wear.compose.foundation.CurvedTextStyle +import androidx.wear.compose.foundation.lazy.ScalingLazyListState import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState import androidx.wear.compose.material3.AppScaffold import androidx.wear.compose.material3.MaterialTheme @@ -24,63 +25,65 @@ import dev.marlonlom.mocca.wearos.onboarding.OnboardingScreen import dev.marlonlom.mocca.wearos.ui.navigation.NavigationHost /** - * Wear app main content composable. + * Root composable for Wear OS app content. * * @author marlonlom + * + * @param isRound Whether the device screen is round. + * @param scalingLazyListState State holder for ScalingLazyColumn scroll position. */ @Composable -fun WearAppContent() { - val isRound = LocalConfiguration.current.isScreenRound - val scalingLazyListState = rememberScalingLazyListState() - AppScaffold( - timeText = { - if (!scalingLazyListState.isScrollInProgress) { - val fontStyle = MaterialTheme.typography.bodySmall - if (isRound) { - TimeText { time -> - timeTextCurvedText( - time = time, - style = CurvedTextStyle( - fontSize = fontStyle.fontSize, - ), - ) - } - } else { - val time = TimeTextDefaults.rememberTimeSource(TimeTextDefaults.timeFormat()).currentTime() - Text( - modifier = Modifier.fillMaxWidth(), - text = time, - textAlign = TextAlign.Center, - style = fontStyle, +fun WearAppContent( + isRound: Boolean = LocalConfiguration.current.isScreenRound, + scalingLazyListState: ScalingLazyListState = rememberScalingLazyListState(), +) = AppScaffold( + timeText = { + if (!scalingLazyListState.isScrollInProgress) { + val fontStyle = MaterialTheme.typography.bodySmall + if (isRound) { + TimeText { time -> + timeTextCurvedText( + time = time, + style = CurvedTextStyle( + fontSize = fontStyle.fontSize, + ), ) } + } else { + val time = TimeTextDefaults.rememberTimeSource(TimeTextDefaults.timeFormat()).currentTime() + Text( + modifier = Modifier.fillMaxWidth(), + text = time, + textAlign = TextAlign.Center, + style = fontStyle, + ) } - }, - content = { - NavigationHost( - home = { onCalculateClick, onViewFeesClick -> - OnboardingScreen( - listState = scalingLazyListState, - onCalculateClick = onCalculateClick, - onViewFeesClick = onViewFeesClick, - ) - }, - viewFees = { onBackNavigationAction -> - CalculatorFeesListScreen( - listState = scalingLazyListState, - onBackNavigationAction = onBackNavigationAction, - ) - }, - calculatorInput = { onCalculationReadyAction -> - CalculatorInputScreen(onCalculationReadyAction = onCalculationReadyAction) - }, - calculatorOutput = { amountText, onBackNavigationAction -> - CalculatorOutputScreen( - amountText = amountText, - onBackNavigationAction = onBackNavigationAction, - ) - }, - ) - }, - ) -} + } + }, + content = { + NavigationHost( + home = { onCalculateClick, onViewFeesClick -> + OnboardingScreen( + listState = scalingLazyListState, + onCalculateClick = onCalculateClick, + onViewFeesClick = onViewFeesClick, + ) + }, + viewFees = { onBackNavigationAction -> + CalculatorFeesListScreen( + listState = scalingLazyListState, + onBackNavigationAction = onBackNavigationAction, + ) + }, + calculatorInput = { onCalculationReadyAction -> + CalculatorInputScreen(onCalculationReadyAction = onCalculationReadyAction) + }, + calculatorOutput = { amountText, onBackNavigationAction -> + CalculatorOutputScreen( + amountText = amountText, + onBackNavigationAction = onBackNavigationAction, + ) + }, + ) + }, +) From 4820327fd9f65597049a6cc550b1190f86364b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marlon=20L=C3=B3pez?= Date: Sun, 21 Jun 2026 09:23:29 -0500 Subject: [PATCH 10/10] test(wearos:ui): refactor and add tests for NavigationRoutes - Fix Home.route assertion to match the updated "welcome" destination. - Add ViewFees.route assertion for "calculator_fees". - Add Calculator.route assertion for "calculator_input". --- .../wearos/ui/navigation/NavigationRoutesTest.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/features/wearos/ui/src/test/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationRoutesTest.kt b/features/wearos/ui/src/test/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationRoutesTest.kt index 6e8af126..926081c3 100644 --- a/features/wearos/ui/src/test/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationRoutesTest.kt +++ b/features/wearos/ui/src/test/kotlin/dev/marlonlom/mocca/wearos/ui/navigation/NavigationRoutesTest.kt @@ -4,8 +4,10 @@ */ package dev.marlonlom.mocca.wearos.ui.navigation +import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.Calculator import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.Home import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.Result +import dev.marlonlom.mocca.wearos.ui.navigation.NavigationRoutes.ViewFees import org.junit.Assert.assertEquals import org.junit.Test @@ -13,7 +15,17 @@ internal class NavigationRoutesTest { @Test fun `Should assert home destination route`() { - assertEquals("calculator_input", Home.route) + assertEquals("welcome", Home.route) + } + + @Test + fun `Should assert fees listing destination route`() { + assertEquals("calculator_fees", ViewFees.route) + } + + @Test + fun `Should assert calculator input destination route`() { + assertEquals("calculator_input", Calculator.route) } @Test