diff --git a/KisaniCal.xcodeproj/project.pbxproj b/KisaniCal.xcodeproj/project.pbxproj index bfcfbc4..dcbfc1c 100644 --- a/KisaniCal.xcodeproj/project.pbxproj +++ b/KisaniCal.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 754D3DE3CEB998E36E585A61 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0506183945D16EC443A69651 /* LiveActivityManager.swift */; }; 79FC1DF6762C6F02D01AB643 /* KisaniCalWidgets.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7CEBC340BFA9238D121946AC /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 57714B79AFFF60DA90EB86E3 /* Preview Assets.xcassets */; }; + 8387093D19FB3397CCB8FEF8 /* WenzaWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF528FCC224EF283F95851AD /* WenzaWatchApp.swift */; }; 83EA218392952885C97144D1 /* TodayMenuFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5DB5340BEB3EF7A78CA153 /* TodayMenuFeatures.swift */; }; 8BE9D3D650B0F06169EC7048 /* SharedComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A4491BFA50721082024756 /* SharedComponents.swift */; }; 8C9567A3DE3F63A1ECAE84D5 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3D5289C5F93484E22DEB63 /* TutorialView.swift */; }; @@ -81,13 +82,14 @@ 0506183945D16EC443A69651 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = ""; }; 0513EB8C7F6B6853B9E93E09 /* TaskLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskLiveActivity.swift; sourceTree = ""; }; 0C96EC00F6B021DBA3CC1A79 /* ProfileSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSetupView.swift; sourceTree = ""; }; - 0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = KisaniCalWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = KisaniCalWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 106EEF572C6F8990408329F0 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; 1AA7498EFED7692022F3E7E1 /* KisaniCal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KisaniCal.entitlements; sourceTree = ""; }; 208F82348DEBD9FF7B0DCF17 /* MyTasksWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyTasksWidget.swift; 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 = ""; }; 326B77A7B317A7DB44E13EA5 /* ShortcutHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutHandler.swift; sourceTree = ""; }; + 35FF274EFF2361B7F3D2D227 /* WenzaWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WenzaWatch.entitlements; sourceTree = ""; }; 42650F655DB8B320C7C03929 /* KisaniCalWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KisaniCalWidgets.swift; sourceTree = ""; }; 429806CE1021C8DE2EB770CE /* WidgetViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetViews.swift; sourceTree = ""; }; 449C34805DC6B2CB66886544 /* CloudSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudSyncManager.swift; sourceTree = ""; }; @@ -99,15 +101,17 @@ 72308FEE0226F45414C04DDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 72FDF9C8DD37134576356B89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 768D4E314252E2A90A06CCF2 /* AddExerciseSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExerciseSheet.swift; sourceTree = ""; }; + 7A9D47B284FD6A217AEF813B /* WenzaWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WenzaWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 89550F2CD19B950CCC6AD37F /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = ""; }; 8DC8687EA2FBA9FB2EEE51C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8E7FC6446D5C5B2A2D9387DA /* KisaniCalWidgets.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KisaniCalWidgets.entitlements; sourceTree = ""; }; 9100804DB1E61EA882CC54DA /* FloatingTabState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingTabState.swift; sourceTree = ""; }; 92824ED40ECD41EFD4F78BEC /* ISSUES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ISSUES.md; sourceTree = ""; }; 93D045FE3DEB1D22D908A29F /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; - 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KisaniCal.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = KisaniCal.app; sourceTree = BUILT_PRODUCTS_DIR; }; ADF6CCD95A587E26E30F5712 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = ""; }; AF905C574F34B4EE51A8D21E /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = ""; }; + B4CFFDFE4653A9E901CEF28D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; 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 = ""; }; @@ -119,6 +123,7 @@ FA3D5289C5F93484E22DEB63 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = ""; }; FC17C7BA15BD2E84AE5F77CF /* EventCountdownWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCountdownWidget.swift; sourceTree = ""; }; FEEB07EB89E89383C32ADB34 /* TaskActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskActivityAttributes.swift; sourceTree = ""; }; + FF528FCC224EF283F95851AD /* WenzaWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WenzaWatchApp.swift; sourceTree = ""; }; FF5AFD143B693B77D07FBDA4 /* HealthKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -141,11 +146,22 @@ path = Models; sourceTree = ""; }; + 48146B56E740528496663D47 /* WenzaWatch */ = { + isa = PBXGroup; + children = ( + B4CFFDFE4653A9E901CEF28D /* Info.plist */, + 35FF274EFF2361B7F3D2D227 /* WenzaWatch.entitlements */, + FF528FCC224EF283F95851AD /* WenzaWatchApp.swift */, + ); + path = WenzaWatch; + sourceTree = ""; + }; 4D2F2B3472A5A6D99F2FFCD2 = { isa = PBXGroup; children = ( F70DA4746C68CD405435DAB6 /* KisaniCal */, E8D727EEA0C3A4B8006FB087 /* KisaniCalWidgets */, + 48146B56E740528496663D47 /* WenzaWatch */, 51BD1B5DEDE9FAD9CA2FF6DA /* Products */, ); sourceTree = ""; @@ -155,6 +171,7 @@ children = ( 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */, 0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */, + 7A9D47B284FD6A217AEF813B /* WenzaWatch.app */, ); name = Products; sourceTree = ""; @@ -285,6 +302,23 @@ productReference = 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */; productType = "com.apple.product-type.application"; }; + 403D989BDC6268361CFFB479 /* WenzaWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6F608724A2EAA63171766586 /* Build configuration list for PBXNativeTarget "WenzaWatch" */; + buildPhases = ( + 6DEFD550940764CA28D993CE /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = WenzaWatch; + packageProductDependencies = ( + ); + productName = WenzaWatch; + productReference = 7A9D47B284FD6A217AEF813B /* WenzaWatch.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -292,7 +326,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 2620; + LastUpgradeCheck = 1500; TargetAttributes = { 28B43516AD88946E21D9BFE0 = { DevelopmentTeam = K8BLMMR883; @@ -302,6 +336,10 @@ DevelopmentTeam = K8BLMMR883; ProvisioningStyle = Automatic; }; + 403D989BDC6268361CFFB479 = { + DevelopmentTeam = K8BLMMR883; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = EF1C4F3BDA4AA7E008FC2CDE /* Build configuration list for PBXProject "KisaniCal" */; @@ -320,6 +358,7 @@ targets = ( 297C0C2BFBA10AB52D5D49CE /* KisaniCal */, 28B43516AD88946E21D9BFE0 /* KisaniCalWidgets */, + 403D989BDC6268361CFFB479 /* WenzaWatch */, ); }; /* End PBXProject section */ @@ -339,6 +378,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 6DEFD550940764CA28D993CE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8387093D19FB3397CCB8FEF8 /* WenzaWatchApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 82EA49FE155821D424C46912 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -409,7 +456,6 @@ CODE_SIGN_ENTITLEMENTS = KisaniCal/KisaniCal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = Wenza; @@ -421,14 +467,13 @@ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; - INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2; PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal; SDKROOT = iphoneos; SWIFT_VERSION = 5.9; @@ -475,7 +520,6 @@ DEVELOPMENT_TEAM = K8BLMMR883; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -489,8 +533,6 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; @@ -503,16 +545,10 @@ APPLICATION_EXTENSION_API_ONLY = YES; CODE_SIGN_ENTITLEMENTS = KisaniCalWidgets/KisaniCalWidgets.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = KisaniCalWidgets/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 18.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets; SDKROOT = iphoneos; SWIFT_VERSION = 5.9; @@ -559,7 +595,6 @@ DEVELOPMENT_TEAM = K8BLMMR883; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -580,30 +615,40 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; + D3A1329FCB6BBA1E8CC25FFC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = WenzaWatch/WenzaWatch.entitlements; + CODE_SIGN_STYLE = Automatic; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = WenzaWatch/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Wenza; + PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.watchkitapp; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.9; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; + }; DF56CDD73E9B177D57AE17FB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CODE_SIGN_ENTITLEMENTS = KisaniCalWidgets/KisaniCalWidgets.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = KisaniCalWidgets/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 18.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets; SDKROOT = iphoneos; SWIFT_VERSION = 5.9; @@ -611,6 +656,24 @@ }; name = Debug; }; + F00C7E0C784B90820717D614 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = WenzaWatch/WenzaWatch.entitlements; + CODE_SIGN_STYLE = Automatic; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = WenzaWatch/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Wenza; + PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.watchkitapp; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.9; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; F364512BEB70ECB7CB83FBFE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -619,7 +682,6 @@ CODE_SIGN_ENTITLEMENTS = KisaniCal/KisaniCal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = Wenza; @@ -631,14 +693,13 @@ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; - INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2; PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal; SDKROOT = iphoneos; SWIFT_VERSION = 5.9; @@ -658,6 +719,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + 6F608724A2EAA63171766586 /* Build configuration list for PBXNativeTarget "WenzaWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F00C7E0C784B90820717D614 /* Debug */, + D3A1329FCB6BBA1E8CC25FFC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; B60FDD2C88D1D33CF1EC02B3 /* Build configuration list for PBXNativeTarget "KisaniCal" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/KisaniCal.xcodeproj/xcshareddata/xcschemes/KisaniCal.xcscheme b/KisaniCal.xcodeproj/xcshareddata/xcschemes/KisaniCal.xcscheme index 675309e..9db359c 100644 --- a/KisaniCal.xcodeproj/xcshareddata/xcschemes/KisaniCal.xcscheme +++ b/KisaniCal.xcodeproj/xcshareddata/xcschemes/KisaniCal.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1500" + version = "1.7"> + buildImplicitDependencies = "YES" + runPostActionsOnFailure = "NO"> + shouldUseLaunchSchemeArgsEnv = "YES" + onlyGenerateCoverageForSpecifiedTargets = "NO"> + + + + + + diff --git a/KisaniCal/Managers/NotificationManager.swift b/KisaniCal/Managers/NotificationManager.swift index 3e11648..a4cf86c 100644 --- a/KisaniCal/Managers/NotificationManager.swift +++ b/KisaniCal/Managers/NotificationManager.swift @@ -136,7 +136,7 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable // MARK: - Period milestones ("one more week / month / year passed") - private static let periodIds = ["kisani.period.week", "kisani.period.month", "kisani.period.year"] + private static let periodIds = ["kisani.period.day", "kisani.period.week", "kisani.period.month", "kisani.period.year"] /// Recurring nudges at the close of each week, month, and year. Fixed identifiers /// + remove-then-add keep them de-duplicated across reschedules. Disabled (and @@ -155,17 +155,21 @@ final class NotificationManager: NSObject, ObservableObject, @unchecked Sendable trigger: UNCalendarNotificationTrigger(dateMatching: comps, repeats: true))) } - // End of week → Sunday 20:00 - var week = DateComponents(); week.weekday = 1; week.hour = 20; week.minute = 0 - add(Self.periodIds[0], "This Week", "One more week passed.", week) + // Every midnight → a new day began + var day = DateComponents(); day.hour = 0; day.minute = 0 + add(Self.periodIds[0], "Today", "One more day passed.", day) - // End of month → 1st of the next month, 09:00 - var month = DateComponents(); month.day = 1; month.hour = 9; month.minute = 0 - add(Self.periodIds[1], "This Month", "One more month passed.", month) + // Start of the week (Mon 00:00) → the week that just ended + var week = DateComponents(); week.weekday = 2; week.hour = 0; week.minute = 0 + add(Self.periodIds[1], "This Week", "One more week passed.", week) - // End of year → Jan 1, 09:00 - var year = DateComponents(); year.month = 1; year.day = 1; year.hour = 9; year.minute = 0 - add(Self.periodIds[2], "This Year", "One more year passed.", year) + // 1st of the month, 00:00 → the month that just ended + var month = DateComponents(); month.day = 1; month.hour = 0; month.minute = 0 + add(Self.periodIds[2], "This Month", "One more month passed.", month) + + // Jan 1, 00:00 → the year that just ended + var year = DateComponents(); year.month = 1; year.day = 1; year.hour = 0; year.minute = 0 + add(Self.periodIds[3], "This Year", "One more year passed.", year) } // MARK: - Recurring task completion confirmation diff --git a/WenzaWatch/Info.plist b/WenzaWatch/Info.plist new file mode 100644 index 0000000..1671de6 --- /dev/null +++ b/WenzaWatch/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Wenza + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + WKApplication + + WKCompanionAppBundleIdentifier + com.kutesir.KisaniCal + + diff --git a/WenzaWatch/WenzaWatch.entitlements b/WenzaWatch/WenzaWatch.entitlements new file mode 100644 index 0000000..db75750 --- /dev/null +++ b/WenzaWatch/WenzaWatch.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.ubiquity-kvstore-identifier + $(TeamIdentifierPrefix)com.kutesir.KisaniCal + + diff --git a/WenzaWatch/WenzaWatchApp.swift b/WenzaWatch/WenzaWatchApp.swift new file mode 100644 index 0000000..31c3dc9 --- /dev/null +++ b/WenzaWatch/WenzaWatchApp.swift @@ -0,0 +1,221 @@ +import SwiftUI +import WatchKit + +// MARK: - Shared iCloud task store +// +// The Watch is a separate device and can't read the iPhone's App Group, so it +// reads/writes the SAME tasks via the iCloud key-value store (NSUbiquitousKeyValueStore) +// that CloudSyncManager already mirrors on the phone. Writes here propagate back to +// the phone, which applies external KV changes into its App Group. + +private func tasksKey() -> String { + let uid = NSUbiquitousKeyValueStore.default.string(forKey: "kisani.activeUserId") ?? "shared" + return "kisani.tasks.v2.\(uid)" +} + +private let occFmt: DateFormatter = { + let f = DateFormatter(); f.dateFormat = "yyyy-MM-dd"; return f +}() + +struct WatchTask: Identifiable { + let id: UUID + var title: String + var dueDate: Date? + var isComplete: Bool + var isRecurring: Bool + var recurrenceLabel: String? + var completedOccurrences: [String] +} + +final class WatchStore: ObservableObject { + @Published var tasks: [WatchTask] = [] + private let kv = NSUbiquitousKeyValueStore.default + private let cal = Calendar.current + + init() { + NotificationCenter.default.addObserver( + self, selector: #selector(externalChange), + name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: kv) + kv.synchronize() + load() + } + + @objc private func externalChange() { + kv.synchronize() + DispatchQueue.main.async { [weak self] in self?.load() } + } + + func load() { + guard let data = kv.data(forKey: tasksKey()), + let arr = (try? JSONSerialization.jsonObject(with: data)) as? [[String: Any]] else { + tasks = []; return + } + tasks = arr.compactMap { d in + guard let idStr = d["id"] as? String, let id = UUID(uuidString: idStr), + let title = d["title"] as? String else { return nil } + let due = (d["dueDate"] as? Double).map { Date(timeIntervalSinceReferenceDate: $0) } + return WatchTask(id: id, title: title, dueDate: due, + isComplete: d["isComplete"] as? Bool ?? false, + isRecurring: d["isRecurring"] as? Bool ?? false, + recurrenceLabel: d["recurrenceLabel"] as? String, + completedOccurrences: d["completedOccurrences"] as? [String] ?? []) + } + } + + // Today's actionable tasks: dated today/overdue, plus recurring occurrences due today. + var todayTasks: [WatchTask] { + let end = cal.date(byAdding: .day, value: 1, to: cal.startOfDay(for: Date()))! + return tasks.filter { t in + if t.isRecurring { return occursToday(t) && !completedToday(t) } + guard !t.isComplete, let due = t.dueDate else { return false } + return due < end + } + .sorted { ($0.dueDate ?? .distantFuture) < ($1.dueDate ?? .distantFuture) } + } + + private func completedToday(_ t: WatchTask) -> Bool { + t.completedOccurrences.contains(occFmt.string(from: Date())) + } + + private func occursToday(_ t: WatchTask) -> Bool { + guard let due = t.dueDate else { return false } + let now = Date() + if due > cal.date(byAdding: .day, value: 1, to: cal.startOfDay(for: now))! { return false } + switch t.recurrenceLabel { + case "Daily": return true + case "Every Weekday": let wd = cal.component(.weekday, from: now); return wd >= 2 && wd <= 6 + case "Weekly": return cal.component(.weekday, from: due) == cal.component(.weekday, from: now) + case "Monthly": return cal.component(.day, from: due) == cal.component(.day, from: now) + case "Yearly": return cal.component(.month, from: due) == cal.component(.month, from: now) + && cal.component(.day, from: due) == cal.component(.day, from: now) + default: return false + } + } + + func toggle(_ task: WatchTask) { + mutate { arr in + for i in arr.indices where (arr[i]["id"] as? String) == task.id.uuidString { + if task.isRecurring { + var occ = arr[i]["completedOccurrences"] as? [String] ?? [] + let k = occFmt.string(from: Date()) + if let idx = occ.firstIndex(of: k) { occ.remove(at: idx) } else { occ.append(k) } + arr[i]["completedOccurrences"] = occ + } else { + let done = (arr[i]["isComplete"] as? Bool) ?? false + arr[i]["isComplete"] = !done + if !done { arr[i]["completedAt"] = Date().timeIntervalSinceReferenceDate } + else { arr[i].removeValue(forKey: "completedAt") } + } + } + } + } + + func add(_ title: String) { + let t = title.trimmingCharacters(in: .whitespaces) + guard !t.isEmpty else { return } + mutate { arr in + // All non-optional TaskItem keys must be present (synthesized Decodable + // throws on missing keys); raw values mirror the iOS model. + arr.append([ + "id": UUID().uuidString, + "title": t, + "dueDate": self.cal.startOfDay(for: Date()).timeIntervalSinceReferenceDate, + "hasTime": false, + "isComplete": false, + "quadrant": "Urgent + Important", + "category": "Reminder", + "taskColor": "orange", + "isRecurring": false, + "isPinned": false, + "isAllDay": false, + "priority": "None", + "constantReminder": false, + ]) + } + } + + private func mutate(_ change: (inout [[String: Any]]) -> Void) { + let key = tasksKey() + var arr: [[String: Any]] = [] + if let data = kv.data(forKey: key), + let parsed = (try? JSONSerialization.jsonObject(with: data)) as? [[String: Any]] { arr = parsed } + change(&arr) + if let out = try? JSONSerialization.data(withJSONObject: arr) { + kv.set(out, forKey: key) + kv.synchronize() + } + load() + } +} + +// MARK: - UI + +@main +struct WenzaWatchApp: App { + var body: some Scene { + WindowGroup { TaskListView() } + } +} + +struct TaskListView: View { + @StateObject private var store = WatchStore() + @State private var showAdd = false + + private let timeFmt: DateFormatter = { + let f = DateFormatter(); f.dateFormat = "h:mm a"; return f + }() + + var body: some View { + NavigationStack { + List { + Button { showAdd = true } label: { + Label("Add Task", systemImage: "plus.circle.fill") + .foregroundStyle(.orange) + } + + if store.todayTasks.isEmpty { + Text("All caught up") + .foregroundStyle(.secondary) + .font(.footnote) + } else { + ForEach(store.todayTasks) { task in + Button { withAnimation { WKInterfaceDevice.current().play(.success); store.toggle(task) } } label: { + HStack(spacing: 8) { + Image(systemName: "circle") + .foregroundStyle(.orange) + VStack(alignment: .leading, spacing: 1) { + Text(task.title).lineLimit(2) + if let due = task.dueDate, !task.isRecurring { + Text(timeFmt.string(from: due)) + .font(.caption2).foregroundStyle(.secondary) + } + } + } + } + } + } + } + .navigationTitle("Today") + .sheet(isPresented: $showAdd) { + AddTaskView { store.add($0); showAdd = false } + } + .onAppear { store.reload() } + } + } +} + +struct AddTaskView: View { + let onAdd: (String) -> Void + @Environment(\.dismiss) private var dismiss + @State private var text = "" + + var body: some View { + VStack(spacing: 10) { + TextField("New task", text: $text) // Watch dictation / Scribble + Button("Add") { onAdd(text) } + .disabled(text.trimmingCharacters(in: .whitespaces).isEmpty) + Button("Cancel", role: .cancel) { dismiss() } + } + .padding(.horizontal, 4) + } +} diff --git a/project.yml b/project.yml index 62e769c..91446f4 100644 --- a/project.yml +++ b/project.yml @@ -38,6 +38,11 @@ targets: dependencies: - target: KisaniCalWidgets embed: true + # WenzaWatch is built/embedded only when the watchOS platform is installed. + # Re-enable shipping it on the Watch by uncommenting the embed below (requires + # Xcode > Settings > Components > watchOS): + # - target: WenzaWatch + # embed: true settings: base: SWIFT_VERSION: 5.9 @@ -85,3 +90,27 @@ targets: LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks" APPLICATION_EXTENSION_API_ONLY: YES CODE_SIGN_ENTITLEMENTS: KisaniCalWidgets/KisaniCalWidgets.entitlements + + WenzaWatch: + type: application + platform: watchOS + deploymentTarget: "10.0" + sources: + - path: WenzaWatch + info: + path: WenzaWatch/Info.plist + properties: + CFBundleDisplayName: "Wenza" + CFBundleShortVersionString: "$(MARKETING_VERSION)" + CFBundleVersion: "$(CURRENT_PROJECT_VERSION)" + WKApplication: true + WKCompanionAppBundleIdentifier: com.kutesir.KisaniCal + settings: + base: + SWIFT_VERSION: 5.9 + GENERATE_INFOPLIST_FILE: NO + PRODUCT_BUNDLE_IDENTIFIER: com.kutesir.KisaniCal.watchkitapp + TARGETED_DEVICE_FAMILY: "4" + CODE_SIGN_STYLE: Automatic + CODE_SIGN_ENTITLEMENTS: WenzaWatch/WenzaWatch.entitlements + INFOPLIST_KEY_CFBundleDisplayName: "Wenza"