From 1ce3c60e60f2a1f8a124870aa420985f5c0656e8 Mon Sep 17 00:00:00 2001 From: Gigi <42325924+g-cqd@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:28:06 +0200 Subject: [PATCH] Upgrade to version 1.1.0 - Added Interactivity to widget - New Event summary complication --- Cami.xcodeproj/project.pbxproj | 72 ++++++- .../xcshareddata/swiftpm/Package.resolved | 7 +- Cami/CamiApp.swift | 1 + Cami/Info.plist | 13 +- Cami/View/ContentView.swift | 25 +-- Cami/View/OnboardingView.swift | 2 +- .../Settings/PermissionAccessSection.swift | 2 +- Cami/View/Settings/PermissionsView.swift | 2 +- CamiWidget/CamiWidgetEntry.swift | 2 +- CamiWidget/Info.plist | 11 + .../Intents/CamiWidgetConfiguration.swift | 6 +- CamiWidget/Intents/CamiWidgetIntent.swift | 8 +- .../CornerComplicationEnum.swift | 35 +++ .../Content/Events/CamiWidgetEvent.swift | 63 +++--- .../Events/CamiWidgetEventsByDate.swift | 7 +- .../Views/Header/CamiWidgetHeader.swift | 23 +- .../Header/CamiWidgetHeaderBirthdays.swift | 94 ++++---- .../CamiWidgetHeaderCornerComplication.swift | 29 +++ .../Header/CamiWidgetHeaderEventSummary.swift | 83 ++++++++ Localizable.xcstrings | 201 ++++++++++++++++++ Multiplatform/Extensions/Locale.swift | 16 ++ .../Helper/CamiHelper+Extensions.swift | 28 +++ Multiplatform/Helper/CamiHelper.swift | 1 + .../Helper/ContactHelper+Extensions.swift | 20 ++ .../Helper/EventHelper+Extensions.swift | 30 +++ Multiplatform/Helper/EventHelper.swift | 6 + Multiplatform/Modifiers/MiniBadge.swift | 1 + Multiplatform/Router.swift | 75 +++++++ Multiplatform/SettingsKeys.swift | 15 ++ Settings.bundle/en.lproj/Root.strings | Bin 598 -> 740 bytes 30 files changed, 756 insertions(+), 122 deletions(-) create mode 100644 CamiWidget/Intents/Intents Parameters/CornerComplicationEnum.swift create mode 100644 CamiWidget/Views/Header/CamiWidgetHeaderCornerComplication.swift create mode 100644 CamiWidget/Views/Header/CamiWidgetHeaderEventSummary.swift create mode 100644 Localizable.xcstrings create mode 100644 Multiplatform/Extensions/Locale.swift create mode 100644 Multiplatform/Helper/CamiHelper+Extensions.swift create mode 100644 Multiplatform/Helper/ContactHelper+Extensions.swift create mode 100644 Multiplatform/Helper/EventHelper+Extensions.swift create mode 100644 Multiplatform/Router.swift create mode 100644 Multiplatform/SettingsKeys.swift diff --git a/Cami.xcodeproj/project.pbxproj b/Cami.xcodeproj/project.pbxproj index 6e98d81..db227e6 100644 --- a/Cami.xcodeproj/project.pbxproj +++ b/Cami.xcodeproj/project.pbxproj @@ -29,6 +29,10 @@ 461D69112B0DF2B000A600D7 /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 461D69102B0DF2B000A600D7 /* EventView.swift */; }; 461D69132B0E1D6E00A600D7 /* Dates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 461D69122B0E1D6E00A600D7 /* Dates.swift */; }; 461D69162B0E22A700A600D7 /* Weeks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 461D69152B0E22A700A600D7 /* Weeks.swift */; }; + 461DC4482C92722B00EAFFAC /* Locale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 461DC4472C92722B00EAFFAC /* Locale.swift */; }; + 461DC4492C92722B00EAFFAC /* Locale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 461DC4472C92722B00EAFFAC /* Locale.swift */; }; + 461DC44B2C93157700EAFFAC /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 461DC44A2C93157700EAFFAC /* Localizable.xcstrings */; }; + 461DC44C2C93157700EAFFAC /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 461DC44A2C93157700EAFFAC /* Localizable.xcstrings */; }; 46225B012B58970900765185 /* EditEventViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46225B002B58970900765185 /* EditEventViewController.swift */; }; 46225B032B589CDE00765185 /* EventViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46225B022B589CDE00765185 /* EventViewController.swift */; }; 46498EC22B76C19C00FC480C /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46498EC12B76C19C00FC480C /* OnboardingView.swift */; }; @@ -69,6 +73,18 @@ 469E059C2B0956B500F3263D /* EventDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469E059B2B0956B500F3263D /* EventDict.swift */; }; 469E059D2B0956B500F3263D /* EventDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469E059B2B0956B500F3263D /* EventDict.swift */; }; 46A4F6892B0BB69200C0A0DC /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A4F6882B0BB69200C0A0DC /* ViewModel.swift */; }; + 46AB12552C90F31500FBD578 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB12542C90F31500FBD578 /* Router.swift */; }; + 46AB125B2C910BD300FBD578 /* EventHelper+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB125A2C910BD300FBD578 /* EventHelper+Extensions.swift */; }; + 46AB125D2C911D6600FBD578 /* CamiHelper+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB125C2C911D6600FBD578 /* CamiHelper+Extensions.swift */; }; + 46AB125F2C911E3800FBD578 /* SettingsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB125E2C911E3800FBD578 /* SettingsKeys.swift */; }; + 46AB12602C9122BC00FBD578 /* CamiHelper+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB125C2C911D6600FBD578 /* CamiHelper+Extensions.swift */; }; + 46AB12612C91230800FBD578 /* SettingsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB125E2C911E3800FBD578 /* SettingsKeys.swift */; }; + 46AB12632C91263D00FBD578 /* ContactHelper+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB12622C91263D00FBD578 /* ContactHelper+Extensions.swift */; }; + 46AB12642C91264100FBD578 /* ContactHelper+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB12622C91263D00FBD578 /* ContactHelper+Extensions.swift */; }; + 46AB12662C9134E000FBD578 /* CornerComplicationEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB12652C9134E000FBD578 /* CornerComplicationEnum.swift */; }; + 46AB12672C91358000FBD578 /* CornerComplicationEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB12652C9134E000FBD578 /* CornerComplicationEnum.swift */; }; + 46AB126A2C9136EC00FBD578 /* CamiWidgetHeaderCornerComplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB12682C91364A00FBD578 /* CamiWidgetHeaderCornerComplication.swift */; }; + 46AB126C2C9137A600FBD578 /* CamiWidgetHeaderEventSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AB126B2C9137A600FBD578 /* CamiWidgetHeaderEventSummary.swift */; }; 46B1606B2B07D87100A44FBF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 46B1606A2B07D87100A44FBF /* Assets.xcassets */; }; 46B160702B07D87100A44FBF /* CamiWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 46B160622B07D87000A44FBF /* CamiWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 46B160802B07D90500A44FBF /* Rounded.swift in Sources */ = {isa = PBXBuildFile; fileRef = 460CAB112B0650370059B2F7 /* Rounded.swift */; }; @@ -167,6 +183,8 @@ 461D69102B0DF2B000A600D7 /* EventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventView.swift; sourceTree = ""; }; 461D69122B0E1D6E00A600D7 /* Dates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dates.swift; sourceTree = ""; }; 461D69152B0E22A700A600D7 /* Weeks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weeks.swift; sourceTree = ""; }; + 461DC4472C92722B00EAFFAC /* Locale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locale.swift; sourceTree = ""; }; + 461DC44A2C93157700EAFFAC /* Localizable.xcstrings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; 46225B002B58970900765185 /* EditEventViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventViewController.swift; sourceTree = ""; }; 46225B022B589CDE00765185 /* EventViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewController.swift; sourceTree = ""; }; 462861FE2AF7E616004FDC9E /* CamiWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CamiWidgetView.swift; sourceTree = ""; }; @@ -211,6 +229,14 @@ 469E05902B08F7DA00F3263D /* CamiWidgetIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CamiWidgetIntent.swift; sourceTree = ""; }; 469E059B2B0956B500F3263D /* EventDict.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDict.swift; sourceTree = ""; }; 46A4F6882B0BB69200C0A0DC /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; + 46AB12542C90F31500FBD578 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; + 46AB125A2C910BD300FBD578 /* EventHelper+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventHelper+Extensions.swift"; sourceTree = ""; }; + 46AB125C2C911D6600FBD578 /* CamiHelper+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CamiHelper+Extensions.swift"; sourceTree = ""; }; + 46AB125E2C911E3800FBD578 /* SettingsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeys.swift; sourceTree = ""; }; + 46AB12622C91263D00FBD578 /* ContactHelper+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactHelper+Extensions.swift"; sourceTree = ""; }; + 46AB12652C9134E000FBD578 /* CornerComplicationEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerComplicationEnum.swift; sourceTree = ""; }; + 46AB12682C91364A00FBD578 /* CamiWidgetHeaderCornerComplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CamiWidgetHeaderCornerComplication.swift; sourceTree = ""; }; + 46AB126B2C9137A600FBD578 /* CamiWidgetHeaderEventSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CamiWidgetHeaderEventSummary.swift; sourceTree = ""; }; 46B160622B07D87000A44FBF /* CamiWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = CamiWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 46B1606A2B07D87100A44FBF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46B1606C2B07D87100A44FBF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -263,6 +289,8 @@ 46CC7E192AF7959200607AAF /* Extensions */, 46CC7E202AF795F600607AAF /* Helper */, 46968DBE2B06022600EA1C8D /* Modifiers */, + 46AB12542C90F31500FBD578 /* Router.swift */, + 46AB125E2C911E3800FBD578 /* SettingsKeys.swift */, ); path = Multiplatform; sourceTree = ""; @@ -364,6 +392,7 @@ children = ( 469E05892B086C3100F3263D /* WidgetCalendarEntity.swift */, 469E057F2B08172700F3263D /* AllDayStyleEnum.swift */, + 46AB12652C9134E000FBD578 /* CornerComplicationEnum.swift */, ); path = "Intents Parameters"; sourceTree = ""; @@ -395,6 +424,8 @@ 4675FC6B2B04B4710050F0FC /* CamiWidgetHeader.swift */, 4675FC682B04B4460050F0FC /* CamiWidgetHeaderDate.swift */, 4675FC622B0379800050F0FC /* CamiWidgetHeaderBirthdays.swift */, + 46AB12682C91364A00FBD578 /* CamiWidgetHeaderCornerComplication.swift */, + 46AB126B2C9137A600FBD578 /* CamiWidgetHeaderEventSummary.swift */, ); path = Header; sourceTree = ""; @@ -451,6 +482,7 @@ 46968DC22B0613FC00EA1C8D /* LiveActivity */, 46B1606A2B07D87100A44FBF /* Assets.xcassets */, 46B1606C2B07D87100A44FBF /* Info.plist */, + 461DC44A2C93157700EAFFAC /* Localizable.xcstrings */, 46B1606D2B07D87100A44FBF /* CamiWidget.entitlements */, ); path = CamiWidget; @@ -472,6 +504,7 @@ 460CAB1A2B065F630059B2F7 /* WidgetFamily.swift */, 461D69152B0E22A700A600D7 /* Weeks.swift */, 4611A4BD2B7A5287007D85ED /* OSLog.swift */, + 461DC4472C92722B00EAFFAC /* Locale.swift */, ); path = Extensions; sourceTree = ""; @@ -482,6 +515,9 @@ 46968DB72B0518AB00EA1C8D /* CamiHelper.swift */, 46CC7E1D2AF795EF00607AAF /* EventHelper.swift */, 467F3DCE2B04C76400F10899 /* ContactHelper.swift */, + 46AB125A2C910BD300FBD578 /* EventHelper+Extensions.swift */, + 46AB125C2C911D6600FBD578 /* CamiHelper+Extensions.swift */, + 46AB12622C91263D00FBD578 /* ContactHelper+Extensions.swift */, ); path = Helper; sourceTree = ""; @@ -610,6 +646,7 @@ buildActionMask = 2147483647; files = ( 461649352B09A89A00F7E7E1 /* PrivacyInfo.xcprivacy in Resources */, + 461DC44B2C93157700EAFFAC /* Localizable.xcstrings in Resources */, 465159302B7252AB0073544A /* Settings.bundle in Resources */, 465533112AF4EA6A000B537B /* Assets.xcassets in Resources */, ); @@ -621,6 +658,7 @@ files = ( 461649362B09A89A00F7E7E1 /* PrivacyInfo.xcprivacy in Resources */, 46B1606B2B07D87100A44FBF /* Assets.xcassets in Resources */, + 461DC44C2C93157700EAFFAC /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -670,6 +708,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 46AB12632C91263D00FBD578 /* ContactHelper+Extensions.swift in Sources */, 469E05932B0919A000F3263D /* CamiWidgetIntent.swift in Sources */, 467F3DCF2B04C76400F10899 /* ContactHelper.swift in Sources */, 4673449E2B7907F400470979 /* PermissionStatus.swift in Sources */, @@ -679,6 +718,7 @@ 46225B032B589CDE00765185 /* EventViewController.swift in Sources */, 46CC7E172AF7946700607AAF /* Date.swift in Sources */, 4616493A2B09B57500F7E7E1 /* MonthWeekRow.swift in Sources */, + 46AB125D2C911D6600FBD578 /* CamiHelper+Extensions.swift in Sources */, 46F331422B7812D70060FA8B /* Notifications.swift in Sources */, 460CAB122B0650370059B2F7 /* Rounded.swift in Sources */, 460CAB1E2B06602E0059B2F7 /* Pad.swift in Sources */, @@ -687,9 +727,12 @@ 461649382B09B54A00F7E7E1 /* MonthView.swift in Sources */, 460CAB1B2B065F630059B2F7 /* WidgetFamily.swift in Sources */, 46A4F6892B0BB69200C0A0DC /* ViewModel.swift in Sources */, + 46AB12552C90F31500FBD578 /* Router.swift in Sources */, + 461DC4482C92722B00EAFFAC /* Locale.swift in Sources */, 46CC7E142AF7944800607AAF /* EKEvent.swift in Sources */, 465159282B712DAC0073544A /* WebView.swift in Sources */, 46968DC42B06148500EA1C8D /* Calendars.swift in Sources */, + 46AB12662C9134E000FBD578 /* CornerComplicationEnum.swift in Sources */, 461D69132B0E1D6E00A600D7 /* Dates.swift in Sources */, 461D69162B0E22A700A600D7 /* Weeks.swift in Sources */, 469853142B0C25ED00A1F35B /* CalendarSelectionView.swift in Sources */, @@ -700,6 +743,7 @@ 46CC7E1E2AF795EF00607AAF /* EventHelper.swift in Sources */, 46225B012B58970900765185 /* EditEventViewController.swift in Sources */, 46E58C752B75A4E7003B48A0 /* FAQInformationModel.swift in Sources */, + 46AB125B2C910BD300FBD578 /* EventHelper+Extensions.swift in Sources */, 469E058A2B086C3100F3263D /* WidgetCalendarEntity.swift in Sources */, 4673449C2B79071600470979 /* PermissionAccessSection.swift in Sources */, 46E58C712B75A468003B48A0 /* InformationModalView.swift in Sources */, @@ -728,6 +772,7 @@ 46152C802B12463800EF7F39 /* Generics.swift in Sources */, 467F3DCB2B04C49200F10899 /* PermissionsView.swift in Sources */, 469853122B0C19AF00A1F35B /* DayView.swift in Sources */, + 46AB125F2C911E3800FBD578 /* SettingsKeys.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -740,8 +785,10 @@ 46B160952B07D91000A44FBF /* TypeAliases.swift in Sources */, 46B160A72B07D9A100A44FBF /* CamiWidgetView.swift in Sources */, 46B1609C2B07D9A100A44FBF /* CamiWidgetBundle.swift in Sources */, + 461DC4492C92722B00EAFFAC /* Locale.swift in Sources */, 46B160A12B07D9A100A44FBF /* CamiWidgetEventsByDate.swift in Sources */, 469E05842B0848AE00F3263D /* Seconds.swift in Sources */, + 46AB126C2C9137A600FBD578 /* CamiWidgetHeaderEventSummary.swift in Sources */, 46F331442B781B170060FA8B /* PermissionModel.swift in Sources */, 46B160892B07D90A00A44FBF /* Events.swift in Sources */, 46B160A62B07D9A100A44FBF /* CamiWidgetEvents.swift in Sources */, @@ -755,19 +802,24 @@ 46B160882B07D90A00A44FBF /* WidgetFamily.swift in Sources */, 46B160A22B07D9A100A44FBF /* CamiWidgetHeaderDate.swift in Sources */, 4619D6E02B0CDFD10046A7B5 /* DateComponents.swift in Sources */, + 46AB12672C91358000FBD578 /* CornerComplicationEnum.swift in Sources */, 46B1609E2B07D9A100A44FBF /* CamiWidgetHeader.swift in Sources */, 469E05642B07DDA600F3263D /* CamiWidget.swift in Sources */, 46B1609D2B07D9A100A44FBF /* CamiWidgetEvent.swift in Sources */, 469E058F2B08F57600F3263D /* CamiWidgetConfiguration.swift in Sources */, + 46AB12602C9122BC00FBD578 /* CamiHelper+Extensions.swift in Sources */, 46B160802B07D90500A44FBF /* Rounded.swift in Sources */, 469E058B2B086C3100F3263D /* WidgetCalendarEntity.swift in Sources */, 46B1608C2B07D90A00A44FBF /* EdgeInsets.swift in Sources */, 46B1608F2B07D90A00A44FBF /* Calendars.swift in Sources */, + 46AB12642C91264100FBD578 /* ContactHelper+Extensions.swift in Sources */, 46B160832B07D90500A44FBF /* Pad.swift in Sources */, 469E059D2B0956B500F3263D /* EventDict.swift in Sources */, + 46AB12612C91230800FBD578 /* SettingsKeys.swift in Sources */, 4673449F2B7907FE00470979 /* PermissionStatus.swift in Sources */, 46B160842B07D90500A44FBF /* MiniBadge.swift in Sources */, 46B1608B2B07D90A00A44FBF /* Bool.swift in Sources */, + 46AB126A2C9136EC00FBD578 /* CamiWidgetHeaderCornerComplication.swift in Sources */, 46B160872B07D90A00A44FBF /* RectangleCornerRadii.swift in Sources */, 469E05812B08172700F3263D /* AllDayStyleEnum.swift in Sources */, 46B1609F2B07D9A100A44FBF /* CamiWidgetProvider.swift in Sources */, @@ -1070,7 +1122,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_HEADERS_RUN_UNIFDEF = YES; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = NP94WB3P75; @@ -1083,7 +1135,7 @@ INFOPLIST_KEY_CLKComplicationPrincipalClass = ""; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_NSCalendarsUsageDescription = "Cami ONLY uses your on-device calendar information to display events in widgets.\nCami DOES NOT edit, delete, save or send those information away."; - INFOPLIST_KEY_NSContactsUsageDescription = "Cami ONLY uses your on-device contacts information to display birthdays information in widgets.\nCami DOES NOT edit, delete, save or send those information away."; + INFOPLIST_KEY_NSContactsUsageDescription = "Cami ONLY uses your on-device contact information to display birthdays in widgets."; INFOPLIST_KEY_NSHumanReadableCopyright = "© 2024 Aemi Studio. All Rights Reserved."; INFOPLIST_KEY_NSRemindersUsageDescription = "Cami ONLY uses your on-device reminders information to display them in widgets and the application.\nCami DOES NOT edit, delete, save or send those information away."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; @@ -1101,7 +1153,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = studio.aemi.ada23.Cami; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1130,7 +1182,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_HEADERS_RUN_UNIFDEF = YES; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = NP94WB3P75; @@ -1143,7 +1195,7 @@ INFOPLIST_KEY_CLKComplicationPrincipalClass = ""; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_NSCalendarsUsageDescription = "Cami ONLY uses your on-device calendar information to display events in widgets.\nCami DOES NOT edit, delete, save or send those information away."; - INFOPLIST_KEY_NSContactsUsageDescription = "Cami ONLY uses your on-device contacts information to display birthdays information in widgets.\nCami DOES NOT edit, delete, save or send those information away."; + INFOPLIST_KEY_NSContactsUsageDescription = "Cami ONLY uses your on-device contact information to display birthdays in widgets."; INFOPLIST_KEY_NSHumanReadableCopyright = "© 2024 Aemi Studio. All Rights Reserved."; INFOPLIST_KEY_NSRemindersUsageDescription = "Cami ONLY uses your on-device reminders information to display them in widgets and the application.\nCami DOES NOT edit, delete, save or send those information away."; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; @@ -1161,7 +1213,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = studio.aemi.ada23.Cami; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1185,7 +1237,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = CamiWidgetExtension.entitlements; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = NP94WB3P75; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -1211,7 +1263,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = studio.aemi.ada23.Cami.CamiWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -1234,7 +1286,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = CamiWidgetExtension.entitlements; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = NP94WB3P75; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -1260,7 +1312,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = studio.aemi.ada23.Cami.CamiWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; diff --git a/Cami.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Cami.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1a0f425..1e68baf 100644 --- a/Cami.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Cami.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,14 +1,15 @@ { + "originHash" : "51f90653b2c9f9f7064c0d52159b40bf7d222e5f314be23e62fe28520fec03db", "pins" : [ { "identity" : "swift-collections", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", - "version" : "1.1.0" + "revision" : "9bf03ff58ce34478e66aaee630e491823326fd06", + "version" : "1.1.3" } } ], - "version" : 2 + "version" : 3 } diff --git a/Cami/CamiApp.swift b/Cami/CamiApp.swift index 412ad6e..3820c5a 100644 --- a/Cami/CamiApp.swift +++ b/Cami/CamiApp.swift @@ -19,6 +19,7 @@ struct CamiApp: App { var body: some Scene { WindowGroup { ContentView() + .onOpenURL(perform: Router.shared.handleURL) .environment(model) .environment(perms) } diff --git a/Cami/Info.plist b/Cami/Info.plist index 2c9fc69..36bb290 100644 --- a/Cami/Info.plist +++ b/Cami/Info.plist @@ -9,8 +9,6 @@ NSCalendarsFullAccessUsageDescription Cami ONLY uses your on-device calendar information to display events in widgets. Cami DOES NOT edit or delete or send those information away. - NSContactsUsageDescription - Cami ONLY uses your on-device contact information to display birthdays in widgets. NSRemindersFullAccessUsageDescription Cami ONLY uses your on-device reminders information to display them in widgets and the application. Cami DOES NOT edit or delete or send those information away. @@ -20,5 +18,16 @@ Cami DOES NOT edit or delete or send those information away. com.apple.security.personal-information.calendars + CFBundleURLTypes + + + CFBundleURLName + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleURLSchemes + + camical + + + diff --git a/Cami/View/ContentView.swift b/Cami/View/ContentView.swift index d6a73f7..c73d26a 100644 --- a/Cami/View/ContentView.swift +++ b/Cami/View/ContentView.swift @@ -36,7 +36,7 @@ struct ContentView: View { PermissionModel.shared.global == .restricted } - @AppStorage("accessWorkInProgressFeatures") + @AppStorage(SettingsKeys.accessWorkInProgressFeatures) private var accessWorkInProgressFeatures: Bool = false var body: some View { @@ -44,22 +44,23 @@ struct ContentView: View { @Bindable var perms = perms @Bindable var model = model - Group { - if accessWorkInProgressFeatures { - NavigationStack(path: $model.path) { + NavigationStack(path: $model.path) { + + Group { + if accessWorkInProgressFeatures { CalendarView( areSettingsPresented: $areSettingsPresented ) - .navigationDestination(for: Day.self, destination: DayView.init) - .navigationDestination(for: EKEvent.self, destination: EventView.init) + } else { + OnboardingView( + areSettingsPresented: $areSettingsPresented, + areInformationsPresented: $areInformationsPresented + ) + .frame(maxWidth: 720) } - } else { - OnboardingView( - areSettingsPresented: $areSettingsPresented, - areInformationsPresented: $areInformationsPresented - ) - .frame(maxWidth: 720) } + .navigationDestination(for: Day.self, destination: DayView.init) + .navigationDestination(for: EKEvent.self, destination: EventView.init) } .onChange(of: scenePhase) { _, _ in WidgetCenter.shared.reloadAllTimelines() diff --git a/Cami/View/OnboardingView.swift b/Cami/View/OnboardingView.swift index ae7f4a5..8c2fe1a 100644 --- a/Cami/View/OnboardingView.swift +++ b/Cami/View/OnboardingView.swift @@ -22,7 +22,7 @@ struct OnboardingView: View { @Binding var areInformationsPresented: Bool - @AppStorage("accessWorkInProgressFeatures") + @AppStorage(SettingsKeys.accessWorkInProgressFeatures) private var accessWorkInProgressFeatures = false private var authorized: Bool { diff --git a/Cami/View/Settings/PermissionAccessSection.swift b/Cami/View/Settings/PermissionAccessSection.swift index 4c9b4f3..a460057 100644 --- a/Cami/View/Settings/PermissionAccessSection.swift +++ b/Cami/View/Settings/PermissionAccessSection.swift @@ -17,7 +17,7 @@ struct PermissionAccessSection: View { var restrictedDescription: String var radius: Double = 12 - @AppStorage("accessWorkInProgressFeatures") + @AppStorage(SettingsKeys.accessWorkInProgressFeatures) private var accessWorkInProgressFeatures: Bool = false private var consideringReminders: Bool { diff --git a/Cami/View/Settings/PermissionsView.swift b/Cami/View/Settings/PermissionsView.swift index 0c20649..724ad47 100644 --- a/Cami/View/Settings/PermissionsView.swift +++ b/Cami/View/Settings/PermissionsView.swift @@ -24,7 +24,7 @@ struct PermissionsView: View { @State private var conInfo: Bool = false - @AppStorage("accessWorkInProgressFeatures") + @AppStorage(SettingsKeys.accessWorkInProgressFeatures) private var accessWorkInProgressFeatures = false var body: some View { diff --git a/CamiWidget/CamiWidgetEntry.swift b/CamiWidget/CamiWidgetEntry.swift index 093bee2..9d7d2ad 100644 --- a/CamiWidget/CamiWidgetEntry.swift +++ b/CamiWidget/CamiWidgetEntry.swift @@ -44,7 +44,7 @@ final class CamiWidgetEntry: TimelineEntry { inlineCalendars: (intent.inlineCalendars.map { $0.calendar }).asEKCalendars(), events: CamiHelper.events(from: intent.calendars, relativeTo: date), inlineEvents: CamiHelper.events(from: intent.inlineCalendars, where: { $0.isAllDay }, relativeTo: date), - birthdays: intent.displayBirthdays + birthdays: intent.cornerComplication == .birthdays ? CamiHelper.birthdays(from: date) : [] ) diff --git a/CamiWidget/Info.plist b/CamiWidget/Info.plist index b55da12..fd86ce7 100644 --- a/CamiWidget/Info.plist +++ b/CamiWidget/Info.plist @@ -2,6 +2,17 @@ + CFBundleURLTypes + + + CFBundleURLName + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleURLSchemes + + camical + + + NSCalendarsFullAccessUsageDescription Cami ONLY uses your on-device calendar information to display events in widgets. Cami DOES NOT edit or delete or send those information away. diff --git a/CamiWidget/Intents/CamiWidgetConfiguration.swift b/CamiWidget/Intents/CamiWidgetConfiguration.swift index 5c42b3b..9db35b9 100644 --- a/CamiWidget/Intents/CamiWidgetConfiguration.swift +++ b/CamiWidget/Intents/CamiWidgetConfiguration.swift @@ -10,20 +10,20 @@ import Foundation final class CamiWidgetConfiguration { let allDayStyle: AllDayStyleEnum - let displayBirthdays: Bool + let cornerComplication: CornerComplicationEnum let displayOngoingEvents: Bool let groupEvents: Bool init() { self.allDayStyle = .event - self.displayBirthdays = true + self.cornerComplication = .birthdays self.displayOngoingEvents = true self.groupEvents = true } init(from intent: CamiWidgetIntent) { self.allDayStyle = intent.allDayStyle - self.displayBirthdays = intent.displayBirthdays + self.cornerComplication = intent.cornerComplication self.displayOngoingEvents = intent.displayOngoingEvents self.groupEvents = intent.groupEvents } diff --git a/CamiWidget/Intents/CamiWidgetIntent.swift b/CamiWidget/Intents/CamiWidgetIntent.swift index b0e3b7b..95c7a00 100644 --- a/CamiWidget/Intents/CamiWidgetIntent.swift +++ b/CamiWidget/Intents/CamiWidgetIntent.swift @@ -29,8 +29,12 @@ struct CamiWidgetIntent: WidgetConfigurationIntent { @Parameter(title: "Group Similar Events", default: true) var groupEvents: Bool - @Parameter(title: "Display Birthdays", default: true) - var displayBirthdays: Bool + @Parameter( + title: "Corner Complication", + default: .birthdays, + optionsProvider: CornerComplicationOptionsProvider() + ) + var cornerComplication: CornerComplicationEnum @Parameter(title: "Display Ongoing Events", default: true) var displayOngoingEvents: Bool diff --git a/CamiWidget/Intents/Intents Parameters/CornerComplicationEnum.swift b/CamiWidget/Intents/Intents Parameters/CornerComplicationEnum.swift new file mode 100644 index 0000000..08c56de --- /dev/null +++ b/CamiWidget/Intents/Intents Parameters/CornerComplicationEnum.swift @@ -0,0 +1,35 @@ +// +// CornerComplicationEnum.swift +// Cami +// +// Created by Guillaume Coquard on 11/09/24. +// + +import Foundation +import AppIntents + +enum CornerComplicationEnum: String, CaseIterable, AppEnum { + typealias RawValue = String + + case hidden = "Hidden" + case birthdays = "Birthdays" + case summary = "Summary" + + static var typeDisplayRepresentation: TypeDisplayRepresentation = .init(stringLiteral: "Style") + + static var caseDisplayRepresentations: [CornerComplicationEnum: DisplayRepresentation] = [ + .hidden: .init(stringLiteral: "Hidden"), + .birthdays: .init(stringLiteral: "Birthdays"), + .summary: .init(stringLiteral: "Summary") + ] + + var title: String { + self.rawValue + } +} + +struct CornerComplicationOptionsProvider: DynamicOptionsProvider { + func results() async throws -> [CornerComplicationEnum] { + CornerComplicationEnum.allCases + } +} diff --git a/CamiWidget/Views/Content/Events/CamiWidgetEvent.swift b/CamiWidget/Views/Content/Events/CamiWidgetEvent.swift index c6c4b40..d84710d 100644 --- a/CamiWidget/Views/Content/Events/CamiWidgetEvent.swift +++ b/CamiWidget/Views/Content/Events/CamiWidgetEvent.swift @@ -13,6 +13,9 @@ struct CamiWidgetEvent: View { @Environment(CamiWidgetEntry.self) private var entry: CamiWidgetEntry + @AppStorage(SettingsKeys.openInCami) + private var openInPlace: Bool = UserDefaults.standard.bool(forKey: SettingsKeys.openInCami) + var event: (EKEvent, Events) private var _event: EKEvent { @@ -25,42 +28,44 @@ struct CamiWidgetEvent: View { var body: some View { - HStack(alignment: .center) { + Link(destination: CamiHelper.destination(for: _event, inPlace: openInPlace)) { + HStack(alignment: .center) { - VStack(alignment: .leading) { - Text(_event.title) - .font(.caption) - .fontDesign(.rounded) - .fontWeight(.medium) - .lineLimit(1) - .foregroundStyle(Color(cgColor: _event.calendar.cgColor)) - .accessibilityLabel("You have an event titled: \(_event.title).") - } + VStack(alignment: .leading) { + Text(_event.title) + .font(.caption) + .fontDesign(.rounded) + .fontWeight(.medium) + .lineLimit(1) + .foregroundStyle(Color(cgColor: _event.calendar.cgColor)) + .accessibilityLabel("You have an event titled: \(_event.title).") + } - Spacer(minLength: 8) + Spacer(minLength: 8) - if !_event.isAllDay || entry.config.displayOngoingEvents && _event.spansMore(than: entry.date) { - VStack(alignment: .trailing, spacing: 1) { - Group { - ForEach(_other, id: \.self) { otherEvent in - RemainingTimeComponent( - from: otherEvent.startDate, - to: otherEvent.endDate, - accuracy: otherEvent.spansMore(than: entry.date) - ? [.day, .hour] - : [.day, .hour, .minute] - ) - .font(.caption) - .foregroundStyle(Color(cgColor: otherEvent.calendar.cgColor)) + if !_event.isAllDay || entry.config.displayOngoingEvents && _event.spansMore(than: entry.date) { + VStack(alignment: .trailing, spacing: 1) { + Group { + ForEach(_other, id: \.self) { otherEvent in + RemainingTimeComponent( + from: otherEvent.startDate, + to: otherEvent.endDate, + accuracy: otherEvent.spansMore(than: entry.date) + ? [.day, .hour] + : [.day, .hour, .minute] + ) + .font(.caption) + .foregroundStyle(Color(cgColor: otherEvent.calendar.cgColor)) + } } + .accessibilityLabel( + _other.count > 1 + ? "This event happens \(_other.count) times in your day." : "") } - .accessibilityLabel( - _other.count > 1 - ? "This event happens \(_other.count) times in your day." : "") } - } + } + .roundedBorder( _event.calendar.cgColor, bordered: entry.config.allDayStyle == .bordered && _event.isAllDay ) } - .roundedBorder( _event.calendar.cgColor, bordered: entry.config.allDayStyle == .bordered && _event.isAllDay ) } } diff --git a/CamiWidget/Views/Content/Events/CamiWidgetEventsByDate.swift b/CamiWidget/Views/Content/Events/CamiWidgetEventsByDate.swift index 7d4f49f..9392bc3 100644 --- a/CamiWidget/Views/Content/Events/CamiWidgetEventsByDate.swift +++ b/CamiWidget/Views/Content/Events/CamiWidgetEventsByDate.swift @@ -59,7 +59,9 @@ struct CamiWidgetEventsByDate: View { HStack { Group { if ongoingEvents { - Text("Ongoing Events") + Text( + NSLocalizedString("ongoingEvents.Title", comment: "") + ) } else if isUpToTomorrow { Text(date.formattedUntilTomorrow) .accessibilityLabel( @@ -67,7 +69,7 @@ struct CamiWidgetEventsByDate: View { ) } else { Group { - Text(date.formattedAfterTomorrow) + + Text(date.formattedAfterTomorrow.capitalized(with: .prefered)) + Text(" • ") + Text(date.relativeToNow) } @@ -80,6 +82,7 @@ struct CamiWidgetEventsByDate: View { .fontWeight(.medium) .foregroundStyle(.white.opacity(0.25)) .lineLimit(1) + Spacer() if !ongoingEvents && inlineEvents.count > 0 { HStack { diff --git a/CamiWidget/Views/Header/CamiWidgetHeader.swift b/CamiWidget/Views/Header/CamiWidgetHeader.swift index 52fb78d..a5390e1 100644 --- a/CamiWidget/Views/Header/CamiWidgetHeader.swift +++ b/CamiWidget/Views/Header/CamiWidgetHeader.swift @@ -13,26 +13,29 @@ struct CamiWidgetHeader: View { @Environment(CamiWidgetEntry.self) private var entry: CamiWidgetEntry + @AppStorage(SettingsKeys.openInCami) + private var openInPlace: Bool = UserDefaults.standard.bool(forKey: SettingsKeys.openInCami) + var body: some View { @Bindable var entry = entry HStack(spacing: 0) { - CamiWidgetHeaderDate() - .accessibilityLabel("Today's date is " + entry.date.formatter { - $0.dateStyle = .full - $0.timeStyle = .none - $0.formattingContext = .standalone - }) + Link(destination: CamiHelper.destination(for: entry.date, inPlace: openInPlace)) { + CamiWidgetHeaderDate() + .accessibilityLabel("Today's date is " + entry.date.formatter { + $0.dateStyle = .full + $0.timeStyle = .none + $0.formattingContext = .standalone + }) + } Spacer() - if entry.config.displayBirthdays && !entry.birthdays.isEmpty { - CamiWidgetHeaderBirthdays() - } + CamiWidgetHeaderCornerComplication() } - .padding(.init(top: 4, leading: 8, bottom: 4, trailing: 4)) + .padding(.init(top: 4, leading: 8, bottom: 4, trailing: 5)) .background(.black.opacity(0.2)) .rounded([ .all: .init( top: 16, bottom: 8 ) ]) .lineLimit(1) diff --git a/CamiWidget/Views/Header/CamiWidgetHeaderBirthdays.swift b/CamiWidget/Views/Header/CamiWidgetHeaderBirthdays.swift index 4cfc587..ec7f4bb 100644 --- a/CamiWidget/Views/Header/CamiWidgetHeaderBirthdays.swift +++ b/CamiWidget/Views/Header/CamiWidgetHeaderBirthdays.swift @@ -64,64 +64,68 @@ struct CamiWidgetHeaderBirthdays: View { } var body: some View { - if nextBirthdays.1.count > 0 { - HStack(alignment: .center, spacing: 4) { - if todayBirthdayEvent != nil { + if !entry.birthdays.isEmpty && nextBirthdays.1.count > 0 { + Link(destination: CamiHelper.destination(for: birthdays.first!)) { + HStack(alignment: .center, spacing: 4) { + if todayBirthdayEvent != nil { - let name = ContactHelper.resolveContactName( - todayBirthdayEvent!.birthdayContactIdentifier! - ) + let name = ContactHelper.resolveContactName( + todayBirthdayEvent!.birthdayContactIdentifier! + ) - let age = ContactHelper.resolveBirthdate( - todayBirthdayEvent!.birthdayContactIdentifier! - )!.yearsAgo + let age = ContactHelper.resolveBirthdate( + todayBirthdayEvent!.birthdayContactIdentifier! + )!.yearsAgo - Group { - HStack(spacing: 0) { + Group { + HStack(spacing: 0) { - Text("\(age)") - .hiddenIf(isSmall) + Text("\(age)") + .hiddenIf(isSmall) - Label("\(age) years old", systemImage: "birthday.cake.fill") - .labelStyle(.iconOnly) - .font(.caption2) - .scaleEffect(0.8) - .lineSpacing(0) - } - .miniBadge(color: bCalColor) - - Text("\(name)") - .hiddenIf(isSmall) - } - .accessibilityLabel( - "It is \(name)'s birthday today. \(name) is now \(age) years old" - ) - - } else { - let daysToGo: String = nextBirthdays.0.toDays - if nextBirthdays.1.count > 1 { - let birthdaysCount = nextBirthdays.1.count - Group { - Text(daysToGo) - .miniBadge(color: bCalColor) + Label("\(age) years old", systemImage: "birthday.cake.fill") + .labelStyle(.iconOnly) + .font(.caption2) + .scaleEffect(0.8) + .lineSpacing(0) + } + .miniBadge(color: bCalColor) - Text("\(birthdaysCount)") + Text("\(name)") .hiddenIf(isSmall) } .accessibilityLabel( - "You have \(birthdaysCount) in \(daysToGo) days." + "It is \(name)'s birthday today. \(name) is now \(age) years old" ) + } else { - let people: String = nextBirthdays.1[0] - Group { - Text(daysToGo) + let daysToGo: String = nextBirthdays.0.toDays + if nextBirthdays.1.count > 1 { + let birthdaysCount = nextBirthdays.1.count + Group { + HStack { + Text(daysToGo) + } .miniBadge(color: bCalColor) - Text(people) - .hiddenIf(isSmall) + + Text("\(birthdaysCount)") + .hiddenIf(isSmall) + } + .accessibilityLabel( + "You have \(birthdaysCount) in \(daysToGo) days." + ) + } else { + let people: String = nextBirthdays.1[0] + Group { + Text(daysToGo) + .miniBadge(color: bCalColor) + Text(people) + .hiddenIf(isSmall) + } + .accessibilityLabel( + "The next birthday is in \(daysToGo). It will be \(people)'s birthday." + ) } - .accessibilityLabel( - "The next birthday is in \(daysToGo). It will be \(people)'s birthday." - ) } } } diff --git a/CamiWidget/Views/Header/CamiWidgetHeaderCornerComplication.swift b/CamiWidget/Views/Header/CamiWidgetHeaderCornerComplication.swift new file mode 100644 index 0000000..82d8e4e --- /dev/null +++ b/CamiWidget/Views/Header/CamiWidgetHeaderCornerComplication.swift @@ -0,0 +1,29 @@ +// +// CamiWidgetHeaderCornerComplication.swift +// Cami +// +// Created by Guillaume Coquard on 11/09/24. +// + +import SwiftUI +import WidgetKit + +struct CamiWidgetHeaderCornerComplication: View { + + @Environment(\.widgetFamily) + private var widgetFamily: WidgetFamily + + @Environment(CamiWidgetEntry.self) + private var entry: CamiWidgetEntry + + var body: some View { + switch entry.config.cornerComplication { + case .hidden: + EmptyView() + case .birthdays: + CamiWidgetHeaderBirthdays() + case .summary: + CamiWidgetHeaderEventSummary() + } + } +} diff --git a/CamiWidget/Views/Header/CamiWidgetHeaderEventSummary.swift b/CamiWidget/Views/Header/CamiWidgetHeaderEventSummary.swift new file mode 100644 index 0000000..d07eccc --- /dev/null +++ b/CamiWidget/Views/Header/CamiWidgetHeaderEventSummary.swift @@ -0,0 +1,83 @@ +// +// CamiWidgetHeaderEventSummary.swift +// CamiWidgetExtension +// +// Created by Guillaume Coquard on 11/09/24. +// + +import SwiftUI +import WidgetKit + +struct CamiWidgetHeaderEventSummary: View { + + @Environment(\.widgetFamily) + private var widgetFamily: WidgetFamily + + @Environment(CamiWidgetEntry.self) + private var entry: CamiWidgetEntry + + private var isSmall: Bool { + widgetFamily == WidgetFamily.systemSmall + } + + var numberOfEvents: Int { + let today = Date.now.zero + if entry.events.keys.contains(Date.now.zero) { + guard let events = entry.events[today] else { return 0 } + return events.filter { + $0.isStartingToday && $0.isEndingToday && !$0.isAllDay + }.count + } else { + return 0 + } + } + + var text: some View { + + var text: String + + if numberOfEvents < 1 { + text = NSLocalizedString("noEventToday.complication", comment: "") + } else { + text = "\(numberOfEvents)" + } + + return Text(text) + } + + var body: some View { + + HStack(alignment: .center, spacing: 3) { + if numberOfEvents > 0 { + Image(systemName: "circle.fill") + .resizable() + .renderingMode(.template) + .frame(width: 4, height: 4) + } + text + } + .font(.footnote) + .fontWeight(.bold) + .fontWidth(.compressed) + .pad([ + .notSmall: .init(top: 4, leading: 4, bottom: 4, trailing: 6), + .systemSmall: .init(all: 0) + ]) + .background(numberOfEvents == 0 ? .clear : .red.opacity(0.6)) + .foregroundStyle( + numberOfEvents == 0 + ? AnyShapeStyle(Color.primary.tertiary) + : AnyShapeStyle(Color.white) + ) + .rounded([ + .all: .init( + topLeading: 4, + bottomLeading: 4, + bottomTrailing: 4, + topTrailing: 12 + ) + ]) + .lineLimit(1) + .fontDesign(.rounded) + } +} diff --git a/Localizable.xcstrings b/Localizable.xcstrings new file mode 100644 index 0000000..4b175ad --- /dev/null +++ b/Localizable.xcstrings @@ -0,0 +1,201 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "" : { + + }, + " • " : { + + }, + "%@" : { + + }, + "%@ - %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ - %2$@" + } + } + } + }, + "%lld" : { + + }, + "%lld years old" : { + + }, + "+%lld" : { + + }, + "All-Day Events Style" : { + + }, + "All-Day Inline Calendars" : { + + }, + "And you have also %lld other all-day events." : { + + }, + "Birthdays" : { + + }, + "Calendar" : { + + }, + "Calendars" : { + + }, + "Cami Calendar" : { + + }, + "Cami Calendar Minimal Widget Configuration" : { + + }, + "Cami Calendar, in its current version, is only a widget.\nWe propose it as a better and improved way to display your current native calendar content on your homescreen." : { + + }, + "Cami needs access to your calendars to work properly.\nIt can also use your contacts information to display birthdays in widgets." : { + + }, + "Configuration" : { + + }, + "Corner Complication" : { + + }, + "Details" : { + + }, + "Display Ongoing Events" : { + + }, + "Edit" : { + + }, + "Event" : { + + }, + "Event Bordered" : { + + }, + "Events for %@" : { + + }, + "Events for %@, %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Events for %1$@, %2$@" + } + } + } + }, + "Exit" : { + + }, + "Group Similar Events" : { + + }, + "Hidden" : { + + }, + "Information" : { + + }, + "It is %@'s birthday today. %@ is now %lld years old" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "It is %1$@'s birthday today. %2$@ is now %3$lld years old" + } + } + } + }, + "No Events for Today." : { + + }, + "noEventToday.complication" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No Events" + } + } + } + }, + "ongoingEvents.Title" : { + + }, + "Open in your default browser" : { + + }, + "Refresh Widgets" : { + + }, + "Remaining Time: %@" : { + + }, + "Set Up Cami" : { + + }, + "Settings" : { + + }, + "Style" : { + + }, + "Summary" : { + + }, + "The next birthday is in %@. It will be %@'s birthday." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The next birthday is in %1$@. It will be %2$@'s birthday." + } + } + } + }, + "This event ends in %@." : { + + }, + "This event happens %lld times in your day." : { + + }, + "This event starts at %@." : { + + }, + "Today have %@ as first all-day events." : { + + }, + "Welcome to" : { + + }, + "What do you want to know?" : { + + }, + "You have %lld all-day events." : { + + }, + "You have %lld in %@ days." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "You have %1$lld in %2$@ days." + } + } + } + }, + "You have an event titled: %@." : { + + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/Multiplatform/Extensions/Locale.swift b/Multiplatform/Extensions/Locale.swift new file mode 100644 index 0000000..7d4884f --- /dev/null +++ b/Multiplatform/Extensions/Locale.swift @@ -0,0 +1,16 @@ +// +// Locale.swift +// Cami +// +// Created by Guillaume Coquard on 12/09/24. +// + +import Foundation + +extension Locale { + + static var prefered: Locale { + Locale(identifier: Locale.preferredLanguages.first!) + } + +} diff --git a/Multiplatform/Helper/CamiHelper+Extensions.swift b/Multiplatform/Helper/CamiHelper+Extensions.swift new file mode 100644 index 0000000..5876437 --- /dev/null +++ b/Multiplatform/Helper/CamiHelper+Extensions.swift @@ -0,0 +1,28 @@ +// +// CamiHelper+Extensions.swift +// Cami +// +// Created by Guillaume Coquard on 11/09/24. +// + +import Foundation +import EventKit + +extension CamiHelper { + public static func destination(for event: EKEvent, inPlace: Bool = false) -> URL { + if inPlace { + guard let identifier = event.eventIdentifier else { return .init(string: "")! } + return URL(string: "camical:event?id=\(identifier)")! + } else { + return URL(string: "calshow:\(event.startDate.timeIntervalSinceReferenceDate)")! + } + } + + public static func destination(for date: Date, inPlace: Bool = false) -> URL { + if inPlace { + URL(string: "camical:day?time=\(date.timeIntervalSinceReferenceDate)")! + } else { + URL(string: "calshow:\(date.timeIntervalSinceReferenceDate)")! + } + } +} diff --git a/Multiplatform/Helper/CamiHelper.swift b/Multiplatform/Helper/CamiHelper.swift index bd3aa0b..e265b86 100644 --- a/Multiplatform/Helper/CamiHelper.swift +++ b/Multiplatform/Helper/CamiHelper.swift @@ -8,6 +8,7 @@ import Foundation import EventKit import Contacts +import SwiftUI struct CamiHelper { diff --git a/Multiplatform/Helper/ContactHelper+Extensions.swift b/Multiplatform/Helper/ContactHelper+Extensions.swift new file mode 100644 index 0000000..9ffcbde --- /dev/null +++ b/Multiplatform/Helper/ContactHelper+Extensions.swift @@ -0,0 +1,20 @@ +// +// ContactHelper+Extensions.swift +// Cami +// +// Created by Guillaume Coquard on 11/09/24. +// + +import Foundation +import Contacts + +extension ContactHelper { + + // public static func destination(for date: CNContact) -> URL { + // if UserDefaults.standard.bool(forKey: SettingsKeys.openInCami) { + // URL(string: "camical:contact?time=\(date.timeIntervalSinceReferenceDate)")! + // } else { + // URL(string: "calshow:\(date.timeIntervalSinceReferenceDate)")! + // } + // } +} diff --git a/Multiplatform/Helper/EventHelper+Extensions.swift b/Multiplatform/Helper/EventHelper+Extensions.swift new file mode 100644 index 0000000..99ba7f4 --- /dev/null +++ b/Multiplatform/Helper/EventHelper+Extensions.swift @@ -0,0 +1,30 @@ +// +// EventHelper+Extensions.swift +// Cami +// +// Created by Guillaume Coquard on 11/09/24. +// + +import Foundation +import EventKit +import SwiftUI + +extension EventHelper { + static func openCalendarEvent(withId eventId: String) { + if self.calendarsAccess { + DispatchQueue.main.async { + if let event = EventHelper.event(for: eventId) { + ViewModel.shared.path.append(event) + } + } + } + } + + static func openCalendarDay(atTime timeInterval: String) { + if self.calendarsAccess { + DispatchQueue.main.async { + ViewModel.shared.path.append(Day(.init(timeIntervalSinceReferenceDate: TimeInterval(timeInterval)!))) + } + } + } +} diff --git a/Multiplatform/Helper/EventHelper.swift b/Multiplatform/Helper/EventHelper.swift index 484f4ff..de39dc2 100644 --- a/Multiplatform/Helper/EventHelper.swift +++ b/Multiplatform/Helper/EventHelper.swift @@ -8,6 +8,7 @@ import Foundation import EventKit import OSLog +import SwiftUI struct EventHelper { @@ -199,4 +200,9 @@ struct EventHelper { return Events() } + public static func event(for id: String) -> EKEvent? { + Self.store.refreshSourcesIfNecessary() + return Self.store.event(withIdentifier: id) + } + } diff --git a/Multiplatform/Modifiers/MiniBadge.swift b/Multiplatform/Modifiers/MiniBadge.swift index 8e24fbb..8b35573 100644 --- a/Multiplatform/Modifiers/MiniBadge.swift +++ b/Multiplatform/Modifiers/MiniBadge.swift @@ -26,6 +26,7 @@ struct MiniBadge: ViewModifier { content .fontWeight(.bold) .foregroundStyle(Color.init(white: 0.1)) + .fixedSize(horizontal: true, vertical: true) .pad([ .notSmall: .init( vertical: 2, diff --git a/Multiplatform/Router.swift b/Multiplatform/Router.swift new file mode 100644 index 0000000..20110f0 --- /dev/null +++ b/Multiplatform/Router.swift @@ -0,0 +1,75 @@ +// +// Router.swift +// Stations +// +// Created by Guillaume Coquard on 12/05/24. +// + +import SwiftUI + +import Foundation + +enum RouterError: Error { + case invalidURL + case missingParameter(String) +} + +final class Router { + + static let shared: Router = .init() + + private let scheme = "camical" + private var routes: [String: ([String: String]) -> Void] = [:] + + private init() { + self.routes.updateValue( { parameters in + guard let id = parameters["id"] else { + print("Error: Missing 'id' parameter for event") + return + } + EventHelper.openCalendarEvent(withId: id) + }, forKey: "event") + + self.routes.updateValue( { parameters in + guard let time = parameters["time"] else { + print("Error: Missing 'time' parameter for event") + return + } + EventHelper.openCalendarDay(atTime: time) + }, forKey: "day") + } + + func addRoute(_ path: String, handler: @escaping ([String: String]) -> Void) { + if routes[path] != nil { + routes[path] = handler + } + } + + func handleURL(_ url: URL) { + + guard url.scheme == scheme else { + UIApplication.shared.open(url) + return + } + + let components = URLComponents(url: url, resolvingAgainstBaseURL: false) + guard let path = components?.path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) else { + print(RouterError.invalidURL) + return + } + + guard let handler = routes[path] else { + print("No handler found for path: \(path)") + return + } + + var parameters: [String: String] = [:] + components?.queryItems?.forEach { item in + if let value = item.value { + parameters[item.name] = value + } + } + + handler(parameters) + } +} diff --git a/Multiplatform/SettingsKeys.swift b/Multiplatform/SettingsKeys.swift new file mode 100644 index 0000000..646d659 --- /dev/null +++ b/Multiplatform/SettingsKeys.swift @@ -0,0 +1,15 @@ +// +// SettingsKeys.swift +// Cami +// +// Created by Guillaume Coquard on 11/09/24. +// + +import Foundation + +struct SettingsKeys { + + static let accessWorkInProgressFeatures = "accessWorkInProgressFeatures" + static let openInCami = "openInCami" + +} diff --git a/Settings.bundle/en.lproj/Root.strings b/Settings.bundle/en.lproj/Root.strings index 926f75ad71e6a04fd35063c19ef7960b4b5fd933..02da9292300a7a7748d1974a98b5efe49896fb1e 100644 GIT binary patch delta 146 zcmcb{@`QE6F~)j-h609EhCBuZ23LkMFsp>27|2rt;vgWum?58`l%a?r8O&B-$ONl( cW=Len1+tZZs%(K+iGfUW*dPW?-pe=>0A}4B9RL6T delta 11 ScmaFDdW~hnF~-RuOcMYf1O&7I