refactor: extract connection row from nwc
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package com.bitcointxoko.gudariwallet.ui.nwc
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.Cable
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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 java.time.Instant
|
||||
|
||||
@Composable
|
||||
internal fun NwcConnectionRow(
|
||||
connection : NwcGetResponse,
|
||||
isDeleting : Boolean,
|
||||
onDeleteConfirm : () -> Unit,
|
||||
onConnectionClick: () -> Unit,
|
||||
modifier : Modifier = Modifier
|
||||
) {
|
||||
val key = connection.data
|
||||
val expired = key.expires_at > 0 && key.expires_at < Instant.now().epochSecond
|
||||
val semantic = semanticColors()
|
||||
|
||||
// ── Swipe-to-delete state ────────────────────────────────────────────────
|
||||
val dismissState = rememberSwipeToDismissBoxState(
|
||||
confirmValueChange = {
|
||||
// Always reject — we never want the row to stay dismissed.
|
||||
// The dialog is triggered via LaunchedEffect on targetValue below.
|
||||
false
|
||||
},
|
||||
positionalThreshold = { totalDistance -> totalDistance * 0.4f }
|
||||
)
|
||||
|
||||
// targetValue changes to EndToStart as soon as the drag crosses the
|
||||
// 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) }
|
||||
|
||||
LaunchedEffect(dismissState.targetValue) {
|
||||
if (dismissState.targetValue == SwipeToDismissBoxValue.EndToStart
|
||||
&& !isDeleting
|
||||
&& !dialogTriggered
|
||||
) {
|
||||
dialogTriggered = true
|
||||
onDeleteConfirm()
|
||||
// Snap the row back to its resting position.
|
||||
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) {
|
||||
dialogTriggered = false
|
||||
}
|
||||
}
|
||||
|
||||
SwipeToDismissBox(
|
||||
state = dismissState,
|
||||
modifier = modifier,
|
||||
enableDismissFromStartToEnd = false,
|
||||
enableDismissFromEndToStart = !isDeleting,
|
||||
backgroundContent = {
|
||||
val fraction = dismissState.progress
|
||||
val bgAlpha = (fraction / 0.4f).coerceIn(0f, 1f)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.error.copy(alpha = bgAlpha))
|
||||
.padding(end = 24.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Delete connection",
|
||||
tint = MaterialTheme.colorScheme.onError.copy(alpha = bgAlpha)
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.clickable(onClick = onConnectionClick)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Cable,
|
||||
contentDescription = null,
|
||||
tint = if (expired)
|
||||
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f)
|
||||
else
|
||||
MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = key.description.ifBlank { "Unnamed connection" },
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (expired)
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
|
||||
else
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
text = if (key.last_used <= 0L) "Never used"
|
||||
else "Last used ${formatEpoch(key.last_used)}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
if (expired) {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.extraSmall,
|
||||
color = semantic.errorContainer,
|
||||
contentColor = semantic.onErrorContainer
|
||||
) {
|
||||
Text(
|
||||
text = "Expired",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@@ -15,12 +13,9 @@ import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Cable
|
||||
import androidx.compose.material.icons.filled.CalendarToday
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Schedule
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
@@ -29,15 +24,11 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bitcointxoko.gudariwallet.api.NwcGetResponse
|
||||
import com.bitcointxoko.gudariwallet.api.NwcNewBudget
|
||||
import com.bitcointxoko.gudariwallet.ui.receive.QrDisplayCard
|
||||
import com.bitcointxoko.gudariwallet.ui.theme.semanticColors
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import androidx.core.net.toUri
|
||||
@@ -609,135 +600,4 @@ private fun NwcPairingUrlDialog(
|
||||
TextButton(onClick = onDismiss) { Text("Done") }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NwcConnectionRow(
|
||||
connection : NwcGetResponse,
|
||||
isDeleting : Boolean,
|
||||
onDeleteConfirm : () -> Unit,
|
||||
onConnectionClick: () -> Unit,
|
||||
modifier : Modifier = Modifier
|
||||
) {
|
||||
val key = connection.data
|
||||
val expired = key.expires_at > 0 && key.expires_at < Instant.now().epochSecond
|
||||
val semantic = semanticColors()
|
||||
|
||||
// ── Swipe-to-delete state ────────────────────────────────────────────────
|
||||
val dismissState = rememberSwipeToDismissBoxState(
|
||||
confirmValueChange = {
|
||||
// Always reject — we never want the row to stay dismissed.
|
||||
// The dialog is triggered via LaunchedEffect on targetValue below.
|
||||
false
|
||||
},
|
||||
positionalThreshold = { totalDistance -> totalDistance * 0.4f }
|
||||
)
|
||||
|
||||
// targetValue changes to EndToStart as soon as the drag crosses the
|
||||
// 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) }
|
||||
|
||||
LaunchedEffect(dismissState.targetValue) {
|
||||
if (dismissState.targetValue == SwipeToDismissBoxValue.EndToStart
|
||||
&& !isDeleting
|
||||
&& !dialogTriggered
|
||||
) {
|
||||
dialogTriggered = true
|
||||
onDeleteConfirm()
|
||||
// Snap the row back to its resting position.
|
||||
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) {
|
||||
dialogTriggered = false
|
||||
}
|
||||
}
|
||||
|
||||
SwipeToDismissBox(
|
||||
state = dismissState,
|
||||
modifier = modifier,
|
||||
enableDismissFromStartToEnd = false,
|
||||
enableDismissFromEndToStart = !isDeleting,
|
||||
backgroundContent = {
|
||||
val fraction = dismissState.progress
|
||||
val bgAlpha = (fraction / 0.4f).coerceIn(0f, 1f)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.error.copy(alpha = bgAlpha))
|
||||
.padding(end = 24.dp),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Delete connection",
|
||||
tint = MaterialTheme.colorScheme.onError.copy(alpha = bgAlpha)
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.clickable(onClick = onConnectionClick)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Cable,
|
||||
contentDescription = null,
|
||||
tint = if (expired)
|
||||
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f)
|
||||
else
|
||||
MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = key.description.ifBlank { "Unnamed connection" },
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (expired)
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
|
||||
else
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
text = if (key.last_used <= 0L) "Never used"
|
||||
else "Last used ${formatEpoch(key.last_used)}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
if (expired) {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.extraSmall,
|
||||
color = semantic.errorContainer,
|
||||
contentColor = semantic.onErrorContainer
|
||||
) {
|
||||
Text(
|
||||
text = "Expired",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user