diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddWalletScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddWalletScreen.kt index 2c33e6523f..60f33b14dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddWalletScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddWalletScreen.kt @@ -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)) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuMintRecommendationsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuMintRecommendationsScreen.kt index c7b75093a5..19efa32152 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuMintRecommendationsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuMintRecommendationsScreen.kt @@ -93,7 +93,6 @@ fun CashuMintRecommendationsScreen( val recommendations by viewModel.ownRecommendations.collectAsState() var pendingDelete by remember { mutableStateOf(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, + 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, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt index bb564f030f..203edc5f97 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt @@ -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) }