feat: wire alternate icons into settings, launch, and panic wipe

Expose the picker in App Info, re-apply the stored preference on appear,
and clear the disguise choice during a full data wipe.
This commit is contained in:
Taksh 2026-07-31 11:42:22 +03:00
parent 0fc3ccb237
commit 4db056ad7a
3 changed files with 55 additions and 0 deletions

View File

@ -45,6 +45,9 @@ struct BitchatApp: App {
.onAppear {
appDelegate.runtime = runtime
runtime.start()
#if os(iOS)
AlternateAppIconSettings.applyStoredPreference()
#endif
}
.onOpenURL { url in
runtime.handleOpenURL(url)

View File

@ -1637,6 +1637,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, SynchronousMessage
MeshSightingsTracker.shared.clear()
MeshEchoSettings.reset()
NotificationPrivacySettings.reset()
AlternateAppIconSettings.reset()
// A hand-added relay names an operator someone chose to route through,
// which is the kind of trace a wipe should not leave behind.
NostrRelaySettings.reset()

View File

@ -34,6 +34,9 @@ struct AppInfoView: View {
/// The override changed this session; localization resolves at process
/// start, so surface the restart hint.
@State private var showLanguageRestartNote = false
#if os(iOS)
@State private var selectedAppIcon = AlternateAppIconSettings.selected
#endif
private enum Pane: String {
case settings
@ -61,6 +64,9 @@ struct AppInfoView: View {
static let tabSettings = String(localized: "app_info.tab.settings", defaultValue: "settings", comment: "Segmented control label for the settings pane of the app info sheet")
static let tabInfo = String(localized: "app_info.tab.info", defaultValue: "info", comment: "Segmented control label for the info pane of the app info sheet")
static let iconTitle = String(localized: "app_info.settings.icon.title", defaultValue: "HOME SCREEN ICON", comment: "Section header (uppercase) for alternate app icon picker")
static let iconSubtitle = String(localized: "app_info.settings.icon.subtitle", defaultValue: "optional low-profile icons. the real app name still shows in settings and search — this only changes the home-screen glyph.", comment: "Caption under the alternate app icon picker explaining the limits of disguise")
static let connectivityTitle = String(localized: "app_info.settings.connectivity.title", defaultValue: "CONNECTIVITY", comment: "Section header (uppercase) for the connectivity toggles: mesh bridge, internet gateway, tor routing")
static let languageTitle = String(localized: "app_info.settings.language.title", defaultValue: "LANGUAGE", comment: "Section header (uppercase) for the app language picker in settings")
@ -357,6 +363,51 @@ struct AppInfoView: View {
}
}
#if os(iOS)
// Low-profile home-screen icons for the opsec / disguise path (#1447).
// macOS has no alternate-icon API equivalent, so this stays iOS-only.
VStack(alignment: .leading, spacing: 12) {
SectionHeader(verbatim: Strings.Settings.iconTitle)
settingsCard {
Text(verbatim: Strings.Settings.iconSubtitle)
.bitchatFont(size: 11)
.foregroundColor(secondaryTextColor)
HStack(spacing: 8) {
ForEach(AlternateAppIconSettings.Icon.allCases) { icon in
Button {
selectedAppIcon = icon
AlternateAppIconSettings.setSelected(icon, in: .standard, applySystem: true)
} label: {
Text(verbatim: icon.title)
.bitchatFont(
size: 12,
weight: selectedAppIcon == icon ? .semibold : .regular
)
.foregroundColor(
selectedAppIcon == icon ? palette.accent : secondaryTextColor
)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(
selectedAppIcon == icon
? palette.accent.opacity(0.15)
: Color.clear
)
)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityAddTraits(selectedAppIcon == icon ? .isSelected : [])
}
}
}
}
#endif
// Language an in-app override so the UI language can differ
// from the device language (takes effect on next launch).
VStack(alignment: .leading, spacing: 12) {