From 1cc8122848b6908c8cbea0c76d88f21b15309b06 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Sat, 8 Aug 2026 17:35:57 +0200 Subject: [PATCH] review round 3 consolidation --- .../chat/simplex/common/model/SimpleXAPI.kt | 8 +++- .../common/views/usersettings/BuyNameView.kt | 4 +- .../common/views/usersettings/GiveNameView.kt | 3 +- .../usersettings/ImportRecoveryKeyView.kt | 37 ++++++++----------- .../views/usersettings/IncomingNamesView.kt | 7 +++- .../views/usersettings/NameDetailView.kt | 24 ++++++++++-- .../views/usersettings/NameRecoveryKeyView.kt | 11 +++++- .../views/usersettings/SimplexNamesView.kt | 6 ++- .../commonMain/resources/MR/base/strings.xml | 17 +++++---- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Library/Commands.hs | 14 ++++--- src/Simplex/Chat/View.hs | 2 +- 12 files changed, 85 insertions(+), 50 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 9e327c3d11..fdbe6f6b1b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -3061,6 +3061,12 @@ object ChatController { r.chatItems.forEach { chatItem -> val cInfo = chatItem.chatInfo val cItem = chatItem.chatItem + // A name arriving is the one incoming event with no other signal, so + // the settings badge is bumped here rather than only when the names + // screen is opened - otherwise it can never draw the user to a name. + if (!cItem.chatDir.sent && cItem.content.msgContent is MsgContent.MCAssetTransfer) { + chatModel.namesWaiting.value = chatModel.namesWaiting.value + 1 + } if (active(r.user)) { withContext(Dispatchers.Main) { chatModel.chatsContext.addChatItem(rhId, cInfo, cItem) @@ -6797,7 +6803,7 @@ sealed class CR { @Serializable @SerialName("nameAddress") class NameAddress(val user: User, val nameAddress: String, val nameAccount: Int, val nameMetaAddress: String): CR() @Serializable @SerialName("nameInfo") class NameInfo(val user: User, val nameFqdn: String, val nameOwner: String, val nameContact: List, val nameChannel: List, val nameExpires: Int, val nameEditCredits: Int): CR() @Serializable @SerialName("nameRenewed") class NameRenewed(val user: User, val nameFqdn: String, val nameExpires: Int, val nameReRegistered: Boolean): CR() - @Serializable @SerialName("nameStatus") class NameStatus(val user: User, val nameHasWallet: Boolean, val nameKeySaved: Boolean): CR() + @Serializable @SerialName("nameStatus") class NameStatus(val user: User, val nameHasWallet: Boolean, val nameKeySaved: Boolean, val nameAnySeed: Boolean = false): CR() @Serializable @SerialName("namesIncoming") class NamesIncoming(val user: User, val incomingNames: List): CR() @Serializable @SerialName("nameAccepted") class NameAccepted(val user: User, val nameOneTimeAddr: String, val acceptedNames: List): CR() @Serializable @SerialName("nameDeclined") class NameDeclined(val user: User, val nameOneTimeAddr: String): CR() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/BuyNameView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/BuyNameView.kt index 535aa3f7e0..dc17b2c777 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/BuyNameView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/BuyNameView.kt @@ -69,13 +69,13 @@ fun BuyNameView(rhId: Long?, close: () -> Unit) { val label = label.value.trim().lowercase() // Keyed on what is being bought, so a retry after a failed // registration reuses the receipt instead of charging again. - val paid = NamePayment.purchaseFor("buy:$label:${years.value}", years.value) + val paid = NamePayment.purchaseFor("buy:$rhId:$label:${years.value}", years.value) if (paid == null) return@launch // cancelled: nothing charged, nothing registered // Bought to be used, so it points at this profile from the moment it // exists rather than needing a second trip through the detail screen. val link = if (pointAtMe.value) myLink else null val reg = chatModel.controller.apiNameBuy(rhId, label, years.value, paid.token, link) - if (reg != null) NamePayment.spent("buy:$label:${years.value}") + if (reg != null) NamePayment.spent("buy:$rhId:$label:${years.value}") if (reg != null) { // The switch already said whether this name is for this profile, so // it is acted on rather than asked again. Chaining dialogs here was diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/GiveNameView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/GiveNameView.kt index 76c98839bc..75a06fe134 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/GiveNameView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/GiveNameView.kt @@ -52,7 +52,8 @@ fun GiveNameView(rhId: Long?, fqdn: String, close: () -> Unit) { } } SectionTextFooter( - if (cannotReceive > 0) stringResource(MR.strings.names_give_some_cannot).format(cannotReceive) + if (cannotReceive == 1) stringResource(MR.strings.names_give_one_cannot) + else if (cannotReceive > 0) stringResource(MR.strings.names_give_some_cannot).format(cannotReceive) else stringResource(MR.strings.names_give_footer) ) SectionBottomSpacer() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ImportRecoveryKeyView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ImportRecoveryKeyView.kt index 9ee930e6f8..12244b3cc8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ImportRecoveryKeyView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ImportRecoveryKeyView.kt @@ -29,7 +29,7 @@ import kotlinx.coroutines.launch fun ImportRecoveryKeyView(rhId: Long?, close: () -> Unit) { val phrase = rememberSaveable { mutableStateOf("") } val working = remember { mutableStateOf(false) } - val hasWallet = remember { mutableStateOf(false) } + val hasWallet = remember { mutableStateOf(null) } val scope = rememberCoroutineScope() LaunchedEffect(Unit) { @@ -62,6 +62,17 @@ fun ImportRecoveryKeyView(rhId: Long?, close: () -> Unit) { ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.names_import_title)) + + if (hasWallet.value == true) { + // The core refuses to import over an existing key, so say so plainly + // rather than warn about a replacement that will not happen. + SectionView { + SectionItemView { Text(stringResource(MR.strings.names_import_already), color = MaterialTheme.colors.secondary) } + } + SectionBottomSpacer() + return@ColumnWithScrollBar + } + SectionTextFooter(stringResource(MR.strings.names_import_intro)) SectionView(stringResource(MR.strings.names_import_section).uppercase()) { @@ -77,25 +88,15 @@ fun ImportRecoveryKeyView(rhId: Long?, close: () -> Unit) { } SectionTextFooter( if (phrase.value.isBlank()) stringResource(MR.strings.names_import_hint) + else if (words.size == 1) stringResource(MR.strings.names_import_word_count_one) else stringResource(MR.strings.names_import_word_count).format(words.size), if (phrase.value.isBlank() || looksComplete) MaterialTheme.colors.secondary else WarningOrange ) - if (hasWallet.value) { - // Switching keys is not additive: names held by the current key are no - // longer reachable from this profile afterwards. - SectionDividerSpaced(maxTopPadding = true) - SectionView(stringResource(MR.strings.names_import_replace_section).uppercase()) { - SectionItemView { Text(stringResource(MR.strings.names_import_replace_warning), color = Color.Red) } - } - } - SectionDividerSpaced(maxTopPadding = true) SectionView { SectionItemView( - click = if (working.value || !looksComplete) null else { - { if (hasWallet.value) confirmReplace { doImport() } else doImport() } - }, + click = if (working.value || !looksComplete) null else { { doImport() } }, disabled = working.value || !looksComplete, ) { Text( @@ -108,12 +109,4 @@ fun ImportRecoveryKeyView(rhId: Long?, close: () -> Unit) { } } -private fun confirmReplace(proceed: () -> Unit) { - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.names_import_replace_title), - text = generalGetString(MR.strings.names_import_replace_text), - confirmText = generalGetString(MR.strings.names_import_action), - destructive = true, - onConfirm = proceed, - ) -} + diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncomingNamesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncomingNamesView.kt index d5de40605d..1425bf7526 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncomingNamesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncomingNamesView.kt @@ -77,8 +77,11 @@ fun IncomingNamesView(rhId: Long?, close: () -> Unit) { if (found != null) { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.names_rescan_done_title), - text = if (found == 0) generalGetString(MR.strings.names_rescan_none) - else generalGetString(MR.strings.names_rescan_found).format(found) + text = when (found) { + 0 -> generalGetString(MR.strings.names_rescan_none) + 1 -> generalGetString(MR.strings.names_rescan_found_one) + else -> generalGetString(MR.strings.names_rescan_found).format(found) + } ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NameDetailView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NameDetailView.kt index 89f0812bd6..fe5a68d324 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NameDetailView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NameDetailView.kt @@ -29,10 +29,15 @@ import kotlinx.datetime.Instant @Composable fun NameDetailView(rhId: Long?, fqdn: String, close: () -> Unit) { val info = remember { mutableStateOf(null) } + val loadFailed = remember { mutableStateOf(false) } val busy = remember { mutableStateOf(false) } val scope = rememberCoroutineScope() - suspend fun reload() { info.value = chatModel.controller.apiNameInfo(rhId, fqdn) } + suspend fun reload() { + val r = chatModel.controller.apiNameInfo(rhId, fqdn) + info.value = r + loadFailed.value = r == null + } LaunchedEffect(Unit) { reload() } val myLink = chatModel.userAddress.value?.connLinkContact?.simplexChatUri(short = true) @@ -40,6 +45,7 @@ fun NameDetailView(rhId: Long?, fqdn: String, close: () -> Unit) { NameDetailLayout( fqdn = fqdn, info = info.value, + loadFailed = loadFailed.value, myLink = myLink, busy = busy.value, repoint = { @@ -74,9 +80,9 @@ fun NameDetailView(rhId: Long?, fqdn: String, close: () -> Unit) { withBGApi { busy.value = true try { - val paid = NamePayment.purchaseFor("renew:$fqdn", 1) ?: return@withBGApi + val paid = NamePayment.purchaseFor("renew:$rhId:$fqdn", 1) ?: return@withBGApi val r = chatModel.controller.apiNameRenew(rhId, fqdn, 1, paid.token) - if (r != null) NamePayment.spent("renew:$fqdn") + if (r != null) NamePayment.spent("renew:$rhId:$fqdn") if (r != null) { reload() AlertManager.shared.showAlertMsg( @@ -99,6 +105,7 @@ fun NameDetailView(rhId: Long?, fqdn: String, close: () -> Unit) { private fun NameDetailLayout( fqdn: String, info: CR.NameInfo?, + loadFailed: Boolean, myLink: String?, busy: Boolean, repoint: () -> Unit, @@ -112,7 +119,14 @@ private fun NameDetailLayout( } if (info == null) { - SectionView { SectionItemView { Text(stringResource(MR.strings.names_incoming_loading), color = MaterialTheme.colors.secondary) } } + SectionView { + SectionItemView { + Text( + if (loadFailed) stringResource(MR.strings.names_list_load_failed) else stringResource(MR.strings.names_incoming_loading), + color = if (loadFailed) WarningOrange else MaterialTheme.colors.secondary + ) + } + } SectionBottomSpacer() return@ColumnWithScrollBar } @@ -159,6 +173,7 @@ private fun NameDetailLayout( Text(info.nameEditCredits.toString(), color = MaterialTheme.colors.secondary) } } + SectionTextFooter(stringResource(MR.strings.names_detail_changes_footer)) // The app sends no expiry notifications, so the only reminder that will // actually reach the user is one in their own calendar. SectionItemView(click = { @@ -238,6 +253,7 @@ private fun expiryText(expires: Long): String { return when { d < 0 -> generalGetString(MR.strings.names_detail_expired) d == 0L -> generalGetString(MR.strings.names_detail_expires_today) + d == 1L -> generalGetString(MR.strings.names_detail_expires_tomorrow) d < 30 -> generalGetString(MR.strings.names_detail_expires_in_days).format(d) else -> Instant.fromEpochSeconds(expires).toString().substring(0, 10) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NameRecoveryKeyView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NameRecoveryKeyView.kt index be66465c06..3692007bf2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NameRecoveryKeyView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NameRecoveryKeyView.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.launch @Composable fun NameRecoveryKeyView(rhId: Long?, close: () -> Unit) { val phrase = remember { mutableStateOf(null) } + val loadFailed = remember { mutableStateOf(false) } val saved = remember { mutableStateOf(false) } val revealed = remember { mutableStateOf(false) } val scope = rememberCoroutineScope() @@ -48,11 +49,14 @@ fun NameRecoveryKeyView(rhId: Long?, close: () -> Unit) { if (r != null) { phrase.value = r.recoveryPhrase saved.value = r.recoveryKeySaved + } else { + loadFailed.value = true } } NameRecoveryKeyLayout( phrase = phrase.value, + loadFailed = loadFailed.value, saved = saved.value, revealed = revealed.value, reveal = { revealed.value = true }, @@ -70,6 +74,7 @@ fun NameRecoveryKeyView(rhId: Long?, close: () -> Unit) { @Composable private fun NameRecoveryKeyLayout( phrase: String?, + loadFailed: Boolean, saved: Boolean, revealed: Boolean, reveal: () -> Unit, @@ -88,7 +93,11 @@ private fun NameRecoveryKeyLayout( SectionDividerSpaced(maxTopPadding = true) - if (phrase == null) { + if (loadFailed) { + SectionView { + SectionItemView { Text(stringResource(MR.strings.names_list_load_failed), color = WarningOrange) } + } + } else if (phrase == null) { SectionView { SectionItemView { Text(stringResource(MR.strings.names_recovery_key_none), color = MaterialTheme.colors.secondary) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SimplexNamesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SimplexNamesView.kt index 2d0c7bc1fc..1aa08ed3a3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SimplexNamesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SimplexNamesView.kt @@ -31,6 +31,7 @@ fun SimplexNamesView(rhId: Long?, close: () -> Unit) { val keySaved = remember { mutableStateOf(true) } val hasKey = remember { mutableStateOf(false) } val loadFailed = remember { mutableStateOf(false) } + val anySeed = remember { mutableStateOf(false) } LaunchedEffect(Unit) { // Status first, and nothing else until it says a wallet exists. Every other @@ -43,6 +44,7 @@ fun SimplexNamesView(rhId: Long?, close: () -> Unit) { loadFailed.value = st == null hasKey.value = st?.nameHasWallet ?: false keySaved.value = st?.nameKeySaved ?: true + anySeed.value = st?.nameAnySeed ?: false if (hasKey.value) { names.value = chatModel.controller.apiNameList(rhId) incomingCount.value = chatModel.controller.apiNameIncoming(rhId)?.size ?: 0 @@ -63,6 +65,7 @@ fun SimplexNamesView(rhId: Long?, close: () -> Unit) { keySaved = keySaved.value, hasKey = hasKey.value, loadFailed = loadFailed.value, + hasOtherSeed = anySeed.value, rhId = rhId, ) } @@ -75,6 +78,7 @@ private fun SimplexNamesLayout( keySaved: Boolean, hasKey: Boolean, loadFailed: Boolean, + hasOtherSeed: Boolean, rhId: Long?, ) { ColumnWithScrollBar { @@ -140,7 +144,7 @@ private fun SimplexNamesLayout( if (!hasKey) SettingsActionItem( painterResource(MR.images.ic_add), stringResource(MR.strings.names_setup_title), - click = { ModalManager.start.showModalCloseable { c -> WalletSetupView(rhId, hasOtherSeed = false, onDone = c) } }, + click = { ModalManager.start.showModalCloseable { c -> WalletSetupView(rhId, hasOtherSeed = hasOtherSeed, onDone = c) } }, textColor = MaterialTheme.colors.primary, iconColor = MaterialTheme.colors.primary, ) 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 9cee2496ef..5c44c2126a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -3217,6 +3217,7 @@ Check complete No new names found. Found %d names sent to you — see the list above. + Found a name sent to you — see the list above. SimpleX names Your names You do not have a name yet @@ -3248,7 +3249,7 @@ That name is already taken. Buy this name — %s Registering… - Could not get the name + Could not buy the name The name is yours %s now points at this profile. Save recovery key @@ -3286,6 +3287,7 @@ None of your contacts can receive a name yet. Only contacts who can receive names are listed. %d of your contacts cannot receive names yet — they need a newer app version, or to turn receiving on. + One of your contacts cannot receive names yet — they need a newer app version, or to turn receiving on. Give the name away? %1$s will belong to %2$s. Only they will be able to change or move it after this. Give it away @@ -3311,24 +3313,22 @@ You do not have a SimpleX address yet, so there is nothing for the name to point at. Create one first, or buy the name to give away. You sent the SimpleX name %s You were sent the SimpleX name %s — open SimpleX names to accept it - You were given a SimpleX name. Update SimpleX to accept it. + You were sent a SimpleX name. Update SimpleX to accept it. Restore names from a recovery key If you have a recovery key from another device, enter it here to get your names back. Nothing is bought and no one is contacted. Recovery key your recovery words, separated by spaces Enter the words in the order they were shown to you. %d words entered — a recovery key has 12, 15, 18, 21 or 24. + 1 word entered — a recovery key has 12, 15, 18, 21 or 24. Restore Restoring… That recovery key was not accepted Restored - No names were found for that recovery key yet. - Found %d names sent to you. - This will replace your current key - This profile already has a recovery key. Restoring a different one means names held by the current key can no longer be reached from this profile. - Replace your recovery key? - Names held by your current key will no longer be reachable from this profile. Make sure you have saved it first. + Recovery key added. Open Your names to see what it holds. + Recovery key added, and %d names were sent to you. Check Your names and names sent to you. In %d days + Tomorrow Pointing this name somewhere new uses one change. Extending the name adds 10 more. Extend this name Extend this name? @@ -3354,4 +3354,5 @@ Using the key you already have means one set of words to write down, and it covers every profile. A separate key keeps this profile\'s names unconnected to your others, but is a second thing to keep safe. You will be shown the key afterwards. Write it down — it is the only way to get your names back if you lose this device. Could not set up names for this profile + This profile already has a recovery key, so you cannot import a different one here. To use another key, set up a new profile and import it there. \ No newline at end of file diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 8d1f0fe61c..923f101f40 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -964,7 +964,7 @@ data ChatResponse | CRNameAddress {user :: User, nameAddress :: Text, nameAccount :: Int, nameMetaAddress :: Text} | -- | Read-only: never creates a wallet. Screens use this to decide what to -- offer, so that opening one cannot create keys or notify contacts. - CRNameStatus {user :: User, nameHasWallet :: Bool, nameKeySaved :: Bool} + CRNameStatus {user :: User, nameHasWallet :: Bool, nameKeySaved :: Bool, nameAnySeed :: Bool} | CRNameRecoveryKey {user :: User, recoveryPhrase :: Text, recoveryKeySaved :: Bool} | CRNameQuoted {user :: User, nameLabel :: Text, nameAvailable :: Bool, namePriceCents :: Int} | CRNameRegistered {user :: User, nameFqdn :: Text, nameTxHash :: Text} diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index ddc746c407..16e4f107c7 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2413,13 +2413,14 @@ processChatCommand cxt nm = \case r <- withStore' $ \db -> WS.bindNewAccountOnSeed db user w -- Contacts need the new address before they can send anything to it. void $ publishMetaAddress user - pure $ CRNameStatus user True (W.wsBackedUp w) + pure $ CRNameStatus user True (W.wsBackedUp w) True APINameStatus userId -> withUserId userId $ \user -> do ref <- withStore' $ \db -> WS.getAccountRef db user saved <- case ref of Nothing -> pure False Just r -> maybe False W.wsBackedUp <$> withStore' (\db -> WS.getWalletSeed db (W.arSeedId r)) - pure $ CRNameStatus user (isJust ref) saved + anySeed <- not . null <$> withStore' WS.getWalletSeeds + pure $ CRNameStatus user (isJust ref) saved anySeed APINameAddress userId -> withUserId userId $ \user -> do -- Asking to see your address is an explicit act, so publishing here is -- fine - it is how someone can receive a name before buying one. What must @@ -2594,10 +2595,11 @@ processChatCommand cxt nm = \case -- twice. forM_ ct_ $ \ct -> do let mc = MCAssetTransfer {text = "You were given the SimpleX name " <> fqdn, transfer = AssetTransfer {kind = "simplexName", asset = fqdn, ephemeralPubKey = Just ephHex}} - -- Protocol message only. Creating a local chat item here made this - -- command's own response be emitted twice; the sender's bubble is the - -- client's business, not the core's. - void $ sendDirectContactMessage user ct $ XMsgNew $ mcSimple mc + -- Best-effort: the transfer already landed on chain above, so a failed + -- notification must not fail the command and report the gift as failed + -- for a name that has already moved. Protocol message only; the sender's + -- bubble is the client's business. + (void (sendDirectContactMessage user ct $ XMsgNew $ mcSimple mc)) `catchAllErrors` \_ -> pure () -- The profile must stop advertising a name it no longer owns, or contacts -- keep seeing it and the user's own list reports it as not managed here. let User {profile = LocalProfile {contactDomain}} = user diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 07a314567b..9940b60af8 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -159,7 +159,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, showFullLinks, te CRNameGifted u fqdn tx _eph -> ttyUser u ["gift " <> plain fqdn <> " done", "tx " <> plain tx] CRNameRenewed u fqdn expires reReg -> ttyUser u [(if reReg then "registered again: " else "renewed: ") <> plain fqdn, "expires " <> sShow expires] - CRNameStatus u hasWallet keySaved -> + CRNameStatus u hasWallet keySaved _anySeed -> ttyUser u [if hasWallet then (if keySaved then "wallet ready, recovery key saved" else "wallet ready, recovery key NOT saved") else "no wallet yet - people cannot send you names"] CRNamesIncoming u ns | null ns -> ttyUser u ["no names have been sent to you"]