Drop the source name from the status line, make it localized

statusLine() used to build the whole "via Coinbase, updated ..."
string in shared code with hardcoded English literals ("updated",
"loading rates…", "via ") — not localizable, since this shared code
has no Composable/moko-resources context to resolve a string from,
and said more than needed (the source is already shown in the Price
Source picker right above it).

Replaced with lastUpdatedDateTime(): String?, which returns only the
locale-formatted date/time (still shared, since that part is genuine
platform-native locale formatting, not translated text) — null for
Manual, or while the first fetch hasn't completed. Each UI layer now
builds the localized "Updated %1$s" / "Loading rates…" text itself
via stringResource()/IosLocalizationKt around that value, using two
new string resources.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy
This commit is contained in:
2026-09-10 15:45:52 +03:00
co-authored by Claude Sonnet 5
parent 70ce0bccb1
commit cd37d5c472
5 changed files with 39 additions and 11 deletions
+17 -2
View File
@@ -37,6 +37,21 @@ struct ContentView: View {
: state.fiatRows
}
/// Nil for Manual (a manually typed rate has no "last updated" moment to show). Doesn't name
/// the price source just when the rate was last fetched.
private func statusText(for state: IosConverterState) -> String? {
if state.isManualSource {
return nil
}
if let dateTime = state.lastUpdatedDateTime {
return IosLocalizationKt.localizedFormattedString(
resource: MR.strings.shared.updated_status,
args: [dateTime]
)
}
return IosLocalizationKt.localizedString(resource: MR.strings.shared.loading_rates_status)
}
#if os(macOS)
/// [codes] with [moving] relocated to sit right before [target] how macOS's manual
/// drag-and-drop (see `.dropDestination` above) computes its new currency order, since
@@ -56,8 +71,8 @@ struct ContentView: View {
Form {
Section(
footer: Group {
if !state.statusLine.isEmpty {
Text(state.statusLine)
if let statusText = statusText(for: state) {
Text(statusText)
}
}
) {
@@ -9,9 +9,9 @@ import xyz.tyiu.satsprice.ui.PriceViewModel
import xyz.tyiu.satsprice.ui.currentCurrency
import xyz.tyiu.satsprice.ui.defaultCurrencyRate
import xyz.tyiu.satsprice.ui.exceedsMaxSupply
import xyz.tyiu.satsprice.ui.lastUpdatedDateTime
import xyz.tyiu.satsprice.ui.oneCurrencyToSats
import xyz.tyiu.satsprice.ui.selectedOtherCurrencies
import xyz.tyiu.satsprice.ui.statusLine
import xyz.tyiu.satsprice.ui.unselectedCurrencies
data class FiatRow(val code: String, val amount: String, val rateDisplay: String)
@@ -34,7 +34,7 @@ data class IosConverterState(
val manualSatsPerCurrencyInput: String,
val isLoading: Boolean,
val errorMessage: String?,
val statusLine: String,
val lastUpdatedDateTime: String?,
val defaultCurrencyRate: String,
val oneCurrencyToSats: String,
)
@@ -89,7 +89,7 @@ private fun ConverterUiState.toIosState(): IosConverterState = IosConverterState
manualSatsPerCurrencyInput = manualSatsPerCurrencyInput,
isLoading = isLoading,
errorMessage = errorMessage,
statusLine = statusLine(),
lastUpdatedDateTime = lastUpdatedDateTime(),
defaultCurrencyRate = defaultCurrencyRate(),
oneCurrencyToSats = oneCurrencyToSats(),
)
@@ -11,10 +11,16 @@ import kotlin.time.Instant
/** Derived display strings/flags shared between the Compose UI and the iOS SwiftUI bridge. */
fun ConverterUiState.statusLine(): String {
if (isManualSource) return ""
val updated = lastUpdated?.let { "updated ${it.toDateTimeString()}" } ?: "loading rates…"
return if (sourceName.isEmpty()) updated else "via $sourceName, $updated"
/**
* The current platform's locale-formatted rendering of [ConverterUiState.lastUpdated] just the
* date/time itself, not the surrounding localized "Updated ..."/"Loading rates…" text, since this
* shared code has no Composable/moko-resources context to resolve a localized string from; the UI
* layer supplies that around whatever this returns. Null while manual (nothing to show a
* manually typed rate has no "last updated" moment) or before the first fetch completes.
*/
fun ConverterUiState.lastUpdatedDateTime(): String? {
if (isManualSource) return null
return lastUpdated?.toDateTimeString()
}
fun ConverterUiState.exceedsMaxSupply(): Boolean {
@@ -141,8 +141,13 @@ fun PriceScreen(
) {
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) {
Text("SatsPrice", style = MaterialTheme.typography.headlineMedium)
val statusLine = state.statusLine()
if (statusLine.isNotEmpty()) {
val statusLine = if (state.isManualSource) {
null
} else {
state.lastUpdatedDateTime()?.let { stringResource(MR.strings.updated_status, it) }
?: stringResource(MR.strings.loading_rates_status)
}
if (statusLine != null) {
Text(
text = statusLine,
style = MaterialTheme.typography.bodySmall,
@@ -15,6 +15,7 @@
<string name="currency_to_sats">%1$s to Sats</string>
<string name="done">Done</string>
<string name="exceeds_max_supply">Exceeds the %1$s maximum supply</string>
<string name="loading_rates_status">Loading rates…</string>
<string name="move_currency_down_content_description">Move down</string>
<string name="move_currency_to_bottom_content_description">Move to bottom</string>
<string name="move_currency_to_top_content_description">Move to top</string>
@@ -31,4 +32,5 @@
<string name="search_currencies_placeholder">Search currencies</string>
<string name="selected_currencies_section_title">Selected Currencies</string>
<string name="unpriced_currencies_section_title">Unpriced Currencies</string>
<string name="updated_status">Updated %1$s</string>
</resources>