Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 18 additions & 31 deletions app/src/main/java/com/getcode/CodeApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,11 @@ fun CodeApp() {
CodeTheme {
val appState = rememberCodeAppState()
AppNavHost {
val codeNavigator = LocalCodeNavigator.current

CodeScaffold(
scaffoldState = appState.scaffoldState
) { innerPaddingModifier ->
val codeNavigator = LocalCodeNavigator.current
var replacingStackFromDeepLink by remember {
mutableStateOf(false)
}

Navigator(
screen = MainRoot,
Expand All @@ -77,39 +75,28 @@ fun CodeApp() {
modifier = Modifier
.padding(innerPaddingModifier)
) {
if (replacingStackFromDeepLink) {
CurrentScreen()
replacingStackFromDeepLink = false
} else {
when (navigator.lastItem) {
is LoginScreen, is MainRoot -> {
CrossfadeTransition(navigator = navigator)
}

else -> {
SlideTransition(navigator = navigator)
}
}
when (navigator.lastItem) {
is LoginScreen, is MainRoot -> CrossfadeTransition(navigator = navigator)
else -> SlideTransition(navigator = navigator)
}
}
}
}

//Listen for authentication changes here
AuthCheck(
navigator = codeNavigator,
onNavigate = { screens, fromDeeplink ->
replacingStackFromDeepLink = fromDeeplink
codeNavigator.replaceAll(screens, inSheet = false)
},
onSwitchAccounts = { seed ->
activity?.let {
tlvm.logout(it) {
appState.navigator.replaceAll(LoginScreen(seed))
}
//Listen for authentication changes here
AuthCheck(
navigator = codeNavigator,
onNavigate = { screens ->
codeNavigator.replaceAll(screens, inSheet = false)
},
onSwitchAccounts = { seed ->
activity?.let {
tlvm.logout(it) {
appState.navigator.replaceAll(LoginScreen(seed))
}
}
)
}
}
)
}

TopBarContainer(appState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.getcode.model.KinAmount
import com.getcode.navigation.core.LocalCodeNavigator
import com.getcode.util.RepeatOnLifecycle
import com.getcode.util.getActivityScopedViewModel
import com.getcode.view.components.startupLog
import com.getcode.view.main.account.AccountHome
import com.getcode.view.main.account.AccountSheetViewModel
import com.getcode.view.main.balance.BalanceSheet
Expand All @@ -41,6 +42,7 @@ sealed interface HomeResult {

@Parcelize
data class HomeScreen(
val seed: String? = null,
val cashLink: String? = null,
val requestPayload: String? = null,
) : AppScreen(), MainGraph {
Expand All @@ -50,6 +52,7 @@ data class HomeScreen(
@Composable
override fun Content() {
val vm = getActivityScopedViewModel<HomeViewModel>()
startupLog("home rendered")
HomeScreen(vm, cashLink, requestPayload)

OnScreenResult<HomeResult> { result ->
Expand Down
130 changes: 77 additions & 53 deletions app/src/main/java/com/getcode/view/components/AuthCheck.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.screen.Screen
Expand All @@ -22,7 +21,9 @@ import com.getcode.navigation.screens.LoginGraph
import com.getcode.navigation.screens.LoginScreen
import com.getcode.util.DeeplinkHandler
import com.getcode.util.getActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
Expand All @@ -36,12 +37,13 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber

const val AUTH_NAV = "Authentication Navigation"
private const val APP_STARTUP_TAG = "app-startup"
private typealias DeeplinkFlowState = Pair<Pair<DeeplinkHandler.Type, List<Screen>>, SessionManager.SessionState>

@Composable
fun AuthCheck(
navigator: CodeNavigator,
onNavigate: (List<Screen>, Boolean) -> Unit,
onNavigate: (List<Screen>) -> Unit,
onSwitchAccounts: (String) -> Unit,
) {
val deeplinkHandler = LocalDeeplinks.current
Expand All @@ -60,18 +62,18 @@ fun AuthCheck(
// Allow the seed input screen to complete and avoid
// premature navigation
if (currentRoute is AccessKeyLoginScreen) {
log("No navigation within seed input")
startupLog("No navigation within seed input")
return@LaunchedEffect
}
if (currentRoute is LoginGraph) {
log("No navigation within account creation and onboarding")
} else {
startupLog("No navigation within account creation and onboarding")
} else {
if (authenticated) {
log("Navigating to home")
onNavigate(listOf(HomeScreen()), false)
startupLog("Navigating to home")
onNavigate(listOf(HomeScreen()))
} else {
log("Navigating to login")
onNavigate(listOf(LoginScreen()), false)
startupLog("Navigating to login")
onNavigate(listOf(LoginScreen()))
}
}
} else {
Expand All @@ -86,58 +88,80 @@ fun AuthCheck(
LaunchedEffect(deeplinkHandler) {
val scope = this
deeplinkHandler.intent
.filterNotNull()
.distinctUntilChanged()
.mapNotNull { deeplinkHandler.handle() }
.flatMapLatest { combine(flowOf(it), SessionManager.authState) { a, b -> a to b } }
.filter { (data, authState) ->
if (data.first is DeeplinkHandler.Type.Cash || data.first is DeeplinkHandler.Type.Sdk) {
return@filter authState.isAuthenticated == true
}
return@filter true
}
.mapNotNull { (data, auth) ->
val (type, screens) = data
if (type is DeeplinkHandler.Type.Login) {
if (auth.isAuthenticated == true) {
val entropy = (screens.first() as? LoginScreen)?.seed
log("showing logout confirm")
if (entropy != null) {
deeplinkRouted = true
context.getActivity()?.intent = null
deeplinkHandler.debounceIntent = null
showLogoutMessage(
context = context,
entropyB64 = entropy,
onSwitchAccounts = {
scope.launch {
delay(300) // wait for dismiss
onSwitchAccounts(it)
deeplinkRouted = false
}
},
onCancel = {
deeplinkRouted = false
}
)
return@mapNotNull null
}
}
}
screens
.flatMapLatest { combine(flowOf(deeplinkHandler.handle(it)), SessionManager.authState) { a, b -> a to b } }
.filter { (result, authState) ->
startupLog("checking auth state=${authState.isAuthenticated}")
// wait for authentication
return@filter result != null && authState.isAuthenticated == true
}.mapNotNull { (result, state) ->
result ?: return@mapNotNull null
result to state
}
.onEach { screens ->
.mapSeedToHome()
.map { it.first }
.onEach { (_, screens) ->
deeplinkRouted = true
log("navigated")
onNavigate(screens, true)
startupLog("navigating from deep link")
onNavigate(screens)
deeplinkHandler.debounceIntent = null
context.getActivity()?.intent = null
}
.showLogoutConfirmationIfNeeded(
context = context,
scope = scope,
onSwitchAccounts = {
onSwitchAccounts(it)
deeplinkRouted = false
},
onCancel = {
deeplinkRouted = false
}
)
.launchIn(this)
}
}

private fun log(message: String) = Timber.tag(AUTH_NAV).d(message)
fun startupLog(message: String) = Timber.tag(APP_STARTUP_TAG).d(message)

private fun Flow<DeeplinkFlowState>.mapSeedToHome(): Flow<DeeplinkFlowState> =
map { (data, auth) ->
startupLog("checking type")
val (type, screens) = data
if (type is DeeplinkHandler.Type.Login && auth.isAuthenticated == true) {
// send the user to home screen
val entropy = (screens.first() as? LoginScreen)?.seed
val updatedData = type to listOf(HomeScreen(seed = entropy))
updatedData to auth
} else {
data to auth
}
}


private fun Flow<Pair<DeeplinkHandler.Type, List<Screen>>>.showLogoutConfirmationIfNeeded(
context: Context,
scope: CoroutineScope,
onSwitchAccounts: (String) -> Unit,
onCancel: () -> Unit
): Flow<Pair<DeeplinkHandler.Type, List<Screen>>> = onEach { (type, screens) ->
if (type is DeeplinkHandler.Type.Login) {
val entropy = (screens.first() as? HomeScreen)?.seed
startupLog("showing logout confirm")
if (entropy != null) {
showLogoutMessage(
context = context,
entropyB64 = entropy,
onSwitchAccounts = {
scope.launch {
delay(300) // wait for dismiss
onSwitchAccounts(it)
}
},
onCancel = onCancel
)
}
}
}

private fun showLogoutMessage(
context: Context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.material.LocalContentColor
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.getcode.manager.BottomBarManager
Expand All @@ -30,6 +31,9 @@ fun BottomBarView(
) {
bottomBarMessage ?: return

LaunchedEffect(bottomBarMessage) {
startupLog("bottom bar message shown=${bottomBarMessage.title}")
}
BackHandler {
onBackPressed()
}
Expand Down
Loading