Merge pull request #1784 from vitorpamplona/claude/improve-zap-settings-ui-D36RD
feat: improve Zap Settings UI with sections, chips, and animations
This commit is contained in:
+341
-169
@@ -31,6 +31,19 @@ import androidx.activity.result.ActivityResult
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.biometric.BiometricManager
|
import androidx.biometric.BiometricManager
|
||||||
import androidx.biometric.BiometricPrompt
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.animation.core.Spring
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
@@ -42,12 +55,18 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.ExpandLess
|
||||||
|
import androidx.compose.material.icons.filled.ExpandMore
|
||||||
import androidx.compose.material.icons.outlined.Add
|
import androidx.compose.material.icons.outlined.Add
|
||||||
import androidx.compose.material.icons.outlined.ContentPaste
|
import androidx.compose.material.icons.outlined.ContentPaste
|
||||||
|
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
||||||
import androidx.compose.material.icons.outlined.Visibility
|
import androidx.compose.material.icons.outlined.Visibility
|
||||||
import androidx.compose.material.icons.outlined.VisibilityOff
|
import androidx.compose.material.icons.outlined.VisibilityOff
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
@@ -55,7 +74,10 @@ import androidx.compose.material3.ButtonDefaults
|
|||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.InputChip
|
||||||
|
import androidx.compose.material3.InputChipDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -75,7 +97,6 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
|
|||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.components.TextSpinner
|
import com.vitorpamplona.amethyst.ui.components.TextSpinner
|
||||||
@@ -88,6 +109,9 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.SettingsCategoryFirstModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.SettingsCategorySpacingModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||||
@@ -103,7 +127,6 @@ fun UpdateZapAmountContent(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
|
|
||||||
val zapTypes =
|
val zapTypes =
|
||||||
@@ -155,46 +178,82 @@ fun UpdateZapAmountContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var qrScanning by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Expand manual config automatically when a wallet connection exists
|
||||||
|
var showManualConfig by remember { mutableStateOf(postViewModel.walletConnectPubkey.text.isNotBlank()) }
|
||||||
|
LaunchedEffect(postViewModel.walletConnectPubkey.text) {
|
||||||
|
if (postViewModel.walletConnectPubkey.text.isNotBlank()) {
|
||||||
|
showManualConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(10.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.imePadding()
|
.imePadding()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
|
// ── Section 1: Quick Zap Amounts ──────────────────────────────────────
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.quick_zap_amounts),
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
modifier = SettingsCategoryFirstModifier,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.quick_zap_amounts_explainer),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
modifier = Modifier.padding(bottom = 12.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Amount chips — animateContentSize gives a smooth expand/collapse when
|
||||||
|
// chips are added or removed
|
||||||
FlowRow(
|
FlowRow(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier =
|
||||||
horizontalArrangement = Arrangement.Center,
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.animateContentSize(animationSpec = spring(stiffness = Spring.StiffnessMediumLow)),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
) {
|
) {
|
||||||
postViewModel.amountSet.forEach { amountInSats ->
|
postViewModel.amountSet.forEach { amountInSats ->
|
||||||
Button(
|
InputChip(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp),
|
selected = false,
|
||||||
shape = ButtonBorder,
|
|
||||||
colors =
|
|
||||||
ButtonDefaults.buttonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
onClick = { postViewModel.removeAmount(amountInSats) },
|
onClick = { postViewModel.removeAmount(amountInSats) },
|
||||||
) {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
"⚡ ${
|
text = "⚡ ${showAmount(amountInSats.toBigDecimal().setScale(1))}",
|
||||||
showAmount(
|
)
|
||||||
amountInSats.toBigDecimal().setScale(1),
|
},
|
||||||
)
|
trailingIcon = {
|
||||||
} ✖",
|
Icon(
|
||||||
color = Color.White,
|
imageVector = Icons.Filled.Close,
|
||||||
textAlign = TextAlign.Center,
|
contentDescription = stringRes(R.string.remove),
|
||||||
)
|
modifier = Modifier.size(InputChipDefaults.AvatarSize),
|
||||||
}
|
)
|
||||||
|
},
|
||||||
|
colors =
|
||||||
|
InputChipDefaults.inputChipColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f),
|
||||||
|
labelColor = MaterialTheme.colorScheme.primary,
|
||||||
|
trailingIconColor = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
// Add new amount
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
label = { Text(text = stringRes(R.string.new_amount_in_sats)) },
|
label = { Text(text = stringRes(R.string.new_amount_in_sats)) },
|
||||||
@@ -212,7 +271,7 @@ fun UpdateZapAmountContent(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.padding(end = 10.dp).weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
@@ -223,67 +282,159 @@ fun UpdateZapAmountContent(
|
|||||||
containerColor = MaterialTheme.colorScheme.primary,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Add,
|
||||||
|
contentDescription = stringRes(R.string.add),
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Text(text = stringRes(R.string.add), color = Color.White)
|
Text(text = stringRes(R.string.add), color = Color.White)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
// ── Section 2: Zap Privacy ────────────────────────────────────────────
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
TextSpinner(
|
|
||||||
label = stringRes(id = R.string.zap_type_explainer),
|
|
||||||
placeholder = zapTypes.firstOrNull { it.first == accountViewModel.defaultZapType() }?.second ?: zapTypes.firstOrNull()?.second ?: "",
|
|
||||||
options = zapOptions,
|
|
||||||
onSelect = { postViewModel.selectedZapType = zapTypes[it].first },
|
|
||||||
modifier = Modifier.weight(1f).padding(end = 5.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalDivider(
|
Text(
|
||||||
modifier = Modifier.padding(vertical = 10.dp),
|
text = stringRes(R.string.zap_privacy_section),
|
||||||
thickness = DividerThickness,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
modifier = SettingsCategorySpacingModifier,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.zap_type_section_explainer),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
var qrScanning by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
TextSpinner(
|
||||||
stringRes(id = R.string.wallet_connect_service),
|
label = stringRes(id = R.string.zap_type_explainer),
|
||||||
Modifier.weight(1f),
|
placeholder =
|
||||||
|
zapTypes
|
||||||
|
.firstOrNull { it.first == accountViewModel.defaultZapType() }
|
||||||
|
?.second
|
||||||
|
?: zapTypes.firstOrNull()?.second
|
||||||
|
?: "",
|
||||||
|
options = zapOptions,
|
||||||
|
onSelect = { postViewModel.selectedZapType = zapTypes[it].first },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
IconButton(
|
// ── Section 3: Nostr Wallet Connect ───────────────────────────────────
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = 16.dp),
|
||||||
|
thickness = DividerThickness,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Section header + connection status indicator
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = RowColSpacing,
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.wallet_connect_service),
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.wallet_connect_service_explainer),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
modifier = Modifier.padding(top = 2.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animated connection status badge
|
||||||
|
val isConnected = postViewModel.walletConnectPubkey.text.isNotBlank()
|
||||||
|
val statusColor by animateColorAsState(
|
||||||
|
targetValue = if (isConnected) Color(0xFF4CAF50) else MaterialTheme.colorScheme.placeholderText,
|
||||||
|
animationSpec = tween(durationMillis = 400),
|
||||||
|
label = "nwc_status_color",
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = isConnected,
|
||||||
|
transitionSpec = { fadeIn(tween(300)) togetherWith fadeOut(tween(300)) },
|
||||||
|
label = "nwc_status_badge",
|
||||||
|
) { connected ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (connected) Icons.Filled.CheckCircle else Icons.Outlined.RadioButtonUnchecked,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = statusColor,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
if (connected) {
|
||||||
|
stringRes(R.string.wallet_connect_status_connected)
|
||||||
|
} else {
|
||||||
|
stringRes(R.string.wallet_connect_status_not_connected)
|
||||||
|
},
|
||||||
|
color = statusColor,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect action buttons
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
// Primary "Connect Wallet" button — opens the NWC app deep link
|
||||||
|
OutlinedButton(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
shape = ButtonBorder,
|
||||||
onClick = {
|
onClick = {
|
||||||
onClose()
|
onClose()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uri.openUri("nostrnwc://connect?appname=Amethyst&appicon=https%3A%2F%2Fraw.githubusercontent.com%2Fvitorpamplona%2Famethyst%2Frefs%2Fheads%2Fmain%2Ficon.png&callback=amethyst%2Bwalletconnect%3A%2F%2Fdlnwc")
|
uri.openUri(
|
||||||
|
"nostrnwc://connect?appname=Amethyst&appicon=https%3A%2F%2Fraw.githubusercontent.com%2Fvitorpamplona%2Famethyst%2Frefs%2Fheads%2Fmain%2Ficon.png&callback=amethyst%2Bwalletconnect%3A%2F%2Fdlnwc",
|
||||||
|
)
|
||||||
} catch (_: IllegalArgumentException) {
|
} catch (_: IllegalArgumentException) {
|
||||||
accountViewModel.toastManager.toast(R.string.couldnt_find_nwc_wallets, R.string.couldnt_find_nwc_wallets_description)
|
accountViewModel.toastManager.toast(
|
||||||
|
R.string.couldnt_find_nwc_wallets,
|
||||||
|
R.string.couldnt_find_nwc_wallets_description,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Add,
|
imageVector = Icons.Outlined.Add,
|
||||||
contentDescription = stringRes(id = R.string.connect_to_new_nwc_wallet),
|
contentDescription = null,
|
||||||
modifier = Size24Modifier,
|
modifier = Modifier.size(18.dp),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(text = stringRes(R.string.wallet_connect_connect_app))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Paste from clipboard
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
val uri = clipboardManager.getText()?.text
|
val clipText = clipboardManager.getText()?.text
|
||||||
try {
|
try {
|
||||||
uri?.let {
|
clipText?.let { postViewModel.copyFromClipboard(it) }
|
||||||
postViewModel.copyFromClipboard(it)
|
|
||||||
}
|
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
accountViewModel.toastManager.toast(R.string.invalid_nip47_uri_title, R.string.invalid_nip47_uri_description, uri ?: "")
|
accountViewModel.toastManager.toast(
|
||||||
|
R.string.invalid_nip47_uri_title,
|
||||||
|
R.string.invalid_nip47_uri_description,
|
||||||
|
clipText ?: "",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@@ -295,6 +446,7 @@ fun UpdateZapAmountContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QR code scanner
|
||||||
IconButton(onClick = { qrScanning = true }) {
|
IconButton(onClick = { qrScanning = true }) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterRes(R.drawable.ic_qrcode, 3),
|
painter = painterRes(R.drawable.ic_qrcode, 3),
|
||||||
@@ -305,18 +457,6 @@ fun UpdateZapAmountContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 5.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringRes(id = R.string.wallet_connect_service_explainer),
|
|
||||||
Modifier.weight(1f),
|
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
|
||||||
fontSize = Font14SP,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qrScanning) {
|
if (qrScanning) {
|
||||||
SimpleQrCodeScanner {
|
SimpleQrCodeScanner {
|
||||||
qrScanning = false
|
qrScanning = false
|
||||||
@@ -340,121 +480,153 @@ fun UpdateZapAmountContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expandable manual configuration section
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { showManualConfig = !showManualConfig }
|
||||||
|
.padding(vertical = 10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
Text(
|
||||||
label = { Text(text = stringRes(R.string.wallet_connect_service_pubkey)) },
|
text = stringRes(R.string.wallet_connect_manual_config),
|
||||||
value = postViewModel.walletConnectPubkey,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
onValueChange = { postViewModel.walletConnectPubkey = it },
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
keyboardOptions =
|
|
||||||
KeyboardOptions.Default.copy(
|
|
||||||
capitalization = KeyboardCapitalization.None,
|
|
||||||
),
|
|
||||||
placeholder = {
|
|
||||||
Text(
|
|
||||||
text = "npub, hex",
|
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
singleLine = true,
|
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
fontSize = Font14SP,
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = if (showManualConfig) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
AnimatedVisibility(
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
visible = showManualConfig,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
enter = expandVertically(animationSpec = spring(stiffness = Spring.StiffnessMediumLow)) + fadeIn(),
|
||||||
|
exit = shrinkVertically(animationSpec = spring(stiffness = Spring.StiffnessMediumLow)) + fadeOut(),
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
Column {
|
||||||
label = { Text(text = stringRes(R.string.wallet_connect_service_relay)) },
|
Row(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
||||||
value = postViewModel.walletConnectRelay,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
onValueChange = { postViewModel.walletConnectRelay = it },
|
) {
|
||||||
placeholder = {
|
OutlinedTextField(
|
||||||
Text(
|
label = { Text(text = stringRes(R.string.wallet_connect_service_pubkey)) },
|
||||||
text = "wss://relay.server.com",
|
value = postViewModel.walletConnectPubkey,
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
onValueChange = { postViewModel.walletConnectPubkey = it },
|
||||||
maxLines = 1,
|
keyboardOptions =
|
||||||
|
KeyboardOptions.Default.copy(
|
||||||
|
capitalization = KeyboardCapitalization.None,
|
||||||
|
),
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "npub, hex",
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
},
|
|
||||||
singleLine = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var showPassword by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val keyguardLauncher =
|
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
|
||||||
showPassword = true
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val authTitle = stringRes(id = R.string.wallet_connect_service_show_secret)
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
||||||
Row(
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
) {
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
OutlinedTextField(
|
||||||
) {
|
label = { Text(text = stringRes(R.string.wallet_connect_service_relay)) },
|
||||||
OutlinedTextField(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
label = { Text(text = stringRes(R.string.wallet_connect_service_secret)) },
|
value = postViewModel.walletConnectRelay,
|
||||||
modifier = Modifier.weight(1f),
|
onValueChange = { postViewModel.walletConnectRelay = it },
|
||||||
value = postViewModel.walletConnectSecret,
|
placeholder = {
|
||||||
onValueChange = { postViewModel.walletConnectSecret = it },
|
Text(
|
||||||
keyboardOptions =
|
text = "wss://relay.server.com",
|
||||||
KeyboardOptions(
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
autoCorrectEnabled = false,
|
maxLines = 1,
|
||||||
keyboardType = KeyboardType.Password,
|
)
|
||||||
imeAction = ImeAction.Go,
|
},
|
||||||
),
|
singleLine = true,
|
||||||
placeholder = {
|
|
||||||
Text(
|
|
||||||
text = stringRes(R.string.wallet_connect_service_secret_placeholder),
|
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
trailingIcon = {
|
|
||||||
IconButton(
|
var showPassword by remember { mutableStateOf(false) }
|
||||||
onClick = {
|
|
||||||
if (!showPassword) {
|
val secretContext = LocalContext.current
|
||||||
authenticate(
|
|
||||||
title = authTitle,
|
val keyguardLauncher =
|
||||||
context = context,
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||||
keyguardLauncher = keyguardLauncher,
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
onApproved = { showPassword = true },
|
showPassword = true
|
||||||
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val authTitle = stringRes(id = R.string.wallet_connect_service_show_secret)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
label = { Text(text = stringRes(R.string.wallet_connect_service_secret)) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = postViewModel.walletConnectSecret,
|
||||||
|
onValueChange = { postViewModel.walletConnectSecret = it },
|
||||||
|
keyboardOptions =
|
||||||
|
KeyboardOptions(
|
||||||
|
autoCorrectEnabled = false,
|
||||||
|
keyboardType = KeyboardType.Password,
|
||||||
|
imeAction = ImeAction.Go,
|
||||||
|
),
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.wallet_connect_service_secret_placeholder),
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
if (!showPassword) {
|
||||||
|
authenticate(
|
||||||
|
title = authTitle,
|
||||||
|
context = secretContext,
|
||||||
|
keyguardLauncher = keyguardLauncher,
|
||||||
|
onApproved = { showPassword = true },
|
||||||
|
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
showPassword = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector =
|
||||||
|
if (showPassword) {
|
||||||
|
Icons.Outlined.VisibilityOff
|
||||||
|
} else {
|
||||||
|
Icons.Outlined.Visibility
|
||||||
|
},
|
||||||
|
contentDescription =
|
||||||
|
if (showPassword) {
|
||||||
|
stringRes(R.string.show_password)
|
||||||
|
} else {
|
||||||
|
stringRes(R.string.hide_password)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
showPassword = false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
visualTransformation =
|
||||||
Icon(
|
if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
imageVector =
|
)
|
||||||
if (showPassword) {
|
}
|
||||||
Icons.Outlined.VisibilityOff
|
}
|
||||||
} else {
|
|
||||||
Icons.Outlined.Visibility
|
|
||||||
},
|
|
||||||
contentDescription =
|
|
||||||
if (showPassword) {
|
|
||||||
stringRes(R.string.show_password)
|
|
||||||
} else {
|
|
||||||
stringRes(
|
|
||||||
R.string.hide_password,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
visualTransformation =
|
|
||||||
if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -454,6 +454,14 @@
|
|||||||
<string name="wallet_connect_service_secret">Wallet Connect Secret</string>
|
<string name="wallet_connect_service_secret">Wallet Connect Secret</string>
|
||||||
<string name="wallet_connect_service_show_secret">Show secret key</string>
|
<string name="wallet_connect_service_show_secret">Show secret key</string>
|
||||||
<string name="wallet_connect_service_secret_placeholder">nsec / hex private key</string>
|
<string name="wallet_connect_service_secret_placeholder">nsec / hex private key</string>
|
||||||
|
<string name="wallet_connect_status_connected">Connected</string>
|
||||||
|
<string name="wallet_connect_status_not_connected">Not connected</string>
|
||||||
|
<string name="wallet_connect_manual_config">Advanced: enter connection details manually</string>
|
||||||
|
<string name="quick_zap_amounts">Quick Zap Amounts</string>
|
||||||
|
<string name="quick_zap_amounts_explainer">Shown when long-pressing the zap button. Tap an amount to remove it.</string>
|
||||||
|
<string name="zap_privacy_section">Zap Privacy</string>
|
||||||
|
<string name="zap_type_section_explainer">Controls how your identity is shown when you send a zap.</string>
|
||||||
|
<string name="wallet_connect_connect_app">Connect Wallet</string>
|
||||||
|
|
||||||
<string name="pledge_amount_in_sats">Pledge Amount in Sats</string>
|
<string name="pledge_amount_in_sats">Pledge Amount in Sats</string>
|
||||||
<string name="post_poll">Post Poll</string>
|
<string name="post_poll">Post Poll</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user