From a2de9f13df0168ca2dcc2789a599ca07cf39e479 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 29 Aug 2026 18:59:12 +0100 Subject: [PATCH] ui: show countdown until SimpleX domains sale launch (#7434) * ui: show countdown until SimpleX domains sale launch * list * ios: update * update kotlin, ios countdown * kotlin countdown * fix formats * padding * update --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- .../Views/UserSettings/UserAddressView.swift | 45 ++++++++++++++++++- apps/multiplatform/common/build.gradle.kts | 4 +- .../views/usersettings/SetSimplexNameView.kt | 45 ++++++++++++++++++- .../commonMain/resources/MR/base/strings.xml | 5 +++ 4 files changed, 94 insertions(+), 5 deletions(-) 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