Make the max-supply warning's BTC amount a link on every platform
Tapping "21,000,000 BTC" in the exceeds-max-supply warning now sets the BTC field to that value, on both Compose (Android/Desktop/Web) and the native SwiftUI screen (iOS/macOS) — Compose UI isn't rendered on iOS/macOS at all, so that side needed its own AttributedString-based link. The link is styled to match the surrounding warning text: Compose's LinkAnnotation otherwise falls back to the theme's accent color unless every interaction state's style is set explicitly, and SwiftUI renders `.link` runs in the accent color regardless of the Text's own foregroundColor modifier unless it's set directly on the link's AttributedString range. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpjMKWGoiT5aJBzhxvXkwp
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import Foundation
|
||||||
import Shared
|
import Shared
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ struct ContentView: View {
|
|||||||
onChange: { viewModel.onBtcAmountChanged($0) }
|
onChange: { viewModel.onBtcAmountChanged($0) }
|
||||||
)
|
)
|
||||||
if state.exceedsMaxSupply {
|
if state.exceedsMaxSupply {
|
||||||
Text(IosLocalizationKt.localizedString(resource: MR.strings.shared.exceeds_max_supply))
|
exceedsMaxSupplyText
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.red)
|
.foregroundColor(.red)
|
||||||
}
|
}
|
||||||
@@ -167,6 +168,31 @@ struct ContentView: View {
|
|||||||
onToggle: { viewModel.onFiatCurrencyToggled($0) }
|
onToggle: { viewModel.onFiatCurrencyToggled($0) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
|
guard url == Self.maxSupplyLinkURL else { return .systemAction }
|
||||||
|
viewModel.onBtcAmountChanged(Self.maxSupplyBtcAmount)
|
||||||
|
return .handled
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches the literal text of MR.strings.exceeds_max_supply so it can be turned into a link;
|
||||||
|
// falls back to plain text if that ever drifts apart. `maxSupplyBtcAmount` is the sanitized
|
||||||
|
// digit-only form CurrencyConverter.MAX_BTC_SUPPLY formats to, mirroring the shared Compose UI.
|
||||||
|
private static let maxSupplyLinkText = "21,000,000 BTC"
|
||||||
|
private static let maxSupplyBtcAmount = "21000000"
|
||||||
|
private static let maxSupplyLinkURL = URL(string: "satsprice://set-max-supply")!
|
||||||
|
|
||||||
|
private var exceedsMaxSupplyText: Text {
|
||||||
|
let warning = IosLocalizationKt.localizedString(resource: MR.strings.shared.exceeds_max_supply)
|
||||||
|
var attributed = AttributedString(warning)
|
||||||
|
if let range = attributed.range(of: Self.maxSupplyLinkText) {
|
||||||
|
attributed[range].link = Self.maxSupplyLinkURL
|
||||||
|
attributed[range].underlineStyle = .single
|
||||||
|
// SwiftUI renders `.link` runs in the accent color regardless of the Text's own
|
||||||
|
// .foregroundColor modifier, so it has to be set directly on the link's range.
|
||||||
|
attributed[range].foregroundColor = .red
|
||||||
|
}
|
||||||
|
return Text(attributed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|||||||
@@ -46,13 +46,22 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.TextLinkStyles
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.withLink
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import xyz.tyiu.satsprice.CurrencyInfo
|
import xyz.tyiu.satsprice.CurrencyInfo
|
||||||
|
import xyz.tyiu.satsprice.domain.CurrencyConverter
|
||||||
|
import xyz.tyiu.satsprice.domain.formatAmount
|
||||||
import xyz.tyiu.satsprice.shared.MR
|
import xyz.tyiu.satsprice.shared.MR
|
||||||
|
|
||||||
private val SectionColors
|
private val SectionColors
|
||||||
@@ -207,8 +216,43 @@ fun PriceScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (exceedsMaxSupply) {
|
if (exceedsMaxSupply) {
|
||||||
|
val warning = stringResource(MR.strings.exceeds_max_supply)
|
||||||
|
// Matches the literal text of MR.strings.exceeds_max_supply so it can be
|
||||||
|
// turned into a link; falls back to plain text if that ever drifts apart.
|
||||||
|
val maxSupplyText = "21,000,000 BTC"
|
||||||
|
val linkStart = warning.indexOf(maxSupplyText)
|
||||||
|
val annotatedWarning = if (linkStart < 0) {
|
||||||
|
AnnotatedString(warning)
|
||||||
|
} else {
|
||||||
|
// Set explicitly (and identically) for every interaction state, since
|
||||||
|
// LinkAnnotation otherwise renders in the theme's link/accent color
|
||||||
|
// rather than inheriting the surrounding warning text's color.
|
||||||
|
val linkStyle = SpanStyle(
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
)
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(warning.substring(0, linkStart))
|
||||||
|
withLink(
|
||||||
|
LinkAnnotation.Clickable(
|
||||||
|
tag = "max_supply",
|
||||||
|
styles = TextLinkStyles(
|
||||||
|
style = linkStyle,
|
||||||
|
focusedStyle = linkStyle,
|
||||||
|
hoveredStyle = linkStyle,
|
||||||
|
pressedStyle = linkStyle,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
viewModel.onBtcAmountChanged(formatAmount(CurrencyConverter.MAX_BTC_SUPPLY, 0))
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
append(maxSupplyText)
|
||||||
|
}
|
||||||
|
append(warning.substring(linkStart + maxSupplyText.length))
|
||||||
|
}
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(MR.strings.exceeds_max_supply),
|
text = annotatedWarning,
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
|||||||
Reference in New Issue
Block a user