Skip to content

Commit e2a9983

Browse files
authored
Merge pull request #111 from code-payments/fix/order-of-events-deeplinks
fix: ensure consistent app setup when launching from deeplinks cold
2 parents a68ba83 + 95d80e3 commit e2a9983

9 files changed

Lines changed: 97 additions & 99 deletions

File tree

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,14 @@ class PaymentRepository @Inject constructor(
5959
messagingRepository.rejectLogin(rendezvousKey)
6060
}
6161

62-
fun attemptRequest(payload: CodePayload): Pair<KinAmount, CodePayload>? {
62+
suspend fun attemptRequest(payload: CodePayload): Pair<KinAmount, CodePayload>? {
6363
val fiat = payload.fiat
6464
if (fiat == null) {
6565
Timber.d("payload does not contain Fiat value")
6666
return null
6767
}
6868

69+
exchange.fetchRatesIfNeeded()
6970
val rate = exchange.rateFor(fiat.currency)
7071
if (rate == null) {
7172
Timber.d("Unable to determine rate")

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,13 @@ class DeeplinkHandler @Inject constructor() {
3636

3737
fun checkIntent(intent: Intent): Intent? {
3838
Timber.d("checking intent=${intent.data}")
39-
handle(intent) ?: return null
40-
return intent
39+
val uri = intent.data ?: return null
40+
return when (uri.deeplinkType) {
41+
is Type.Cash,
42+
is Type.Login,
43+
is Type.Sdk -> intent
44+
is Type.Unknown -> null
45+
}
4146
}
4247

4348
fun handle(intent: Intent? = debounceIntent): Pair<Type, List<Screen>>? {
@@ -48,6 +53,7 @@ class DeeplinkHandler @Inject constructor() {
4853
}
4954

5055
is Type.Cash -> {
56+
Timber.d("cash=${type.link}")
5157
type to listOf(HomeScreen(cashLink = type.link))
5258
}
5359

app/src/main/java/com/getcode/view/MainActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ class MainActivity : FragmentActivity() {
8484
handleUncaughtException()
8585
authManager.init(this)
8686
setFullscreen()
87-
deeplinkHandler.debounceIntent = deeplinkHandler.checkIntent(intent)
8887

8988
setContent {
9089
CompositionLocalProvider(
@@ -98,6 +97,8 @@ class MainActivity : FragmentActivity() {
9897
CodeApp()
9998
}
10099
}
100+
101+
deeplinkHandler.debounceIntent = intent
101102
}
102103

103104
override fun onResume() {

app/src/main/java/com/getcode/view/camera/KikCodeScannerView.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import io.reactivex.rxjava3.core.Flowable
88
import io.reactivex.rxjava3.disposables.Disposable
99
import io.reactivex.rxjava3.functions.BiFunction
1010
import io.reactivex.rxjava3.processors.BehaviorProcessor
11+
import kotlinx.coroutines.flow.Flow
1112
import org.kin.sdk.base.tools.Optional
1213

1314
class KikCodeScannerView @JvmOverloads constructor(
@@ -16,7 +17,7 @@ class KikCodeScannerView @JvmOverloads constructor(
1617
defStyleAttr: Int = 0
1718
) : FrameLayout(context, attrs, defStyleAttr) {
1819

19-
private var previewing = false
20+
var previewing = false
2021

2122
private val cameraController: CameraController = LegacyCameraController(context)
2223

@@ -44,9 +45,10 @@ class KikCodeScannerView @JvmOverloads constructor(
4445
cameraController.startPreview()
4546

4647
previewSizeSubscription = Flowable
47-
.combineLatest(cameraController.previewSize(), onLayoutChangeSubject.distinctUntilChanged(), BiFunction { previewSize: Optional<CameraController.PreviewSize>, _: Pair<Int, Int> ->
48-
previewSize
49-
})
48+
.combineLatest(
49+
cameraController.previewSize(),
50+
onLayoutChangeSubject.distinctUntilChanged()
51+
) { previewSize: Optional<CameraController.PreviewSize>, _: Pair<Int, Int> -> previewSize }
5052
.filter { it.isPresent }
5153
.observeOn(UiThreadScheduler.uiThread())
5254
.subscribe {

app/src/main/java/com/getcode/view/camera/LegacyCameraController.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import android.graphics.Rect
66
import android.hardware.Camera
77
import android.media.CamcorderProfile
88
import android.media.MediaRecorder
9-
import android.util.Log
109
import android.view.Surface
1110
import android.view.SurfaceHolder
1211
import android.view.SurfaceView
@@ -21,7 +20,6 @@ import kotlinx.coroutines.Dispatchers
2120
import kotlinx.coroutines.launch
2221
import org.kin.sdk.base.tools.Optional
2322
import timber.log.Timber
24-
import java.lang.IllegalStateException
2523
import java.net.URL
2624
import kotlin.math.abs
2725
import kotlin.math.max
@@ -562,7 +560,7 @@ class LegacyCameraController(
562560
return Single.fromCallable {
563561
val videoOutputUrl = videoOutputUrl
564562
val mediaRecorder = mediaRecorder
565-
val recordingStart = recordingStartSubject.value!!.get()!!
563+
val recordingStart = recordingStartSubject.value!!.get()
566564

567565
this@LegacyCameraController.videoOutputUrl = null
568566
this@LegacyCameraController.mediaRecorder = null

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

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@ import com.getcode.navigation.screens.LoginScreen
2323
import com.getcode.util.DeeplinkHandler
2424
import com.getcode.util.getActivity
2525
import kotlinx.coroutines.delay
26+
import kotlinx.coroutines.flow.combine
27+
import kotlinx.coroutines.flow.distinctUntilChanged
2628
import kotlinx.coroutines.flow.filter
2729
import kotlinx.coroutines.flow.filterNotNull
30+
import kotlinx.coroutines.flow.flatMapLatest
31+
import kotlinx.coroutines.flow.flowOf
2832
import kotlinx.coroutines.flow.launchIn
33+
import kotlinx.coroutines.flow.map
2934
import kotlinx.coroutines.flow.mapNotNull
3035
import kotlinx.coroutines.flow.onEach
3136
import kotlinx.coroutines.launch
@@ -50,23 +55,24 @@ fun AuthCheck(
5055
}
5156

5257
LaunchedEffect(isAuthenticated) {
53-
Timber.tag(AUTH_NAV).d("authenticated=$isAuthenticated")
5458
isAuthenticated?.let { authenticated ->
55-
//Allow the seed input screen to complete and avoid
56-
//premature navigation
57-
if (currentRoute is AccessKeyLoginScreen) {
58-
Timber.tag(AUTH_NAV).d("No navigation within seed input")
59-
return@LaunchedEffect
60-
}
61-
if (currentRoute is LoginGraph) {
62-
Timber.tag(AUTH_NAV).d("No navigation within account creation and onboarding")
63-
} else if (!deeplinkRouted) {
64-
if (authenticated) {
65-
Timber.tag(AUTH_NAV).d("Navigating to home")
66-
onNavigate(listOf(HomeScreen()), false)
67-
} else {
68-
Timber.tag(AUTH_NAV).d("Navigating to login")
69-
onNavigate(listOf(LoginScreen()), false)
59+
if (!deeplinkRouted) {
60+
// Allow the seed input screen to complete and avoid
61+
// premature navigation
62+
if (currentRoute is AccessKeyLoginScreen) {
63+
log("No navigation within seed input")
64+
return@LaunchedEffect
65+
}
66+
if (currentRoute is LoginGraph) {
67+
log("No navigation within account creation and onboarding")
68+
} else {
69+
if (authenticated) {
70+
log("Navigating to home")
71+
onNavigate(listOf(HomeScreen()), false)
72+
} else {
73+
log("Navigating to login")
74+
onNavigate(listOf(LoginScreen()), false)
75+
}
7076
}
7177
} else {
7278
deeplinkRouted = false
@@ -81,23 +87,21 @@ fun AuthCheck(
8187
val scope = this
8288
deeplinkHandler.intent
8389
.filterNotNull()
84-
.onEach {
85-
deeplinkRouted = false
86-
Timber.tag(AUTH_NAV).d("intent=${it.data}")
87-
}
90+
.distinctUntilChanged()
8891
.mapNotNull { deeplinkHandler.handle() }
89-
.onEach { Timber.d("${it.first}") }
90-
.filter {
91-
if (it.first is DeeplinkHandler.Type.Cash) {
92-
return@filter SessionManager.isAuthenticated() == true
92+
.flatMapLatest { combine(flowOf(it), SessionManager.authState) { a, b -> a to b } }
93+
.filter { (data, authState) ->
94+
if (data.first is DeeplinkHandler.Type.Cash || data.first is DeeplinkHandler.Type.Sdk) {
95+
return@filter authState.isAuthenticated == true
9396
}
9497
return@filter true
9598
}
96-
.mapNotNull { (type, screens) ->
99+
.mapNotNull { (data, auth) ->
100+
val (type, screens) = data
97101
if (type is DeeplinkHandler.Type.Login) {
98-
if (SessionManager.isAuthenticated() == true) {
102+
if (auth.isAuthenticated == true) {
99103
val entropy = (screens.first() as? LoginScreen)?.seed
100-
Timber.d("showing logout confirm")
104+
log("showing logout confirm")
101105
if (entropy != null) {
102106
deeplinkRouted = true
103107
context.getActivity()?.intent = null
@@ -107,7 +111,7 @@ fun AuthCheck(
107111
entropyB64 = entropy,
108112
onSwitchAccounts = {
109113
scope.launch {
110-
delay(300)
114+
delay(300) // wait for dismiss
111115
onSwitchAccounts(it)
112116
deeplinkRouted = false
113117
}
@@ -124,15 +128,17 @@ fun AuthCheck(
124128
}
125129
.onEach { screens ->
126130
deeplinkRouted = true
131+
log("navigated")
127132
onNavigate(screens, true)
128133
deeplinkHandler.debounceIntent = null
129134
context.getActivity()?.intent = null
130-
deeplinkRouted = false
131135
}
132136
.launchIn(this)
133137
}
134138
}
135139

140+
private fun log(message: String) = Timber.tag(AUTH_NAV).d(message)
141+
136142
private fun showLogoutMessage(
137143
context: Context,
138144
entropyB64: String,
@@ -142,7 +148,6 @@ private fun showLogoutMessage(
142148
BottomBarManager.showMessage(
143149
BottomBarManager.BottomBarMessage(
144150
title = context.getString(R.string.subtitle_logoutAndLoginConfirmation),
145-
subtitle = "",
146151
positiveText = context.getString(R.string.action_logIn),
147152
negativeText = context.getString(R.string.action_cancel),
148153
isDismissible = false,

app/src/main/java/com/getcode/view/main/account/AccountHome.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import com.getcode.theme.CodeTheme
4949
import com.getcode.theme.White10
5050
import com.getcode.util.getActivity
5151
import com.getcode.util.rememberedClickable
52+
import kotlinx.coroutines.delay
5253
import kotlinx.coroutines.launch
5354

5455
@Composable
@@ -80,8 +81,11 @@ fun AccountHome(
8081
positiveText = context.getString(R.string.action_logout),
8182
negativeText = context.getString(R.string.action_cancel),
8283
onPositive = {
83-
context.getActivity()?.let {
84-
viewModel.logout(it)
84+
composeScope.launch {
85+
delay(150) // wait for dismiss
86+
context.getActivity()?.let {
87+
viewModel.logout(it)
88+
}
8589
}
8690
}
8791
)

app/src/main/java/com/getcode/view/main/home/HomeScan.kt

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -160,32 +160,38 @@ private fun HomeScan(
160160

161161
var kikCodeScannerView: KikCodeScannerView? by remember { mutableStateOf(null) }
162162

163-
164163
val focusManager = LocalFocusManager.current
165164
LaunchedEffect(dataState.isCameraScanEnabled) {
166165
if (dataState.isCameraScanEnabled) {
167166
focusManager.clearFocus()
168167
}
169168
}
170169

171-
LaunchedEffect(homeViewModel) {
172-
if (!deepLink.isNullOrBlank()) {
173-
homeViewModel.onCashLinkGrabStart()
174-
}
175-
if (!deepLink.isNullOrBlank() && !dataState.isDeepLinkHandled) {
176-
homeViewModel.openCashLink(deepLink)
170+
LaunchedEffect(kikCodeScannerView?.previewing, deepLink, requestPayload) {
171+
if (kikCodeScannerView?.previewing == true) {
172+
if (!deepLink.isNullOrBlank()) {
173+
delay(1_000)
174+
homeViewModel.openCashLink(deepLink)
175+
}
176+
177+
if (!requestPayload.isNullOrBlank()) {
178+
delay(1_000)
179+
homeViewModel.handlePaymentRequest(requestPayload)
180+
}
177181
}
182+
}
178183

179-
if (!requestPayload.isNullOrBlank()) {
180-
delay(500)
181-
homeViewModel.handlePaymentRequest(requestPayload)
184+
LaunchedEffect(kikCodeScannerView?.previewing) {
185+
val view = kikCodeScannerView ?: return@LaunchedEffect
186+
if (view.previewing) { // kick off preview scanning once preview established
187+
homeViewModel.startScan(view)
182188
}
183189
}
184190

185191
fun startScanPreview() {
186192
val view = kikCodeScannerView ?: return
193+
// establish preview
187194
view.startPreview()
188-
homeViewModel.startScan(view)
189195
}
190196

191197
fun stopScanPreview() {
@@ -225,7 +231,7 @@ private fun HomeScan(
225231
update = { }
226232
)
227233
},
228-
isCameraReady = dataState.isCameraReady,
234+
isCameraReady = kikCodeScannerView?.previewing == true,
229235
showBottomSheet = { showBottomSheet(it) }
230236
)
231237

@@ -270,11 +276,6 @@ private fun HomeScan(
270276
homeViewModel.stopScan()
271277
}
272278
}
273-
LaunchedEffect(dataState.isCameraScanEnabled) {
274-
if (dataState.isCameraScanEnabled) {
275-
startScanPreview()
276-
}
277-
}
278279

279280
val context = LocalContext.current as Activity
280281
LaunchedEffect(dataState.billState.bill) {
@@ -301,7 +302,6 @@ private fun BillContainer(
301302

302303
val launcher = getPermissionLauncher(onPermissionResult)
303304
val context = LocalContext.current as Activity
304-
val composeScope = rememberCoroutineScope()
305305

306306
SideEffect {
307307
PermissionCheck.requestPermission(
@@ -322,13 +322,9 @@ private fun BillContainer(
322322
if (dataState.isCameraPermissionGranted == true || dataState.isCameraPermissionGranted == null) {
323323
scannerView()
324324

325-
var show by rememberSaveable {
326-
mutableStateOf(true)
327-
}
328-
329325
AnimatedVisibility(
330326
modifier = Modifier.fillMaxSize(),
331-
visible = show,
327+
visible = !isCameraReady,
332328
enter = fadeIn(
333329
animationSpec = tween(AnimationUtils.animationTime)
334330
),
@@ -339,11 +335,6 @@ private fun BillContainer(
339335
.fillMaxSize()
340336
.background(CodeTheme.colors.background)
341337
)
342-
LaunchedEffect(isCameraReady) {
343-
if (isCameraReady) {
344-
show = false
345-
}
346-
}
347338
}
348339
} else {
349340
PermissionsBlockingView(

0 commit comments

Comments
 (0)