pageta-native/Sources/Features/AccountTabView.swift
Till JS c1e360f103 fix(marke): Verein-Domain auf mana.how
mana-ev.ch war die alte Adresse des Vereins — „-ev" ist eine deutsche
Rechtsform und in der Schweiz falsch. Seit 2026-08-01 ist mana.how die
Hauptadresse; die alte Domain leitet weiter und wird nicht gekuendigt.

Mail-Adressen ziehen mit: hallo@, kontakt@, datenschutz@, vorstand@,
verein@, seepuls@, herbatrium@ und takedown@ sind auf mana.how als
Aliase angelegt und per SMTP als zustellbar bestaetigt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 00:03:15 +02:00

149 lines
4.9 KiB
Swift

import ManaAuthUI
import ManaCore
import ManaLLMUI
import ManaTokens
import SwiftUI
/// Konto-Tab — vereinheitlicht über das geteilte `ManaAccountView`
/// (E-Mail/Passwort ändern, 2FA, Konto löschen, Abmelden, Sign-In-CTA).
/// Pageta-spezifische Sektionen (Darstellung, Badge, lokale KI, Feedback,
/// Über) hängen im `extra`-Slot und sind — soft-auth — immer sichtbar.
struct AccountTabView: View {
@Environment(AuthClient.self) private var auth
@Environment(ManaAuthGate.self) private var authGate
@AppStorage(BadgeManager.userDefaultsKey) private var badgeEnabled = false
@AppStorage(ManaTheme.storageKey) private var variantRaw: String = ManaTheme.pagetaDefault
.rawValue
@State private var syncEnabled = false
@State private var syncBusy = false
private var theme: ManaTheme {
ManaTheme(rawValue: variantRaw) ?? .paper
}
/// Kontext für die geteilten `ManaLLMUI`-Sections — Pageta-spezifisch.
/// Wird auch von der macOS-`SettingsView` konsumiert, deshalb static.
static let llmContext = ManaLLMContext(
useCaseShort: PagetaStrings.accountSummaryFeatureTitle,
useCaseLong: PagetaStrings.accountSummaryFeatureBody
)
var body: some View {
NavigationStack {
ManaAccountView(
auth: auth,
config: .init(showManaBalance: true,
changeEmailCallbackLink: URL(string: "https://pageta.com/auth/email-changed"),
signedOutMessage: PagetaStrings.accountLocalFirstNote
),
onSignIn: { authGate.require(reason: "konto") {} },
onSignOut: {
// Spotlight-Index leeren, sonst leaken die saved-Articles
// über Account-Wechsel.
await SpotlightIndex().clearAll()
await auth.signOut()
},
// Feedback an fester Position (geteilte ManaFeedbackCard, direkt
// nach dem Konto-Block) statt unscheinbar tief im extra-Slot.
feedbackClient: FeedbackClient(appId: "pageta") {
try? await auth.freshAccessToken()
},
// Geteilte „Über"-Sektion (App/Version/Build + Links) an fester
// Position ganz unten — ersetzt die frühere handgebaute Über-Section.
about: .init(
appName: "Pageta",
links: [
.init("pageta.com", URL(string: "https://pageta.com")!),
.init(
"Datenschutz",
URL(string: "https://app.pageta.com/datenschutz")!,
inApp: true
),
.init(
"Impressum",
URL(string: "https://mana.how/impressum")!,
inApp: true
)
]
),
extra: { appSections }
)
.navigationTitle("Konto")
.paperBackground()
.manaBrand(PagetaBrand.config(for: theme))
}
}
/// Pageta-spezifische Sektionen — immer sichtbar (auch ausgeloggt).
@ViewBuilder
private var appSections: some View {
PagetaAppearanceSection()
// Cloud-Sync ist Opt-in (DSGVO Art. 25): nur eingeloggt relevant, und
// ohne Zustimmung bleibt die Lese-Liste lokal auf diesem Gerät.
if case .signedIn = auth.status {
Section {
Toggle("Cloud-Sync", isOn: $syncEnabled)
.disabled(syncBusy)
.onChange(of: syncEnabled) { _, enabled in
Task { await handleSyncToggle(enabled: enabled) }
}
} header: {
Text("Synchronisierung")
} footer: {
Text(
"Deine Lese-Liste Ende-zu-Ende-verschlüsselt über deine Geräte synchronisieren. Standardmäßig aus — ohne Zustimmung bleibt alles nur auf diesem Gerät."
)
}
.task { syncEnabled = EventSyncCoordinator.shared?.hasSyncConsent ?? false }
}
Section {
Toggle("App-Icon-Badge zeigen", isOn: $badgeEnabled)
.onChange(of: badgeEnabled) { _, enabled in
Task { await handleBadgeToggle(enabled: enabled) }
}
} header: {
Text("Benachrichtigungen")
} footer: {
Text(
PagetaStrings.settingsBadgeHintIos
)
}
// KI-Einstellungen hinter einer eigenen Unterseite — eine Zeile
// statt der Sections inline (flottenweites Muster). Auf macOS lebt
// dieselbe Konfig im KI-Tab der Settings-Scene (⌘,).
Section {
ManaLLMSettingsLink(context: AccountTabView.llmContext)
}
}
/// Wenn der User Badge aktiviert: System-Permission anfragen, bei
/// Denial das Toggle zurücksetzen. Wenn deaktiviert: Badge auf 0
/// löschen, damit die alte Zahl nicht stehenbleibt.
/// Persistiert die Cloud-Sync-Einwilligung + schaltet die Engine. Der
/// Vergleich mit `hasSyncConsent` verhindert, dass der Initial-Load in
/// `.task` (der `syncEnabled` setzt) fälschlich als User-Toggle persistiert.
private func handleSyncToggle(enabled: Bool) async {
guard let coordinator = EventSyncCoordinator.shared else { return }
if enabled == coordinator.hasSyncConsent { return }
syncBusy = true
defer { syncBusy = false }
do {
try await coordinator.setSyncConsentAndPersist(enabled, auth: auth)
} catch {
syncEnabled = !enabled // Rollback bei Persist-Fehler
}
}
private func handleBadgeToggle(enabled: Bool) async {
if enabled {
let granted = await BadgeManager.requestPermission()
if !granted {
badgeEnabled = false
}
} else {
await BadgeManager.setBadge(count: 0)
}
}
}