Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
14 changes: 14 additions & 0 deletions android/app/src/main/java/org/bitcoinppl/cove/AppManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,20 @@ class AppManager private constructor() : FfiReconcile {
is AppStateReconcileMessage.WalletsChanged -> {
wallets = runCatching { database.wallets().all() }.getOrElse { emptyList() }
}

is AppStateReconcileMessage.ShowLoadingPopup -> {
alertState =
TaggedItem(
AppAlertState.General(
title = "Working on it...",
message = "",
),
)
}

is AppStateReconcileMessage.HideLoadingPopup -> {
alertState = null
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,11 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.bitcoinppl.cove.AppAlertState
import org.bitcoinppl.cove.AppManager
import org.bitcoinppl.cove.TaggedItem
import org.bitcoinppl.cove.WalletManager

// delay before showing export loading alert
private const val EXPORT_LOADING_ALERT_DELAY_MS = 500L

// delay before showing file picker after dismissing alert
private const val ALERT_DISMISS_DELAY_MS = 500L

// export type for tracking what is being exported
sealed class ExportType {
data object Labels : ExportType()
Expand Down Expand Up @@ -102,58 +92,23 @@ fun rememberWalletExportLaunchers(
) { uri ->
uri?.let {
scope.launch {
// capture manager at coroutine start to avoid null during suspension
val currentManager = manager
exportState.isExporting = true
val currentExportType = exportState.exportType

// track the alert by identity to avoid race conditions
var loadingAlertItem: TaggedItem<AppAlertState>? = null

try {
// show loading alert for transaction exports after a delay
val alertJob =
scope.launch {
delay(EXPORT_LOADING_ALERT_DELAY_MS)
if (exportState.isExporting && currentExportType is ExportType.Transactions) {
val alert: TaggedItem<AppAlertState> =
TaggedItem(
AppAlertState.General(
title = "Exporting, please wait...",
message = "Creating a transaction export file. If this is the first time it might take a while",
),
)
loadingAlertItem = alert
app.alertState = alert
}
}

// fetch content using new async methods that handle loading popup
val content =
when (currentExportType) {
is ExportType.Transactions -> {
withContext(Dispatchers.IO) {
currentManager?.rust?.createTransactionsWithFiatExport()
}
currentManager?.rust?.exportTransactionsCsv()?.content
}
is ExportType.Labels -> {
withContext(Dispatchers.IO) {
currentManager?.rust?.labelManager()?.use { it.export() }
}
currentManager?.rust?.exportLabelsForShare()?.content
}
null -> null
}

// cancel alert job and wait for it to complete to avoid race
alertJob.cancelAndJoin()

// clear alert only if it's still the one we set (identity check)
loadingAlertItem?.let { alert ->
if (app.alertState?.id == alert.id) {
app.alertState = null
delay(ALERT_DISMISS_DELAY_MS)
}
}

content?.let { data ->
withContext(Dispatchers.IO) {
context.contentResolver.openOutputStream(uri)?.use { output ->
Expand All @@ -179,13 +134,6 @@ fun rememberWalletExportLaunchers(
}
} catch (e: Exception) {
android.util.Log.e(tag, "error exporting file", e)
// clear loading alert on error only if it's still ours
loadingAlertItem?.let { alert ->
if (app.alertState?.id == alert.id) {
app.alertState = null
}
}

val errorType =
when (currentExportType) {
is ExportType.Transactions -> "transactions"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,14 @@ internal fun WalletSheetsHost(
TextButton(
onClick = {
showExportLabelsDialog = false
exportLabelManager?.close()
exportLabelManager = null
scope.launch {
try {
shareLabelsFile(context, manager)
} catch (e: Exception) {
android.util.Log.e(tag, "Failed to share labels", e)
snackbarHostState.showSnackbar("Unable to share labels: ${e.localizedMessage ?: e.message}")
} finally {
exportLabelManager?.close()
exportLabelManager = null
}
}
},
Expand Down Expand Up @@ -239,16 +238,11 @@ private suspend fun shareLabelsFile(
context: Context,
manager: WalletManager,
) {
withContext(Dispatchers.IO) {
val metadata = manager.walletMetadata
val labelsContent = manager.rust.labelManager().use { it.export() }
val fileName =
manager.rust.labelManager().use { lm ->
"${lm.exportDefaultFileName(metadata?.name ?: "wallet")}.jsonl"
}
val result = manager.rust.exportLabelsForShare()

val file = File(context.cacheDir, fileName)
file.writeText(labelsContent)
withContext(Dispatchers.IO) {
val file = File(context.cacheDir, result.filename)
file.writeText(result.content)

val uri =
FileProvider.getUriForFile(
Expand Down
Loading