WidgetSnapshot-Bridge App ↔ Widget via App-Group-UserDefaults (`group.ev.mana.moodlit`). MoodStore.refreshWidgetSnapshot läuft nach loadAll + toggleFavorite und pingt WidgetCenter. Widget-Extension (`ev.mana.moodlit.widget`, iOS-only app-extension): - Small: Last-Played oder erstes Favorit als Glow-Tile + Name + Animation-Slug - Medium: 2×2-Grid, bis zu 4 Favoriten, jede Kachel hat eigene Link-Destination zum App-Player - Large: 3×3-Grid, bis zu 9 Favoriten + Footer mit Total-Count - Empty-State, wenn keine Favoriten gesetzt sind Deep-Links: - `moodlit://play/<id>` (Custom-Scheme aus Widget-Tap): `url.host == "play"`, ID aus pathComponents - `https://moodlit.mana.how/play/<id>` (Universal-Link via AASA): pathComponents == ["/", "play", "<id>"] Beide öffnen MoodPlayerView als fullScreenCover direkt auf RootView (unabhängig vom aktiven Tab). Wegen Widget-Target-Sharing: `Mood.swiftUIColors` aus HexColor.swift nach Mood+SwiftUI.swift extrahiert (Widget kennt den Mood-Type nicht). xcodebuild iOS-Sim + macOS beide BUILD SUCCEEDED. Widget-Extension korrekt eingebettet in `MoodlitNative.app/PlugIns/`. 11 Unit-Tests weiter grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
67 lines
1.6 KiB
Swift
67 lines
1.6 KiB
Swift
import SwiftUI
|
||
import WidgetKit
|
||
|
||
/// Medium-Widget — 2×2-Grid mit bis zu 4 Favoriten. Tap auf eine
|
||
/// Kachel öffnet die App im Player für das jeweilige Mood.
|
||
struct MediumMoodsView: View {
|
||
let entry: MoodsEntry
|
||
|
||
private var moods: [WidgetMood] {
|
||
Array((entry.snapshot?.favorites ?? []).prefix(4))
|
||
}
|
||
|
||
var body: some View {
|
||
if moods.isEmpty {
|
||
emptyState
|
||
} else {
|
||
grid
|
||
}
|
||
}
|
||
|
||
private var grid: some View {
|
||
LazyVGrid(columns: [GridItem(.flexible(), spacing: 6), GridItem(.flexible(), spacing: 6)], spacing: 6) {
|
||
ForEach(moods) { mood in
|
||
Link(destination: URL(string: "moodlit://play/\(mood.id)")!) {
|
||
tile(mood)
|
||
}
|
||
}
|
||
}
|
||
.padding(6)
|
||
}
|
||
|
||
private func tile(_ mood: WidgetMood) -> some View {
|
||
ZStack(alignment: .bottomLeading) {
|
||
LinearGradient(
|
||
colors: mood.colors.map { Color(hex: $0) },
|
||
startPoint: .topLeading,
|
||
endPoint: .bottomTrailing
|
||
)
|
||
LinearGradient(
|
||
colors: [.black.opacity(0.55), .clear],
|
||
startPoint: .bottom,
|
||
endPoint: .center
|
||
)
|
||
Text(mood.name)
|
||
.font(.caption.weight(.semibold))
|
||
.foregroundStyle(.white)
|
||
.shadow(radius: 3)
|
||
.padding(6)
|
||
}
|
||
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
|
||
}
|
||
|
||
private var emptyState: some View {
|
||
VStack(spacing: 8) {
|
||
Image(systemName: "heart").font(.title2)
|
||
.foregroundStyle(.white.opacity(0.7))
|
||
Text("Markiere Moods als Favorit")
|
||
.font(.subheadline)
|
||
.foregroundStyle(.white.opacity(0.85))
|
||
Text("in der Moodlit-App, dann erscheinen sie hier.")
|
||
.font(.caption2)
|
||
.foregroundStyle(.white.opacity(0.55))
|
||
.multilineTextAlignment(.center)
|
||
}
|
||
.padding(8)
|
||
}
|
||
}
|