refactor: extract common ui components
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
package com.bitcointxoko.gudariwallet.ui.common
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun AmountWithFiatColumn(
|
||||||
|
amountSat : Long,
|
||||||
|
isOutgoing : Boolean,
|
||||||
|
amountColor : Color,
|
||||||
|
fiatLine : String?,
|
||||||
|
style : TextStyle = MaterialTheme.typography.bodyMedium,
|
||||||
|
horizontalAlignment: Alignment.Horizontal = Alignment.End
|
||||||
|
) {
|
||||||
|
val prefix = if (isOutgoing) "−" else "+"
|
||||||
|
Column(horizontalAlignment = horizontalAlignment) {
|
||||||
|
Text(
|
||||||
|
text = "$prefix$amountSat sats",
|
||||||
|
style = style.copy(fontFamily = FontFamily.Monospace),
|
||||||
|
color = amountColor
|
||||||
|
)
|
||||||
|
if (fiatLine != null) {
|
||||||
|
Spacer(Modifier.height(if (horizontalAlignment == Alignment.CenterHorizontally) 4.dp else 1.dp))
|
||||||
|
Text(
|
||||||
|
text = fiatLine,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.75f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.bitcointxoko.gudariwallet.ui.common
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ContentCopy
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.ClipEntry
|
||||||
|
import androidx.compose.ui.platform.LocalClipboard
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A small icon button that copies [value] to the clipboard under [label].
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CopyIconButton(
|
||||||
|
label : String,
|
||||||
|
value : String,
|
||||||
|
size : Dp = 32.dp,
|
||||||
|
iconSize : Dp = 16.dp,
|
||||||
|
tint : Color = Color.Unspecified
|
||||||
|
) {
|
||||||
|
val clipboard = LocalClipboard.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
clipboard.setClipEntry(ClipEntry(ClipData.newPlainText(label, value)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(size)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.ContentCopy,
|
||||||
|
contentDescription = "Copy $label",
|
||||||
|
modifier = Modifier.size(iconSize),
|
||||||
|
tint = if (tint == Color.Unspecified)
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
else tint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.bitcointxoko.gudariwallet.ui.common
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ContentCopy
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DetailRow(
|
||||||
|
label : String,
|
||||||
|
value : String,
|
||||||
|
subtitle : String? = null, // ← from components
|
||||||
|
monospace : Boolean = false, // ← from history
|
||||||
|
copyable : Boolean = false, // ← from history
|
||||||
|
onCopy : (() -> Unit)? = null, // ← from history
|
||||||
|
maxLines : Int = Int.MAX_VALUE,
|
||||||
|
valueColor : Color = Color.Unspecified,
|
||||||
|
textDecoration : TextDecoration? = null,
|
||||||
|
onValueClick : (() -> Unit)? = null,
|
||||||
|
trailingContent: (@Composable () -> Unit)? = null
|
||||||
|
) {
|
||||||
|
val baseStyle = MaterialTheme.typography.bodySmall
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.then(if (copyable && onCopy != null) Modifier.clickable(onClick = onCopy) else Modifier)
|
||||||
|
.padding(vertical = 6.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.Top
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
style = baseStyle,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.weight(0.35f)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(0.65f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f, fill = false)) {
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
style = baseStyle.copy(
|
||||||
|
fontFamily = if (monospace) FontFamily.Monospace else FontFamily.Default,
|
||||||
|
textDecoration = textDecoration
|
||||||
|
),
|
||||||
|
color = valueColor,
|
||||||
|
maxLines = maxLines,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = if (onValueClick != null) Modifier.clickable(onClick = onValueClick)
|
||||||
|
else Modifier
|
||||||
|
)
|
||||||
|
if (subtitle != null) {
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (copyable) {
|
||||||
|
Spacer(Modifier.width(4.dp))
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.ContentCopy,
|
||||||
|
contentDescription = "Copy $label",
|
||||||
|
modifier = Modifier.size(14.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
trailingContent?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.bitcointxoko.gudariwallet.ui.common
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DetailSection(
|
||||||
|
title : String,
|
||||||
|
content: @Composable ColumnScope.() -> Unit
|
||||||
|
) {
|
||||||
|
Card(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.bitcointxoko.gudariwallet.ui.common
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
||||||
|
import com.bitcointxoko.gudariwallet.ui.theme.semanticColors
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun StatusChip(status: String) {
|
||||||
|
val semantic = semanticColors()
|
||||||
|
val strings = LocalAppStrings.current
|
||||||
|
|
||||||
|
data class ChipStyle(
|
||||||
|
val containerColor: Color,
|
||||||
|
val contentColor: Color,
|
||||||
|
val label: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
val style = when (status.lowercase()) {
|
||||||
|
"success", "complete", "paid" -> ChipStyle(
|
||||||
|
semantic.successContainer, semantic.onSuccessContainer, strings.statusSuccess
|
||||||
|
)
|
||||||
|
"pending", "in_flight", "inflight" -> ChipStyle(
|
||||||
|
semantic.warningContainer, semantic.onWarningContainer, strings.statusPending
|
||||||
|
)
|
||||||
|
"failed", "error", "expired" -> ChipStyle(
|
||||||
|
semantic.errorContainer, semantic.onErrorContainer, strings.statusFailed
|
||||||
|
)
|
||||||
|
else -> ChipStyle(
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
strings.statusUnknown,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
color = style.containerColor,
|
||||||
|
contentColor = style.contentColor,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = style.label,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
package com.bitcointxoko.gudariwallet.ui.components
|
|
||||||
|
|
||||||
import android.content.ClipData
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.ContentCopy
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.ClipEntry
|
|
||||||
import androidx.compose.ui.platform.LocalClipboard
|
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A two-column label/value row used throughout the send flow.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun DetailRow(
|
|
||||||
label : String,
|
|
||||||
value : String,
|
|
||||||
subtitle : String? = null,
|
|
||||||
valueColor : Color = Color.Unspecified,
|
|
||||||
textDecoration : TextDecoration? = null,
|
|
||||||
onValueClick : (() -> Unit)? = null,
|
|
||||||
trailingContent: (@Composable () -> Unit)? = null
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 4.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.weight(0.35f)
|
|
||||||
)
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(0.65f)
|
|
||||||
.padding(start = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.weight(1f, fill = false)) {
|
|
||||||
Text(
|
|
||||||
text = value,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
color = valueColor,
|
|
||||||
style = LocalTextStyle.current.copy(textDecoration = textDecoration),
|
|
||||||
modifier = if (onValueClick != null) Modifier.clickable(onClick = onValueClick)
|
|
||||||
else Modifier
|
|
||||||
)
|
|
||||||
if (subtitle != null) {
|
|
||||||
Text(
|
|
||||||
text = subtitle,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trailingContent?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A small icon button that copies [value] to the clipboard under [label].
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun CopyIconButton(
|
|
||||||
label : String,
|
|
||||||
value : String,
|
|
||||||
size : Dp = 32.dp,
|
|
||||||
iconSize : Dp = 16.dp,
|
|
||||||
tint : Color = Color.Unspecified
|
|
||||||
) {
|
|
||||||
val clipboard = LocalClipboard.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
clipboard.setClipEntry(ClipEntry(ClipData.newPlainText(label, value)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(size)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.ContentCopy,
|
|
||||||
contentDescription = "Copy $label",
|
|
||||||
modifier = Modifier.size(iconSize),
|
|
||||||
tint = if (tint == Color.Unspecified)
|
|
||||||
MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
else tint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
package com.bitcointxoko.gudariwallet.ui.history
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.ContentCopy
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
|
||||||
import com.bitcointxoko.gudariwallet.ui.theme.semanticColors
|
|
||||||
|
|
||||||
// ── Amount + fiat column ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun AmountWithFiatColumn(
|
|
||||||
amountSat : Long,
|
|
||||||
isOutgoing : Boolean,
|
|
||||||
amountColor : Color,
|
|
||||||
fiatLine : String?,
|
|
||||||
style : TextStyle = MaterialTheme.typography.bodyMedium,
|
|
||||||
horizontalAlignment: Alignment.Horizontal = Alignment.End
|
|
||||||
) {
|
|
||||||
val prefix = if (isOutgoing) "−" else "+"
|
|
||||||
Column(horizontalAlignment = horizontalAlignment) {
|
|
||||||
Text(
|
|
||||||
text = "$prefix$amountSat sats",
|
|
||||||
style = style.copy(fontFamily = FontFamily.Monospace),
|
|
||||||
color = amountColor
|
|
||||||
)
|
|
||||||
if (fiatLine != null) {
|
|
||||||
Spacer(Modifier.height(if (horizontalAlignment == Alignment.CenterHorizontally) 4.dp else 1.dp))
|
|
||||||
Text(
|
|
||||||
text = fiatLine,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.75f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Status chip ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun StatusChip(status: String) {
|
|
||||||
val semantic = semanticColors()
|
|
||||||
val strings = LocalAppStrings.current // or however you access Lyricist strings
|
|
||||||
|
|
||||||
data class ChipStyle(
|
|
||||||
val containerColor: Color,
|
|
||||||
val contentColor: Color,
|
|
||||||
val label: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
val style = when (status.lowercase()) {
|
|
||||||
"success", "complete", "paid" -> ChipStyle(
|
|
||||||
semantic.successContainer, semantic.onSuccessContainer, strings.statusSuccess
|
|
||||||
)
|
|
||||||
"pending", "in_flight", "inflight" -> ChipStyle(
|
|
||||||
semantic.warningContainer, semantic.onWarningContainer, strings.statusPending
|
|
||||||
)
|
|
||||||
"failed", "error", "expired" -> ChipStyle(
|
|
||||||
semantic.errorContainer, semantic.onErrorContainer, strings.statusFailed
|
|
||||||
)
|
|
||||||
else -> ChipStyle(
|
|
||||||
MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
strings.statusUnknown,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
shape = MaterialTheme.shapes.small,
|
|
||||||
color = style.containerColor,
|
|
||||||
contentColor = style.contentColor,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = style.label,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ── Section card wrapper ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun DetailSection(
|
|
||||||
title : String,
|
|
||||||
content: @Composable ColumnScope.() -> Unit
|
|
||||||
) {
|
|
||||||
Card(modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(12.dp))
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Label / value row ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun DetailRow(
|
|
||||||
label : String,
|
|
||||||
value : String,
|
|
||||||
monospace : Boolean = false,
|
|
||||||
copyable : Boolean = false,
|
|
||||||
onCopy : (() -> Unit)? = null,
|
|
||||||
maxLines : Int = Int.MAX_VALUE,
|
|
||||||
valueColor : Color = Color.Unspecified,
|
|
||||||
textDecoration : TextDecoration? = null,
|
|
||||||
onValueClick : (() -> Unit)? = null,
|
|
||||||
trailingContent: (@Composable () -> Unit)? = null
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.then(if (copyable && onCopy != null) Modifier.clickable(onClick = onCopy) else Modifier)
|
|
||||||
.padding(vertical = 6.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.Top
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier.weight(0.35f)
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(8.dp))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.weight(0.65f),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = value,
|
|
||||||
style = if (monospace)
|
|
||||||
MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace, textDecoration = textDecoration)
|
|
||||||
else
|
|
||||||
MaterialTheme.typography.bodySmall.copy(textDecoration = textDecoration),
|
|
||||||
color = valueColor,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f, fill = false)
|
|
||||||
.then(if (onValueClick != null) Modifier.clickable(onClick = onValueClick) else Modifier),
|
|
||||||
maxLines = maxLines,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
if (copyable) {
|
|
||||||
Spacer(Modifier.width(4.dp))
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.ContentCopy,
|
|
||||||
contentDescription = "Copy $label",
|
|
||||||
modifier = Modifier.size(14.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
trailingContent?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -45,6 +45,11 @@ import androidx.core.net.toUri
|
|||||||
import com.bitcointxoko.gudariwallet.api.PaymentRecord
|
import com.bitcointxoko.gudariwallet.api.PaymentRecord
|
||||||
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
||||||
import com.bitcointxoko.gudariwallet.ui.DetailState
|
import com.bitcointxoko.gudariwallet.ui.DetailState
|
||||||
|
import com.bitcointxoko.gudariwallet.ui.common.AmountWithFiatColumn
|
||||||
|
import com.bitcointxoko.gudariwallet.ui.common.CopyIconButton
|
||||||
|
import com.bitcointxoko.gudariwallet.ui.common.DetailRow
|
||||||
|
import com.bitcointxoko.gudariwallet.ui.common.DetailSection
|
||||||
|
import com.bitcointxoko.gudariwallet.ui.common.StatusChip
|
||||||
import com.bitcointxoko.gudariwallet.util.feePpm
|
import com.bitcointxoko.gudariwallet.util.feePpm
|
||||||
import com.bitcointxoko.gudariwallet.util.formatFiatForSats
|
import com.bitcointxoko.gudariwallet.util.formatFiatForSats
|
||||||
import com.bitcointxoko.gudariwallet.util.formatTimestamp
|
import com.bitcointxoko.gudariwallet.util.formatTimestamp
|
||||||
@@ -297,23 +302,7 @@ private fun PaymentDetailContent(
|
|||||||
context.startActivity(Intent(Intent.ACTION_VIEW, ambossUrl.toUri()))
|
context.startActivity(Intent(Intent.ACTION_VIEW, ambossUrl.toUri()))
|
||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
IconButton(
|
CopyIconButton(label = "node_id", value = pubkey)
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
clipboard.setClipEntry(
|
|
||||||
ClipEntry(ClipData.newPlainText("node_id", pubkey))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(32.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.ContentCopy,
|
|
||||||
contentDescription = strings.detailCopyNodeId, // ← "Copy node ID"
|
|
||||||
modifier = Modifier.size(14.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.bitcointxoko.gudariwallet.api.PaymentRecord
|
import com.bitcointxoko.gudariwallet.api.PaymentRecord
|
||||||
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
||||||
|
import com.bitcointxoko.gudariwallet.ui.common.AmountWithFiatColumn
|
||||||
import com.bitcointxoko.gudariwallet.ui.theme.semanticColors
|
import com.bitcointxoko.gudariwallet.ui.theme.semanticColors
|
||||||
import com.bitcointxoko.gudariwallet.util.formatFiatForSats
|
import com.bitcointxoko.gudariwallet.util.formatFiatForSats
|
||||||
import com.bitcointxoko.gudariwallet.util.formatTimestamp
|
import com.bitcointxoko.gudariwallet.util.formatTimestamp
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
||||||
import com.bitcointxoko.gudariwallet.i18n.AppStrings
|
import com.bitcointxoko.gudariwallet.i18n.AppStrings
|
||||||
import com.bitcointxoko.gudariwallet.ui.history.DetailRow
|
import com.bitcointxoko.gudariwallet.ui.common.DetailRow
|
||||||
import com.bitcointxoko.gudariwallet.ui.history.DetailSection
|
import com.bitcointxoko.gudariwallet.ui.common.DetailSection
|
||||||
import com.bitcointxoko.gudariwallet.util.formatEpoch
|
import com.bitcointxoko.gudariwallet.util.formatEpoch
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ import com.bitcointxoko.gudariwallet.LocalAppStrings
|
|||||||
import com.bitcointxoko.gudariwallet.ui.DecodedInvoice
|
import com.bitcointxoko.gudariwallet.ui.DecodedInvoice
|
||||||
import com.bitcointxoko.gudariwallet.ui.SendState
|
import com.bitcointxoko.gudariwallet.ui.SendState
|
||||||
import com.bitcointxoko.gudariwallet.ui.WalletViewModel
|
import com.bitcointxoko.gudariwallet.ui.WalletViewModel
|
||||||
import com.bitcointxoko.gudariwallet.ui.components.CopyIconButton
|
import com.bitcointxoko.gudariwallet.ui.common.CopyIconButton
|
||||||
import com.bitcointxoko.gudariwallet.ui.components.DetailRow
|
import com.bitcointxoko.gudariwallet.ui.common.DetailRow
|
||||||
import com.bitcointxoko.gudariwallet.util.BiometricHelper
|
import com.bitcointxoko.gudariwallet.util.BiometricHelper
|
||||||
import com.bitcointxoko.gudariwallet.util.fiatLabel
|
import com.bitcointxoko.gudariwallet.util.fiatLabel
|
||||||
import com.bitcointxoko.gudariwallet.util.payingToLabel
|
import com.bitcointxoko.gudariwallet.util.payingToLabel
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.bitcointxoko.gudariwallet.api.RouteHintHop
|
import com.bitcointxoko.gudariwallet.api.RouteHintHop
|
||||||
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
import com.bitcointxoko.gudariwallet.LocalAppStrings
|
||||||
import com.bitcointxoko.gudariwallet.ui.components.CopyIconButton
|
import com.bitcointxoko.gudariwallet.ui.common.CopyIconButton
|
||||||
import com.bitcointxoko.gudariwallet.util.RiskLevel
|
import com.bitcointxoko.gudariwallet.util.RiskLevel
|
||||||
import com.bitcointxoko.gudariwallet.util.RouteHintRisk
|
import com.bitcointxoko.gudariwallet.util.RouteHintRisk
|
||||||
import com.bitcointxoko.gudariwallet.util.baseFeeLabel
|
import com.bitcointxoko.gudariwallet.util.baseFeeLabel
|
||||||
|
|||||||
Reference in New Issue
Block a user