Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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: 46 additions & 3 deletions android/app/src/main/java/org/bitcoinppl/cove/AppManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.bitcoinppl.cove.cloudbackup.CloudBackupManager
Expand All @@ -29,6 +30,8 @@ class AppManager private constructor() : FfiReconcile {

// Scope for UI-bound work; reconcile() hops to Main here
private val mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private var navigationGeneration = 0L
private var pendingSidebarNavigationJob: Job? = null

// rust bridge - not observable
internal var rust: FfiApp = FfiApp()
Expand Down Expand Up @@ -221,13 +224,24 @@ class AppManager private constructor() : FfiReconcile {
*/
fun selectWallet(id: WalletId) {
try {
rust.selectWallet(id)
isSidebarVisible = false
selectWalletOrThrow(id)
} catch (e: Exception) {
Log.e(tag, "Unable to select wallet $id", e)
}
}

@Throws(Exception::class)
fun selectWalletOrThrow(id: WalletId) {
beginNavigationIntent()
rust.selectWallet(id)
isSidebarVisible = false
}

fun selectLatestOrNewWallet() {
beginNavigationIntent()
rust.selectLatestOrNewWallet()
}

fun toggleSidebar() {
isSidebarVisible = !isSidebarVisible
}
Expand All @@ -237,14 +251,18 @@ class AppManager private constructor() : FfiReconcile {
}

fun closeSidebarAndNavigate(action: suspend () -> Unit) {
pendingSidebarNavigationJob?.cancel()
val generation = beginNavigationIntent()
isSidebarVisible = false
mainScope.launch {
pendingSidebarNavigationJob = mainScope.launch {
kotlinx.coroutines.delay(SIDEBAR_NAVIGATION_DELAY_MS)
if (!isNavigationGenerationCurrent(generation)) return@launch
action()
}
}

Comment thread
praveenperera marked this conversation as resolved.
fun pushRoute(route: Route) {
beginNavigationIntent()
Log.d(tag, "pushRoute: $route")
isSidebarVisible = false
val newRoutes = router.routes.toMutableList().apply { add(route) }
Expand All @@ -257,6 +275,7 @@ class AppManager private constructor() : FfiReconcile {
}

fun pushRoutes(routes: List<Route>) {
beginNavigationIntent()
Log.d(tag, "pushRoutes: ${routes.size} routes")
isSidebarVisible = false
val newRoutes = router.routes.toMutableList().apply { addAll(routes) }
Expand All @@ -269,6 +288,7 @@ class AppManager private constructor() : FfiReconcile {
}

fun popRoute() {
beginNavigationIntent()
Log.d(tag, "popRoute")
if (rust.canGoBack()) {
val newRoutes = router.routes.dropLast(1)
Expand All @@ -282,6 +302,7 @@ class AppManager private constructor() : FfiReconcile {
}

fun setRoute(routes: List<Route>) {
beginNavigationIntent()
Log.d(tag, "setRoute: ${routes.size} routes")

// only dispatch if routes actually changed
Expand All @@ -300,6 +321,7 @@ class AppManager private constructor() : FfiReconcile {
}

fun resetRoute(to: List<Route>) {
beginNavigationIntent()
if (to.size > 1) {
rust.resetNestedRoutesTo(to[0], to.drop(1))
} else if (to.isNotEmpty()) {
Expand All @@ -308,13 +330,34 @@ class AppManager private constructor() : FfiReconcile {
}

fun resetRoute(to: Route) {
beginNavigationIntent()
rust.resetDefaultRouteTo(to)
}

fun loadAndReset(to: Route) {
beginNavigationIntent()
rust.loadAndResetDefaultRoute(to)
}

fun captureLoadAndResetGeneration(): Long = navigationGeneration

fun resetAfterLoadingIfCurrent(
generation: Long,
route: Route.LoadAndReset,
nextRoutes: List<Route>,
) {
if (!isNavigationGenerationCurrent(generation)) return
if (router.default != route) return
rust.resetAfterLoading(nextRoutes)
}

private fun beginNavigationIntent(): Long {
navigationGeneration += 1
return navigationGeneration
}

private fun isNavigationGenerationCurrent(generation: Long): Boolean = generation == navigationGeneration

fun agreeToTerms() {
dispatch(AppAction.AcceptTerms)
isTermsAccepted = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class AuthManager private constructor() : AuthManagerReconciler {
app.isLoading = true

// select the latest (most recently used) wallet or navigate to new wallet flow
app.rust.selectLatestOrNewWallet()
app.selectLatestOrNewWallet()
}

/**
Expand Down
8 changes: 4 additions & 4 deletions android/app/src/main/java/org/bitcoinppl/cove/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ private fun GlobalAlertDialog(
TextButton(onClick = {
onDismiss()
try {
app.rust.selectWallet(state.walletId)
app.selectWalletOrThrow(state.walletId)
app.resetRoute(Route.SelectedWallet(state.walletId))
} catch (e: Exception) {
Log.e("GlobalAlert", "Failed to select wallet", e)
Expand Down Expand Up @@ -821,7 +821,7 @@ private fun GlobalAlertDialog(
TextButton(onClick = {
onDismiss()
try {
app.rust.selectWallet(state.walletId)
app.selectWalletOrThrow(state.walletId)
app.resetRoute(Route.SelectedWallet(state.walletId))
} catch (e: Exception) {
Log.e("GlobalAlert", "Failed to select wallet", e)
Expand Down Expand Up @@ -994,7 +994,7 @@ private fun GlobalAlertDialog(
try {
Wallet.newFromXpub(xpub = text.trim()).use { wallet ->
val id = wallet.id()
app.rust.selectWallet(id)
app.selectWalletOrThrow(id)
app.resetRoute(Route.SelectedWallet(id))
}
} catch (e: Exception) {
Expand Down Expand Up @@ -1055,7 +1055,7 @@ private fun GlobalAlertDialog(
}
TextButton(onClick = {
onDismiss()
app.rust.selectLatestOrNewWallet()
app.selectLatestOrNewWallet()
}) { Text("Cancel") }
}
},
Expand Down
13 changes: 5 additions & 8 deletions android/app/src/main/java/org/bitcoinppl/cove/RouteView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ fun RouteView(app: AppManager, route: Route) {
is Route.LoadAndReset -> {
LoadAndResetContainer(
app = app,
route = route,
nextRoutes = route.resetTo.map { it.route() },
loadingTimeMs = route.afterMillis.toLong(),
)
Expand All @@ -92,6 +93,7 @@ fun RouteView(app: AppManager, route: Route) {
@Composable
private fun LoadAndResetContainer(
app: AppManager,
route: Route.LoadAndReset,
nextRoutes: List<Route>,
loadingTimeMs: Long,
) {
Expand All @@ -101,15 +103,10 @@ private fun LoadAndResetContainer(
}

// execute reset after delay
LaunchedEffect(Unit) {
LaunchedEffect(route) {
val generation = app.captureLoadAndResetGeneration()
delay(loadingTimeMs)

if (nextRoutes.size > 1) {
// nested routes: first route is default, rest are nested
app.resetRoute(nextRoutes)
} else if (nextRoutes.isNotEmpty()) {
// single route becomes new default
app.resetRoute(nextRoutes[0])
}
app.resetAfterLoadingIfCurrent(generation, route, nextRoutes)
}
}
8 changes: 4 additions & 4 deletions android/app/src/main/java/org/bitcoinppl/cove/ScanManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ class ScanManager private constructor() {
val manager = ImportWalletManager()
try {
val walletMetadata = manager.rust.importWallet(listOf(words))
app.rust.selectWallet(walletMetadata.id)
app.selectWalletOrThrow(walletMetadata.id)
} catch (e: ImportWalletException.InvalidWordGroup) {
Log.d(tag, "Invalid word group detected")
app.alertState = TaggedItem(AppAlertState.InvalidWordGroup)
} catch (e: ImportWalletException.WalletAlreadyExists) {
Log.w(tag, "Attempted to import words for an existing hot wallet: ${e.v1}")
app.alertState = TaggedItem(AppAlertState.DuplicateWallet(e.v1))
try {
app.rust.selectWallet(e.v1)
app.selectWalletOrThrow(e.v1)
} catch (selectError: Exception) {
Log.e(tag, "Unable to select existing wallet", selectError)
}
Expand All @@ -132,7 +132,7 @@ class ScanManager private constructor() {
app.alertState = TaggedItem(AppAlertState.ImportedSuccessfully)

if (app.walletManager?.id != id) {
app.rust.selectWallet(id)
app.selectWalletOrThrow(id)
}

if (app.walletManager?.id == id && app.walletManager?.walletMetadata?.walletType != WalletType.HOT) {
Expand All @@ -148,7 +148,7 @@ class ScanManager private constructor() {
} catch (e: WalletException.WalletAlreadyExists) {
app.alertState = TaggedItem(AppAlertState.DuplicateWallet(e.v1))
try {
app.rust.selectWallet(e.v1)
app.selectWalletOrThrow(e.v1)
} catch (selectError: Exception) {
Log.e(tag, "Unable to select existing wallet", selectError)
app.alertState = TaggedItem(AppAlertState.UnableToSelectWallet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ fun NewWalletSelectScreen(
val id = wallet.id()
android.util.Log.d("NewWalletSelectScreen", "Imported Wallet: $id")

app.rust.selectWallet(id = id)
app.selectWalletOrThrow(id)
app.popRoute()
app.alertState = TaggedItem(AppAlertState.ImportedSuccessfully)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fun ColdWalletQrScanScreen(app: AppManager, modifier: Modifier = Modifier) {
Log.d("ColdWalletQrScanScreen", "Imported Wallet: $id")

app.popRoute()
app.rust.selectWallet(id = id)
app.selectWalletOrThrow(id)
app.alertState = TaggedItem(AppAlertState.ImportedSuccessfully)
} catch (e: WalletException.WalletAlreadyExists) {
app.popRoute()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fun QrCodeImportScreen(app: AppManager, modifier: Modifier = Modifier) {
wallet.close()
Log.d("QrCodeImportScreen", "Imported Wallet: $id")

app.rust.selectWallet(id = id)
app.selectWalletOrThrow(id)
app.popRoute()
app.alertState = TaggedItem(AppAlertState.ImportedSuccessfully)
} catch (e: WalletException.WalletAlreadyExists) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,10 @@ fun HotWalletImportScreen(
fun importWallet() {
try {
val walletMetadata = manager.importWallet(enteredWords)
app.selectWalletOrThrow(walletMetadata.id)
app.clearWalletManager()
if (onImported != null) {
onImported.invoke(walletMetadata.id)
} else {
app.rust.selectWallet(walletMetadata.id)
onImported?.invoke(walletMetadata.id) ?: run {
app.selectWalletOrThrow(walletMetadata.id)
app.resetRoute(Route.SelectedWallet(walletMetadata.id))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
} catch (e: ImportWalletException.InvalidWordGroup) {
Expand Down Expand Up @@ -520,7 +519,7 @@ fun HotWalletImportScreen(
app.clearWalletManager()
manager.close()
onImported?.invoke(walletId) ?: run {
app.rust.selectWallet(walletId)
app.selectWalletOrThrow(walletId)
app.resetRoute(Route.SelectedWallet(walletId))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ fun SelectedWalletContainer(
val otherWallet = wallets.firstOrNull { it.id != id }

if (otherWallet != null) {
app.rust.selectWallet(otherWallet.id)
app.selectWalletOrThrow(otherWallet.id)
} else {
app.loadAndReset(RouteFactory().newWalletSelect())
}
Expand All @@ -103,14 +103,13 @@ fun SelectedWalletContainer(

// start wallet scan after loading (matches iOS .task)
LaunchedEffect(manager) {
manager?.let { wm ->
try {
wm.rust.startWalletScan()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
android.util.Log.e(tag, "wallet scan failed: ${e.message}", e)
}
val wm = manager ?: return@LaunchedEffect
try {
wm.rust.startWalletScan()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
android.util.Log.e(tag, "wallet scan failed: ${e.message}", e)
}
}

Expand All @@ -122,10 +121,11 @@ fun SelectedWalletContainer(
}

// update app wallet manager when loaded
LaunchedEffect(manager?.loadState) {
val loadState = manager?.loadState
if (loadState is WalletLoadState.LOADED) {
manager?.let { app.setWalletManager(it) }
val loadedManager = manager
val loadState = loadedManager?.loadState
LaunchedEffect(loadedManager, loadState) {
if (loadedManager != null && loadState is WalletLoadState.LOADED) {
app.setWalletManager(loadedManager)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ fun SelectedWalletScreen(
}
},
colors =
TopAppBarDefaults.centerAlignedTopAppBarColors(
TopAppBarDefaults.topAppBarColors(
// gradual fade from transparent to midnight blue based on scroll progress
containerColor = CoveColor.midnightBlue.copy(alpha = scrollProgress),
titleContentColor = Color.White,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ fun TransactionDetailsScreen(
topBar = {
CenterAlignedTopAppBar(
colors =
TopAppBarDefaults.centerAlignedTopAppBarColors(
TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
titleContentColor = fg,
actionIconContentColor = fg,
Expand Down
Loading
Loading