diff --git a/KisaniCal.xcodeproj/project.pbxproj b/KisaniCal.xcodeproj/project.pbxproj index 0eadef7..9216fb1 100644 --- a/KisaniCal.xcodeproj/project.pbxproj +++ b/KisaniCal.xcodeproj/project.pbxproj @@ -10,7 +10,12 @@ 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 */; }; 8BE9D3D650B0F06169EC7048 /* SharedComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A4491BFA50721082024756 /* SharedComponents.swift */; }; 9070521B1D36A5551976C275 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADF6CCD95A587E26E30F5712 /* CalendarView.swift */; }; @@ -19,35 +24,74 @@ 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 */; }; + CF130E143FC6FFE2FE853FB6 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */; }; D591A72235A53D4038FBC2B4 /* KisaniCalApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */; }; - E7F1A2B3C4D5E6F7A8B9C0D1 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A1B2C3D4E5F6A7B8C9D0E1 /* NotificationManager.swift */; }; - B1C2D3E4F5A6B7C8D9E0F1A2 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D3E4F5A6B7C8D9E0F1A2B3 /* OnboardingView.swift */; }; - E2F3A4B5C6D7E8F9A0B1C2D3 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3E4F5A6B7C8D9E0F1A2B3C4 /* SplashView.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 = ""; }; - 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KisaniCal.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 825C519927190A5CDB3CD4A8 /* KisaniCalWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KisaniCalWidget.swift; sourceTree = ""; }; + 8934E4F21B90C9650F71D0A2 /* WidgetBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBridge.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 = ""; }; + BE2D50B4B4AC227BD21F4B60 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; C786EBC7DF879D64EB28165E /* TodayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayView.swift; sourceTree = ""; }; - C2D3E4F5A6B7C8D9E0F1A2B3 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; - D3E4F5A6B7C8D9E0F1A2B3C4 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; D44530A77DF12A17E52AAF34 /* MatrixView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixView.swift; sourceTree = ""; }; DCC2AFB4FA765383740767CB /* TaskItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskItem.swift; sourceTree = ""; }; - F7A1B2C3D4E5F6A7B8C9D0E1 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; /* 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 = ( @@ -56,6 +100,14 @@ path = Components; sourceTree = ""; }; + 2217B7EEC45957B820311EC7 /* Shared */ = { + isa = PBXGroup; + children = ( + A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */, + ); + path = Shared; + sourceTree = ""; + }; 23CBCF100C5EF55E737379CA /* Models */ = { isa = PBXGroup; children = ( @@ -69,6 +121,8 @@ isa = PBXGroup; children = ( F70DA4746C68CD405435DAB6 /* KisaniCal */, + 10E8E769F7D262FF425B460B /* KisaniCalWidget */, + 2217B7EEC45957B820311EC7 /* Shared */, 51BD1B5DEDE9FAD9CA2FF6DA /* Products */, ); sourceTree = ""; @@ -77,6 +131,7 @@ isa = PBXGroup; children = ( 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */, + 363924D4E7E82486E23C1C57 /* KisaniCalWidget.appex */, ); name = Products; sourceTree = ""; @@ -87,11 +142,11 @@ 768D4E314252E2A90A06CCF2 /* AddExerciseSheet.swift */, ADF6CCD95A587E26E30F5712 /* CalendarView.swift */, D44530A77DF12A17E52AAF34 /* MatrixView.swift */, + 106EEF572C6F8990408329F0 /* OnboardingView.swift */, 72308FEE0226F45414C04DDD /* SettingsView.swift */, + BE2D50B4B4AC227BD21F4B60 /* SplashView.swift */, C786EBC7DF879D64EB28165E /* TodayView.swift */, 20DAD771EFCC8B3B1DBD4DD6 /* WorkoutView.swift */, - C2D3E4F5A6B7C8D9E0F1A2B3 /* OnboardingView.swift */, - D3E4F5A6B7C8D9E0F1A2B3C4 /* SplashView.swift */, ); path = Views; sourceTree = ""; @@ -112,22 +167,16 @@ path = Theme; sourceTree = ""; }; - A1B2C3D4E5F6070A8B9C0D1E /* Managers */ = { - isa = PBXGroup; - children = ( - F7A1B2C3D4E5F6A7B8C9D0E1 /* NotificationManager.swift */, - ); - path = Managers; - sourceTree = ""; - }; F70DA4746C68CD405435DAB6 /* KisaniCal */ = { isa = PBXGroup; children = ( 72FDF9C8DD37134576356B89 /* Assets.xcassets */, BC5E179A9189B0A8C3F856F6 /* ContentView.swift */, + 1AA7498EFED7692022F3E7E1 /* KisaniCal.entitlements */, 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */, + 8934E4F21B90C9650F71D0A2 /* WidgetBridge.swift */, 21B93C269F283F11B415B18C /* Components */, - A1B2C3D4E5F6070A8B9C0D1E /* Managers */, + FB9BF734B9E493EEB09ACE21 /* Managers */, 23CBCF100C5EF55E737379CA /* Models */, 9981C53A4E263A0DBC3DF194 /* Preview Content */, E0A03E79A679740978E61BF1 /* Theme */, @@ -136,6 +185,14 @@ path = KisaniCal; sourceTree = ""; }; + FB9BF734B9E493EEB09ACE21 /* Managers */ = { + isa = PBXGroup; + children = ( + 93D045FE3DEB1D22D908A29F /* NotificationManager.swift */, + ); + path = Managers; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -145,10 +202,12 @@ buildPhases = ( 82EA49FE155821D424C46912 /* Sources */, E1F1A3EE4469B851B972E4AE /* Resources */, + B3BE3000B362E4DDC4EE9E76 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 3FA0FA885B1A4FC539D57049 /* PBXTargetDependency */, ); name = KisaniCal; packageProductDependencies = ( @@ -157,6 +216,23 @@ 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 */ @@ -169,6 +245,9 @@ 297C0C2BFBA10AB52D5D49CE = { ProvisioningStyle = Automatic; }; + 532C52E96C1EE1E30A7E9157 = { + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = EF1C4F3BDA4AA7E008FC2CDE /* Build configuration list for PBXProject "KisaniCal" */; @@ -186,6 +265,7 @@ projectRoot = ""; targets = ( 297C0C2BFBA10AB52D5D49CE /* KisaniCal */, + 532C52E96C1EE1E30A7E9157 /* KisaniCalWidget */, ); }; /* End PBXProject section */ @@ -214,28 +294,47 @@ 1A22EE21460821170E44B1DF /* ExerciseModels.swift in Sources */, D591A72235A53D4038FBC2B4 /* KisaniCalApp.swift in Sources */, BEAFF968632A34C70B11C5AC /* MatrixView.swift in Sources */, + EF03EC9F974B14D100DD5528 /* NotificationManager.swift in Sources */, + 67CE1747E5DB3FDA79D0FDFD /* OnboardingView.swift in Sources */, A3B2D6622A2EE35C8D5A3C9B /* SettingsView.swift in Sources */, 8BE9D3D650B0F06169EC7048 /* SharedComponents.swift in Sources */, + 6921CB73A3257502FF778381 /* SplashView.swift in Sources */, 096804560A2F0A7D74E64780 /* TaskItem.swift in Sources */, - E7F1A2B3C4D5E6F7A8B9C0D1 /* NotificationManager.swift in Sources */, C79C33BE2802E81AA00175CC /* TodayView.swift in Sources */, + 4F829CF0B3BD04CD2EAB8C73 /* WidgetBridge.swift in Sources */, + 3256F64040E5BBCD30644047 /* WidgetDataStore.swift in Sources */, 9E3F966F6B6AF2E4F8109E51 /* WorkoutView.swift in Sources */, - B1C2D3E4F5A6B7C8D9E0F1A2 /* OnboardingView.swift in Sources */, - E2F3A4B5C6D7E8F9A0B1C2D3 /* SplashView.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; - DEVELOPMENT_TEAM = K8BLMMR883; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Kisani Cal"; @@ -314,6 +413,40 @@ }; 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 = 16.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 = 16.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 = { @@ -382,9 +515,9 @@ 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; - DEVELOPMENT_TEAM = K8BLMMR883; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Kisani Cal"; @@ -410,6 +543,15 @@ /* 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.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/KisaniCal.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/KisaniCal.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/KisaniCal/ContentView.swift b/KisaniCal/ContentView.swift index ce5dc78..9024147 100644 --- a/KisaniCal/ContentView.swift +++ b/KisaniCal/ContentView.swift @@ -73,12 +73,19 @@ struct ContentView: View { OnboardingView(isPresented: $showOnboarding) .onDisappear { hasOnboarded = true } } - .onAppear { if !hasOnboarded { showOnboarding = true } } + .onAppear { + if !hasOnboarded { showOnboarding = true } + taskVM.syncWidget() + workoutVM.syncWidget() + } .onChange(of: scenePhase) { phase in if phase == .active { NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM) + taskVM.syncWidget() + workoutVM.syncWidget() } } + .onChange(of: taskVM.tasks.count) { _ in taskVM.syncWidget() } .onChange(of: workoutVM.doneSets) { _ in NotificationManager.shared.reschedule(workoutVM: workoutVM, taskVM: taskVM) } diff --git a/KisaniCal/KisaniCal.entitlements b/KisaniCal/KisaniCal.entitlements new file mode 100644 index 0000000..6598947 --- /dev/null +++ b/KisaniCal/KisaniCal.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.kutesir.KisaniCal + + + diff --git a/KisaniCal/WidgetBridge.swift b/KisaniCal/WidgetBridge.swift new file mode 100644 index 0000000..8cbbf56 --- /dev/null +++ b/KisaniCal/WidgetBridge.swift @@ -0,0 +1,41 @@ +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 new file mode 100644 index 0000000..6598947 --- /dev/null +++ b/KisaniCalWidget/KisaniCalWidget.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.kutesir.KisaniCal + + + diff --git a/KisaniCalWidget/KisaniCalWidget.swift b/KisaniCalWidget/KisaniCalWidget.swift new file mode 100644 index 0000000..27921f7 --- /dev/null +++ b/KisaniCalWidget/KisaniCalWidget.swift @@ -0,0 +1,414 @@ +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 new file mode 100644 index 0000000..92bf08f --- /dev/null +++ b/Shared/WidgetDataStore.swift @@ -0,0 +1,61 @@ +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 8267f64..a6c5f09 100644 --- a/project.yml +++ b/project.yml @@ -13,6 +13,7 @@ targets: deploymentTarget: "16.0" sources: - path: KisaniCal + - path: Shared settings: base: SWIFT_VERSION: 5.9 @@ -28,5 +29,27 @@ 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: "16.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"