diff --git a/KisaniCal.xcodeproj/project.pbxproj b/KisaniCal.xcodeproj/project.pbxproj index 02210a5..85b6af9 100644 --- a/KisaniCal.xcodeproj/project.pbxproj +++ b/KisaniCal.xcodeproj/project.pbxproj @@ -10,10 +10,7 @@ 096804560A2F0A7D74E64780 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC2AFB4FA765383740767CB /* TaskItem.swift */; }; 13E4B9854F595394FC9D5912 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5E179A9189B0A8C3F856F6 /* ContentView.swift */; }; 1A22EE21460821170E44B1DF /* ExerciseModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5722CC4B59E3939724142710 /* ExerciseModels.swift */; }; - 3256F64040E5BBCD30644047 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */; }; 3C793FD5DA00D3E9C0D51FEC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 72FDF9C8DD37134576356B89 /* Assets.xcassets */; }; - 46D78859DD34FDE6172F213D /* KisaniCalWidget.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 363924D4E7E82486E23C1C57 /* KisaniCalWidget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 4F829CF0B3BD04CD2EAB8C73 /* WidgetBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8934E4F21B90C9650F71D0A2 /* WidgetBridge.swift */; }; 67CE1747E5DB3FDA79D0FDFD /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106EEF572C6F8990408329F0 /* OnboardingView.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 */; }; @@ -24,57 +21,25 @@ A3B2D6622A2EE35C8D5A3C9B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72308FEE0226F45414C04DDD /* SettingsView.swift */; }; A9FF93259AE8FF0ABF69D71A /* DesignTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4A35C0E1270E2E15C03F23 /* DesignTokens.swift */; }; BEAFF968632A34C70B11C5AC /* MatrixView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44530A77DF12A17E52AAF34 /* MatrixView.swift */; }; - C1B3FC29820FCAF069D2C6FE /* KisaniCalWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825C519927190A5CDB3CD4A8 /* KisaniCalWidget.swift */; }; C79C33BE2802E81AA00175CC /* TodayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C786EBC7DF879D64EB28165E /* TodayView.swift */; }; CBE7295BF5ADE08FE93AFAAF /* FloatingTabState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9100804DB1E61EA882CC54DA /* FloatingTabState.swift */; }; - CF130E143FC6FFE2FE853FB6 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */; }; D591A72235A53D4038FBC2B4 /* KisaniCalApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */; }; EF03EC9F974B14D100DD5528 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D045FE3DEB1D22D908A29F /* NotificationManager.swift */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - D4A1B57264F223C8871E1BC9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = AF6DE7A812408E3742522E90 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 532C52E96C1EE1E30A7E9157; - remoteInfo = KisaniCalWidget; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - B3BE3000B362E4DDC4EE9E76 /* Embed Foundation Extensions */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 13; - files = ( - 46D78859DD34FDE6172F213D /* KisaniCalWidget.appex in Embed Foundation Extensions */, - ); - name = "Embed Foundation Extensions"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KisaniCalApp.swift; sourceTree = ""; }; 106EEF572C6F8990408329F0 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; - 1AA7498EFED7692022F3E7E1 /* KisaniCal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KisaniCal.entitlements; sourceTree = ""; }; 20DAD771EFCC8B3B1DBD4DD6 /* WorkoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutView.swift; sourceTree = ""; }; 23A4491BFA50721082024756 /* SharedComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedComponents.swift; sourceTree = ""; }; - 363924D4E7E82486E23C1C57 /* KisaniCalWidget.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = KisaniCalWidget.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; 72308FEE0226F45414C04DDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 72FDF9C8DD37134576356B89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 74EE7C2E74094E3E27FD78E4 /* KisaniCalWidget.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KisaniCalWidget.entitlements; sourceTree = ""; }; 768D4E314252E2A90A06CCF2 /* AddExerciseSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExerciseSheet.swift; sourceTree = ""; }; - 825C519927190A5CDB3CD4A8 /* KisaniCalWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KisaniCalWidget.swift; sourceTree = ""; }; - 8934E4F21B90C9650F71D0A2 /* WidgetBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBridge.swift; sourceTree = ""; }; 9100804DB1E61EA882CC54DA /* FloatingTabState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingTabState.swift; 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; }; - A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = ""; }; ADF6CCD95A587E26E30F5712 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = ""; }; BC5E179A9189B0A8C3F856F6 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; BD4A35C0E1270E2E15C03F23 /* DesignTokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignTokens.swift; sourceTree = ""; }; @@ -85,15 +50,6 @@ /* End PBXFileReference section */ /* Begin PBXGroup section */ - 10E8E769F7D262FF425B460B /* KisaniCalWidget */ = { - isa = PBXGroup; - children = ( - 74EE7C2E74094E3E27FD78E4 /* KisaniCalWidget.entitlements */, - 825C519927190A5CDB3CD4A8 /* KisaniCalWidget.swift */, - ); - path = KisaniCalWidget; - sourceTree = ""; - }; 21B93C269F283F11B415B18C /* Components */ = { isa = PBXGroup; children = ( @@ -103,14 +59,6 @@ path = Components; sourceTree = ""; }; - 2217B7EEC45957B820311EC7 /* Shared */ = { - isa = PBXGroup; - children = ( - A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */, - ); - path = Shared; - sourceTree = ""; - }; 23CBCF100C5EF55E737379CA /* Models */ = { isa = PBXGroup; children = ( @@ -124,8 +72,6 @@ isa = PBXGroup; children = ( F70DA4746C68CD405435DAB6 /* KisaniCal */, - 10E8E769F7D262FF425B460B /* KisaniCalWidget */, - 2217B7EEC45957B820311EC7 /* Shared */, 51BD1B5DEDE9FAD9CA2FF6DA /* Products */, ); sourceTree = ""; @@ -134,7 +80,6 @@ isa = PBXGroup; children = ( 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */, - 363924D4E7E82486E23C1C57 /* KisaniCalWidget.appex */, ); name = Products; sourceTree = ""; @@ -175,9 +120,7 @@ children = ( 72FDF9C8DD37134576356B89 /* Assets.xcassets */, BC5E179A9189B0A8C3F856F6 /* ContentView.swift */, - 1AA7498EFED7692022F3E7E1 /* KisaniCal.entitlements */, 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */, - 8934E4F21B90C9650F71D0A2 /* WidgetBridge.swift */, 21B93C269F283F11B415B18C /* Components */, FB9BF734B9E493EEB09ACE21 /* Managers */, 23CBCF100C5EF55E737379CA /* Models */, @@ -205,12 +148,10 @@ buildPhases = ( 82EA49FE155821D424C46912 /* Sources */, E1F1A3EE4469B851B972E4AE /* Resources */, - B3BE3000B362E4DDC4EE9E76 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( - 3FA0FA885B1A4FC539D57049 /* PBXTargetDependency */, ); name = KisaniCal; packageProductDependencies = ( @@ -219,23 +160,6 @@ productReference = 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */; productType = "com.apple.product-type.application"; }; - 532C52E96C1EE1E30A7E9157 /* KisaniCalWidget */ = { - isa = PBXNativeTarget; - buildConfigurationList = 6B8469B838ACDD00336D1CAD /* Build configuration list for PBXNativeTarget "KisaniCalWidget" */; - buildPhases = ( - B3D66A55B74C7B9AE6099149 /* Sources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = KisaniCalWidget; - packageProductDependencies = ( - ); - productName = KisaniCalWidget; - productReference = 363924D4E7E82486E23C1C57 /* KisaniCalWidget.appex */; - productType = "com.apple.product-type.app-extension"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -248,9 +172,6 @@ 297C0C2BFBA10AB52D5D49CE = { ProvisioningStyle = Automatic; }; - 532C52E96C1EE1E30A7E9157 = { - ProvisioningStyle = Automatic; - }; }; }; buildConfigurationList = EF1C4F3BDA4AA7E008FC2CDE /* Build configuration list for PBXProject "KisaniCal" */; @@ -268,7 +189,6 @@ projectRoot = ""; targets = ( 297C0C2BFBA10AB52D5D49CE /* KisaniCal */, - 532C52E96C1EE1E30A7E9157 /* KisaniCalWidget */, ); }; /* End PBXProject section */ @@ -305,38 +225,18 @@ 6921CB73A3257502FF778381 /* SplashView.swift in Sources */, 096804560A2F0A7D74E64780 /* TaskItem.swift in Sources */, C79C33BE2802E81AA00175CC /* TodayView.swift in Sources */, - 4F829CF0B3BD04CD2EAB8C73 /* WidgetBridge.swift in Sources */, - 3256F64040E5BBCD30644047 /* WidgetDataStore.swift in Sources */, 9E3F966F6B6AF2E4F8109E51 /* WorkoutView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - B3D66A55B74C7B9AE6099149 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C1B3FC29820FCAF069D2C6FE /* KisaniCalWidget.swift in Sources */, - CF130E143FC6FFE2FE853FB6 /* WidgetDataStore.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - 3FA0FA885B1A4FC539D57049 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 532C52E96C1EE1E30A7E9157 /* KisaniCalWidget */; - targetProxy = D4A1B57264F223C8871E1BC9 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin XCBuildConfiguration section */ 0CEA6637F45C32A237FBA20D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = KisaniCal/KisaniCal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; ENABLE_PREVIEWS = YES; @@ -417,40 +317,6 @@ }; name = Release; }; - 47C8DA15444717168992CD67 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = KisaniCalWidget/KisaniCalWidget.entitlements; - CODE_SIGN_STYLE = Automatic; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.widget; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.9; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 9F668AA275094D23AACA2F61 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = KisaniCalWidget/KisaniCalWidget.entitlements; - CODE_SIGN_STYLE = Automatic; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.widget; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.9; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; BD527AA54887489CC665FB9D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -519,7 +385,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = KisaniCal/KisaniCal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; ENABLE_PREVIEWS = YES; @@ -547,15 +412,6 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 6B8469B838ACDD00336D1CAD /* Build configuration list for PBXNativeTarget "KisaniCalWidget" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 47C8DA15444717168992CD67 /* Debug */, - 9F668AA275094D23AACA2F61 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; - }; B60FDD2C88D1D33CF1EC02B3 /* Build configuration list for PBXNativeTarget "KisaniCal" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/KisaniCal/KisaniCal.entitlements b/KisaniCal.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 58% rename from KisaniCal/KisaniCal.entitlements rename to KisaniCal.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 6598947..54782e3 100644 --- a/KisaniCal/KisaniCal.entitlements +++ b/KisaniCal.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -2,9 +2,7 @@ - com.apple.security.application-groups - - group.com.kutesir.KisaniCal - + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/KisaniCal.xcodeproj/project.xcworkspace/xcuserdata/kutesir.xcuserdatad/UserInterfaceState.xcuserstate b/KisaniCal.xcodeproj/project.xcworkspace/xcuserdata/kutesir.xcuserdatad/UserInterfaceState.xcuserstate index 077aa32..7851de8 100644 Binary files a/KisaniCal.xcodeproj/project.xcworkspace/xcuserdata/kutesir.xcuserdatad/UserInterfaceState.xcuserstate and b/KisaniCal.xcodeproj/project.xcworkspace/xcuserdata/kutesir.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/KisaniCal/ContentView.swift b/KisaniCal/ContentView.swift index 059a0f2..3d4ae77 100644 --- a/KisaniCal/ContentView.swift +++ b/KisaniCal/ContentView.swift @@ -76,18 +76,14 @@ struct ContentView: View { } .onAppear { if !hasOnboarded { showOnboarding = true } - taskVM.syncWidget() - workoutVM.syncWidget() + NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM) } .onChange(of: scenePhase) { phase in if phase == .active { NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM) - taskVM.syncWidget() - workoutVM.syncWidget() } } .onChange(of: taskVM.tasks) { _ in - taskVM.syncWidget() NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM) } .onChange(of: workoutVM.doneSets) { _ in diff --git a/KisaniCal/WidgetBridge.swift b/KisaniCal/WidgetBridge.swift deleted file mode 100644 index 8cbbf56..0000000 --- a/KisaniCal/WidgetBridge.swift +++ /dev/null @@ -1,41 +0,0 @@ -import WidgetKit - -// Syncs app ViewModels into App Group UserDefaults so the widget can read them, -// then asks WidgetKit to reload all timelines. - -extension TaskViewModel { - func syncWidget() { - let cal = Calendar.current - let start = cal.startOfDay(for: Date()) - let end = cal.date(byAdding: .day, value: 1, to: start)! - - let entries = tasks - .filter { !$0.isComplete } - .prefix(10) - .map { t in - WidgetDataStore.WidgetTask( - id: t.id, - title: t.title, - dueDate: t.dueDate, - isToday: t.dueDate.map { $0 >= start && $0 < end } ?? false, - isOverdue: t.dueDate.map { $0 < start } ?? false - ) - } - WidgetDataStore.saveTasks(Array(entries)) - WidgetCenter.shared.reloadAllTimelines() - } -} - -extension WorkoutViewModel { - func syncWidget() { - let weekday = Calendar.current.component(.weekday, from: Date()) - let todayProgram = program(for: Date()) - let workout = WidgetDataStore.WidgetWorkout( - name: activeProgram?.name ?? "Workout", - sectionsCount: activeProgram?.sections.count ?? 0, - hasWorkoutToday: todayProgram != nil - ) - WidgetDataStore.saveWorkout(workout) - WidgetCenter.shared.reloadAllTimelines() - } -} diff --git a/KisaniCalWidget/KisaniCalWidget.entitlements b/KisaniCalWidget/KisaniCalWidget.entitlements deleted file mode 100644 index 6598947..0000000 --- a/KisaniCalWidget/KisaniCalWidget.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.application-groups - - group.com.kutesir.KisaniCal - - - diff --git a/KisaniCalWidget/KisaniCalWidget.swift b/KisaniCalWidget/KisaniCalWidget.swift deleted file mode 100644 index 27921f7..0000000 --- a/KisaniCalWidget/KisaniCalWidget.swift +++ /dev/null @@ -1,414 +0,0 @@ -import WidgetKit -import SwiftUI - -// MARK: - Colors (no UIKit dependency) - -private enum WC { - static let accent = Color(red: 232/255, green: 98/255, blue: 42/255) - static let green = Color(red: 62/255, green: 207/255, blue: 142/255) - static let blue = Color(red: 77/255, green: 157/255, blue: 224/255) - - static func bg(_ s: ColorScheme) -> Color { - s == .dark ? Color(red: 9/255, green: 9/255, blue: 9/255) - : Color(red: 245/255, green: 243/255, blue: 240/255) - } - static func surface(_ s: ColorScheme) -> Color { - s == .dark ? Color(red: 19/255, green: 19/255, blue: 19/255) - : Color(red: 255/255, green: 253/255, blue: 250/255) - } - static func text(_ s: ColorScheme) -> Color { - s == .dark ? Color(red: 240/255, green: 237/255, blue: 232/255) - : Color(red: 26/255, green: 23/255, blue: 20/255) - } - static func text2(_ s: ColorScheme) -> Color { text(s).opacity(0.48) } - static func text3(_ s: ColorScheme) -> Color { text(s).opacity(0.30) } - static func border(_ s: ColorScheme) -> Color { - s == .dark ? Color.white.opacity(0.07) : Color.black.opacity(0.09) - } -} - -// MARK: - Timeline Entry - -struct KisaniEntry: TimelineEntry { - let date: Date - let todayTasks: [WidgetDataStore.WidgetTask] - let overdueTasks: [WidgetDataStore.WidgetTask] - let workout: WidgetDataStore.WidgetWorkout? -} - -// MARK: - Provider - -struct KisaniProvider: TimelineProvider { - func placeholder(in context: Context) -> KisaniEntry { - KisaniEntry( - date: Date(), - todayTasks: previewTasks, - overdueTasks: [], - workout: WidgetDataStore.WidgetWorkout( - name: "Shoulders & Arms", sectionsCount: 4, hasWorkoutToday: true) - ) - } - - func getSnapshot(in context: Context, completion: @escaping (KisaniEntry) -> Void) { - completion(entry()) - } - - func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { - let next = Calendar.current.date(byAdding: .minute, value: 30, to: Date())! - completion(Timeline(entries: [entry()], policy: .after(next))) - } - - private func entry() -> KisaniEntry { - let all = WidgetDataStore.loadTasks() - let today = all.filter { $0.isToday } - let overdue = all.filter { $0.isOverdue } - let workout = WidgetDataStore.loadWorkout() - return KisaniEntry(date: Date(), todayTasks: today, overdueTasks: overdue, workout: workout) - } - - private var previewTasks: [WidgetDataStore.WidgetTask] { - [ - .init(id: UUID(), title: "Azure exam prep", dueDate: Date(), isToday: true, isOverdue: false), - .init(id: UUID(), title: "Push AWS project", dueDate: Date(), isToday: true, isOverdue: false), - .init(id: UUID(), title: "Review PR #42", dueDate: Date(), isToday: false, isOverdue: true), - ] - } -} - -// MARK: - Widget Bundle - -@main -struct KisaniCalWidgetBundle: WidgetBundle { - var body: some Widget { - KisaniCalWidget() - } -} - -struct KisaniCalWidget: Widget { - let kind = "KisaniCalWidget" - - var body: some WidgetConfiguration { - StaticConfiguration(kind: kind, provider: KisaniProvider()) { entry in - KisaniWidgetView(entry: entry) - .containerBackground(for: .widget) { Color.clear } - } - .configurationDisplayName("Kisani Cal") - .description("Today's tasks and workout at a glance.") - .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) - } -} - -// MARK: - Root Widget View - -struct KisaniWidgetView: View { - @Environment(\.widgetFamily) private var family - let entry: KisaniEntry - - var body: some View { - switch family { - case .systemSmall: SmallWidget(entry: entry) - case .systemMedium: MediumWidget(entry: entry) - default: LargeWidget(entry: entry) - } - } -} - -// MARK: - Shared Sub-Views - -private struct DateBadge: View { - @Environment(\.colorScheme) private var cs - let size: CGFloat - - private static let monthFmt: DateFormatter = { - let f = DateFormatter(); f.dateFormat = "MMM"; return f - }() - - var body: some View { - let day = Calendar.current.component(.day, from: Date()) - VStack(spacing: 0) { - Text(Self.monthFmt.string(from: Date()).uppercased()) - .font(.system(size: size * 0.18, weight: .bold, design: .monospaced)) - .foregroundColor(.white) - .kerning(1) - .frame(maxWidth: .infinity) - .frame(height: size * 0.32) - .background(WC.accent) - Text("\(day)") - .font(.system(size: size * 0.48, weight: .bold, design: .monospaced)) - .foregroundColor(WC.text(cs)) - .frame(maxWidth: .infinity) - .frame(height: size * 0.68) - .background(WC.surface(cs)) - } - .frame(width: size, height: size) - .clipShape(RoundedRectangle(cornerRadius: size * 0.22)) - .overlay( - RoundedRectangle(cornerRadius: size * 0.22) - .stroke(WC.border(cs), lineWidth: 1) - ) - } -} - -private struct TaskRow: View { - @Environment(\.colorScheme) private var cs - let task: WidgetDataStore.WidgetTask - - private static let fmt: DateFormatter = { - let f = DateFormatter(); f.dateFormat = "d MMM"; return f - }() - - var body: some View { - HStack(spacing: 5) { - Circle() - .stroke(task.isOverdue ? WC.accent : WC.text3(cs), lineWidth: 1.2) - .frame(width: 7, height: 7) - Text(task.title) - .font(.system(size: 11, weight: .regular, design: .default)) - .foregroundColor(WC.text(cs)) - .lineLimit(1) - Spacer(minLength: 0) - if let d = task.dueDate { - Text(task.isOverdue ? "Overdue" : Self.fmt.string(from: d)) - .font(.system(size: 9, weight: .regular, design: .monospaced)) - .foregroundColor(task.isOverdue ? WC.accent : WC.text3(cs)) - } - } - } -} - -private struct WorkoutRow: View { - @Environment(\.colorScheme) private var cs - let workout: WidgetDataStore.WidgetWorkout - - var body: some View { - HStack(spacing: 6) { - WorkoutSegBar(count: workout.sectionsCount) - .frame(width: 22, height: 22) - Text(workout.name) - .font(.system(size: 11, weight: .semibold, design: .default)) - .foregroundColor(WC.text(cs)) - .lineLimit(1) - Spacer(minLength: 0) - Text("Workout") - .font(.system(size: 9, weight: .regular, design: .monospaced)) - .foregroundColor(WC.blue) - } - } -} - -private struct WorkoutSegBar: View { - @Environment(\.colorScheme) private var cs - let count: Int - - var body: some View { - let n = max(1, min(6, count)) - VStack(spacing: 1.5) { - ForEach(0.. 0 { - Text("\(overdueCount) overdue · \(todayCount) today") - .foregroundColor(WC.accent) - } else if todayCount > 0 { - Text("\(todayCount) task\(todayCount == 1 ? "" : "s") today") - .foregroundColor(WC.text2(cs)) - } else { - Text("All clear") - .foregroundColor(WC.text3(cs)) - } - } - .font(.system(size: 9, weight: .regular, design: .monospaced)) - } - } - .padding(14) - } - } -} - -// MARK: - Medium Widget - -private struct MediumWidget: View { - @Environment(\.colorScheme) private var cs - let entry: KisaniEntry - - private var displayTasks: [WidgetDataStore.WidgetTask] { - let all = entry.overdueTasks + entry.todayTasks - return Array(all.prefix(3)) - } - - var body: some View { - ZStack { - WC.bg(cs).ignoresSafeArea() - - HStack(alignment: .top, spacing: 14) { - // Left: badge + brand - VStack(spacing: 8) { - DateBadge(size: 64) - HStack(spacing: 0) { - Text("kisani") - .font(.system(size: 9, weight: .regular, design: .monospaced)) - .foregroundColor(WC.text2(cs)) - Text("CAL.") - .font(.system(size: 9, weight: .bold, design: .monospaced)) - .foregroundColor(WC.accent) - } - } - - // Right: tasks + workout - VStack(alignment: .leading, spacing: 5) { - if displayTasks.isEmpty && entry.workout == nil { - Spacer() - Text("Nothing scheduled") - .font(.system(size: 11, design: .monospaced)) - .foregroundColor(WC.text3(cs)) - Spacer() - } else { - ForEach(displayTasks) { task in - TaskRow(task: task) - } - if let workout = entry.workout, workout.hasWorkoutToday, - displayTasks.count < 3 { - if !displayTasks.isEmpty { - Divider().opacity(0.4) - } - WorkoutRow(workout: workout) - } - Spacer(minLength: 0) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - } - .padding(14) - } - } -} - -// MARK: - Large Widget - -private struct LargeWidget: View { - @Environment(\.colorScheme) private var cs - let entry: KisaniEntry - - private static let dayFmt: DateFormatter = { - let f = DateFormatter(); f.dateFormat = "EEEE, MMM d"; return f - }() - - private var allTasks: [WidgetDataStore.WidgetTask] { - Array((entry.overdueTasks + entry.todayTasks).prefix(6)) - } - - var body: some View { - ZStack { - WC.bg(cs).ignoresSafeArea() - - VStack(alignment: .leading, spacing: 0) { - // Header row - HStack(spacing: 12) { - DateBadge(size: 56) - - VStack(alignment: .leading, spacing: 3) { - HStack(spacing: 0) { - Text("kisani") - .font(.system(size: 14, weight: .regular, design: .monospaced)) - .foregroundColor(WC.text2(cs)) - Text("CAL") - .font(.system(size: 14, weight: .bold, design: .monospaced)) - .foregroundColor(WC.text(cs)) - Text(".") - .font(.system(size: 14, weight: .bold, design: .monospaced)) - .foregroundColor(WC.accent) - } - Text(Self.dayFmt.string(from: Date())) - .font(.system(size: 10, design: .monospaced)) - .foregroundColor(WC.text2(cs)) - } - - Spacer() - - let total = entry.todayTasks.count + entry.overdueTasks.count - VStack(alignment: .trailing, spacing: 2) { - Text("\(total)") - .font(.system(size: 22, weight: .bold, design: .monospaced)) - .foregroundColor(entry.overdueTasks.isEmpty ? WC.text(cs) : WC.accent) - Text("tasks") - .font(.system(size: 9, design: .monospaced)) - .foregroundColor(WC.text3(cs)) - } - } - .padding(.horizontal, 14) - .padding(.top, 14) - .padding(.bottom, 12) - - Divider().opacity(0.4).padding(.horizontal, 14) - - // Task list - VStack(alignment: .leading, spacing: 7) { - if allTasks.isEmpty { - HStack { - Spacer() - Text("Nothing due today") - .font(.system(size: 11, design: .monospaced)) - .foregroundColor(WC.text3(cs)) - Spacer() - } - .padding(.vertical, 8) - } else { - ForEach(allTasks) { task in - TaskRow(task: task) - } - } - } - .padding(.horizontal, 14) - .padding(.vertical, 10) - - Spacer(minLength: 0) - - // Workout row - if let workout = entry.workout, workout.hasWorkoutToday { - Divider().opacity(0.4).padding(.horizontal, 14) - WorkoutRow(workout: workout) - .padding(.horizontal, 14) - .padding(.vertical, 10) - } - } - } - } -} diff --git a/Shared/WidgetDataStore.swift b/Shared/WidgetDataStore.swift deleted file mode 100644 index 92bf08f..0000000 --- a/Shared/WidgetDataStore.swift +++ /dev/null @@ -1,61 +0,0 @@ -import Foundation - -// Shared between app and widget extension via App Group UserDefaults. -// Types are pure Codable — no UIKit / AppColors dependency. - -struct WidgetDataStore { - static let suiteName = "group.com.kutesir.KisaniCal" - static let tasksKey = "kisani.widget.tasks" - static let workoutKey = "kisani.widget.workout" - - // MARK: - Shared types - - struct WidgetTask: Codable, Identifiable { - let id: UUID - let title: String - let dueDate: Date? - let isToday: Bool - let isOverdue: Bool - } - - struct WidgetWorkout: Codable { - let name: String - let sectionsCount: Int - let hasWorkoutToday: Bool - } - - // MARK: - Write (app target) - - static func saveTasks(_ tasks: [WidgetTask]) { - guard let data = try? JSONEncoder().encode(tasks), - let ud = UserDefaults(suiteName: suiteName) else { return } - ud.set(data, forKey: tasksKey) - } - - static func saveWorkout(_ workout: WidgetWorkout?) { - guard let ud = UserDefaults(suiteName: suiteName) else { return } - if let workout, let data = try? JSONEncoder().encode(workout) { - ud.set(data, forKey: workoutKey) - } else { - ud.removeObject(forKey: workoutKey) - } - } - - // MARK: - Read (widget target) - - static func loadTasks() -> [WidgetTask] { - guard let ud = UserDefaults(suiteName: suiteName), - let data = ud.data(forKey: tasksKey), - let list = try? JSONDecoder().decode([WidgetTask].self, from: data) - else { return [] } - return list - } - - static func loadWorkout() -> WidgetWorkout? { - guard let ud = UserDefaults(suiteName: suiteName), - let data = ud.data(forKey: workoutKey), - let workout = try? JSONDecoder().decode(WidgetWorkout.self, from: data) - else { return nil } - return workout - } -} diff --git a/project.yml b/project.yml index bb37048..8267f64 100644 --- a/project.yml +++ b/project.yml @@ -13,7 +13,6 @@ targets: deploymentTarget: "16.0" sources: - path: KisaniCal - - path: Shared settings: base: SWIFT_VERSION: 5.9 @@ -29,27 +28,5 @@ targets: ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor CODE_SIGN_STYLE: Automatic ENABLE_PREVIEWS: YES - CODE_SIGN_ENTITLEMENTS: KisaniCal/KisaniCal.entitlements INFOPLIST_KEY_NSCalendarsUsageDescription: "Kisani Cal shows your calendar events alongside tasks." INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription: "Kisani Cal shows your calendar events alongside tasks." - dependencies: - - target: KisaniCalWidget - embed: true - - KisaniCalWidget: - type: app-extension - platform: iOS - deploymentTarget: "17.0" - sources: - - path: KisaniCalWidget - - path: Shared - settings: - base: - SWIFT_VERSION: 5.9 - PRODUCT_BUNDLE_IDENTIFIER: com.kutesir.KisaniCal.widget - GENERATE_INFOPLIST_FILE: YES - INFOPLIST_KEY_NSExtensionPointIdentifier: com.apple.widgetkit-extension - CODE_SIGN_STYLE: Automatic - CODE_SIGN_ENTITLEMENTS: KisaniCalWidget/KisaniCalWidget.entitlements - SKIP_INSTALL: YES - LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"