From dd9f4b32d9bf7b0e884b67e57a570e2323d7e6bf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 12:06:25 +0000 Subject: [PATCH 1/3] Replace remember-choice toggle row with a modal bottom sheet The old inline horizontal toggle row could only fit five short labels (Never / 1m / 5m / 10m / Always). Replace it with a trigger row that shows the current selection and opens a Material 3 ModalBottomSheet listing every option with full labels. Adds three new durations: 1 hour, 1 day, 1 week. Also reuses the same picker in EditPermission.PermissionRow. --- .../nostrsigner/service/AmberUtils.kt | 6 + .../nostrsigner/ui/EditPermission.kt | 117 ++++--------- .../nostrsigner/ui/SettingsScreen.kt | 25 ++- .../ui/components/RememberMyChoice.kt | 164 ++++++++++++------ app/src/main/res/values/strings.xml | 3 - 5 files changed, 168 insertions(+), 147 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt index da5a5239..c6d245cf 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt @@ -94,6 +94,9 @@ object AmberUtils { RememberType.ONE_MINUTE -> TimeUtils.oneMinuteFromNow() RememberType.FIVE_MINUTES -> TimeUtils.now() + TimeUtils.FIVE_MINUTES RememberType.TEN_MINUTES -> TimeUtils.now() + TimeUtils.FIFTEEN_MINUTES + RememberType.ONE_HOUR -> TimeUtils.now() + 3600 + RememberType.ONE_DAY -> TimeUtils.now() + 86400 + RememberType.ONE_WEEK -> TimeUtils.now() + 604800 RememberType.NEVER -> 0L } @@ -240,6 +243,9 @@ object AmberUtils { RememberType.ONE_MINUTE -> TimeUtils.oneMinuteFromNow() RememberType.FIVE_MINUTES -> TimeUtils.now() + TimeUtils.FIVE_MINUTES RememberType.TEN_MINUTES -> TimeUtils.now() + TimeUtils.FIFTEEN_MINUTES + RememberType.ONE_HOUR -> TimeUtils.now() + 3600 + RememberType.ONE_DAY -> TimeUtils.now() + 86400 + RememberType.ONE_WEEK -> TimeUtils.now() + 604800 else -> 0L } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index fb1d1f68..622d0afa 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -53,6 +53,7 @@ import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.ui.actions.RemoveAllPermissionsDialog import com.greenart7c3.nostrsigner.ui.components.AmberButton import com.greenart7c3.nostrsigner.ui.components.AmberToggles +import com.greenart7c3.nostrsigner.ui.components.RememberTypePicker import com.greenart7c3.nostrsigner.ui.components.ToggleOption import com.greenart7c3.nostrsigner.ui.components.TrustScoreBadge import com.greenart7c3.nostrsigner.ui.theme.orange @@ -240,29 +241,15 @@ fun EditPermission( } } -fun rememberTypeIndexToRememberType(rememberTypeIndex: Int): RememberType = when (rememberTypeIndex) { - 0 -> RememberType.ALWAYS - 1 -> RememberType.ONE_MINUTE - 2 -> RememberType.FIVE_MINUTES - 3 -> RememberType.TEN_MINUTES - else -> RememberType.NEVER -} - -fun rememberTypeToIndex(rememberType: RememberType): Int = when (rememberType) { - RememberType.ALWAYS -> 0 - RememberType.ONE_MINUTE -> 1 - RememberType.FIVE_MINUTES -> 2 - RememberType.TEN_MINUTES -> 3 - else -> 0 -} - -fun onSetPermission(optionIndex: Int, rememberTypeIndex: Int, permission: ApplicationPermissionsEntity, onToggle: (ApplicationPermissionsEntity) -> Unit) { - val rememberType = rememberTypeIndexToRememberType(rememberTypeIndex) +fun onSetPermission(optionIndex: Int, rememberType: RememberType, permission: ApplicationPermissionsEntity, onToggle: (ApplicationPermissionsEntity) -> Unit) { val time = when (rememberType) { RememberType.ALWAYS -> Long.MAX_VALUE / 1000 RememberType.ONE_MINUTE -> TimeUtils.oneMinuteFromNow() RememberType.FIVE_MINUTES -> TimeUtils.now() + TimeUtils.FIVE_MINUTES RememberType.TEN_MINUTES -> TimeUtils.now() + TimeUtils.FIFTEEN_MINUTES + RememberType.ONE_HOUR -> TimeUtils.now() + 3600 + RememberType.ONE_DAY -> TimeUtils.now() + 86400 + RememberType.ONE_WEEK -> TimeUtils.now() + 604800 RememberType.NEVER -> 0L } val isAcceptable = optionIndex == 0 || optionIndex == 2 @@ -284,7 +271,7 @@ fun onSetPermission(optionIndex: Int, rememberTypeIndex: Int, permission: Applic } else { 0L }, - rememberType = rememberTypeIndexToRememberType(rememberTypeIndex).screenCode, + rememberType = rememberType.screenCode, ), ) } @@ -322,8 +309,12 @@ fun PermissionRow( mutableIntStateOf(2) } } - var rememberTypeIndex by remember { - mutableIntStateOf(rememberTypeToIndex(parseRememberType(permission.rememberType))) + var rememberType by remember { + mutableStateOf( + parseRememberType(permission.rememberType).let { + if (it == RememberType.NEVER) RememberType.ALWAYS else it + }, + ) } Column( @@ -375,7 +366,7 @@ fun PermissionRow( onSetPermission( optionIndex, - rememberTypeIndex, + rememberType, permission, onToggle, ) @@ -390,7 +381,7 @@ fun PermissionRow( onSetPermission( optionIndex, - rememberTypeIndex, + rememberType, permission, onToggle, ) @@ -405,7 +396,7 @@ fun PermissionRow( onSetPermission( optionIndex, - rememberTypeIndex, + rememberType, permission, onToggle, ) @@ -414,66 +405,24 @@ fun PermissionRow( } if (optionIndex != 2) { - AmberToggles( - selectedIndex = rememberTypeIndex, - count = 4, - content = { - ToggleOption( - text = "Always", - isSelected = rememberTypeIndex == 0, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - rememberTypeIndex = 0 - - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, - ) - ToggleOption( - text = "1m", - isSelected = rememberTypeIndex == 1, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - rememberTypeIndex = 1 - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, - ) - ToggleOption( - text = "5m", - isSelected = rememberTypeIndex == 2, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - rememberTypeIndex = 2 - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, - ) - ToggleOption( - text = "10m", - isSelected = rememberTypeIndex == 3, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - rememberTypeIndex = 3 - onSetPermission( - optionIndex, - rememberTypeIndex, - permission, - onToggle, - ) - }, + RememberTypePicker( + selected = rememberType, + options = listOf( + RememberType.ONE_MINUTE, + RememberType.FIVE_MINUTES, + RememberType.TEN_MINUTES, + RememberType.ONE_HOUR, + RememberType.ONE_DAY, + RememberType.ONE_WEEK, + RememberType.ALWAYS, + ), + onSelected = { newType -> + rememberType = newType + onSetPermission( + optionIndex, + rememberType, + permission, + onToggle, ) }, ) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt index 1ce39f93..12fda1ad 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SettingsScreen.kt @@ -480,13 +480,30 @@ enum class RememberType(val screenCode: Int, val resourceId: Int) { FIVE_MINUTES(2, R.string.five_minutes), TEN_MINUTES(3, R.string.ten_minutes), ALWAYS(4, R.string.always), + ONE_HOUR(5, R.string.one_hour), + ONE_DAY(6, R.string.one_day), + ONE_WEEK(7, R.string.one_week), } +val rememberTypeDisplayOrder: List = listOf( + RememberType.NEVER, + RememberType.ONE_MINUTE, + RememberType.FIVE_MINUTES, + RememberType.TEN_MINUTES, + RememberType.ONE_HOUR, + RememberType.ONE_DAY, + RememberType.ONE_WEEK, + RememberType.ALWAYS, +) + fun parseRememberType(screenCode: Int): RememberType = when (screenCode) { - 0 -> RememberType.NEVER - 1 -> RememberType.ONE_MINUTE - 2 -> RememberType.FIVE_MINUTES - 3 -> RememberType.TEN_MINUTES + RememberType.NEVER.screenCode -> RememberType.NEVER + RememberType.ONE_MINUTE.screenCode -> RememberType.ONE_MINUTE + RememberType.FIVE_MINUTES.screenCode -> RememberType.FIVE_MINUTES + RememberType.TEN_MINUTES.screenCode -> RememberType.TEN_MINUTES + RememberType.ONE_HOUR.screenCode -> RememberType.ONE_HOUR + RememberType.ONE_DAY.screenCode -> RememberType.ONE_DAY + RememberType.ONE_WEEK.screenCode -> RememberType.ONE_WEEK else -> RememberType.ALWAYS } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt index 3eb13795..f8a0a64d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt @@ -2,19 +2,35 @@ package com.greenart7c3.nostrsigner.ui.components import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +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.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material3.BottomSheetDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource @@ -23,6 +39,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.ui.RememberType +import com.greenart7c3.nostrsigner.ui.rememberTypeDisplayOrder +import kotlinx.coroutines.launch @Composable fun LabeledBorderBox( @@ -57,6 +75,85 @@ fun LabeledBorderBox( } } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun RememberTypePicker( + selected: RememberType, + options: List, + onSelected: (RememberType) -> Unit, + modifier: Modifier = Modifier, +) { + var showSheet by remember { mutableStateOf(false) } + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val scope = rememberCoroutineScope() + + Row( + modifier = modifier + .fillMaxWidth() + .clickable { showSheet = true } + .padding(horizontal = 12.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(selected.resourceId), + modifier = Modifier.weight(1f), + fontSize = 16.sp, + ) + Icon( + imageVector = Icons.Filled.ExpandMore, + contentDescription = null, + ) + } + + if (showSheet) { + ModalBottomSheet( + onDismissRequest = { showSheet = false }, + sheetState = sheetState, + dragHandle = { BottomSheetDefaults.DragHandle() }, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + ) { + options.forEach { option -> + val isSelected = option == selected + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onSelected(option) + scope.launch { sheetState.hide() }.invokeOnCompletion { + if (!sheetState.isVisible) showSheet = false + } + } + .padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(option.resourceId), + fontSize = 16.sp, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + modifier = Modifier.weight(1f), + ) + if (isSelected) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + } else { + Spacer(Modifier.size(24.dp)) + } + } + } + Spacer(Modifier.height(8.dp)) + } + } + } +} + @Composable fun RememberMyChoice( shouldRunAcceptOrReject: Boolean?, @@ -66,15 +163,13 @@ fun RememberMyChoice( onReject: (RememberType) -> Unit, onChanged: (RememberType) -> Unit, ) { - var index by remember { - mutableIntStateOf(0) - } + var selected by remember { mutableStateOf(RememberType.NEVER) } if (shouldRunAcceptOrReject != null) { LaunchedEffect(Unit) { if (shouldRunAcceptOrReject) { - onAccept(RememberType.entries[index]) + onAccept(selected) } else { - onReject(RememberType.entries[index]) + onReject(selected) } } } @@ -82,55 +177,12 @@ fun RememberMyChoice( LabeledBorderBox( label = stringResource(R.string.automatically_sign_this_for), ) { - AmberToggles( - selectedIndex = index, - count = 5, - content = { - ToggleOption( - modifier = Modifier.width(55.dp), - text = stringResource(RememberType.NEVER.resourceId), - isSelected = RememberType.NEVER == RememberType.entries[index], - onClick = { - index = RememberType.NEVER.screenCode - onChanged(RememberType.NEVER) - }, - ) - ToggleOption( - modifier = Modifier.width(55.dp), - text = stringResource(R.string.one_minute_short), - isSelected = RememberType.ONE_MINUTE == RememberType.entries[index], - onClick = { - index = RememberType.ONE_MINUTE.screenCode - onChanged(RememberType.ONE_MINUTE) - }, - ) - ToggleOption( - modifier = Modifier.width(55.dp), - text = stringResource(R.string.five_minutes_short), - isSelected = RememberType.FIVE_MINUTES == RememberType.entries[index], - onClick = { - index = RememberType.FIVE_MINUTES.screenCode - onChanged(RememberType.FIVE_MINUTES) - }, - ) - ToggleOption( - modifier = Modifier.width(55.dp), - text = stringResource(R.string.ten_minutes_short), - isSelected = RememberType.TEN_MINUTES == RememberType.entries[index], - onClick = { - index = RememberType.TEN_MINUTES.screenCode - onChanged(RememberType.TEN_MINUTES) - }, - ) - ToggleOption( - modifier = Modifier.width(55.dp), - text = stringResource(RememberType.ALWAYS.resourceId), - isSelected = RememberType.ALWAYS == RememberType.entries[index], - onClick = { - index = RememberType.ALWAYS.screenCode - onChanged(RememberType.entries[RememberType.ALWAYS.screenCode]) - }, - ) + RememberTypePicker( + selected = selected, + options = rememberTypeDisplayOrder, + onSelected = { + selected = it + onChanged(it) }, ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 150d6018..35927988 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -226,9 +226,6 @@ One minute Five minutes Ten Minutes - 1m - 5m - 10m Add user to group Remove user from group Edit group metadata From 66e94c3f60ae0393e4cc88d5e6fa1ef907832789 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 12:20:21 +0000 Subject: [PATCH 2/3] Convert all approval-screen option pickers to bottom sheets Extract the bottom-sheet picker from RememberMyChoice into a generic OptionBottomSheetPicker and reuse it for: - Allow / Deny / Ask on each per-app permission row - "For this relay only / For all relays" on relay-auth approvals (BunkerRelayAuthScreen, BunkerMultiEventHomeScreen, IntentMultiEventHomeScreen) - "For this method only / For all methods" on encrypt/decrypt approvals (EncryptDecryptData, 2 sites) --- .../nostrsigner/ui/EditPermission.kt | 82 ++++--------- .../components/BunkerMultiEventHomeScreen.kt | 32 ++--- .../ui/components/BunkerRelayAuthScreen.kt | 29 ++--- .../ui/components/EncryptDecryptData.kt | 56 ++++----- .../components/IntentMultiEventHomeScreen.kt | 32 ++--- .../ui/components/OptionBottomSheetPicker.kt | 113 ++++++++++++++++++ .../ui/components/RememberMyChoice.kt | 100 +--------------- app/src/main/res/values/strings.xml | 1 + 8 files changed, 197 insertions(+), 248 deletions(-) create mode 100644 app/src/main/java/com/greenart7c3/nostrsigner/ui/components/OptionBottomSheetPicker.kt diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index 622d0afa..ecd2c64a 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -10,7 +10,6 @@ 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.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card @@ -52,9 +51,7 @@ import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.ui.actions.RemoveAllPermissionsDialog import com.greenart7c3.nostrsigner.ui.components.AmberButton -import com.greenart7c3.nostrsigner.ui.components.AmberToggles -import com.greenart7c3.nostrsigner.ui.components.RememberTypePicker -import com.greenart7c3.nostrsigner.ui.components.ToggleOption +import com.greenart7c3.nostrsigner.ui.components.OptionBottomSheetPicker import com.greenart7c3.nostrsigner.ui.components.TrustScoreBadge import com.greenart7c3.nostrsigner.ui.theme.orange import com.vitorpamplona.quartz.utils.TimeUtils @@ -298,8 +295,6 @@ fun PermissionRow( localPermission.toLocalizedString(context) } } - val fixedSegmentWidth = 55.dp - var optionIndex by remember { if (permission.acceptUntil > 0) { mutableIntStateOf(0) @@ -353,59 +348,31 @@ fun PermissionRow( ) } - AmberToggles( - count = 3, - selectedIndex = optionIndex, - ) { - ToggleOption( - text = "Allow", - isSelected = optionIndex == 0, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - optionIndex = 0 - - onSetPermission( - optionIndex, - rememberType, - permission, - onToggle, - ) - }, - ) - ToggleOption( - text = "Deny", - isSelected = optionIndex == 1, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - optionIndex = 1 - - onSetPermission( - optionIndex, - rememberType, - permission, - onToggle, - ) - }, - ) - ToggleOption( - text = "Ask", - isSelected = optionIndex == 2, - modifier = Modifier.width(fixedSegmentWidth), - onClick = { - optionIndex = 2 - - onSetPermission( - optionIndex, - rememberType, - permission, - onToggle, - ) - }, - ) - } + OptionBottomSheetPicker( + selected = optionIndex, + options = listOf(0, 1, 2), + onSelected = { newIndex -> + optionIndex = newIndex + onSetPermission( + optionIndex, + rememberType, + permission, + onToggle, + ) + }, + label = { + stringResource( + when (it) { + 0 -> R.string.allow + 1 -> R.string.deny + else -> R.string.ask + }, + ) + }, + ) if (optionIndex != 2) { - RememberTypePicker( + OptionBottomSheetPicker( selected = rememberType, options = listOf( RememberType.ONE_MINUTE, @@ -425,6 +392,7 @@ fun PermissionRow( onToggle, ) }, + label = { stringResource(it.resourceId) }, ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerMultiEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerMultiEventHomeScreen.kt index 4f17a91c..31dc7a9d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerMultiEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerMultiEventHomeScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ButtonDefaults @@ -170,24 +169,19 @@ fun BunkerMultiEventHomeScreen( .fillMaxWidth() .padding(horizontal = 4.dp, vertical = 8.dp), ) { - AmberToggles( - selectedIndex = if (relayAuthScope == RelayAuthScope.SPECIFIC) 0 else 1, - count = 2, - segmentWidth = 120.dp, - ) { - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_this_relay_only), - isSelected = relayAuthScope == RelayAuthScope.SPECIFIC, - onClick = { relayAuthScope = RelayAuthScope.SPECIFIC }, - ) - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_all_relays), - isSelected = relayAuthScope == RelayAuthScope.ALL, - onClick = { relayAuthScope = RelayAuthScope.ALL }, - ) - } + OptionBottomSheetPicker( + selected = relayAuthScope, + options = listOf(RelayAuthScope.SPECIFIC, RelayAuthScope.ALL), + onSelected = { relayAuthScope = it }, + label = { + stringResource( + when (it) { + RelayAuthScope.SPECIFIC -> R.string.for_this_relay_only + RelayAuthScope.ALL -> R.string.for_all_relays + }, + ) + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerRelayAuthScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerRelayAuthScreen.kt index 9b39072c..7209d728 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerRelayAuthScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerRelayAuthScreen.kt @@ -6,12 +6,10 @@ import androidx.compose.foundation.layout.fillMaxSize 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.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -52,7 +50,6 @@ fun BunkerRelayAuthScreen( ) { var rememberType by remember { mutableStateOf(RememberType.NEVER) } var scope by remember { mutableStateOf(defaultScope) } - val scopeIndex by remember(scope) { mutableIntStateOf(if (scope == RelayAuthScope.SPECIFIC) 0 else 1) } if (shouldAcceptOrReject != null) { LaunchedEffect(Unit) { @@ -109,22 +106,16 @@ fun BunkerRelayAuthScreen( .fillMaxWidth() .padding(horizontal = 16.dp), ) { - AmberToggles( - selectedIndex = scopeIndex, - count = 2, - segmentWidth = 120.dp, - content = { - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_this_relay_only), - isSelected = scope == RelayAuthScope.SPECIFIC, - onClick = { scope = RelayAuthScope.SPECIFIC }, - ) - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_all_relays), - isSelected = scope == RelayAuthScope.ALL, - onClick = { scope = RelayAuthScope.ALL }, + OptionBottomSheetPicker( + selected = scope, + options = listOf(RelayAuthScope.SPECIFIC, RelayAuthScope.ALL), + onSelected = { scope = it }, + label = { + stringResource( + when (it) { + RelayAuthScope.SPECIFIC -> R.string.for_this_relay_only + RelayAuthScope.ALL -> R.string.for_all_relays + }, ) }, ) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EncryptDecryptData.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EncryptDecryptData.kt index c411764f..cad44473 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EncryptDecryptData.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EncryptDecryptData.kt @@ -5,14 +5,12 @@ 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.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -55,7 +53,6 @@ fun EncryptDecryptData( mutableStateOf(RememberType.NEVER) } var scope by remember { mutableStateOf(defaultScope) } - val scopeIndex by remember(scope) { mutableIntStateOf(if (scope == DecryptTypeScope.SPECIFIC) 0 else 1) } val showScopeToggle = type != SignerType.DECRYPT_ZAP_EVENT Column( @@ -199,22 +196,16 @@ fun EncryptDecryptData( .fillMaxWidth() .padding(horizontal = 16.dp), ) { - AmberToggles( - selectedIndex = scopeIndex, - count = 2, - segmentWidth = 120.dp, - content = { - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_this_method_only), - isSelected = scope == DecryptTypeScope.SPECIFIC, - onClick = { scope = DecryptTypeScope.SPECIFIC }, - ) - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_all_methods), - isSelected = scope == DecryptTypeScope.ALL, - onClick = { scope = DecryptTypeScope.ALL }, + OptionBottomSheetPicker( + selected = scope, + options = listOf(DecryptTypeScope.SPECIFIC, DecryptTypeScope.ALL), + onSelected = { scope = it }, + label = { + stringResource( + when (it) { + DecryptTypeScope.SPECIFIC -> R.string.for_this_method_only + DecryptTypeScope.ALL -> R.string.for_all_methods + }, ) }, ) @@ -274,7 +265,6 @@ fun BunkerEncryptDecryptData( mutableStateOf(RememberType.NEVER) } var scope by remember { mutableStateOf(defaultScope) } - val scopeIndex by remember(scope) { mutableIntStateOf(if (scope == DecryptTypeScope.SPECIFIC) 0 else 1) } val showScopeToggle = type != SignerType.DECRYPT_ZAP_EVENT Column( @@ -409,22 +399,16 @@ fun BunkerEncryptDecryptData( .fillMaxWidth() .padding(horizontal = 16.dp), ) { - AmberToggles( - selectedIndex = scopeIndex, - count = 2, - segmentWidth = 120.dp, - content = { - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_this_method_only), - isSelected = scope == DecryptTypeScope.SPECIFIC, - onClick = { scope = DecryptTypeScope.SPECIFIC }, - ) - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_all_methods), - isSelected = scope == DecryptTypeScope.ALL, - onClick = { scope = DecryptTypeScope.ALL }, + OptionBottomSheetPicker( + selected = scope, + options = listOf(DecryptTypeScope.SPECIFIC, DecryptTypeScope.ALL), + onSelected = { scope = it }, + label = { + stringResource( + when (it) { + DecryptTypeScope.SPECIFIC -> R.string.for_this_method_only + DecryptTypeScope.ALL -> R.string.for_all_methods + }, ) }, ) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentMultiEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentMultiEventHomeScreen.kt index b0f08a40..516ebfbf 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentMultiEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentMultiEventHomeScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ButtonDefaults @@ -145,24 +144,19 @@ fun IntentMultiEventHomeScreen( .fillMaxWidth() .padding(horizontal = 4.dp, vertical = 8.dp), ) { - AmberToggles( - selectedIndex = if (relayAuthScope == RelayAuthScope.SPECIFIC) 0 else 1, - count = 2, - segmentWidth = 120.dp, - ) { - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_this_relay_only), - isSelected = relayAuthScope == RelayAuthScope.SPECIFIC, - onClick = { relayAuthScope = RelayAuthScope.SPECIFIC }, - ) - ToggleOption( - modifier = Modifier.width(120.dp), - text = stringResource(R.string.for_all_relays), - isSelected = relayAuthScope == RelayAuthScope.ALL, - onClick = { relayAuthScope = RelayAuthScope.ALL }, - ) - } + OptionBottomSheetPicker( + selected = relayAuthScope, + options = listOf(RelayAuthScope.SPECIFIC, RelayAuthScope.ALL), + onSelected = { relayAuthScope = it }, + label = { + stringResource( + when (it) { + RelayAuthScope.SPECIFIC -> R.string.for_this_relay_only + RelayAuthScope.ALL -> R.string.for_all_relays + }, + ) + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/OptionBottomSheetPicker.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/OptionBottomSheetPicker.kt new file mode 100644 index 00000000..219fe151 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/OptionBottomSheetPicker.kt @@ -0,0 +1,113 @@ +package com.greenart7c3.nostrsigner.ui.components + +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material3.BottomSheetDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun OptionBottomSheetPicker( + selected: T, + options: List, + onSelected: (T) -> Unit, + modifier: Modifier = Modifier, + label: @Composable (T) -> String, +) { + var showSheet by remember { mutableStateOf(false) } + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val scope = rememberCoroutineScope() + + Row( + modifier = modifier + .fillMaxWidth() + .clickable { showSheet = true } + .padding(horizontal = 12.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = label(selected), + modifier = Modifier.weight(1f), + fontSize = 16.sp, + ) + Icon( + imageVector = Icons.Filled.ExpandMore, + contentDescription = null, + ) + } + + if (showSheet) { + ModalBottomSheet( + onDismissRequest = { showSheet = false }, + sheetState = sheetState, + dragHandle = { BottomSheetDefaults.DragHandle() }, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + ) { + options.forEach { option -> + val isSelected = option == selected + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onSelected(option) + scope.launch { sheetState.hide() }.invokeOnCompletion { + if (!sheetState.isVisible) showSheet = false + } + } + .padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = label(option), + fontSize = 16.sp, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + modifier = Modifier.weight(1f), + ) + if (isSelected) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + } else { + Spacer(Modifier.size(24.dp)) + } + } + } + Spacer(Modifier.height(8.dp)) + } + } + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt index f8a0a64d..ce90678f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt @@ -2,35 +2,18 @@ package com.greenart7c3.nostrsigner.ui.components import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -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.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.ExpandMore -import androidx.compose.material3.BottomSheetDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource @@ -40,7 +23,6 @@ import androidx.compose.ui.unit.sp import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.ui.RememberType import com.greenart7c3.nostrsigner.ui.rememberTypeDisplayOrder -import kotlinx.coroutines.launch @Composable fun LabeledBorderBox( @@ -75,85 +57,6 @@ fun LabeledBorderBox( } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun RememberTypePicker( - selected: RememberType, - options: List, - onSelected: (RememberType) -> Unit, - modifier: Modifier = Modifier, -) { - var showSheet by remember { mutableStateOf(false) } - val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - val scope = rememberCoroutineScope() - - Row( - modifier = modifier - .fillMaxWidth() - .clickable { showSheet = true } - .padding(horizontal = 12.dp, vertical = 14.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(selected.resourceId), - modifier = Modifier.weight(1f), - fontSize = 16.sp, - ) - Icon( - imageVector = Icons.Filled.ExpandMore, - contentDescription = null, - ) - } - - if (showSheet) { - ModalBottomSheet( - onDismissRequest = { showSheet = false }, - sheetState = sheetState, - dragHandle = { BottomSheetDefaults.DragHandle() }, - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp), - ) { - options.forEach { option -> - val isSelected = option == selected - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - onSelected(option) - scope.launch { sheetState.hide() }.invokeOnCompletion { - if (!sheetState.isVisible) showSheet = false - } - } - .padding(horizontal = 24.dp, vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Text( - text = stringResource(option.resourceId), - fontSize = 16.sp, - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, - modifier = Modifier.weight(1f), - ) - if (isSelected) { - Icon( - imageVector = Icons.Filled.Check, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - } else { - Spacer(Modifier.size(24.dp)) - } - } - } - Spacer(Modifier.height(8.dp)) - } - } - } -} - @Composable fun RememberMyChoice( shouldRunAcceptOrReject: Boolean?, @@ -177,13 +80,14 @@ fun RememberMyChoice( LabeledBorderBox( label = stringResource(R.string.automatically_sign_this_for), ) { - RememberTypePicker( + OptionBottomSheetPicker( selected = selected, options = rememberTypeDisplayOrder, onSelected = { selected = it onChanged(it) }, + label = { stringResource(it.resourceId) }, ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 35927988..22827781 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -427,6 +427,7 @@ Notifications are needed to use Amber as a nsec bunker. Allow Deny + Ask Never Warning %1$s of %2$s events From 5997f4b566aead74699946997541265b25cea50a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 12:24:04 +0000 Subject: [PATCH 3/3] Label the action and duration pickers in EditPermission rows Wrap the Allow/Deny/Ask picker in a LabeledBorderBox labeled "Action", and the duration picker in one labeled "Automatically sign this for", matching the framing used on approval screens. --- .../nostrsigner/ui/EditPermission.kt | 85 +++++++++++-------- app/src/main/res/values/strings.xml | 1 + 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt index ecd2c64a..005bed0e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/EditPermission.kt @@ -51,6 +51,7 @@ import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.ui.actions.RemoveAllPermissionsDialog import com.greenart7c3.nostrsigner.ui.components.AmberButton +import com.greenart7c3.nostrsigner.ui.components.LabeledBorderBox import com.greenart7c3.nostrsigner.ui.components.OptionBottomSheetPicker import com.greenart7c3.nostrsigner.ui.components.TrustScoreBadge import com.greenart7c3.nostrsigner.ui.theme.orange @@ -348,43 +349,15 @@ fun PermissionRow( ) } - OptionBottomSheetPicker( - selected = optionIndex, - options = listOf(0, 1, 2), - onSelected = { newIndex -> - optionIndex = newIndex - onSetPermission( - optionIndex, - rememberType, - permission, - onToggle, - ) - }, - label = { - stringResource( - when (it) { - 0 -> R.string.allow - 1 -> R.string.deny - else -> R.string.ask - }, - ) - }, - ) - - if (optionIndex != 2) { + LabeledBorderBox( + label = stringResource(R.string.action), + modifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp), + ) { OptionBottomSheetPicker( - selected = rememberType, - options = listOf( - RememberType.ONE_MINUTE, - RememberType.FIVE_MINUTES, - RememberType.TEN_MINUTES, - RememberType.ONE_HOUR, - RememberType.ONE_DAY, - RememberType.ONE_WEEK, - RememberType.ALWAYS, - ), - onSelected = { newType -> - rememberType = newType + selected = optionIndex, + options = listOf(0, 1, 2), + onSelected = { newIndex -> + optionIndex = newIndex onSetPermission( optionIndex, rememberType, @@ -392,9 +365,47 @@ fun PermissionRow( onToggle, ) }, - label = { stringResource(it.resourceId) }, + label = { + stringResource( + when (it) { + 0 -> R.string.allow + 1 -> R.string.deny + else -> R.string.ask + }, + ) + }, ) } + + if (optionIndex != 2) { + LabeledBorderBox( + label = stringResource(R.string.automatically_sign_this_for), + modifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp), + ) { + OptionBottomSheetPicker( + selected = rememberType, + options = listOf( + RememberType.ONE_MINUTE, + RememberType.FIVE_MINUTES, + RememberType.TEN_MINUTES, + RememberType.ONE_HOUR, + RememberType.ONE_DAY, + RememberType.ONE_WEEK, + RememberType.ALWAYS, + ), + onSelected = { newType -> + rememberType = newType + onSetPermission( + optionIndex, + rememberType, + permission, + onToggle, + ) + }, + label = { stringResource(it.resourceId) }, + ) + } + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 22827781..019c32a4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -527,6 +527,7 @@ Media starter packs Media follows Automatically sign this for + Action Always Proxy is connected Proxy is not working. Check your proxy settings/application.