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:
rene 2026-06-02 20:01:16 +02:00
parent a2646a18ef
commit d807db57a2
14 changed files with 307 additions and 6 deletions

View file

@ -0,0 +1,33 @@
import Foundation
/// Vom App-Target geschrieben, vom Widget-Target gelesen über die App Group
/// `group.app.banyaro.ios` geteilt. Bewusst in beiden Targets identisch
/// dupliziert (statt Shared/-pbxproj-Handarbeit); JSON-kompatibel.
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 save(_ data: HomeWidgetData) {
guard let defaults = UserDefaults(suiteName: appGroup),
let encoded = try? JSONEncoder().encode(data) else { return }
defaults.set(encoded, forKey: key)
}
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)
}
static func clear() {
UserDefaults(suiteName: appGroup)?.removeObject(forKey: key)
}
}

View file

@ -0,0 +1,32 @@
import AppIntents
/// Siri-/Kurzbefehl-Intent Gassi gehen": öffnet die App und stößt die
/// Aufzeichnung an (über das App-Group-Flag, das die App beim Aktivwerden liest).
struct StartWalkIntent: AppIntent {
static var title: LocalizedStringResource = "Gassi gehen"
static var description = IntentDescription("Startet die Aufzeichnung einer Gassi-Tour in Ban Yaro Go.")
static var openAppWhenRun: Bool = true
@MainActor
func perform() async throws -> some IntentResult {
WalkLauncher.requestStartViaAppGroup()
WalkLauncher.shared.pendingStart = true // Fast-Path, falls in-process
return .result()
}
}
/// Macht den Intent als Siri-Phrase + Kurzbefehl verfügbar (automatisch erkannt).
struct BanYaroAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: StartWalkIntent(),
phrases: [
"Gassi gehen mit \(.applicationName)",
"Geh Gassi mit \(.applicationName)",
"\(.applicationName) Gassi gehen"
],
shortTitle: "Gassi gehen",
systemImageName: "figure.walk"
)
}
}

View file

@ -0,0 +1,33 @@
import Foundation
import Observation
/// Brücke vom Siri-Kurzbefehl Gassi gehen" zur UI.
///
/// Der App Intent läuft evtl. außerhalb des App-Prozesses er setzt ein Flag in
/// der **App Group**. Beim Aktivwerden liest die App das Flag und stößt über
/// `pendingStart` den Wechsel auf den Aufnehmen-Tab + den Aufnahme-Start an.
@Observable
@MainActor
final class WalkLauncher {
static let shared = WalkLauncher()
private init() {}
/// UI-Signal: true Aufnehmen-Tab wählen und Aufnahme starten.
var pendingStart = false
static let appGroup = "group.app.banyaro.ios"
static let flagKey = "pendingStartWalk"
/// Vom App Intent aufgerufen (cross-process über die App Group).
static func requestStartViaAppGroup() {
UserDefaults(suiteName: appGroup)?.set(true, forKey: flagKey)
}
/// Beim Aktivwerden der App das Flag einlösen `pendingStart`.
func consumePendingFlag() {
let defaults = UserDefaults(suiteName: Self.appGroup)
guard defaults?.bool(forKey: Self.flagKey) == true else { return }
defaults?.removeObject(forKey: Self.flagKey)
pendingStart = true
}
}