Skip to content

Commit 27453dc

Browse files
committed
Merge remote-tracking branch 'origin/develop' into HEAD
* origin/develop: feat: render cash bills programmatically fix(nav): move AuthCheck inside Navigator root chore(modals): remove ModalContainer overloads outside of ModalContent interface build: bump build tools to 34.0.0 build: update AGP, Kotlin, Compose Compiler and Gradle chore(phone/confirm): tweak OTP box to better match iOS fix(modals): only dismiss IME (and wait) when IME is open
2 parents 65bdc60 + d20e93c commit 27453dc

23 files changed

Lines changed: 585 additions & 287 deletions

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ android {
5252
compose = true
5353
}
5454
composeOptions {
55-
kotlinCompilerExtensionVersion = "1.5.3"
55+
kotlinCompilerExtensionVersion = Versions.compose_compiler
5656
}
5757

5858
buildTypes {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.appcompat.app.AppCompatDelegate
55
import com.bugsnag.android.Bugsnag
66
import com.getcode.manager.AuthManager
77
import com.getcode.utils.ErrorUtils
8+
import com.getcode.view.main.bill.CashBillAssets
89
import dagger.hilt.android.HiltAndroidApp
910
import io.reactivex.rxjava3.plugins.RxJavaPlugins
1011
import timber.log.Timber
@@ -20,6 +21,8 @@ class App : Application() {
2021
super.onCreate()
2122
instance = this
2223

24+
CashBillAssets.load(this)
25+
2326
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
2427

2528
RxJavaPlugins.setErrorHandler {

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,23 @@ fun CodeApp() {
8888
StackEvent.Replace -> CurrentScreen()
8989
}
9090
}
91-
}
92-
}
9391

94-
//Listen for authentication changes here
95-
AuthCheck(
96-
navigator = codeNavigator,
97-
onNavigate = { screens ->
98-
codeNavigator.replaceAll(screens, inSheet = false)
99-
},
100-
onSwitchAccounts = { seed ->
101-
activity?.let {
102-
tlvm.logout(it) {
103-
appState.navigator.replaceAll(LoginScreen(seed))
92+
//Listen for authentication changes here
93+
AuthCheck(
94+
navigator = codeNavigator,
95+
onNavigate = { screens ->
96+
codeNavigator.replaceAll(screens, inSheet = false)
97+
},
98+
onSwitchAccounts = { seed ->
99+
activity?.let {
100+
tlvm.logout(it) {
101+
appState.navigator.replaceAll(LoginScreen(seed))
102+
}
103+
}
104104
}
105-
}
105+
)
106106
}
107-
)
107+
}
108108
}
109109

110110
TopBarContainer(appState)

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

Lines changed: 92 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -26,116 +26,114 @@ import com.getcode.navigation.core.CodeNavigator
2626
import com.getcode.navigation.core.LocalCodeNavigator
2727
import com.getcode.theme.CodeTheme
2828
import com.getcode.theme.sheetHeight
29-
import com.getcode.util.recomposeHighlighter
29+
import com.getcode.util.keyboardAsState
3030
import com.getcode.view.components.SheetTitle
3131
import kotlinx.coroutines.delay
3232
import kotlinx.coroutines.launch
33-
import timber.log.Timber
3433

35-
internal interface ModalContent {
3634

37-
@Composable
38-
fun Screen.ModalContainer(
39-
closeButton: (Screen?) -> Boolean = { false },
40-
screenContent: @Composable () -> Unit
41-
) {
42-
ModalContainer(
43-
navigator = LocalCodeNavigator.current,
44-
displayLogo = false,
45-
backButton = { false },
46-
onLogoClicked = {},
47-
closeButton = closeButton,
48-
screenContent = screenContent,
49-
)
50-
}
35+
@Composable
36+
internal fun Screen.ModalContainer(
37+
closeButton: (Screen?) -> Boolean = { false },
38+
screenContent: @Composable () -> Unit
39+
) {
40+
ModalContainer(
41+
navigator = LocalCodeNavigator.current,
42+
displayLogo = false,
43+
backButton = { false },
44+
onLogoClicked = {},
45+
closeButton = closeButton,
46+
screenContent = screenContent,
47+
)
48+
}
5149

52-
@Composable
53-
fun Screen.ModalContainer(
54-
displayLogo: Boolean = false,
55-
onLogoClicked: () -> Unit = { },
56-
closeButton: (Screen?) -> Boolean = { false },
57-
screenContent: @Composable () -> Unit
58-
) {
59-
ModalContainer(
60-
navigator = LocalCodeNavigator.current,
61-
displayLogo = displayLogo,
62-
backButton = { false },
63-
onLogoClicked = onLogoClicked,
64-
closeButton = closeButton,
65-
screenContent = screenContent,
66-
)
67-
}
50+
@Composable
51+
internal fun Screen.ModalContainer(
52+
displayLogo: Boolean = false,
53+
onLogoClicked: () -> Unit = { },
54+
closeButton: (Screen?) -> Boolean = { false },
55+
screenContent: @Composable () -> Unit
56+
) {
57+
ModalContainer(
58+
navigator = LocalCodeNavigator.current,
59+
displayLogo = displayLogo,
60+
backButton = { false },
61+
onLogoClicked = onLogoClicked,
62+
closeButton = closeButton,
63+
screenContent = screenContent,
64+
)
65+
}
6866

69-
@OptIn(ExperimentalFoundationApi::class)
70-
@Composable
71-
fun Screen.ModalContainer(
72-
navigator: CodeNavigator = LocalCodeNavigator.current,
73-
displayLogo: Boolean = false,
74-
backButton: (Screen?) -> Boolean = { false },
75-
onBackClicked: (() -> Unit)? = null,
76-
closeButton: (Screen?) -> Boolean = { false },
77-
onCloseClicked: (() -> Unit)? = null,
78-
onLogoClicked: () -> Unit = { },
79-
screenContent: @Composable () -> Unit
67+
@OptIn(ExperimentalFoundationApi::class)
68+
@Composable
69+
internal fun Screen.ModalContainer(
70+
navigator: CodeNavigator = LocalCodeNavigator.current,
71+
displayLogo: Boolean = false,
72+
backButton: (Screen?) -> Boolean = { false },
73+
onBackClicked: (() -> Unit)? = null,
74+
closeButton: (Screen?) -> Boolean = { false },
75+
onCloseClicked: (() -> Unit)? = null,
76+
onLogoClicked: () -> Unit = { },
77+
screenContent: @Composable () -> Unit
78+
) {
79+
Column(
80+
modifier = Modifier
81+
.fillMaxWidth()
82+
.fillMaxHeight(sheetHeight)
8083
) {
81-
Column(
82-
modifier = Modifier
83-
.fillMaxWidth()
84-
.fillMaxHeight(sheetHeight)
85-
) {
86-
val lastItem by remember(navigator.lastModalItem) {
87-
derivedStateOf { navigator.lastModalItem }
88-
}
84+
val lastItem by remember(navigator.lastModalItem) {
85+
derivedStateOf { navigator.lastModalItem }
86+
}
8987

90-
val isBackEnabled by remember(backButton, lastItem) {
91-
derivedStateOf { backButton(lastItem) }
92-
}
88+
val isBackEnabled by remember(backButton, lastItem) {
89+
derivedStateOf { backButton(lastItem) }
90+
}
9391

94-
val isCloseEnabled by remember(closeButton, lastItem) {
95-
derivedStateOf { closeButton(lastItem) }
96-
}
92+
val isCloseEnabled by remember(closeButton, lastItem) {
93+
derivedStateOf { closeButton(lastItem) }
94+
}
9795

98-
val keyboardController = LocalSoftwareKeyboardController.current
99-
val composeScope = rememberCoroutineScope()
96+
val keyboardController = LocalSoftwareKeyboardController.current
97+
val composeScope = rememberCoroutineScope()
10098

101-
val hideSheet = {
102-
composeScope.launch {
103-
keyboardController?.hide()
104-
delay(500)
105-
navigator.hide()
106-
}
99+
val hideSheet = {
100+
composeScope.launch {
101+
keyboardController?.hide()
102+
delay(500)
103+
navigator.hide()
107104
}
108-
SheetTitle(
109-
modifier = Modifier,
110-
title = {
111-
val name = (lastItem as? NamedScreen)?.name
112-
val sheetName by remember(lastItem) {
113-
derivedStateOf { name }
114-
}
115-
sheetName.takeIf { !displayLogo && lastItem == this@ModalContainer }
116-
},
117-
displayLogo = displayLogo,
118-
onLogoClicked = onLogoClicked,
119-
// hide while transitioning to/from other destinations
120-
backButton = isBackEnabled,
121-
closeButton = isCloseEnabled,
122-
onBackIconClicked = onBackClicked?.let { { it() } } ?: { navigator.pop() },
123-
onCloseIconClicked = onCloseClicked?.let { { it() } } ?: { hideSheet() }
124-
)
125-
Box(
126-
modifier = Modifier
127-
.windowInsetsPadding(WindowInsets.navigationBars)
128-
) {
129-
CompositionLocalProvider(
130-
LocalOverscrollConfiguration provides null
131-
) {
132-
screenContent()
105+
}
106+
SheetTitle(
107+
modifier = Modifier,
108+
title = {
109+
val name = (lastItem as? NamedScreen)?.name
110+
val sheetName by remember(lastItem) {
111+
derivedStateOf { name }
133112
}
113+
sheetName.takeIf { !displayLogo && lastItem == this@ModalContainer }
114+
},
115+
displayLogo = displayLogo,
116+
onLogoClicked = onLogoClicked,
117+
// hide while transitioning to/from other destinations
118+
backButton = isBackEnabled,
119+
closeButton = isCloseEnabled,
120+
onBackIconClicked = onBackClicked?.let { { it() } } ?: { navigator.pop() },
121+
onCloseIconClicked = onCloseClicked?.let { { it() } } ?: { hideSheet() }
122+
)
123+
Box(
124+
modifier = Modifier
125+
.windowInsetsPadding(WindowInsets.navigationBars)
126+
) {
127+
CompositionLocalProvider(
128+
LocalOverscrollConfiguration provides null
129+
) {
130+
screenContent()
134131
}
135132
}
136133
}
137134
}
138135

136+
internal interface ModalContent
139137
internal sealed interface ModalRoot : ModalContent
140138

141139
data object MainRoot : Screen {
@@ -147,6 +145,8 @@ data object MainRoot : Screen {
147145
// TODO: potentially add a loading state here
148146
// so app doesn't appear stuck in a dead state
149147
// while we wait for auth check to complete
150-
Box(modifier = Modifier.fillMaxSize().background(CodeTheme.colors.background))
148+
Box(modifier = Modifier
149+
.fillMaxSize()
150+
.background(CodeTheme.colors.background))
151151
}
152152
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.getcode.util
2+
3+
import android.view.ViewTreeObserver
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.DisposableEffect
6+
import androidx.compose.runtime.State
7+
import androidx.compose.runtime.mutableStateOf
8+
import androidx.compose.runtime.remember
9+
import androidx.compose.ui.platform.LocalView
10+
import androidx.core.view.ViewCompat
11+
import androidx.core.view.WindowInsetsCompat
12+
13+
@Composable
14+
fun keyboardAsState(): State<Boolean> {
15+
val keyboardState = remember { mutableStateOf(false) }
16+
val view = LocalView.current
17+
val viewTreeObserver = view.viewTreeObserver
18+
DisposableEffect(viewTreeObserver) {
19+
val listener = ViewTreeObserver.OnGlobalLayoutListener {
20+
keyboardState.value = ViewCompat.getRootWindowInsets(view)
21+
?.isVisible(WindowInsetsCompat.Type.ime()) ?: true
22+
}
23+
viewTreeObserver.addOnGlobalLayoutListener(listener)
24+
onDispose { viewTreeObserver.removeOnGlobalLayoutListener(listener) }
25+
}
26+
return keyboardState
27+
}

app/src/main/java/com/getcode/util/ModifierExt.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.compose.foundation.clickable
99
import androidx.compose.foundation.combinedClickable
1010
import androidx.compose.foundation.interaction.MutableInteractionSource
1111
import androidx.compose.material.ripple.rememberRipple
12+
import androidx.compose.runtime.Composable
1213
import androidx.compose.runtime.remember
1314
import androidx.compose.ui.ExperimentalComposeUiApi
1415
import androidx.compose.ui.Modifier
@@ -27,11 +28,13 @@ import androidx.compose.ui.unit.dp
2728

2829
inline fun Modifier.addIf(
2930
predicate: Boolean,
30-
crossinline whenTrue: () -> Modifier,
31-
): Modifier = if (predicate) {
32-
this.then(whenTrue())
33-
} else {
34-
this
31+
crossinline whenTrue: @Composable () -> Modifier,
32+
): Modifier = composed {
33+
if (predicate) {
34+
this.then(whenTrue())
35+
} else {
36+
this
37+
}
3538
}
3639

3740
fun Modifier.unboundedClickable(

app/src/main/java/com/getcode/view/components/AuthCheck.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ fun AuthCheck(
9191
deeplinkHandler.intent
9292
.flatMapLatest { combine(flowOf(deeplinkHandler.handle(it)), SessionManager.authState) { a, b -> a to b } }
9393
.filter { (result, authState) ->
94-
startupLog("checking auth state=${authState.isAuthenticated}")
95-
// wait for authentication
9694
if (result == null) return@filter false
95+
// wait for authentication
96+
startupLog("checking auth state=${authState.isAuthenticated}")
9797
if( authState.isAuthenticated == null) {
9898
startupLog("awaiting auth state confirmation")
9999
return@filter false

app/src/main/java/com/getcode/view/components/OtpBox.kt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,22 @@ fun OtpBox(
2626
) {
2727

2828
val height = when (CodeTheme.dimens.heightWindowSizeClass) {
29-
WindowSizeClass.COMPACT -> 45
30-
else -> 60
31-
}
32-
val width = when (CodeTheme.dimens.widthWindowSizeClass) {
33-
WindowSizeClass.COMPACT -> 30
34-
else -> 42
29+
WindowSizeClass.COMPACT -> CodeTheme.dimens.staticGrid.x9
30+
else -> CodeTheme.dimens.staticGrid.x11
3531
}
3632

3733
Box(
3834
modifier = modifier
3935
.padding(CodeTheme.dimens.grid.x1)
40-
.height(height.dp)
41-
.width(width.dp)
36+
.height(height)
37+
.width(CodeTheme.dimens.staticGrid.x7)
4238
.clip(CodeTheme.shapes.small)
4339
.rememberedClickable(onClick = onClick)
4440
.border(
4541
border = if (isHighlighted)
46-
BorderStroke(CodeTheme.dimens.thickBorder, color = BrandLight.copy(alpha = 0.8f))
42+
BorderStroke(CodeTheme.dimens.thickBorder, color = BrandLight.copy(alpha = 0.7f))
4743
else
48-
BorderStroke(CodeTheme.dimens.border, color = BrandLight.copy(alpha = 0.4f)),
44+
BorderStroke(CodeTheme.dimens.border, color = BrandLight.copy(alpha = 0.3f)),
4945
shape = CodeTheme.shapes.small
5046
)
5147
.background(Color.White.copy(alpha = 0.1f)),

app/src/main/java/com/getcode/view/login/PhoneConfirm.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ fun PhoneConfirm(
115115
.padding(top = CodeTheme.dimens.inset)
116116
.wrapContentHeight()
117117
.fillMaxWidth(),
118-
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1)
118+
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x2)
119119
) {
120120

121121
ProvideTextStyle(

0 commit comments

Comments
 (0)