Skip to content

Commit 010a3c5

Browse files
authored
feat(menu): show username card for auto-assigned handles (#1643)
The custom-username card only appeared for accounts with no handle, so a user whose handle the server assigned never got the nudge to pick their own. usernameGate now takes isAutoAssigned and treats an assigned handle as unclaimed; the balance and minimum rules are unchanged. The view model passes UserProfile.isUsernameAutoAssigned through.
1 parent fc5234f commit 010a3c5

3 files changed

Lines changed: 39 additions & 14 deletions

File tree

‎apps/flipcash/features/menu/src/main/kotlin/com/flipcash/app/menu/internal/MenuScreenViewModel.kt‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,11 +302,11 @@ internal class MenuScreenViewModel @Inject constructor(
302302
combine(
303303
userManager.state
304304
.filter { it.authState is AuthState.Ready }
305-
.map { it.userProfile?.username },
305+
.map { it.userProfile?.username to (it.userProfile?.isUsernameAutoAssigned == true) },
306306
userFlags.resolvedFlags.map { it.usernameMinBalance.effectiveValue },
307307
totalBalance.observeTotalBalance(),
308-
) { username, minimum, balance ->
309-
val progress = when (val gate = usernameGate(username, minimum, balance)) {
308+
) { (username, isAutoAssigned), minimum, balance ->
309+
val progress = when (val gate = usernameGate(username, isAutoAssigned, minimum, balance)) {
310310
UsernameGate.Claimed -> null
311311
UsernameGate.Unlocked -> UsernameProgress.Unlocked
312312
is UsernameGate.Locked -> UsernameProgress.Locked(

‎apps/flipcash/features/menu/src/main/kotlin/com/flipcash/app/menu/internal/UsernameGate.kt‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import com.getcode.opencode.model.financial.minus
1616
* Mirrors iOS `usernameGate(session:minimum:)` in `UsernameGate.swift`.
1717
*/
1818
internal sealed interface UsernameGate {
19-
/** A handle is already claimed — the nudge is spent, and changing it lives in My Account. */
19+
/**
20+
* A handle the user chose is already claimed — the nudge is spent, and changing it lives in
21+
* My Account. A handle the server assigned does not count: the user hasn't picked it.
22+
*/
2023
data object Claimed : UsernameGate
2124

2225
/** Nothing in the way: either the balance clears the minimum, or there is no minimum. */
@@ -35,16 +38,19 @@ internal sealed interface UsernameGate {
3538
}
3639

3740
/**
38-
* @param username the account's claimed handle, null or blank when it hasn't claimed one.
41+
* @param username the account's handle, null or blank when it has none.
42+
* @param isAutoAssigned whether [username] was assigned by the server rather than chosen by the
43+
* user. An assigned handle still counts as unclaimed, so the nudge keeps showing.
3944
* @param minimum the balance the account must hold to claim, from the `usernameMinBalance` flag.
4045
* @param balance the account's total balance, in the same currency as [minimum].
4146
*/
4247
internal fun usernameGate(
4348
username: String?,
49+
isAutoAssigned: Boolean,
4450
minimum: Fiat,
4551
balance: Fiat,
4652
): UsernameGate = when {
47-
!username.isNullOrBlank() -> UsernameGate.Claimed
53+
!username.isNullOrBlank() && !isAutoAssigned -> UsernameGate.Claimed
4854
// A zero minimum is no gate at all — which is also what an unresolved flag looks like. Either
4955
// reading leaves nothing holding the account back, so both fail open.
5056
!minimum.isPositive -> UsernameGate.Unlocked

‎apps/flipcash/features/menu/src/test/kotlin/com/flipcash/app/menu/internal/UsernameGateTest.kt‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,63 @@ class UsernameGateTest {
1414
fun `a claimed handle spends the nudge, whatever the balance`() {
1515
assertEquals(
1616
UsernameGate.Claimed,
17-
usernameGate(username = "mcansh", minimum = usd(25.0), balance = Fiat.Zero),
17+
usernameGate(username = "mcansh", isAutoAssigned = false, minimum = usd(25.0), balance = Fiat.Zero),
1818
)
1919
}
2020

2121
@Test
2222
fun `a blank handle counts as unclaimed`() {
2323
assertIs<UsernameGate.Locked>(
24-
usernameGate(username = " ", minimum = usd(25.0), balance = Fiat.Zero),
24+
usernameGate(username = " ", isAutoAssigned = false, minimum = usd(25.0), balance = Fiat.Zero),
25+
)
26+
}
27+
28+
@Test
29+
fun `an auto-assigned handle still counts as unclaimed`() {
30+
assertIs<UsernameGate.Locked>(
31+
usernameGate(username = "user4821", isAutoAssigned = true, minimum = usd(25.0), balance = Fiat.Zero),
32+
)
33+
}
34+
35+
@Test
36+
fun `an auto-assigned handle follows the balance rules of no handle`() {
37+
assertEquals(
38+
UsernameGate.Unlocked,
39+
usernameGate(username = "user4821", isAutoAssigned = true, minimum = usd(25.0), balance = usd(25.0)),
40+
)
41+
assertEquals(
42+
UsernameGate.Unlocked,
43+
usernameGate(username = "user4821", isAutoAssigned = true, minimum = Fiat.Zero, balance = Fiat.Zero),
2544
)
2645
}
2746

2847
@Test
2948
fun `a zero minimum fails open`() {
3049
assertEquals(
3150
UsernameGate.Unlocked,
32-
usernameGate(username = null, minimum = Fiat.Zero, balance = Fiat.Zero),
51+
usernameGate(username = null, isAutoAssigned = false, minimum = Fiat.Zero, balance = Fiat.Zero),
3352
)
3453
}
3554

3655
@Test
3756
fun `exactly the minimum unlocks`() {
3857
assertEquals(
3958
UsernameGate.Unlocked,
40-
usernameGate(username = null, minimum = usd(25.0), balance = usd(25.0)),
59+
usernameGate(username = null, isAutoAssigned = false, minimum = usd(25.0), balance = usd(25.0)),
4160
)
4261
}
4362

4463
@Test
4564
fun `above the minimum unlocks`() {
4665
assertEquals(
4766
UsernameGate.Unlocked,
48-
usernameGate(username = null, minimum = usd(25.0), balance = usd(25.01)),
67+
usernameGate(username = null, isAutoAssigned = false, minimum = usd(25.0), balance = usd(25.01)),
4968
)
5069
}
5170

5271
@Test
5372
fun `below the minimum reports the shortfall and how far along it is`() {
54-
val gate = usernameGate(username = null, minimum = usd(25.0), balance = usd(20.0))
73+
val gate = usernameGate(username = null, isAutoAssigned = false, minimum = usd(25.0), balance = usd(20.0))
5574

5675
assertIs<UsernameGate.Locked>(gate)
5776
assertEquals(usd(25.0), gate.minimum)
@@ -61,7 +80,7 @@ class UsernameGateTest {
6180

6281
@Test
6382
fun `an empty balance is zero progress, not a divide by zero`() {
64-
val gate = usernameGate(username = null, minimum = usd(25.0), balance = Fiat.Zero)
83+
val gate = usernameGate(username = null, isAutoAssigned = false, minimum = usd(25.0), balance = Fiat.Zero)
6584

6685
assertIs<UsernameGate.Locked>(gate)
6786
assertEquals(usd(25.0), gate.shortfall)
@@ -70,7 +89,7 @@ class UsernameGateTest {
7089

7190
@Test
7291
fun `a negative balance clamps to zero progress rather than a backwards bar`() {
73-
val gate = usernameGate(username = null, minimum = usd(25.0), balance = usd(-5.0))
92+
val gate = usernameGate(username = null, isAutoAssigned = false, minimum = usd(25.0), balance = usd(-5.0))
7493

7594
assertIs<UsernameGate.Locked>(gate)
7695
assertEquals(0f, gate.fraction)

0 commit comments

Comments
 (0)