1.1: Home-Screen-Widget + Siri-Kurzbefehl „Gassi gehen"
App Group group.app.banyaro.ios verbindet App und Widget-Extension (Entitlements in beiden Targets, CODE_SIGN_ENTITLEMENTS fürs Widget). Home-Screen-Widget (D): - BanYaroHomeWidget (klein + mittel): Tagesfoto, Hundename, nächster Termin. - App schreibt beim Heim-Laden einen Snapshot (HomeWidgetData) in die App Group und triggert WidgetCenter-Reload; Snapshot wird bei Logout/401 geleert. Siri-/Kurzbefehl (E): - StartWalkIntent „Gassi gehen" + AppShortcutsProvider (öffnet die App). - WalkLauncher überbrückt Intent → UI: Flag in der App Group, beim Aktivwerden eingelöst → Aufnehmen-Tab + Aufnahme-Start (TrackingView.startFresh). - MainTabView mit Tab-Auswahl (Tags), BanYaroGoApp liest scenePhase.
This commit is contained in:
parent
a2646a18ef
commit
d807db57a2
14 changed files with 307 additions and 6 deletions
|
|
@ -5,5 +5,6 @@ import SwiftUI
|
|||
struct BanYaroGoWidgetBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
WalkLiveActivity()
|
||||
BanYaroHomeWidget()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
88
BanYaroGoWidget/BanYaroHomeWidget.swift
Normal file
88
BanYaroGoWidget/BanYaroHomeWidget.swift
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import WidgetKit
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
struct BanYaroEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let data: HomeWidgetData?
|
||||
}
|
||||
|
||||
struct BanYaroProvider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> BanYaroEntry {
|
||||
BanYaroEntry(date: Date(), data: nil)
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (BanYaroEntry) -> Void) {
|
||||
completion(BanYaroEntry(date: Date(), data: HomeWidgetStore.load()))
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<BanYaroEntry>) -> Void) {
|
||||
let entry = BanYaroEntry(date: Date(), data: HomeWidgetStore.load())
|
||||
// Die App pusht bei Updates reloadAllTimelines(); als Sicherheitsnetz
|
||||
// stündlich neu laden.
|
||||
let next = Calendar.current.date(byAdding: .hour, value: 1, to: Date())
|
||||
?? Date().addingTimeInterval(3600)
|
||||
completion(Timeline(entries: [entry], policy: .after(next)))
|
||||
}
|
||||
}
|
||||
|
||||
struct BanYaroHomeWidgetEntryView: View {
|
||||
@Environment(\.widgetFamily) private var family
|
||||
let entry: BanYaroEntry
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Spacer()
|
||||
Text(entry.data?.dogName ?? "Ban Yaro")
|
||||
.font(family == .systemSmall ? .headline : .title3)
|
||||
.bold()
|
||||
.foregroundStyle(.white)
|
||||
.shadow(radius: 3)
|
||||
if family != .systemSmall {
|
||||
if let appt = entry.data?.nextAppointment, !appt.isEmpty {
|
||||
Label(appt, systemImage: "calendar")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.white)
|
||||
.shadow(radius: 3)
|
||||
} else if let n = entry.data?.diaryCount, n > 0 {
|
||||
Label("\(n) Tagebuch-Einträge", systemImage: "book")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.white.opacity(0.9))
|
||||
.shadow(radius: 3)
|
||||
} else {
|
||||
Text("Schön, dass du da bist 🐾")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.white.opacity(0.9))
|
||||
.shadow(radius: 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
struct BanYaroHomeWidget: Widget {
|
||||
let kind = "BanYaroHomeWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: BanYaroProvider()) { entry in
|
||||
BanYaroHomeWidgetEntryView(entry: entry)
|
||||
.containerBackground(for: .widget) {
|
||||
if let jpeg = entry.data?.photoJPEG, let ui = UIImage(data: jpeg) {
|
||||
ZStack {
|
||||
Image(uiImage: ui).resizable().scaledToFill()
|
||||
LinearGradient(
|
||||
colors: [.black.opacity(0.0), .black.opacity(0.55)],
|
||||
startPoint: .center, endPoint: .bottom
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Color.accentColor.opacity(0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
.configurationDisplayName("Ban Yaro")
|
||||
.description("Tagesfoto deines Hundes und nächster Termin.")
|
||||
.supportedFamilies([.systemSmall, .systemMedium])
|
||||
}
|
||||
}
|
||||
22
BanYaroGoWidget/HomeWidgetData.swift
Normal file
22
BanYaroGoWidget/HomeWidgetData.swift
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import Foundation
|
||||
|
||||
/// Kopie im Widget-Target (identisch zur App-Variante). Über die App Group
|
||||
/// `group.app.banyaro.ios` liest das Widget den von der App geschriebenen Stand.
|
||||
struct HomeWidgetData: Codable {
|
||||
var dogName: String
|
||||
var photoJPEG: Data?
|
||||
var nextAppointment: String?
|
||||
var diaryCount: Int?
|
||||
var updatedAt: Date
|
||||
}
|
||||
|
||||
enum HomeWidgetStore {
|
||||
static let appGroup = "group.app.banyaro.ios"
|
||||
static let key = "homeWidgetData"
|
||||
|
||||
static func load() -> HomeWidgetData? {
|
||||
guard let defaults = UserDefaults(suiteName: appGroup),
|
||||
let data = defaults.data(forKey: key) else { return nil }
|
||||
return try? JSONDecoder().decode(HomeWidgetData.self, from: data)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue