Commit 0f90e48f by Dmitry Stepanets

[IOS-172]: Added precipitation type to minutely forecast

parent 7f340d29
...@@ -19,4 +19,3 @@ class MinutelyForecastCell: UITableViewCell { ...@@ -19,4 +19,3 @@ class MinutelyForecastCell: UITableViewCell {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
} }
...@@ -12,8 +12,11 @@ class PrecipitationCell: UITableViewCell { ...@@ -12,8 +12,11 @@ class PrecipitationCell: UITableViewCell {
//Private //Private
private let headingLabel = UILabel() private let headingLabel = UILabel()
private let headingButton = ArrowButton() private let headingButton = ArrowButton()
private let minutelyForecastView = MinutelyForecastView()
private let scrollView = UIScrollView() private let scrollView = UIScrollView()
private let stackView = UIStackView() private let stackView = UIStackView()
private let periodSegmentedControl = ForecastTimePeriodControl(items: ["forecast.timePeriod.daily".localized(),
"forecast.timePeriod.minutely".localized()])
private let descriptionView = ForecastDescriptionView(lightStyleBackgroundColor: UIColor(hex: 0xd9ebfe), private let descriptionView = ForecastDescriptionView(lightStyleBackgroundColor: UIColor(hex: 0xd9ebfe),
gradientColors: [UIColor(hex: 0x44a4ff).withAlphaComponent(0.65).cgColor, gradientColors: [UIColor(hex: 0x44a4ff).withAlphaComponent(0.65).cgColor,
UIColor(hex: 0x73bbff).withAlphaComponent(0).cgColor]) UIColor(hex: 0x73bbff).withAlphaComponent(0).cgColor])
...@@ -23,6 +26,8 @@ class PrecipitationCell: UITableViewCell { ...@@ -23,6 +26,8 @@ class PrecipitationCell: UITableViewCell {
prepareCell() prepareCell()
prepareHeading() prepareHeading()
prepareSegmentControl()
prepareMinutelyForecastView()
prepareScrollView() prepareScrollView()
prepareStackView() prepareStackView()
prepareSummaryView() prepareSummaryView()
...@@ -32,9 +37,11 @@ class PrecipitationCell: UITableViewCell { ...@@ -32,9 +37,11 @@ class PrecipitationCell: UITableViewCell {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
public func configure(with dayily:[DailyWeather]) { public func configure(with dayily:[DailyWeather], location: Location) {
//TODO: Hide button for now //TODO: Hide button for now
headingButton.isHidden = true headingButton.isHidden = true
minutelyForecastView.configure(with: location, forecastType: .precipitation)
if stackView.arrangedSubviews.count != dayily.count { if stackView.arrangedSubviews.count != dayily.count {
let diff = stackView.arrangedSubviews.count - dayily.count let diff = stackView.arrangedSubviews.count - dayily.count
...@@ -69,12 +76,14 @@ class PrecipitationCell: UITableViewCell { ...@@ -69,12 +76,14 @@ class PrecipitationCell: UITableViewCell {
} }
} }
public func configure(with hourly:[HourlyWeather]) { public func configure(with hourly:[HourlyWeather], location: Location) {
self.headingLabel.font = AppFont.SFPro.bold(size: 18) self.headingLabel.font = AppFont.SFPro.bold(size: 18)
self.headingButton.isHidden = true self.headingButton.isHidden = true
self.headingLabel.text = "precipitation.title".localized().capitalized self.headingLabel.text = "precipitation.title".localized().capitalized
self.headingLabel.textColor = ThemeManager.currentTheme.primaryTextColor self.headingLabel.textColor = ThemeManager.currentTheme.primaryTextColor
minutelyForecastView.configure(with: location, forecastType: .precipitation)
if stackView.arrangedSubviews.count != hourly.count { if stackView.arrangedSubviews.count != hourly.count {
let diff = stackView.arrangedSubviews.count - hourly.count let diff = stackView.arrangedSubviews.count - hourly.count
for _ in 0..<abs(diff) { for _ in 0..<abs(diff) {
...@@ -120,6 +129,17 @@ class PrecipitationCell: UITableViewCell { ...@@ -120,6 +129,17 @@ class PrecipitationCell: UITableViewCell {
} }
} }
} }
@objc private func handleSegmentDidChange() {
if self.periodSegmentedControl.selectedSegmentIndex == 0 {
scrollView.isHidden = false
minutelyForecastView.isHidden = true
}
else {
scrollView.isHidden = true
minutelyForecastView.isHidden = false
}
}
} }
//MARK:- Prepare //MARK:- Prepare
...@@ -152,6 +172,18 @@ private extension PrecipitationCell { ...@@ -152,6 +172,18 @@ private extension PrecipitationCell {
} }
} }
func prepareSegmentControl() {
periodSegmentedControl.selectedSegmentIndex = 0
periodSegmentedControl.addTarget(self, action: #selector(handleSegmentDidChange), for: .valueChanged)
contentView.addSubview(periodSegmentedControl)
periodSegmentedControl.snp.makeConstraints { (make) in
make.top.equalTo(headingLabel.snp.bottom).offset(18)
make.left.right.equalToSuperview().inset(16)
make.height.equalTo(40)
}
}
func prepareScrollView() { func prepareScrollView() {
scrollView.showsVerticalScrollIndicator = false scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false scrollView.showsHorizontalScrollIndicator = false
...@@ -161,7 +193,19 @@ private extension PrecipitationCell { ...@@ -161,7 +193,19 @@ private extension PrecipitationCell {
scrollView.snp.makeConstraints { (make) in scrollView.snp.makeConstraints { (make) in
make.left.equalToSuperview() make.left.equalToSuperview()
make.right.equalToSuperview() make.right.equalToSuperview()
make.top.equalTo(headingLabel.snp.bottom).offset(18) make.top.equalTo(periodSegmentedControl.snp.bottom).offset(18)
make.height.equalTo(240)
}
}
func prepareMinutelyForecastView() {
minutelyForecastView.isHidden = true
contentView.addSubview(minutelyForecastView)
minutelyForecastView.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.right.equalToSuperview()
make.top.equalTo(periodSegmentedControl.snp.bottom).offset(40).priority(.medium)
make.height.equalTo(240) make.height.equalTo(240)
} }
} }
......
...@@ -34,6 +34,12 @@ private class MinutelyLevelView: UIView { ...@@ -34,6 +34,12 @@ private class MinutelyLevelView: UIView {
} }
layer.addSublayer(gradient) layer.addSublayer(gradient)
//Shadow
layer.shadowColor = UIColor(hex: 0xd8ddfa).withAlphaComponent(0.8).cgColor
layer.shadowOffset = .init(width: 0, height: 3)
layer.shadowRadius = 3
layer.shadowOpacity = 1
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
...@@ -45,6 +51,7 @@ private class MinutelyLevelView: UIView { ...@@ -45,6 +51,7 @@ private class MinutelyLevelView: UIView {
gradient.cornerRadius = bounds.width / 2 gradient.cornerRadius = bounds.width / 2
gradient.frame = bounds gradient.frame = bounds
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: bounds.width / 2).cgPath
} }
} }
...@@ -56,11 +63,13 @@ class MinutelyForecastView: UIView { ...@@ -56,11 +63,13 @@ class MinutelyForecastView: UIView {
private let verticalStackView = UIStackView() private let verticalStackView = UIStackView()
private let scrollView = UIScrollView() private let scrollView = UIScrollView()
private let centerDashline = CAShapeLayer() private let centerDashline = CAShapeLayer()
private var levelsDashline = [CAShapeLayer]()
private let feedbackGenerator = UISelectionFeedbackGenerator() private let feedbackGenerator = UISelectionFeedbackGenerator()
private var levelsPositionXCache = [Int : CGFloat]() private var levelsPositionXCache = [Int : CGFloat]()
private var weatherTypeCache = [Int : UIImage]() private var weatherTypeCache = [Int : UIImage]()
private var lastSelectedLevelIndex = 0 private var lastSelectedLevelIndex = 0
private var minutelyForecast = [MinutelyItem]() private var minutelyForecast = [MinutelyItem]()
private var forecastType = MinutelyForecastType.temperature
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"
...@@ -93,10 +102,23 @@ class MinutelyForecastView: UIView { ...@@ -93,10 +102,23 @@ class MinutelyForecastView: UIView {
dashlinePath.addLine(to: .init(x: detailsInfoView.frame.origin.x + detailsInfoView.bounds.width / 2, dashlinePath.addLine(to: .init(x: detailsInfoView.frame.origin.x + detailsInfoView.bounds.width / 2,
y: verticalStackView.frame.origin.y + verticalStackView.bounds.height)) y: verticalStackView.frame.origin.y + verticalStackView.bounds.height))
centerDashline.path = dashlinePath centerDashline.path = dashlinePath
//Levels dashline
for (index, levelDashShape) in levelsDashline.enumerated() {
let dashlinePath = CGMutablePath()
let levelView = verticalStackView.arrangedSubviews[index]
dashlinePath.move(to: .init(x: scrollView.frame.origin.x,
y: verticalStackView.frame.origin.y + levelView.frame.origin.y + levelView.frame.height / 2))
dashlinePath.addLine(to: .init(x: scrollView.frame.width + scrollView.frame.origin.x,
y: verticalStackView.frame.origin.y + levelView.frame.origin.y + levelView.frame.height / 2))
levelDashShape.path = dashlinePath
}
} }
func configure(with location: Location) { func configure(with location: Location, forecastType: MinutelyForecastType) {
self.location = location self.location = location
self.forecastType = forecastType
self.dateFormatter.timeZone = location.timeZone self.dateFormatter.timeZone = location.timeZone
centerDashline.strokeColor = kTemperatureColors.last?.cgColor centerDashline.strokeColor = kTemperatureColors.last?.cgColor
...@@ -108,11 +130,20 @@ class MinutelyForecastView: UIView { ...@@ -108,11 +130,20 @@ class MinutelyForecastView: UIView {
} }
private func updateDetailsView(minutelyItem: MinutelyItem) { private func updateDetailsView(minutelyItem: MinutelyItem) {
self.detailsInfoView.configure(valueStirng: minutelyItem.temp.shortString, switch forecastType {
date: minutelyItem.time, case .temperature:
weatherImage: minutelyItem.weatherTypeImage, self.detailsInfoView.configure(valueStirng: minutelyItem.temp.shortString,
timeZone: location?.timeZone ?? .current, date: minutelyItem.time,
colors: kTemperatureColors) weatherImage: minutelyItem.weatherTypeImage,
timeZone: location?.timeZone ?? .current,
colors: kTemperatureColors)
case .precipitation:
self.detailsInfoView.configure(valueStirng: "\(Int(minutelyItem.precipitation * 100))%",
date: minutelyItem.time,
weatherImage: minutelyItem.weatherTypeImage,
timeZone: location?.timeZone ?? .current,
colors: kPrecipitationColors)
}
} }
private func prepareMinutelyItems() { private func prepareMinutelyItems() {
...@@ -145,6 +176,8 @@ class MinutelyForecastView: UIView { ...@@ -145,6 +176,8 @@ class MinutelyForecastView: UIView {
verticalStackView.removeAll() verticalStackView.removeAll()
levelsStackView.removeAll() levelsStackView.removeAll()
levelsPositionXCache.removeAll() levelsPositionXCache.removeAll()
levelsDashline.forEach { $0.removeFromSuperlayer() }
levelsDashline.removeAll()
scrollView.subviews.forEach { scrollView.subviews.forEach {
if $0.isKind(of: UILabel.self) { if $0.isKind(of: UILabel.self) {
$0.removeFromSuperview() $0.removeFromSuperview()
...@@ -152,14 +185,14 @@ class MinutelyForecastView: UIView { ...@@ -152,14 +185,14 @@ class MinutelyForecastView: UIView {
} }
guard guard
let maxTemp = (minutelyForecast.compactMap{$0.temp}.max{ $0.value < $1.value} ), let maxTemp = (minutelyForecast.compactMap{ $0.temp }.max{ $0.value < $1.value} ),
let minTemp = (minutelyForecast.compactMap{$0.temp}.min{ $0.value < $1.value} ) let minTemp = (minutelyForecast.compactMap{ $0.temp }.min{ $0.value < $1.value} )
else { else {
return return
} }
var uniqTemps = minutelyForecast.compactMap{$0.temp}.unique().sorted{$0.value > $1.value} var uniqTemps = minutelyForecast.compactMap{ $0.temp }.unique().sorted{$0.value > $1.value}
if uniqTemps.count > 4 { if uniqTemps.count > 4 {
let uniqMax = uniqTemps.removeFirst() let uniqMax = uniqTemps.removeFirst()
let uniqMin = uniqTemps.removeLast() let uniqMin = uniqTemps.removeLast()
...@@ -183,7 +216,7 @@ class MinutelyForecastView: UIView { ...@@ -183,7 +216,7 @@ class MinutelyForecastView: UIView {
} }
for index in 0..<minutelyForecast.count { for index in 0..<minutelyForecast.count {
let view = MinutelyLevelView(forecastType: .temperature) let view = MinutelyLevelView(forecastType: self.forecastType)
levelsStackView.addArrangedSubview(view) levelsStackView.addArrangedSubview(view)
let level = (0.05 + 0.9 * ((minutelyForecast[index].temp.value - minTemp.value) / (maxTemp.value - minTemp.value))) let level = (0.05 + 0.9 * ((minutelyForecast[index].temp.value - minTemp.value) / (maxTemp.value - minTemp.value)))
view.snp.makeConstraints { make in view.snp.makeConstraints { make in
...@@ -206,6 +239,19 @@ class MinutelyForecastView: UIView { ...@@ -206,6 +239,19 @@ class MinutelyForecastView: UIView {
} }
levelsStackView.layoutIfNeeded() levelsStackView.layoutIfNeeded()
verticalStackView.layoutIfNeeded()
//Levels dashline
for _ in 0..<verticalStackView.arrangedSubviews.count {
let levelShape = CAShapeLayer()
levelShape.opacity = 0.3
levelShape.lineWidth = 1
levelShape.lineDashPattern = [4, 5]
levelShape.strokeColor = UIColor(hex: 0x979797).cgColor
layer.insertSublayer(levelShape, at: 0)
levelsDashline.append(levelShape)
}
for (index, view) in levelsStackView.arrangedSubviews.enumerated() { for (index, view) in levelsStackView.arrangedSubviews.enumerated() {
levelsPositionXCache[index] = view.frame.origin.x levelsPositionXCache[index] = view.frame.origin.x
} }
......
...@@ -158,7 +158,9 @@ class ForecastCellFactory: CellFactory { ...@@ -158,7 +158,9 @@ class ForecastCellFactory: CellFactory {
let cell = dequeueReusableCell(type: PrecipitationCell.self, tableView: tableView, indexPath: indexPath) let cell = dequeueReusableCell(type: PrecipitationCell.self, tableView: tableView, indexPath: indexPath)
if let hourly = forecastViewModel.location?.hourly { if let hourly = forecastViewModel.location?.hourly {
if cellsToUpdate.contains(.precipitation) { if cellsToUpdate.contains(.precipitation) {
cell.configure(with: hourly) if let location = forecastViewModel.location {
cell.configure(with: hourly, location: location)
}
cellsToUpdate.remove(.precipitation) cellsToUpdate.remove(.precipitation)
} }
} }
......
...@@ -157,7 +157,9 @@ class TodayCellFactory: CellFactory { ...@@ -157,7 +157,9 @@ class TodayCellFactory: CellFactory {
case .precipitation: case .precipitation:
let cell = dequeueReusableCell(type: PrecipitationCell.self, tableView: tableView, indexPath: indexPath) let cell = dequeueReusableCell(type: PrecipitationCell.self, tableView: tableView, indexPath: indexPath)
if cellsToUpdate.contains(.precipitation) { if cellsToUpdate.contains(.precipitation) {
cell.configure(with: loc.daily) if let location = todayViewModel.location {
cell.configure(with: loc.daily, location: location)
}
cellsToUpdate.remove(.precipitation) cellsToUpdate.remove(.precipitation)
} }
return cell return cell
......
...@@ -37,10 +37,10 @@ class TodayForecastTimePeriodCell: UITableViewCell { ...@@ -37,10 +37,10 @@ class TodayForecastTimePeriodCell: UITableViewCell {
} }
//Public //Public
public func configure(with location:Location) { public func configure(with location: Location) {
self.location = location self.location = location
self.forecastTimePeriodView.set(daily: location.daily, hourly: location.hourly) self.forecastTimePeriodView.set(daily: location.daily, hourly: location.hourly)
self.minutelyForecastView.configure(with: location) self.minutelyForecastView.configure(with: location, forecastType: .temperature)
self.handleSegmentDidChange() self.handleSegmentDidChange()
} }
......
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