From b153b1e6d6a3afe8ddeda83294cf64c80f0ca4da Mon Sep 17 00:00:00 2001 From: Daniel Eden Date: Wed, 19 Jun 2024 06:42:02 +0100 Subject: [PATCH] Migrate to date components for better cross-timezone notification scheduling --- Localizable.xcstrings | 9 +++++ Solstice.xcodeproj/project.pbxproj | 34 ++++++------------- .../Solstice for watchOS Watch App.xcscheme | 2 +- .../Solstice watchOS Watch App.xcscheme | 2 +- .../xcshareddata/xcschemes/Solstice.xcscheme | 2 +- .../xcschemes/iOS Widget Extension.xcscheme | 2 +- .../xcschemes/macOS Widget Extension.xcscheme | 2 +- .../watchOS Widget Extension.xcscheme | 2 +- Solstice/ContentView.swift | 4 +-- Solstice/Detail View/DetailView.swift | 8 ++--- .../EquinoxAndSolsticeInfoView.swift | 2 +- .../AnyTransition+VerticalMove.swift | 26 -------------- Solstice/Extensions/AppStorage++.swift | 6 +++- Solstice/Extensions/Date++.swift | 20 +++++++++++ Solstice/Helper Views/ContentToggle.swift | 8 ++++- Solstice/Helpers/NotificationManager.swift | 10 +++--- Solstice/List View/DaylightSummaryRow.swift | 18 +++++++--- Solstice/Settings/NotificationSettings.swift | 22 ++++++++---- Solstice/SolsticeApp.swift | 25 ++++++++------ ...rviewWidgetView+AccessoryWidgetViews.swift | 2 +- 20 files changed, 115 insertions(+), 91 deletions(-) delete mode 100644 Solstice/Extensions/AnyTransition+VerticalMove.swift diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 58c70d30..3939f558 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -516,6 +516,12 @@ } } } + }, + "Content #1" : { + + }, + "Content #2" : { + }, "Countdown" : { "localizations" : { @@ -1719,6 +1725,9 @@ } } } + }, + "Test Label" : { + }, "The equinox and solstice define the transitions between the seasons of the astronomical calendar and are a key part of the Earth’s orbit around the Sun." : { "localizations" : { diff --git a/Solstice.xcodeproj/project.pbxproj b/Solstice.xcodeproj/project.pbxproj index 2fb84ad4..a8fed6e1 100644 --- a/Solstice.xcodeproj/project.pbxproj +++ b/Solstice.xcodeproj/project.pbxproj @@ -24,11 +24,6 @@ 7106C88929A277680007A7EC /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7198468D28E5895F00E866CE /* Persistence.swift */; }; 7106C88B29A277740007A7EC /* Solar in Frameworks */ = {isa = PBXBuildFile; productRef = 7106C88A29A277740007A7EC /* Solar */; }; 7106C88D29A27D0A0007A7EC /* OverviewWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7106C88C29A27D0A0007A7EC /* OverviewWidgetView.swift */; }; - 710D9B6B2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D9B6A2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift */; }; - 710D9B6C2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D9B6A2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift */; }; - 710D9B6D2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D9B6A2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift */; }; - 710D9B6E2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D9B6A2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift */; }; - 710D9B6F2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D9B6A2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift */; }; 710D9B712AB4752D0093A9A6 /* ContentToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D9B702AB4752D0093A9A6 /* ContentToggle.swift */; }; 710D9B722AB4752D0093A9A6 /* ContentToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710D9B702AB4752D0093A9A6 /* ContentToggle.swift */; }; 7117008929A52ABE001BE478 /* CountdownWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7117008829A52ABE001BE478 /* CountdownWidgetView.swift */; }; @@ -104,6 +99,8 @@ 7193E92D29C0BAB300103D6B /* DaylightChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F641B829995ED000FE5AB5 /* DaylightChart.swift */; }; 7193E92E29C0BAB300103D6B /* DaylightChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F641B829995ED000FE5AB5 /* DaylightChart.swift */; }; 7193E92F29C0BAB400103D6B /* DaylightChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F641B829995ED000FE5AB5 /* DaylightChart.swift */; }; + 7194650B2C229F73008408C0 /* AppChangeMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7194650A2C229F73008408C0 /* AppChangeMigrator.swift */; }; + 7194650C2C229F73008408C0 /* AppChangeMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7194650A2C229F73008408C0 /* AppChangeMigrator.swift */; }; 719511F629AF5CB4009D282F /* GetSunriseTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719511F529AF5CB4009D282F /* GetSunriseTime.swift */; }; 719511F829AF5CE1009D282F /* GetSunsetTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719511F729AF5CE1009D282F /* GetSunsetTime.swift */; }; 719511FA29AF5CF7009D282F /* ViewDaylight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719511F929AF5CF7009D282F /* ViewDaylight.swift */; }; @@ -350,7 +347,6 @@ 7106C87529A276B40007A7EC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7106C87729A276B40007A7EC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7106C88C29A27D0A0007A7EC /* OverviewWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewWidgetView.swift; sourceTree = ""; }; - 710D9B6A2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnyTransition+VerticalMove.swift"; sourceTree = ""; }; 710D9B702AB4752D0093A9A6 /* ContentToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentToggle.swift; sourceTree = ""; }; 7117008829A52ABE001BE478 /* CountdownWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownWidgetView.swift; sourceTree = ""; }; 7117008A29A52B04001BE478 /* SkyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkyGradient.swift; sourceTree = ""; }; @@ -377,6 +373,7 @@ 718B136B29A918680001D4DC /* SupporterSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupporterSettings.swift; sourceTree = ""; }; 718B136D29A918AD0001D4DC /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 71908ACC2AC95A5500C7B610 /* StringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringBuilder.swift; sourceTree = ""; }; + 7194650A2C229F73008408C0 /* AppChangeMigrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppChangeMigrator.swift; path = ../../../AppChangeMigrator.swift; sourceTree = ""; }; 719511F529AF5CB4009D282F /* GetSunriseTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSunriseTime.swift; sourceTree = ""; }; 719511F729AF5CE1009D282F /* GetSunsetTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSunsetTime.swift; sourceTree = ""; }; 719511F929AF5CF7009D282F /* ViewDaylight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDaylight.swift; sourceTree = ""; }; @@ -674,6 +671,7 @@ 71C8F4CD29A38752009A86B4 /* SolsticeCalculator.swift */, 71F641C0299FCCFF00FE5AB5 /* TimeMachine.swift */, 71908ACC2AC95A5500C7B610 /* StringBuilder.swift */, + 7194650A2C229F73008408C0 /* AppChangeMigrator.swift */, ); path = Helpers; sourceTree = ""; @@ -693,7 +691,6 @@ 7195120029AFBBEC009D282F /* View+EllipticalEdgeMask.swift */, 7188714229DC8C60001A4327 /* View+RectangularEdgeMask.swift */, 7187147429DDE6B600EADF03 /* CLLocationCoordinate2D+RawRepresentable.swift */, - 710D9B6A2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift */, ); path = Extensions; sourceTree = ""; @@ -886,7 +883,7 @@ earth, ); LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1500; + LastUpgradeCheck = 1540; TargetAttributes = { 7106C86729A276B30007A7EC = { CreatedOnToolsVersion = 14.2; @@ -1004,7 +1001,6 @@ buildActionMask = 2147483647; files = ( 71F2C3A52ABB366E00E69426 /* Widget.intentdefinition in Sources */, - 710D9B6D2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */, 71AEB7B229AE0A9B00A7952D /* AnyLocation.swift in Sources */, 7150CC5B29AB7B3C00E6B90C /* AppStorage++.swift in Sources */, 7187147729DDE6B700EADF03 /* CLLocationCoordinate2D+RawRepresentable.swift in Sources */, @@ -1067,7 +1063,6 @@ 7198C40829DB0670009A457E /* CountdownWidgetTimelineProvider.swift in Sources */, 7198C41029DB06C1009A457E /* OverviewWidget.swift in Sources */, 7195122129AFBE71009D282F /* CurrentLocation.swift in Sources */, - 710D9B6F2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */, 713F800329BE88E800BEA156 /* DaylightSummaryTitle.swift in Sources */, 7198C40C29DB06A8009A457E /* CountdownWidget.swift in Sources */, 7195122C29AFBEAD009D282F /* Solstice.xcdatamodeld in Sources */, @@ -1101,7 +1096,6 @@ buildActionMask = 2147483647; files = ( 71F2C3A62ABB366E00E69426 /* Widget.intentdefinition in Sources */, - 710D9B6E2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */, 7195123229B0B3D6009D282F /* AnyLocation.swift in Sources */, 7195123329B0B3D6009D282F /* AppStorage++.swift in Sources */, 7187147829DDE6B700EADF03 /* CLLocationCoordinate2D+RawRepresentable.swift in Sources */, @@ -1163,11 +1157,11 @@ 713F7FF929BE843700BEA156 /* AnnualOverview.swift in Sources */, 71F641B729994DD900FE5AB5 /* DaylightSummaryRow.swift in Sources */, 713F7FFF29BE88E800BEA156 /* DaylightSummaryTitle.swift in Sources */, + 7194650B2C229F73008408C0 /* AppChangeMigrator.swift in Sources */, 71BD5E4029A785FE00E40C01 /* NotificationManager.swift in Sources */, 719846B028EEE48900E866CE /* CurrentLocation.swift in Sources */, 71A2BDE629B747940071ACE9 /* Globals.swift in Sources */, 719511FC29AF5D0E009D282F /* ViewRemainingDaylight.swift in Sources */, - 710D9B6B2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */, 719511F629AF5CB4009D282F /* GetSunriseTime.swift in Sources */, 71F641BD299968BF00FE5AB5 /* TimeInterval++.swift in Sources */, 7195125829B48ECD009D282F /* TimeZone++.swift in Sources */, @@ -1224,7 +1218,6 @@ 719F927B29ACD21A00C06921 /* TimeInterval++.swift in Sources */, 719F926D29ACD21300C06921 /* DaylightSummaryRow.swift in Sources */, 71A2BDE729B747940071ACE9 /* Globals.swift in Sources */, - 710D9B6C2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift in Sources */, 719F927529ACD21300C06921 /* Persistence.swift in Sources */, 719F927429ACD21300C06921 /* SolsticeApp.swift in Sources */, 71AEB7BE29AE475300A7952D /* LocationPermissionScreenerView.swift in Sources */, @@ -1247,6 +1240,7 @@ 719F927A29ACD21A00C06921 /* AppStorage++.swift in Sources */, 719F927629ACD21A00C06921 /* Solar+SolarEvent.swift in Sources */, 719F926E29ACD21300C06921 /* AnnualDaylightChart.swift in Sources */, + 7194650C2C229F73008408C0 /* AppChangeMigrator.swift in Sources */, 713F7FFD29BE867E00BEA156 /* DailyOverview.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1314,7 +1308,6 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1; PRODUCT_BUNDLE_IDENTIFIER = me.daneden.Solstice.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1345,7 +1338,6 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1; PRODUCT_BUNDLE_IDENTIFIER = me.daneden.Solstice.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1378,7 +1370,6 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.1; - MARKETING_VERSION = 2.1; PRODUCT_BUNDLE_IDENTIFIER = "me.daneden.Solstice.macOS-Widget-Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -1410,7 +1401,6 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.1; - MARKETING_VERSION = 2.1; PRODUCT_BUNDLE_IDENTIFIER = "me.daneden.Solstice.macOS-Widget-Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -1441,7 +1431,6 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1; PRODUCT_BUNDLE_IDENTIFIER = me.daneden.Solstice.watchkitapp.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -1474,7 +1463,6 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1; PRODUCT_BUNDLE_IDENTIFIER = me.daneden.Solstice.watchkitapp.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -1491,6 +1479,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -1539,6 +1528,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.1; + MARKETING_VERSION = 2.1.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1553,6 +1543,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -1595,6 +1586,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.1; + MARKETING_VERSION = 2.1.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -1633,7 +1625,6 @@ "@executable_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.1; - MARKETING_VERSION = 2.1; ON_DEMAND_RESOURCES_PREFETCH_ORDER = earth; PRODUCT_BUNDLE_IDENTIFIER = me.daneden.Solstice; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1676,7 +1667,6 @@ "@executable_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.1; - MARKETING_VERSION = 2.1; ON_DEMAND_RESOURCES_PREFETCH_ORDER = earth; PRODUCT_BUNDLE_IDENTIFIER = me.daneden.Solstice; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1713,7 +1703,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1; PRODUCT_BUNDLE_IDENTIFIER = me.daneden.Solstice.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -1748,7 +1737,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1; PRODUCT_BUNDLE_IDENTIFIER = me.daneden.Solstice.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; diff --git a/Solstice.xcodeproj/xcshareddata/xcschemes/Solstice for watchOS Watch App.xcscheme b/Solstice.xcodeproj/xcshareddata/xcschemes/Solstice for watchOS Watch App.xcscheme index 68290ab0..8d22f994 100644 --- a/Solstice.xcodeproj/xcshareddata/xcschemes/Solstice for watchOS Watch App.xcscheme +++ b/Solstice.xcodeproj/xcshareddata/xcschemes/Solstice for watchOS Watch App.xcscheme @@ -1,6 +1,6 @@ : View { .toolbar { toolbarItems } - .onChange(of: timeMachine.isOn) { _ in - if timeMachine.isOn == true { + .task(id: timeMachine.isOn) { + if timeMachine.isOn { withAnimation { scrollProxy.scrollTo("timeMachineView") } } } - .onChange(of: timeMachine.targetDate) { _ in + .task(id: timeMachine.targetDate) { if timeMachine.isOn { withAnimation { scrollProxy.scrollTo("timeMachineView") @@ -153,7 +153,7 @@ struct DetailView: View { } #else ToolbarItem(id: "timeMachineToggle") { - Toggle(isOn: $timeMachine.isOn.animation(.interactiveSpring())) { + Toggle(isOn: $timeMachine.isOn.animation()) { Label("Time Travel", systemImage: "clock.arrow.2.circlepath") } } diff --git a/Solstice/Equinox and Solstice Info View/EquinoxAndSolsticeInfoView.swift b/Solstice/Equinox and Solstice Info View/EquinoxAndSolsticeInfoView.swift index 34c9d322..8e7d9eb8 100644 --- a/Solstice/Equinox and Solstice Info View/EquinoxAndSolsticeInfoView.swift +++ b/Solstice/Equinox and Solstice Info View/EquinoxAndSolsticeInfoView.swift @@ -160,7 +160,7 @@ struct EquinoxAndSolsticeInfoView: View { } .formStyle(.grouped) .navigationTitle("Equinox and Solstice") - .onChange(of: selection) { _ in + .task(id: selection) { let action = SCNAction.rotateTo( x: 0, y: selection.sunAngle, diff --git a/Solstice/Extensions/AnyTransition+VerticalMove.swift b/Solstice/Extensions/AnyTransition+VerticalMove.swift deleted file mode 100644 index f9ede986..00000000 --- a/Solstice/Extensions/AnyTransition+VerticalMove.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// AnyTransition+VerticalMove.swift -// Solstice -// -// Created by Daniel Eden on 15/09/2023. -// - -import SwiftUI - -extension AnyTransition { - static var verticalMove: AnyTransition { - .asymmetric( - insertion: .move(edge: .top), - removal: .move(edge: .bottom) - ) - .combined(with: .opacity) - } - - static var horizontalMove: AnyTransition { - .asymmetric( - insertion: .move(edge: .leading), - removal: .move(edge: .trailing) - ) - .combined(with: .opacity) - } -} diff --git a/Solstice/Extensions/AppStorage++.swift b/Solstice/Extensions/AppStorage++.swift index 87fd0617..4e72e240 100644 --- a/Solstice/Extensions/AppStorage++.swift +++ b/Solstice/Extensions/AppStorage++.swift @@ -85,7 +85,11 @@ struct Preferences { static let scheduleType: Value = ("notificationScheduleType", .specificTime) /// The date/time for notification scheduling. Only the time will be used. - static let notificationTime: Value = ("notifTime", defaultNotificationDate) + static let _notificationTime: Value = ("notifTime", defaultNotificationDate) + + /// The date components for notification scheduling. + static let notificationDateComponents: Value = ("notifDateComponents", NotificationSettings.defaultDateComponents) + static let defaultDateComponents = DateComponents(timeZone: .autoupdatingCurrent, hour: 8, minute: 0) /// Which solar event notifications are sent relative to static let relation: Value = ("notificationRelation", .sunrise) diff --git a/Solstice/Extensions/Date++.swift b/Solstice/Extensions/Date++.swift index 58da7461..94180113 100644 --- a/Solstice/Extensions/Date++.swift +++ b/Solstice/Extensions/Date++.swift @@ -37,3 +37,23 @@ extension Date: RawRepresentable { self = Date(timeIntervalSinceReferenceDate: Double(rawValue) ?? 0.0) } } + +extension DateComponents: RawRepresentable { + public var rawValue: String { + guard let data = try? JSONEncoder().encode(self), + let string = String(data: data, encoding: .utf8) else { + return "{}" + } + + return string + } + + public init?(rawValue: String) { + guard let data = rawValue.data(using: .utf8), + let decoded = try? JSONDecoder().decode(DateComponents.self, from: data) else { + return nil + } + + self = decoded + } +} diff --git a/Solstice/Helper Views/ContentToggle.swift b/Solstice/Helper Views/ContentToggle.swift index 4ac95280..7a03f3c4 100644 --- a/Solstice/Helper Views/ContentToggle.swift +++ b/Solstice/Helper Views/ContentToggle.swift @@ -14,7 +14,13 @@ struct ContentToggle: View { var body: some View { HStack { content(showToggledContent) - .transition(.verticalMove) + .modify { content in + if #available(iOS 17, macOS 13, watchOS 10, *) { + content.transition(.blurReplace) + } else { + content + } + } } .animation(.default, value: showToggledContent) .onTapGesture { diff --git a/Solstice/Helpers/NotificationManager.swift b/Solstice/Helpers/NotificationManager.swift index 199565e3..d59ad2e9 100644 --- a/Solstice/Helpers/NotificationManager.swift +++ b/Solstice/Helpers/NotificationManager.swift @@ -19,7 +19,7 @@ class NotificationManager: NSObject, UNUserNotificationCenterDelegate, Observabl @AppStorage(Preferences.notificationsIncludeDaylightDuration) static var includeDaylightDuration @AppStorage(Preferences.notificationsIncludeSolsticeCountdown) static var includeSolsticeCountdown @AppStorage(Preferences.NotificationSettings.scheduleType) static var scheduleType - @AppStorage(Preferences.NotificationSettings.notificationTime) static var userPreferenceNotificationTime + @AppStorage(Preferences.NotificationSettings.notificationDateComponents) static var notificationDateComponents @AppStorage(Preferences.NotificationSettings.relativeOffset) static var userPreferenceNotificationOffset @AppStorage(Preferences.sadPreference) static var sadPreference @AppStorage(Preferences.customNotificationLocationUUID) static var customNotificationLocationUUID @@ -91,7 +91,8 @@ class NotificationManager: NSObject, UNUserNotificationCenterDelegate, Observabl content.title = notificationContent.title content.body = notificationContent.body - let components = calendar.dateComponents([.hour, .minute, .day, .month], from: notificationDate) + var components = calendar.dateComponents([.hour, .minute, .day, .month], from: notificationDate) + components.timeZone = .autoupdatingCurrent let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false) @@ -108,8 +109,9 @@ class NotificationManager: NSObject, UNUserNotificationCenterDelegate, Observabl static func getNextNotificationDate(after date: Date, with solar: Solar? = nil) -> Date { if scheduleType == .specificTime { - let scheduleComponents = calendar.dateComponents([.hour, .minute], from: userPreferenceNotificationTime) - return calendar.date(bySettingHour: scheduleComponents.hour ?? 0, minute: scheduleComponents.minute ?? 0, second: 0, of: date) ?? date + let hour = notificationDateComponents.hour ?? 0 + let minute = notificationDateComponents.minute ?? 0 + return calendar.date(bySettingHour: hour, minute: minute, second: 0, of: date) ?? date } else { guard let solar else { return date } let relativeDate = scheduleType == .sunset ? solar.safeSunset : solar.safeSunrise diff --git a/Solstice/List View/DaylightSummaryRow.swift b/Solstice/List View/DaylightSummaryRow.swift index 32b643d3..b83972e6 100644 --- a/Solstice/List View/DaylightSummaryRow.swift +++ b/Solstice/List View/DaylightSummaryRow.swift @@ -79,18 +79,28 @@ struct DaylightSummaryRow: View { } Text(location.title ?? "Current location") - .id(location.title) - .transition(.verticalMove) + .modify { content in + if #available(iOS 17, macOS 13, watchOS 10, *) { + content.transition(.blurReplace) + } else { + content + } + } .lineLimit(2) } if let subtitle, !subtitle.isEmpty { Text(subtitle) - .id(subtitle) - .transition(.verticalMove) .foregroundStyle(.secondary) .font(.footnote) + .modify { content in + if #available(iOS 17, macOS 13, watchOS 10, *) { + content.transition(.blurReplace) + } else { + content + } + } } } diff --git a/Solstice/Settings/NotificationSettings.swift b/Solstice/Settings/NotificationSettings.swift index 901ecd75..de64b5c3 100644 --- a/Solstice/Settings/NotificationSettings.swift +++ b/Solstice/Settings/NotificationSettings.swift @@ -18,7 +18,7 @@ struct NotificationSettings: View { @AppStorage(Preferences.NotificationSettings.scheduleType) var scheduleType @AppStorage(Preferences.NotificationSettings.relativeOffset) var notificationOffset - @AppStorage(Preferences.NotificationSettings.notificationTime) var notificationTime + @AppStorage(Preferences.NotificationSettings.notificationDateComponents) var notificationDateComponents // Notification fragment settings @AppStorage(Preferences.notificationsIncludeSunTimes) var notifsIncludeSunTimes @@ -44,6 +44,16 @@ struct NotificationSettings: View { ] } + private var notificationTime: Binding { + Binding { + let hour = notificationDateComponents.hour ?? 0 + let minute = notificationDateComponents.minute ?? 0 + return Calendar.autoupdatingCurrent.date(bySettingHour: hour, minute: minute, second: 0, of: .now) ?? .now + } set: { + notificationDateComponents = Calendar.autoupdatingCurrent.dateComponents([.hour, .minute, .timeZone], from: $0) + } + } + var body: some View { #if os(iOS) NavigationStack { @@ -58,11 +68,9 @@ struct NotificationSettings: View { var content: some View { Form { Toggle("Enable notifications", isOn: $notificationsEnabled) - .onChange(of: notificationsEnabled) { _ in - Task { - if notificationsEnabled == true { - notificationsEnabled = await NotificationManager.requestAuthorization() ?? false - } + .task(id: notificationsEnabled) { + if notificationsEnabled == true { + notificationsEnabled = await NotificationManager.requestAuthorization() ?? false } } @@ -103,7 +111,7 @@ struct NotificationSettings: View { } if scheduleType == .specificTime { - DatePicker(selection: $notificationTime, displayedComponents: [.hourAndMinute]) { + DatePicker(selection: notificationTime, displayedComponents: [.hourAndMinute]) { Text("Time") } } else { diff --git a/Solstice/SolsticeApp.swift b/Solstice/SolsticeApp.swift index 23813ee9..9a4761b0 100644 --- a/Solstice/SolsticeApp.swift +++ b/Solstice/SolsticeApp.swift @@ -33,18 +33,21 @@ struct SolsticeApp: App { } } } - } - .onChange(of: phase) { _ in - switch phase { - #if !os(watchOS) - case .background: - Task { - await NotificationManager.scheduleNotifications(locationManager: currentLocation) + .task(id: phase) { + switch phase { + #if !os(watchOS) + case .background: + Task { + await NotificationManager.scheduleNotifications(locationManager: currentLocation) + } + #endif + case .active: + currentLocation.requestLocation() + default: + return + } } - #endif - default: - currentLocation.requestLocation() - } + .migrateAppFeatures() } #if os(visionOS) diff --git a/Widget/Overview Widget/OverviewWidgetView+AccessoryWidgetViews.swift b/Widget/Overview Widget/OverviewWidgetView+AccessoryWidgetViews.swift index a1caa68f..f269c0d9 100644 --- a/Widget/Overview Widget/OverviewWidgetView+AccessoryWidgetViews.swift +++ b/Widget/Overview Widget/OverviewWidgetView+AccessoryWidgetViews.swift @@ -65,7 +65,7 @@ extension OverviewWidgetView { } } .foregroundStyle(.secondary) - .transition(.verticalMove) + .transition(.blurReplace) } }