- Default-Titelfarbe primary (Akzent) -> foreground (monochrom). Der unterstrichene Marken-Titel wirkt jetzt ruhig/einheitlich auf Haupt- und Unterseiten; Akzent bleibt Aktionen/Icons vorbehalten. - Neu in ManaComponentsUI: ManaScreenHeaderRow (Header als erste, randlos mitscrollende List-/Form-Zeile) + View.manaScreenHeaderTitleHidden() (versteckt System-navigationTitle, Zurueck-Pfeil bleibt). - ManaAccountView: optionaler title: -> traegt Konto-Titel ueber den globalen Header statt System-Titel (Default nil = altes Verhalten). - ManaView (Mana-Guthaben): globaler Header statt navigationTitle. - Package.swift: ManaComponentsUI-Dep fuer ManaAuthUI + ManaCreditsUI. CHANGELOG bewusst ausgelassen (Parallel-Session-WIP in der Datei). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
150 lines
5.4 KiB
Swift
150 lines
5.4 KiB
Swift
import ManaComponentsUI
|
|
import ManaCore
|
|
import SwiftUI
|
|
|
|
/// Die geteilte „Mana"-Seite für native mana-Apps — Pendant zur Web-
|
|
/// `<ManaPage>`: ökosystemweites Gesamt-Guthaben, der Topf-Split
|
|
/// (80/5/5/5/5 aus MISSION.md) und die letzten Bewegungen. Read-only —
|
|
/// Aufladen läuft heute über das Web. App-agnostisch (System-Styling).
|
|
public struct ManaView: View {
|
|
@State private var model: ManaCreditsModel
|
|
|
|
public init(client: ManaCreditsClient) {
|
|
_model = State(initialValue: ManaCreditsModel(client: client))
|
|
}
|
|
|
|
public var body: some View {
|
|
List {
|
|
// Globaler Seiten-Header (monochrom, unterstrichen) als erste Zeile —
|
|
// einheitlich mit allen anderen mana-Seiten statt System-Titel.
|
|
ManaScreenHeaderRow("Mana")
|
|
switch model.state {
|
|
case .idle, .loading:
|
|
Section {
|
|
HStack { Spacer(); ProgressView(); Spacer() }
|
|
.listRowBackground(Color.clear)
|
|
}
|
|
case let .failed(message):
|
|
Section {
|
|
Text("Guthaben konnte nicht geladen werden.")
|
|
.font(.subheadline)
|
|
Text(message)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
case .loaded:
|
|
balanceSection
|
|
toepfeSection
|
|
transactionsSection
|
|
footerSection
|
|
}
|
|
}
|
|
.manaScreenHeaderTitleHidden()
|
|
.task { if model.state == .idle { await model.load() } }
|
|
.refreshable { await model.load() }
|
|
}
|
|
|
|
// MARK: - Guthaben
|
|
|
|
@ViewBuilder
|
|
private var balanceSection: some View {
|
|
if let b = model.balance {
|
|
Section {
|
|
VStack(spacing: 4) {
|
|
Text(ManaFormat.mana(b.balance))
|
|
.font(.system(size: 40, weight: .bold, design: .rounded))
|
|
.foregroundStyle(Color.accentColor)
|
|
Text("Mana · \(ManaFormat.euro(b.balance))")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
HStack(spacing: 16) {
|
|
Text("Erhalten \(ManaFormat.mana(b.totalEarned))")
|
|
Text("Ausgegeben \(ManaFormat.mana(b.totalSpent))")
|
|
}
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
.padding(.top, 2)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 8)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Töpfe
|
|
|
|
private struct Topf: Identifiable {
|
|
let id = UUID()
|
|
let pct: Int
|
|
let label: String
|
|
let hint: String
|
|
}
|
|
|
|
private let toepfe: [Topf] = [
|
|
.init(pct: 80, label: "Leistung", hint: "Rechenkosten / Urheber:innen"),
|
|
.init(pct: 5, label: "Natur", hint: "Regionale Renaturierung"),
|
|
.init(pct: 5, label: "Infrastruktur", hint: "Eigene Rechen-Souveränität"),
|
|
.init(pct: 5, label: "Betrieb", hint: "Vereinsverwaltung"),
|
|
.init(pct: 5, label: "Rücklagen", hint: "Vereins-Reserve"),
|
|
]
|
|
|
|
private var toepfeSection: some View {
|
|
Section {
|
|
ForEach(toepfe) { t in
|
|
HStack(alignment: .firstTextBaseline) {
|
|
Text("\(t.pct) %")
|
|
.font(.subheadline.weight(.semibold).monospacedDigit())
|
|
.frame(width: 48, alignment: .leading)
|
|
VStack(alignment: .leading, spacing: 1) {
|
|
Text(t.label).font(.subheadline)
|
|
Text(t.hint).font(.caption2).foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
}
|
|
} header: {
|
|
Text("Wohin dein Mana fließt")
|
|
} footer: {
|
|
Text("Jede Mana-Ausgabe teilt sich so auf — transparent nach MISSION.md.")
|
|
}
|
|
}
|
|
|
|
// MARK: - Bewegungen
|
|
|
|
@ViewBuilder
|
|
private var transactionsSection: some View {
|
|
Section {
|
|
if model.transactions.isEmpty {
|
|
Text("Noch keine Bewegungen.")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
} else {
|
|
ForEach(model.transactions) { tx in
|
|
HStack {
|
|
VStack(alignment: .leading, spacing: 1) {
|
|
Text(tx.description.isEmpty ? tx.type : tx.description)
|
|
.font(.subheadline)
|
|
.lineLimit(1)
|
|
Text(ManaFormat.shortDate(tx.createdAt))
|
|
.font(.caption2)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
Spacer()
|
|
Text(ManaFormat.signedMana(tx.amount))
|
|
.font(.subheadline.weight(.semibold).monospacedDigit())
|
|
.foregroundStyle(tx.amount < 0 ? Color.red : Color.green)
|
|
}
|
|
}
|
|
}
|
|
} header: {
|
|
Text("Letzte Bewegungen")
|
|
}
|
|
}
|
|
|
|
private var footerSection: some View {
|
|
Section {
|
|
Text("Mana ist eine Währung für alle mana-Apps. 1 Mana = 1 Cent, verfällt nie. Aufladen im Web.")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
}
|