Skip to content

Commit a5b9335

Browse files
committed
feat(onboarding): match profile choices and required progress
The prior setup tests checked that Finish was enabled after optional skips but omitted the header count, allowing a misleading incomplete fraction. Extend them to cover the required-only progress rule.
1 parent 63ebc63 commit a5b9335

5 files changed

Lines changed: 66 additions & 28 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ All notable changes to CallShield will be documented in this file.
44

55
## Unreleased
66

7+
- Setup now counts only the two required grants in its progress header. Its
8+
protection cards use tighter spacing and clearer selection styling.
79
- Home now puts today's results, call and text readiness, the latest blocked
810
caller and protection levels ahead of the longer checks and tools.
911
- Home shows an empty recent-calls card until CallShield has stopped a call.

‎app/src/androidTest/java/com/sysadmindoc/callshield/ui/screens/onboarding/OnboardingTest.kt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class OnboardingTest {
129129
repeat(3) { composeRule.onNodeWithTag(ONBOARDING_SKIP_OPTIONAL_BUTTON_TAG).performClick() }
130130

131131
assertPage(7, "Protection is ready")
132+
composeRule.onNodeWithText("2 of 2 ready").assertIsDisplayed()
132133
composeRule.onAllNodesWithText("Skipped")[0].assertIsDisplayed()
133134
composeRule
134135
.onNodeWithText("Floating caller ID and live enrichment cards stay hidden until overlay access is allowed.")

‎app/src/main/java/com/sysadmindoc/callshield/ui/screens/onboarding/OnboardingScreen.kt‎

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import androidx.compose.material.icons.filled.Shield
5050
import androidx.compose.material.icons.filled.VerifiedUser
5151
import androidx.compose.material3.HorizontalDivider
5252
import androidx.compose.material3.Icon
53-
import androidx.compose.material3.LinearProgressIndicator
5453
import androidx.compose.material3.MaterialTheme
5554
import androidx.compose.material3.RadioButton
5655
import androidx.compose.material3.SnackbarHost
@@ -72,15 +71,18 @@ import androidx.compose.runtime.setValue
7271
import androidx.compose.ui.Alignment
7372
import androidx.compose.ui.Modifier
7473
import androidx.compose.ui.graphics.Color
74+
import androidx.compose.ui.graphics.compositeOver
7575
import androidx.compose.ui.graphics.vector.ImageVector
7676
import androidx.compose.ui.platform.LocalContext
7777
import androidx.compose.ui.platform.testTag
7878
import androidx.compose.ui.res.painterResource
7979
import androidx.compose.ui.res.stringResource
8080
import androidx.compose.ui.semantics.LiveRegionMode
81+
import androidx.compose.ui.semantics.ProgressBarRangeInfo
8182
import androidx.compose.ui.semantics.Role
8283
import androidx.compose.ui.semantics.contentDescription
8384
import androidx.compose.ui.semantics.liveRegion
85+
import androidx.compose.ui.semantics.progressBarRangeInfo
8486
import androidx.compose.ui.semantics.semantics
8587
import androidx.compose.ui.text.font.FontWeight
8688
import androidx.compose.ui.text.style.TextAlign
@@ -98,6 +100,7 @@ import com.sysadmindoc.callshield.ui.theme.CatGreen
98100
import com.sysadmindoc.callshield.ui.theme.CatMauve
99101
import com.sysadmindoc.callshield.ui.theme.CatOverlay
100102
import com.sysadmindoc.callshield.ui.theme.CatPeach
103+
import com.sysadmindoc.callshield.ui.theme.CatRed
101104
import com.sysadmindoc.callshield.ui.theme.CatSubtext
102105
import com.sysadmindoc.callshield.ui.theme.CatTeal
103106
import com.sysadmindoc.callshield.ui.theme.CatText
@@ -410,18 +413,32 @@ internal fun OnboardingScreenContent(
410413
fontWeight = FontWeight.SemiBold,
411414
)
412415
Text(
413-
text = stringResource(R.string.onboarding_ready_count, setupState.completedSetupCount, setupSteps.size),
416+
text = stringResource(R.string.onboarding_ready_count, setupState.requiredSetupCount, setupState.requiredSetupTotal),
414417
style = MaterialTheme.typography.labelMedium,
415418
color = if (setupState.isReady) CatGreen else CatSubtext,
416419
)
417420
}
418421
Spacer(Modifier.height(8.dp))
419-
LinearProgressIndicator(
420-
progress = { (currentPage + 1) / onboardingSteps.size.toFloat() },
421-
modifier = Modifier.fillMaxWidth().height(4.dp),
422-
color = presentation.accent,
423-
trackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
424-
)
422+
Row(
423+
modifier =
424+
Modifier.fillMaxWidth().semantics {
425+
progressBarRangeInfo = ProgressBarRangeInfo((currentPage + 1).toFloat(), 1f..onboardingSteps.size.toFloat())
426+
},
427+
horizontalArrangement = Arrangement.spacedBy(4.dp),
428+
) {
429+
onboardingSteps.indices.forEach { index ->
430+
Box(
431+
modifier =
432+
Modifier
433+
.weight(1f)
434+
.height(4.dp)
435+
.background(
436+
if (index <= currentPage) presentation.accent else MaterialTheme.colorScheme.surfaceContainerHighest,
437+
RoundedCornerShape(4.dp),
438+
),
439+
)
440+
}
441+
}
425442
Text(
426443
text = stringResource(R.string.onboarding_step, currentPage + 1, onboardingSteps.size),
427444
modifier = Modifier.padding(top = 8.dp),
@@ -755,41 +772,51 @@ private fun OnboardingProfileOptions(
755772
)
756773
options.forEach { (profile, labelRes, tag) ->
757774
val selected = selectedProfile == profile
775+
val accent =
776+
when (profile) {
777+
BlockingProfiles.Profile.WORK -> CatGreen
778+
BlockingProfiles.Profile.MAX -> CatRed
779+
else -> CatBlue
780+
}
758781
Surface(
759782
modifier =
760783
Modifier
761784
.fillMaxWidth()
762785
.selectable(selected = selected, role = Role.RadioButton) { onChooseProfile(profile) }
763786
.testTag(tag),
764-
color = MaterialTheme.colorScheme.surfaceContainerLow,
787+
color =
788+
if (selected) {
789+
accent.copy(alpha = 0.08f).compositeOver(MaterialTheme.colorScheme.surfaceContainerLow)
790+
} else {
791+
MaterialTheme.colorScheme.surfaceContainerLow
792+
},
765793
shape = RoundedCornerShape(12.dp),
766-
border = BorderStroke(1.dp, if (selected) CatGreen else CatOverlay.copy(alpha = 0.35f)),
794+
border = BorderStroke(1.dp, if (selected) accent else CatOverlay.copy(alpha = 0.35f)),
767795
) {
768796
Row(
769-
modifier = Modifier.fillMaxWidth().padding(16.dp),
797+
modifier = Modifier.fillMaxWidth().padding(12.dp),
770798
verticalAlignment = Alignment.Top,
771-
horizontalArrangement = Arrangement.spacedBy(12.dp),
799+
horizontalArrangement = Arrangement.spacedBy(10.dp),
772800
) {
773-
Icon(
774-
imageVector = if (profile == BlockingProfiles.Profile.CONTACTS_ONLY) Icons.Default.VerifiedUser else Icons.Default.Shield,
775-
contentDescription = null,
776-
tint = if (selected) CatGreen else CatSubtext,
777-
modifier = Modifier.size(26.dp),
778-
)
801+
Box(
802+
modifier = Modifier.size(38.dp).background(accent.copy(alpha = 0.14f), RoundedCornerShape(19.dp)),
803+
contentAlignment = Alignment.Center,
804+
) {
805+
Icon(
806+
imageVector = if (profile == BlockingProfiles.Profile.CONTACTS_ONLY) Icons.Default.VerifiedUser else Icons.Default.Shield,
807+
contentDescription = null,
808+
tint = accent,
809+
modifier = Modifier.size(23.dp),
810+
)
811+
}
779812
Column(modifier = Modifier.weight(1f)) {
780813
Text(
781814
text = stringResource(labelRes),
782-
color = CatText,
815+
color = if (selected) accent else CatText,
783816
style = MaterialTheme.typography.titleMedium,
784817
fontWeight = FontWeight.SemiBold,
785818
)
786-
Spacer(Modifier.height(4.dp))
787-
Text(
788-
text = stringResource(profile.descriptionRes),
789-
color = CatSubtext,
790-
style = MaterialTheme.typography.bodySmall,
791-
)
792-
Spacer(Modifier.height(12.dp))
819+
Spacer(Modifier.height(8.dp))
793820
val details =
794821
listOf(
795822
R.string.profile_detail_calls to
@@ -804,7 +831,7 @@ private fun OnboardingProfileOptions(
804831
)
805832
details.forEach { (labelRes, valueRes) ->
806833
Row(
807-
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
834+
modifier = Modifier.fillMaxWidth().padding(vertical = 1.dp),
808835
horizontalArrangement = Arrangement.SpaceBetween,
809836
) {
810837
Text(stringResource(labelRes), color = CatSubtext, style = MaterialTheme.typography.bodySmall)

‎app/src/main/java/com/sysadmindoc/callshield/ui/screens/onboarding/OnboardingSetupFlow.kt‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,14 @@ internal data class OnboardingSetupState(
4444
val completedSetupCount: Int
4545
get() = setupSteps.count(::isComplete)
4646

47+
val requiredSetupCount: Int
48+
get() = setupSteps.count { it !in optionalSetupSteps && isComplete(it) }
49+
50+
val requiredSetupTotal: Int
51+
get() = setupSteps.count { it !in optionalSetupSteps }
52+
4753
val isReady: Boolean
48-
get() = setupSteps.filterNot { it in optionalSetupSteps }.all(::isComplete)
54+
get() = requiredSetupCount == requiredSetupTotal
4955
}
5056

5157
internal val onboardingSteps = OnboardingSetupStep.entries

‎app/src/test/java/com/sysadmindoc/callshield/ui/screens/onboarding/OnboardingSetupFlowTest.kt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class OnboardingSetupFlowTest {
3535

3636
assertTrue(state.isReady)
3737
assertEquals(2, state.completedSetupCount)
38+
assertEquals(2, state.requiredSetupCount)
39+
assertEquals(2, state.requiredSetupTotal)
3840
assertFalse(state.isComplete(OnboardingSetupStep.NotificationAccess))
3941
}
4042

0 commit comments

Comments
 (0)