Skip to content

Commit 1c546ab

Browse files
committed
Merge branch 'develop'
2 parents 0ab81cc + 5141d5c commit 1c546ab

File tree

21 files changed

+538
-144
lines changed

21 files changed

+538
-144
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ class AnalyticsManager @Inject constructor(
256256
override fun appSettingToggled(setting: AppSetting, value: Boolean) {
257257
val name = when (setting) {
258258
PrefsBool.CAMERA_START_BY_DEFAULT -> Name.AutoStartCamera
259+
PrefsBool.REQUIRE_BIOMETRICS -> Name.RequireBiometrics
259260
}
260261

261262
track(
@@ -311,7 +312,8 @@ class AnalyticsManager @Inject constructor(
311312
Recompute("Recompute"),
312313

313314
// App Settings
314-
AutoStartCamera("Auto Start Camera")
315+
AutoStartCamera("Auto Start Camera"),
316+
RequireBiometrics("Require Biometrics")
315317
}
316318

317319
enum class Property(val value: String) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ sealed class PrefsBool(val value: String) {
2929

3030
// app settings
3131
data object CAMERA_START_BY_DEFAULT: PrefsBool("camera_start_default"), AppSetting
32+
data object REQUIRE_BIOMETRICS: PrefsBool("require_biometrics"), AppSetting
3233

3334
// beta flags
3435
data object BUCKET_DEBUGGER_ENABLED: PrefsBool("debug_buckets"), BetaFlag
@@ -47,4 +48,4 @@ sealed class PrefsBool(val value: String) {
4748
data object BALANCE_CURRENCY_SELECTION_ENABLED: PrefsBool("balance_currency_enabled"), BetaFlag
4849
}
4950

50-
val APP_SETTINGS: List<AppSetting> = listOf(PrefsBool.CAMERA_START_BY_DEFAULT)
51+
val APP_SETTINGS: List<AppSetting> = listOf(PrefsBool.CAMERA_START_BY_DEFAULT, PrefsBool.REQUIRE_BIOMETRICS)

api/src/main/java/com/getcode/network/repository/AppSettingsRepository.kt

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ import com.getcode.analytics.AnalyticsService
44
import com.getcode.model.AppSetting
55
import com.getcode.model.PrefsBool
66
import kotlinx.coroutines.flow.Flow
7-
import kotlinx.coroutines.flow.map
7+
import kotlinx.coroutines.flow.combine
88
import javax.inject.Inject
99

1010
data class AppSettings(
11-
val cameraStartByDefault: Boolean
11+
val cameraStartByDefault: Boolean,
12+
val requireBiometrics: Boolean,
1213
) {
1314
companion object {
1415
val Defaults = AppSettings(
15-
cameraStartByDefault = true
16+
cameraStartByDefault = true,
17+
requireBiometrics = false,
1618
)
1719
}
1820
}
@@ -22,8 +24,15 @@ class AppSettingsRepository @Inject constructor(
2224
) {
2325

2426
fun observe(): Flow<AppSettings> = AppSettings.Defaults.let { defaults ->
25-
prefRepository.observeOrDefault(PrefsBool.CAMERA_START_BY_DEFAULT, defaults.cameraStartByDefault)
26-
.map { AppSettings(it) }
27+
combine(
28+
prefRepository.observeOrDefault(PrefsBool.CAMERA_START_BY_DEFAULT, defaults.cameraStartByDefault),
29+
prefRepository.observeOrDefault(PrefsBool.REQUIRE_BIOMETRICS, defaults.requireBiometrics)
30+
) { camera, biometrics ->
31+
AppSettings(
32+
cameraStartByDefault = camera,
33+
requireBiometrics = biometrics
34+
)
35+
}
2736
}
2837

2938
suspend fun get(setting: AppSetting): Boolean {
@@ -32,15 +41,26 @@ class AppSettingsRepository @Inject constructor(
3241
PrefsBool.CAMERA_START_BY_DEFAULT,
3342
AppSettings.Defaults.cameraStartByDefault
3443
)
44+
45+
PrefsBool.REQUIRE_BIOMETRICS -> prefRepository.get(
46+
PrefsBool.REQUIRE_BIOMETRICS,
47+
AppSettings.Defaults.requireBiometrics
48+
)
3549
}
3650
}
3751

38-
fun update(setting: AppSetting, value: Boolean) {
39-
analytics.appSettingToggled(setting, value)
52+
fun update(setting: AppSetting, value: Boolean, fromUser: Boolean = true) {
53+
if (fromUser) {
54+
analytics.appSettingToggled(setting, value)
55+
}
4056
when (setting) {
4157
PrefsBool.CAMERA_START_BY_DEFAULT -> {
4258
prefRepository.set(PrefsBool.CAMERA_START_BY_DEFAULT, value)
4359
}
60+
61+
PrefsBool.REQUIRE_BIOMETRICS -> {
62+
prefRepository.set(PrefsBool.REQUIRE_BIOMETRICS, value)
63+
}
4464
}
4565
}
4666
}

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ dependencies {
160160
implementation(Libs.compose_voyager_navigation_bottomsheet)
161161
implementation(Libs.compose_voyager_navigation_hilt)
162162

163+
implementation(Libs.androidx_biometrics)
164+
163165
// cameraX
164166
implementation(Libs.androidx_camerax_core)
165167
implementation(Libs.androidx_camerax_camera2)

app/src/main/java/com/getcode/CodeApp.kt

Lines changed: 80 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,26 @@
11
package com.getcode
22

3-
import androidx.compose.animation.AnimatedContent
4-
import androidx.compose.animation.AnimatedVisibility
53
import androidx.compose.animation.fadeIn
64
import androidx.compose.animation.fadeOut
75
import androidx.compose.animation.togetherWith
8-
import androidx.compose.foundation.Image
96
import androidx.compose.foundation.background
10-
import androidx.compose.foundation.layout.Arrangement
117
import androidx.compose.foundation.layout.Box
12-
import androidx.compose.foundation.layout.Column
138
import androidx.compose.foundation.layout.PaddingValues
14-
import androidx.compose.foundation.layout.fillMaxHeight
159
import androidx.compose.foundation.layout.fillMaxSize
16-
import androidx.compose.foundation.layout.fillMaxWidth
1710
import androidx.compose.foundation.layout.padding
1811
import androidx.compose.material.ExperimentalMaterialApi
1912
import androidx.compose.runtime.Composable
2013
import androidx.compose.runtime.CompositionLocalProvider
2114
import androidx.compose.runtime.LaunchedEffect
15+
import androidx.compose.runtime.collectAsState
2216
import androidx.compose.runtime.getValue
2317
import androidx.compose.runtime.mutableStateOf
2418
import androidx.compose.runtime.remember
2519
import androidx.compose.runtime.setValue
26-
import androidx.compose.ui.Alignment
2720
import androidx.compose.ui.Modifier
2821
import androidx.compose.ui.platform.LocalContext
29-
import androidx.compose.ui.res.painterResource
3022
import androidx.compose.ui.unit.dp
23+
import androidx.lifecycle.Lifecycle
3124
import cafe.adriel.voyager.core.screen.Screen
3225
import cafe.adriel.voyager.core.screen.ScreenKey
3326
import cafe.adriel.voyager.core.screen.uniqueScreenKey
@@ -46,102 +39,115 @@ import com.getcode.theme.CodeTheme
4639
import com.getcode.theme.LocalCodeColors
4740
import com.getcode.ui.components.AuthCheck
4841
import com.getcode.ui.components.BottomBarContainer
49-
import com.getcode.ui.components.CodeCircularProgressIndicator
5042
import com.getcode.ui.components.CodeScaffold
43+
import com.getcode.ui.components.OnLifecycleEvent
5144
import com.getcode.ui.components.TitleBar
5245
import com.getcode.ui.components.TopBarContainer
5346
import com.getcode.ui.utils.getActivity
5447
import com.getcode.ui.utils.getActivityScopedViewModel
5548
import com.getcode.ui.utils.measured
49+
import com.getcode.view.main.home.components.BiometricsBlockingView
50+
import com.getcode.view.main.home.components.rememberBiometricsState
5651
import dev.bmcreations.tipkit.TipScaffold
5752
import dev.bmcreations.tipkit.engines.TipsEngine
58-
import kotlinx.coroutines.delay
5953

6054
@Composable
6155
fun CodeApp(tipsEngine: TipsEngine) {
6256
val tlvm = MainRoot.getActivityScopedViewModel<TopLevelViewModel>()
57+
val state by tlvm.state.collectAsState()
6358
val activity = LocalContext.current.getActivity()
59+
val biometricsState = rememberBiometricsState(
60+
requireBiometrics = state.requireBiometrics,
61+
onBiometricsNotEnrolled = { tlvm.onMissingBiometrics() }
62+
)
63+
64+
OnLifecycleEvent { _, event ->
65+
if (event == Lifecycle.Event.ON_RESUME) {
66+
tlvm.onResume()
67+
}
68+
}
6469

6570
CodeTheme {
6671
val appState = rememberCodeAppState()
67-
AppNavHost {
68-
val codeNavigator = LocalCodeNavigator.current
69-
70-
TipScaffold(tipsEngine = tipsEngine) {
71-
CodeScaffold(
72-
scaffoldState = appState.scaffoldState
73-
) { innerPaddingModifier ->
74-
Navigator(
75-
screen = MainRoot,
76-
) { navigator ->
77-
appState.navigator = codeNavigator
78-
79-
LaunchedEffect(navigator.lastItem) {
80-
// update global navigator for platform access to support push/pop from a single
81-
// navigator current
82-
codeNavigator.screensNavigator = navigator
83-
}
72+
CompositionLocalProvider(LocalBiometricsState provides biometricsState) {
73+
AppNavHost {
74+
val codeNavigator = LocalCodeNavigator.current
75+
TipScaffold(tipsEngine = tipsEngine) {
76+
CodeScaffold(
77+
scaffoldState = appState.scaffoldState
78+
) { innerPaddingModifier ->
79+
Navigator(
80+
screen = MainRoot,
81+
) { navigator ->
82+
appState.navigator = codeNavigator
83+
84+
LaunchedEffect(navigator.lastItem) {
85+
// update global navigator for platform access to support push/pop from a single
86+
// navigator current
87+
codeNavigator.screensNavigator = navigator
88+
}
8489

85-
var topBarHeight by remember {
86-
mutableStateOf(0.dp)
87-
}
90+
var topBarHeight by remember {
91+
mutableStateOf(0.dp)
92+
}
8893

89-
val (isVisibleTopBar, isVisibleBackButton) = appState.isVisibleTopBar
90-
if (isVisibleTopBar && appState.currentTitle.isNotBlank()) {
91-
TitleBar(
92-
modifier = Modifier.measured { topBarHeight = it.height },
93-
title = appState.currentTitle,
94-
backButton = isVisibleBackButton,
95-
onBackIconClicked = appState::upPress
96-
)
97-
} else {
98-
topBarHeight = 0.dp
99-
}
94+
val (isVisibleTopBar, isVisibleBackButton) = appState.isVisibleTopBar
95+
if (isVisibleTopBar && appState.currentTitle.isNotBlank()) {
96+
TitleBar(
97+
modifier = Modifier.measured { topBarHeight = it.height },
98+
title = appState.currentTitle,
99+
backButton = isVisibleBackButton,
100+
onBackIconClicked = appState::upPress
101+
)
102+
} else {
103+
topBarHeight = 0.dp
104+
}
100105

101-
CompositionLocalProvider(
102-
LocalTopBarPadding provides PaddingValues(top = topBarHeight),
103-
) {
104-
Box(
105-
modifier = Modifier
106-
.padding(innerPaddingModifier)
106+
CompositionLocalProvider(
107+
LocalTopBarPadding provides PaddingValues(top = topBarHeight),
107108
) {
108-
when (navigator.lastEvent) {
109-
StackEvent.Push,
110-
StackEvent.Pop -> {
111-
when (navigator.lastItem) {
112-
is LoginScreen, is MainRoot -> CrossfadeTransition(
113-
navigator = navigator
114-
)
115-
116-
else -> SlideTransition(navigator = navigator)
109+
Box(
110+
modifier = Modifier
111+
.padding(innerPaddingModifier)
112+
) {
113+
when (navigator.lastEvent) {
114+
StackEvent.Push,
115+
StackEvent.Pop -> {
116+
when (navigator.lastItem) {
117+
is LoginScreen, is MainRoot -> CrossfadeTransition(
118+
navigator = navigator
119+
)
120+
121+
else -> SlideTransition(navigator = navigator)
122+
}
117123
}
118-
}
119124

120-
StackEvent.Idle,
121-
StackEvent.Replace -> CurrentScreen()
125+
StackEvent.Idle,
126+
StackEvent.Replace -> CurrentScreen()
127+
}
122128
}
123129
}
124-
}
125130

126-
//Listen for authentication changes here
127-
AuthCheck(
128-
navigator = codeNavigator,
129-
onNavigate = { screens ->
130-
codeNavigator.replaceAll(screens, inSheet = false)
131-
},
132-
onSwitchAccounts = { seed ->
133-
activity?.let {
134-
tlvm.logout(it) {
135-
appState.navigator.replaceAll(LoginScreen(seed))
131+
//Listen for authentication changes here
132+
AuthCheck(
133+
navigator = codeNavigator,
134+
onNavigate = { screens ->
135+
codeNavigator.replaceAll(screens, inSheet = false)
136+
},
137+
onSwitchAccounts = { seed ->
138+
activity?.let {
139+
tlvm.logout(it) {
140+
appState.navigator.replaceAll(LoginScreen(seed))
141+
}
136142
}
137143
}
138-
}
139-
)
144+
)
145+
}
140146
}
141147
}
142148
}
143149
}
144-
150+
BiometricsBlockingView(modifier = Modifier.fillMaxSize(), biometricsState)
145151
TopBarContainer(appState)
146152
BottomBarContainer(appState)
147153
}

app/src/main/java/com/getcode/Locals.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.getcode.util.DeeplinkHandler
1414
import com.getcode.util.PhoneUtils
1515
import com.getcode.utils.network.NetworkConnectivityListener
1616
import com.getcode.utils.network.NetworkObserverStub
17+
import com.getcode.view.main.home.components.BiometricsState
1718

1819
val LocalAnalytics: ProvidableCompositionLocal<AnalyticsService> = staticCompositionLocalOf { AnalyticsServiceNull() }
1920
val LocalNetworkObserver: ProvidableCompositionLocal<NetworkConnectivityListener> = staticCompositionLocalOf { NetworkObserverStub() }
@@ -24,4 +25,4 @@ val LocalDeeplinks: ProvidableCompositionLocal<DeeplinkHandler?> = staticComposi
2425
val LocalTopBarPadding: ProvidableCompositionLocal<PaddingValues> = staticCompositionLocalOf { PaddingValues() }
2526
val LocalBetaFlags: ProvidableCompositionLocal<BetaOptions> = staticCompositionLocalOf { BetaOptions.Defaults }
2627
val LocalDownloadQrCode: ProvidableCompositionLocal<BitmapPainter?> = staticCompositionLocalOf { null }
27-
28+
val LocalBiometricsState: ProvidableCompositionLocal<BiometricsState> = staticCompositionLocalOf { BiometricsState() }

0 commit comments

Comments
 (0)