Skip to content

Commit 4f5d8e6

Browse files
committed
fix: ensure consistent app setup when launching from deeplinks cold
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 9806405 commit 4f5d8e6

8 files changed

Lines changed: 90 additions & 97 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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class MainActivity : FragmentActivity() {
6767
*/
6868
override fun onNewIntent(intent: Intent?) {
6969
super.onNewIntent(intent)
70+
Timber.d("onnewintent")
7071
if (intent != null) {
7172
val cachedIntent = deeplinkHandler.debounceIntent
7273
if (cachedIntent != null && cachedIntent.data == intent.data) {
@@ -84,7 +85,7 @@ class MainActivity : FragmentActivity() {
8485
handleUncaughtException()
8586
authManager.init(this)
8687
setFullscreen()
87-
deeplinkHandler.debounceIntent = deeplinkHandler.checkIntent(intent)
88+
Timber.d("oncreate")
8889

8990
setContent {
9091
CompositionLocalProvider(
@@ -98,6 +99,8 @@ class MainActivity : FragmentActivity() {
9899
CodeApp()
99100
}
100101
}
102+
103+
deeplinkHandler.debounceIntent = intent
101104
}
102105

103106
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: 32 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,26 +55,25 @@ 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+
Timber.tag(AUTH_NAV).d("No navigation within seed input")
64+
return@LaunchedEffect
65+
}
66+
if (currentRoute is LoginGraph) {
67+
Timber.tag(AUTH_NAV).d("No navigation within account creation and onboarding")
68+
} else {
69+
if (authenticated) {
70+
Timber.tag(AUTH_NAV).d("Navigating to home")
71+
onNavigate(listOf(HomeScreen()), false)
72+
} else {
73+
Timber.tag(AUTH_NAV).d("Navigating to login")
74+
onNavigate(listOf(LoginScreen()), false)
75+
}
7076
}
71-
} else {
72-
deeplinkRouted = false
7377
}
7478
}
7579
}
@@ -81,23 +85,21 @@ fun AuthCheck(
8185
val scope = this
8286
deeplinkHandler.intent
8387
.filterNotNull()
84-
.onEach {
85-
deeplinkRouted = false
86-
Timber.tag(AUTH_NAV).d("intent=${it.data}")
87-
}
88+
.distinctUntilChanged()
8889
.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
90+
.flatMapLatest { combine(flowOf(it), SessionManager.authState) { a, b -> a to b } }
91+
.filter { (data, authState) ->
92+
if (data.first is DeeplinkHandler.Type.Cash || data.first is DeeplinkHandler.Type.Sdk) {
93+
return@filter authState.isAuthenticated == true
9394
}
9495
return@filter true
9596
}
96-
.mapNotNull { (type, screens) ->
97+
.mapNotNull { (data, auth) ->
98+
val (type, screens) = data
9799
if (type is DeeplinkHandler.Type.Login) {
98-
if (SessionManager.isAuthenticated() == true) {
100+
if (auth.isAuthenticated == true) {
99101
val entropy = (screens.first() as? LoginScreen)?.seed
100-
Timber.d("showing logout confirm")
102+
Timber.tag(AUTH_NAV).d("showing logout confirm")
101103
if (entropy != null) {
102104
deeplinkRouted = true
103105
context.getActivity()?.intent = null
@@ -124,10 +126,10 @@ fun AuthCheck(
124126
}
125127
.onEach { screens ->
126128
deeplinkRouted = true
129+
Timber.tag(AUTH_NAV).d("navigated")
127130
onNavigate(screens, true)
128131
deeplinkHandler.debounceIntent = null
129132
context.getActivity()?.intent = null
130-
deeplinkRouted = false
131133
}
132134
.launchIn(this)
133135
}

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)