From d0d982f4f51a90bfacb14d93341b9b47ab6e25bc Mon Sep 17 00:00:00 2001 From: kutesir Date: Sat, 6 Jun 2026 19:29:25 +0300 Subject: [PATCH] Add unified task menu, Live Activity, and calendar event actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task context menu (new shared TaskMenuItems) consistent across Today, Matrix, and Calendar: Pin · Date · Move · Priority · Tags · Add to Live Activity · Delete. Adds TaskViewModel.setPriority. Live Activity (ActivityKit): - TaskActivityAttributes shared between app and widget targets - LiveActivityManager (start/update/end/toggle, iOS 16.1+) - TaskLiveActivity widget: lock-screen banner + Dynamic Island - NSSupportsLiveActivities via project.yml; project regenerated Calendar events (non-subscription, writable only): - Edit via system EKEventEditViewController (EventEditView wrapper) - Delete via new CalendarStore.deleteEvent Also sweeps in prior in-progress edits already present in the working tree (AuthManager, NotificationManager, TaskItem, task views) and the updated ISSUES.md. Co-Authored-By: Claude Opus 4.8 --- KisaniCal.xcodeproj/project.pbxproj | 24 +++ .../UserInterfaceState.xcuserstate | Bin 108163 -> 49476 bytes KisaniCal/ISSUES.md | 41 ++++- KisaniCal/Managers/AuthManager.swift | 5 + KisaniCal/Managers/LiveActivityManager.swift | 68 +++++++++ KisaniCal/Managers/NotificationManager.swift | 12 +- .../Managers/TaskActivityAttributes.swift | 18 +++ KisaniCal/Models/TaskItem.swift | 33 ++-- KisaniCal/Views/CalendarView.swift | 128 +++++++++++++--- KisaniCal/Views/MatrixView.swift | 144 +++++++++--------- KisaniCal/Views/TaskContextMenu.swift | 80 ++++++++++ KisaniCal/Views/TodayView.swift | 6 +- KisaniCalWidgets/KisaniCalWidgets.swift | 1 + KisaniCalWidgets/TaskLiveActivity.swift | 74 +++++++++ project.yml | 2 + 15 files changed, 525 insertions(+), 111 deletions(-) create mode 100644 KisaniCal/Managers/LiveActivityManager.swift create mode 100644 KisaniCal/Managers/TaskActivityAttributes.swift create mode 100644 KisaniCal/Views/TaskContextMenu.swift create mode 100644 KisaniCalWidgets/TaskLiveActivity.swift diff --git a/KisaniCal.xcodeproj/project.pbxproj b/KisaniCal.xcodeproj/project.pbxproj index 0f5ef69..c04e6a0 100644 --- a/KisaniCal.xcodeproj/project.pbxproj +++ b/KisaniCal.xcodeproj/project.pbxproj @@ -12,7 +12,9 @@ 12E42CE8B8E535FAE6268A0C /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF905C574F34B4EE51A8D21E /* AppGroup.swift */; }; 13E4B9854F595394FC9D5912 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5E179A9189B0A8C3F856F6 /* ContentView.swift */; }; 1A22EE21460821170E44B1DF /* ExerciseModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5722CC4B59E3939724142710 /* ExerciseModels.swift */; }; + 205846F9651EDCE0D8207358 /* TaskLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0513EB8C7F6B6853B9E93E09 /* TaskLiveActivity.swift */; }; 2CD2313EDC4BFCE6E89C6787 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF905C574F34B4EE51A8D21E /* AppGroup.swift */; }; + 32C63D81925FBFE51CAE1FB7 /* TaskActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEB07EB89E89383C32ADB34 /* TaskActivityAttributes.swift */; }; 3AD7F62D231DCFFFFCB31649 /* TutorialManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A27D42E141DC056D32C1A3 /* TutorialManager.swift */; }; 3B57EA3600AFE975850DF39A /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89550F2CD19B950CCC6AD37F /* AuthManager.swift */; }; 3C793FD5DA00D3E9C0D51FEC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 72FDF9C8DD37134576356B89 /* Assets.xcassets */; }; @@ -23,10 +25,13 @@ 5EF4A5B6CE91ADA0CCF72D0D /* ProfileSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C96EC00F6B021DBA3CC1A79 /* ProfileSetupView.swift */; }; 67CE1747E5DB3FDA79D0FDFD /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106EEF572C6F8990408329F0 /* OnboardingView.swift */; }; 6921CB73A3257502FF778381 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2D50B4B4AC227BD21F4B60 /* SplashView.swift */; }; + 703E068364256D6F3F867961 /* ISSUES.md in Resources */ = {isa = PBXBuildFile; fileRef = 92824ED40ECD41EFD4F78BEC /* ISSUES.md */; }; + 754D3DE3CEB998E36E585A61 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0506183945D16EC443A69651 /* LiveActivityManager.swift */; }; 79FC1DF6762C6F02D01AB643 /* KisaniCalWidgets.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7CEBC340BFA9238D121946AC /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 57714B79AFFF60DA90EB86E3 /* Preview Assets.xcassets */; }; 8BE9D3D650B0F06169EC7048 /* SharedComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A4491BFA50721082024756 /* SharedComponents.swift */; }; 8C9567A3DE3F63A1ECAE84D5 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3D5289C5F93484E22DEB63 /* TutorialView.swift */; }; + 8DA396DACE99DC4B7B4A460E /* TaskActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEB07EB89E89383C32ADB34 /* TaskActivityAttributes.swift */; }; 9070521B1D36A5551976C275 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADF6CCD95A587E26E30F5712 /* CalendarView.swift */; }; 9E3F966F6B6AF2E4F8109E51 /* WorkoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20DAD771EFCC8B3B1DBD4DD6 /* WorkoutView.swift */; }; A1DBC4D2F09C02B8CEE6449E /* AddExerciseSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 768D4E314252E2A90A06CCF2 /* AddExerciseSheet.swift */; }; @@ -40,6 +45,7 @@ CBE7295BF5ADE08FE93AFAAF /* FloatingTabState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9100804DB1E61EA882CC54DA /* FloatingTabState.swift */; }; D591A72235A53D4038FBC2B4 /* KisaniCalApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */; }; ECEAA5CF7309E5993D12B571 /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AD014B7E3E30A34E18696A0 /* AuthView.swift */; }; + EE7BCEB45B1F0B91C9D3C1D2 /* TaskContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 670A8C6F8243EC973A1BC431 /* TaskContextMenu.swift */; }; EF03EC9F974B14D100DD5528 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D045FE3DEB1D22D908A29F /* NotificationManager.swift */; }; /* End PBXBuildFile section */ @@ -70,6 +76,8 @@ /* Begin PBXFileReference section */ 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KisaniCalApp.swift; sourceTree = ""; }; 01A27D42E141DC056D32C1A3 /* TutorialManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialManager.swift; sourceTree = ""; }; + 0506183945D16EC443A69651 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = ""; }; + 0513EB8C7F6B6853B9E93E09 /* TaskLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskLiveActivity.swift; sourceTree = ""; }; 0C96EC00F6B021DBA3CC1A79 /* ProfileSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSetupView.swift; sourceTree = ""; }; 0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = KisaniCalWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 106EEF572C6F8990408329F0 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; @@ -85,6 +93,7 @@ 5722CC4B59E3939724142710 /* ExerciseModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExerciseModels.swift; sourceTree = ""; }; 57714B79AFFF60DA90EB86E3 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 61BFF3BA28331A16D0ADE9D0 /* WidgetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetData.swift; sourceTree = ""; }; + 670A8C6F8243EC973A1BC431 /* TaskContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskContextMenu.swift; sourceTree = ""; }; 72308FEE0226F45414C04DDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 72FDF9C8DD37134576356B89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 768D4E314252E2A90A06CCF2 /* AddExerciseSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExerciseSheet.swift; sourceTree = ""; }; @@ -92,6 +101,7 @@ 8DC8687EA2FBA9FB2EEE51C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8E7FC6446D5C5B2A2D9387DA /* KisaniCalWidgets.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KisaniCalWidgets.entitlements; sourceTree = ""; }; 9100804DB1E61EA882CC54DA /* FloatingTabState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingTabState.swift; sourceTree = ""; }; + 92824ED40ECD41EFD4F78BEC /* ISSUES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ISSUES.md; sourceTree = ""; }; 93D045FE3DEB1D22D908A29F /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = KisaniCal.app; sourceTree = BUILT_PRODUCTS_DIR; }; ADF6CCD95A587E26E30F5712 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = ""; }; @@ -104,6 +114,7 @@ DCC2AFB4FA765383740767CB /* TaskItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskItem.swift; sourceTree = ""; }; FA3D5289C5F93484E22DEB63 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = ""; }; FC17C7BA15BD2E84AE5F77CF /* EventCountdownWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCountdownWidget.swift; sourceTree = ""; }; + FEEB07EB89E89383C32ADB34 /* TaskActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskActivityAttributes.swift; sourceTree = ""; }; FF5AFD143B693B77D07FBDA4 /* HealthKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -155,6 +166,7 @@ 0C96EC00F6B021DBA3CC1A79 /* ProfileSetupView.swift */, 72308FEE0226F45414C04DDD /* SettingsView.swift */, BE2D50B4B4AC227BD21F4B60 /* SplashView.swift */, + 670A8C6F8243EC973A1BC431 /* TaskContextMenu.swift */, C786EBC7DF879D64EB28165E /* TodayView.swift */, FA3D5289C5F93484E22DEB63 /* TutorialView.swift */, 20DAD771EFCC8B3B1DBD4DD6 /* WorkoutView.swift */, @@ -186,6 +198,7 @@ 8E7FC6446D5C5B2A2D9387DA /* KisaniCalWidgets.entitlements */, 42650F655DB8B320C7C03929 /* KisaniCalWidgets.swift */, 208F82348DEBD9FF7B0DCF17 /* MyTasksWidget.swift */, + 0513EB8C7F6B6853B9E93E09 /* TaskLiveActivity.swift */, 61BFF3BA28331A16D0ADE9D0 /* WidgetData.swift */, 429806CE1021C8DE2EB770CE /* WidgetViews.swift */, ); @@ -197,6 +210,7 @@ children = ( 72FDF9C8DD37134576356B89 /* Assets.xcassets */, BC5E179A9189B0A8C3F856F6 /* ContentView.swift */, + 92824ED40ECD41EFD4F78BEC /* ISSUES.md */, 1AA7498EFED7692022F3E7E1 /* KisaniCal.entitlements */, 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */, 21B93C269F283F11B415B18C /* Components */, @@ -216,8 +230,10 @@ 89550F2CD19B950CCC6AD37F /* AuthManager.swift */, 449C34805DC6B2CB66886544 /* CloudSyncManager.swift */, FF5AFD143B693B77D07FBDA4 /* HealthKitManager.swift */, + 0506183945D16EC443A69651 /* LiveActivityManager.swift */, 93D045FE3DEB1D22D908A29F /* NotificationManager.swift */, 326B77A7B317A7DB44E13EA5 /* ShortcutHandler.swift */, + FEEB07EB89E89383C32ADB34 /* TaskActivityAttributes.swift */, 01A27D42E141DC056D32C1A3 /* TutorialManager.swift */, ); path = Managers; @@ -308,6 +324,7 @@ buildActionMask = 2147483647; files = ( 3C793FD5DA00D3E9C0D51FEC /* Assets.xcassets in Resources */, + 703E068364256D6F3F867961 /* ISSUES.md in Resources */, 7CEBC340BFA9238D121946AC /* Preview Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -331,6 +348,7 @@ CBE7295BF5ADE08FE93AFAAF /* FloatingTabState.swift in Sources */, BD0DB4B0AA8A63D124EDFF2C /* HealthKitManager.swift in Sources */, D591A72235A53D4038FBC2B4 /* KisaniCalApp.swift in Sources */, + 754D3DE3CEB998E36E585A61 /* LiveActivityManager.swift in Sources */, BEAFF968632A34C70B11C5AC /* MatrixView.swift in Sources */, EF03EC9F974B14D100DD5528 /* NotificationManager.swift in Sources */, 67CE1747E5DB3FDA79D0FDFD /* OnboardingView.swift in Sources */, @@ -339,6 +357,8 @@ 8BE9D3D650B0F06169EC7048 /* SharedComponents.swift in Sources */, 497732557745AE9BDA44FB2F /* ShortcutHandler.swift in Sources */, 6921CB73A3257502FF778381 /* SplashView.swift in Sources */, + 32C63D81925FBFE51CAE1FB7 /* TaskActivityAttributes.swift in Sources */, + EE7BCEB45B1F0B91C9D3C1D2 /* TaskContextMenu.swift in Sources */, 096804560A2F0A7D74E64780 /* TaskItem.swift in Sources */, C79C33BE2802E81AA00175CC /* TodayView.swift in Sources */, 3AD7F62D231DCFFFFCB31649 /* TutorialManager.swift in Sources */, @@ -355,6 +375,8 @@ 552E2F85B01C9CC437D383B5 /* EventCountdownWidget.swift in Sources */, C7767143D9617ECA04ED1935 /* KisaniCalWidgets.swift in Sources */, 3E9DE1CF20BAC479805DF940 /* MyTasksWidget.swift in Sources */, + 8DA396DACE99DC4B7B4A460E /* TaskActivityAttributes.swift in Sources */, + 205846F9651EDCE0D8207358 /* TaskLiveActivity.swift in Sources */, AC0D814DA54D5EF5E25CEB99 /* WidgetData.swift in Sources */, 55F00C433F853F7B54F136B3 /* WidgetViews.swift in Sources */, ); @@ -386,6 +408,7 @@ INFOPLIST_KEY_NSCalendarsUsageDescription = "Kisani Cal shows your calendar events alongside tasks."; INFOPLIST_KEY_NSHealthShareUsageDescription = "Kisani Cal reads your steps, energy, heart rate, and workouts to show health stats on your dashboard."; INFOPLIST_KEY_NSHealthUpdateUsageDescription = "Kisani Cal saves workouts you complete to the Health app."; + INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UILaunchStoryboardName = ""; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; @@ -577,6 +600,7 @@ INFOPLIST_KEY_NSCalendarsUsageDescription = "Kisani Cal shows your calendar events alongside tasks."; INFOPLIST_KEY_NSHealthShareUsageDescription = "Kisani Cal reads your steps, energy, heart rate, and workouts to show health stats on your dashboard."; INFOPLIST_KEY_NSHealthUpdateUsageDescription = "Kisani Cal saves workouts you complete to the Health app."; + INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UILaunchStoryboardName = ""; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; diff --git a/KisaniCal.xcodeproj/project.xcworkspace/xcuserdata/kutesir.xcuserdatad/UserInterfaceState.xcuserstate b/KisaniCal.xcodeproj/project.xcworkspace/xcuserdata/kutesir.xcuserdatad/UserInterfaceState.xcuserstate index 4784b70aceb80d1a2cd2b500bf0a9348a684d62d..4edff7dfd4cc715b083e8ae87a02a7035852fb54 100644 GIT binary patch delta 27785 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�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 diff --git a/KisaniCal/ISSUES.md b/KisaniCal/ISSUES.md index bf6498e..3f81b04 100644 --- a/KisaniCal/ISSUES.md +++ b/KisaniCal/ISSUES.md @@ -33,8 +33,10 @@ Set completion is now a per-day state: Files: `KisaniCal/Models/ExerciseModels.swift`, `KisaniCal/ContentView.swift` ### Verification -Builds cleanly (Xcode 26.2 SDK, simulator). Same program on consecutive days now -starts unchecked each day; streak preserved. +Builds cleanly (Xcode 26.2 SDK, simulator). Verified in-process on the simulator +via the real `resetSetsForNewDayIfNeeded()` against a seeded completed set: +`before=[true] after=[false] day=2026-06-06 -> PASS`. Streak preserved. +Committed in `cdc46e9`. --- @@ -55,4 +57,37 @@ is fully opaque (App Store Connect compliant) and device icons are full-bleed. Files: `KisaniCal/Assets.xcassets/AppIcon.appiconset/` ### Verification -Builds cleanly (app + widget extension); `hasAlpha: no` on all icons. +Builds cleanly (app + widget extension); `hasAlpha: no` on all icons. New logo +confirmed compiled into `Assets.car` (home, lock-screen notification, Spotlight, +Settings all derive from `AppIcon`). Committed in `cdc46e9`. +Note: device still shows old icon until the new build is installed (delete + reinstall). + +--- + +## KC-3 — Today view: section order + RSS feeds leaking into Tasks + +**Status:** In Progress (implemented & build-verified, pending release) +**Reported by:** User +**Area:** Tasks / Today + +### Description +Two changes to the Today view: +1. The Overdue card sat at the very top, above today's active tasks. Active tasks + should always be the top section. +2. Subscribed/RSS calendar feeds (F1 schedule, TV-show calendars) were appearing + in the Today/Tasks timeline. They belong in the Calendar tab only. + +### Fix +- Moved the `OverdueCard` to render directly below today's active tasks (above + Next 3 Days), so active tasks stay at the top. +- `todayCalEvents` now excludes events whose `calendar.type == .subscription`, so + RSS/ICS feeds are filtered out of the Today timeline. The Calendar view reads + `monthEvents` directly and is unaffected. +- Birthdays (`.birthday`) and personal/CalDAV calendars are intentionally left + visible; the existing per-calendar visibility toggle (`hiddenCalendarIDs`) lets + the user hide any of them. User-created tasks are never affected. + +Files: `KisaniCal/Views/TodayView.swift` + +### Verification +Builds cleanly. Committed in `f66f032`. diff --git a/KisaniCal/Managers/AuthManager.swift b/KisaniCal/Managers/AuthManager.swift index 5107001..36cd72d 100644 --- a/KisaniCal/Managers/AuthManager.swift +++ b/KisaniCal/Managers/AuthManager.swift @@ -27,6 +27,11 @@ final class AuthManager: NSObject, ObservableObject { override init() { super.init() currentUser = loadUser() + #if DEBUG + if currentUser == nil, ProcessInfo.processInfo.environment["KISANI_PREVIEW_AUTH"] == "1" { + currentUser = AppUser(id: "preview-user", email: "preview@kisani.app", displayName: "Preview") + } + #endif if let uid = currentUser?.id { UserDefaults.kisani.set(uid, forKey: Self.activeUserIdKey) } diff --git a/KisaniCal/Managers/LiveActivityManager.swift b/KisaniCal/Managers/LiveActivityManager.swift new file mode 100644 index 0000000..0723c76 --- /dev/null +++ b/KisaniCal/Managers/LiveActivityManager.swift @@ -0,0 +1,68 @@ +import Foundation +import ActivityKit + +/// Starts / ends a Live Activity for a task. iOS 16.1+ only; no-ops otherwise. +enum LiveActivityManager { + + /// Toggle: if the task already has a Live Activity, end it; else start one. + static func toggle(for task: TaskItem) { + guard #available(iOS 16.1, *) else { return } + if isActive(taskID: task.id.uuidString) { + end(taskID: task.id.uuidString) + } else { + start(for: task) + } + } + + /// Whether a Live Activity is currently running for this task. + @available(iOS 16.1, *) + static func isActive(taskID: String) -> Bool { + Activity.activities.contains { $0.attributes.taskID == taskID } + } + + @available(iOS 16.1, *) + static func start(for task: TaskItem) { + guard ActivityAuthorizationInfo().areActivitiesEnabled else { return } + let attributes = TaskActivityAttributes( + taskID: task.id.uuidString, + title: task.title, + categoryRaw: task.category.rawValue + ) + let state = TaskActivityAttributes.ContentState( + dueDate: task.dueDate, + isComplete: task.isComplete, + priorityRaw: task.priority.rawValue + ) + do { + _ = try Activity.request(attributes: attributes, contentState: state, pushType: nil) + } catch { + print("LiveActivity start failed: \(error.localizedDescription)") + } + } + + /// Push updated state to a running activity (e.g. after edit/complete). + @available(iOS 16.1, *) + static func update(for task: TaskItem) { + let state = TaskActivityAttributes.ContentState( + dueDate: task.dueDate, + isComplete: task.isComplete, + priorityRaw: task.priority.rawValue + ) + Task { + for activity in Activity.activities + where activity.attributes.taskID == task.id.uuidString { + await activity.update(using: state) + } + } + } + + @available(iOS 16.1, *) + static func end(taskID: String) { + Task { + for activity in Activity.activities + where activity.attributes.taskID == taskID { + await activity.end(dismissalPolicy: .immediate) + } + } + } +} diff --git a/KisaniCal/Managers/NotificationManager.swift b/KisaniCal/Managers/NotificationManager.swift index ae725c4..ed07749 100644 --- a/KisaniCal/Managers/NotificationManager.swift +++ b/KisaniCal/Managers/NotificationManager.swift @@ -245,7 +245,9 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable } else { continue } - guard fireDate > now else { continue } + // A one-off reminder in the past is dropped; a constant (daily-repeating) + // reminder still schedules so it keeps nudging on its next occurrence. + guard task.constantReminder || fireDate > now else { continue } let content = UNMutableNotificationContent() content.title = task.title @@ -254,11 +256,15 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable content.categoryIdentifier = Self.categoryId content.userInfo = ["taskId": task.id.uuidString, "deeplink": "tasks"] - let trigComps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: fireDate) + // Constant reminders re-fire daily (at the reminder time) until the + // task is completed; one-off reminders match the full date. + let trigComps = task.constantReminder + ? cal.dateComponents([.hour, .minute], from: fireDate) + : cal.dateComponents([.year, .month, .day, .hour, .minute], from: fireDate) center.add(UNNotificationRequest( identifier: "kisani.task.\(task.id.uuidString)", content: content, - trigger: UNCalendarNotificationTrigger(dateMatching: trigComps, repeats: false) + trigger: UNCalendarNotificationTrigger(dateMatching: trigComps, repeats: task.constantReminder) )) } diff --git a/KisaniCal/Managers/TaskActivityAttributes.swift b/KisaniCal/Managers/TaskActivityAttributes.swift new file mode 100644 index 0000000..48254a4 --- /dev/null +++ b/KisaniCal/Managers/TaskActivityAttributes.swift @@ -0,0 +1,18 @@ +import Foundation +import ActivityKit + +// Shared between the app (which starts/ends the activity) and the widget +// extension (which renders it). Added to both targets in project.yml. +@available(iOS 16.1, *) +struct TaskActivityAttributes: ActivityAttributes { + public struct ContentState: Codable, Hashable { + var dueDate: Date? + var isComplete: Bool + var priorityRaw: String + } + + // Static identity for the life of the activity. + var taskID: String + var title: String + var categoryRaw: String +} diff --git a/KisaniCal/Models/TaskItem.swift b/KisaniCal/Models/TaskItem.swift index b844920..c24cb1a 100644 --- a/KisaniCal/Models/TaskItem.swift +++ b/KisaniCal/Models/TaskItem.swift @@ -19,6 +19,7 @@ struct TaskItem: Identifiable, Codable, Equatable { var isAllDay: Bool = false var priority: Priority = .none var reminderDate: Date? = nil + var constantReminder: Bool = false } enum Priority: String, Codable, CaseIterable, Identifiable { @@ -236,6 +237,12 @@ class TaskViewModel: ObservableObject { tasks[idx].category = category save() } + + func setPriority(_ task: TaskItem, priority: Priority) { + guard let idx = tasks.firstIndex(where: { $0.id == task.id }) else { return } + tasks[idx].priority = priority + save() + } func moveToQuadrant(taskID: UUID, quadrant: Quadrant) { guard let idx = tasks.firstIndex(where: { $0.id == taskID }) else { return } tasks[idx].quadrant = quadrant @@ -247,28 +254,32 @@ class TaskViewModel: ObservableObject { taskColor: TaskColor = .orange, isRecurring: Bool = false, recurrenceLabel: String? = nil, endDate: Date? = nil, isAllDay: Bool = false, - priority: Priority = .none, reminderDate: Date? = nil) { + priority: Priority = .none, reminderDate: Date? = nil, + constantReminder: Bool = false) { tasks.append(TaskItem(title: title, dueDate: dueDate, hasTime: hasTime, quadrant: quadrant, category: category, taskColor: taskColor, isRecurring: isRecurring, recurrenceLabel: recurrenceLabel, endDate: endDate, isAllDay: isAllDay, - priority: priority, reminderDate: reminderDate)) + priority: priority, reminderDate: reminderDate, + constantReminder: constantReminder)) save() } func updateTask(_ task: TaskItem, title: String, dueDate: Date?, hasTime: Bool, isRecurring: Bool, recurrenceLabel: String?, - endDate: Date?, isAllDay: Bool, reminderDate: Date?) { + endDate: Date?, isAllDay: Bool, reminderDate: Date?, + constantReminder: Bool = false) { guard let idx = tasks.firstIndex(where: { $0.id == task.id }) else { return } - tasks[idx].title = title - tasks[idx].dueDate = dueDate - tasks[idx].hasTime = hasTime - tasks[idx].isRecurring = isRecurring - tasks[idx].recurrenceLabel = recurrenceLabel - tasks[idx].endDate = endDate - tasks[idx].isAllDay = isAllDay - tasks[idx].reminderDate = reminderDate + tasks[idx].title = title + tasks[idx].dueDate = dueDate + tasks[idx].hasTime = hasTime + tasks[idx].isRecurring = isRecurring + tasks[idx].recurrenceLabel = recurrenceLabel + tasks[idx].endDate = endDate + tasks[idx].isAllDay = isAllDay + tasks[idx].reminderDate = reminderDate + tasks[idx].constantReminder = constantReminder save() } diff --git a/KisaniCal/Views/CalendarView.swift b/KisaniCal/Views/CalendarView.swift index 717d93a..651e4b6 100644 --- a/KisaniCal/Views/CalendarView.swift +++ b/KisaniCal/Views/CalendarView.swift @@ -1,5 +1,6 @@ import SwiftUI import EventKit +import EventKitUI // MARK: - Calendar Store @MainActor @@ -115,6 +116,20 @@ final class CalendarStore: ObservableObject { return authStatus == .authorized } + /// Exposed so the edit sheet can hand it to EKEventEditViewController. + var eventStore: EKEventStore { ekStore } + + /// Delete a real (writable) calendar event, then refresh the month cache. + func deleteEvent(_ event: EKEvent) { + guard event.calendar?.allowsContentModifications == true else { return } + do { + try ekStore.remove(event, span: .thisEvent) + fetchMonth(containing: event.startDate ?? Date()) + } catch { + print("Delete event failed: \(error.localizedDescription)") + } + } + /// Only `.notDetermined` can show the system prompt; otherwise we must deep-link to Settings. var canRequest: Bool { authStatus == .notDetermined } var isDenied: Bool { authStatus == .denied || authStatus == .restricted } @@ -227,6 +242,7 @@ struct CalendarView: View { @State private var showAddTask = false @State private var showCalendarConnect = false @State private var showWorkoutSession = false + @State private var editingEvent: EditableEvent? = nil private let cal = Calendar.current private let weekdays = ["S","M","T","W","T","F","S"] @@ -334,6 +350,12 @@ struct CalendarView: View { .presentationDetents([.large]) .presentationDragIndicator(.visible) } + .sheet(item: $editingEvent) { wrapper in + EventEditView(event: wrapper.event, store: calStore.eventStore) { + calStore.fetchMonth(containing: selectedDate) + } + .ignoresSafeArea() + } .onAppear { calStore.refreshStatus() if calStore.isAuthorized { calStore.fetchMonth(containing: displayMonth) } @@ -517,7 +539,15 @@ struct CalendarView: View { }, onToggle: { task in withAnimation(KisaniSpring.snappy) { taskVM.toggle(task) } - } + }, + onPin: { taskVM.pin($0) }, + onDelete: { taskVM.delete($0) }, + onSetDate: { taskVM.setDate($0, date: $1) }, + onMove: { taskVM.moveToQuadrant(taskID: $0.id, quadrant: $1) }, + onSetPriority: { taskVM.setPriority($0, priority: $1) }, + onSetCategory: { taskVM.setCategory($0, category: $1) }, + onEditEvent: { editingEvent = EditableEvent(event: $0) }, + onDeleteEvent: { calStore.deleteEvent($0) } ) .padding(.horizontal, 16).padding(.top, 12).padding(.bottom, 20) } @@ -1442,6 +1472,7 @@ private struct DayTLEntry: Identifiable { var sortGroup: Int = 0 // 0 = active, 1 = completed, 2 = overdue var taskRef: TaskItem? var workoutRef: WorkoutProgram? + var eventRef: EKEvent? } struct DayTimelineView: View { @@ -1458,6 +1489,13 @@ struct DayTimelineView: View { var onPostpone: ((TaskItem) -> Void)? = nil var onEdit: ((TaskItem) -> Void)? = nil var onDelete: ((TaskItem) -> Void)? = nil + var onSetDate: ((TaskItem, Date?) -> Void)? = nil + var onMove: ((TaskItem, Quadrant) -> Void)? = nil + var onSetPriority: ((TaskItem, Priority) -> Void)? = nil + var onSetCategory: ((TaskItem, TaskCategory) -> Void)? = nil + // Calendar events (non-subscription only): edit/delete in the real calendar. + var onEditEvent: ((EKEvent) -> Void)? = nil + var onDeleteEvent: ((EKEvent) -> Void)? = nil private let cal = Calendar.current private static let overdueFmt: DateFormatter = { @@ -1485,7 +1523,8 @@ struct DayTimelineView: View { timeLabel: "All Day", title: ev.title ?? "Event", color: Color(cgColor: ev.calendar.cgColor), - isComplete: false, isAllDay: true, sortKey: -1, sortGroup: 0 + isComplete: false, isAllDay: true, sortKey: -1, sortGroup: 0, + eventRef: ev )) } @@ -1533,7 +1572,8 @@ struct DayTimelineView: View { timeLabel: "\(hd)\(ms)", ampm: h < 12 ? "AM" : "PM", title: ev.title ?? "Event", subtitle: endLabel, color: Color(cgColor: ev.calendar.cgColor), - isComplete: false, isAllDay: false, sortKey: h * 60 + m, sortGroup: 0 + isComplete: false, isAllDay: false, sortKey: h * 60 + m, sortGroup: 0, + eventRef: ev )) } @@ -1587,6 +1627,11 @@ struct DayTimelineView: View { ) if let t = entry.taskRef { row.contextMenu { taskContextMenu(t) } + } else if let ev = entry.eventRef, + onEditEvent != nil || onDeleteEvent != nil, + ev.calendar?.type != .subscription, + ev.calendar?.allowsContentModifications == true { + row.contextMenu { eventContextMenu(ev) } } else { row } @@ -1597,26 +1642,30 @@ struct DayTimelineView: View { @ViewBuilder private func taskContextMenu(_ task: TaskItem) -> some View { - Button { onToggle?(task) } label: { - Label(task.isComplete ? "Mark Incomplete" : "Mark Complete", systemImage: "checkmark.circle") + TaskMenuItems( + task: task, + onPin: { onPin?($0) }, + onSetDate: { onSetDate?($0, $1) }, + onMove: { onMove?($0, $1) }, + onSetPriority: { onSetPriority?($0, $1) }, + onSetCategory: { onSetCategory?($0, $1) }, + onLiveActivity:{ LiveActivityManager.toggle(for: $0) }, + onEdit: onEdit, + onDelete: { onDelete?($0) } + ) + } + + // Edit / Delete for a real (non-subscription) calendar event. + @ViewBuilder + private func eventContextMenu(_ event: EKEvent) -> some View { + if let onEditEvent { + Button { onEditEvent(event) } label: { Label("Edit", systemImage: "pencil") } } - if let onPostpone { - Button { onPostpone(task) } label: { - Label("Postpone 1 Day", systemImage: "clock.arrow.circlepath") - } - } - if let onPin { - Button { onPin(task) } label: { - Label(task.isPinned ? "Unpin" : "Pin", systemImage: task.isPinned ? "pin.slash" : "pin") - } - } - if let onEdit { - Button { onEdit(task) } label: { Label("Edit", systemImage: "pencil") } - } - ShareLink(item: task.title) { Label("Share", systemImage: "square.and.arrow.up") } - if let onDelete { + if let onDeleteEvent { Divider() - Button(role: .destructive) { onDelete(task) } label: { Label("Delete", systemImage: "trash") } + Button(role: .destructive) { onDeleteEvent(event) } label: { + Label("Delete", systemImage: "trash") + } } } } @@ -1816,3 +1865,40 @@ struct WorkoutSectionBar: View { .clipShape(RoundedRectangle(cornerRadius: 8)) } } + +// MARK: - Event Edit Sheet + +/// Identifiable wrapper so an EKEvent can drive `.sheet(item:)`. +struct EditableEvent: Identifiable { + let id = UUID() + let event: EKEvent +} + +/// Wraps the system event editor (EKEventEditViewController). +struct EventEditView: UIViewControllerRepresentable { + let event: EKEvent + let store: EKEventStore + var onDone: () -> Void + + func makeUIViewController(context: Context) -> EKEventEditViewController { + let vc = EKEventEditViewController() + vc.eventStore = store + vc.event = event + vc.editViewDelegate = context.coordinator + return vc + } + + func updateUIViewController(_ vc: EKEventEditViewController, context: Context) {} + + func makeCoordinator() -> Coordinator { Coordinator(onDone: onDone) } + + final class Coordinator: NSObject, EKEventEditViewDelegate { + let onDone: () -> Void + init(onDone: @escaping () -> Void) { self.onDone = onDone } + func eventEditViewController(_ controller: EKEventEditViewController, + didCompleteWith action: EKEventEditViewAction) { + controller.dismiss(animated: true) + onDone() + } + } +} diff --git a/KisaniCal/Views/MatrixView.swift b/KisaniCal/Views/MatrixView.swift index de39543..32d7361 100644 --- a/KisaniCal/Views/MatrixView.swift +++ b/KisaniCal/Views/MatrixView.swift @@ -66,6 +66,7 @@ struct MatrixView: View { onSetDate: { taskVM.setDate($0, date: $1) }, onMove: { taskVM.moveToQuadrant(taskID: $0.id, quadrant: $1) }, onSetCategory: { taskVM.setCategory($0, category: $1) }, + onSetPriority: { taskVM.setPriority($0, priority: $1) }, onDelete: { taskVM.delete($0) }, onTapHeader: { drillQuadrant = .urgent; showDrill = true } ) @@ -83,6 +84,7 @@ struct MatrixView: View { onSetDate: { taskVM.setDate($0, date: $1) }, onMove: { taskVM.moveToQuadrant(taskID: $0.id, quadrant: $1) }, onSetCategory: { taskVM.setCategory($0, category: $1) }, + onSetPriority: { taskVM.setPriority($0, priority: $1) }, onDelete: { taskVM.delete($0) }, onTapHeader: { drillQuadrant = .schedule; showDrill = true } ) @@ -104,6 +106,7 @@ struct MatrixView: View { onSetDate: { taskVM.setDate($0, date: $1) }, onMove: { taskVM.moveToQuadrant(taskID: $0.id, quadrant: $1) }, onSetCategory: { taskVM.setCategory($0, category: $1) }, + onSetPriority: { taskVM.setPriority($0, priority: $1) }, onDelete: { taskVM.delete($0) }, onTapHeader: { drillQuadrant = .delegate_; showDrill = true } ) @@ -121,6 +124,7 @@ struct MatrixView: View { onSetDate: { taskVM.setDate($0, date: $1) }, onMove: { taskVM.moveToQuadrant(taskID: $0.id, quadrant: $1) }, onSetCategory: { taskVM.setCategory($0, category: $1) }, + onSetPriority: { taskVM.setPriority($0, priority: $1) }, onDelete: { taskVM.delete($0) }, onTapHeader: { drillQuadrant = .eliminate; showDrill = true } ) @@ -183,6 +187,7 @@ struct QuadrantCard: View { var onSetDate: ((TaskItem, Date?) -> Void)? = nil var onMove: ((TaskItem, Quadrant) -> Void)? = nil var onSetCategory: ((TaskItem, TaskCategory) -> Void)? = nil + var onSetPriority: ((TaskItem, Priority) -> Void)? = nil var onDelete: ((TaskItem) -> Void)? = nil var onTapHeader: (() -> Void)? = nil @@ -278,68 +283,17 @@ struct QuadrantCard: View { .padding(.leading, 2) .draggable(task.id.uuidString) .contextMenu { - Button { - withAnimation(KisaniSpring.snappy) { onPin?(task) } - } label: { - Label(task.isPinned ? "Unpin" : "Pin", - systemImage: task.isPinned ? "pin.slash" : "pin") - } - - Menu { - Button { - onSetDate?(task, Calendar.current.startOfDay(for: Date())) - } label: { Label("Today", systemImage: "sun.max") } - Button { - let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())! - onSetDate?(task, Calendar.current.startOfDay(for: tomorrow)) - } label: { Label("Tomorrow", systemImage: "sunrise") } - Button { - let nextWeek = Calendar.current.date(byAdding: .weekOfYear, value: 1, to: Date())! - onSetDate?(task, Calendar.current.startOfDay(for: nextWeek)) - } label: { Label("Next Week", systemImage: "calendar") } - Divider() - Button { - onSetDate?(task, nil) - } label: { Label("Remove Date", systemImage: "xmark.circle") } - } label: { - Label("Date", systemImage: "calendar.badge.clock") - } - - Menu { - ForEach(Quadrant.allCases.filter { $0 != quadrant }) { q in - Button { - withAnimation(KisaniSpring.snappy) { onMove?(task, q) } - } label: { - Text(q.rawValue) - } - } - } label: { - Label("Move", systemImage: "arrow.right.square") - } - - Menu { - ForEach([TaskCategory.reminder, .birthday, .domain, .annual, .custom], id: \.rawValue) { cat in - Button { - onSetCategory?(task, cat) - } label: { - Label(cat.rawValue, systemImage: task.category == cat ? "checkmark" : "tag") - } - } - } label: { - Label("Tags", systemImage: "tag") - } - - Button { } label: { - Label("Add to Live Activity", systemImage: "pin.circle") - } - - Divider() - - Button(role: .destructive) { - withAnimation(KisaniSpring.snappy) { onDelete?(task) } - } label: { - Label("Delete", systemImage: "trash") - } + TaskMenuItems( + task: task, + currentQuadrant: quadrant, + onPin: { t in withAnimation(KisaniSpring.snappy) { onPin?(t) } }, + onSetDate: { onSetDate?($0, $1) }, + onMove: { t, q in withAnimation(KisaniSpring.snappy) { onMove?(t, q) } }, + onSetPriority: { onSetPriority?($0, $1) }, + onSetCategory: { onSetCategory?($0, $1) }, + onLiveActivity:{ LiveActivityManager.toggle(for: $0) }, + onDelete: { t in withAnimation(KisaniSpring.snappy) { onDelete?(t) } } + ) } } } @@ -484,6 +438,20 @@ struct QuadrantDetailView: View { QDTaskRow(task: task, quadrant: quadrant, onTap: { editingTask = task }, onToggle: { withAnimation(KisaniSpring.bounce) { taskVM.toggle(task) } }) + .contextMenu { + TaskMenuItems( + task: task, + currentQuadrant: quadrant, + onPin: { taskVM.pin($0) }, + onSetDate: { taskVM.setDate($0, date: $1) }, + onMove: { taskVM.moveToQuadrant(taskID: $0.id, quadrant: $1) }, + onSetPriority: { taskVM.setPriority($0, priority: $1) }, + onSetCategory: { taskVM.setCategory($0, category: $1) }, + onLiveActivity:{ LiveActivityManager.toggle(for: $0) }, + onEdit: { editingTask = $0 }, + onDelete: { taskVM.delete($0) } + ) + } if task.id != overdueTasks.last?.id { Divider().padding(.leading, 46) } } } @@ -495,6 +463,20 @@ struct QuadrantDetailView: View { QDTaskRow(task: task, quadrant: quadrant, onTap: { editingTask = task }, onToggle: { withAnimation(KisaniSpring.bounce) { taskVM.toggle(task) } }) + .contextMenu { + TaskMenuItems( + task: task, + currentQuadrant: quadrant, + onPin: { taskVM.pin($0) }, + onSetDate: { taskVM.setDate($0, date: $1) }, + onMove: { taskVM.moveToQuadrant(taskID: $0.id, quadrant: $1) }, + onSetPriority: { taskVM.setPriority($0, priority: $1) }, + onSetCategory: { taskVM.setCategory($0, category: $1) }, + onLiveActivity:{ LiveActivityManager.toggle(for: $0) }, + onEdit: { editingTask = $0 }, + onDelete: { taskVM.delete($0) } + ) + } if task.id != laterTasks.last?.id { Divider().padding(.leading, 46) } } } @@ -507,6 +489,20 @@ struct QuadrantDetailView: View { QDTaskRow(task: task, quadrant: quadrant, onTap: { editingTask = task }, onToggle: { withAnimation(KisaniSpring.bounce) { taskVM.toggle(task) } }) + .contextMenu { + TaskMenuItems( + task: task, + currentQuadrant: quadrant, + onPin: { taskVM.pin($0) }, + onSetDate: { taskVM.setDate($0, date: $1) }, + onMove: { taskVM.moveToQuadrant(taskID: $0.id, quadrant: $1) }, + onSetPriority: { taskVM.setPriority($0, priority: $1) }, + onSetCategory: { taskVM.setCategory($0, category: $1) }, + onLiveActivity:{ LiveActivityManager.toggle(for: $0) }, + onEdit: { editingTask = $0 }, + onDelete: { taskVM.delete($0) } + ) + } if task.id != displayed.last?.id { Divider().padding(.leading, 46) } } if completedTasks.count > completedLimit && !showAllCompleted { @@ -682,18 +678,20 @@ struct TaskEditSheet: View { @State private var endDate: Date? @State private var isAllDay: Bool @State private var reminderDate: Date? + @State private var constantReminder: Bool @State private var showDatePicker = false init(task: TaskItem) { self.task = task - _title = State(initialValue: task.title) - _dueDate = State(initialValue: task.dueDate) - _hasTime = State(initialValue: task.hasTime) - _isRecurring = State(initialValue: task.isRecurring) - _recurrenceLabel = State(initialValue: task.recurrenceLabel) - _endDate = State(initialValue: task.endDate) - _isAllDay = State(initialValue: task.isAllDay) - _reminderDate = State(initialValue: task.reminderDate) + _title = State(initialValue: task.title) + _dueDate = State(initialValue: task.dueDate) + _hasTime = State(initialValue: task.hasTime) + _isRecurring = State(initialValue: task.isRecurring) + _recurrenceLabel = State(initialValue: task.recurrenceLabel) + _endDate = State(initialValue: task.endDate) + _isAllDay = State(initialValue: task.isAllDay) + _reminderDate = State(initialValue: task.reminderDate) + _constantReminder = State(initialValue: task.constantReminder) } private var dateSummary: String { @@ -839,7 +837,8 @@ struct TaskEditSheet: View { recurrenceLabel: $recurrenceLabel, endDate: $endDate, isAllDay: $isAllDay, - reminderDate: $reminderDate + reminderDate: $reminderDate, + constantReminder: $constantReminder ) } } @@ -849,7 +848,8 @@ struct TaskEditSheet: View { guard !t.isEmpty else { return } taskVM.updateTask(task, title: t, dueDate: dueDate, hasTime: hasTime, isRecurring: isRecurring, recurrenceLabel: recurrenceLabel, - endDate: endDate, isAllDay: isAllDay, reminderDate: reminderDate) + endDate: endDate, isAllDay: isAllDay, reminderDate: reminderDate, + constantReminder: constantReminder) dismiss() } } diff --git a/KisaniCal/Views/TaskContextMenu.swift b/KisaniCal/Views/TaskContextMenu.swift new file mode 100644 index 0000000..3863f36 --- /dev/null +++ b/KisaniCal/Views/TaskContextMenu.swift @@ -0,0 +1,80 @@ +import SwiftUI + +/// The unified long-press menu for a task, shared across Today, Matrix, and Calendar. +/// Pass only the closures a given screen needs; the rest default to no-ops. +struct TaskMenuItems: View { + let task: TaskItem + /// The quadrant the row currently lives in (so "Move" hides the current one). + var currentQuadrant: Quadrant? = nil + + var onPin: (TaskItem) -> Void = { _ in } + var onSetDate: (TaskItem, Date?) -> Void = { _, _ in } + var onMove: (TaskItem, Quadrant) -> Void = { _, _ in } + var onSetPriority: (TaskItem, Priority) -> Void = { _, _ in } + var onSetCategory: (TaskItem, TaskCategory) -> Void = { _, _ in } + var onLiveActivity:(TaskItem) -> Void = { LiveActivityManager.toggle(for: $0) } + var onEdit: ((TaskItem) -> Void)? = nil + var onDelete: (TaskItem) -> Void = { _ in } + + private var cal: Calendar { .current } + + var body: some View { + Button { onPin(task) } label: { + Label(task.isPinned ? "Unpin" : "Pin", systemImage: task.isPinned ? "pin.slash" : "pin") + } + + Menu { + Button { onSetDate(task, cal.startOfDay(for: Date())) } label: { + Label("Today", systemImage: "sun.max") + } + Button { + let d = cal.date(byAdding: .day, value: 1, to: Date())! + onSetDate(task, cal.startOfDay(for: d)) + } label: { Label("Tomorrow", systemImage: "sunrise") } + Button { + let d = cal.date(byAdding: .weekOfYear, value: 1, to: Date())! + onSetDate(task, cal.startOfDay(for: d)) + } label: { Label("Next Week", systemImage: "calendar") } + Divider() + Button { onSetDate(task, nil) } label: { + Label("Remove Date", systemImage: "xmark.circle") + } + } label: { Label("Date", systemImage: "calendar.badge.clock") } + + Menu { + ForEach(Quadrant.allCases.filter { $0 != (currentQuadrant ?? task.quadrant) }) { q in + Button { onMove(task, q) } label: { Text(q.rawValue) } + } + } label: { Label("Move", systemImage: "arrow.right.square") } + + Menu { + ForEach(Priority.allCases) { p in + Button { onSetPriority(task, p) } label: { + Label(p.label, systemImage: task.priority == p ? "checkmark" : "flag") + } + } + } label: { Label("Priority", systemImage: "flag") } + + Menu { + ForEach([TaskCategory.reminder, .birthday, .domain, .annual, .custom], id: \.rawValue) { cat in + Button { onSetCategory(task, cat) } label: { + Label(cat.rawValue, systemImage: task.category == cat ? "checkmark" : "tag") + } + } + } label: { Label("Tags", systemImage: "tag") } + + Button { onLiveActivity(task) } label: { + Label("Add to Live Activity", systemImage: "pin.circle") + } + + if let onEdit { + Button { onEdit(task) } label: { Label("Edit", systemImage: "pencil") } + } + + Divider() + + Button(role: .destructive) { onDelete(task) } label: { + Label("Delete", systemImage: "trash") + } + } +} diff --git a/KisaniCal/Views/TodayView.swift b/KisaniCal/Views/TodayView.swift index 21ce872..fbafd5e 100644 --- a/KisaniCal/Views/TodayView.swift +++ b/KisaniCal/Views/TodayView.swift @@ -100,7 +100,11 @@ struct TodayView: View { onPin: { taskVM.pin($0) }, onPostpone: { taskVM.postpone($0) }, onEdit: { editingTask = $0 }, - onDelete: { taskVM.delete($0) } + onDelete: { taskVM.delete($0) }, + onSetDate: { taskVM.setDate($0, date: $1) }, + onMove: { taskVM.moveToQuadrant(taskID: $0.id, quadrant: $1) }, + onSetPriority: { taskVM.setPriority($0, priority: $1) }, + onSetCategory: { taskVM.setCategory($0, category: $1) } ) .padding(.horizontal, 18) diff --git a/KisaniCalWidgets/KisaniCalWidgets.swift b/KisaniCalWidgets/KisaniCalWidgets.swift index abaa4b8..a9cc3a8 100644 --- a/KisaniCalWidgets/KisaniCalWidgets.swift +++ b/KisaniCalWidgets/KisaniCalWidgets.swift @@ -111,5 +111,6 @@ struct KisaniWidgetBundle: WidgetBundle { TasksCompleteWidget() LockScreenWidget() LockScreenCircularWidget() + TaskLiveActivity() } } diff --git a/KisaniCalWidgets/TaskLiveActivity.swift b/KisaniCalWidgets/TaskLiveActivity.swift new file mode 100644 index 0000000..0c19a3f --- /dev/null +++ b/KisaniCalWidgets/TaskLiveActivity.swift @@ -0,0 +1,74 @@ +import ActivityKit +import WidgetKit +import SwiftUI + +@available(iOS 16.1, *) +struct TaskLiveActivity: Widget { + private let accent = Color(red: 0.93, green: 0.42, blue: 0.20) // brand orange + + var body: some WidgetConfiguration { + ActivityConfiguration(for: TaskActivityAttributes.self) { context in + // ── Lock screen / banner ── + HStack(spacing: 12) { + Image(systemName: context.state.isComplete ? "checkmark.circle.fill" : "circle") + .font(.system(size: 22)) + .foregroundColor(context.state.isComplete ? .green : accent) + VStack(alignment: .leading, spacing: 2) { + Text(context.attributes.title) + .font(.system(size: 15, weight: .semibold)) + .foregroundColor(.white) + .lineLimit(1) + .strikethrough(context.state.isComplete) + if let due = context.state.dueDate { + Text(due, style: context.state.isComplete ? .date : .relative) + .font(.system(size: 12, design: .monospaced)) + .foregroundColor(.white.opacity(0.6)) + } + } + Spacer() + if let due = context.state.dueDate, !context.state.isComplete { + Text(due, style: .timer) + .font(.system(size: 16, weight: .bold, design: .monospaced)) + .foregroundColor(accent) + .frame(maxWidth: 70) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .activityBackgroundTint(Color.black.opacity(0.55)) + .activitySystemActionForegroundColor(.white) + + } dynamicIsland: { context in + DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + Image(systemName: "checklist") + .foregroundColor(accent) + } + DynamicIslandExpandedRegion(.trailing) { + if let due = context.state.dueDate, !context.state.isComplete { + Text(due, style: .timer) + .font(.system(.body, design: .monospaced)) + .foregroundColor(accent) + .frame(maxWidth: 64) + } + } + DynamicIslandExpandedRegion(.center) { + Text(context.attributes.title) + .font(.system(size: 14, weight: .semibold)) + .lineLimit(1) + } + } compactLeading: { + Image(systemName: "checklist").foregroundColor(accent) + } compactTrailing: { + if let due = context.state.dueDate, !context.state.isComplete { + Text(due, style: .timer) + .font(.system(.caption2, design: .monospaced)) + .foregroundColor(accent) + .frame(maxWidth: 44) + } + } minimal: { + Image(systemName: "checklist").foregroundColor(accent) + } + } + } +} diff --git a/project.yml b/project.yml index 23b5a28..67feeca 100644 --- a/project.yml +++ b/project.yml @@ -51,6 +51,7 @@ targets: INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone: "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight" ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor + INFOPLIST_KEY_NSSupportsLiveActivities: YES CODE_SIGN_STYLE: Automatic CODE_SIGN_ENTITLEMENTS: KisaniCal/KisaniCal.entitlements ENABLE_PREVIEWS: YES @@ -66,6 +67,7 @@ targets: sources: - path: KisaniCalWidgets - path: KisaniCal/Managers/AppGroup.swift + - path: KisaniCal/Managers/TaskActivityAttributes.swift info: path: KisaniCalWidgets/Info.plist properties: