From 70ce0bccb1e1a59a6ffca27f1912d020a13a8f5e Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Thu, 10 Sep 2026 15:29:40 +0300 Subject: [PATCH] Add a Reset button to clear selected currencies, with confirmation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New "Reset" button in the currency picker's top bar (next to Done), shown only when more than the pinned default currency is selected. Tapping it asks for confirmation before removing every other selected currency, via PriceViewModel.onSelectedCurrenciesReset() — same recompute/persist pattern as onFiatCurrencyToggled. Compose uses an AlertDialog; iOS/macOS uses .alert with a destructive Reset action, matching the destructive-role toolbar button that triggers it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy --- iosApp/iosApp/ContentView.swift | 4 ++- iosApp/iosApp/ConverterViewModel.swift | 4 +++ iosApp/iosApp/CurrencyPickerSheet.swift | 30 ++++++++++++++++ .../xyz/tyiu/satsprice/IosPriceViewModel.kt | 1 + .../xyz/tyiu/satsprice/ui/PriceScreen.kt | 35 ++++++++++++++++++- .../xyz/tyiu/satsprice/ui/PriceViewModel.kt | 10 ++++++ .../moko-resources/base/strings.xml | 4 +++ 7 files changed, 86 insertions(+), 2 deletions(-) diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index f44e724..ebe6d1b 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -238,7 +238,9 @@ struct ContentView: View { pricedCurrencyCodes: state.pricedCurrencyCodes, sourceName: state.sourceName, localeCurrencyCode: state.localeCurrencyCode, - onToggle: { viewModel.onFiatCurrencyToggled($0) } + selectedCount: state.selectedCurrencyCodes.count, + onToggle: { viewModel.onFiatCurrencyToggled($0) }, + onReset: { viewModel.onSelectedCurrenciesReset() } ) } .environment(\.openURL, OpenURLAction { url in diff --git a/iosApp/iosApp/ConverterViewModel.swift b/iosApp/iosApp/ConverterViewModel.swift index 9fb7370..35b6eb4 100644 --- a/iosApp/iosApp/ConverterViewModel.swift +++ b/iosApp/iosApp/ConverterViewModel.swift @@ -39,6 +39,10 @@ final class ConverterViewModel: ObservableObject { bridge.onFiatCurrenciesReordered(newOrder: newOrder) } + func onSelectedCurrenciesReset() { + bridge.onSelectedCurrenciesReset() + } + func onSourceSelected(_ name: String) { bridge.onSourceSelected(name: name) } diff --git a/iosApp/iosApp/CurrencyPickerSheet.swift b/iosApp/iosApp/CurrencyPickerSheet.swift index 92de1f9..eaf6987 100644 --- a/iosApp/iosApp/CurrencyPickerSheet.swift +++ b/iosApp/iosApp/CurrencyPickerSheet.swift @@ -8,10 +8,13 @@ struct CurrencyPickerSheet: View { let pricedCurrencyCodes: [String] let sourceName: String let localeCurrencyCode: String? + let selectedCount: Int let onToggle: (String) -> Void + let onReset: () -> Void @Environment(\.dismiss) private var dismiss @State private var searchQuery = "" + @State private var showResetConfirmation = false private func matches(_ info: CurrencyInfo) -> Bool { SystemCurrenciesKt.matchesCurrencySearch(info: info, query: searchQuery) @@ -60,6 +63,16 @@ struct CurrencyPickerSheet: View { .navigationBarTitleDisplayMode(.inline) #endif .toolbar { + if selectedCount > 1 { + ToolbarItem(placement: .cancellationAction) { + Button( + IosLocalizationKt.localizedString(resource: MR.strings.shared.reset_selected_currencies_button), + role: .destructive + ) { + showResetConfirmation = true + } + } + } ToolbarItem(placement: .confirmationAction) { Button(IosLocalizationKt.localizedString(resource: MR.strings.shared.done)) { dismiss() } } @@ -74,6 +87,23 @@ struct CurrencyPickerSheet: View { text: $searchQuery, prompt: IosLocalizationKt.localizedString(resource: MR.strings.shared.search_currencies_placeholder) ) + .alert( + IosLocalizationKt.localizedString(resource: MR.strings.shared.reset_selected_currencies_confirmation_title), + isPresented: $showResetConfirmation + ) { + Button( + IosLocalizationKt.localizedString(resource: MR.strings.shared.reset_selected_currencies_button), + role: .destructive + ) { + onReset() + } + Button(IosLocalizationKt.localizedString(resource: MR.strings.shared.cancel), role: .cancel) {} + } message: { + Text(IosLocalizationKt.localizedFormattedString( + resource: MR.strings.shared.reset_selected_currencies_confirmation_message, + args: [currentCurrency.code] + )) + } #if os(macOS) // macOS sizes a .sheet() to its content's ideal size rather than the parent window's // size (unlike iOS, which presents modally full-size); without an explicit frame here, diff --git a/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/IosPriceViewModel.kt b/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/IosPriceViewModel.kt index 1db6127..357b116 100644 --- a/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/IosPriceViewModel.kt +++ b/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/IosPriceViewModel.kt @@ -63,6 +63,7 @@ class IosPriceViewModel { fun onFiatAmountChanged(code: String, value: String) = viewModel.onFiatAmountChanged(code, value) fun onFiatCurrencyToggled(code: String) = viewModel.onFiatCurrencyToggled(code) fun onFiatCurrenciesReordered(newOrder: List) = viewModel.onFiatCurrenciesReordered(newOrder) + fun onSelectedCurrenciesReset() = viewModel.onSelectedCurrenciesReset() fun onSourceSelected(name: String) = viewModel.onSourceSelected(name) fun onManualRateChanged(value: String) = viewModel.onManualRateChanged(value) fun onManualSatsPerCurrencyChanged(value: String) = viewModel.onManualSatsPerCurrencyChanged(value) diff --git a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt index d52c34c..f1e7183 100644 --- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt +++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.material.icons.filled.KeyboardDoubleArrowUp import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.AlertDialog import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator @@ -120,6 +121,7 @@ fun PriceScreen( CurrencyPickerScreen( state = state, onToggle = viewModel::onFiatCurrencyToggled, + onReset = viewModel::onSelectedCurrenciesReset, onDone = { showCurrencyPicker = false }, ) return @@ -501,8 +503,32 @@ private fun CurrencyRowMenu( private fun CurrencyPickerScreen( state: ConverterUiState, onToggle: (String) -> Unit, + onReset: () -> Unit, onDone: () -> Unit, ) { + var showResetConfirmation by remember { mutableStateOf(false) } + + if (showResetConfirmation) { + AlertDialog( + onDismissRequest = { showResetConfirmation = false }, + title = { Text(stringResource(MR.strings.reset_selected_currencies_confirmation_title)) }, + text = { + Text(stringResource(MR.strings.reset_selected_currencies_confirmation_message, state.defaultCurrencyCode)) + }, + confirmButton = { + TextButton( + onClick = { + showResetConfirmation = false + onReset() + }, + ) { Text(stringResource(MR.strings.reset_selected_currencies_button)) } + }, + dismissButton = { + TextButton(onClick = { showResetConfirmation = false }) { Text(stringResource(MR.strings.cancel)) } + }, + ) + } + Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { Column( modifier = Modifier @@ -515,7 +541,14 @@ private fun CurrencyPickerScreen( verticalAlignment = Alignment.CenterVertically, ) { Text(stringResource(MR.strings.currencies_section_title), style = MaterialTheme.typography.headlineSmall) - TextButton(onClick = onDone) { Text(stringResource(MR.strings.done)) } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + if (state.selectedFiatCurrencies.size > 1) { + TextButton(onClick = { showResetConfirmation = true }) { + Text(stringResource(MR.strings.reset_selected_currencies_button)) + } + } + TextButton(onClick = onDone) { Text(stringResource(MR.strings.done)) } + } } var searchQuery by remember { mutableStateOf("") } diff --git a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt index 1ec75ba..3549183 100644 --- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt +++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt @@ -207,6 +207,16 @@ class PriceViewModel( persistSelectionIfChanged(_uiState.value.selectedFiatCurrencies) } + /** Removes every selected currency except the pinned [defaultCurrencyCode]. */ + fun onSelectedCurrenciesReset() { + _uiState.update { state -> + if (state.selectedFiatCurrencies == listOf(defaultCurrencyCode)) return@update state + val newState = state.copy(selectedFiatCurrencies = listOf(defaultCurrencyCode)) + rates?.let { recomputeFromKnownField(newState, it) } ?: newState + } + persistSelectionIfChanged(_uiState.value.selectedFiatCurrencies) + } + /** * Reorders the selected currencies to [newOrder]. Any code in [newOrder] that isn't * currently selected is ignored, and any currently-selected code missing from [newOrder] diff --git a/shared/src/commonMain/moko-resources/base/strings.xml b/shared/src/commonMain/moko-resources/base/strings.xml index bb6fbb8..442d3b2 100644 --- a/shared/src/commonMain/moko-resources/base/strings.xml +++ b/shared/src/commonMain/moko-resources/base/strings.xml @@ -3,6 +3,7 @@ Bitcoin BTC BTC to %1$s + Cancel Clear search Currencies %1$d selected @@ -22,6 +23,9 @@ Priced Currencies Refresh Remove + Reset + This keeps %1$s, but removes every other currency you\'ve added. + Remove all selected currencies? Retry Sats Search currencies