Commit c3e394ce by Demid Merzlyakov

Merge branch 'feature/ios-183-minutely-smart-text' into release/5.4.1

# Conflicts:
#	1Weather/UI/SharedViews/MinutelyForecastView/MinutelyForecastView.swift
parents 9432979a 76edf32f
{
"images" : [
{
"filename" : "bitmapCopy.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "bitmapCopy@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "bitmapCopy@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "bitmapCopy20.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "bitmapCopy20@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "bitmapCopy20@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
"today.smart.approachingPrecipitation" = "#EXPECTED_PRECIPITATION_PROBABILITY#% chance of #EXPECTED_PRECIPITATION_WEATHER_TYPE# by #EXPECTED_PRECIPITATION_HOUR#."; "today.smart.approachingPrecipitation" = "#EXPECTED_PRECIPITATION_PROBABILITY#% chance of #EXPECTED_PRECIPITATION_WEATHER_TYPE# by #EXPECTED_PRECIPITATION_HOUR#.";
"today.smart.wind" = "#WIND_TYPE# - #WIND_SPEED# blowing from #WIND_DIRECTION#."; "today.smart.wind" = "#WIND_TYPE# - #WIND_SPEED# blowing from #WIND_DIRECTION#.";
"today.smart.humidity" = "Feels like #FEELS_LIKE_TEMP# due to #HUMIDITY_TYPE# humidity (#HUMIDITY#%)."; "today.smart.humidity" = "Feels like #FEELS_LIKE_TEMP# due to #HUMIDITY_TYPE# humidity (#HUMIDITY#%).";
"today.minutely.smart.hottestDay" = "Today is the hottest day of this week";
//Forecast //Forecast
"forecast.sunny" = "Sunny"; "forecast.sunny" = "Sunny";
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
import UIKit import UIKit
import OneWeatherCore import OneWeatherCore
import Accelerate import Accelerate
import SnapKit
enum MinutelyForecastType { enum MinutelyForecastType {
case temperature case temperature
...@@ -16,6 +17,9 @@ enum MinutelyForecastType { ...@@ -16,6 +17,9 @@ enum MinutelyForecastType {
private let kTemperatureColors = [UIColor(hex: 0xff934f), UIColor(hex: 0xff414a)] private let kTemperatureColors = [UIColor(hex: 0xff934f), UIColor(hex: 0xff414a)]
private let kPrecipitationColors = [UIColor(hex: 0x2d99ff), UIColor(hex: 0x8fc6fb)] private let kPrecipitationColors = [UIColor(hex: 0x2d99ff), UIColor(hex: 0x8fc6fb)]
private let kSmartTextBackgroundColorTemperature = UIColor(hex: 0xfaedda80)
private let kSmartTextBackgroundColorPrecipitation = UIColor(hex: 0xd9ebfe)
private let kSmartTextAreaHeight = CGFloat(40)
private class MinutelyLevelView: UIView { private class MinutelyLevelView: UIView {
private let gradient = CAGradientLayer() private let gradient = CAGradientLayer()
...@@ -118,6 +122,10 @@ class MinutelyForecastView: UIView { ...@@ -118,6 +122,10 @@ class MinutelyForecastView: UIView {
private let levelsStackView = UIStackView() private let levelsStackView = UIStackView()
private let verticalStackView = UIStackView() private let verticalStackView = UIStackView()
private let scrollView = UIScrollView() private let scrollView = UIScrollView()
private let smartTextBackgroundView = UIView()
private var smartTextBackgroundViewHeightContraint: Constraint?
private let smartTextLabel = UILabel()
private let smartTextIcon = UIImageView()
private let centerDashline = CAShapeLayer() private let centerDashline = CAShapeLayer()
private var levelsDashline = [CAShapeLayer]() private var levelsDashline = [CAShapeLayer]()
private let feedbackGenerator = UISelectionFeedbackGenerator() private let feedbackGenerator = UISelectionFeedbackGenerator()
...@@ -127,6 +135,8 @@ class MinutelyForecastView: UIView { ...@@ -127,6 +135,8 @@ class MinutelyForecastView: UIView {
private var minutelyForecast = [MinutelyItem]() private var minutelyForecast = [MinutelyItem]()
private var timer: Timer? private var timer: Timer?
private var forecastType = MinutelyForecastType.temperature private var forecastType = MinutelyForecastType.temperature
private var smartTextProvider: SmartTextProvider = SmartTextProvider(prioritizedSmartTexts: [])
private lazy var dateFormatter: DateFormatter = { private lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.dateFormat = "h:mm a" formatter.dateFormat = "h:mm a"
...@@ -145,6 +155,7 @@ class MinutelyForecastView: UIView { ...@@ -145,6 +155,7 @@ class MinutelyForecastView: UIView {
prepareDetailView() prepareDetailView()
prepareCenterDashLine() prepareCenterDashLine()
prepareScrollView() prepareScrollView()
prepareSmartTextView()
prepareVerticalStackView() prepareVerticalStackView()
prepareNudgeView() prepareNudgeView()
} }
...@@ -193,6 +204,7 @@ class MinutelyForecastView: UIView { ...@@ -193,6 +204,7 @@ class MinutelyForecastView: UIView {
centerDashline.strokeColor = forecastType == .temperature ? kTemperatureColors.last?.cgColor centerDashline.strokeColor = forecastType == .temperature ? kTemperatureColors.last?.cgColor
: kPrecipitationColors.last?.cgColor : kPrecipitationColors.last?.cgColor
updateSmartText()
prepareMinutelyItems() prepareMinutelyItems()
updateChart() updateChart()
...@@ -218,6 +230,38 @@ class MinutelyForecastView: UIView { ...@@ -218,6 +230,38 @@ class MinutelyForecastView: UIView {
} }
} }
private func updateSmartText() {
smartTextBackgroundView.backgroundColor = self.forecastType == .temperature ? kSmartTextBackgroundColorTemperature : kSmartTextBackgroundColorPrecipitation
smartTextIcon.image = UIImage(named: forecastType == .temperature ? "minutely_smart_text_temperature" : "minutely_smart_text_precipitation")
let smartTextsToUse: [SmartText]
if forecastType == .temperature {
smartTextsToUse = [HottestDaySmartText()]
}
else {
smartTextsToUse = [ApproachingPrecipitationSmartText()]
}
smartTextProvider = SmartTextProvider(prioritizedSmartTexts: smartTextsToUse)
var smartText = ""
if let location = self.location {
smartText = smartTextProvider.smartText(for: location)
}
smartTextLabel.text = smartText
let newHeight: CGFloat
if smartText.isEmpty {
newHeight = 0
smartTextBackgroundView.isHidden = true
}
else {
newHeight = kSmartTextAreaHeight
smartTextBackgroundView.isHidden = false
}
smartTextBackgroundViewHeightContraint?.deactivate()
smartTextBackgroundView.snp.makeConstraints { make in
smartTextBackgroundViewHeightContraint = make.height.equalTo(newHeight).constraint
}
}
private func updateDetailsView(minutelyItem: MinutelyItem) { private func updateDetailsView(minutelyItem: MinutelyItem) {
switch forecastType { switch forecastType {
case .temperature: case .temperature:
...@@ -482,10 +526,41 @@ private extension MinutelyForecastView { ...@@ -482,10 +526,41 @@ private extension MinutelyForecastView {
make.left.equalToSuperview().inset(42) make.left.equalToSuperview().inset(42)
make.top.equalTo(detailsInfoView.snp.bottom).offset(8) make.top.equalTo(detailsInfoView.snp.bottom).offset(8)
make.right.equalToSuperview().inset(20) make.right.equalToSuperview().inset(20)
make.bottom.equalToSuperview().inset(40)
} }
} }
func prepareSmartTextView() {
smartTextBackgroundView.translatesAutoresizingMaskIntoConstraints = false
smartTextBackgroundView.layer.cornerRadius = 12
smartTextBackgroundView.clipsToBounds = true
addSubview(smartTextBackgroundView)
smartTextBackgroundView.snp.makeConstraints { make in
make.top.equalTo(scrollView.snp.bottom).offset(18)
make.left.right.equalToSuperview().inset(20)
make.bottom.equalToSuperview()
}
smartTextLabel.font = AppFont.SFPro.regular(size: 13)
smartTextLabel.numberOfLines = 0
smartTextBackgroundView.addSubview(smartTextIcon)
smartTextBackgroundView.addSubview(smartTextLabel)
smartTextIcon.snp.makeConstraints { make in
make.leading.equalToSuperview().inset(18)
make.width.height.equalTo(12)
make.centerY.equalToSuperview()
}
smartTextLabel.snp.makeConstraints { make in
make.leading.equalTo(smartTextIcon.snp.trailing).offset(7)
make.centerY.equalToSuperview()
make.trailing.lessThanOrEqualToSuperview().inset(18)
}
updateSmartText()
}
func prepareNudgeView() { func prepareNudgeView() {
addSubview(swipeNudgeView) addSubview(swipeNudgeView)
swipeNudgeView.snp.makeConstraints { make in swipeNudgeView.snp.makeConstraints { make in
......
...@@ -94,6 +94,7 @@ ...@@ -94,6 +94,7 @@
CE72A76A26D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE72A76926D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift */; }; CE72A76A26D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE72A76926D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift */; };
CE72A77026D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE72A76F26D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift */; }; CE72A77026D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE72A76F26D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift */; };
CEC363AD26E93EE1009F2607 /* ClosureFeatureAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC363AC26E93EE1009F2607 /* ClosureFeatureAvailabilityChecker.swift */; }; CEC363AD26E93EE1009F2607 /* ClosureFeatureAvailabilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC363AC26E93EE1009F2607 /* ClosureFeatureAvailabilityChecker.swift */; };
CEE798FE270DD74700218318 /* HottestDaySmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE798FD270DD74700218318 /* HottestDaySmartText.swift */; };
CEFE851826948C15003C67D3 /* SmartTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851726948C15003C67D3 /* SmartTextProvider.swift */; }; CEFE851826948C15003C67D3 /* SmartTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851726948C15003C67D3 /* SmartTextProvider.swift */; };
CEFE851C2694986D003C67D3 /* SmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851B2694986D003C67D3 /* SmartText.swift */; }; CEFE851C2694986D003C67D3 /* SmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851B2694986D003C67D3 /* SmartText.swift */; };
CEFE85202694C4BC003C67D3 /* DefaultSmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851F2694C4BC003C67D3 /* DefaultSmartText.swift */; }; CEFE85202694C4BC003C67D3 /* DefaultSmartText.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFE851F2694C4BC003C67D3 /* DefaultSmartText.swift */; };
...@@ -210,6 +211,7 @@ ...@@ -210,6 +211,7 @@
CE72A76926D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = USOnlyFeatureAvailabilityChecker.swift; sourceTree = "<group>"; }; CE72A76926D676A000F13CF7 /* USOnlyFeatureAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = USOnlyFeatureAvailabilityChecker.swift; sourceTree = "<group>"; };
CE72A76F26D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeFeatureAvailabilityChecker.swift; sourceTree = "<group>"; }; CE72A76F26D6917300F13CF7 /* DeviceTypeFeatureAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeFeatureAvailabilityChecker.swift; sourceTree = "<group>"; };
CEC363AC26E93EE1009F2607 /* ClosureFeatureAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosureFeatureAvailabilityChecker.swift; sourceTree = "<group>"; }; CEC363AC26E93EE1009F2607 /* ClosureFeatureAvailabilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosureFeatureAvailabilityChecker.swift; sourceTree = "<group>"; };
CEE798FD270DD74700218318 /* HottestDaySmartText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HottestDaySmartText.swift; sourceTree = "<group>"; };
CEFE851726948C15003C67D3 /* SmartTextProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartTextProvider.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>"; }; 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>"; }; CEFE851D2694C477003C67D3 /* SmartTextMacro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartTextMacro.swift; sourceTree = "<group>"; };
...@@ -576,6 +578,7 @@ ...@@ -576,6 +578,7 @@
CEFE85232694C5D1003C67D3 /* ApproachingPrecipitationSmartText.swift */, CEFE85232694C5D1003C67D3 /* ApproachingPrecipitationSmartText.swift */,
CEFE85252694C5E4003C67D3 /* WindSmartText.swift */, CEFE85252694C5E4003C67D3 /* WindSmartText.swift */,
CEFE85272694C5F7003C67D3 /* HumiditySmartText.swift */, CEFE85272694C5F7003C67D3 /* HumiditySmartText.swift */,
CEE798FD270DD74700218318 /* HottestDaySmartText.swift */,
); );
path = SmartTexts; path = SmartTexts;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -807,6 +810,7 @@ ...@@ -807,6 +810,7 @@
CD615F9E265526E700B717DB /* Health.swift in Sources */, CD615F9E265526E700B717DB /* Health.swift in Sources */,
CD2D55DE2655377F007B70F4 /* NWSSeverityLevel.swift in Sources */, CD2D55DE2655377F007B70F4 /* NWSSeverityLevel.swift in Sources */,
CD8B659127047ED800343897 /* LossyCodableList.swift in Sources */, CD8B659127047ED800343897 /* LossyCodableList.swift in Sources */,
CEE798FE270DD74700218318 /* HottestDaySmartText.swift in Sources */,
CEC363AD26E93EE1009F2607 /* ClosureFeatureAvailabilityChecker.swift in Sources */, CEC363AD26E93EE1009F2607 /* ClosureFeatureAvailabilityChecker.swift in Sources */,
CD615F9F265526E700B717DB /* AirQuality.swift in Sources */, CD615F9F265526E700B717DB /* AirQuality.swift in Sources */,
CDD2F8EF2665102B00B48322 /* LocationManager.swift in Sources */, CDD2F8EF2665102B00B48322 /* LocationManager.swift in Sources */,
......
...@@ -20,4 +20,6 @@ public struct ApproachingPrecipitationSmartText: SmartText { ...@@ -20,4 +20,6 @@ public struct ApproachingPrecipitationSmartText: SmartText {
} }
return SmartTextMacros.expectedPrecipitation(for: location) != nil return SmartTextMacros.expectedPrecipitation(for: location) != nil
} }
public init() {}
} }
...@@ -16,4 +16,6 @@ public struct DefaultSmartText: SmartText { ...@@ -16,4 +16,6 @@ public struct DefaultSmartText: SmartText {
public func applicable(to location: Location) -> Bool { public func applicable(to location: Location) -> Bool {
true true
} }
public init() {}
} }
//
// HottestDaySmartText.swift
// OneWeatherCore
//
// Created by Demid Merzlyakov on 06.10.2021.
//
import Foundation
public struct HottestDaySmartText: SmartText {
public let templateKey: String = "today.minutely.smart.hottestDay"
public let requiredMacros: [SmartTextMacro] = []
public func applicable(to location: Location) -> Bool {
guard let todayMaxTemp = location.today?.maxTemp else {
return false
}
let oneWeek = TimeInterval(7 * 24 * 3600)
let sevenDaysAhead = location.daily.filter { weather in
let timeUntilDate = weather.date.timeIntervalSince(Date())
return !weather.isToday
&& timeUntilDate > 0
&& timeUntilDate < oneWeek
}
guard sevenDaysAhead.count > 5 else {
return false
}
let dayHotterThanToday = sevenDaysAhead.first {
guard let maxTemp = $0.maxTemp else {
return false
}
return maxTemp > todayMaxTemp
}
return dayHotterThanToday == nil
}
public init() {}
}
...@@ -17,4 +17,6 @@ public struct HumiditySmartText: SmartText { ...@@ -17,4 +17,6 @@ public struct HumiditySmartText: SmartText {
} }
return humidity > 50 || humidity < 30 return humidity > 50 || humidity < 30
} }
public init() {}
} }
...@@ -17,4 +17,6 @@ public struct OngoingPrecipitationSmartText: SmartText { ...@@ -17,4 +17,6 @@ public struct OngoingPrecipitationSmartText: SmartText {
} }
return today.type.isPrecipitation return today.type.isPrecipitation
} }
public init() {}
} }
...@@ -21,4 +21,8 @@ public struct WindSmartText: SmartText { ...@@ -21,4 +21,8 @@ public struct WindSmartText: SmartText {
} }
return windSpeed >= minAllowedWindSpeed return windSpeed >= minAllowedWindSpeed
} }
public init(minAllowedWindSpeed: WindSpeed) {
self.minAllowedWindSpeed = minAllowedWindSpeed
}
} }
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