feat: add iOS support and harden receipt scanning
This commit is contained in:
162
app/ios/ScanReceipts/Features/Settings/SettingsView.swift
Normal file
162
app/ios/ScanReceipts/Features/Settings/SettingsView.swift
Normal file
@@ -0,0 +1,162 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Tab root for "Konto" (see `App/MainTabView.swift`, which references
|
||||
/// `SettingsView()` with a zero-arg initializer as its third tab). Wrapped in
|
||||
/// its own `NavigationStack` — every push (folders, change password, delete
|
||||
/// account) and the Pro-upgrade sheet originate from here.
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject private var appState: AppState
|
||||
@State private var showPaywall = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
profileSection
|
||||
foldersSection
|
||||
accountSection
|
||||
}
|
||||
.zenithListBackground()
|
||||
.navigationTitle("Konto")
|
||||
.sheet(isPresented: $showPaywall) {
|
||||
PaywallView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Profil
|
||||
|
||||
@ViewBuilder
|
||||
private var profileSection: some View {
|
||||
Section {
|
||||
if let user = appState.currentUser {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(user.name?.isEmpty == false ? user.name! : "Unbenannt")
|
||||
.zenithHeadlineMdStyle()
|
||||
if let email = user.email {
|
||||
Text(email)
|
||||
.zenithBodySmStyle()
|
||||
}
|
||||
}
|
||||
.padding(.vertical, ZenithSpacing.unit)
|
||||
.listRowBackground(Color.zenithSurface)
|
||||
.listRowSeparatorTint(Color.zenithBorder)
|
||||
|
||||
if user.isPro {
|
||||
proStatusRow(for: user)
|
||||
.listRowBackground(Color.zenithSurface)
|
||||
.listRowSeparatorTint(Color.zenithBorder)
|
||||
} else {
|
||||
freeStatusRow
|
||||
.listRowBackground(Color.zenithSurface)
|
||||
.listRowSeparatorTint(Color.zenithBorder)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func proStatusRow(for user: User) -> some View {
|
||||
VStack(alignment: .leading, spacing: ZenithSpacing.xs) {
|
||||
HStack(spacing: ZenithSpacing.xs) {
|
||||
Text("PRO")
|
||||
.zenithLabelCapsStyle(color: .white)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 3)
|
||||
.background(Color.zenithBlack)
|
||||
ZenithChip(text: planDisplayName(user.plan))
|
||||
}
|
||||
if let expiresAt = user.expiresAt, let date = ISO8601.parse(expiresAt) {
|
||||
let formatted = date.formatted(date: .abbreviated, time: .omitted)
|
||||
Text(user.cancelAtPeriodEnd ? "Läuft aus am \(formatted)" : "Verlängert sich am \(formatted)")
|
||||
.zenithLabelMdStyle(color: .zenithMuted)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, ZenithSpacing.unit)
|
||||
}
|
||||
|
||||
private var freeStatusRow: some View {
|
||||
VStack(alignment: .leading, spacing: ZenithSpacing.xs) {
|
||||
Text("Kostenlos")
|
||||
.zenithBodyStyle(color: .zenithMuted)
|
||||
Button("Auf Pro upgraden") {
|
||||
showPaywall = true
|
||||
}
|
||||
.buttonStyle(.zenithPrimary)
|
||||
}
|
||||
.padding(.vertical, ZenithSpacing.unit)
|
||||
}
|
||||
|
||||
private func planDisplayName(_ plan: String) -> String {
|
||||
switch plan {
|
||||
case "weekly": return "Wochen-Pass"
|
||||
case "annual": return "Jahres-Pass"
|
||||
case "lifetime": return "Lifetime"
|
||||
case "free": return "Kostenlos"
|
||||
default: return plan.capitalized
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Ordner
|
||||
|
||||
private var foldersSection: some View {
|
||||
Section {
|
||||
NavigationLink {
|
||||
ProjectsListView()
|
||||
} label: {
|
||||
Label {
|
||||
Text("Ordner verwalten").zenithBodyStyle()
|
||||
} icon: {
|
||||
Image(systemName: "folder").foregroundStyle(Color.zenithMuted)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.zenithSurface)
|
||||
.listRowSeparatorTint(Color.zenithBorder)
|
||||
} header: {
|
||||
Text("Ordner").zenithLabelCapsStyle()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Konto
|
||||
|
||||
private var accountSection: some View {
|
||||
Section {
|
||||
NavigationLink {
|
||||
ChangePasswordView()
|
||||
} label: {
|
||||
Label {
|
||||
Text("Passwort ändern").zenithBodyStyle()
|
||||
} icon: {
|
||||
Image(systemName: "lock").foregroundStyle(Color.zenithMuted)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.zenithSurface)
|
||||
.listRowSeparatorTint(Color.zenithBorder)
|
||||
|
||||
Button {
|
||||
Task { await appState.logout() }
|
||||
} label: {
|
||||
Label {
|
||||
Text("Abmelden")
|
||||
} icon: {
|
||||
Image(systemName: "arrow.backward.square")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.zenithPlain)
|
||||
.listRowBackground(Color.zenithSurface)
|
||||
.listRowSeparatorTint(Color.zenithBorder)
|
||||
|
||||
NavigationLink {
|
||||
DeleteAccountView()
|
||||
} label: {
|
||||
Label {
|
||||
Text("Konto löschen").zenithBodyStyle(color: .zenithError)
|
||||
} icon: {
|
||||
Image(systemName: "trash").foregroundStyle(Color.zenithError)
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.zenithSurface)
|
||||
.listRowSeparatorTint(Color.zenithBorder)
|
||||
} header: {
|
||||
Text("Konto").zenithLabelCapsStyle()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user