diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift
index b9048becda..02d6f1402e 100644
--- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift
+++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift
@@ -802,6 +802,8 @@ private func saveAddressSettings(_ settings: AddressSettingsState, _ savedSettin
}
}
+private let simplexNameSaleStart = Calendar(identifier: .gregorian).date(from: DateComponents(timeZone: TimeZone(identifier: "UTC"), year: 2026, month: 12, day: 12, hour: 18))!
+
struct SetSimplexDomainView: View {
let title: LocalizedStringKey
let footer: LocalizedStringKey
@@ -815,6 +817,8 @@ struct SetSimplexDomainView: View {
@State private var original = ""
@State private var didSave = false
@State private var editing = false
+ @State private var timeToSaleStart = simplexNameSaleStart.timeIntervalSinceNow
+ @State private var saleTimer: Timer? = nil
@FocusState private var nameFocused: Bool
init(title: LocalizedStringKey, footer: LocalizedStringKey, prompt: String, simplexName: String, broadcastWarning: String? = nil, save: @escaping (String?) async -> Bool) {
@@ -873,7 +877,7 @@ struct SetSimplexDomainView: View {
Section {
if editing {
Button {
- openBrowserAlert(uri: "https://github.com/simplex-chat/simplex-chat/blob/master/docs/guide/register-simplex-name.md")
+ openBrowserAlert(uri: "https://simplex.domains/#testing")
} label: {
HStack {
Text("How to register a test name")
@@ -901,15 +905,39 @@ struct SetSimplexDomainView: View {
}
}
}
+ Section {
+ VStack(alignment: .leading, spacing: 4) {
+ Text(verbatim: saleCountdown(timeToSaleStart))
+ Text(timeToSaleStart > 0 ? "until you can register a SimpleX domain" : "Update the app to register a SimpleX domain")
+ .font(.caption)
+ .foregroundColor(theme.colors.secondary)
+ }
+ } header: {
+ Text("SimpleX name sale starts in")
+ .foregroundColor(theme.colors.secondary)
+ } footer: {
+ Text("Crowdfunding investors can reserve their name before sale starts: [simplex.domains](https://simplex.domains/)")
+ .foregroundColor(theme.colors.secondary)
+ .padding(.bottom)
+ }
}
+ .modifier(ThemedBackground(grouped: true))
.navigationTitle(title)
.navigationBarTitleDisplayMode(.large)
.onAppear {
if editing {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { nameFocused = true }
}
+ if timeToSaleStart > 0 {
+ saleTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { t in
+ timeToSaleStart = simplexNameSaleStart.timeIntervalSinceNow
+ if timeToSaleStart <= 0 { t.invalidate() }
+ }
+ }
}
.onDisappear {
+ saleTimer?.invalidate()
+ saleTimer = nil
if !didSave, !saving, changed, isValid {
let domain = normalized(simplexName)
let saveName = save
@@ -948,6 +976,21 @@ struct SetSimplexDomainView: View {
: addSimplexTLD((t.hasPrefix("@") || t.hasPrefix("#") ? String(t.dropFirst()) : t).lowercased())
}
+ private func saleCountdown(_ remaining: TimeInterval) -> String {
+ let total = max(0, Int(remaining))
+ let days = total / 86400
+ let dayStr = String.localizedStringWithFormat(
+ days == 1
+ ? NSLocalizedString("%d day", comment: "time interval")
+ : NSLocalizedString("%d days", comment: "time interval"),
+ days
+ )
+ return dayStr + " " + String.localizedStringWithFormat(
+ NSLocalizedString("%02d hrs %02d min %02d sec", comment: "countdown"),
+ total / 3600 % 24, total / 60 % 60, total % 60
+ )
+ }
+
private func addSimplexTLD(_ d: String) -> String {
if d.contains(".") { d } else { "\(d).simplex" }
}
diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts
index 43bc114f21..5b91b0574d 100644
--- a/apps/multiplatform/common/build.gradle.kts
+++ b/apps/multiplatform/common/build.gradle.kts
@@ -212,7 +212,7 @@ afterEvaluate {
val fontLtGtRegex = Regex("[^>]*>.*<font[^>]*>.*</font>.*")
val unbracketedColorRegex = Regex("color=#[abcdefABCDEF0-9]{3,6}")
val correctHtmlRegex = Regex("[^>]*>.*.*.*|[^>]*>.*.*.*|[^>]*>.*.*.*|[^>]*>.*]*>.*.*")
- val possibleFormat = listOf("s", "d", "1\$s", "2\$s", "3\$s", "4\$s", "1\$d", "2\$d", "3\$d", "4\$d", "2s", "f")
+ val possibleFormat = listOf("s", "d", "1\$s", "2\$s", "3\$s", "4\$s", "1\$d", "2\$d", "3\$d", "4\$d", "1\$02d", "2\$02d", "3\$02d", "2s", "f")
fun String.id(): String = replace(" 0) {
+ delay(1000)
+ msToSaleStart.value = simplexNameSaleStart.toEpochMilliseconds() - System.currentTimeMillis()
+ }
+ }
+ SectionView(stringResource(MR.strings.simplex_name_sales)) {
+ SectionItemView {
+ Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
+ Text(saleCountdown(msToSaleStart.value))
+ Text(
+ stringResource(if (msToSaleStart.value > 0) MR.strings.until_register_simplex_domain else MR.strings.update_app_register_simplex_domain),
+ color = MaterialTheme.colors.secondary,
+ fontSize = 12.sp
+ )
+ }
+ }
+ }
+ SectionTextFooter(buildAnnotatedString {
+ append(generalGetString(MR.strings.simplex_name_sales_footer))
+ append(" ")
+ withLink(LinkAnnotation.Url(SIMPLEX_DOMAINS_URL) { uriHandler.openUriCatching(SIMPLEX_DOMAINS_URL) }) {
+ withStyle(SpanStyle(color = MaterialTheme.colors.primary)) {
+ append("simplex.domains")
+ }
+ }
+ })
SectionBottomSpacer()
}
}
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
index 6701a74005..c072fd7849 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -397,6 +397,11 @@
Get SimpleX name (BETA)
Channel SimpleX name
How to register a test name
+ SimpleX name sale starts in
+ until you can register a SimpleX domain
+ Update the app to register a SimpleX domain
+ %1$02d hrs %2$02d min %3$02d sec
+ Crowdfunding investors can reserve their name before sale starts:
Remove name
Save
Edit