Initiales HabitTracker-Projekt: SwiftUI + SwiftData Gewohnheiten-Tracker
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
This commit is contained in:
commit
22b8f5d806
24 changed files with 1448 additions and 0 deletions
121
HabitTrackerWidget/HabitTrackerWidget.swift
Normal file
121
HabitTrackerWidget/HabitTrackerWidget.swift
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct HabitTimelineEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let snapshot: WidgetSnapshot
|
||||
}
|
||||
|
||||
struct Provider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> HabitTimelineEntry {
|
||||
HabitTimelineEntry(date: .now, snapshot: .empty)
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (HabitTimelineEntry) -> Void) {
|
||||
completion(HabitTimelineEntry(date: .now, snapshot: SharedStore.load()))
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<HabitTimelineEntry>) -> Void) {
|
||||
let entry = HabitTimelineEntry(date: .now, snapshot: SharedStore.load())
|
||||
// Reset "today" at the next midnight.
|
||||
let nextMidnight = Calendar.current.startOfDay(for: .now.addingTimeInterval(86_400))
|
||||
completion(Timeline(entries: [entry], policy: .after(nextMidnight)))
|
||||
}
|
||||
}
|
||||
|
||||
struct HabitTrackerWidgetEntryView: View {
|
||||
var entry: Provider.Entry
|
||||
@Environment(\.widgetFamily) private var family
|
||||
|
||||
private var items: [WidgetSnapshot.Item] { entry.snapshot.items }
|
||||
private var doneCount: Int { items.filter(\.isDoneToday).count }
|
||||
|
||||
var body: some View {
|
||||
if items.isEmpty {
|
||||
emptyState
|
||||
} else {
|
||||
switch family {
|
||||
case .systemSmall:
|
||||
smallView
|
||||
default:
|
||||
mediumView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var emptyState: some View {
|
||||
VStack(spacing: 6) {
|
||||
Image(systemName: "checklist")
|
||||
.font(.title)
|
||||
.foregroundStyle(.secondary)
|
||||
Text("Keine Gewohnheiten")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
HStack {
|
||||
Text("Heute")
|
||||
.font(.caption.bold())
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Text("\(doneCount)/\(items.count)")
|
||||
.font(.caption.bold())
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
private var smallView: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
header
|
||||
Spacer(minLength: 0)
|
||||
Text("\(doneCount)")
|
||||
.font(.system(size: 44, weight: .bold))
|
||||
+ Text(" / \(items.count)")
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundColor(.secondary)
|
||||
Text(doneCount == items.count ? "Alles erledigt 🎉" : "erledigt")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
private var mediumView: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
header
|
||||
ForEach(items.prefix(3)) { item in
|
||||
HStack(spacing: 10) {
|
||||
Image(systemName: item.symbolName)
|
||||
.foregroundStyle(Color(hex: item.colorHex))
|
||||
.frame(width: 22)
|
||||
Text(item.name)
|
||||
.font(.subheadline)
|
||||
.lineLimit(1)
|
||||
Spacer()
|
||||
Image(systemName: item.isDoneToday ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundStyle(item.isDoneToday ? Color(hex: item.colorHex) : .secondary)
|
||||
}
|
||||
}
|
||||
if items.count > 3 {
|
||||
Text("+ \(items.count - 3) weitere")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HabitTrackerWidget: Widget {
|
||||
let kind = "HabitTrackerWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
||||
HabitTrackerWidgetEntryView(entry: entry)
|
||||
.containerBackground(.fill.tertiary, for: .widget)
|
||||
}
|
||||
.configurationDisplayName("Gewohnheiten")
|
||||
.description("Dein Fortschritt von heute.")
|
||||
.supportedFamilies([.systemSmall, .systemMedium])
|
||||
}
|
||||
}
|
||||
9
HabitTrackerWidget/HabitTrackerWidgetBundle.swift
Normal file
9
HabitTrackerWidget/HabitTrackerWidgetBundle.swift
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct HabitTrackerWidgetBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
HabitTrackerWidget()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue