Skip to content

Commit

Permalink
Migrate to date components for better cross-timezone notification sch…
Browse files Browse the repository at this point in the history
…eduling
  • Loading branch information
daneden committed Jun 19, 2024
1 parent 48dc695 commit b153b1e
Show file tree
Hide file tree
Showing 20 changed files with 115 additions and 91 deletions.
9 changes: 9 additions & 0 deletions Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,12 @@
}
}
}
},
"Content #1" : {

},
"Content #2" : {

},
"Countdown" : {
"localizations" : {
Expand Down Expand Up @@ -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" : {
Expand Down
34 changes: 11 additions & 23 deletions Solstice.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 */; };
Expand Down Expand Up @@ -350,7 +347,6 @@
7106C87529A276B40007A7EC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
7106C87729A276B40007A7EC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7106C88C29A27D0A0007A7EC /* OverviewWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewWidgetView.swift; sourceTree = "<group>"; };
710D9B6A2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnyTransition+VerticalMove.swift"; sourceTree = "<group>"; };
710D9B702AB4752D0093A9A6 /* ContentToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentToggle.swift; sourceTree = "<group>"; };
7117008829A52ABE001BE478 /* CountdownWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownWidgetView.swift; sourceTree = "<group>"; };
7117008A29A52B04001BE478 /* SkyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkyGradient.swift; sourceTree = "<group>"; };
Expand All @@ -377,6 +373,7 @@
718B136B29A918680001D4DC /* SupporterSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupporterSettings.swift; sourceTree = "<group>"; };
718B136D29A918AD0001D4DC /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
71908ACC2AC95A5500C7B610 /* StringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringBuilder.swift; sourceTree = "<group>"; };
7194650A2C229F73008408C0 /* AppChangeMigrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppChangeMigrator.swift; path = ../../../AppChangeMigrator.swift; sourceTree = "<group>"; };
719511F529AF5CB4009D282F /* GetSunriseTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSunriseTime.swift; sourceTree = "<group>"; };
719511F729AF5CE1009D282F /* GetSunsetTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSunsetTime.swift; sourceTree = "<group>"; };
719511F929AF5CF7009D282F /* ViewDaylight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDaylight.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -674,6 +671,7 @@
71C8F4CD29A38752009A86B4 /* SolsticeCalculator.swift */,
71F641C0299FCCFF00FE5AB5 /* TimeMachine.swift */,
71908ACC2AC95A5500C7B610 /* StringBuilder.swift */,
7194650A2C229F73008408C0 /* AppChangeMigrator.swift */,
);
path = Helpers;
sourceTree = "<group>";
Expand All @@ -693,7 +691,6 @@
7195120029AFBBEC009D282F /* View+EllipticalEdgeMask.swift */,
7188714229DC8C60001A4327 /* View+RectangularEdgeMask.swift */,
7187147429DDE6B600EADF03 /* CLLocationCoordinate2D+RawRepresentable.swift */,
710D9B6A2AB4621A0093A9A6 /* AnyTransition+VerticalMove.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -886,7 +883,7 @@
earth,
);
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1500;
LastUpgradeCheck = 1540;
TargetAttributes = {
7106C86729A276B30007A7EC = {
CreatedOnToolsVersion = 14.2;
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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";
Expand Down Expand Up @@ -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;
Expand All @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)";
Expand Down Expand Up @@ -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)";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1540"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1540"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1540"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1540"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1540"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1540"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
Expand Down
4 changes: 2 additions & 2 deletions Solstice/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ struct ContentView: View {
selectedLocation = currentLocation.id
}
}
.onChange(of: scenePhase) { _ in
.task(id: scenePhase) {
timeMachine.referenceDate = Date()
if currentLocation.isAuthorized,
selectedLocation == currentLocation.id,
scenePhase != .background {
scenePhase == .active {
currentLocation.requestLocation()
}
}
Expand Down
8 changes: 4 additions & 4 deletions Solstice/Detail View/DetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ struct DetailView<Location: ObservableLocation>: 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")
Expand Down Expand Up @@ -153,7 +153,7 @@ struct DetailView<Location: ObservableLocation>: View {
}
#else
ToolbarItem(id: "timeMachineToggle") {
Toggle(isOn: $timeMachine.isOn.animation(.interactiveSpring())) {
Toggle(isOn: $timeMachine.isOn.animation()) {
Label("Time Travel", systemImage: "clock.arrow.2.circlepath")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 0 additions & 26 deletions Solstice/Extensions/AnyTransition+VerticalMove.swift

This file was deleted.

6 changes: 5 additions & 1 deletion Solstice/Extensions/AppStorage++.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ struct Preferences {
static let scheduleType: Value<ScheduleType> = ("notificationScheduleType", .specificTime)

/// The date/time for notification scheduling. Only the time will be used.
static let notificationTime: Value<Date> = ("notifTime", defaultNotificationDate)
static let _notificationTime: Value<Date> = ("notifTime", defaultNotificationDate)

/// The date components for notification scheduling.
static let notificationDateComponents: Value<DateComponents> = ("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<Solar.Phase> = ("notificationRelation", .sunrise)
Expand Down
Loading

0 comments on commit b153b1e

Please sign in to comment.