135 lines
4.5 KiB
Swift
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)
|
|
}
|