Commit ae034e64 by Dmitriy Stepanets

Merge branch 'feature/IOS-101-widget-medium-temperature' into…

Merge branch 'feature/IOS-101-widget-medium-temperature' into feature/IOS-123-widget-promotion-sheet
parents 84bc8c41 0ebf55bb
...@@ -211,7 +211,6 @@ ...@@ -211,7 +211,6 @@
CE5F0CBA268A02C100B99572 /* MediumTemperatureWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5F0CB9268A02C100B99572 /* MediumTemperatureWidget.swift */; }; CE5F0CBA268A02C100B99572 /* MediumTemperatureWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5F0CB9268A02C100B99572 /* MediumTemperatureWidget.swift */; };
CE5F0CBC268A031800B99572 /* OneWeatherWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5F0CBB268A031800B99572 /* OneWeatherWidgetsBundle.swift */; }; CE5F0CBC268A031800B99572 /* OneWeatherWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5F0CBB268A031800B99572 /* OneWeatherWidgetsBundle.swift */; };
CE6BE4942634170800626822 /* USStateCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6BE4932634170800626822 /* USStateCode.swift */; }; CE6BE4942634170800626822 /* USStateCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6BE4932634170800626822 /* USStateCode.swift */; };
CE6F5F0C263C8B3D00973137 /* SmartTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE6F5F0B263C8B3C00973137 /* SmartTextProvider.swift */; };
CE7298C9267A34F3002745D0 /* BlendFIPSSource.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEEF40FF265E47FF00425D8F /* BlendFIPSSource.framework */; }; CE7298C9267A34F3002745D0 /* BlendFIPSSource.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEEF40FF265E47FF00425D8F /* BlendFIPSSource.framework */; };
CE7298CA267A34F3002745D0 /* BlendFIPSSource.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEEF40FF265E47FF00425D8F /* BlendFIPSSource.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CE7298CA267A34F3002745D0 /* BlendFIPSSource.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEEF40FF265E47FF00425D8F /* BlendFIPSSource.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CE7298CC267A34F5002745D0 /* BlendHealthSource.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD3883C12657B6A10070FD6F /* BlendHealthSource.framework */; }; CE7298CC267A34F5002745D0 /* BlendHealthSource.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD3883C12657B6A10070FD6F /* BlendHealthSource.framework */; };
...@@ -519,7 +518,6 @@ ...@@ -519,7 +518,6 @@
CE5F0CB9268A02C100B99572 /* MediumTemperatureWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediumTemperatureWidget.swift; sourceTree = "<group>"; }; CE5F0CB9268A02C100B99572 /* MediumTemperatureWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediumTemperatureWidget.swift; sourceTree = "<group>"; };
CE5F0CBB268A031800B99572 /* OneWeatherWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneWeatherWidgetsBundle.swift; sourceTree = "<group>"; }; CE5F0CBB268A031800B99572 /* OneWeatherWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneWeatherWidgetsBundle.swift; sourceTree = "<group>"; };
CE6BE4932634170800626822 /* USStateCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = USStateCode.swift; sourceTree = "<group>"; }; CE6BE4932634170800626822 /* USStateCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = USStateCode.swift; sourceTree = "<group>"; };
CE6F5F0B263C8B3C00973137 /* SmartTextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartTextProvider.swift; sourceTree = "<group>"; };
CE81A421266E289E00800EFF /* NativeAdView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdView.swift; sourceTree = "<group>"; }; CE81A421266E289E00800EFF /* NativeAdView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeAdView.swift; sourceTree = "<group>"; };
CE849DB52638C33600DEFFBD /* OneWeatherNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OneWeatherNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; CE849DB52638C33600DEFFBD /* OneWeatherNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OneWeatherNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
CE849DB72638C33600DEFFBD /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; }; CE849DB72638C33600DEFFBD /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
...@@ -1348,7 +1346,6 @@ ...@@ -1348,7 +1346,6 @@
CE13B76126246743007CBD4D /* CCPA */, CE13B76126246743007CBD4D /* CCPA */,
CE6BE4932634170800626822 /* USStateCode.swift */, CE6BE4932634170800626822 /* USStateCode.swift */,
CE895F0E26393FD800214175 /* WeatherImageProvider.swift */, CE895F0E26393FD800214175 /* WeatherImageProvider.swift */,
CE6F5F0B263C8B3C00973137 /* SmartTextProvider.swift */,
); );
path = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -1896,7 +1893,6 @@ ...@@ -1896,7 +1893,6 @@
CD17C5F625D15B4400EE884E /* TodayViewController.swift in Sources */, CD17C5F625D15B4400EE884E /* TodayViewController.swift in Sources */,
CD86245E25E646350097F3FB /* SunUvView.swift in Sources */, CD86245E25E646350097F3FB /* SunUvView.swift in Sources */,
CDF8F12D26208E7B00DB384A /* MapCurrentTimeView.swift in Sources */, CDF8F12D26208E7B00DB384A /* MapCurrentTimeView.swift in Sources */,
CE6F5F0C263C8B3D00973137 /* SmartTextProvider.swift in Sources */,
CEC7D8EE2639FE2700B8836D /* OLInAppStoreManager.swift in Sources */, CEC7D8EE2639FE2700B8836D /* OLInAppStoreManager.swift in Sources */,
CD82300725D6A73F00A05501 /* TodayConditionButton.swift in Sources */, CD82300725D6A73F00A05501 /* TodayConditionButton.swift in Sources */,
CDC6126A25E90C8800188DA7 /* GraphLineSettings.swift in Sources */, CDC6126A25E90C8800188DA7 /* GraphLineSettings.swift in Sources */,
......
...@@ -80,8 +80,8 @@ ...@@ -80,8 +80,8 @@
<EnvironmentVariables> <EnvironmentVariables>
<EnvironmentVariable <EnvironmentVariable
key = "_XCWidgetKind" key = "_XCWidgetKind"
value = "com.outlouder.oneweather.widget" value = "com.onelouder.oneweather.widget.small.temperature"
isEnabled = "NO"> isEnabled = "YES">
</EnvironmentVariable> </EnvironmentVariable>
<EnvironmentVariable <EnvironmentVariable
key = "_XCWidgetDefaultView" key = "_XCWidgetDefaultView"
......
...@@ -9,6 +9,7 @@ import Foundation ...@@ -9,6 +9,7 @@ import Foundation
import CoreData import CoreData
import OneWeatherCore import OneWeatherCore
import OneWeatherAnalytics import OneWeatherAnalytics
import WidgetKit
public class CoreDataStorage: Storage { public class CoreDataStorage: Storage {
private let modelName = "1WModel" private let modelName = "1WModel"
...@@ -92,6 +93,11 @@ public class CoreDataStorage: Storage { ...@@ -92,6 +93,11 @@ public class CoreDataStorage: Storage {
context.insert(coreAppData) context.insert(coreAppData)
try self.save(context: context) try self.save(context: context)
self.lastSavedAppData = appData self.lastSavedAppData = appData
// This shouldn't be here in theory, but it's the simplest way to work around the DelayedSaveStorage.
// TODO: find a better place for it.
if #available(iOS 14, *) {
WidgetCenter.shared.reloadAllTimelines()
}
} }
self.log.info("Save: success") self.log.info("Save: success")
} }
......
...@@ -75,6 +75,7 @@ public class DelayedSaveStorage: Storage { ...@@ -75,6 +75,7 @@ public class DelayedSaveStorage: Storage {
self?.latestKnownAppData = nil self?.latestKnownAppData = nil
} }
} }
self?.saveQueue.addOperation(saveOperation)
} }
saveDelayQueue.addOperation(saveWithDelayOperation) saveDelayQueue.addOperation(saveWithDelayOperation)
} }
...@@ -106,5 +107,6 @@ public class DelayedSaveStorage: Storage { ...@@ -106,5 +107,6 @@ public class DelayedSaveStorage: Storage {
self?.latestKnownAppDataSynchronizationQueue.waitUntilAllOperationsAreFinished() self?.latestKnownAppDataSynchronizationQueue.waitUntilAllOperationsAreFinished()
} }
self.saveQueue.waitUntilAllOperationsAreFinished() self.saveQueue.waitUntilAllOperationsAreFinished()
self.latestKnownAppDataSynchronizationQueue.waitUntilAllOperationsAreFinished()
} }
} }
...@@ -82,6 +82,14 @@ ...@@ -82,6 +82,14 @@
CDFE458D26566BD50021A29F /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFE458C26566BD50021A29F /* Storage.swift */; }; CDFE458D26566BD50021A29F /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFE458C26566BD50021A29F /* Storage.swift */; };
CDFE459426566D7B0021A29F /* HealthSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFE459326566D7B0021A29F /* HealthSource.swift */; }; CDFE459426566D7B0021A29F /* HealthSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFE459326566D7B0021A29F /* HealthSource.swift */; };
CDFE459626566D860021A29F /* FIPSSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFE459526566D860021A29F /* FIPSSource.swift */; }; CDFE459626566D860021A29F /* FIPSSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFE459526566D860021A29F /* FIPSSource.swift */; };
CEFE851826948C15003C67D3 /* SmartTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851726948C15003C67D3 /* SmartTextProvider.swift */; };
CEFE851C2694986D003C67D3 /* SmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851B2694986D003C67D3 /* SmartText.swift */; };
CEFE851E2694C477003C67D3 /* SmartTextMacro.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851D2694C477003C67D3 /* SmartTextMacro.swift */; };
CEFE85202694C4BC003C67D3 /* DefaultSmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851F2694C4BC003C67D3 /* DefaultSmartText.swift */; };
CEFE85222694C57A003C67D3 /* OngoingPrecipitationSmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE85212694C57A003C67D3 /* OngoingPrecipitationSmartText.swift */; };
CEFE85242694C5D1003C67D3 /* ApproachingPrecipitationSmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE85232694C5D1003C67D3 /* ApproachingPrecipitationSmartText.swift */; };
CEFE85262694C5E4003C67D3 /* WindSmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE85252694C5E4003C67D3 /* WindSmartText.swift */; };
CEFE85282694C5F7003C67D3 /* HumiditySmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE85272694C5F7003C67D3 /* HumiditySmartText.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
...@@ -180,6 +188,14 @@ ...@@ -180,6 +188,14 @@
CDFE458C26566BD50021A29F /* Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; }; CDFE458C26566BD50021A29F /* Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
CDFE459326566D7B0021A29F /* HealthSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthSource.swift; sourceTree = "<group>"; }; CDFE459326566D7B0021A29F /* HealthSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthSource.swift; sourceTree = "<group>"; };
CDFE459526566D860021A29F /* FIPSSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FIPSSource.swift; sourceTree = "<group>"; }; CDFE459526566D860021A29F /* FIPSSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FIPSSource.swift; sourceTree = "<group>"; };
CEFE851726948C15003C67D3 /* SmartTextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartTextProvider.swift; sourceTree = "<group>"; };
CEFE851B2694986D003C67D3 /* SmartText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartText.swift; sourceTree = "<group>"; };
CEFE851D2694C477003C67D3 /* SmartTextMacro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartTextMacro.swift; sourceTree = "<group>"; };
CEFE851F2694C4BC003C67D3 /* DefaultSmartText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultSmartText.swift; sourceTree = "<group>"; };
CEFE85212694C57A003C67D3 /* OngoingPrecipitationSmartText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingPrecipitationSmartText.swift; sourceTree = "<group>"; };
CEFE85232694C5D1003C67D3 /* ApproachingPrecipitationSmartText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApproachingPrecipitationSmartText.swift; sourceTree = "<group>"; };
CEFE85252694C5E4003C67D3 /* WindSmartText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindSmartText.swift; sourceTree = "<group>"; };
CEFE85272694C5F7003C67D3 /* HumiditySmartText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HumiditySmartText.swift; sourceTree = "<group>"; };
D9993F9D0D3A6FC16441D26F /* Pods_OneWeatherCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OneWeatherCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D9993F9D0D3A6FC16441D26F /* Pods_OneWeatherCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OneWeatherCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
...@@ -414,6 +430,7 @@ ...@@ -414,6 +430,7 @@
CDD2F8ED2665100F00B48322 /* Model */ = { CDD2F8ED2665100F00B48322 /* Model */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CEFE851926949844003C67D3 /* SmartTexts */,
CDD2F8EE2665102B00B48322 /* LocationManager.swift */, CDD2F8EE2665102B00B48322 /* LocationManager.swift */,
CDD2F8F02665112800B48322 /* DeviceLocationMonitor.swift */, CDD2F8F02665112800B48322 /* DeviceLocationMonitor.swift */,
); );
...@@ -451,6 +468,29 @@ ...@@ -451,6 +468,29 @@
path = Sources; path = Sources;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CEFE851926949844003C67D3 /* SmartTexts */ = {
isa = PBXGroup;
children = (
CEFE851726948C15003C67D3 /* SmartTextProvider.swift */,
CEFE851B2694986D003C67D3 /* SmartText.swift */,
CEFE851D2694C477003C67D3 /* SmartTextMacro.swift */,
CEFE851A26949855003C67D3 /* SmartTexts */,
);
path = SmartTexts;
sourceTree = "<group>";
};
CEFE851A26949855003C67D3 /* SmartTexts */ = {
isa = PBXGroup;
children = (
CEFE851F2694C4BC003C67D3 /* DefaultSmartText.swift */,
CEFE85212694C57A003C67D3 /* OngoingPrecipitationSmartText.swift */,
CEFE85232694C5D1003C67D3 /* ApproachingPrecipitationSmartText.swift */,
CEFE85252694C5E4003C67D3 /* WindSmartText.swift */,
CEFE85272694C5F7003C67D3 /* HumiditySmartText.swift */,
);
path = SmartTexts;
sourceTree = "<group>";
};
F74D258AC48F534A8A9B9EDB /* Frameworks */ = { F74D258AC48F534A8A9B9EDB /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
...@@ -600,8 +640,10 @@ ...@@ -600,8 +640,10 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
CD3883972657AFE00070FD6F /* Settings.swift in Sources */, CD3883972657AFE00070FD6F /* Settings.swift in Sources */,
CEFE85242694C5D1003C67D3 /* ApproachingPrecipitationSmartText.swift in Sources */,
CD2D55DA26553751007B70F4 /* NWSAlert.swift in Sources */, CD2D55DA26553751007B70F4 /* NWSAlert.swift in Sources */,
CD11AFE526651BCD00EC4BA0 /* LegacySettings.swift in Sources */, CD11AFE526651BCD00EC4BA0 /* LegacySettings.swift in Sources */,
CEFE85262694C5E4003C67D3 /* WindSmartText.swift in Sources */,
CD11AFED26651E0A00EC4BA0 /* WdtWeatherSourceError.swift in Sources */, CD11AFED26651E0A00EC4BA0 /* WdtWeatherSourceError.swift in Sources */,
CD2D55D526553384007B70F4 /* UserDefaultsOptionalValue.swift in Sources */, CD2D55D526553384007B70F4 /* UserDefaultsOptionalValue.swift in Sources */,
CD11AFE126651A4700EC4BA0 /* NWSAlertInfoParser.swift in Sources */, CD11AFE126651A4700EC4BA0 /* NWSAlertInfoParser.swift in Sources */,
...@@ -613,10 +655,12 @@ ...@@ -613,10 +655,12 @@
CD550FBC265531A100257FB5 /* WeatherLayerType.swift in Sources */, CD550FBC265531A100257FB5 /* WeatherLayerType.swift in Sources */,
CD550FBD265531A100257FB5 /* SevereLayerType.swift in Sources */, CD550FBD265531A100257FB5 /* SevereLayerType.swift in Sources */,
CD91685826552FD000EC04EF /* DefaultSettingsFactory.swift in Sources */, CD91685826552FD000EC04EF /* DefaultSettingsFactory.swift in Sources */,
CEFE851C2694986D003C67D3 /* SmartText.swift in Sources */,
CDFE459626566D860021A29F /* FIPSSource.swift in Sources */, CDFE459626566D860021A29F /* FIPSSource.swift in Sources */,
CD91685926552FD000EC04EF /* DefaultSettings.swift in Sources */, CD91685926552FD000EC04EF /* DefaultSettings.swift in Sources */,
CD91685A26552FD000EC04EF /* DefaultSettingsImperial.swift in Sources */, CD91685A26552FD000EC04EF /* DefaultSettingsImperial.swift in Sources */,
CD91685B26552FD000EC04EF /* DefaultSettingsMetric.swift in Sources */, CD91685B26552FD000EC04EF /* DefaultSettingsMetric.swift in Sources */,
CEFE85282694C5F7003C67D3 /* HumiditySmartText.swift in Sources */,
CD615FB82655295C00B717DB /* UIColor+Hex.swift in Sources */, CD615FB82655295C00B717DB /* UIColor+Hex.swift in Sources */,
CD2D55E0265537DC007B70F4 /* NWSAlertInfoBlock.swift in Sources */, CD2D55E0265537DC007B70F4 /* NWSAlertInfoBlock.swift in Sources */,
CD91685726552FAE00EC04EF /* MulticastDelegate.swift in Sources */, CD91685726552FAE00EC04EF /* MulticastDelegate.swift in Sources */,
...@@ -638,22 +682,26 @@ ...@@ -638,22 +682,26 @@
CD615FC42655295C00B717DB /* UnitPressure+Atmosphere.swift in Sources */, CD615FC42655295C00B717DB /* UnitPressure+Atmosphere.swift in Sources */,
CD615FC52655295C00B717DB /* CLAuthorizationStatus+Localized.swift in Sources */, CD615FC52655295C00B717DB /* CLAuthorizationStatus+Localized.swift in Sources */,
CD615FC62655295C00B717DB /* UIApplication+Settings.swift in Sources */, CD615FC62655295C00B717DB /* UIApplication+Settings.swift in Sources */,
CEFE85202694C4BC003C67D3 /* DefaultSmartText.swift in Sources */,
CD8E48A526651414008E7F8D /* NWSCurrentEventsReponse.swift in Sources */, CD8E48A526651414008E7F8D /* NWSCurrentEventsReponse.swift in Sources */,
CD2D55D8265533F4007B70F4 /* UserDefaultsWrapper.swift in Sources */, CD2D55D8265533F4007B70F4 /* UserDefaultsWrapper.swift in Sources */,
CD615FC72655295C00B717DB /* UITabBarController+Hide.swift in Sources */, CD615FC72655295C00B717DB /* UITabBarController+Hide.swift in Sources */,
CDD2F8F62665117400B48322 /* NWSAlertsManager.swift in Sources */, CDD2F8F62665117400B48322 /* NWSAlertsManager.swift in Sources */,
CD11AFE726651BF900EC4BA0 /* LegacyWdtLocation.swift in Sources */, CD11AFE726651BF900EC4BA0 /* LegacyWdtLocation.swift in Sources */,
CD615FC82655295C00B717DB /* CACornerMask+All.swift in Sources */, CD615FC82655295C00B717DB /* CACornerMask+All.swift in Sources */,
CEFE851E2694C477003C67D3 /* SmartTextMacro.swift in Sources */,
CD11AFE326651B6300EC4BA0 /* LegacyMigrationManager.swift in Sources */, CD11AFE326651B6300EC4BA0 /* LegacyMigrationManager.swift in Sources */,
CD2D55DD2655377F007B70F4 /* NWSAlertExtendedInfo.swift in Sources */, CD2D55DD2655377F007B70F4 /* NWSAlertExtendedInfo.swift in Sources */,
CD615FC92655295C00B717DB /* UIDevice+Convenience.swift in Sources */, CD615FC92655295C00B717DB /* UIDevice+Convenience.swift in Sources */,
CD615F95265526E700B717DB /* UpdatableModelObject.swift in Sources */, CD615F95265526E700B717DB /* UpdatableModelObject.swift in Sources */,
CD615FA4265528F000B717DB /* HelperTypes.swift in Sources */, CD615FA4265528F000B717DB /* HelperTypes.swift in Sources */,
CD3883EC2657B83D0070FD6F /* FIPSResponse.swift in Sources */, CD3883EC2657B83D0070FD6F /* FIPSResponse.swift in Sources */,
CEFE85222694C57A003C67D3 /* OngoingPrecipitationSmartText.swift in Sources */,
CD615F96265526E700B717DB /* UpdatableModelObjectInTime.swift in Sources */, CD615F96265526E700B717DB /* UpdatableModelObjectInTime.swift in Sources */,
CD615F97265526E700B717DB /* PartialLocation.swift in Sources */, CD615F97265526E700B717DB /* PartialLocation.swift in Sources */,
CD615F98265526E700B717DB /* GeoNamesPlace.swift in Sources */, CD615F98265526E700B717DB /* GeoNamesPlace.swift in Sources */,
CD615F99265526E700B717DB /* Location.swift in Sources */, CD615F99265526E700B717DB /* Location.swift in Sources */,
CEFE851826948C15003C67D3 /* SmartTextProvider.swift in Sources */,
CD11AFE926651C9200EC4BA0 /* StuffThatIsPresentInTheMainProject.swift in Sources */, CD11AFE926651C9200EC4BA0 /* StuffThatIsPresentInTheMainProject.swift in Sources */,
CDFE459426566D7B0021A29F /* HealthSource.swift in Sources */, CDFE459426566D7B0021A29F /* HealthSource.swift in Sources */,
CD615F9A265526E700B717DB /* CurrentWeather.swift in Sources */, CD615F9A265526E700B717DB /* CurrentWeather.swift in Sources */,
......
...@@ -62,6 +62,11 @@ public enum WeatherType: String, CaseIterable { ...@@ -62,6 +62,11 @@ public enum WeatherType: String, CaseIterable {
} }
return stringId return stringId
} }
public var isPrecipitation: Bool {
let precipitationTypes = Set<WeatherType>([.snowy, .thunderstorm, .heavySnow, .lightSnow, .freezingRain, .lightHail, .lightDrizzle, .heavyRain])
return precipitationTypes.contains(self)
}
} }
public enum WeatherConditionType: CaseIterable { public enum WeatherConditionType: CaseIterable {
......
...@@ -428,8 +428,9 @@ public class LocationManager { ...@@ -428,8 +428,9 @@ public class LocationManager {
if let updatedLocation = updatedLocation { if let updatedLocation = updatedLocation {
self.log.info("Update weather finished for \(location)") self.log.info("Update weather finished for \(location)")
self.makeChanges(to: location, in: "weather") { (oldLocation) -> Location in self.makeChanges(to: location, in: "weather") { (oldLocation) -> Location in
completion?(updatedLocation) let merged = oldLocation.mergedWith(incrementalChanges: updatedLocation)
return oldLocation.mergedWith(incrementalChanges: updatedLocation) completion?(merged)
return merged
} }
} }
else { else {
......
//
// SmartText.swift
// OneWeatherCore
//
// Created by Demid Merzlyakov on 06.07.2021.
//
import Foundation
/// A single smart text case. E.g. "feels like X degrees", "strong wind, stay at home", etc.
/// The text itself is stored in localized strings, and `templateKey` is used to fetch the actual text. But then we need to insert values into localized text, like temperature, humidity, etc. And it needs to be localized as well (e.g. temperature in F, or in C depending on the user settings.).
/// For that purpose, `SmartTextMacro` surve. See the description for `SmartTextMacro`for more details, but in general smart text macro can check wether a particular piece of information is available in this smart text, and is capable of extracting it from the weather data and formatting it for being printed.
/// So, the overall logic is as follows:
/// ```
/// 1. Take smart text.
/// 2. Check if it's applicable.
/// 3. If it is, extract macro values from a location object.
/// 4. Take the templateKey and get it's localized value.
/// 5. Substitute macros in the localized template with actual localized values.
/// ```
public protocol SmartText {
var templateKey: String { get }
var requiredMacros: [SmartTextMacro] { get }
/// Wether this smart text can be applied to this location. E.g. if this smart text requires temperature forecast for the next 5 days, and this location object only has 3 days forecast, it is not applicable.
/// - Parameter location: A location object, for which we'd like to generate a smart text.
func applicable(to location: Location) -> Bool
/// A function for building
/// - Parameter location: <#location description#>
func buildText(for location: Location) -> String?
}
public extension SmartText {
func buildText(for location: Location) -> String? {
var result: String? = self.templateKey.localized()
for macro in requiredMacros {
if let macroValue = macro.string(from: location) {
result = result?.replacingOccurrences(of: macro.templateKey, with: macroValue)
}
else {
result = nil
break
}
}
return result
}
}
// //
// SmartTextProvider.swift // SmartTextMacro.swift
// 1Weather // OneWeatherCore
// //
// Created by Demid Merzlyakov on 01.05.2021. // Created by Demid Merzlyakov on 06.07.2021.
// //
import Foundation import Foundation
import OneWeatherCore
private enum Macro: String {
case feelsLikeTemp = "#FEELS_LIKE_TEMP#"
case weatherType = "#WEATHER_TYPE#"
case weatherTypeChangeHour = "#WEATHER_TYPE_CHANGE_HOUR#"
case expectedPrecipitationHour = "#EXPECTED_PRECIPITATION_HOUR#"
case expectedPrecipitationProbability = "#EXPECTED_PRECIPITATION_PROBABILITY#"
case expectedPrecipitationWeatherType = "#EXPECTED_PRECIPITATION_WEATHER_TYPE#"
case windType = "#WIND_TYPE#"
case windSpeed = "#WIND_SPEED#"
case windDirection = "#WIND_DIRECTION#"
case humidityType = "#HUMIDITY_TYPE#"
case humidity = "#HUMIDITY#"
static let hourFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "h a"
return dateFormatter
}()
static func expectedPrecipitation(for location: Location) -> HourlyWeather? {
location.hourly.first { (hourly) -> Bool in
hourly.date > Date()
&& hourly.date < Date().addingTimeInterval(6 * 3600)
&& precipitationWeatherTypes.contains(hourly.type)
}
}
func windType(from windSpeed: WindSpeed) -> String? { /// A macro that can be used within a SmartText template.
let speedKPH = windSpeed.converted(to: .kilometersPerHour).value /// SmartTexts will then look for all occurances of `templateKey` in their template text, and replace it with whatever `string(from location:)` returns.
guard speedKPH >= 50 else { public protocol SmartTextMacro {
return nil var templateKey: String { get }
} func string(from location: Location) -> String?
var windType: String = "" }
if speedKPH < 62 {
windType = "moderateGale"
} /// A container struct for all SmartTextMacro, so that they didn't clutter the global namespace.
else if speedKPH < 75 { public class SmartTextMacros {
windType = "gale"
} @available(*, unavailable)
else if speedKPH < 89 { public init() {}
windType = "strongGale"
} public struct FeelsLikeTemp: SmartTextMacro {
else if speedKPH < 103 { public let templateKey = "#FEELS_LIKE_TEMP#"
windType = "storm" public func string(from location: Location) -> String? {
} location.today?.apparentTemp?.settingsConverted.shortString ?? "--"
else if speedKPH < 118 {
windType = "violentStorm"
}
else {
windType = "hurricane"
} }
return ("wind.type." + windType).localized()
} }
func string(from location: Location) -> String? { public struct WeatherType: SmartTextMacro {
switch self { public let templateKey = "#WEATHER_TYPE#"
case .feelsLikeTemp: public func string(from location: Location) -> String? {
return location.today?.apparentTemp?.settingsConverted.shortString ?? "--"
case .weatherType:
guard let today = location.today else { guard let today = location.today else {
return nil return nil
} }
return today.type.localized(isDay: today.isDay) return today.type.localized(isDay: today.isDay)
case .weatherTypeChangeHour: }
}
public struct WeatherTypeChangeHour: SmartTextMacro {
public let templateKey = "#WEATHER_TYPE_CHANGE_HOUR#"
public func string(from location: Location) -> String? {
guard let today = location.today else { guard let today = location.today else {
return nil return nil
} }
...@@ -81,174 +51,142 @@ private enum Macro: String { ...@@ -81,174 +51,142 @@ private enum Macro: String {
&& hourly.date > Date() && hourly.date > Date()
&& hourly.date < Date().addingTimeInterval(6 * 3600) && hourly.date < Date().addingTimeInterval(6 * 3600)
}) { }) {
let dateFormatter = Macro.hourFormatter let dateFormatter = SmartTextMacros.hourFormatter
dateFormatter.timeZone = location.timeZone dateFormatter.timeZone = location.timeZone
return dateFormatter.string(from: firstDifferentHour.date) return dateFormatter.string(from: firstDifferentHour.date)
} }
else { else {
return nil return nil
} }
case .expectedPrecipitationHour: }
guard let expectedPrecipitationHour = Macro.expectedPrecipitation(for: location) else { }
public struct ExpectedPrecipitationHour: SmartTextMacro {
public let templateKey = "#EXPECTED_PRECIPITATION_HOUR#"
public func string(from location: Location) -> String? {
guard let expectedPrecipitationHour = SmartTextMacros.expectedPrecipitation(for: location) else {
return nil return nil
} }
let dateFormatter = Macro.hourFormatter let dateFormatter = SmartTextMacros.hourFormatter
dateFormatter.timeZone = location.timeZone dateFormatter.timeZone = location.timeZone
return dateFormatter.string(from: expectedPrecipitationHour.date) return dateFormatter.string(from: expectedPrecipitationHour.date)
case .expectedPrecipitationProbability: }
guard let expectedPrecipitationHour = Macro.expectedPrecipitation(for: location) else { }
public struct ExpectedPrecipitationProbability: SmartTextMacro {
public let templateKey = "#EXPECTED_PRECIPITATION_PROBABILITY#"
public func string(from location: Location) -> String? {
guard let expectedPrecipitationHour = SmartTextMacros.expectedPrecipitation(for: location) else {
return nil return nil
} }
guard let precipitationProbability = expectedPrecipitationHour.precipitationProbability else { guard let precipitationProbability = expectedPrecipitationHour.precipitationProbability else {
return nil return nil
} }
return "\(precipitationProbability)" return "\(precipitationProbability)"
case .expectedPrecipitationWeatherType: }
guard let expectedPrecipitationHour = Macro.expectedPrecipitation(for: location) else { }
public struct ExpectedPrecipitationWeatherType: SmartTextMacro {
public let templateKey = "#EXPECTED_PRECIPITATION_WEATHER_TYPE#"
public func string(from location: Location) -> String? {
guard let expectedPrecipitationHour = SmartTextMacros.expectedPrecipitation(for: location) else {
return nil return nil
} }
return expectedPrecipitationHour.type.localized(isDay: expectedPrecipitationHour.isDay) return expectedPrecipitationHour.type.localized(isDay: expectedPrecipitationHour.isDay)
case .windType: }
}
public struct WindType: SmartTextMacro {
public let templateKey = "#WIND_TYPE#"
public func string(from location: Location) -> String? {
guard let windSpeed = location.today?.windSpeed else { guard let windSpeed = location.today?.windSpeed else {
return nil return nil
} }
return windType(from: windSpeed) return windType(from: windSpeed)
case .windSpeed:
return location.today?.windSpeed?.settingsConverted.string
case .windDirection:
return location.today?.windDirection?.fullLocalized
case .humidityType:
guard let humidity = location.today?.humidity else {
return nil
} }
if humidity > 50 {
return "humidity.type.high".localized() private func windType(from windSpeed: OneWeatherCore.WindSpeed) -> String? {
} let speedKPH = windSpeed.converted(to: .kilometersPerHour).value
else if humidity < 30 { guard speedKPH >= 50 else {
return "humidity.type.low".localized()
}
else {
return nil return nil
} }
case .humidity: var windType: String = ""
guard let humidity = location.today?.humidity else { if speedKPH < 62 {
return nil windType = "moderateGale"
} }
return "\(humidity)" else if speedKPH < 75 {
windType = "gale"
} }
else if speedKPH < 89 {
windType = "strongGale"
} }
} else if speedKPH < 103 {
windType = "storm"
fileprivate let precipitationWeatherTypes = Set<WeatherType>([.snowy, .thunderstorm, .heavySnow, .lightSnow, .freezingRain, .lightHail, .lightDrizzle, .heavyRain]) }
else if speedKPH < 118 {
fileprivate protocol SmartText { windType = "violentStorm"
var templateKey: String { get }
var requiredMacros: [Macro] { get }
func applicable(to location: Location) -> Bool
func buildText(for location: Location) -> String?
}
fileprivate extension SmartText {
func buildText(for location: Location) -> String? {
var result: String? = self.templateKey.localized()
for macro in requiredMacros {
if let macroValue = macro.string(from: location) {
result = result?.replacingOccurrences(of: macro.rawValue, with: macroValue)
} }
else { else {
result = nil windType = "hurricane"
break
} }
return ("wind.type." + windType).localized()
} }
return result
} }
}
fileprivate struct DefaultSmartText: SmartText {
let templateKey = "today.smart.default"
let requiredMacros: [Macro] = [.feelsLikeTemp]
func applicable(to location: Location) -> Bool { public struct WindSpeed: SmartTextMacro {
true public let templateKey = "#WIND_SPEED#"
public func string(from location: Location) -> String? {
location.today?.windSpeed?.settingsConverted.string
}
} }
}
fileprivate struct OngoingPrecipitationSmartText: SmartText {
let templateKey: String = "today.smart.ongoingPrecipitation"
let requiredMacros: [Macro] = [.weatherType, .weatherTypeChangeHour]
func applicable(to location: Location) -> Bool { public struct WindDirection: SmartTextMacro {
guard let today = location.today else { public let templateKey = "#WIND_DIRECTION#"
return false public func string(from location: Location) -> String? {
location.today?.windDirection?.fullLocalized
} }
return precipitationWeatherTypes.contains(today.type)
} }
}
fileprivate struct ApproachingPrecipitationSmartText: SmartText {
let templateKey: String = "today.smart.approachingPrecipitation"
let requiredMacros: [Macro] = [.expectedPrecipitationHour, .expectedPrecipitationProbability, .expectedPrecipitationWeatherType]
func applicable(to location: Location) -> Bool { public struct HumidityType: SmartTextMacro {
guard let today = location.today else { public let templateKey = "#HUMIDITY_TYPE#"
return false public func string(from location: Location) -> String? {
guard let humidity = location.today?.humidity else {
return nil
} }
guard !precipitationWeatherTypes.contains(today.type) else { if humidity > 50 {
return false return "humidity.type.high".localized()
} }
return Macro.expectedPrecipitation(for: location) != nil else if humidity < 30 {
return "humidity.type.low".localized()
} }
} else {
return nil
fileprivate struct WindSmartText: SmartText {
let templateKey: String = "today.smart.wind"
let requiredMacros: [Macro] = [.windType, .windSpeed, .windDirection]
let minAllowedWindSpeed: WindSpeed
func applicable(to location: Location) -> Bool {
guard let today = location.today else {
return false
} }
guard let windSpeed = today.windSpeed, let _ = today.windDirection else {
return false
} }
return windSpeed >= minAllowedWindSpeed
} }
}
fileprivate struct HumiditySmartText: SmartText { public struct Humidity: SmartTextMacro {
let templateKey: String = "today.smart.humidity" public let templateKey = "#HUMIDITY#"
let requiredMacros: [Macro] = [.feelsLikeTemp, .humidity, .humidityType] public func string(from location: Location) -> String? {
func applicable(to location: Location) -> Bool {
guard let humidity = location.today?.humidity else { guard let humidity = location.today?.humidity else {
return false return nil
}
return "\(humidity)"
} }
return humidity > 50 || humidity < 30
} }
}
class SmartTextProvider {
private var prioritizedSmartTexts: [SmartText] = [
OngoingPrecipitationSmartText(),
ApproachingPrecipitationSmartText(),
WindSmartText(minAllowedWindSpeed: WindSpeed(value: 75, unit: .kilometersPerHour)),
HumiditySmartText(),
WindSmartText(minAllowedWindSpeed: WindSpeed(value: 50, unit: .kilometersPerHour)),
DefaultSmartText()] /// Helper tools
public static let hourFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "h a"
return dateFormatter
}()
public func smartText(for location: Location) -> String { public static func expectedPrecipitation(for location: Location) -> HourlyWeather? {
var result = "" location.hourly.first { (hourly) -> Bool in
for candidate in prioritizedSmartTexts { hourly.date > Date()
if candidate.applicable(to: location) { && hourly.date < Date().addingTimeInterval(6 * 3600)
if let candidateText = candidate.buildText(for: location) { && hourly.type.isPrecipitation
result = candidateText
break
}
}
} }
return result
} }
} }
//
// SmartTextProvider.swift
// 1Weather
//
// Created by Demid Merzlyakov on 01.05.2021.
//
import Foundation
public class SmartTextProvider {
private var prioritizedSmartTexts: [SmartText] = [
OngoingPrecipitationSmartText(),
ApproachingPrecipitationSmartText(),
WindSmartText(minAllowedWindSpeed: WindSpeed(value: 75, unit: .kilometersPerHour)),
HumiditySmartText(),
WindSmartText(minAllowedWindSpeed: WindSpeed(value: 50, unit: .kilometersPerHour)),
DefaultSmartText()]
public init() {
}
public func smartText(for location: Location) -> String {
var result = ""
for candidate in prioritizedSmartTexts {
if candidate.applicable(to: location) {
if let candidateText = candidate.buildText(for: location) {
result = candidateText
break
}
}
}
return result
}
}
//
// ApproachingPrecipitationSmartText.swift
// OneWeatherCore
//
// Created by Demid Merzlyakov on 06.07.2021.
//
import Foundation
public struct ApproachingPrecipitationSmartText: SmartText {
public let templateKey: String = "today.smart.approachingPrecipitation"
public let requiredMacros: [SmartTextMacro] = [SmartTextMacros.ExpectedPrecipitationHour(), SmartTextMacros.ExpectedPrecipitationProbability(), SmartTextMacros.ExpectedPrecipitationWeatherType()]
public func applicable(to location: Location) -> Bool {
guard let today = location.today else {
return false
}
guard !today.type.isPrecipitation else {
return false
}
return SmartTextMacros.expectedPrecipitation(for: location) != nil
}
}
//
// DefaultSmartText.swift
// OneWeatherCore
//
// Created by Demid Merzlyakov on 06.07.2021.
//
import Foundation
/// A smart text that is always applicable. It can be used to provide default text.
public struct DefaultSmartText: SmartText {
public let templateKey = "today.smart.default"
public let requiredMacros: [SmartTextMacro] = [SmartTextMacros.FeelsLikeTemp()]
public func applicable(to location: Location) -> Bool {
true
}
}
//
// HumiditySmartText.swift
// OneWeatherCore
//
// Created by Demid Merzlyakov on 06.07.2021.
//
import Foundation
public struct HumiditySmartText: SmartText {
public let templateKey: String = "today.smart.humidity"
public let requiredMacros: [SmartTextMacro] = [SmartTextMacros.FeelsLikeTemp(), SmartTextMacros.Humidity(), SmartTextMacros.HumidityType()]
public func applicable(to location: Location) -> Bool {
guard let humidity = location.today?.humidity else {
return false
}
return humidity > 50 || humidity < 30
}
}
//
// OngoingPrecipitationSmartText.swift
// OneWeatherCore
//
// Created by Demid Merzlyakov on 06.07.2021.
//
import Foundation
public struct OngoingPrecipitationSmartText: SmartText {
public let templateKey: String = "today.smart.ongoingPrecipitation"
public let requiredMacros: [SmartTextMacro] = [SmartTextMacros.WeatherType(), SmartTextMacros.WeatherTypeChangeHour()]
public func applicable(to location: Location) -> Bool {
guard let today = location.today else {
return false
}
return today.type.isPrecipitation
}
}
//
// WindSmartText.swift
// OneWeatherCore
//
// Created by Demid Merzlyakov on 06.07.2021.
//
import Foundation
public struct WindSmartText: SmartText {
public let templateKey: String = "today.smart.wind"
public let requiredMacros: [SmartTextMacro] = [SmartTextMacros.WindType(), SmartTextMacros.WindSpeed(), SmartTextMacros.WindDirection()]
let minAllowedWindSpeed: WindSpeed
public func applicable(to location: Location) -> Bool {
guard let today = location.today else {
return false
}
guard let windSpeed = today.windSpeed, let _ = today.windDirection else {
return false
}
return windSpeed >= minAllowedWindSpeed
}
}
...@@ -55,7 +55,7 @@ public struct MediumTemperatureWidgetView: View { ...@@ -55,7 +55,7 @@ public struct MediumTemperatureWidgetView: View {
.padding(.top, 12) .padding(.top, 12)
HStack(alignment: .top) { HStack(alignment: .top) {
Text("SmartText") Text(widgetViewModel.smartText)
.font(WidgetFont.SFProDisplay.regular(size: 12).font) .font(WidgetFont.SFProDisplay.regular(size: 12).font)
Spacer() Spacer()
......
...@@ -18,4 +18,5 @@ public protocol WidgetViewModel { ...@@ -18,4 +18,5 @@ public protocol WidgetViewModel {
var highTemperature: String { get } var highTemperature: String { get }
var lowTemperature: String { get } var lowTemperature: String { get }
var isDeviceLocation: Bool { get } var isDeviceLocation: Bool { get }
var smartText: String { get }
} }
...@@ -10,8 +10,8 @@ import UIKit ...@@ -10,8 +10,8 @@ import UIKit
@available(iOS 14, *) @available(iOS 14, *)
struct WidgetViewModelMock: WidgetViewModel { struct WidgetViewModelMock: WidgetViewModel {
private class OneWeatherUIClass {} private class OneWeatherUIClass {}
let showLastTimeUpdated = false let showLastTimeUpdated = true
let lastTimeUpdatedText = "Last update text" let lastTimeUpdatedText = "Last updated 2h ago."
let cityName = "New York" let cityName = "New York"
let temperature = "96°" let temperature = "96°"
let weatherType = "Partly Cloudy" let weatherType = "Partly Cloudy"
...@@ -19,4 +19,5 @@ struct WidgetViewModelMock: WidgetViewModel { ...@@ -19,4 +19,5 @@ struct WidgetViewModelMock: WidgetViewModel {
let highTemperature = "97°" let highTemperature = "97°"
let lowTemperature = "89°" let lowTemperature = "89°"
let isDeviceLocation = true let isDeviceLocation = true
let smartText = "Feels like 101° due to high humidity (77%)."
} }
...@@ -17,16 +17,6 @@ import CoreDataStorage ...@@ -17,16 +17,6 @@ import CoreDataStorage
class WeatherProvider: TimelineProvider { class WeatherProvider: TimelineProvider {
typealias Entry = WeatherEntry typealias Entry = WeatherEntry
private lazy var manager: LocationManager = {
LocationManager.shared = LocationManager(weatherUpdateSource: WdtWeatherSource(),
healthSource: BlendHealthSource(),
nwsAlertsManager: NWSAlertsManager(),
fipsSource: BlendFIPSSource(),
pushNotificationsManager: nil,
storage: DelayedSaveStorage(storage: CoreDataStorage(), delay: 2))
return LocationManager.shared
}()
func placeholder(in context: Context) -> WeatherEntry { func placeholder(in context: Context) -> WeatherEntry {
return WeatherEntry() return WeatherEntry()
} }
...@@ -35,13 +25,45 @@ class WeatherProvider: TimelineProvider { ...@@ -35,13 +25,45 @@ class WeatherProvider: TimelineProvider {
let entry = WeatherEntry() let entry = WeatherEntry()
completion(entry) completion(entry)
} }
var storage: Storage = CoreDataStorage()
var weatherSource: WeatherSource = WdtWeatherSource()
func getTimeline(in context: Context, completion: @escaping (Timeline<WeatherEntry>) -> Void) { func isFreshEnough(_ location: Location) -> Bool {
guard let currentLocation = manager.selectedLocation else { return } guard let lastTimeUpdated = location.lastWeatherUpdateDate else {
return false
}
return Date().timeIntervalSince(lastTimeUpdated) < self.weatherSource.weatherUpdateInterval
}
func getUpToDateLocation(_ completion: @escaping (Location?) -> () ) {
storage.load { [weak self] (locations, selectedIndex, error) in
guard let self = self else {
completion(nil)
return
}
guard let locations = locations, let selectedIndex = selectedIndex, selectedIndex < locations.count else {
completion(nil)
return
}
let selectedLocation = locations[selectedIndex]
manager.updateWeather(for: currentLocation, updateType: .preferIncremental) { updatedLocation in guard !self.isFreshEnough(selectedLocation) else {
guard let location = updatedLocation else { return } completion(selectedLocation)
return
}
self.weatherSource.updateWeather(for: selectedLocation, type: .preferIncremental) { updatedLocation, error in
guard let updatedLocation = updatedLocation else {
completion(selectedLocation)
return
}
completion(updatedLocation)
}
}
}
func getTimeline(in context: Context, completion: @escaping (Timeline<WeatherEntry>) -> Void) {
getUpToDateLocation { location in
let nextRefresh = Calendar.current.date(byAdding: .minute, value: 30, to: Date())! let nextRefresh = Calendar.current.date(byAdding: .minute, value: 30, to: Date())!
let entry = WeatherEntry(location: location, date: nextRefresh) let entry = WeatherEntry(location: location, date: nextRefresh)
let timeline = Timeline(entries: [entry], policy: .atEnd) let timeline = Timeline(entries: [entry], policy: .atEnd)
......
...@@ -19,6 +19,7 @@ struct ForecastWidgetViewModel: WidgetViewModel { ...@@ -19,6 +19,7 @@ struct ForecastWidgetViewModel: WidgetViewModel {
let highTemperature: String let highTemperature: String
let lowTemperature: String let lowTemperature: String
let isDeviceLocation: Bool let isDeviceLocation: Bool
var smartText: String
init(location: Location) { init(location: Location) {
self.cityName = location.cityName ?? "--" self.cityName = location.cityName ?? "--"
...@@ -47,5 +48,7 @@ struct ForecastWidgetViewModel: WidgetViewModel { ...@@ -47,5 +48,7 @@ struct ForecastWidgetViewModel: WidgetViewModel {
self.showLastTimeUpdated = false self.showLastTimeUpdated = false
lastTimeUpdatedText = "" lastTimeUpdatedText = ""
} }
let smartTextProvider = SmartTextProvider()
self.smartText = smartTextProvider.smartText(for: location)
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment