Commit a7dbecdb by Dmitriy Stepanets

Finished precipitation widget integration

parent 9b566df2
No preview for this file type
...@@ -80,7 +80,7 @@ ...@@ -80,7 +80,7 @@
<EnvironmentVariables> <EnvironmentVariables>
<EnvironmentVariable <EnvironmentVariable
key = "_XCWidgetKind" key = "_XCWidgetKind"
value = "com.onelouder.oneweather.widget.medium.temperature" value = "com.onelouder.oneweather.widget.temperature"
isEnabled = "YES"> isEnabled = "YES">
</EnvironmentVariable> </EnvironmentVariable>
<EnvironmentVariable <EnvironmentVariable
......
...@@ -285,10 +285,8 @@ ...@@ -285,10 +285,8 @@
"widget.promotion.learn" = "Learn to add widget"; "widget.promotion.learn" = "Learn to add widget";
//Widget //Widget
"widget.small.title" = "Temperature Forecast"; "widget.temperature.title" = "Temperature Forecast";
"widget.small.description" = ""; "widget.temperature.description" = "";
"widget.medium.title" = "Temperature Forecast"; "widget.precipitation.title" = "Precipitation Forecast";
"widget.medium.description" = ""; "widget.precipitation.description" = "";
"widget.large.title" = "Temperature Forecast";
"widget.large.description" = "";
"widget.lastUpdatedTemplate" = "Last updated #LAST_UPDATED ago."; "widget.lastUpdatedTemplate" = "Last updated #LAST_UPDATED ago.";
//
// PromotionWidgetViewWrapper.swift
// 1Weather
//
// Created by Dmitry Stepanets on 15.07.2021.
//
import UIKit
import SwiftUI
import OneWeatherUI
@available(iOS 14, *)
enum PromotionWidgetType {
case temperatureSmall
case temperatureMedium
case temperatureLarge
case precipitationMedium
var widgetHeight: Int {
switch self {
case .temperatureLarge:
return 376
default:
return 169
}
}
}
@available(iOS 14, *)
class PromotionWidgetViewWrapper: UIView {
private let widgetController: UIViewController
private let widgetType: PromotionWidgetType
init(widgetType: PromotionWidgetType) {
switch widgetType {
case .temperatureSmall:
widgetController = UIHostingController(rootView: SmallTemperatureWidgetView(widgetViewModel: nil))
case .temperatureMedium:
widgetController = UIHostingController(rootView: MediumTemperatureWidgetView(widgetViewModel: nil))
case .temperatureLarge:
widgetController = UIHostingController(rootView: LargeTemperatureWidgetView(widgetViewModel: nil))
case .precipitationMedium:
widgetController = UIHostingController(rootView: MediumPrecipitationWidgetView(widgetViewModel: nil))
}
self.widgetType = widgetType
super.init(frame: .zero)
prepareWidgetView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MARK:- Prepare
@available(iOS 14, *)
private extension PromotionWidgetViewWrapper {
func prepareWidgetView() {
let container = UIView()
container.addSubview(widgetController.view)
container.clipsToBounds = true
container.layer.cornerRadius = 20
addSubview(container)
widgetController.view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
container.snp.makeConstraints { make in
make.height.equalTo(widgetType.widgetHeight)
make.left.right.equalToSuperview().inset(18)
make.top.equalToSuperview().inset(20)
make.bottom.equalToSuperview()
}
}
}
...@@ -169,7 +169,7 @@ private extension WidgetPromotionController { ...@@ -169,7 +169,7 @@ private extension WidgetPromotionController {
scrollView.snp.makeConstraints { make in scrollView.snp.makeConstraints { make in
make.left.right.equalToSuperview() make.left.right.equalToSuperview()
make.top.equalToSuperview().inset(kScrollViewTopInset) make.top.equalToSuperview().inset(kScrollViewTopInset)
make.bottom.equalTo(footerView.snp.top).offset(4) make.bottom.equalTo(footerView.snp.top).offset(8)
} }
} }
...@@ -179,8 +179,8 @@ private extension WidgetPromotionController { ...@@ -179,8 +179,8 @@ private extension WidgetPromotionController {
stackView.spacing = 0 stackView.spacing = 0
stackView.addArrangedSubview(PromotionHeaderView()) stackView.addArrangedSubview(PromotionHeaderView())
stackView.addArrangedSubview(PromotionSmallWidgetView()) stackView.addArrangedSubview(PromotionSmallWidgetView())
stackView.addArrangedSubview(PromotionMediumWidgetView()) stackView.addArrangedSubview(PromotionWidgetViewWrapper(widgetType: .temperatureMedium))
stackView.addArrangedSubview(PromotionLargeWidgetView()) stackView.addArrangedSubview(PromotionWidgetViewWrapper(widgetType: .precipitationMedium))
scrollView.addSubview(stackView) scrollView.addSubview(stackView)
stackView.snp.makeConstraints { make in stackView.snp.makeConstraints { make in
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
CD2E07BF269C5834001CBF40 /* SF-Pro-Display-Thin.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD2E07BE269C5834001CBF40 /* SF-Pro-Display-Thin.otf */; }; CD2E07BF269C5834001CBF40 /* SF-Pro-Display-Thin.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD2E07BE269C5834001CBF40 /* SF-Pro-Display-Thin.otf */; };
CD2E07C1269C5ABF001CBF40 /* HourlyTemperatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2E07C0269C5ABF001CBF40 /* HourlyTemperatureView.swift */; }; CD2E07C1269C5ABF001CBF40 /* HourlyTemperatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2E07C0269C5ABF001CBF40 /* HourlyTemperatureView.swift */; };
CD3C83C326933ABD0087A225 /* MediumTemperatureWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3C83C226933ABC0087A225 /* MediumTemperatureWidgetView.swift */; }; CD3C83C326933ABD0087A225 /* MediumTemperatureWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3C83C226933ABC0087A225 /* MediumTemperatureWidgetView.swift */; };
CD4319FA26A00E090019A232 /* MediumWidgetTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4319F926A00E090019A232 /* MediumWidgetTopView.swift */; };
CD4319FC26A014CF0019A232 /* MediumPrecipitationWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4319FB26A014CF0019A232 /* MediumPrecipitationWidgetView.swift */; };
CD50556126983C2F006776AB /* CubicCurveAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD50556026983C2E006776AB /* CubicCurveAlgorithm.swift */; }; CD50556126983C2F006776AB /* CubicCurveAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD50556026983C2E006776AB /* CubicCurveAlgorithm.swift */; };
CD7D3161268EEF49000D01FA /* CityNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7D315F268EEF48000D01FA /* CityNameView.swift */; }; CD7D3161268EEF49000D01FA /* CityNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7D315F268EEF48000D01FA /* CityNameView.swift */; };
CD7D3162268EEF49000D01FA /* HighLowTemperatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7D3160268EEF49000D01FA /* HighLowTemperatureView.swift */; }; CD7D3162268EEF49000D01FA /* HighLowTemperatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7D3160268EEF49000D01FA /* HighLowTemperatureView.swift */; };
...@@ -36,7 +38,6 @@ ...@@ -36,7 +38,6 @@
CDBF6D08269887A800715981 /* LargeTemperatureWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDBF6D07269887A800715981 /* LargeTemperatureWidgetView.swift */; }; CDBF6D08269887A800715981 /* LargeTemperatureWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDBF6D07269887A800715981 /* LargeTemperatureWidgetView.swift */; };
CDC3F85A26946D0700AAE3BF /* HourlyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC3F85926946D0700AAE3BF /* HourlyView.swift */; }; CDC3F85A26946D0700AAE3BF /* HourlyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC3F85926946D0700AAE3BF /* HourlyView.swift */; };
CDC3F85C269471C900AAE3BF /* SF-Pro-Display-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = CDC3F85B269471C900AAE3BF /* SF-Pro-Display-Bold.otf */; }; CDC3F85C269471C900AAE3BF /* SF-Pro-Display-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = CDC3F85B269471C900AAE3BF /* SF-Pro-Display-Bold.otf */; };
CDE43FD62695C65700DBE79E /* HourlyTemps.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE43FD52695C65700DBE79E /* HourlyTemps.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
...@@ -66,6 +67,8 @@ ...@@ -66,6 +67,8 @@
CD2E07BE269C5834001CBF40 /* SF-Pro-Display-Thin.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro-Display-Thin.otf"; sourceTree = "<group>"; }; CD2E07BE269C5834001CBF40 /* SF-Pro-Display-Thin.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro-Display-Thin.otf"; sourceTree = "<group>"; };
CD2E07C0269C5ABF001CBF40 /* HourlyTemperatureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HourlyTemperatureView.swift; sourceTree = "<group>"; }; CD2E07C0269C5ABF001CBF40 /* HourlyTemperatureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HourlyTemperatureView.swift; sourceTree = "<group>"; };
CD3C83C226933ABC0087A225 /* MediumTemperatureWidgetView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediumTemperatureWidgetView.swift; sourceTree = "<group>"; }; CD3C83C226933ABC0087A225 /* MediumTemperatureWidgetView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediumTemperatureWidgetView.swift; sourceTree = "<group>"; };
CD4319F926A00E090019A232 /* MediumWidgetTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediumWidgetTopView.swift; sourceTree = "<group>"; };
CD4319FB26A014CF0019A232 /* MediumPrecipitationWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediumPrecipitationWidgetView.swift; sourceTree = "<group>"; };
CD50556026983C2E006776AB /* CubicCurveAlgorithm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CubicCurveAlgorithm.swift; sourceTree = "<group>"; }; CD50556026983C2E006776AB /* CubicCurveAlgorithm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CubicCurveAlgorithm.swift; sourceTree = "<group>"; };
CD6F063D269854A7002A99C2 /* BezierKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BezierKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CD6F063D269854A7002A99C2 /* BezierKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BezierKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CD7D315F268EEF48000D01FA /* CityNameView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CityNameView.swift; sourceTree = "<group>"; }; CD7D315F268EEF48000D01FA /* CityNameView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CityNameView.swift; sourceTree = "<group>"; };
...@@ -87,7 +90,6 @@ ...@@ -87,7 +90,6 @@
CDBF6D07269887A800715981 /* LargeTemperatureWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeTemperatureWidgetView.swift; sourceTree = "<group>"; }; CDBF6D07269887A800715981 /* LargeTemperatureWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeTemperatureWidgetView.swift; sourceTree = "<group>"; };
CDC3F85926946D0700AAE3BF /* HourlyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HourlyView.swift; sourceTree = "<group>"; }; CDC3F85926946D0700AAE3BF /* HourlyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HourlyView.swift; sourceTree = "<group>"; };
CDC3F85B269471C900AAE3BF /* SF-Pro-Display-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro-Display-Bold.otf"; sourceTree = "<group>"; }; CDC3F85B269471C900AAE3BF /* SF-Pro-Display-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro-Display-Bold.otf"; sourceTree = "<group>"; };
CDE43FD52695C65700DBE79E /* HourlyTemps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HourlyTemps.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
...@@ -176,15 +178,31 @@ ...@@ -176,15 +178,31 @@
CD259C12268DE1F7008D205E /* Widgets */ = { CD259C12268DE1F7008D205E /* Widgets */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CD4319F826A00DB70019A232 /* Precipitation */,
CD4319F726A00DAB0019A232 /* Temperature */,
CDF2CF7026982DD6005EF42E /* Helpers */, CDF2CF7026982DD6005EF42E /* Helpers */,
CD1BF3B02698238A00F60E2E /* ViewModels */, CD1BF3B02698238A00F60E2E /* ViewModels */,
CDE43FD52695C65700DBE79E /* HourlyTemps.swift */, CD7D315E268EEF37000D01FA /* SharedViews */,
);
path = Widgets;
sourceTree = "<group>";
};
CD4319F726A00DAB0019A232 /* Temperature */ = {
isa = PBXGroup;
children = (
CD7D3163268EEF56000D01FA /* SmallTemperatureWidgetView.swift */, CD7D3163268EEF56000D01FA /* SmallTemperatureWidgetView.swift */,
CD3C83C226933ABC0087A225 /* MediumTemperatureWidgetView.swift */, CD3C83C226933ABC0087A225 /* MediumTemperatureWidgetView.swift */,
CDBF6D07269887A800715981 /* LargeTemperatureWidgetView.swift */, CDBF6D07269887A800715981 /* LargeTemperatureWidgetView.swift */,
CD7D315E268EEF37000D01FA /* SharedViews */,
); );
path = Widgets; path = Temperature;
sourceTree = "<group>";
};
CD4319F826A00DB70019A232 /* Precipitation */ = {
isa = PBXGroup;
children = (
CD4319FB26A014CF0019A232 /* MediumPrecipitationWidgetView.swift */,
);
path = Precipitation;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CD7D315E268EEF37000D01FA /* SharedViews */ = { CD7D315E268EEF37000D01FA /* SharedViews */ = {
...@@ -195,6 +213,7 @@ ...@@ -195,6 +213,7 @@
CDC3F85926946D0700AAE3BF /* HourlyView.swift */, CDC3F85926946D0700AAE3BF /* HourlyView.swift */,
CD1BF3AE26981B5300F60E2E /* TemperatureGraphView.swift */, CD1BF3AE26981B5300F60E2E /* TemperatureGraphView.swift */,
CD2E07C0269C5ABF001CBF40 /* HourlyTemperatureView.swift */, CD2E07C0269C5ABF001CBF40 /* HourlyTemperatureView.swift */,
CD4319F926A00E090019A232 /* MediumWidgetTopView.swift */,
); );
path = SharedViews; path = SharedViews;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -363,6 +382,7 @@ ...@@ -363,6 +382,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
CD4319FC26A014CF0019A232 /* MediumPrecipitationWidgetView.swift in Sources */,
CD1BF3AF26981B5300F60E2E /* TemperatureGraphView.swift in Sources */, CD1BF3AF26981B5300F60E2E /* TemperatureGraphView.swift in Sources */,
CD7D3162268EEF49000D01FA /* HighLowTemperatureView.swift in Sources */, CD7D3162268EEF49000D01FA /* HighLowTemperatureView.swift in Sources */,
CD2E07C1269C5ABF001CBF40 /* HourlyTemperatureView.swift in Sources */, CD2E07C1269C5ABF001CBF40 /* HourlyTemperatureView.swift in Sources */,
...@@ -374,8 +394,8 @@ ...@@ -374,8 +394,8 @@
CD7D3187268F1F2E000D01FA /* UIImage+Resize.swift in Sources */, CD7D3187268F1F2E000D01FA /* UIImage+Resize.swift in Sources */,
CD1BF3B2269823BA00F60E2E /* GraphViewModel.swift in Sources */, CD1BF3B2269823BA00F60E2E /* GraphViewModel.swift in Sources */,
CD50556126983C2F006776AB /* CubicCurveAlgorithm.swift in Sources */, CD50556126983C2F006776AB /* CubicCurveAlgorithm.swift in Sources */,
CD4319FA26A00E090019A232 /* MediumWidgetTopView.swift in Sources */,
CD7D3178268EFB9E000D01FA /* WidgetViewModelMock.swift in Sources */, CD7D3178268EFB9E000D01FA /* WidgetViewModelMock.swift in Sources */,
CDE43FD62695C65700DBE79E /* HourlyTemps.swift in Sources */,
CD7D316F268EF2BC000D01FA /* WidgetViewModel.swift in Sources */, CD7D316F268EF2BC000D01FA /* WidgetViewModel.swift in Sources */,
CD7D3182268F0C60000D01FA /* OneWeatherUI+Global.swift in Sources */, CD7D3182268F0C60000D01FA /* OneWeatherUI+Global.swift in Sources */,
CDBF6D08269887A800715981 /* LargeTemperatureWidgetView.swift in Sources */, CDBF6D08269887A800715981 /* LargeTemperatureWidgetView.swift in Sources */,
......
{
"images" : [
{
"filename" : "precipitation_full.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
{
"images" : [
{
"filename" : "precipitation_zero.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
//
// MediumPrecipitationWidgetView.swift
// OneWeatherUI
//
// Created by Dmitry Stepanets on 15.07.2021.
//
import SwiftUI
@available(iOS 14, *)
public struct MediumPrecipitationWidgetView: View {
let widgetViewModel: WidgetViewModel
public init(widgetViewModel: WidgetViewModel?) {
OneWeatherUI.loadFonts
self.widgetViewModel = widgetViewModel ?? WidgetViewModelMock()
}
public var body: some View {
VStack(alignment: .center) {
MediumWidgetTopView(widgetViewModel: widgetViewModel)
.padding(.top, 12)
HStack(spacing: 10) {
ForEach(0 ..< widgetViewModel.hourlyWeather.count) { index in
HourlyView(hourlyWeather: widgetViewModel.hourlyWeather[index],
viewType: .precipitation)
.frame(maxWidth: 70)
}
}
.padding(.bottom, 10)
}
.padding([.leading, .trailing], 10)
.background(WidgetColor.Name("PrimaryBackground"))
}
}
@available(iOS 14, *)
struct MediumPrecipitationWidgetView_Preview: PreviewProvider {
public static var previews: some View {
MediumPrecipitationWidgetView(widgetViewModel: WidgetViewModelMock())
.previewDevice("iPhone 11")
.preferredColorScheme(.dark)
.frame(width: 338, height: 158)
}
}
...@@ -20,7 +20,7 @@ struct HourlyTemperatureView: View { ...@@ -20,7 +20,7 @@ struct HourlyTemperatureView: View {
ZStack { ZStack {
HStack(spacing: 10) { HStack(spacing: 10) {
ForEach(0 ..< hourlyWeather.count) { index in ForEach(0 ..< hourlyWeather.count) { index in
HourlyView(hourlyWeather: hourlyWeather[index]) HourlyView(hourlyWeather: hourlyWeather[index], viewType: .temperature)
.frame(maxWidth: 70) .frame(maxWidth: 70)
} }
} }
...@@ -28,7 +28,6 @@ struct HourlyTemperatureView: View { ...@@ -28,7 +28,6 @@ struct HourlyTemperatureView: View {
GeometryReader { geoInner in GeometryReader { geoInner in
Color(.clear) Color(.clear)
.onAppear { .onAppear {
print("[Widget] \(geoInner.frame(in: .global))")
hourlyStackFrame = hourlyWeather.isEmpty ? .zero : geoInner.frame(in: .global) hourlyStackFrame = hourlyWeather.isEmpty ? .zero : geoInner.frame(in: .global)
} }
} }
......
...@@ -8,23 +8,35 @@ ...@@ -8,23 +8,35 @@
import SwiftUI import SwiftUI
@available(iOS 14, *) @available(iOS 14, *)
enum HourlyViewType {
case temperature
case precipitation
}
@available(iOS 14, *)
struct HourlyView: View { struct HourlyView: View {
//Private //Private
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
private let hourlyWeather: WidgetHourlyWeather private let hourlyWeather: WidgetHourlyWeather
private let viewType: HourlyViewType
public init(hourlyWeather: WidgetHourlyWeather) { public init(hourlyWeather: WidgetHourlyWeather, viewType: HourlyViewType) {
self.hourlyWeather = hourlyWeather self.hourlyWeather = hourlyWeather
self.viewType = viewType
} }
var body: some View { var body: some View {
VStack { VStack {
Text(hourlyWeather.tempLocalized) Text(viewType == .temperature ? hourlyWeather.tempLocalized : "\(hourlyWeather.precipitationPercent)%")
.foregroundColor(WidgetColor.Name("HourlyText")) .foregroundColor(WidgetColor.Name("HourlyText"))
.font(hourlyWeather.isSelected ? WidgetFont.SFProDisplay.bold(size: 16).font .font(hourlyWeather.isSelected ? WidgetFont.SFProDisplay.bold(size: 16).font
: WidgetFont.SFProDisplay.regular(size: 16).font) : WidgetFont.SFProDisplay.regular(size: 16).font)
.padding(.top, 8) .padding(.top, 8)
Spacer() Spacer()
if viewType == .precipitation {
precipitationImage()
Spacer()
}
Text(hourlyWeather.dateString) Text(hourlyWeather.dateString)
.foregroundColor(WidgetColor.Name("HourlyText")) .foregroundColor(WidgetColor.Name("HourlyText"))
.font(hourlyWeather.isSelected ? WidgetFont.SFProDisplay.bold(size: 12).font .font(hourlyWeather.isSelected ? WidgetFont.SFProDisplay.bold(size: 12).font
...@@ -36,9 +48,18 @@ struct HourlyView: View { ...@@ -36,9 +48,18 @@ struct HourlyView: View {
.compositingGroup() .compositingGroup()
.cornerRadius(12) .cornerRadius(12)
.modifier(Shadow(isSelected: hourlyWeather.isSelected)) .modifier(Shadow(isSelected: hourlyWeather.isSelected))
.opacity(hourlyWeather.isSelected ? 1 : 0.7)
.modifier(Border(isSelected: hourlyWeather.isSelected)) .modifier(Border(isSelected: hourlyWeather.isSelected))
} }
@available(iOS 14, *)
private func precipitationImage() -> Image {
let imageName = hourlyWeather.precipitationPercent > 0 ? "precipitation_full" : "precipitation_zero"
guard let uiImage = UIImage(named: imageName, in: OneWeatherUI.frameworkBundle, compatibleWith: nil) else {
return Image("")
}
return Image(uiImage: uiImage.scalePreservingAspectRatio(targetSize: .init(width: 18, height: 18)))
}
} }
@available(iOS 14, *) @available(iOS 14, *)
......
// //
// MediumTemperatureWidgetView.swift // MediumWidgetTopView.swift
// OneWeatherWidgetExtension // OneWeatherUI
// //
// Created by Demid Merzlyakov on 28.06.2021. // Created by Dmitry Stepanets on 15.07.2021.
// //
import SwiftUI import SwiftUI
import WidgetKit
@available (iOS 14, *) @available(iOS 14, *)
public struct MediumTemperatureWidgetView: View { struct MediumWidgetTopView: View {
//Public
let widgetViewModel: WidgetViewModel let widgetViewModel: WidgetViewModel
public init(widgetViewModel: WidgetViewModel?) { var body: some View {
OneWeatherUI.loadFonts VStack(spacing: 4) {
self.widgetViewModel = widgetViewModel ?? WidgetViewModelMock()
}
//Private
@Environment(\.colorScheme) private var colorScheme
public var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
HStack { HStack {
HStack(spacing: 8) { HStack(spacing: 8) {
Text(widgetViewModel.temperature) Text(widgetViewModel.temperature)
...@@ -41,22 +30,24 @@ public struct MediumTemperatureWidgetView: View { ...@@ -41,22 +30,24 @@ public struct MediumTemperatureWidgetView: View {
weatherImage(uiImage: widgetViewModel.weatherIcon) weatherImage(uiImage: widgetViewModel.weatherIcon)
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30, alignment: .center) .frame(width: 30, height: 30, alignment: .center)
.shadow(for: colorScheme) .modifier(Shadow())
.padding(.trailing, 4) .padding(.trailing, 4)
} }
Spacer() Spacer()
VStack (spacing: 2) { VStack (spacing: 2) {
CityNameView(cityName: widgetViewModel.cityName, isDeviceLocation: widgetViewModel.isDeviceLocation) CityNameView(cityName: widgetViewModel.cityName,
HighLowTemperatureView(highTemperature: widgetViewModel.highTemperature, lowTemperature: widgetViewModel.lowTemperature) isDeviceLocation: widgetViewModel.isDeviceLocation)
HighLowTemperatureView(highTemperature: widgetViewModel.highTemperature,
lowTemperature: widgetViewModel.lowTemperature)
} }
.frame(height: 35) .frame(height: 35)
} }
.frame(height: 35) .frame(height: 35)
.padding(.top, 12)
HStack(alignment: .top) { HStack(alignment: .top) {
Text(widgetViewModel.smartText) Text(widgetViewModel.smartText)
.lineLimit(2)
.font(WidgetFont.SFProDisplay.regular(size: 12).font) .font(WidgetFont.SFProDisplay.regular(size: 12).font)
Spacer() Spacer()
...@@ -65,39 +56,38 @@ public struct MediumTemperatureWidgetView: View { ...@@ -65,39 +56,38 @@ public struct MediumTemperatureWidgetView: View {
.font(WidgetFont.SFProDisplay.regular(size: 9).font) .font(WidgetFont.SFProDisplay.regular(size: 9).font)
} }
} }
HourlyTemperatureView(hourlyWeather: widgetViewModel.hourlyWeather)
} }
.padding([.leading, .trailing], 10)
.background(WidgetColor.Name("PrimaryBackground"))
} }
@available(iOS 14, *)
private func weatherImage(uiImage: UIImage) -> Image {
return Image(uiImage: uiImage.scalePreservingAspectRatio(targetSize: .init(width: 30, height: 30)))
} }
} }
@available(iOS 14, *) @available(iOS 14, *)
private extension View { private struct Shadow: ViewModifier {
func shadow(for colorScheme: ColorScheme) -> some View { @Environment(\.colorScheme) private var colorScheme
func body(content: Content) -> some View {
switch colorScheme { switch colorScheme {
case .light: case .light:
return self.shadow(color: Color.black.opacity(0.5), radius: 16, x: 2, y: 0) content.shadow(color: Color.black.opacity(0.5), radius: 16, x: 2, y: 0)
case .dark: case .dark:
return self.shadow(color: Color.white.opacity(0.24), radius: 16, x: 2, y: 0) content.shadow(color: Color.white.opacity(0.24), radius: 16, x: 2, y: 0)
@unknown default: default:
return self.shadow(color: Color.black.opacity(0.5), radius: 16, x: 2, y: 0) fatalError("[OneWeatherUI] Unsopported color scheme")
} }
} }
func weatherImage(uiImage: UIImage) -> Image {
return Image(uiImage: uiImage.scalePreservingAspectRatio(targetSize: .init(width: 30, height: 30)))
}
} }
@available(iOS 14, *) @available(iOS 14, *)
public struct MediumTemperatureWidgetView_Preview: PreviewProvider { public struct MediumWidgetTopView_Preview: PreviewProvider {
public static var previews: some View { public static var previews: some View {
MediumTemperatureWidgetView(widgetViewModel: WidgetViewModelMock()) MediumWidgetTopView(widgetViewModel: WidgetViewModelMock())
.previewDevice("iPhone 11") .previewDevice("iPhone 11")
.preferredColorScheme(.light) .preferredColorScheme(.dark)
.background(WidgetColor.Name("PrimaryBackground"))
.frame(width: 338, height: 158) .frame(width: 338, height: 158)
} }
} }
...@@ -54,7 +54,7 @@ struct TemperatureGraphView: View { ...@@ -54,7 +54,7 @@ struct TemperatureGraphView: View {
Circle() Circle()
.overlay( .overlay(
Circle() Circle()
.stroke(hourlyWeather[index].isSelected ? WidgetColor.Name("GraphLineTint") .stroke(index == 0 ? WidgetColor.Name("GraphLineTint")
: WidgetColor.Name("GraphLine"), : WidgetColor.Name("GraphLine"),
lineWidth: 2) lineWidth: 2)
) )
......
//
// MediumTemperatureWidgetView.swift
// OneWeatherWidgetExtension
//
// Created by Demid Merzlyakov on 28.06.2021.
//
import SwiftUI
import WidgetKit
@available (iOS 14, *)
public struct MediumTemperatureWidgetView: View {
//Public
let widgetViewModel: WidgetViewModel
public init(widgetViewModel: WidgetViewModel?) {
OneWeatherUI.loadFonts
self.widgetViewModel = widgetViewModel ?? WidgetViewModelMock()
}
public var body: some View {
VStack(alignment: .leading) {
MediumWidgetTopView(widgetViewModel: widgetViewModel)
.padding(.top, 10)
HourlyTemperatureView(hourlyWeather: widgetViewModel.hourlyWeather)
.padding(.bottom, 10)
}
.padding([.leading, .trailing], 10)
.background(WidgetColor.Name("PrimaryBackground"))
}
}
@available(iOS 14, *)
public struct MediumTemperatureWidgetView_Preview: PreviewProvider {
public static var previews: some View {
MediumTemperatureWidgetView(widgetViewModel: WidgetViewModelMock())
.previewDevice("iPhone 11")
.preferredColorScheme(.dark)
.frame(width: 338, height: 158)
}
}
...@@ -28,7 +28,7 @@ public struct SmallTemperatureWidgetView: View { ...@@ -28,7 +28,7 @@ public struct SmallTemperatureWidgetView: View {
isDeviceLocation: widgetViewModel.isDeviceLocation) isDeviceLocation: widgetViewModel.isDeviceLocation)
Spacer() Spacer()
} }
.padding(.top, 12) .padding(.top, 8)
.padding(.leading, 10) .padding(.leading, 10)
HStack(spacing: 0) { HStack(spacing: 0) {
...@@ -62,6 +62,7 @@ public struct SmallTemperatureWidgetView: View { ...@@ -62,6 +62,7 @@ public struct SmallTemperatureWidgetView: View {
.padding(.bottom, 21) .padding(.bottom, 21)
.padding(.leading, 10) .padding(.leading, 10)
} }
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(WidgetColor.Name("PrimaryBackground")) .background(WidgetColor.Name("PrimaryBackground"))
} }
} }
......
...@@ -16,9 +16,10 @@ public struct WidgetHourlyWeather { ...@@ -16,9 +16,10 @@ public struct WidgetHourlyWeather {
let windSpeedLocalized: String let windSpeedLocalized: String
let windDirection: String let windDirection: String
let windDegrees: CGFloat let windDegrees: CGFloat
let precipitationPercent: Int
let isSelected: Bool let isSelected: Bool
public init(dateString: String, temp: Double, tempLocalized: String, windSpeed: Double, windSpeedLocalized: String, windDirection: String, windDegrees: CGFloat, isSelected: Bool) { public init(dateString: String, temp: Double, tempLocalized: String, windSpeed: Double, windSpeedLocalized: String, windDirection: String, windDegrees: CGFloat, precipitationPercent: Int, isSelected: Bool) {
self.dateString = dateString self.dateString = dateString
self.temp = temp self.temp = temp
self.tempLocalized = tempLocalized self.tempLocalized = tempLocalized
...@@ -26,6 +27,7 @@ public struct WidgetHourlyWeather { ...@@ -26,6 +27,7 @@ public struct WidgetHourlyWeather {
self.windSpeedLocalized = windSpeedLocalized self.windSpeedLocalized = windSpeedLocalized
self.windDirection = windDirection self.windDirection = windDirection
self.windDegrees = windDegrees self.windDegrees = windDegrees
self.precipitationPercent = precipitationPercent
self.isSelected = isSelected self.isSelected = isSelected
} }
} }
......
...@@ -31,6 +31,7 @@ struct WidgetViewModelMock: WidgetViewModel { ...@@ -31,6 +31,7 @@ struct WidgetViewModelMock: WidgetViewModel {
windSpeedLocalized: "8m/s", windSpeedLocalized: "8m/s",
windDirection: "SSE", windDirection: "SSE",
windDegrees: 25, windDegrees: 25,
precipitationPercent: 12,
isSelected: index == 0) isSelected: index == 0)
array.append(hourly) array.append(hourly)
} }
......
...@@ -10,7 +10,37 @@ import WidgetKit ...@@ -10,7 +10,37 @@ import WidgetKit
import OneWeatherCore import OneWeatherCore
struct WeatherEntry: TimelineEntry { struct WeatherEntry: TimelineEntry {
static let defaultLocation = Location(deviceLocation: true, static let defaultLocation: Location = {
func generateHourly() -> [HourlyWeather] {
var array = [HourlyWeather]()
for index in 1...7 {
let hourly = HourlyWeather(lastTimeUpdated: Date(),
date: Date(timeIntervalSinceNow: TimeInterval(index * 3600)),
timeZone: TimeZone(abbreviation: "PST")!,
weekDay: WeekDay.init(grigorianWeekDayNumber: index) ?? .monday,
type: .partlyCloudy,
isDay: true,
temp: .init(value: 69 + Double(index) * 2, unit: .fahrenheit),
dewPoint: .init(value: 69 + Double(index) * 2, unit: .fahrenheit),
apparentTemp: .init(value: 65 + Double(index) * 2, unit: .fahrenheit),
windSpeed: .init(value: Double(8 + index), unit: .metersPerSecond),
windDirection: .northNorthEast,
precipitationProbability: 55,
humidity: 80)
array.append(hourly)
}
return array
}
func generateDaily() -> [DailyWeather] {
var array = [DailyWeather]()
return array
}
let loc = Location(deviceLocation: true,
lastWeatherUpdateDate: Date(timeIntervalSinceNow: -4 * 3600), lastWeatherUpdateDate: Date(timeIntervalSinceNow: -4 * 3600),
coordinates: .init(latitude: 37.3230, longitude: -122.0322), coordinates: .init(latitude: 37.3230, longitude: -122.0322),
countryName: "USA", countryName: "USA",
...@@ -26,9 +56,11 @@ struct WeatherEntry: TimelineEntry { ...@@ -26,9 +56,11 @@ struct WeatherEntry: TimelineEntry {
minTemp: .init(value: 89, unit: .fahrenheit), minTemp: .init(value: 89, unit: .fahrenheit),
maxTemp: .init(value: 97, unit: .fahrenheit), maxTemp: .init(value: 97, unit: .fahrenheit),
temp: .init(value: 96, unit: .fahrenheit)), temp: .init(value: 96, unit: .fahrenheit)),
daily: [], daily: generateDaily(),
hourly: [], hourly: generateHourly(),
dayTimeForecast: []) dayTimeForecast: [])
return loc
}()
let date: Date let date: Date
let location: Location let location: Location
......
...@@ -10,8 +10,7 @@ import SwiftUI ...@@ -10,8 +10,7 @@ import SwiftUI
@main @main
struct OneWeatherWidgets: WidgetBundle { struct OneWeatherWidgets: WidgetBundle {
var body: some Widget { var body: some Widget {
SmallTemperatureWidget() TemperatureWidget()
MediumTemperatureWidget() PrecipitationWidget()
LargeTemperatureWidget()
} }
} }
...@@ -10,12 +10,16 @@ import OneWeatherCore ...@@ -10,12 +10,16 @@ import OneWeatherCore
import OneWeatherUI import OneWeatherUI
private struct Formatter { private struct Formatter {
private static let fmt: DateFormatter = { private static var fmt: DateFormatter = {
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.dateFormat = "h a" formatter.dateFormat = "h a"
return formatter return formatter
}() }()
static func set(timeZone: TimeZone) {
fmt.timeZone = timeZone
}
static func dateString(date: Date) -> String { static func dateString(date: Date) -> String {
return fmt.string(from: date) return fmt.string(from: date)
} }
...@@ -42,6 +46,7 @@ struct ForecastWidgetViewModel: WidgetViewModel { ...@@ -42,6 +46,7 @@ struct ForecastWidgetViewModel: WidgetViewModel {
for index in 0..<min(modelHourly.count, 4) { for index in 0..<min(modelHourly.count, 4) {
let hourly = modelHourly[index] let hourly = modelHourly[index]
let isSelected = Calendar.isNow(fromDate: hourly.date, timeZone: hourly.timeZone) let isSelected = Calendar.isNow(fromDate: hourly.date, timeZone: hourly.timeZone)
Formatter.set(timeZone: hourly.timeZone)
let dateString = isSelected ? "day.now".localized().uppercased() : Formatter.dateString(date: hourly.date) let dateString = isSelected ? "day.now".localized().uppercased() : Formatter.dateString(date: hourly.date)
let widgetHourly = WidgetHourlyWeather(dateString: dateString, let widgetHourly = WidgetHourlyWeather(dateString: dateString,
temp: hourly.temp?.settingsConvertedValue ?? 0, temp: hourly.temp?.settingsConvertedValue ?? 0,
...@@ -50,6 +55,7 @@ struct ForecastWidgetViewModel: WidgetViewModel { ...@@ -50,6 +55,7 @@ struct ForecastWidgetViewModel: WidgetViewModel {
windSpeedLocalized: hourly.windSpeed?.settingsConverted.shortString ?? "", windSpeedLocalized: hourly.windSpeed?.settingsConverted.shortString ?? "",
windDirection: hourly.windDirection?.fullLocalized ?? "", windDirection: hourly.windDirection?.fullLocalized ?? "",
windDegrees: hourly.windDirection?.degrees ?? 0, windDegrees: hourly.windDirection?.degrees ?? 0,
precipitationPercent: Int(hourly.precipitationProbability ?? 0),
isSelected: isSelected) isSelected: isSelected)
array.append(widgetHourly) array.append(widgetHourly)
} }
......
//
// PrecipitationWidget.swift
// OneWeatherWidgetExtension
//
// Created by Dmitry Stepanets on 15.07.2021.
//
import WidgetKit
import SwiftUI
import OneWeatherUI
import Localize_Swift
struct PrecipitationWidget: Widget {
private let kind = "com.onelouder.oneweather.widget.precipitation"
var body: some WidgetConfiguration {
// We currently display selectedLocation, so it's not really configurable,
// but we'll probably need to switch to an IntentConfiguration at some point.
StaticConfiguration(kind: kind,
provider: WeatherProvider()
) { weatherEntry in
MediumPrecipitationWidgetView(widgetViewModel: ForecastWidgetViewModel(location: weatherEntry.location))
}
.configurationDisplayName("widget.precipitation.title".localized())
.description("widget.precipitation.description".localized())
.supportedFamilies([.systemMedium])
}
}
...@@ -14,15 +14,16 @@ struct TemperatureWidget: Widget { ...@@ -14,15 +14,16 @@ struct TemperatureWidget: Widget {
private let kind = "com.onelouder.oneweather.widget.temperature" private let kind = "com.onelouder.oneweather.widget.temperature"
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
// We currently display selectedLocation, so it's not really configurable, but we'll probably need to switch to an IntentConfiguration at some point. // We currently display selectedLocation, so it's not really configurable,
// but we'll probably need to switch to an IntentConfiguration at some point.
StaticConfiguration(kind: kind, StaticConfiguration(kind: kind,
provider: WeatherProvider() provider: WeatherProvider()
) { weatherEntry in ) { weatherEntry in
WidgetView(entry: weatherEntry) WidgetView(entry: weatherEntry)
} }
.configurationDisplayName("widget.small.title".localized()) .configurationDisplayName("widget.temperature.title".localized())
.description("widget.small.description".localized()) .description("widget.temperature.description".localized())
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) .supportedFamilies([.systemSmall, .systemMedium])
} }
} }
...@@ -46,11 +47,13 @@ private struct WidgetView: View { ...@@ -46,11 +47,13 @@ private struct WidgetView: View {
} }
} }
struct SmallTemperatureWidgetView_Preview: PreviewProvider { struct WidgetView_Preview: PreviewProvider {
public static var previews: some View { public static var previews: some View {
SmallTemperatureWidgetView(widgetViewModel: nil) WidgetView(entry: WeatherEntry(location: WeatherEntry.defaultLocation,
date: Date()))
.preferredColorScheme(.dark) .preferredColorScheme(.dark)
.previewContext(WidgetPreviewContext(family: .systemSmall)) .previewDevice("iPhone 11")
.previewContext(WidgetPreviewContext(family: .systemMedium))
} }
} }
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