mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 16:14:40 +00:00
Merge pull request #3365 from vitorpamplona/claude/happy-brown-tlu9nq
Extract AddRecommendationSection and WalletTypeCardContent composables
This commit is contained in:
+45
-36
@@ -130,41 +130,50 @@ private fun WalletTypeCard(
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
symbol = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = description,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Icon(
|
||||
symbol = MaterialSymbols.AutoMirrored.ArrowForward,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
WalletTypeCardContent(icon = icon, title = title, description = description)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletTypeCardContent(
|
||||
icon: MaterialSymbol,
|
||||
title: String,
|
||||
description: String,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
symbol = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = description,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Icon(
|
||||
symbol = MaterialSymbols.AutoMirrored.ArrowForward,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
|
||||
+63
-56
@@ -93,7 +93,6 @@ fun CashuMintRecommendationsScreen(
|
||||
|
||||
val recommendations by viewModel.ownRecommendations.collectAsState()
|
||||
var pendingDelete by remember { mutableStateOf<MintRecommendationEvent?>(null) }
|
||||
var newRecommendationInput by remember { mutableStateOf("") }
|
||||
|
||||
// Kick the one-shot directory backfill so the autocomplete is useful
|
||||
// on first screen open instead of waiting for new relay deliveries.
|
||||
@@ -126,63 +125,11 @@ fun CashuMintRecommendationsScreen(
|
||||
) {
|
||||
item { Spacer(modifier = Modifier.height(8.dp)) }
|
||||
|
||||
// Add-recommendation input + autocomplete. Suggestions come
|
||||
// from LocalCache.mintDirectory (kind:10019 / kind:38000 /
|
||||
// kind:38172 the cache has seen), filtered to drop URLs the
|
||||
// user has already recommended so they can't double-publish
|
||||
// and the same row doesn't show up twice on screen.
|
||||
item {
|
||||
val alreadyRecommended =
|
||||
remember(recommendations) {
|
||||
recommendations
|
||||
.flatMap { it.mintUrls() }
|
||||
.map { it.lowercase().trimEnd('/') }
|
||||
.toSet()
|
||||
}
|
||||
val suggestions by remember(newRecommendationInput, alreadyRecommended) {
|
||||
derivedStateOf {
|
||||
val typed = newRecommendationInput.trim().trimEnd('/').lowercase()
|
||||
// Only react to what the user types — never show
|
||||
// the full directory on an empty field, which
|
||||
// would dump every mint we've ever seen as
|
||||
// unsolicited suggestions.
|
||||
if (typed.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
LocalCache.mintDirectory
|
||||
.suggest(typed, limit = 6)
|
||||
.filter { it != typed && it !in alreadyRecommended }
|
||||
}
|
||||
}
|
||||
}
|
||||
AddRecommendationRow(
|
||||
input = newRecommendationInput,
|
||||
onInputChange = { newRecommendationInput = it },
|
||||
canAdd =
|
||||
newRecommendationInput.isNotBlank() &&
|
||||
newRecommendationInput.trim().trimEnd('/').lowercase() !in alreadyRecommended,
|
||||
onAdd = {
|
||||
val trimmed = newRecommendationInput.trim().trimEnd('/')
|
||||
if (trimmed.isNotEmpty()) {
|
||||
viewModel.recommendMint(trimmed)
|
||||
newRecommendationInput = ""
|
||||
}
|
||||
},
|
||||
AddRecommendationSection(
|
||||
recommendations = recommendations,
|
||||
onRecommendMint = { viewModel.recommendMint(it) },
|
||||
)
|
||||
if (suggestions.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
RecommendationSuggestionList(
|
||||
suggestions = suggestions,
|
||||
onPick = { url ->
|
||||
// One-tap recommend: publish straight from
|
||||
// the suggestion and clear the field so the
|
||||
// user can chain multiple adds without
|
||||
// re-tapping the text input.
|
||||
viewModel.recommendMint(url)
|
||||
newRecommendationInput = ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (recommendations.isEmpty()) {
|
||||
@@ -227,6 +174,66 @@ fun CashuMintRecommendationsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddRecommendationSection(
|
||||
recommendations: List<MintRecommendationEvent>,
|
||||
onRecommendMint: (String) -> Unit,
|
||||
) {
|
||||
var newRecommendationInput by remember { mutableStateOf("") }
|
||||
|
||||
val alreadyRecommended =
|
||||
remember(recommendations) {
|
||||
recommendations
|
||||
.flatMap { it.mintUrls() }
|
||||
.map { it.lowercase().trimEnd('/') }
|
||||
.toSet()
|
||||
}
|
||||
val suggestions by remember(newRecommendationInput, alreadyRecommended) {
|
||||
derivedStateOf {
|
||||
val typed = newRecommendationInput.trim().trimEnd('/').lowercase()
|
||||
// Only react to what the user types — never show
|
||||
// the full directory on an empty field, which
|
||||
// would dump every mint we've ever seen as
|
||||
// unsolicited suggestions.
|
||||
if (typed.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
LocalCache.mintDirectory
|
||||
.suggest(typed, limit = 6)
|
||||
.filter { it != typed && it !in alreadyRecommended }
|
||||
}
|
||||
}
|
||||
}
|
||||
AddRecommendationRow(
|
||||
input = newRecommendationInput,
|
||||
onInputChange = { newRecommendationInput = it },
|
||||
canAdd =
|
||||
newRecommendationInput.isNotBlank() &&
|
||||
newRecommendationInput.trim().trimEnd('/').lowercase() !in alreadyRecommended,
|
||||
onAdd = {
|
||||
val trimmed = newRecommendationInput.trim().trimEnd('/')
|
||||
if (trimmed.isNotEmpty()) {
|
||||
onRecommendMint(trimmed)
|
||||
newRecommendationInput = ""
|
||||
}
|
||||
},
|
||||
)
|
||||
if (suggestions.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
RecommendationSuggestionList(
|
||||
suggestions = suggestions,
|
||||
onPick = { url ->
|
||||
// One-tap recommend: publish straight from
|
||||
// the suggestion and clear the field so the
|
||||
// user can chain multiple adds without
|
||||
// re-tapping the text input.
|
||||
onRecommendMint(url)
|
||||
newRecommendationInput = ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddRecommendationRow(
|
||||
input: String,
|
||||
|
||||
+27
-20
@@ -172,30 +172,37 @@ private fun HeaderRow(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
BitcoinChip(orange)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Text(
|
||||
text = "Bitcoin",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
PublicChip()
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Onchain · Taproot",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
HeaderRowTitle(modifier = Modifier.weight(1f), orange = orange)
|
||||
BalanceBlock(state = balanceState, sats = balanceSats, orange = orange)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HeaderRowTitle(
|
||||
modifier: Modifier = Modifier,
|
||||
orange: Color,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
BitcoinChip(orange)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Text(
|
||||
text = "Bitcoin",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
PublicChip()
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Onchain · Taproot",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PublicChip() {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Reference in New Issue
Block a user