From bcbcda5ba50e04668cf7c4a502f027a2a1bba7bb Mon Sep 17 00:00:00 2001 From: kutesir Date: Tue, 7 Jul 2026 23:36:45 +0300 Subject: [PATCH] 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 --- KisaniCal/Analytics/AnalyticsDeepLink.swift | 4 +++- KisaniCal/ContentView.swift | 7 +++++++ KisaniCal/ISSUES.md | 8 ++++++++ KisaniCal/Views/AnalyticsView.swift | 9 +++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/KisaniCal/Analytics/AnalyticsDeepLink.swift b/KisaniCal/Analytics/AnalyticsDeepLink.swift index a47e0f5..e785817 100644 --- a/KisaniCal/Analytics/AnalyticsDeepLink.swift +++ b/KisaniCal/Analytics/AnalyticsDeepLink.swift @@ -7,7 +7,7 @@ import Foundation /// Format: wenza://analytics → overview /// wenza://analytics/weekly/ → a weekly report /// wenza://analytics/monthly/ → a monthly report -enum AnalyticsDeepLink: Equatable { +enum AnalyticsDeepLink: Equatable, Identifiable { case overview case weekly(weekStartKey: String) case monthly(monthKey: String) @@ -15,6 +15,8 @@ enum AnalyticsDeepLink: Equatable { static let scheme = "wenza" static let host = "analytics" + var id: String { stringValue } + /// Stable userInfo key carried on notifications. static let userInfoKey = "kisani.analytics.deeplink" diff --git a/KisaniCal/ContentView.swift b/KisaniCal/ContentView.swift index 8ba9048..06332b5 100644 --- a/KisaniCal/ContentView.swift +++ b/KisaniCal/ContentView.swift @@ -18,6 +18,7 @@ struct ContentView: View { return 0 }() @State private var showQuickAddTask = false + @State private var analyticsLink: AnalyticsDeepLink? @StateObject private var tabState = FloatingTabState() @ObservedObject private var shortcuts = ShortcutHandler.shared @ObservedObject private var tutorial = TutorialManager.shared @@ -108,6 +109,7 @@ struct ContentView: View { // Backfill any missing analytics reports (idempotent), then notify. let generated = AnalyticsService.shared.reconcile(using: workoutVM) NotificationManager.shared.scheduleAnalyticsReports(generated) + if let link = NotificationManager.shared.consumePendingAnalyticsDeepLink() { analyticsLink = link } // Pull workouts from Health, then reschedule so a detected workout updates reminders. Task { await workoutVM.syncFromHealthKit() @@ -166,6 +168,11 @@ struct ContentView: View { .presentationDetents([.height(150)]) .presentationDragIndicator(.visible) } + .sheet(item: $analyticsLink) { link in + AnalyticsView(initialLink: link) + .presentationDetents([.large]) + .presentationDragIndicator(.visible) + } #if DEBUG .onAppear { // Test hook: auto-open Quick Add (used to verify the voice→field binding diff --git a/KisaniCal/ISSUES.md b/KisaniCal/ISSUES.md index fbcdfdb..c76148f 100644 --- a/KisaniCal/ISSUES.md +++ b/KisaniCal/ISSUES.md @@ -1627,3 +1627,11 @@ last. Device/build QA tracked here. 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 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). diff --git a/KisaniCal/Views/AnalyticsView.swift b/KisaniCal/Views/AnalyticsView.swift index 39c6825..83a1e1d 100644 --- a/KisaniCal/Views/AnalyticsView.swift +++ b/KisaniCal/Views/AnalyticsView.swift @@ -13,6 +13,15 @@ struct AnalyticsView: View { @State private var area: Area = .overview @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 { case overview = "Overview", daily = "Daily", weekly = "Weekly" case monthly = "Monthly", trends = "12-Month", exercises = "Exercises"