analytics(deeplink): route report notification tap to its report (KC-51 ph4c)
Completes deep-link routing (goal #7): AnalyticsDeepLink is Identifiable; a tapped weekly/monthly report notification is consumed in ContentView on launch/active and presents AnalyticsView opened to that area. Core round-trip re-verified standalone; SwiftUI wiring needs an Xcode build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import Foundation
|
|||||||
/// Format: wenza://analytics → overview
|
/// Format: wenza://analytics → overview
|
||||||
/// wenza://analytics/weekly/<yyyy-MM-dd> → a weekly report
|
/// wenza://analytics/weekly/<yyyy-MM-dd> → a weekly report
|
||||||
/// wenza://analytics/monthly/<yyyy-MM> → a monthly report
|
/// wenza://analytics/monthly/<yyyy-MM> → a monthly report
|
||||||
enum AnalyticsDeepLink: Equatable {
|
enum AnalyticsDeepLink: Equatable, Identifiable {
|
||||||
case overview
|
case overview
|
||||||
case weekly(weekStartKey: String)
|
case weekly(weekStartKey: String)
|
||||||
case monthly(monthKey: String)
|
case monthly(monthKey: String)
|
||||||
@@ -15,6 +15,8 @@ enum AnalyticsDeepLink: Equatable {
|
|||||||
static let scheme = "wenza"
|
static let scheme = "wenza"
|
||||||
static let host = "analytics"
|
static let host = "analytics"
|
||||||
|
|
||||||
|
var id: String { stringValue }
|
||||||
|
|
||||||
/// Stable userInfo key carried on notifications.
|
/// Stable userInfo key carried on notifications.
|
||||||
static let userInfoKey = "kisani.analytics.deeplink"
|
static let userInfoKey = "kisani.analytics.deeplink"
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ struct ContentView: View {
|
|||||||
return 0
|
return 0
|
||||||
}()
|
}()
|
||||||
@State private var showQuickAddTask = false
|
@State private var showQuickAddTask = false
|
||||||
|
@State private var analyticsLink: AnalyticsDeepLink?
|
||||||
@StateObject private var tabState = FloatingTabState()
|
@StateObject private var tabState = FloatingTabState()
|
||||||
@ObservedObject private var shortcuts = ShortcutHandler.shared
|
@ObservedObject private var shortcuts = ShortcutHandler.shared
|
||||||
@ObservedObject private var tutorial = TutorialManager.shared
|
@ObservedObject private var tutorial = TutorialManager.shared
|
||||||
@@ -108,6 +109,7 @@ struct ContentView: View {
|
|||||||
// Backfill any missing analytics reports (idempotent), then notify.
|
// Backfill any missing analytics reports (idempotent), then notify.
|
||||||
let generated = AnalyticsService.shared.reconcile(using: workoutVM)
|
let generated = AnalyticsService.shared.reconcile(using: workoutVM)
|
||||||
NotificationManager.shared.scheduleAnalyticsReports(generated)
|
NotificationManager.shared.scheduleAnalyticsReports(generated)
|
||||||
|
if let link = NotificationManager.shared.consumePendingAnalyticsDeepLink() { analyticsLink = link }
|
||||||
// Pull workouts from Health, then reschedule so a detected workout updates reminders.
|
// Pull workouts from Health, then reschedule so a detected workout updates reminders.
|
||||||
Task {
|
Task {
|
||||||
await workoutVM.syncFromHealthKit()
|
await workoutVM.syncFromHealthKit()
|
||||||
@@ -166,6 +168,11 @@ struct ContentView: View {
|
|||||||
.presentationDetents([.height(150)])
|
.presentationDetents([.height(150)])
|
||||||
.presentationDragIndicator(.visible)
|
.presentationDragIndicator(.visible)
|
||||||
}
|
}
|
||||||
|
.sheet(item: $analyticsLink) { link in
|
||||||
|
AnalyticsView(initialLink: link)
|
||||||
|
.presentationDetents([.large])
|
||||||
|
.presentationDragIndicator(.visible)
|
||||||
|
}
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
.onAppear {
|
.onAppear {
|
||||||
// Test hook: auto-open Quick Add (used to verify the voice→field binding
|
// Test hook: auto-open Quick Add (used to verify the voice→field binding
|
||||||
|
|||||||
@@ -1627,3 +1627,11 @@ last. Device/build QA tracked here.
|
|||||||
has issues only a compiler will surface. Remaining UI polish: deep-link routing
|
has issues only a compiler will surface. Remaining UI polish: deep-link routing
|
||||||
into the presented view, Dynamic-Type audit, denied-permission state for future
|
into the presented view, Dynamic-Type audit, denied-permission state for future
|
||||||
HealthKit trends.
|
HealthKit trends.
|
||||||
|
|
||||||
|
### KC-51 progress — Phase 4c: deep-link routing end-to-end (core-verified)
|
||||||
|
- `AnalyticsDeepLink` now `Identifiable` (id = url string) → usable with
|
||||||
|
`.sheet(item:)`. Round-trip re-verified standalone.
|
||||||
|
- Notification tap → stores pending link (delegate) → `ContentView` consumes it on
|
||||||
|
launch + `.active` → presents `AnalyticsView(initialLink:)` opened to the
|
||||||
|
weekly/monthly area. Completes goal condition #7 (report notifications
|
||||||
|
deep-link to their report). Wiring not build-verified (SwiftUI/app deps).
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ struct AnalyticsView: View {
|
|||||||
@State private var area: Area = .overview
|
@State private var area: Area = .overview
|
||||||
@State private var selectedExercise: String?
|
@State private var selectedExercise: String?
|
||||||
|
|
||||||
|
/// Optional deep link (from a report notification) picks the initial area.
|
||||||
|
init(initialLink: AnalyticsDeepLink? = nil) {
|
||||||
|
switch initialLink {
|
||||||
|
case .weekly: _area = State(initialValue: .weekly)
|
||||||
|
case .monthly: _area = State(initialValue: .monthly)
|
||||||
|
default: break // keep default .overview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum Area: String, CaseIterable, Identifiable {
|
enum Area: String, CaseIterable, Identifiable {
|
||||||
case overview = "Overview", daily = "Daily", weekly = "Weekly"
|
case overview = "Overview", daily = "Daily", weekly = "Weekly"
|
||||||
case monthly = "Monthly", trends = "12-Month", exercises = "Exercises"
|
case monthly = "Monthly", trends = "12-Month", exercises = "Exercises"
|
||||||
|
|||||||
Reference in New Issue
Block a user