Files
FeelAloud/Moment/Moment.swift
2026-07-22 22:18:33 +02:00

135 lines
4.5 KiB
Swift

import AppIntents
import Foundation
import SwiftUI
import WidgetKit
struct MomentProvider: TimelineProvider {
func placeholder(in context: Context) -> MomentEntry {
MomentEntry(date: .now, lastRating: nil, lastRecordedAt: nil)
}
func getSnapshot(in context: Context, completion: @escaping (MomentEntry) -> Void) {
completion(currentEntry())
}
func getTimeline(in context: Context, completion: @escaping (Timeline<MomentEntry>) -> Void) {
completion(Timeline(entries: [currentEntry()], policy: .never))
}
private func currentEntry() -> MomentEntry {
let defaults = MomentWidgetStorage.defaults
let lastRating = defaults.string(forKey: MomentWidgetStorage.lastRatingKey)
.flatMap(MomentRatingValue.init(rawValue:))
let lastRecordedAt = defaults.object(forKey: MomentWidgetStorage.lastRecordedAtKey) as? Date
return MomentEntry(date: .now, lastRating: lastRating, lastRecordedAt: lastRecordedAt)
}
}
struct MomentEntry: TimelineEntry {
let date: Date
let lastRating: MomentRatingValue?
let lastRecordedAt: Date?
}
struct MomentEntryView: View {
let entry: MomentEntry
private let ratings: [MomentRatingValue] = [
.veryGood,
.good,
.middle,
.bad,
.veryBad
]
var body: some View {
VStack(alignment: .leading, spacing: 14) {
HStack(alignment: .firstTextBaseline) {
Text("Wie geht es dir?")
.font(.system(size: 18, weight: .bold))
.foregroundStyle(.white)
.lineLimit(1)
.minimumScaleFactor(0.82)
.shadow(color: .black.opacity(0.12), radius: 2, y: 1)
Spacer(minLength: 6)
if showsSavedState {
Text("Gespeichert")
.font(.system(size: 10, weight: .bold))
.foregroundStyle(.white)
.lineLimit(1)
.minimumScaleFactor(0.75)
}
}
HStack(spacing: 7) {
ForEach(ratings, id: \.self) { rating in
Button(intent: TrackMomentIntent(rating: rating)) {
ZStack(alignment: .topTrailing) {
Text(rating.emoji)
.font(.system(size: entry.lastRating == rating ? 28 : 25, weight: .bold))
.frame(maxWidth: .infinity)
.frame(height: 42)
.background(buttonBackground(for: rating), in: RoundedRectangle(cornerRadius: 8))
if showsSavedState, entry.lastRating == rating {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 12, weight: .bold))
.foregroundStyle(.white, Color.green)
.offset(x: 4, y: -4)
}
}
}
.buttonStyle(.plain)
.accessibilityLabel(Text(rating.rawValue))
}
}
}
.padding(14)
.containerBackground(for: .widget) {
LinearGradient(
colors: [
Color(red: 0.82, green: 0.90, blue: 0.76),
Color(red: 0.66, green: 0.78, blue: 0.60)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
}
}
private var showsSavedState: Bool {
guard let lastRecordedAt = entry.lastRecordedAt else { return false }
return Date.now.timeIntervalSince(lastRecordedAt) < 90
}
private func buttonBackground(for rating: MomentRatingValue) -> Color {
guard showsSavedState, entry.lastRating == rating else {
return .white.opacity(0.68)
}
return .white.opacity(0.92)
}
}
struct Moment: Widget {
nonisolated static let kind = "Moment"
var body: some WidgetConfiguration {
StaticConfiguration(kind: Self.kind, provider: MomentProvider()) { entry in
MomentEntryView(entry: entry)
}
.configurationDisplayName("Momentaufnahme")
.description("Tracke deine Stimmung direkt vom Homescreen.")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
#Preview(as: .systemSmall) {
Moment()
} timeline: {
MomentEntry(date: .now, lastRating: .good, lastRecordedAt: .now)
}