Commit a7dbecdb by Dmitriy Stepanets

Finished precipitation widget integration

parent 9b566df2
No preview for this file type
......@@ -80,7 +80,7 @@
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = "com.onelouder.oneweather.widget.medium.temperature"
value = "com.onelouder.oneweather.widget.temperature"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
......
......@@ -285,10 +285,8 @@
"widget.promotion.learn" = "Learn to add widget";
//Widget
"widget.small.title" = "Temperature Forecast";
"widget.small.description" = "";
"widget.medium.title" = "Temperature Forecast";
"widget.medium.description" = "";
"widget.large.title" = "Temperature Forecast";
"widget.large.description" = "";
"widget.temperature.title" = "Temperature Forecast";
"widget.temperature.description" = "";
"widget.precipitation.title" = "Precipitation Forecast";
"widget.precipitation.description" = "";
"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 {
scrollView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
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 {
stackView.spacing = 0
stackView.addArrangedSubview(PromotionHeaderView())
stackView.addArrangedSubview(PromotionSmallWidgetView())
stackView.addArrangedSubview(PromotionMediumWidgetView())
stackView.addArrangedSubview(PromotionLargeWidgetView())
stackView.addArrangedSubview(PromotionWidgetViewWrapper(widgetType: .temperatureMedium))
stackView.addArrangedSubview(PromotionWidgetViewWrapper(widgetType: .precipitationMedium))
scrollView.addSubview(stackView)
stackView.snp.makeConstraints { make in
......
......@@ -17,6 +17,8 @@
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 */; };
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 */; };
CD7D3161268EEF49000D01FA /* CityNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7D315F268EEF48000D01FA /* CityNameView.swift */; };
CD7D3162268EEF49000D01FA /* HighLowTemperatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7D3160268EEF49000D01FA /* HighLowTemperatureView.swift */; };
......@@ -36,7 +38,6 @@
CDBF6D08269887A800715981 /* LargeTemperatureWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDBF6D07269887A800715981 /* LargeTemperatureWidgetView.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 */; };
CDE43FD62695C65700DBE79E /* HourlyTemps.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE43FD52695C65700DBE79E /* HourlyTemps.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
......@@ -66,6 +67,8 @@
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>"; };
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>"; };
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>"; };
......@@ -87,7 +90,6 @@
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>"; };
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 */
/* Begin PBXFrameworksBuildPhase section */
......@@ -176,15 +178,31 @@
CD259C12268DE1F7008D205E /* Widgets */ = {
isa = PBXGroup;
children = (
CD4319F826A00DB70019A232 /* Precipitation */,
CD4319F726A00DAB0019A232 /* Temperature */,
CDF2CF7026982DD6005EF42E /* Helpers */,
CD1BF3B02698238A00F60E2E /* ViewModels */,
CDE43FD52695C65700DBE79E /* HourlyTemps.swift */,
CD7D315E268EEF37000D01FA /* SharedViews */,
);
path = Widgets;
sourceTree = "<group>";
};
CD4319F726A00DAB0019A232 /* Temperature */ = {
isa = PBXGroup;
children = (
CD7D3163268EEF56000D01FA /* SmallTemperatureWidgetView.swift */,
CD3C83C226933ABC0087A225 /* MediumTemperatureWidgetView.swift */,
CDBF6D07269887A800715981 /* LargeTemperatureWidgetView.swift */,
CD7D315E268EEF37000D01FA /* SharedViews */,
);
path = Widgets;
path = Temperature;
sourceTree = "<group>";
};
CD4319F826A00DB70019A232 /* Precipitation */ = {
isa = PBXGroup;
children = (
CD4319FB26A014CF0019A232 /* MediumPrecipitationWidgetView.swift */,
);
path = Precipitation;
sourceTree = "<group>";
};
CD7D315E268EEF37000D01FA /* SharedViews */ = {
......@@ -195,6 +213,7 @@
CDC3F85926946D0700AAE3BF /* HourlyView.swift */,
CD1BF3AE26981B5300F60E2E /* TemperatureGraphView.swift */,
CD2E07C0269C5ABF001CBF40 /* HourlyTemperatureView.swift */,
CD4319F926A00E090019A232 /* MediumWidgetTopView.swift */,
);
path = SharedViews;
sourceTree = "<group>";
......@@ -363,6 +382,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CD4319FC26A014CF0019A232 /* MediumPrecipitationWidgetView.swift in Sources */,
CD1BF3AF26981B5300F60E2E /* TemperatureGraphView.swift in Sources */,
CD7D3162268EEF49000D01FA /* HighLowTemperatureView.swift in Sources */,
CD2E07C1269C5ABF001CBF40 /* HourlyTemperatureView.swift in Sources */,
......@@ -374,8 +394,8 @@
CD7D3187268F1F2E000D01FA /* UIImage+Resize.swift in Sources */,
CD1BF3B2269823BA00F60E2E /* GraphViewModel.swift in Sources */,
CD50556126983C2F006776AB /* CubicCurveAlgorithm.swift in Sources */,
CD4319FA26A00E090019A232 /* MediumWidgetTopView.swift in Sources */,
CD7D3178268EFB9E000D01FA /* WidgetViewModelMock.swift in Sources */,
CDE43FD62695C65700DBE79E /* HourlyTemps.swift in Sources */,
CD7D316F268EF2BC000D01FA /* WidgetViewModel.swift in Sources */,
CD7D3182268F0C60000D01FA /* OneWeatherUI+Global.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 {
ZStack {
HStack(spacing: 10) {
ForEach(0 ..< hourlyWeather.count) { index in
HourlyView(hourlyWeather: hourlyWeather[index])
HourlyView(hourlyWeather: hourlyWeather[index], viewType: .temperature)
.frame(maxWidth: 70)
}
}
......@@ -28,7 +28,6 @@ struct HourlyTemperatureView: View {
GeometryReader { geoInner in
Color(.clear)
.onAppear {
print("[Widget] \(geoInner.frame(in: .global))")
hourlyStackFrame = hourlyWeather.isEmpty ? .zero : geoInner.frame(in: .global)
}
}
......
......@@ -8,23 +8,35 @@
import SwiftUI
@available(iOS 14, *)
enum HourlyViewType {
case temperature
case precipitation
}
@available(iOS 14, *)
struct HourlyView: View {
//Private
@Environment(\.colorScheme) private var colorScheme
private let hourlyWeather: WidgetHourlyWeather
private let viewType: HourlyViewType
public init(hourlyWeather: WidgetHourlyWeather) {
public init(hourlyWeather: WidgetHourlyWeather, viewType: HourlyViewType) {
self.hourlyWeather = hourlyWeather
self.viewType = viewType
}
var body: some View {
VStack {
Text(hourlyWeather.tempLocalized)
Text(viewType == .temperature ? hourlyWeather.tempLocalized : "\(hourlyWeather.precipitationPercent)%")
.foregroundColor(WidgetColor.Name("HourlyText"))
.font(hourlyWeather.isSelected ? WidgetFont.SFProDisplay.bold(size: 16).font
: WidgetFont.SFProDisplay.regular(size: 16).font)
.padding(.top, 8)
Spacer()
if viewType == .precipitation {
precipitationImage()
Spacer()
}
Text(hourlyWeather.dateString)
.foregroundColor(WidgetColor.Name("HourlyText"))
.font(hourlyWeather.isSelected ? WidgetFont.SFProDisplay.bold(size: 12).font
......@@ -36,9 +48,18 @@ struct HourlyView: View {
.compositingGroup()
.cornerRadius(12)
.modifier(Shadow(isSelected: hourlyWeather.isSelected))
.opacity(hourlyWeather.isSelected ? 1 : 0.7)
.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, *)
......
//
// MediumTemperatureWidgetView.swift
// OneWeatherWidgetExtension
// MediumWidgetTopView.swift
// OneWeatherUI
//
// Created by Demid Merzlyakov on 28.06.2021.
// Created by Dmitry Stepanets on 15.07.2021.
//
import SwiftUI
import WidgetKit
@available (iOS 14, *)
public struct MediumTemperatureWidgetView: View {
//Public
@available(iOS 14, *)
struct MediumWidgetTopView: View {
let widgetViewModel: WidgetViewModel
public init(widgetViewModel: WidgetViewModel?) {
OneWeatherUI.loadFonts
self.widgetViewModel = widgetViewModel ?? WidgetViewModelMock()
}
//Private
@Environment(\.colorScheme) private var colorScheme
public var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
HStack {
HStack(spacing: 8) {
Text(widgetViewModel.temperature)
.font(WidgetFont.SFProDisplay.light(size: 32).font)
.foregroundColor(WidgetColor.Name("PrimaryTextColor"))
Divider()
.padding(.top, 8)
.padding(.bottom, 8)
Text(widgetViewModel.weatherType)
.font(WidgetFont.SFProDisplay.regular(size: 12).font)
.foregroundColor(WidgetColor.Name("PrimaryTextColor"))
.lineLimit(2)
weatherImage(uiImage: widgetViewModel.weatherIcon)
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30, alignment: .center)
.shadow(for: colorScheme)
.padding(.trailing, 4)
}
Spacer()
VStack (spacing: 2) {
CityNameView(cityName: widgetViewModel.cityName, isDeviceLocation: widgetViewModel.isDeviceLocation)
HighLowTemperatureView(highTemperature: widgetViewModel.highTemperature, lowTemperature: widgetViewModel.lowTemperature)
}
.frame(height: 35)
var body: some View {
VStack(spacing: 4) {
HStack {
HStack(spacing: 8) {
Text(widgetViewModel.temperature)
.font(WidgetFont.SFProDisplay.light(size: 32).font)
.foregroundColor(WidgetColor.Name("PrimaryTextColor"))
Divider()
.padding(.top, 8)
.padding(.bottom, 8)
}
.frame(height: 35)
.padding(.top, 12)
HStack(alignment: .top) {
Text(widgetViewModel.smartText)
Text(widgetViewModel.weatherType)
.font(WidgetFont.SFProDisplay.regular(size: 12).font)
Spacer()
.foregroundColor(WidgetColor.Name("PrimaryTextColor"))
.lineLimit(2)
if widgetViewModel.showLastTimeUpdated {
Text(widgetViewModel.lastTimeUpdatedText)
.font(WidgetFont.SFProDisplay.regular(size: 9).font)
}
weatherImage(uiImage: widgetViewModel.weatherIcon)
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30, alignment: .center)
.modifier(Shadow())
.padding(.trailing, 4)
}
Spacer()
VStack (spacing: 2) {
CityNameView(cityName: widgetViewModel.cityName,
isDeviceLocation: widgetViewModel.isDeviceLocation)
HighLowTemperatureView(highTemperature: widgetViewModel.highTemperature,
lowTemperature: widgetViewModel.lowTemperature)
}
.frame(height: 35)
HourlyTemperatureView(hourlyWeather: widgetViewModel.hourlyWeather)
}
.padding([.leading, .trailing], 10)
.background(WidgetColor.Name("PrimaryBackground"))
.frame(height: 35)
HStack(alignment: .top) {
Text(widgetViewModel.smartText)
.lineLimit(2)
.font(WidgetFont.SFProDisplay.regular(size: 12).font)
Spacer()
if widgetViewModel.showLastTimeUpdated {
Text(widgetViewModel.lastTimeUpdatedText)
.font(WidgetFont.SFProDisplay.regular(size: 9).font)
}
}
}
}
@available(iOS 14, *)
private func weatherImage(uiImage: UIImage) -> Image {
return Image(uiImage: uiImage.scalePreservingAspectRatio(targetSize: .init(width: 30, height: 30)))
}
}
@available(iOS 14, *)
private extension View {
func shadow(for colorScheme: ColorScheme) -> some View {
private struct Shadow: ViewModifier {
@Environment(\.colorScheme) private var colorScheme
func body(content: Content) -> some View {
switch colorScheme {
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:
return self.shadow(color: Color.white.opacity(0.24), radius: 16, x: 2, y: 0)
@unknown default:
return self.shadow(color: Color.black.opacity(0.5), radius: 16, x: 2, y: 0)
content.shadow(color: Color.white.opacity(0.24), radius: 16, x: 2, y: 0)
default:
fatalError("[OneWeatherUI] Unsopported color scheme")
}
}
func weatherImage(uiImage: UIImage) -> Image {
return Image(uiImage: uiImage.scalePreservingAspectRatio(targetSize: .init(width: 30, height: 30)))
}
}
@available(iOS 14, *)
public struct MediumTemperatureWidgetView_Preview: PreviewProvider {
public struct MediumWidgetTopView_Preview: PreviewProvider {
public static var previews: some View {
MediumTemperatureWidgetView(widgetViewModel: WidgetViewModelMock())
MediumWidgetTopView(widgetViewModel: WidgetViewModelMock())
.previewDevice("iPhone 11")
.preferredColorScheme(.light)
.preferredColorScheme(.dark)
.background(WidgetColor.Name("PrimaryBackground"))
.frame(width: 338, height: 158)
}
}
......@@ -54,8 +54,8 @@ struct TemperatureGraphView: View {
Circle()
.overlay(
Circle()
.stroke(hourlyWeather[index].isSelected ? WidgetColor.Name("GraphLineTint")
: WidgetColor.Name("GraphLine"),
.stroke(index == 0 ? WidgetColor.Name("GraphLineTint")
: WidgetColor.Name("GraphLine"),
lineWidth: 2)
)
.frame(width: 5, height: 5)
......
//
// 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 {
isDeviceLocation: widgetViewModel.isDeviceLocation)
Spacer()
}
.padding(.top, 12)
.padding(.top, 8)
.padding(.leading, 10)
HStack(spacing: 0) {
......@@ -62,6 +62,7 @@ public struct SmallTemperatureWidgetView: View {
.padding(.bottom, 21)
.padding(.leading, 10)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(WidgetColor.Name("PrimaryBackground"))
}
}
......
......@@ -16,9 +16,10 @@ public struct WidgetHourlyWeather {
let windSpeedLocalized: String
let windDirection: String
let windDegrees: CGFloat
let precipitationPercent: Int
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.temp = temp
self.tempLocalized = tempLocalized
......@@ -26,6 +27,7 @@ public struct WidgetHourlyWeather {
self.windSpeedLocalized = windSpeedLocalized
self.windDirection = windDirection
self.windDegrees = windDegrees
self.precipitationPercent = precipitationPercent
self.isSelected = isSelected
}
}
......
......@@ -31,6 +31,7 @@ struct WidgetViewModelMock: WidgetViewModel {
windSpeedLocalized: "8m/s",
windDirection: "SSE",
windDegrees: 25,
precipitationPercent: 12,
isSelected: index == 0)
array.append(hourly)
}
......
......@@ -10,25 +10,57 @@ import WidgetKit
import OneWeatherCore
struct WeatherEntry: TimelineEntry {
static let defaultLocation = Location(deviceLocation: true,
lastWeatherUpdateDate: Date(timeIntervalSinceNow: -4 * 3600),
coordinates: .init(latitude: 37.3230, longitude: -122.0322),
countryName: "USA",
region: "US",
cityName: "Cupertino", // Cupertino
timeZone: TimeZone(abbreviation: "PST")!,
today: .init(lastTimeUpdated: Date(timeIntervalSinceNow: -4 * 3600),
date: Date(),
timeZone: TimeZone.current,
weekDay: .monday,
type: .partlyCloudy,
isDay: true,
minTemp: .init(value: 89, unit: .fahrenheit),
maxTemp: .init(value: 97, unit: .fahrenheit),
temp: .init(value: 96, unit: .fahrenheit)),
daily: [],
hourly: [],
dayTimeForecast: [])
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),
coordinates: .init(latitude: 37.3230, longitude: -122.0322),
countryName: "USA",
region: "US",
cityName: "Cupertino", // Cupertino
timeZone: TimeZone(abbreviation: "PST")!,
today: .init(lastTimeUpdated: Date(timeIntervalSinceNow: -4 * 3600),
date: Date(),
timeZone: TimeZone.current,
weekDay: .monday,
type: .partlyCloudy,
isDay: true,
minTemp: .init(value: 89, unit: .fahrenheit),
maxTemp: .init(value: 97, unit: .fahrenheit),
temp: .init(value: 96, unit: .fahrenheit)),
daily: generateDaily(),
hourly: generateHourly(),
dayTimeForecast: [])
return loc
}()
let date: Date
let location: Location
......
......@@ -10,8 +10,7 @@ import SwiftUI
@main
struct OneWeatherWidgets: WidgetBundle {
var body: some Widget {
SmallTemperatureWidget()
MediumTemperatureWidget()
LargeTemperatureWidget()
TemperatureWidget()
PrecipitationWidget()
}
}
......@@ -10,12 +10,16 @@ import OneWeatherCore
import OneWeatherUI
private struct Formatter {
private static let fmt: DateFormatter = {
private static var fmt: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "h a"
return formatter
}()
static func set(timeZone: TimeZone) {
fmt.timeZone = timeZone
}
static func dateString(date: Date) -> String {
return fmt.string(from: date)
}
......@@ -42,6 +46,7 @@ struct ForecastWidgetViewModel: WidgetViewModel {
for index in 0..<min(modelHourly.count, 4) {
let hourly = modelHourly[index]
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 widgetHourly = WidgetHourlyWeather(dateString: dateString,
temp: hourly.temp?.settingsConvertedValue ?? 0,
......@@ -50,6 +55,7 @@ struct ForecastWidgetViewModel: WidgetViewModel {
windSpeedLocalized: hourly.windSpeed?.settingsConverted.shortString ?? "",
windDirection: hourly.windDirection?.fullLocalized ?? "",
windDegrees: hourly.windDirection?.degrees ?? 0,
precipitationPercent: Int(hourly.precipitationProbability ?? 0),
isSelected: isSelected)
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,21 +14,22 @@ struct TemperatureWidget: Widget {
private let kind = "com.onelouder.oneweather.widget.temperature"
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,
provider: WeatherProvider()
) { weatherEntry in
WidgetView(entry: weatherEntry)
}
.configurationDisplayName("widget.small.title".localized())
.description("widget.small.description".localized())
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
.configurationDisplayName("widget.temperature.title".localized())
.description("widget.temperature.description".localized())
.supportedFamilies([.systemSmall, .systemMedium])
}
}
private struct WidgetView: View {
@Environment(\.widgetFamily) private var widgetSize
let entry: WeatherEntry
let entry: WeatherEntry
var body: some View {
let viewModel = ForecastWidgetViewModel(location: entry.location)
......@@ -46,11 +47,13 @@ private struct WidgetView: View {
}
}
struct SmallTemperatureWidgetView_Preview: PreviewProvider {
struct WidgetView_Preview: PreviewProvider {
public static var previews: some View {
SmallTemperatureWidgetView(widgetViewModel: nil)
WidgetView(entry: WeatherEntry(location: WeatherEntry.defaultLocation,
date: Date()))
.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