feat: home screen quick actions (long press app icon)
Adds four UIApplicationShortcutItems registered dynamically at launch:
• Add Task — opens the add-task sheet immediately, any tab
• Today — switches to the Today tab
• Calendar — switches to the Calendar tab
• Workout — switches to the Workout tab (only registered if the
workout tab is enabled in settings; re-registers when the setting changes)
Cold-launch and foreground-activation paths both handled via AppDelegate.
ShortcutHandler singleton bridges UIKit callbacks to SwiftUI via @Published.
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
13E4B9854F595394FC9D5912 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5E179A9189B0A8C3F856F6 /* ContentView.swift */; };
|
13E4B9854F595394FC9D5912 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5E179A9189B0A8C3F856F6 /* ContentView.swift */; };
|
||||||
1A22EE21460821170E44B1DF /* ExerciseModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5722CC4B59E3939724142710 /* ExerciseModels.swift */; };
|
1A22EE21460821170E44B1DF /* ExerciseModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5722CC4B59E3939724142710 /* ExerciseModels.swift */; };
|
||||||
3C793FD5DA00D3E9C0D51FEC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 72FDF9C8DD37134576356B89 /* Assets.xcassets */; };
|
3C793FD5DA00D3E9C0D51FEC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 72FDF9C8DD37134576356B89 /* Assets.xcassets */; };
|
||||||
|
497732557745AE9BDA44FB2F /* ShortcutHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326B77A7B317A7DB44E13EA5 /* ShortcutHandler.swift */; };
|
||||||
67CE1747E5DB3FDA79D0FDFD /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106EEF572C6F8990408329F0 /* OnboardingView.swift */; };
|
67CE1747E5DB3FDA79D0FDFD /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106EEF572C6F8990408329F0 /* OnboardingView.swift */; };
|
||||||
6921CB73A3257502FF778381 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2D50B4B4AC227BD21F4B60 /* SplashView.swift */; };
|
6921CB73A3257502FF778381 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE2D50B4B4AC227BD21F4B60 /* SplashView.swift */; };
|
||||||
7CEBC340BFA9238D121946AC /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 57714B79AFFF60DA90EB86E3 /* Preview Assets.xcassets */; };
|
7CEBC340BFA9238D121946AC /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 57714B79AFFF60DA90EB86E3 /* Preview Assets.xcassets */; };
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
106EEF572C6F8990408329F0 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
106EEF572C6F8990408329F0 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||||
20DAD771EFCC8B3B1DBD4DD6 /* WorkoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutView.swift; sourceTree = "<group>"; };
|
20DAD771EFCC8B3B1DBD4DD6 /* WorkoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutView.swift; sourceTree = "<group>"; };
|
||||||
23A4491BFA50721082024756 /* SharedComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedComponents.swift; sourceTree = "<group>"; };
|
23A4491BFA50721082024756 /* SharedComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedComponents.swift; sourceTree = "<group>"; };
|
||||||
|
326B77A7B317A7DB44E13EA5 /* ShortcutHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutHandler.swift; sourceTree = "<group>"; };
|
||||||
5722CC4B59E3939724142710 /* ExerciseModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExerciseModels.swift; sourceTree = "<group>"; };
|
5722CC4B59E3939724142710 /* ExerciseModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExerciseModels.swift; sourceTree = "<group>"; };
|
||||||
57714B79AFFF60DA90EB86E3 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
57714B79AFFF60DA90EB86E3 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
72308FEE0226F45414C04DDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
72308FEE0226F45414C04DDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
@@ -135,6 +137,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
93D045FE3DEB1D22D908A29F /* NotificationManager.swift */,
|
93D045FE3DEB1D22D908A29F /* NotificationManager.swift */,
|
||||||
|
326B77A7B317A7DB44E13EA5 /* ShortcutHandler.swift */,
|
||||||
);
|
);
|
||||||
path = Managers;
|
path = Managers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -222,6 +225,7 @@
|
|||||||
67CE1747E5DB3FDA79D0FDFD /* OnboardingView.swift in Sources */,
|
67CE1747E5DB3FDA79D0FDFD /* OnboardingView.swift in Sources */,
|
||||||
A3B2D6622A2EE35C8D5A3C9B /* SettingsView.swift in Sources */,
|
A3B2D6622A2EE35C8D5A3C9B /* SettingsView.swift in Sources */,
|
||||||
8BE9D3D650B0F06169EC7048 /* SharedComponents.swift in Sources */,
|
8BE9D3D650B0F06169EC7048 /* SharedComponents.swift in Sources */,
|
||||||
|
497732557745AE9BDA44FB2F /* ShortcutHandler.swift in Sources */,
|
||||||
6921CB73A3257502FF778381 /* SplashView.swift in Sources */,
|
6921CB73A3257502FF778381 /* SplashView.swift in Sources */,
|
||||||
096804560A2F0A7D74E64780 /* TaskItem.swift in Sources */,
|
096804560A2F0A7D74E64780 /* TaskItem.swift in Sources */,
|
||||||
C79C33BE2802E81AA00175CC /* TodayView.swift in Sources */,
|
C79C33BE2802E81AA00175CC /* TodayView.swift in Sources */,
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ struct ContentView: View {
|
|||||||
@AppStorage("showMatrixTab") private var showMatrixTab = true
|
@AppStorage("showMatrixTab") private var showMatrixTab = true
|
||||||
@AppStorage("showWorkoutTab") private var showWorkoutTab = true
|
@AppStorage("showWorkoutTab") private var showWorkoutTab = true
|
||||||
@AppStorage("hasOnboarded") private var hasOnboarded = false
|
@AppStorage("hasOnboarded") private var hasOnboarded = false
|
||||||
@State private var showOnboarding = false
|
@State private var showOnboarding = false
|
||||||
@State private var selectedTab = 0
|
@State private var selectedTab = 0
|
||||||
@StateObject private var tabState = FloatingTabState()
|
@State private var showQuickAddTask = false
|
||||||
|
@StateObject private var tabState = FloatingTabState()
|
||||||
|
@ObservedObject private var shortcuts = ShortcutHandler.shared
|
||||||
@State private var iPadSelection: Int? = 0
|
@State private var iPadSelection: Int? = 0
|
||||||
@State private var calIconImage: Image = ContentView.buildCalIcon()
|
@State private var calIconImage: Image = ContentView.buildCalIcon()
|
||||||
|
|
||||||
@@ -92,6 +94,27 @@ struct ContentView: View {
|
|||||||
.onChange(of: workoutVM.doneSets) { _ in
|
.onChange(of: workoutVM.doneSets) { _ in
|
||||||
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
|
NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM)
|
||||||
}
|
}
|
||||||
|
.onChange(of: shortcuts.pending) { action in
|
||||||
|
guard let action else { return }
|
||||||
|
switch action {
|
||||||
|
case .addTask:
|
||||||
|
showQuickAddTask = true
|
||||||
|
case .today:
|
||||||
|
withAnimation(KisaniSpring.micro) { selectedTab = 0 }
|
||||||
|
case .calendar:
|
||||||
|
withAnimation(KisaniSpring.micro) { selectedTab = 1 }
|
||||||
|
case .workout:
|
||||||
|
withAnimation(KisaniSpring.micro) { selectedTab = showWorkoutTab ? 3 : 0 }
|
||||||
|
}
|
||||||
|
shortcuts.consume()
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showQuickAddTask) {
|
||||||
|
AddTaskSheet(prefilledDate: Date())
|
||||||
|
.environmentObject(taskVM)
|
||||||
|
.presentationDetents([.height(150)])
|
||||||
|
.presentationDragIndicator(.visible)
|
||||||
|
}
|
||||||
|
.onChange(of: showWorkoutTab) { _ in ShortcutHandler.registerShortcuts() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - iPhone
|
// MARK: - iPhone
|
||||||
|
|||||||
@@ -25,6 +25,22 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
|||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
NotificationManager.shared.requestPermission()
|
NotificationManager.shared.requestPermission()
|
||||||
|
ShortcutHandler.registerShortcuts()
|
||||||
|
|
||||||
|
// Cold launch from a shortcut
|
||||||
|
if let item = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem {
|
||||||
|
ShortcutHandler.shared.handle(item.type)
|
||||||
|
return false // tells UIKit we handled it
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
performActionFor shortcutItem: UIApplicationShortcutItem,
|
||||||
|
completionHandler: @escaping (Bool) -> Void
|
||||||
|
) {
|
||||||
|
ShortcutHandler.shared.handle(shortcutItem.type)
|
||||||
|
completionHandler(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
70
KisaniCal/Managers/ShortcutHandler.swift
Normal file
70
KisaniCal/Managers/ShortcutHandler.swift
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// MARK: - Quick action identifiers
|
||||||
|
|
||||||
|
enum QuickAction: String {
|
||||||
|
case addTask = "com.kutesir.kisanical.addTask"
|
||||||
|
case today = "com.kutesir.kisanical.today"
|
||||||
|
case calendar = "com.kutesir.kisanical.calendar"
|
||||||
|
case workout = "com.kutesir.kisanical.workout"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Singleton observable handler
|
||||||
|
|
||||||
|
final class ShortcutHandler: ObservableObject {
|
||||||
|
static let shared = ShortcutHandler()
|
||||||
|
@Published var pending: QuickAction?
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
func handle(_ type: String) {
|
||||||
|
guard let action = QuickAction(rawValue: type) else { return }
|
||||||
|
DispatchQueue.main.async { self.pending = action }
|
||||||
|
}
|
||||||
|
|
||||||
|
func consume() {
|
||||||
|
pending = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Shortcut registration
|
||||||
|
|
||||||
|
extension ShortcutHandler {
|
||||||
|
static func registerShortcuts() {
|
||||||
|
let ud = UserDefaults.standard
|
||||||
|
let showWorkout = ud.object(forKey: "showWorkoutTab") as? Bool ?? true
|
||||||
|
|
||||||
|
var items: [UIApplicationShortcutItem] = [
|
||||||
|
UIApplicationShortcutItem(
|
||||||
|
type: QuickAction.addTask.rawValue,
|
||||||
|
localizedTitle: "Add Task",
|
||||||
|
localizedSubtitle: "Create a new reminder",
|
||||||
|
icon: UIApplicationShortcutIcon(systemImageName: "plus.circle.fill")
|
||||||
|
),
|
||||||
|
UIApplicationShortcutItem(
|
||||||
|
type: QuickAction.today.rawValue,
|
||||||
|
localizedTitle: "Today",
|
||||||
|
localizedSubtitle: "Tasks & agenda",
|
||||||
|
icon: UIApplicationShortcutIcon(systemImageName: "checkmark.circle")
|
||||||
|
),
|
||||||
|
UIApplicationShortcutItem(
|
||||||
|
type: QuickAction.calendar.rawValue,
|
||||||
|
localizedTitle: "Calendar",
|
||||||
|
localizedSubtitle: "Month view",
|
||||||
|
icon: UIApplicationShortcutIcon(systemImageName: "calendar")
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if showWorkout {
|
||||||
|
items.append(UIApplicationShortcutItem(
|
||||||
|
type: QuickAction.workout.rawValue,
|
||||||
|
localizedTitle: "Workout",
|
||||||
|
localizedSubtitle: "Today's session",
|
||||||
|
icon: UIApplicationShortcutIcon(systemImageName: "dumbbell.fill")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
UIApplication.shared.shortcutItems = items
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user