Natives iOS-App-Gerüst (Xcode 26, synchronisierte Ordner, iOS 18+). Features: - Gewohnheiten anlegen (Name, SF-Symbol, Farbe), heute abhaken, Streaks, Löschen - Detailansicht mit Monatskalender (Tage nachtragbar) und Statistiken - Tägliche Erinnerungen via lokale Notifications - Home-Screen-Widget (klein/mittel) mit App-Group-Datenaustausch
61 lines
1.8 KiB
Swift
61 lines
1.8 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - Snapshot shared between app and widget
|
|
|
|
struct WidgetSnapshot: Codable {
|
|
struct Item: Codable, Identifiable {
|
|
var id: UUID
|
|
var name: String
|
|
var symbolName: String
|
|
var colorHex: String
|
|
var isDoneToday: Bool
|
|
}
|
|
|
|
var generatedAt: Date
|
|
var items: [Item]
|
|
|
|
static let empty = WidgetSnapshot(generatedAt: .now, items: [])
|
|
}
|
|
|
|
// MARK: - App Group backed store
|
|
|
|
enum SharedStore {
|
|
static let appGroupID = "group.de.motocamp.HabitTracker"
|
|
private static let key = "widgetSnapshot"
|
|
|
|
private static var defaults: UserDefaults? {
|
|
UserDefaults(suiteName: appGroupID)
|
|
}
|
|
|
|
static func save(_ snapshot: WidgetSnapshot) {
|
|
guard let data = try? JSONEncoder().encode(snapshot) else { return }
|
|
defaults?.set(data, forKey: key)
|
|
}
|
|
|
|
static func load() -> WidgetSnapshot {
|
|
guard let data = defaults?.data(forKey: key),
|
|
let snapshot = try? JSONDecoder().decode(WidgetSnapshot.self, from: data)
|
|
else { return .empty }
|
|
return snapshot
|
|
}
|
|
}
|
|
|
|
// MARK: - Color from hex (used by both targets)
|
|
|
|
extension Color {
|
|
/// Creates a color from a "#RRGGBB" hex string. Falls back to system green on bad input.
|
|
init(hex: String) {
|
|
let cleaned = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
|
|
var value: UInt64 = 0
|
|
Scanner(string: cleaned).scanHexInt64(&value)
|
|
|
|
if cleaned.count == 6 {
|
|
let r = Double((value >> 16) & 0xFF) / 255
|
|
let g = Double((value >> 8) & 0xFF) / 255
|
|
let b = Double(value & 0xFF) / 255
|
|
self.init(.sRGB, red: r, green: g, blue: b)
|
|
} else {
|
|
self.init(.sRGB, red: 52 / 255, green: 199 / 255, blue: 89 / 255)
|
|
}
|
|
}
|
|
}
|