refactor: fix loading flash
This commit is contained in:
@@ -490,7 +490,7 @@ fun WalletScreen(
|
||||
budgets = state.budgets,
|
||||
onBack = { navController.popBackStack() },
|
||||
onRevoke = {
|
||||
nwcVm.revokeConnection(pubkey)
|
||||
nwcVm.deleteConnection(pubkey)
|
||||
navController.popBackStack()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitcointxoko.gudariwallet.api.NwcGetResponse
|
||||
import com.bitcointxoko.gudariwallet.ui.theme.semanticColors
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Instant
|
||||
|
||||
@Composable
|
||||
@@ -43,7 +44,9 @@ internal fun NwcConnectionRow(
|
||||
// threshold, even though confirmValueChange returns false and currentValue
|
||||
// stays at Settled. Use a flag so we only fire once per swipe gesture.
|
||||
var dialogTriggered by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// Fires when the swipe crosses the threshold — trigger confirm dialog
|
||||
LaunchedEffect(dismissState.targetValue) {
|
||||
if (dismissState.targetValue == SwipeToDismissBoxValue.EndToStart
|
||||
&& !isDeleting
|
||||
@@ -51,12 +54,14 @@ internal fun NwcConnectionRow(
|
||||
) {
|
||||
dialogTriggered = true
|
||||
onDeleteConfirm()
|
||||
// Snap the row back to its resting position.
|
||||
dismissState.reset()
|
||||
// Reset in its own scope — won't be cancelled when targetValue changes back
|
||||
scope.launch { dismissState.reset() }
|
||||
}
|
||||
// Reset the flag once the swipe returns to Settled so the
|
||||
// gesture can be triggered again if the user cancels and re-swipes.
|
||||
if (dismissState.targetValue == SwipeToDismissBoxValue.Settled) {
|
||||
}
|
||||
|
||||
// Fires when the row has fully snapped back — re-arm for next swipe
|
||||
LaunchedEffect(dismissState.currentValue) {
|
||||
if (dismissState.currentValue == SwipeToDismissBoxValue.Settled) {
|
||||
dialogTriggered = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
|
||||
// ── Screen ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NwcScreen(
|
||||
@@ -27,9 +25,11 @@ fun NwcScreen(
|
||||
val isDeleting by viewModel.isDeleting.collectAsStateWithLifecycle()
|
||||
val isCreating by viewModel.isCreating.collectAsStateWithLifecycle()
|
||||
val pairingUrl by viewModel.pairingUrl.collectAsStateWithLifecycle()
|
||||
val showAddSheet by viewModel.showAddSheet.collectAsStateWithLifecycle()
|
||||
val pendingDeletePubkey by viewModel.pendingDeletePubkey.collectAsStateWithLifecycle()
|
||||
|
||||
var showAddSheet by remember { mutableStateOf(false) }
|
||||
var pendingDeletePubkey by remember { mutableStateOf<String?>(null) }
|
||||
// ── Shared refresh lambda (item #13) ──────────────────────────────────────
|
||||
val refresh = { viewModel.loadConnections(includeExpired = true) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -43,7 +43,7 @@ fun NwcScreen(
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = { showAddSheet = true }) {
|
||||
FloatingActionButton(onClick = { viewModel.openAddSheet() }) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Add connection")
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ fun NwcScreen(
|
||||
|
||||
PullToRefreshBox(
|
||||
isRefreshing = isRefreshing,
|
||||
onRefresh = { viewModel.loadConnections(includeExpired = true) },
|
||||
onRefresh = refresh, // ← was loadConnections(includeExpired = true)
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
@@ -68,7 +68,7 @@ fun NwcScreen(
|
||||
is NwcUiState.Error -> {
|
||||
NwcErrorContent(
|
||||
message = state.message,
|
||||
onRetry = { viewModel.loadConnections(includeExpired = true) },
|
||||
onRetry = refresh, // ← was loadConnections(includeExpired = true)
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
@@ -76,7 +76,7 @@ fun NwcScreen(
|
||||
is NwcUiState.Success -> {
|
||||
if (state.connections.isEmpty()) {
|
||||
NwcEmptyContent(
|
||||
onAddNew = { showAddSheet = true },
|
||||
onAddNew = { viewModel.openAddSheet() },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
} else {
|
||||
@@ -91,7 +91,7 @@ fun NwcScreen(
|
||||
NwcConnectionRow(
|
||||
connection,
|
||||
isDeleting,
|
||||
onDeleteConfirm = { pendingDeletePubkey = connection.data.pubkey },
|
||||
onDeleteConfirm = { viewModel.requestDelete(connection.data.pubkey) },
|
||||
onConnectionClick = { onConnectionClick(connection.data.pubkey) }
|
||||
)
|
||||
HorizontalDivider(
|
||||
@@ -117,9 +117,9 @@ fun NwcScreen(
|
||||
expiresAt = expiresAt,
|
||||
budgets = budgets
|
||||
)
|
||||
showAddSheet = false
|
||||
viewModel.closeAddSheet() // ← was openAddSheet()
|
||||
},
|
||||
onDismiss = { showAddSheet = false }
|
||||
onDismiss = { viewModel.closeAddSheet() } // ← was openAddSheet()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -132,21 +132,18 @@ fun NwcScreen(
|
||||
}
|
||||
|
||||
// ── Delete confirmation dialog ─────────────────────────────────────────────
|
||||
pendingDeletePubkey?.let { pubkey ->
|
||||
pendingDeletePubkey?.let {
|
||||
AlertDialog(
|
||||
onDismissRequest = { pendingDeletePubkey = null },
|
||||
onDismissRequest = { viewModel.cancelDelete() },
|
||||
title = { Text("Remove connection?") },
|
||||
text = { Text("This will permanently revoke this Nostr Wallet Connect key. Any app using it will lose access.") },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.deleteConnection(pubkey)
|
||||
pendingDeletePubkey = null
|
||||
}
|
||||
onClick = { viewModel.confirmDelete() } // ← was deleteConnection(pubkey) + cancelDelete()
|
||||
) { Text("Remove", color = MaterialTheme.colorScheme.error) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { pendingDeletePubkey = null }) { Text("Cancel") }
|
||||
TextButton(onClick = { viewModel.cancelDelete() }) { Text("Cancel") }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -65,6 +65,25 @@ class NwcViewModel(
|
||||
val connectionDetailState: StateFlow<ConnectionDetailState> =
|
||||
_connectionDetailState.asStateFlow()
|
||||
|
||||
// ── Sheet / dialog UI state (survives config change) ────────────────────────
|
||||
|
||||
private val _showAddSheet = MutableStateFlow(false)
|
||||
val showAddSheet: StateFlow<Boolean> = _showAddSheet.asStateFlow()
|
||||
|
||||
private val _pendingDeletePubkey = MutableStateFlow<String?>(null)
|
||||
val pendingDeletePubkey: StateFlow<String?> = _pendingDeletePubkey.asStateFlow()
|
||||
|
||||
fun openAddSheet() { _showAddSheet.value = true }
|
||||
fun closeAddSheet() { _showAddSheet.value = false }
|
||||
|
||||
fun requestDelete(pubkey: String) { _pendingDeletePubkey.value = pubkey }
|
||||
fun cancelDelete() { _pendingDeletePubkey.value = null }
|
||||
// confirmDelete() calls the existing deleteConnection() then clears the pending key
|
||||
fun confirmDelete() {
|
||||
_pendingDeletePubkey.value?.let { deleteConnection(it) }
|
||||
_pendingDeletePubkey.value = null
|
||||
}
|
||||
|
||||
init {
|
||||
loadConnections()
|
||||
}
|
||||
@@ -134,7 +153,7 @@ class NwcViewModel(
|
||||
}
|
||||
.onSuccess { url ->
|
||||
_pairingUrl.value = url
|
||||
loadConnections()
|
||||
silentRefresh()
|
||||
}
|
||||
.onFailure { e ->
|
||||
Timber.e(e, "[$TAG] createConnection failed")
|
||||
@@ -152,8 +171,13 @@ class NwcViewModel(
|
||||
_pairingUrl.value = null
|
||||
}
|
||||
|
||||
// ── Delete ────────────────────────────────────────────────────────────────
|
||||
// ── Delete / Revoke ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Deletes an NWC key by pubkey. Used by both the list screen (swipe-to-delete)
|
||||
* and the detail screen (revoke button). Sets [_isDeleting] for the duration
|
||||
* so the list screen can disable row interactions while in flight.
|
||||
*/
|
||||
fun deleteConnection(pubkey: String) {
|
||||
Timber.d("[$TAG] deleteConnection pubkey=${pubkey.take(16)}…")
|
||||
viewModelScope.launch {
|
||||
@@ -168,7 +192,7 @@ class NwcViewModel(
|
||||
current.connections.filter { it.data.pubkey != pubkey }
|
||||
)
|
||||
}
|
||||
loadConnections()
|
||||
silentRefresh()
|
||||
}
|
||||
.onFailure { e ->
|
||||
Timber.e(e, "[$TAG] failed to delete connection pubkey=${pubkey.take(16)}…")
|
||||
@@ -216,34 +240,28 @@ class NwcViewModel(
|
||||
_connectionDetailState.value = ConnectionDetailState.Loading
|
||||
}
|
||||
|
||||
// ── Silent refresh (no Loading flash) ───────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Deletes the key via the repo, then refreshes the list so the revoked
|
||||
* entry disappears from NwcScreen when the user pops back.
|
||||
* Syncs the connection list from the server without transitioning to
|
||||
* [NwcUiState.Loading] first. Used after mutations (create, delete) where
|
||||
* the UI already shows the correct optimistic state.
|
||||
* Failures are non-fatal — the optimistic state is already correct.
|
||||
*/
|
||||
fun revokeConnection(pubkey: String) {
|
||||
Timber.d("[$TAG] revokeConnection pubkey=${pubkey.take(16)}…")
|
||||
private fun silentRefresh(includeExpired: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
runCatching { repo.deleteNwcKey(pubkey) }
|
||||
.onSuccess {
|
||||
Timber.i("[$TAG] revoked connection pubkey=${pubkey.take(16)}… ✓")
|
||||
// Optimistic local removal
|
||||
val current = _uiState.value
|
||||
if (current is NwcUiState.Success) {
|
||||
_uiState.value = NwcUiState.Success(
|
||||
current.connections.filter { it.data.pubkey != pubkey }
|
||||
)
|
||||
}
|
||||
loadConnections()
|
||||
runCatching { repo.getNwcKeys(includeExpired) }
|
||||
.onSuccess { keys ->
|
||||
Timber.d("[$TAG] silentRefresh: ${keys.size} connection(s)")
|
||||
_uiState.value = NwcUiState.Success(keys)
|
||||
}
|
||||
.onFailure { e ->
|
||||
Timber.e(e, "[$TAG] failed to revoke connection")
|
||||
_uiState.value = NwcUiState.Error(
|
||||
e.message ?: "Failed to revoke connection"
|
||||
)
|
||||
Timber.w(e, "[$TAG] silentRefresh failed (non-fatal)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ── Factory ───────────────────────────────────────────────────────────────
|
||||
|
||||
class Factory(private val repo: WalletRepository) :
|
||||
|
||||
Reference in New Issue
Block a user