mana-swift-ui/Sources/ManaCreditsUI/ManaView.swift
Till JS fd7d37f257 ManaScreenHeader: monochromer Titel-Default + Unterseiten-Adoption
- 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>
2026-06-12 16:29:50 +02:00

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)
}
}
}