mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-10-25 07:41:41 +00:00
Merge 9f2dcedb1a4764f01f969b76402dfa5ab3ebc89a into 9b84b361225facd8e623f25d76f889d3dc54a879
This commit is contained in:
commit
f9c6b4c9d6
@ -80,7 +80,13 @@ final class AppChromeModel: ObservableObject {
|
||||
showingFingerprintFor = nil
|
||||
}
|
||||
|
||||
func presentAppInfo() {
|
||||
/// Presents the App Info sheet. Pass a pane when the launching control
|
||||
/// promises one (the header gear is labeled "settings"); nil keeps the
|
||||
/// sheet's own sticky last-used pane.
|
||||
func presentAppInfo(pane: AppInfoView.Pane? = nil) {
|
||||
if let pane {
|
||||
AppInfoView.setSelectedPane(pane)
|
||||
}
|
||||
isAppInfoPresented = true
|
||||
}
|
||||
|
||||
|
||||
@ -32410,7 +32410,7 @@
|
||||
"fil" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "settings"
|
||||
"value" : "mga setting"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
@ -56139,6 +56139,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"content.header.settings" : {
|
||||
"comment" : "Accessibility label and tooltip for the header gear button that opens the settings/app info sheet",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : { "stringUnit" : { "state" : "translated", "value" : "الإعدادات" } },
|
||||
"bn" : { "stringUnit" : { "state" : "translated", "value" : "সেটিংস" } },
|
||||
"de" : { "stringUnit" : { "state" : "translated", "value" : "einstellungen" } },
|
||||
"en" : { "stringUnit" : { "state" : "translated", "value" : "settings" } },
|
||||
"es" : { "stringUnit" : { "state" : "translated", "value" : "ajustes" } },
|
||||
"fa" : { "stringUnit" : { "state" : "translated", "value" : "تنظیمات" } },
|
||||
"fil" : { "stringUnit" : { "state" : "translated", "value" : "mga setting" } },
|
||||
"fr" : { "stringUnit" : { "state" : "translated", "value" : "réglages" } },
|
||||
"he" : { "stringUnit" : { "state" : "translated", "value" : "הגדרות" } },
|
||||
"hi" : { "stringUnit" : { "state" : "translated", "value" : "सेटिंग्स" } },
|
||||
"id" : { "stringUnit" : { "state" : "translated", "value" : "pengaturan" } },
|
||||
"it" : { "stringUnit" : { "state" : "translated", "value" : "impostazioni" } },
|
||||
"ja" : { "stringUnit" : { "state" : "translated", "value" : "設定" } },
|
||||
"ko" : { "stringUnit" : { "state" : "translated", "value" : "설정" } },
|
||||
"ms" : { "stringUnit" : { "state" : "translated", "value" : "tetapan" } },
|
||||
"ne" : { "stringUnit" : { "state" : "translated", "value" : "सेटिङ" } },
|
||||
"nl" : { "stringUnit" : { "state" : "translated", "value" : "instellingen" } },
|
||||
"pl" : { "stringUnit" : { "state" : "translated", "value" : "ustawienia" } },
|
||||
"pt" : { "stringUnit" : { "state" : "translated", "value" : "definições" } },
|
||||
"pt-BR" : { "stringUnit" : { "state" : "translated", "value" : "ajustes" } },
|
||||
"ru" : { "stringUnit" : { "state" : "translated", "value" : "настройки" } },
|
||||
"sv" : { "stringUnit" : { "state" : "translated", "value" : "inställningar" } },
|
||||
"ta" : { "stringUnit" : { "state" : "translated", "value" : "அமைப்புகள்" } },
|
||||
"th" : { "stringUnit" : { "state" : "translated", "value" : "ตั้งค่า" } },
|
||||
"tr" : { "stringUnit" : { "state" : "translated", "value" : "ayarlar" } },
|
||||
"uk" : { "stringUnit" : { "state" : "translated", "value" : "налаштування" } },
|
||||
"ur" : { "stringUnit" : { "state" : "translated", "value" : "سیٹنگز" } },
|
||||
"vi" : { "stringUnit" : { "state" : "translated", "value" : "cài đặt" } },
|
||||
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "设置" } },
|
||||
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "設定" } }
|
||||
}
|
||||
},
|
||||
"content.help.verification" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
|
||||
@ -28,18 +28,28 @@ struct AppInfoView: View {
|
||||
@ObservedObject private var locationManager = LocationChannelManager.shared
|
||||
/// Sticky across opens: first-ever open lands on Info (the gentler
|
||||
/// introduction), and afterwards the sheet reopens wherever it was left.
|
||||
@AppStorage("appInfo.selectedPane") private var selectedPane: Pane = .info
|
||||
@AppStorage(AppInfoView.selectedPaneStorageKey) private var selectedPane: Pane = .info
|
||||
@State private var showPanicConfirmation = false
|
||||
@AppStorage(AppLanguageSettings.overrideKey) private var languageOverride = ""
|
||||
/// The override changed this session; localization resolves at process
|
||||
/// start, so surface the restart hint.
|
||||
@State private var showLanguageRestartNote = false
|
||||
|
||||
private enum Pane: String {
|
||||
enum Pane: String {
|
||||
case settings
|
||||
case info
|
||||
}
|
||||
|
||||
private static let selectedPaneStorageKey = "appInfo.selectedPane"
|
||||
|
||||
/// Overwrites the remembered pane so the next presentation opens there.
|
||||
/// `selectedPane` is sticky by design; a caller whose control names a
|
||||
/// specific pane (the header gear says "settings") uses this so the
|
||||
/// sheet doesn't land first-time users on the Info default instead.
|
||||
static func setSelectedPane(_ pane: Pane) {
|
||||
UserDefaults.standard.set(pane.rawValue, forKey: selectedPaneStorageKey)
|
||||
}
|
||||
|
||||
private var selectedTheme: AppTheme {
|
||||
AppTheme(rawValue: appThemeRawValue) ?? .matrix
|
||||
}
|
||||
|
||||
@ -65,9 +65,10 @@ struct ContentHeaderView: View {
|
||||
// (next to the failed-wipe banner), not on this Text: a host
|
||||
// that can be covered or removed could take the pending
|
||||
// dialog down with it.
|
||||
// This is the only entry point to App Info, but it reads as
|
||||
// static text; surface the tap. (The triple-tap panic wipe
|
||||
// stays undiscoverable on purpose — it's destructive.)
|
||||
// The gear in the icon cluster is the discoverable entry
|
||||
// point to App Info; the logo tap stays as a shortcut but
|
||||
// reads as static text, so surface it. (The triple-tap panic
|
||||
// wipe stays undiscoverable on purpose — it's destructive.)
|
||||
.accessibilityAddTraits(.isButton)
|
||||
.accessibilityHint(
|
||||
String(localized: "content.accessibility.app_info_hint", comment: "Accessibility hint on the bitchat/ logo explaining a tap opens app info")
|
||||
@ -128,7 +129,7 @@ struct ContentHeaderView: View {
|
||||
if locationChannelsModel.gatewayEnabled {
|
||||
// The gateway toggle lives in the App Info settings pane
|
||||
// now, so the indicator deep-links there.
|
||||
Button(action: { appChromeModel.presentAppInfo() }) {
|
||||
Button(action: { appChromeModel.presentAppInfo(pane: .settings) }) {
|
||||
Image(systemName: "globe")
|
||||
.font(.bitchatSystem(size: 12))
|
||||
.foregroundColor(palette.secondary.opacity(0.8))
|
||||
@ -309,6 +310,26 @@ struct ContentHeaderView: View {
|
||||
? String(localized: "content.accessibility.peers_connected", comment: "Accessibility value when peers are reachable")
|
||||
: String(localized: "content.accessibility.peers_none", comment: "Accessibility value when no peers are reachable"))
|
||||
)
|
||||
|
||||
// Settings entry point new users can actually find; the
|
||||
// logo tap remains as a shortcut for those who know it.
|
||||
// Opens on the settings pane explicitly: the sheet's pane
|
||||
// memory defaults to info, and a button labeled "settings"
|
||||
// must not land there.
|
||||
Button(action: { appChromeModel.presentAppInfo(pane: .settings) }) {
|
||||
Image(systemName: "gearshape")
|
||||
.font(.bitchatSystem(size: 12))
|
||||
.foregroundColor(palette.secondary.opacity(0.9))
|
||||
.headerTapTarget()
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.leading, 4)
|
||||
.accessibilityLabel(
|
||||
String(localized: "content.header.settings", defaultValue: "settings", comment: "Accessibility label and tooltip for the header gear button that opens the settings/app info sheet")
|
||||
)
|
||||
.help(
|
||||
String(localized: "content.header.settings", defaultValue: "settings", comment: "Accessibility label and tooltip for the header gear button that opens the settings/app info sheet")
|
||||
)
|
||||
}
|
||||
.layoutPriority(3)
|
||||
.sheet(isPresented: $showVerifySheet) {
|
||||
|
||||
@ -462,6 +462,49 @@ struct AppArchitectureTests {
|
||||
#expect(chromeModel.showingFingerprintFor == nil)
|
||||
}
|
||||
|
||||
@Test("App Info pane routing: a promised pane overrides the sticky memory, a plain present never touches it")
|
||||
@MainActor
|
||||
func presentAppInfoPaneRouting() {
|
||||
let viewModel = makeArchitectureViewModel()
|
||||
let privateInboxModel = PrivateInboxModel(conversations: ConversationStore())
|
||||
let chromeModel = AppChromeModel(chatViewModel: viewModel, privateInboxModel: privateInboxModel)
|
||||
|
||||
// The sticky pane lives in standard defaults (@AppStorage's store);
|
||||
// restore whatever was there so this test can't leak state.
|
||||
let key = "appInfo.selectedPane"
|
||||
let defaults = UserDefaults.standard
|
||||
let original = defaults.string(forKey: key)
|
||||
defer {
|
||||
if let original {
|
||||
defaults.set(original, forKey: key)
|
||||
} else {
|
||||
defaults.removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
// First-ever open: the logo tap passes no pane, and the absent key is
|
||||
// what lands new users on the gentler Info default — a plain present
|
||||
// must not create it.
|
||||
defaults.removeObject(forKey: key)
|
||||
chromeModel.presentAppInfo()
|
||||
#expect(defaults.string(forKey: key) == nil)
|
||||
#expect(chromeModel.isAppInfoPresented)
|
||||
|
||||
// Later opens: the logo tap keeps whatever pane the sheet was left on.
|
||||
chromeModel.isAppInfoPresented = false
|
||||
defaults.set(AppInfoView.Pane.info.rawValue, forKey: key)
|
||||
chromeModel.presentAppInfo()
|
||||
#expect(defaults.string(forKey: key) == AppInfoView.Pane.info.rawValue)
|
||||
#expect(chromeModel.isAppInfoPresented)
|
||||
|
||||
// Controls that promise a pane (the header gear and the gateway globe
|
||||
// both say "settings") must land there even against sticky memory.
|
||||
chromeModel.isAppInfoPresented = false
|
||||
chromeModel.presentAppInfo(pane: .settings)
|
||||
#expect(defaults.string(forKey: key) == AppInfoView.Pane.settings.rawValue)
|
||||
#expect(chromeModel.isAppInfoPresented)
|
||||
}
|
||||
|
||||
@Test("Triple-tap panic entry point only raises the confirmation dialog")
|
||||
@MainActor
|
||||
func requestPanicWipeAsksBeforeDestroying() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user