Skip to content

Commit 8d15780

Browse files
committed
feat: add UserFlag visibility and editor to Labs
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent b9e6800 commit 8d15780

25 files changed

Lines changed: 945 additions & 132 deletions

File tree

apps/flipcash/app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ dependencies {
189189
implementation(project(":apps:flipcash:features:transactions"))
190190
implementation(project(":apps:flipcash:features:bill-customization"))
191191
implementation(project(":apps:flipcash:features:discovery"))
192+
implementation(project(":apps:flipcash:features:userflags"))
192193

193194
implementation(project(":libs:crypto:solana"))
194195
implementation(project(":libs:datetime"))

apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/ui/navigation/AppScreenContent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import com.flipcash.app.tokens.TokenSelectScreen
5252
import com.flipcash.app.tokens.TokenSellReceiptScreen
5353
import com.flipcash.app.tokens.TokenTxProcessingScreen
5454
import com.flipcash.app.transactions.TransactionHistoryScreen
55+
import com.flipcash.app.userflags.UserFlagsScreen
5556
import com.flipcash.app.withdrawal.WithdrawalConfirmationScreen
5657
import com.flipcash.app.withdrawal.WithdrawalDestinationScreen
5758
import com.flipcash.app.withdrawal.WithdrawalEntryScreen
@@ -149,6 +150,7 @@ fun appEntryProvider(
149150
annotatedEntry<AppRoute.Menu.BackupKey> { BackupKeyScreen() }
150151
annotatedEntry<AppRoute.Menu.AdvancedFeatures> { AdvancedFeaturesScreen() }
151152

153+
annotatedEntry<AppRoute.UserFlags> { UserFlagsScreen() }
152154
// Transfers
153155
annotatedEntry<AppRoute.Transfers.Withdrawal.Amount> { key ->
154156
remember { WithdrawalFlow.start() }

apps/flipcash/core/src/main/kotlin/com/flipcash/app/core/AppRoute.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,5 +172,5 @@ sealed interface AppRoute : NavKey, Parcelable {
172172

173173
@Serializable
174174
@Parcelize
175-
sealed interface Advanced : AppRoute
175+
data object UserFlags : AppRoute
176176
}

apps/flipcash/core/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,4 +457,9 @@
457457
<string name="prompt_title_notYetAvailable">Not Yet Available</string>
458458
<string name="prompt_message_currencyCreateNotYetAvailable">Check back soon</string>
459459

460+
<string name="title_settingsSectionFeatures">Features</string>
461+
<string name="title_settingsSectionDeveloper">Developer</string>
462+
<string name="subtitle_settingsUserFlags">User Flags</string>
463+
<string name="title_settingsSectionAccount">Account</string>
464+
<string name="title_userFlags">User Flags</string>
460465
</resources>

apps/flipcash/features/lab/src/main/kotlin/com/flipcash/app/lab/internal/LabsScreenContent.kt

Lines changed: 95 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,134 @@
11
package com.flipcash.app.lab.internal
22

3-
import androidx.compose.foundation.layout.Arrangement
43
import androidx.compose.foundation.layout.Box
54
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.PaddingValues
66
import androidx.compose.foundation.layout.fillMaxSize
77
import androidx.compose.foundation.layout.fillMaxWidth
8-
import androidx.compose.foundation.layout.navigationBarsPadding
98
import androidx.compose.foundation.layout.padding
109
import androidx.compose.foundation.lazy.LazyColumn
1110
import androidx.compose.foundation.lazy.items
12-
import androidx.compose.material.Text
11+
import androidx.compose.foundation.lazy.rememberLazyListState
12+
import androidx.compose.material.icons.Icons
13+
import androidx.compose.material.icons.filled.MarkEmailUnread
14+
import androidx.compose.material.icons.filled.PhonelinkErase
15+
import androidx.compose.material.icons.filled.Token
16+
import androidx.compose.material3.HorizontalDivider
17+
import androidx.compose.material3.Text
1318
import androidx.compose.runtime.Composable
1419
import androidx.compose.runtime.getValue
1520
import androidx.compose.ui.Alignment
1621
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.graphics.vector.rememberVectorPainter
1723
import androidx.compose.ui.res.stringResource
24+
import androidx.compose.ui.unit.dp
1825
import androidx.lifecycle.compose.collectAsStateWithLifecycle
26+
import com.flipcash.app.core.AppRoute
27+
import com.flipcash.app.core.extensions.navigateTo
1928
import com.flipcash.app.featureflags.LocalFeatureFlags
2029
import com.flipcash.app.featureflags.message
2130
import com.flipcash.app.featureflags.title
2231
import com.flipcash.features.lab.R
32+
import com.getcode.navigation.core.LocalCodeNavigator
2333
import com.getcode.theme.CodeTheme
34+
import com.getcode.ui.components.ListItem
2435
import com.getcode.ui.components.SettingsSwitchRow
25-
import com.getcode.ui.theme.ButtonState
26-
import com.getcode.ui.theme.CodeButton
27-
import com.getcode.ui.theme.CodeScaffold
36+
import com.getcode.ui.components.text.SectionHeader
37+
import com.getcode.ui.core.verticalScrollStateGradient
38+
import com.getcode.ui.utils.sheetResignmentBehavior
2839

2940
@Composable
3041
internal fun LabsScreenContent(viewModel: LabsScreenViewModel) {
3142
val betaFlagsController = LocalFeatureFlags.current
3243
val betaFlags by betaFlagsController.observe().collectAsStateWithLifecycle()
33-
44+
val navigator = LocalCodeNavigator.current
3445
val isLoggedIn by viewModel.isLoggedIn.collectAsStateWithLifecycle()
46+
val isStaff by viewModel.isStaff.collectAsStateWithLifecycle()
3547

36-
CodeScaffold(
37-
bottomBar = {
38-
Column(
39-
modifier = Modifier.fillMaxWidth().navigationBarsPadding(),
40-
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x2)
48+
val state = rememberLazyListState()
49+
LazyColumn(
50+
modifier = Modifier
51+
.fillMaxSize()
52+
.verticalScrollStateGradient(
53+
scrollState = state,
54+
isLongGradient = true,
55+
).sheetResignmentBehavior(state),
56+
contentPadding = PaddingValues(bottom = CodeTheme.dimens.grid.x3),
57+
) {
58+
item {
59+
SectionHeader(stringResource(R.string.title_settingsSectionFeatures))
60+
}
61+
items(betaFlags, key = { it.flag.key }) { feature ->
62+
SettingsSwitchRow(
63+
title = feature.flag.title,
64+
subtitle = feature.flag.message,
65+
checked = feature.enabled
4166
) {
42-
if (isLoggedIn) {
43-
CodeButton(
44-
modifier = Modifier
45-
.fillMaxWidth()
46-
.padding(horizontal = CodeTheme.dimens.inset),
47-
text = stringResource(R.string.action_unlinkPhone),
48-
buttonState = ButtonState.Subtle,
49-
onClick = viewModel::unlinkPhone
50-
)
67+
betaFlagsController.set(feature.flag, !feature.enabled)
68+
}
69+
70+
HorizontalDivider(
71+
modifier = Modifier.padding(horizontal = CodeTheme.dimens.inset),
72+
color = CodeTheme.colors.divider,
73+
thickness = 0.5.dp
74+
)
75+
}
5176

52-
CodeButton(
53-
modifier = Modifier
54-
.fillMaxWidth()
55-
.padding(horizontal = CodeTheme.dimens.inset),
56-
text = stringResource(R.string.action_unlinkEmail),
57-
buttonState = ButtonState.Subtle,
58-
onClick = viewModel::unlinkEmail
59-
)
77+
if (betaFlags.isEmpty()) {
78+
item {
79+
Box {
80+
Column(
81+
modifier = Modifier.align(Alignment.Center),
82+
horizontalAlignment = Alignment.CenterHorizontally
83+
) {
84+
Text(
85+
text = "\uD83D\uDE2D",
86+
style = CodeTheme.typography.displayMedium
87+
)
88+
Text(
89+
text = stringResource(R.string.title_labsAreEmpty),
90+
style = CodeTheme.typography.textLarge,
91+
color = CodeTheme.colors.textMain
92+
)
93+
94+
Text(
95+
text = stringResource(R.string.subtitle_labsAreEmpty),
96+
style = CodeTheme.typography.textSmall,
97+
color = CodeTheme.colors.textSecondary,
98+
)
99+
}
60100
}
61101
}
62-
},
63-
) { innerPadding ->
64-
LazyColumn(
65-
modifier = Modifier.fillMaxSize().padding(innerPadding),
66-
) {
67-
items(betaFlags, key = { it.flag.key }) { feature ->
68-
SettingsSwitchRow(
69-
title = feature.flag.title,
70-
subtitle = feature.flag.message,
71-
checked = feature.enabled
102+
}
103+
104+
if (isStaff) {
105+
item { SectionHeader(stringResource(R.string.title_settingsSectionDeveloper)) }
106+
item {
107+
ListItem(
108+
headline = stringResource(R.string.subtitle_settingsUserFlags),
109+
icon = rememberVectorPainter(Icons.Default.Token),
72110
) {
73-
betaFlagsController.set(feature.flag, !feature.enabled)
111+
navigator.navigateTo(AppRoute.UserFlags)
74112
}
75113
}
76-
if (betaFlags.isEmpty()) {
77-
item {
78-
Box(
79-
modifier = Modifier.fillParentMaxSize()
80-
) {
81-
Column(
82-
modifier = Modifier.align(Alignment.Center),
83-
horizontalAlignment = Alignment.CenterHorizontally
84-
) {
85-
Text(
86-
text = "\uD83D\uDE2D",
87-
style = CodeTheme.typography.displayMedium
88-
)
89-
Text(
90-
text = stringResource(R.string.title_labsAreEmpty),
91-
style = CodeTheme.typography.textLarge,
92-
color = CodeTheme.colors.textMain
93-
)
114+
}
94115

95-
Text(
96-
text = stringResource(R.string.subtitle_labsAreEmpty),
97-
style = CodeTheme.typography.textSmall,
98-
color = CodeTheme.colors.textSecondary
99-
)
100-
}
101-
}
116+
if (isLoggedIn) {
117+
item { SectionHeader(stringResource(R.string.title_settingsSectionAccount)) }
118+
item {
119+
ListItem(
120+
headline = stringResource(R.string.action_unlinkPhone),
121+
icon = rememberVectorPainter(Icons.Default.PhonelinkErase),
122+
) {
123+
viewModel.unlinkPhone()
124+
}
125+
}
126+
item {
127+
ListItem(
128+
headline = stringResource(R.string.action_unlinkEmail),
129+
icon = rememberVectorPainter(Icons.Default.MarkEmailUnread),
130+
) {
131+
viewModel.unlinkEmail()
102132
}
103133
}
104134
}

apps/flipcash/features/lab/src/main/kotlin/com/flipcash/app/lab/internal/LabsScreenViewModel.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
55
import com.flipcash.features.lab.R
66
import com.flipcash.services.controllers.ContactVerificationController
7+
import com.flipcash.services.models.UserFlags
78
import com.flipcash.services.models.ContactMethod
89
import com.flipcash.services.models.EmailVerificationError
910
import com.flipcash.services.models.PhoneVerificationError
@@ -33,6 +34,11 @@ class LabsScreenViewModel @Inject constructor(
3334
.map { true }
3435
.stateIn(viewModelScope, started = SharingStarted.Eagerly , initialValue = false)
3536

37+
val isStaff = userManager
38+
.state.map { it.flags }
39+
.map { it?.isStaff == true }
40+
.stateIn(viewModelScope, started = SharingStarted.Eagerly , initialValue = false)
41+
3642
fun unlinkEmail() = viewModelScope.launch {
3743
val email = userManager.profile?.verifiedEmailAddress
3844
if (email == null) {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build/
2+
.gradle/
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
plugins {
2+
alias(libs.plugins.flipcash.android.feature)
3+
}
4+
5+
android {
6+
namespace = "${Gradle.flipcashNamespace}.features.userflags"
7+
}
8+
9+
dependencies {
10+
implementation(project(":apps:flipcash:shared:userflags"))
11+
implementation(project(":libs:coroutines"))
12+
implementation(project(":libs:messaging"))
13+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.flipcash.app.userflags
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.fillMaxSize
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.Alignment
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.res.stringResource
9+
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
10+
import com.flipcash.app.userflags.internal.UserFlagsEditor
11+
import com.flipcash.app.userflags.internal.UserFlagsViewModel
12+
import com.flipcash.core.R
13+
import com.getcode.navigation.core.LocalCodeNavigator
14+
import com.getcode.ui.components.AppBarDefaults
15+
import com.getcode.ui.components.AppBarWithTitle
16+
17+
@Composable
18+
fun UserFlagsScreen() {
19+
val navigator = LocalCodeNavigator.current
20+
val viewModel = hiltViewModel<UserFlagsViewModel>()
21+
22+
Column(
23+
modifier = Modifier.fillMaxSize(),
24+
horizontalAlignment = Alignment.CenterHorizontally,
25+
) {
26+
AppBarWithTitle(
27+
title = stringResource(R.string.title_userFlags),
28+
titleAlignment = Alignment.CenterHorizontally,
29+
backButton = true,
30+
isInModal = true,
31+
onBackIconClicked = navigator::pop,
32+
endContent = {
33+
AppBarDefaults.Reset { viewModel.dispatchEvent(UserFlagsViewModel.Event.Reset) }
34+
}
35+
)
36+
37+
UserFlagsEditor(viewModel)
38+
}
39+
}

0 commit comments

Comments
 (0)