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>
149 lines
4.9 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|