moodlit-native/Widgets/MoodlitWidget/MoodlitWidgetBundle.swift
till 03dca7d84d μ-7.3: Widget (Small/Medium/Large) + Deep-Link-Handling
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>
2026-05-18 15:21:55 +02:00

87 lines
2.9 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
import WidgetKit
/// Widget-Bundle für Moodlit. Ein Widget mit drei Größen:
/// - small: zuletzt gespieltes Mood oder erstes Favorit als Glow-Tile
/// - medium: bis zu 4 Favoriten als Grid
/// - large: bis zu 9 Favoriten als 3×3-Grid + Count
///
/// Tap-Aktion: `widgetURL(moodlit://play/<id>)` öffnet die App im
/// MoodPlayerView für das jeweilige Mood.
@main
struct MoodlitWidgetBundle: WidgetBundle {
var body: some Widget {
MoodsWidget()
}
}
struct MoodsWidget: Widget {
let kind: String = "ev.mana.moodlit.moods"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: MoodsProvider()) { entry in
MoodsEntryView(entry: entry)
.containerBackground(.black, for: .widget)
.widgetURL(URL(string: "moodlit://"))
}
.configurationDisplayName("Meine Moods")
.description("Schnellzugriff auf deine Lieblings-Stimmungen.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
struct MoodsEntry: TimelineEntry {
let date: Date
let snapshot: WidgetSnapshot?
}
struct MoodsProvider: TimelineProvider {
func placeholder(in context: Context) -> MoodsEntry {
MoodsEntry(date: Date(), snapshot: previewSnapshot())
}
func getSnapshot(in context: Context, completion: @escaping (MoodsEntry) -> Void) {
let snap = WidgetSnapshotStore.read() ?? (context.isPreview ? previewSnapshot() : nil)
completion(MoodsEntry(date: Date(), snapshot: snap))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<MoodsEntry>) -> Void) {
let snap = WidgetSnapshotStore.read()
let entry = MoodsEntry(date: Date(), snapshot: snap)
// Refresh-Heartbeat alle 4h; App ruft `WidgetCenter.shared.
// reloadAllTimelines()` bei jedem `loadAll`/`toggleFavorite`
// das überschreibt diesen Heartbeat in der Praxis.
let next = Calendar.current.date(byAdding: .hour, value: 4, to: Date()) ?? Date()
completion(Timeline(entries: [entry], policy: .after(next)))
}
private func previewSnapshot() -> WidgetSnapshot {
WidgetSnapshot(
favorites: [
WidgetMood(id: "fire", name: "Fire",
colors: ["#ff6b35", "#ff4500", "#dc143c", "#8b0000"], animation: "candle"),
WidgetMood(id: "ocean", name: "Ocean",
colors: ["#48dbfb", "#0abde3", "#10ac84", "#1dd1a1"], animation: "wave"),
WidgetMood(id: "midnight", name: "Midnight",
colors: ["#0c0c0c", "#1a1a2e", "#16213e", "#0f3460"], animation: "breath"),
],
lastPlayed: WidgetMood(id: "fire", name: "Fire",
colors: ["#ff6b35", "#ff4500", "#dc143c", "#8b0000"], animation: "candle"),
updatedAt: Date()
)
}
}
struct MoodsEntryView: View {
@Environment(\.widgetFamily) private var family
let entry: MoodsEntry
var body: some View {
switch family {
case .systemSmall: SmallMoodsView(entry: entry)
case .systemMedium: MediumMoodsView(entry: entry)
case .systemLarge: LargeMoodsView(entry: entry)
default: SmallMoodsView(entry: entry)
}
}
}