import SwiftUI extension Color { /// Die Farben aus der Datenbank sind Hex-Zeichenketten wie `#3f51b5`. /// Alles, was nicht passt, wird zum neutralen Grau – eine kaputte Farbe /// darf keine Ansicht leer lassen. init(hex: String) { var text = hex.trimmingCharacters(in: .whitespacesAndNewlines) if text.hasPrefix("#") { text.removeFirst() } // Das Backend erlaubt acht Stellen (#rrggbbaa); die Durchsicht nehmen // wir mit, der Rest wird ignoriert. guard text.count == 6 || text.count == 8, let zahl = UInt64(text, radix: 16) else { self = Color(red: 0.42, green: 0.45, blue: 0.50) return } let hatAlpha = text.count == 8 let r = Double((zahl >> (hatAlpha ? 24 : 16)) & 0xFF) / 255 let g = Double((zahl >> (hatAlpha ? 16 : 8)) & 0xFF) / 255 let b = Double((zahl >> (hatAlpha ? 8 : 0)) & 0xFF) / 255 let a = hatAlpha ? Double(zahl & 0xFF) / 255 : 1 self = Color(.sRGB, red: r, green: g, blue: b, opacity: a) } } /// Übersetzt die Symbolnamen aus der Datenbank in SF Symbols. /// /// Die Weboberfläche benutzt Lucide-Namen und hält dafür ein eigenes /// SVG-Verzeichnis (`web/src/components/Icon.jsx`, knapp 600 Zeilen). Nativ /// wäre das Unsinn: iOS bringt die Symbole mit, sie passen zur Systemschrift /// und können fett, farbig und in jeder Grösse. Nur die Namen muss jemand /// zuordnen – das ist diese Tabelle. /// /// Die Liste deckt `ACCOUNT_ICONS` und `CATEGORY_ICONS` aus /// `web/src/components/IconPicker.jsx` ab. Unbekanntes wird zum Platzhalter, /// nie zu einer leeren Stelle. enum Symbole { static let platzhalter = "circle.dashed" private static let tabelle: [String: String] = [ // Konten "wallet": "wallet.bifold", "landmark": "building.columns", "piggy-bank": "banknote", "vault": "lock.square.stack", "credit-card": "creditcard", "trending-up": "chart.line.uptrend.xyaxis", "banknote": "banknote", "coins": "centsign.circle", // Wohnen "home": "house", "bed": "bed.double", "zap": "bolt", "droplet": "drop", "flame": "flame", "wifi": "wifi", "monitor": "display", "cloud": "cloud", // Alltag "shopping-cart": "cart", "shopping-bag": "bag", "store": "storefront", "utensils": "fork.knife", "coffee": "cup.and.saucer", "wine": "wineglass", "shirt": "tshirt", "scissors": "scissors", "sparkle": "sparkles", // Unterwegs "car": "car", "fuel": "fuelpump", "bus": "bus", "bike": "bicycle", "plane": "airplane", "ticket": "ticket", // Gesundheit "heart-pulse": "heart.text.square", "pill": "pills", "dumbbell": "figure.strengthtraining.traditional", // Freizeit "film": "film", "music": "music.note", "book-open": "book", "newspaper": "newspaper", "graduation-cap": "graduationcap", "paw-print": "pawprint", // Familie "sprout": "leaf", "cake": "birthday.cake", "gift": "gift", "baby": "figure.and.child.holdinghands", "heart": "heart", // Vorsorge "umbrella": "umbrella", "life-buoy": "lifepreserver", "shield": "shield", "scale": "scalemass", // Sonstiges "briefcase": "briefcase", "wrench": "wrench.adjustable", "hammer": "hammer", "phone": "phone", "refresh-ccw": "arrow.triangle.2.circlepath", "percent": "percent", "receipt": "receipt", "list": "list.bullet", "more-horizontal": "ellipsis", // In der Oberfläche benutzt, nicht in der Auswahl "tags": "tag", "inbox": "tray", "settings": "gearshape", "arrow-left-right": "arrow.left.arrow.right", "pie-chart": "chart.pie", "dashboard": "square.grid.2x2", "download": "square.and.arrow.down", "calendar": "calendar", "search": "magnifyingglass", "edit": "pencil", "trash": "trash", "plus": "plus", "check": "checkmark", "close": "xmark", "chevron-down": "chevron.down", ] static func sf(_ name: String?) -> String { guard let name, let treffer = tabelle[name] else { return platzhalter } return treffer } } /// Ein farbiges Symbolplättchen – das native Gegenstück zum `IconChip` im Web. struct Symbolplakette: View { let symbol: String? let farbe: String var groesse: CGFloat = 34 var body: some View { let farbwert = Color(hex: farbe) RoundedRectangle(cornerRadius: groesse * 0.28, style: .continuous) .fill(farbwert.opacity(0.16)) .frame(width: groesse, height: groesse) .overlay { Image(systemName: Symbole.sf(symbol)) .font(.system(size: groesse * 0.46, weight: .medium)) .foregroundStyle(farbwert) } .accessibilityHidden(true) } }