Skip to content

Commit bc1cbf0

Browse files
authored
Merge pull request #449 from code-payments/feat/app-setting-camera-auto-start
feat: create AppSettings; allow disabling camera auto start on launch
2 parents b6030ad + 003aee5 commit bc1cbf0

24 files changed

Lines changed: 542 additions & 123 deletions

File tree

api/src/main/java/com/getcode/analytics/AnalyticsManager.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ class AnalyticsManager @Inject constructor(
343343
Backup("Backup Screen"),
344344
Withdraw("Withdraw Screen"),
345345
Debug("Debug Screen"),
346+
Share("Share Screen"),
347+
AppSettings("App Settings"),
346348
ForceUpgrade("Force Upgrade"),
347349
BuyMoreKin("Buy More Kin Screen"),
348350
SendKin("Send Kin Screen"),

api/src/main/java/com/getcode/model/PrefBool.kt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,24 @@ data class PrefBool(
1111
val value: Boolean
1212
)
1313

14-
14+
sealed interface InternalRouting
15+
sealed interface AppSetting
1516
sealed interface BetaFlag
17+
18+
1619
sealed class PrefsBool(val value: String) {
1720
// internal routing
18-
data object IS_DEBUG_ACTIVE: PrefsBool("debug_menu_active")
19-
data object IS_DEBUG_ALLOWED: PrefsBool("debug_menu_allowed")
20-
data object IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP: PrefsBool("is_eligible_get_first_kin_airdrop")
21-
data object IS_ELIGIBLE_GIVE_FIRST_KIN_AIRDROP: PrefsBool("is_eligible_give_first_kin_airdrop")
22-
data object HAS_REMOVED_LOCAL_CURRENCY: PrefsBool("removed_local_currency")
23-
data object SEEN_TIP_CARD : PrefsBool("seen_tip_card")
21+
data object IS_DEBUG_ACTIVE: PrefsBool("debug_menu_active"), InternalRouting
22+
data object IS_DEBUG_ALLOWED: PrefsBool("debug_menu_allowed"), InternalRouting
23+
data object IS_ELIGIBLE_GET_FIRST_KIN_AIRDROP: PrefsBool("is_eligible_get_first_kin_airdrop"), InternalRouting
24+
data object IS_ELIGIBLE_GIVE_FIRST_KIN_AIRDROP: PrefsBool("is_eligible_give_first_kin_airdrop"), InternalRouting
25+
data object HAS_REMOVED_LOCAL_CURRENCY: PrefsBool("removed_local_currency"), InternalRouting
26+
data object SEEN_TIP_CARD : PrefsBool("seen_tip_card"), InternalRouting
2427

25-
data object BUY_MODULE_AVAILABLE : PrefsBool("buy_module_available")
28+
data object BUY_MODULE_AVAILABLE : PrefsBool("buy_module_available"), InternalRouting
29+
30+
// app settings
31+
data object CAMERA_START_BY_DEFAULT: PrefsBool("camera_start_default"), AppSetting
2632

2733
// beta flags
2834
data object BUCKET_DEBUGGER_ENABLED: PrefsBool("debug_buckets"), BetaFlag
@@ -39,4 +45,6 @@ sealed class PrefsBool(val value: String) {
3945
data object TIPS_CHAT_ENABLED: PrefsBool("tips_chat_enabled"), BetaFlag
4046
data object TIPS_CHAT_CASH_ENABLED: PrefsBool("tips_chat_cash_enabled"), BetaFlag
4147
data object BALANCE_CURRENCY_SELECTION_ENABLED: PrefsBool("balance_currency_enabled"), BetaFlag
42-
}
48+
}
49+
50+
val APP_SETTINGS: List<AppSetting> = listOf(PrefsBool.CAMERA_START_BY_DEFAULT)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.getcode.network.repository
2+
3+
import com.getcode.model.AppSetting
4+
import com.getcode.model.PrefsBool
5+
import kotlinx.coroutines.flow.Flow
6+
import kotlinx.coroutines.flow.map
7+
import javax.inject.Inject
8+
9+
data class AppSettings(
10+
val cameraStartByDefault: Boolean
11+
) {
12+
companion object {
13+
val Defaults = AppSettings(
14+
cameraStartByDefault = true
15+
)
16+
}
17+
}
18+
class AppSettingsRepository @Inject constructor(
19+
private val prefRepository: PrefRepository,
20+
) {
21+
22+
fun observe(): Flow<AppSettings> = AppSettings.Defaults.let { defaults ->
23+
prefRepository.observeOrDefault(PrefsBool.CAMERA_START_BY_DEFAULT, defaults.cameraStartByDefault)
24+
.map { AppSettings(it) }
25+
}
26+
27+
suspend fun get(setting: AppSetting): Boolean {
28+
return when (setting) {
29+
PrefsBool.CAMERA_START_BY_DEFAULT -> prefRepository.get(
30+
PrefsBool.CAMERA_START_BY_DEFAULT,
31+
AppSettings.Defaults.cameraStartByDefault
32+
)
33+
}
34+
}
35+
36+
fun update(setting: AppSetting, value: Boolean) {
37+
when (setting) {
38+
PrefsBool.CAMERA_START_BY_DEFAULT -> {
39+
prefRepository.set(PrefsBool.CAMERA_START_BY_DEFAULT, value)
40+
}
41+
}
42+
}
43+
}

app/src/main/java/com/getcode/navigation/screens/MainScreens.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,11 @@ data object ShareDownloadLinkModal : MainGraph, ModalRoot {
240240
) {
241241
ShareDownloadScreen()
242242
}
243+
244+
AnalyticsScreenWatcher(
245+
lifecycleOwner = LocalLifecycleOwner.current,
246+
event = AnalyticsManager.Screen.Share
247+
)
243248
}
244249
}
245250

app/src/main/java/com/getcode/navigation/screens/ModalScreens.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.getcode.view.main.account.AccountDeposit
2020
import com.getcode.view.main.account.AccountDetails
2121
import com.getcode.view.main.account.AccountFaq
2222
import com.getcode.view.main.account.AccountPhone
23+
import com.getcode.view.main.account.AppSettingsScreen
2324
import com.getcode.view.main.account.BackupKey
2425
import com.getcode.view.main.account.BetaFlagsScreen
2526
import com.getcode.view.main.account.ConfirmDeleteAccount
@@ -101,6 +102,27 @@ data object AccountDebugOptionsScreen : MainGraph, ModalContent {
101102
}
102103
}
103104

105+
@Parcelize
106+
data object AppSettingsScreen : MainGraph, ModalContent {
107+
@IgnoredOnParcel
108+
override val key: ScreenKey = uniqueScreenKey
109+
110+
override val name: String
111+
@Composable get() = stringResource(id = R.string.title_appSettings)
112+
113+
@Composable
114+
override fun Content() {
115+
ModalContainer(backButtonEnabled = { it is AppSettingsScreen }) {
116+
AppSettingsScreen(getViewModel())
117+
}
118+
119+
AnalyticsScreenWatcher(
120+
lifecycleOwner = LocalLifecycleOwner.current,
121+
event = AnalyticsManager.Screen.AppSettings
122+
)
123+
}
124+
}
125+
104126
@Parcelize
105127
data object AccountDetailsScreen : MainGraph, ModalContent {
106128
@IgnoredOnParcel

app/src/main/java/com/getcode/ui/components/CodeButton.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ fun CodeButton(
4444
isLoading: Boolean = false,
4545
isSuccess: Boolean = false,
4646
enabled: Boolean = true,
47+
contentPadding: PaddingValues = PaddingValues(
48+
top = CodeTheme.dimens.grid.x3,
49+
bottom = CodeTheme.dimens.grid.x3,
50+
),
4751
buttonState: ButtonState = ButtonState.Bordered,
4852
textColor: Color = Color.Unspecified,
4953
shape: Shape = CodeTheme.shapes.small,
@@ -55,6 +59,7 @@ fun CodeButton(
5559
isSuccess = isSuccess,
5660
enabled = enabled,
5761
buttonState = buttonState,
62+
contentPadding = contentPadding,
5863
shape = shape,
5964
contentColor = textColor,
6065
) {
@@ -72,6 +77,10 @@ fun CodeButton(
7277
enabled: Boolean = true,
7378
buttonState: ButtonState = ButtonState.Bordered,
7479
shape: Shape = CodeTheme.shapes.small,
80+
contentPadding: PaddingValues = PaddingValues(
81+
top = CodeTheme.dimens.grid.x3,
82+
bottom = CodeTheme.dimens.grid.x3,
83+
),
7584
contentColor: Color = Color.Unspecified,
7685
content: @Composable RowScope.() -> Unit,
7786
) {
@@ -111,10 +120,7 @@ fun CodeButton(
111120
pressedElevation = 0.dp
112121
),
113122
shape = shape,
114-
contentPadding = ButtonDefaults.ContentPadding.plus(
115-
top = CodeTheme.dimens.grid.x3,
116-
bottom = CodeTheme.dimens.grid.x3,
117-
)
123+
contentPadding = ButtonDefaults.ContentPadding.plus(contentPadding)
118124
) {
119125
when {
120126
isLoading -> {
Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,85 @@
11
package com.getcode.ui.components
22

3-
import androidx.compose.foundation.interaction.MutableInteractionSource
4-
import androidx.compose.material.ExperimentalMaterialApi
5-
import androidx.compose.material.Switch
3+
import androidx.compose.animation.core.animateFloatAsState
4+
import androidx.compose.foundation.Canvas
5+
import androidx.compose.foundation.gestures.detectTapGestures
6+
import androidx.compose.foundation.layout.size
7+
import androidx.compose.material.SwitchColors
68
import androidx.compose.material.SwitchDefaults
79
import androidx.compose.runtime.Composable
8-
import androidx.compose.runtime.remember
10+
import androidx.compose.runtime.getValue
911
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.draw.scale
13+
import androidx.compose.ui.geometry.CornerRadius
14+
import androidx.compose.ui.geometry.Offset
1015
import androidx.compose.ui.graphics.Color
11-
import com.getcode.theme.CodeTheme
16+
import androidx.compose.ui.input.pointer.pointerInput
17+
import androidx.compose.ui.platform.LocalDensity
18+
import androidx.compose.ui.unit.dp
19+
import com.getcode.theme.SystemGreen
1220
import com.getcode.theme.White
13-
import com.getcode.theme.White50
21+
import com.getcode.ui.utils.addIf
22+
23+
object CodeToggleSwitchDefaults {
24+
25+
val colors: SwitchColors
26+
@Composable get() = SwitchDefaults.colors(
27+
checkedThumbColor = White,
28+
uncheckedThumbColor = White,
29+
checkedTrackColor = SystemGreen,
30+
checkedTrackAlpha = 1f,
31+
uncheckedTrackAlpha = 1f,
32+
uncheckedTrackColor = Color(0xFF201D2F)
33+
)
34+
}
1435

1536
@Composable
16-
fun CodeSwitch(
17-
checked: Boolean,
18-
onCheckedChange: ((Boolean) -> Unit)?,
37+
fun CodeToggleSwitch(
1938
modifier: Modifier = Modifier,
2039
enabled: Boolean = true,
21-
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
40+
checked: Boolean,
41+
onCheckedChange: ((Boolean) -> Unit)? = null,
2242
) {
23-
Switch(
24-
checked = checked,
25-
onCheckedChange = onCheckedChange,
26-
modifier = modifier,
27-
enabled = enabled,
28-
interactionSource = interactionSource,
29-
colors = SwitchDefaults.colors(
30-
checkedThumbColor = White,
31-
uncheckedThumbColor = White50,
32-
checkedTrackColor = CodeTheme.colors.brandLight,
33-
checkedTrackAlpha = 1f,
34-
uncheckedTrackColor = CodeTheme.colors.brandLight
35-
)
43+
val width = 51.dp
44+
val height = 31.dp
45+
val gapBetweenThumbAndTrackEdge = 2.dp
46+
47+
val thumbRadius = (height / 2) - gapBetweenThumbAndTrackEdge
48+
val animatePosition = animateFloatAsState(
49+
targetValue = if (checked) {
50+
with(LocalDensity.current) { (width - thumbRadius - gapBetweenThumbAndTrackEdge).toPx() }
51+
} else {
52+
with (LocalDensity.current) { (thumbRadius + gapBetweenThumbAndTrackEdge).toPx() }
53+
}, label = "thumb position"
3654
)
55+
56+
val colors = CodeToggleSwitchDefaults.colors
57+
val trackColor by colors.trackColor(enabled = enabled, checked = checked)
58+
val thumbColor by colors.thumbColor(enabled = enabled, checked = checked)
59+
Canvas(
60+
modifier = Modifier
61+
.size(width = width, height = height)
62+
.addIf(onCheckedChange != null) {
63+
Modifier.pointerInput(Unit) {
64+
detectTapGestures(
65+
onTap = {
66+
onCheckedChange?.invoke(!checked)
67+
}
68+
)
69+
}
70+
}.then(modifier)
71+
) {
72+
drawRoundRect(
73+
color = trackColor,
74+
cornerRadius = CornerRadius(x = 45.dp.toPx(), y = 45.dp.toPx())
75+
)
76+
drawCircle(
77+
color = thumbColor,
78+
radius = thumbRadius.toPx(),
79+
center = Offset(
80+
x = animatePosition.value,
81+
y = size.height / 2
82+
)
83+
)
84+
}
3785
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.getcode.ui.components
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.height
6+
import androidx.compose.foundation.layout.padding
7+
import androidx.compose.foundation.layout.width
8+
import androidx.compose.foundation.layout.wrapContentSize
9+
import androidx.compose.material.Text
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Alignment
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.res.painterResource
14+
import com.getcode.theme.CodeTheme
15+
import com.getcode.ui.utils.rememberedClickable
16+
17+
@Composable
18+
fun SettingsRow(
19+
modifier: Modifier = Modifier,
20+
title: String,
21+
icon: Int? = null,
22+
subtitle: String? = null,
23+
checked: Boolean,
24+
onClick: () -> Unit) {
25+
Row(
26+
modifier = modifier
27+
.rememberedClickable { onClick() }
28+
.padding(horizontal = CodeTheme.dimens.grid.x3)
29+
.padding(end = CodeTheme.dimens.grid.x3),
30+
verticalAlignment = Alignment.CenterVertically
31+
) {
32+
if (icon != null) {
33+
Image(
34+
modifier = Modifier
35+
.padding(end = CodeTheme.dimens.inset)
36+
.height(CodeTheme.dimens.staticGrid.x5)
37+
.width(CodeTheme.dimens.staticGrid.x5),
38+
painter = painterResource(id = icon),
39+
contentDescription = ""
40+
)
41+
}
42+
Column(
43+
modifier = Modifier
44+
.weight(1f)
45+
.padding(vertical = CodeTheme.dimens.grid.x3)
46+
) {
47+
Text(
48+
modifier = Modifier
49+
.padding(vertical = CodeTheme.dimens.grid.x2),
50+
text = title,
51+
)
52+
if (!subtitle.isNullOrEmpty()) {
53+
Text(
54+
modifier = Modifier
55+
.padding(vertical = CodeTheme.dimens.grid.x1),
56+
text = subtitle,
57+
style = CodeTheme.typography.caption,
58+
color = CodeTheme.colors.textSecondary
59+
)
60+
}
61+
}
62+
63+
CodeToggleSwitch(
64+
checked = checked,
65+
onCheckedChange = null,
66+
)
67+
}
68+
}

app/src/main/java/com/getcode/ui/components/TopBarContainer.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
1010
import androidx.compose.foundation.layout.*
1111
import androidx.compose.material.*
1212
import androidx.compose.runtime.*
13-
import androidx.compose.runtime.livedata.observeAsState
1413
import androidx.compose.ui.Alignment.Companion.CenterVertically
1514
import androidx.compose.ui.Modifier
1615
import androidx.compose.ui.draw.drawBehind
@@ -92,9 +91,9 @@ private fun TopBarView(
9291
when (topBarMessage.type) {
9392
ERROR_NETWORK, ERROR -> CodeTheme.colors.error
9493
WARNING -> Warning
95-
NOTIFICATION -> topNotification
96-
NEUTRAL -> topNeutral
97-
SUCCESS -> topSuccess
94+
NOTIFICATION -> TopNotification
95+
NEUTRAL -> TopNeutral
96+
SUCCESS -> TopSuccess
9897

9998
}
10099
)

0 commit comments

Comments
 (0)