From fae017d5b36f4a44225970cb2964986fba28409f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 22 Sep 2026 13:40:56 +0000 Subject: [PATCH] ui: show close button on badges banner only after it was opened (#7560) --- apps/ios/Shared/Views/Badges/SupportSimpleXBanner.swift | 5 ++++- apps/ios/Shared/Views/ChatList/ChatListView.swift | 7 ++++++- apps/ios/Shared/Views/UserSettings/SettingsView.swift | 3 +++ apps/ios/spec/client/chat-list.md | 4 ++++ .../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 3 +++ .../simplex/common/views/badges/SupportSimpleXBanner.kt | 5 ++++- .../chat/simplex/common/views/chatlist/ChatListView.kt | 7 ++++++- apps/multiplatform/spec/client/chat-list.md | 4 ++++ 8 files changed, 34 insertions(+), 4 deletions(-) diff --git a/apps/ios/Shared/Views/Badges/SupportSimpleXBanner.swift b/apps/ios/Shared/Views/Badges/SupportSimpleXBanner.swift index d37c1e45e6..507be8ce5e 100644 --- a/apps/ios/Shared/Views/Badges/SupportSimpleXBanner.swift +++ b/apps/ios/Shared/Views/Badges/SupportSimpleXBanner.swift @@ -15,6 +15,7 @@ struct SupportSimpleXBanner: View { var title: LocalizedStringKey = "Support SimpleX" var subtitle: LocalizedStringKey = "Get badge + better files" var warning: Bool = false + var showDismiss: Bool = true let onTap: () -> Void let onDismiss: () -> Void @@ -56,7 +57,9 @@ struct SupportSimpleXBanner: View { .allowsHitTesting(false) } - BannerDismissButton(onDismiss: onDismiss) + if showDismiss { + BannerDismissButton(onDismiss: onDismiss) + } } } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 2a9647ba5a..b7017bad2b 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -177,6 +177,7 @@ struct ChatListView: View { @AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false @AppStorage(DEFAULT_ADDRESS_CREATION_CARD_SHOWN) private var addressCreationCardShown = false @AppStorage(DEFAULT_SUPPORTER_BANNER_SHOWN) private var supporterBannerShown = false + @AppStorage(DEFAULT_SUPPORTER_BANNER_TAPPED) private var supporterBannerTapped = false @AppStorage(DEFAULT_GET_STAKE_BANNER_TAPPED) private var getStakeBannerTapped = false @AppStorage(DEFAULT_GET_STAKE_BANNER_DISMISSED) private var getStakeBannerDismissed = false @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial @@ -516,7 +517,11 @@ struct ChatListView: View { .onAppear { chatModel.chatListBanner = .badgeIssueFailed } } else if chatModel.bannerSlotFree(for: .badgePitch) && !supporterBannerShown && noShownBadge && chatModel.chats.count > 3 { SupportSimpleXBanner( - onTap: { showBadgesSheet = true }, + showDismiss: supporterBannerTapped, + onTap: { + supporterBannerTapped = true + showBadgesSheet = true + }, onDismiss: showSupportSimpleXDismissAlert ) .padding(.vertical, 3) diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index efc16a6309..c1d1e5b111 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -57,6 +57,7 @@ let DEFAULT_CHAT_ITEM_TAIL = "chatItemTail" let DEFAULT_ONE_HAND_UI_CARD_SHOWN = "oneHandUICardShown" let DEFAULT_ADDRESS_CREATION_CARD_SHOWN = "addressCreationCardShown" let DEFAULT_SUPPORTER_BANNER_SHOWN = "supporterBannerShown" +let DEFAULT_SUPPORTER_BANNER_TAPPED = "supporterBannerTapped" let DEFAULT_GET_STAKE_BANNER_TAPPED = "getStakeBannerTapped" let DEFAULT_GET_STAKE_BANNER_DISMISSED = "getStakeBannerDismissed" let DEFAULT_TOOLBAR_MATERIAL = "toolbarMaterial" @@ -121,6 +122,7 @@ let appDefaults: [String: Any] = [ DEFAULT_ONE_HAND_UI_CARD_SHOWN: false, DEFAULT_ADDRESS_CREATION_CARD_SHOWN: false, DEFAULT_SUPPORTER_BANNER_SHOWN: false, + DEFAULT_SUPPORTER_BANNER_TAPPED: false, DEFAULT_GET_STAKE_BANNER_TAPPED: false, DEFAULT_GET_STAKE_BANNER_DISMISSED: false, DEFAULT_TOOLBAR_MATERIAL: ToolbarMaterial.defaultMaterial, @@ -155,6 +157,7 @@ let hintDefaults = [ DEFAULT_ONE_HAND_UI_CARD_SHOWN, DEFAULT_ADDRESS_CREATION_CARD_SHOWN, DEFAULT_SUPPORTER_BANNER_SHOWN, + DEFAULT_SUPPORTER_BANNER_TAPPED, DEFAULT_GET_STAKE_BANNER_TAPPED, DEFAULT_GET_STAKE_BANNER_DISMISSED, DEFAULT_LIVE_MESSAGE_ALERT_SHOWN, diff --git a/apps/ios/spec/client/chat-list.md b/apps/ios/spec/client/chat-list.md index ef131cfec0..2087d0dee3 100644 --- a/apps/ios/spec/client/chat-list.md +++ b/apps/ios/spec/client/chat-list.md @@ -303,6 +303,10 @@ In the onboarding branch the `.scaleEffect` and `ThemedBackground` are applied t |---------|--------|--------| | `DEFAULT_GET_STAKE_BANNER_TAPPED` | [`openGetStake()`](../../Shared/Views/ChatList/ChatListView.swift#L408) | the dismiss X appears from then on, while there are chats | | `DEFAULT_GET_STAKE_BANNER_DISMISSED` | the dismiss X | hides the banner in both placements | +| `DEFAULT_SUPPORTER_BANNER_TAPPED` | tapping the supporter pitch | the pitch's dismiss X appears from then on | +| `DEFAULT_SUPPORTER_BANNER_SHOWN` | the pitch's dismiss X, through its "You can support SimpleX later in Settings." alert, and a successful code redemption | hides the pitch | + +The two badge alert banners always offer the X; only the pitch waits to be tapped once, so a user who has not looked at it cannot dismiss it unseen. Both are in `hintDefaults`, so "Reset all hints" in the developer settings restores the banner. The X is never offered in the onboarding branch, so the banner cannot be dismissed before the user has a chat. 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 ca13924e47..9100b5ecea 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 @@ -193,6 +193,7 @@ class AppPreferences { val oneHandUICardShown = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN, false) val addressCreationCardShown = mkBoolPreference(SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN, false) val supporterBannerShown = mkBoolPreference(SHARED_PREFS_SUPPORTER_BANNER_SHOWN, false) + val supporterBannerTapped = mkBoolPreference(SHARED_PREFS_SUPPORTER_BANNER_TAPPED, false) val getStakeBannerTapped = mkBoolPreference(SHARED_PREFS_GET_STAKE_BANNER_TAPPED, false) val getStakeBannerDismissed = mkBoolPreference(SHARED_PREFS_GET_STAKE_BANNER_DISMISSED, false) val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true) @@ -277,6 +278,7 @@ class AppPreferences { hintPref(oneHandUICardShown, false), hintPref(addressCreationCardShown, false), hintPref(supporterBannerShown, false), + hintPref(supporterBannerTapped, false), hintPref(getStakeBannerTapped, false), hintPref(getStakeBannerDismissed, false), hintPref(liveMessageAlertShown, false), @@ -471,6 +473,7 @@ class AppPreferences { private const val SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN = "OneHandUICardShown" private const val SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN = "AddressCreationCardShown" private const val SHARED_PREFS_SUPPORTER_BANNER_SHOWN = "SupporterBannerShown" + private const val SHARED_PREFS_SUPPORTER_BANNER_TAPPED = "SupporterBannerTapped" private const val SHARED_PREFS_GET_STAKE_BANNER_TAPPED = "GetStakeBannerTapped" private const val SHARED_PREFS_GET_STAKE_BANNER_DISMISSED = "GetStakeBannerDismissed" private const val SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT = "ShowMuteProfileAlert" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/SupportSimpleXBanner.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/SupportSimpleXBanner.kt index 9bcc1215b2..47a4d3b152 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/SupportSimpleXBanner.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/badges/SupportSimpleXBanner.kt @@ -27,6 +27,7 @@ fun SupportSimpleXBanner( title: String = generalGetString(MR.strings.badges_banner_title), subtitle: String = generalGetString(MR.strings.badges_banner_subtitle), warning: Boolean = false, + showDismiss: Boolean = true, onTap: () -> Unit, onDismiss: () -> Unit ) { @@ -75,7 +76,9 @@ fun SupportSimpleXBanner( } } - BannerDismissButton(Modifier.align(Alignment.TopEnd), onDismiss) + if (showDismiss) { + BannerDismissButton(Modifier.align(Alignment.TopEnd), onDismiss) + } } HeroThumbnail( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 1d893ff858..d0748e4a16 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -950,6 +950,7 @@ private fun BoxScope.ChatList(searchText: MutableState, listStat val oneHandUICardShown = remember { appPrefs.oneHandUICardShown.state } val addressCreationCardShown = remember { appPrefs.addressCreationCardShown.state } val supporterBannerShown = remember { appPrefs.supporterBannerShown.state } + val supporterBannerTapped = remember { appPrefs.supporterBannerTapped.state } val getStakeBannerTapped = remember { appPrefs.getStakeBannerTapped.state } val getStakeBannerDismissed = remember { appPrefs.getStakeBannerDismissed.state } // read here rather than in the LazyColumn: it launches an effect, so it needs a composable scope @@ -1074,7 +1075,11 @@ private fun BoxScope.ChatList(searchText: MutableState, listStat SideEffect { chatModel.chatListBanner = ChatListBanner.BadgePitch } Box(Modifier.zIndex(1f).padding(16.dp)) { SupportSimpleXBanner( - onTap = { ModalManager.start.showCustomModal { close -> BadgesView(close) } }, + showDismiss = supporterBannerTapped.value, + onTap = { + appPrefs.supporterBannerTapped.set(true) + ModalManager.start.showCustomModal { close -> BadgesView(close) } + }, onDismiss = ::showSupportSimpleXDismissAlert ) } diff --git a/apps/multiplatform/spec/client/chat-list.md b/apps/multiplatform/spec/client/chat-list.md index fc310e84c1..e34da9613d 100644 --- a/apps/multiplatform/spec/client/chat-list.md +++ b/apps/multiplatform/spec/client/chat-list.md @@ -346,6 +346,10 @@ The list has a single banner slot, filled by an `if`/`else if` chain in priority |------------|--------|--------| | `getStakeBannerTapped` | `openGetStake()` | the dismiss X appears from then on, while there are chats | | `getStakeBannerDismissed` | the dismiss X | hides the banner in both placements | +| `supporterBannerTapped` | tapping the supporter pitch | the pitch's dismiss X appears from then on | +| `supporterBannerShown` | the pitch's dismiss X, through its "You can support SimpleX later in Settings." alert, and a successful code redemption | hides the pitch | + +The two badge alert banners always offer the X; only the pitch waits to be tapped once, so a user who has not looked at it cannot dismiss it unseen. Both are in `AppPreferences.hintPreferences`, so "Reset all hints" restores the banner. The X is never offered below the onboarding cards, so the banner cannot be dismissed before the user has a chat.