Share a single CalendarStore for unified permission state
Introduce CalendarStore.shared so Today, Calendar, and Onboarding read the same auth status and event cache instead of each spinning up its own EKEventStore. Trust the system's authoritative authorization status over the granted bool, debounce repeat permission requests, route denied users to Settings, and prompt when enabling local calendars without access. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -79,7 +79,7 @@
|
|||||||
0506183945D16EC443A69651 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = "<group>"; };
|
0506183945D16EC443A69651 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = "<group>"; };
|
||||||
0513EB8C7F6B6853B9E93E09 /* TaskLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskLiveActivity.swift; sourceTree = "<group>"; };
|
0513EB8C7F6B6853B9E93E09 /* TaskLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskLiveActivity.swift; sourceTree = "<group>"; };
|
||||||
0C96EC00F6B021DBA3CC1A79 /* ProfileSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSetupView.swift; sourceTree = "<group>"; };
|
0C96EC00F6B021DBA3CC1A79 /* ProfileSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSetupView.swift; sourceTree = "<group>"; };
|
||||||
0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = KisaniCalWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = KisaniCalWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
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>"; };
|
||||||
1AA7498EFED7692022F3E7E1 /* KisaniCal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KisaniCal.entitlements; sourceTree = "<group>"; };
|
1AA7498EFED7692022F3E7E1 /* KisaniCal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KisaniCal.entitlements; sourceTree = "<group>"; };
|
||||||
208F82348DEBD9FF7B0DCF17 /* MyTasksWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyTasksWidget.swift; sourceTree = "<group>"; };
|
208F82348DEBD9FF7B0DCF17 /* MyTasksWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyTasksWidget.swift; sourceTree = "<group>"; };
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
9100804DB1E61EA882CC54DA /* FloatingTabState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingTabState.swift; sourceTree = "<group>"; };
|
9100804DB1E61EA882CC54DA /* FloatingTabState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingTabState.swift; sourceTree = "<group>"; };
|
||||||
92824ED40ECD41EFD4F78BEC /* ISSUES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ISSUES.md; sourceTree = "<group>"; };
|
92824ED40ECD41EFD4F78BEC /* ISSUES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ISSUES.md; sourceTree = "<group>"; };
|
||||||
93D045FE3DEB1D22D908A29F /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
93D045FE3DEB1D22D908A29F /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
||||||
9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = KisaniCal.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KisaniCal.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
ADF6CCD95A587E26E30F5712 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
|
ADF6CCD95A587E26E30F5712 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
|
||||||
AF905C574F34B4EE51A8D21E /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; };
|
AF905C574F34B4EE51A8D21E /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; };
|
||||||
BC5E179A9189B0A8C3F856F6 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
BC5E179A9189B0A8C3F856F6 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
@@ -286,7 +286,7 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastUpgradeCheck = 1500;
|
LastUpgradeCheck = 2620;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
28B43516AD88946E21D9BFE0 = {
|
28B43516AD88946E21D9BFE0 = {
|
||||||
DevelopmentTeam = K8BLMMR883;
|
DevelopmentTeam = K8BLMMR883;
|
||||||
@@ -465,6 +465,7 @@
|
|||||||
DEVELOPMENT_TEAM = K8BLMMR883;
|
DEVELOPMENT_TEAM = K8BLMMR883;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
@@ -479,6 +480,7 @@
|
|||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -494,7 +496,11 @@
|
|||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = KisaniCalWidgets/Info.plist;
|
INFOPLIST_FILE = KisaniCalWidgets/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets;
|
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_VERSION = 5.9;
|
SWIFT_VERSION = 5.9;
|
||||||
@@ -541,6 +547,7 @@
|
|||||||
DEVELOPMENT_TEAM = K8BLMMR883;
|
DEVELOPMENT_TEAM = K8BLMMR883;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
@@ -562,6 +569,7 @@
|
|||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -577,7 +585,11 @@
|
|||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = KisaniCalWidgets/Info.plist;
|
INFOPLIST_FILE = KisaniCalWidgets/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets;
|
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_VERSION = 5.9;
|
SWIFT_VERSION = 5.9;
|
||||||
|
|||||||
Binary file not shown.
@@ -1,11 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "2620"
|
||||||
version = "1.7">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES"
|
buildImplicitDependencies = "YES">
|
||||||
runPostActionsOnFailure = "NO">
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
@@ -27,8 +26,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
onlyGenerateCoverageForSpecifiedTargets = "NO">
|
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
@@ -40,8 +38,6 @@
|
|||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
<Testables>
|
<Testables>
|
||||||
</Testables>
|
</Testables>
|
||||||
<CommandLineArguments>
|
|
||||||
</CommandLineArguments>
|
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
@@ -63,8 +59,6 @@
|
|||||||
ReferencedContainer = "container:KisaniCal.xcodeproj">
|
ReferencedContainer = "container:KisaniCal.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
<CommandLineArguments>
|
|
||||||
</CommandLineArguments>
|
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
@@ -82,8 +76,6 @@
|
|||||||
ReferencedContainer = "container:KisaniCal.xcodeproj">
|
ReferencedContainer = "container:KisaniCal.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
<CommandLineArguments>
|
|
||||||
</CommandLineArguments>
|
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Debug">
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import EventKitUI
|
|||||||
// MARK: - Calendar Store
|
// MARK: - Calendar Store
|
||||||
@MainActor
|
@MainActor
|
||||||
final class CalendarStore: ObservableObject {
|
final class CalendarStore: ObservableObject {
|
||||||
|
/// Single shared source of truth so every screen (Today, Calendar, Onboarding,
|
||||||
|
/// Settings) sees the same permission state and event cache.
|
||||||
|
static let shared = CalendarStore()
|
||||||
|
|
||||||
@Published var authStatus: EKAuthorizationStatus = .notDetermined
|
@Published var authStatus: EKAuthorizationStatus = .notDetermined
|
||||||
|
@Published private(set) var isRequesting = false
|
||||||
@Published var monthEvents: [EKEvent] = []
|
@Published var monthEvents: [EKEvent] = []
|
||||||
|
|
||||||
private let ekStore = EKEventStore()
|
private let ekStore = EKEventStore()
|
||||||
@@ -63,20 +68,32 @@ final class CalendarStore: ObservableObject {
|
|||||||
// MARK: - Permission
|
// MARK: - Permission
|
||||||
|
|
||||||
func requestAccess() {
|
func requestAccess() {
|
||||||
|
// Debounce rapid taps; only the system prompt can move us off .notDetermined.
|
||||||
|
guard !isRequesting else { return }
|
||||||
|
guard authStatus == .notDetermined else {
|
||||||
|
// Already decided (granted/denied/write-only) — re-sync from the system.
|
||||||
|
refreshStatus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isRequesting = true
|
||||||
|
|
||||||
|
// Trust the system's authoritative status, not just the `granted` bool.
|
||||||
|
let finish: () -> Void = { [weak self] in
|
||||||
|
Task { @MainActor [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
self.authStatus = EKEventStore.authorizationStatus(for: .event)
|
||||||
|
self.isRequesting = false
|
||||||
|
if self.isAuthorized {
|
||||||
|
self.fetchMonth(containing: self.fetchedMonth ?? Date())
|
||||||
|
self.loadLocalCalendars()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if #available(iOS 17.0, *) {
|
if #available(iOS 17.0, *) {
|
||||||
ekStore.requestFullAccessToEvents { [weak self] granted, _ in
|
ekStore.requestFullAccessToEvents { _, _ in finish() }
|
||||||
Task { @MainActor [weak self] in
|
|
||||||
self?.authStatus = granted ? .fullAccess : .denied
|
|
||||||
if granted { self?.fetchMonth(containing: Date()); self?.loadLocalCalendars() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ekStore.requestAccess(to: .event) { [weak self] granted, _ in
|
ekStore.requestAccess(to: .event) { _, _ in finish() }
|
||||||
Task { @MainActor [weak self] in
|
|
||||||
self?.authStatus = granted ? .authorized : .denied
|
|
||||||
if granted { self?.fetchMonth(containing: Date()); self?.loadLocalCalendars() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +175,8 @@ final class CalendarStore: ObservableObject {
|
|||||||
func setLocalCalendarsEnabled(_ enabled: Bool) {
|
func setLocalCalendarsEnabled(_ enabled: Bool) {
|
||||||
localCalendarsEnabled = enabled
|
localCalendarsEnabled = enabled
|
||||||
saveCalPrefs()
|
saveCalPrefs()
|
||||||
|
// Turning it on with no access yet should prompt, not silently no-op.
|
||||||
|
if enabled && !isAuthorized { requestAccess() }
|
||||||
if let m = fetchedMonth { fetchMonth(containing: m) }
|
if let m = fetchedMonth { fetchMonth(containing: m) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +250,7 @@ struct CalendarView: View {
|
|||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
@EnvironmentObject var taskVM: TaskViewModel
|
@EnvironmentObject var taskVM: TaskViewModel
|
||||||
@EnvironmentObject var workoutVM: WorkoutViewModel
|
@EnvironmentObject var workoutVM: WorkoutViewModel
|
||||||
@StateObject private var calStore = CalendarStore()
|
@StateObject private var calStore = CalendarStore.shared
|
||||||
@State private var selectedDate: Date = Date()
|
@State private var selectedDate: Date = Date()
|
||||||
@State private var displayMonth: Date = {
|
@State private var displayMonth: Date = {
|
||||||
let c = Calendar.current
|
let c = Calendar.current
|
||||||
|
|||||||
@@ -26,12 +26,10 @@ struct OnboardingView: View {
|
|||||||
@State private var weightStr = ""
|
@State private var weightStr = ""
|
||||||
@State private var heightStr = ""
|
@State private var heightStr = ""
|
||||||
@State private var selectedDays: Set<Int> = []
|
@State private var selectedDays: Set<Int> = []
|
||||||
@State private var calAuthStatus: EKAuthorizationStatus = EKEventStore.authorizationStatus(for: .event)
|
@ObservedObject private var calStore = CalendarStore.shared
|
||||||
@ObservedObject private var hk = HealthKitManager.shared
|
@ObservedObject private var hk = HealthKitManager.shared
|
||||||
@FocusState private var focusedField: Field?
|
@FocusState private var focusedField: Field?
|
||||||
|
|
||||||
private let ekStore = EKEventStore()
|
|
||||||
|
|
||||||
private let timeSlots: [(Int, String, String)] = [
|
private let timeSlots: [(Int, String, String)] = [
|
||||||
(0, "Morning", "sunrise.fill"),
|
(0, "Morning", "sunrise.fill"),
|
||||||
(1, "Midday", "sun.max.fill"),
|
(1, "Midday", "sun.max.fill"),
|
||||||
@@ -41,10 +39,7 @@ struct OnboardingView: View {
|
|||||||
fileprivate enum Field { case weight, height }
|
fileprivate enum Field { case weight, height }
|
||||||
private let weekdays: [(Int, String)] = [(2,"M"),(3,"T"),(4,"W"),(5,"T"),(6,"F"),(7,"S"),(1,"S")]
|
private let weekdays: [(Int, String)] = [(2,"M"),(3,"T"),(4,"W"),(5,"T"),(6,"F"),(7,"S"),(1,"S")]
|
||||||
|
|
||||||
private var calIsGranted: Bool {
|
private var calIsGranted: Bool { calStore.isAuthorized }
|
||||||
if #available(iOS 17.0, *) { return calAuthStatus == .fullAccess }
|
|
||||||
return calAuthStatus == .authorized
|
|
||||||
}
|
|
||||||
|
|
||||||
private var liveScheme: ColorScheme? {
|
private var liveScheme: ColorScheme? {
|
||||||
switch localAppearance {
|
switch localAppearance {
|
||||||
@@ -118,7 +113,7 @@ struct OnboardingView: View {
|
|||||||
.font(.system(size: 22))
|
.font(.system(size: 22))
|
||||||
.foregroundColor(AppColors.green)
|
.foregroundColor(AppColors.green)
|
||||||
.transition(.scale.combined(with: .opacity))
|
.transition(.scale.combined(with: .opacity))
|
||||||
} else if calAuthStatus == .denied {
|
} else if calStore.isDenied {
|
||||||
Text("Denied")
|
Text("Denied")
|
||||||
.font(AppFonts.mono(10, weight: .bold))
|
.font(AppFonts.mono(10, weight: .bold))
|
||||||
.foregroundColor(AppColors.accent)
|
.foregroundColor(AppColors.accent)
|
||||||
@@ -137,7 +132,7 @@ struct OnboardingView: View {
|
|||||||
.buttonStyle(PressButtonStyle(scale: 0.92))
|
.buttonStyle(PressButtonStyle(scale: 0.92))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animation(KisaniSpring.micro, value: calAuthStatus.rawValue)
|
.animation(KisaniSpring.micro, value: calStore.authStatus.rawValue)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 14).padding(.vertical, 14)
|
.padding(.horizontal, 14).padding(.vertical, 14)
|
||||||
.cardStyle()
|
.cardStyle()
|
||||||
@@ -551,29 +546,17 @@ struct OnboardingView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
calAuthStatus = EKEventStore.authorizationStatus(for: .event)
|
calStore.refreshStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
private func requestCalendar() {
|
private func requestCalendar() {
|
||||||
if #available(iOS 17.0, *) {
|
if calStore.isDenied, let url = URL(string: UIApplication.openSettingsURLString) {
|
||||||
ekStore.requestFullAccessToEvents { granted, _ in
|
UIApplication.shared.open(url)
|
||||||
Task { @MainActor in
|
|
||||||
withAnimation(KisaniSpring.snappy) {
|
|
||||||
self.calAuthStatus = granted ? .fullAccess : .denied
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ekStore.requestAccess(to: .event) { granted, _ in
|
calStore.requestAccess()
|
||||||
Task { @MainActor in
|
|
||||||
withAnimation(KisaniSpring.snappy) {
|
|
||||||
self.calAuthStatus = granted ? .authorized : .denied
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ struct TodayView: View {
|
|||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
@EnvironmentObject var taskVM: TaskViewModel
|
@EnvironmentObject var taskVM: TaskViewModel
|
||||||
@EnvironmentObject var workoutVM: WorkoutViewModel
|
@EnvironmentObject var workoutVM: WorkoutViewModel
|
||||||
@StateObject private var calStore = CalendarStore()
|
@StateObject private var calStore = CalendarStore.shared
|
||||||
@State private var showAddTask = false
|
@State private var showAddTask = false
|
||||||
@State private var showWorkoutSession = false
|
@State private var showWorkoutSession = false
|
||||||
@State private var editingTask: TaskItem? = nil
|
@State private var editingTask: TaskItem? = nil
|
||||||
|
|||||||
Reference in New Issue
Block a user