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 @@
CE5F0CBA268A02C100B99572 /* MediumTemperatureWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5F0CB9268A02C100B99572 /* MediumTemperatureWidget.swift */; };
CE5F0CBC268A031800B99572 /* OneWeatherWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5F0CBB268A031800B99572 /* OneWeatherWidgetsBundle.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 */; };
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 */; };
......@@ -519,7 +518,6 @@
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>"; };
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>"; };
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>"; };
......@@ -1348,7 +1346,6 @@
CE13B76126246743007CBD4D /* CCPA */,
CE6BE4932634170800626822 /* USStateCode.swift */,
CE895F0E26393FD800214175 /* WeatherImageProvider.swift */,
CE6F5F0B263C8B3C00973137 /* SmartTextProvider.swift */,
);
path = Model;
sourceTree = "<group>";
......@@ -1896,7 +1893,6 @@
CD17C5F625D15B4400EE884E /* TodayViewController.swift in Sources */,
CD86245E25E646350097F3FB /* SunUvView.swift in Sources */,
CDF8F12D26208E7B00DB384A /* MapCurrentTimeView.swift in Sources */,
CE6F5F0C263C8B3D00973137 /* SmartTextProvider.swift in Sources */,
CEC7D8EE2639FE2700B8836D /* OLInAppStoreManager.swift in Sources */,
CD82300725D6A73F00A05501 /* TodayConditionButton.swift in Sources */,
CDC6126A25E90C8800188DA7 /* GraphLineSettings.swift in Sources */,
......
......@@ -80,8 +80,8 @@
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = "com.outlouder.oneweather.widget"
isEnabled = "NO">
value = "com.onelouder.oneweather.widget.small.temperature"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
......
......@@ -9,6 +9,7 @@ import Foundation
import CoreData
import OneWeatherCore
import OneWeatherAnalytics
import WidgetKit
public class CoreDataStorage: Storage {
private let modelName = "1WModel"
......@@ -92,6 +93,11 @@ public class CoreDataStorage: Storage {
context.insert(coreAppData)
try self.save(context: context)
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")
}
......
......@@ -75,6 +75,7 @@ public class DelayedSaveStorage: Storage {
self?.latestKnownAppData = nil
}
}
self?.saveQueue.addOperation(saveOperation)
}
saveDelayQueue.addOperation(saveWithDelayOperation)
}
......@@ -106,5 +107,6 @@ public class DelayedSaveStorage: Storage {
self?.latestKnownAppDataSynchronizationQueue.waitUntilAllOperationsAreFinished()
}
self.saveQueue.waitUntilAllOperationsAreFinished()
self.latestKnownAppDataSynchronizationQueue.waitUntilAllOperationsAreFinished()
}
}
......@@ -82,6 +82,14 @@
CDFE458D26566BD50021A29F /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFE458C26566BD50021A29F /* Storage.swift */; };
CDFE459426566D7B0021A29F /* HealthSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDFE459326566D7B0021A29F /* HealthSource.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 */
/* Begin PBXContainerItemProxy section */
......@@ -180,6 +188,14 @@
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>"; };
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; };
/* End PBXFileReference section */
......@@ -414,6 +430,7 @@
CDD2F8ED2665100F00B48322 /* Model */ = {
isa = PBXGroup;
children = (
CEFE851926949844003C67D3 /* SmartTexts */,
CDD2F8EE2665102B00B48322 /* LocationManager.swift */,
CDD2F8F02665112800B48322 /* DeviceLocationMonitor.swift */,
);
......@@ -451,6 +468,29 @@
path = Sources;
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 */ = {
isa = PBXGroup;
children = (
......@@ -600,8 +640,10 @@
buildActionMask = 2147483647;
files = (
CD3883972657AFE00070FD6F /* Settings.swift in Sources */,
CEFE85242694C5D1003C67D3 /* ApproachingPrecipitationSmartText.swift in Sources */,
CD2D55DA26553751007B70F4 /* NWSAlert.swift in Sources */,
CD11AFE526651BCD00EC4BA0 /* LegacySettings.swift in Sources */,
CEFE85262694C5E4003C67D3 /* WindSmartText.swift in Sources */,
CD11AFED26651E0A00EC4BA0 /* WdtWeatherSourceError.swift in Sources */,
CD2D55D526553384007B70F4 /* UserDefaultsOptionalValue.swift in Sources */,
CD11AFE126651A4700EC4BA0 /* NWSAlertInfoParser.swift in Sources */,
......@@ -613,10 +655,12 @@
CD550FBC265531A100257FB5 /* WeatherLayerType.swift in Sources */,
CD550FBD265531A100257FB5 /* SevereLayerType.swift in Sources */,
CD91685826552FD000EC04EF /* DefaultSettingsFactory.swift in Sources */,
CEFE851C2694986D003C67D3 /* SmartText.swift in Sources */,
CDFE459626566D860021A29F /* FIPSSource.swift in Sources */,
CD91685926552FD000EC04EF /* DefaultSettings.swift in Sources */,
CD91685A26552FD000EC04EF /* DefaultSettingsImperial.swift in Sources */,
CD91685B26552FD000EC04EF /* DefaultSettingsMetric.swift in Sources */,
CEFE85282694C5F7003C67D3 /* HumiditySmartText.swift in Sources */,
CD615FB82655295C00B717DB /* UIColor+Hex.swift in Sources */,
CD2D55E0265537DC007B70F4 /* NWSAlertInfoBlock.swift in Sources */,
CD91685726552FAE00EC04EF /* MulticastDelegate.swift in Sources */,
......@@ -638,22 +682,26 @@
CD615FC42655295C00B717DB /* UnitPressure+Atmosphere.swift in Sources */,
CD615FC52655295C00B717DB /* CLAuthorizationStatus+Localized.swift in Sources */,
CD615FC62655295C00B717DB /* UIApplication+Settings.swift in Sources */,
CEFE85202694C4BC003C67D3 /* DefaultSmartText.swift in Sources */,
CD8E48A526651414008E7F8D /* NWSCurrentEventsReponse.swift in Sources */,
CD2D55D8265533F4007B70F4 /* UserDefaultsWrapper.swift in Sources */,
CD615FC72655295C00B717DB /* UITabBarController+Hide.swift in Sources */,
CDD2F8F62665117400B48322 /* NWSAlertsManager.swift in Sources */,
CD11AFE726651BF900EC4BA0 /* LegacyWdtLocation.swift in Sources */,
CD615FC82655295C00B717DB /* CACornerMask+All.swift in Sources */,
CEFE851E2694C477003C67D3 /* SmartTextMacro.swift in Sources */,
CD11AFE326651B6300EC4BA0 /* LegacyMigrationManager.swift in Sources */,
CD2D55DD2655377F007B70F4 /* NWSAlertExtendedInfo.swift in Sources */,
CD615FC92655295C00B717DB /* UIDevice+Convenience.swift in Sources */,
CD615F95265526E700B717DB /* UpdatableModelObject.swift in Sources */,
CD615FA4265528F000B717DB /* HelperTypes.swift in Sources */,
CD3883EC2657B83D0070FD6F /* FIPSResponse.swift in Sources */,
CEFE85222694C57A003C67D3 /* OngoingPrecipitationSmartText.swift in Sources */,
CD615F96265526E700B717DB /* UpdatableModelObjectInTime.swift in Sources */,
CD615F97265526E700B717DB /* PartialLocation.swift in Sources */,
CD615F98265526E700B717DB /* GeoNamesPlace.swift in Sources */,
CD615F99265526E700B717DB /* Location.swift in Sources */,
CEFE851826948C15003C67D3 /* SmartTextProvider.swift in Sources */,
CD11AFE926651C9200EC4BA0 /* StuffThatIsPresentInTheMainProject.swift in Sources */,
CDFE459426566D7B0021A29F /* HealthSource.swift in Sources */,
CD615F9A265526E700B717DB /* CurrentWeather.swift in Sources */,
......
......@@ -62,6 +62,11 @@ public enum WeatherType: String, CaseIterable {
}
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 {
......
......@@ -428,8 +428,9 @@ public class LocationManager {
if let updatedLocation = updatedLocation {
self.log.info("Update weather finished for \(location)")
self.makeChanges(to: location, in: "weather") { (oldLocation) -> Location in
completion?(updatedLocation)
return oldLocation.mergedWith(incrementalChanges: updatedLocation)
let merged = oldLocation.mergedWith(incrementalChanges: updatedLocation)
completion?(merged)
return merged
}
}
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
// 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 {
.padding(.top, 12)
HStack(alignment: .top) {
Text("SmartText")
Text(widgetViewModel.smartText)
.font(WidgetFont.SFProDisplay.regular(size: 12).font)
Spacer()
......
......@@ -18,4 +18,5 @@ public protocol WidgetViewModel {
var highTemperature: String { get }
var lowTemperature: String { get }
var isDeviceLocation: Bool { get }
var smartText: String { get }
}
......@@ -10,8 +10,8 @@ import UIKit
@available(iOS 14, *)
struct WidgetViewModelMock: WidgetViewModel {
private class OneWeatherUIClass {}
let showLastTimeUpdated = false
let lastTimeUpdatedText = "Last update text"
let showLastTimeUpdated = true
let lastTimeUpdatedText = "Last updated 2h ago."
let cityName = "New York"
let temperature = "96°"
let weatherType = "Partly Cloudy"
......@@ -19,4 +19,5 @@ struct WidgetViewModelMock: WidgetViewModel {
let highTemperature = "97°"
let lowTemperature = "89°"
let isDeviceLocation = true
let smartText = "Feels like 101° due to high humidity (77%)."
}
......@@ -17,16 +17,6 @@ import CoreDataStorage
class WeatherProvider: TimelineProvider {
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 {
return WeatherEntry()
}
......@@ -35,13 +25,45 @@ class WeatherProvider: TimelineProvider {
let entry = WeatherEntry()
completion(entry)
}
var storage: Storage = CoreDataStorage()
var weatherSource: WeatherSource = WdtWeatherSource()
func getTimeline(in context: Context, completion: @escaping (Timeline<WeatherEntry>) -> Void) {
guard let currentLocation = manager.selectedLocation else { return }
manager.updateWeather(for: currentLocation, updateType: .preferIncremental) { updatedLocation in
guard let location = updatedLocation else { return }
func isFreshEnough(_ location: Location) -> Bool {
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]
guard !self.isFreshEnough(selectedLocation) else {
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 entry = WeatherEntry(location: location, date: nextRefresh)
let timeline = Timeline(entries: [entry], policy: .atEnd)
......
......@@ -19,6 +19,7 @@ struct ForecastWidgetViewModel: WidgetViewModel {
let highTemperature: String
let lowTemperature: String
let isDeviceLocation: Bool
var smartText: String
init(location: Location) {
self.cityName = location.cityName ?? "--"
......@@ -47,5 +48,7 @@ struct ForecastWidgetViewModel: WidgetViewModel {
self.showLastTimeUpdated = false
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