Commit dc6c3e1a by Dmitriy Stepanets

Added data sync to widget view model

parent ae034e64
No preview for this file type
...@@ -1449,6 +1449,7 @@ ...@@ -1449,6 +1449,7 @@
CD1B71352660F95000916E71 /* Sources */, CD1B71352660F95000916E71 /* Sources */,
CD1B71362660F95000916E71 /* Frameworks */, CD1B71362660F95000916E71 /* Frameworks */,
CD1B71372660F95000916E71 /* Resources */, CD1B71372660F95000916E71 /* Resources */,
CD950325269DC532009F7B7D /* Run Script: set build number */,
CE7298CB267A34F3002745D0 /* Embed Frameworks */, CE7298CB267A34F3002745D0 /* Embed Frameworks */,
); );
buildRules = ( buildRules = (
...@@ -1650,6 +1651,24 @@ ...@@ -1650,6 +1651,24 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
CD950325269DC532009F7B7D /* Run Script: set build number */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Run Script: set build number";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "./scripts/set_build_number.sh\n";
};
CE0456212629A634003D252B /* Run Script: Crashlytics */ = { CE0456212629A634003D252B /* Run Script: Crashlytics */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
...@@ -2087,7 +2106,7 @@ ...@@ -2087,7 +2106,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = 1Weather/1Weather.entitlements; CODE_SIGN_ENTITLEMENTS = 1Weather/1Weather.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9; CURRENT_PROJECT_VERSION = SET_BY_BUILD_SCRIPT;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 24W4XMQ38L; DEVELOPMENT_TEAM = 24W4XMQ38L;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
...@@ -2118,7 +2137,7 @@ ...@@ -2118,7 +2137,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = 1Weather/1Weather.entitlements; CODE_SIGN_ENTITLEMENTS = 1Weather/1Weather.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9; CURRENT_PROJECT_VERSION = SET_BY_BUILD_SCRIPT;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 24W4XMQ38L; DEVELOPMENT_TEAM = 24W4XMQ38L;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
...@@ -2148,6 +2167,7 @@ ...@@ -2148,6 +2167,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = OneWeatherWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = OneWeatherWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = SET_BY_BUILD_SCRIPT;
DEVELOPMENT_TEAM = 24W4XMQ38L; DEVELOPMENT_TEAM = 24W4XMQ38L;
INFOPLIST_FILE = OneWeatherWidget/Info.plist; INFOPLIST_FILE = OneWeatherWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
...@@ -2155,6 +2175,7 @@ ...@@ -2155,6 +2175,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 5.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.onelouder.oneweather.OneWeatherWidget; PRODUCT_BUNDLE_IDENTIFIER = com.onelouder.oneweather.OneWeatherWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
...@@ -2171,6 +2192,7 @@ ...@@ -2171,6 +2192,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = OneWeatherWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = OneWeatherWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = SET_BY_BUILD_SCRIPT;
DEVELOPMENT_TEAM = 24W4XMQ38L; DEVELOPMENT_TEAM = 24W4XMQ38L;
INFOPLIST_FILE = OneWeatherWidget/Info.plist; INFOPLIST_FILE = OneWeatherWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
...@@ -2178,6 +2200,7 @@ ...@@ -2178,6 +2200,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 5.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.onelouder.oneweather.OneWeatherWidget; PRODUCT_BUNDLE_IDENTIFIER = com.onelouder.oneweather.OneWeatherWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
...@@ -2192,7 +2215,7 @@ ...@@ -2192,7 +2215,7 @@
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = OneWeatherNotificationServiceExtension/OneWeatherNotificationServiceExtension.entitlements; CODE_SIGN_ENTITLEMENTS = OneWeatherNotificationServiceExtension/OneWeatherNotificationServiceExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9; CURRENT_PROJECT_VERSION = SET_BY_BUILD_SCRIPT;
DEVELOPMENT_TEAM = 24W4XMQ38L; DEVELOPMENT_TEAM = 24W4XMQ38L;
INFOPLIST_FILE = OneWeatherNotificationServiceExtension/Info.plist; INFOPLIST_FILE = OneWeatherNotificationServiceExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.4; IPHONEOS_DEPLOYMENT_TARGET = 14.4;
...@@ -2201,7 +2224,7 @@ ...@@ -2201,7 +2224,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 5.0.2; MARKETING_VERSION = 5.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.onelouder.oneweather.OneWeatherNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.onelouder.oneweather.OneWeatherNotificationServiceExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
...@@ -2216,7 +2239,7 @@ ...@@ -2216,7 +2239,7 @@
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = OneWeatherNotificationServiceExtension/OneWeatherNotificationServiceExtension.entitlements; CODE_SIGN_ENTITLEMENTS = OneWeatherNotificationServiceExtension/OneWeatherNotificationServiceExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9; CURRENT_PROJECT_VERSION = SET_BY_BUILD_SCRIPT;
DEVELOPMENT_TEAM = 24W4XMQ38L; DEVELOPMENT_TEAM = 24W4XMQ38L;
INFOPLIST_FILE = OneWeatherNotificationServiceExtension/Info.plist; INFOPLIST_FILE = OneWeatherNotificationServiceExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.4; IPHONEOS_DEPLOYMENT_TARGET = 14.4;
...@@ -2225,7 +2248,7 @@ ...@@ -2225,7 +2248,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 5.0.2; MARKETING_VERSION = 5.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.onelouder.oneweather.OneWeatherNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.onelouder.oneweather.OneWeatherNotificationServiceExtension;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
......
...@@ -80,7 +80,7 @@ ...@@ -80,7 +80,7 @@
<EnvironmentVariables> <EnvironmentVariables>
<EnvironmentVariable <EnvironmentVariable
key = "_XCWidgetKind" key = "_XCWidgetKind"
value = "com.onelouder.oneweather.widget.small.temperature" value = "com.onelouder.oneweather.widget.medium.temperature"
isEnabled = "YES"> isEnabled = "YES">
</EnvironmentVariable> </EnvironmentVariable>
<EnvironmentVariable <EnvironmentVariable
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
<EnvironmentVariable <EnvironmentVariable
key = "_XCWidgetFamily" key = "_XCWidgetFamily"
value = "medium" value = "medium"
isEnabled = "NO"> isEnabled = "YES">
</EnvironmentVariable> </EnvironmentVariable>
</EnvironmentVariables> </EnvironmentVariables>
</LaunchAction> </LaunchAction>
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>9</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>GADApplicationIdentifier</key> <key>GADApplicationIdentifier</key>
<string>ca-app-pub-7118865896152167~5937109115</string> <string>ca-app-pub-7118865896152167~5937109115</string>
<key>GADNativeAdValidatorEnabled</key> <key>GADNativeAdValidatorEnabled</key>
......
...@@ -165,7 +165,7 @@ private extension WidgetPromotionController { ...@@ -165,7 +165,7 @@ private extension WidgetPromotionController {
func prepareScrollView() { func prepareScrollView() {
scrollView.delegate = self scrollView.delegate = self
view.addSubview(scrollView)s view.addSubview(scrollView)
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)
......
...@@ -135,7 +135,7 @@ public enum WindDirection: String, Codable, CaseIterable { ...@@ -135,7 +135,7 @@ public enum WindDirection: String, Codable, CaseIterable {
case northWest = "NW" case northWest = "NW"
case northNorthWest = "NNW" case northNorthWest = "NNW"
public var degrees:CGFloat { public var degrees: CGFloat {
switch self { switch self {
case .north: case .north:
return 0 return 0
......
...@@ -53,7 +53,6 @@ extension HourlyWeather: UpdatableModelObjectInTime { ...@@ -53,7 +53,6 @@ extension HourlyWeather: UpdatableModelObjectInTime {
result.isDay = incrementalChanges.isDay result.isDay = incrementalChanges.isDay
// Since this class is meant to contain frequently updated info, the preference is given to incrementalChanges data. // Since this class is meant to contain frequently updated info, the preference is given to incrementalChanges data.
result.temp = incrementalChanges.temp ?? result.temp result.temp = incrementalChanges.temp ?? result.temp
result.temp = incrementalChanges.dewPoint ?? result.dewPoint result.temp = incrementalChanges.dewPoint ?? result.dewPoint
result.apparentTemp = incrementalChanges.apparentTemp ?? result.apparentTemp result.apparentTemp = incrementalChanges.apparentTemp ?? result.apparentTemp
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>9</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key> <key>NSExtension</key>
<dict> <dict>
<key>NSExtensionPointIdentifier</key> <key>NSExtensionPointIdentifier</key>
......
...@@ -457,7 +457,7 @@ ...@@ -457,7 +457,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 11.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
...@@ -515,7 +515,7 @@ ...@@ -515,7 +515,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 11.4;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
......
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.800",
"blue" : "250",
"green" : "221",
"red" : "216"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.400",
"blue" : "112",
"green" : "68",
"red" : "52"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
"color-space" : "srgb", "color-space" : "srgb",
"components" : { "components" : {
"alpha" : "1.000", "alpha" : "1.000",
"blue" : "1.000", "blue" : "246",
"green" : "1.000", "green" : "238",
"red" : "1.000" "red" : "236"
} }
}, },
"idiom" : "universal" "idiom" : "universal"
......
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "105",
"green" : "77",
"red" : "68"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -8,11 +8,11 @@ ...@@ -8,11 +8,11 @@
import Foundation import Foundation
struct HourlyTemps { struct HourlyTemps {
let currentTemp: Int let currentTemp: Double
let maxTemp: Int let maxTemp: Double
let lowTemp: Int let lowTemp: Double
init(currentTemp: Int, temps: [Int]) { init(currentTemp: Double, temps: [Double]) {
self.currentTemp = currentTemp self.currentTemp = currentTemp
self.maxTemp = temps.sorted{$0 > $1}.first ?? 0 self.maxTemp = temps.sorted{$0 > $1}.first ?? 0
self.lowTemp = temps.sorted{$0 < $1}.first ?? 0 self.lowTemp = temps.sorted{$0 < $1}.first ?? 0
......
...@@ -57,7 +57,8 @@ public struct LargeTemperatureWidgetView: View { ...@@ -57,7 +57,8 @@ public struct LargeTemperatureWidgetView: View {
.padding(.top, 10) .padding(.top, 10)
.frame(height: 35) .frame(height: 35)
HourlyTemperatureView(temps: [25, 31, 22, 19])
HourlyTemperatureView(hourlyWeather: widgetViewModel.hourlyWeather)
HStack { HStack {
Text("Next few Days") Text("Next few Days")
......
...@@ -14,6 +14,7 @@ public struct MediumTemperatureWidgetView: View { ...@@ -14,6 +14,7 @@ public struct MediumTemperatureWidgetView: View {
let widgetViewModel: WidgetViewModel let widgetViewModel: WidgetViewModel
public init(widgetViewModel: WidgetViewModel?) { public init(widgetViewModel: WidgetViewModel?) {
OneWeatherUI.loadFonts
self.widgetViewModel = widgetViewModel ?? WidgetViewModelMock() self.widgetViewModel = widgetViewModel ?? WidgetViewModelMock()
} }
...@@ -65,7 +66,7 @@ public struct MediumTemperatureWidgetView: View { ...@@ -65,7 +66,7 @@ public struct MediumTemperatureWidgetView: View {
} }
} }
HourlyTemperatureView(temps: [25, 31, 22, 19]) HourlyTemperatureView(hourlyWeather: widgetViewModel.hourlyWeather)
} }
.padding([.leading, .trailing], 10) .padding([.leading, .trailing], 10)
.background(WidgetColor.Name("PrimaryBackground")) .background(WidgetColor.Name("PrimaryBackground"))
...@@ -96,7 +97,7 @@ public struct MediumTemperatureWidgetView_Preview: PreviewProvider { ...@@ -96,7 +97,7 @@ public struct MediumTemperatureWidgetView_Preview: PreviewProvider {
public static var previews: some View { public static var previews: some View {
MediumTemperatureWidgetView(widgetViewModel: WidgetViewModelMock()) MediumTemperatureWidgetView(widgetViewModel: WidgetViewModelMock())
.previewDevice("iPhone 11") .previewDevice("iPhone 11")
.preferredColorScheme(.dark) .preferredColorScheme(.light)
.frame(width: 338, height: 158) .frame(width: 338, height: 158)
} }
} }
...@@ -10,7 +10,7 @@ import SwiftUI ...@@ -10,7 +10,7 @@ import SwiftUI
@available(iOS 14, *) @available(iOS 14, *)
struct HourlyTemperatureView: View { struct HourlyTemperatureView: View {
//Public //Public
let temps: [Int] let hourlyWeather: [WidgetHourlyWeather]
//Private //Private
@State private var hourlyStackFrame: CGRect = .zero @State private var hourlyStackFrame: CGRect = .zero
...@@ -19,10 +19,8 @@ struct HourlyTemperatureView: View { ...@@ -19,10 +19,8 @@ struct HourlyTemperatureView: View {
GeometryReader { geo in GeometryReader { geo in
ZStack { ZStack {
HStack(spacing: 10) { HStack(spacing: 10) {
ForEach(0 ..< temps.count) { index in ForEach(0 ..< hourlyWeather.count) { index in
HourlyView(hourlyTemps: .init(currentTemp: temps[index], HourlyView(hourlyWeather: hourlyWeather[index])
temps: temps),
viewIndex: index)
.frame(maxWidth: 70) .frame(maxWidth: 70)
} }
} }
...@@ -30,15 +28,16 @@ struct HourlyTemperatureView: View { ...@@ -30,15 +28,16 @@ struct HourlyTemperatureView: View {
GeometryReader { geoInner in GeometryReader { geoInner in
Color(.clear) Color(.clear)
.onAppear { .onAppear {
hourlyStackFrame = geoInner.frame(in: .global) print("[Widget] \(geoInner.frame(in: .global))")
hourlyStackFrame = hourlyWeather.isEmpty ? .zero : geoInner.frame(in: .global)
} }
} }
) )
if temps.count >= 2 { if hourlyWeather.count >= 2 {
TemperatureGraphView(temps: temps, TemperatureGraphView(hourlyWeather: hourlyWeather,
viewModel: .init(graphSize: .init(width: hourlyStackFrame.width, viewModel: .init(graphSize: .init(width: hourlyStackFrame.width,
height: 22), height: hourlyStackFrame.height - 55),
hourlyItemWidth: 70, hourlyItemWidth: 70,
spacingPerItem: 10, spacingPerItem: 10,
graphInset: .zero)) graphInset: .zero))
......
...@@ -11,69 +11,116 @@ import SwiftUI ...@@ -11,69 +11,116 @@ import SwiftUI
struct HourlyView: View { struct HourlyView: View {
//Private //Private
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
private let hourlyTemps: HourlyTemps private let hourlyWeather: WidgetHourlyWeather
//Public public init(hourlyWeather: WidgetHourlyWeather) {
init(hourlyTemps: HourlyTemps, viewIndex: Int) { self.hourlyWeather = hourlyWeather
self.hourlyTemps = hourlyTemps
} }
var body: some View { var body: some View {
VStack { VStack {
Text("\(hourlyTemps.currentTemp)" + "°") Text(hourlyWeather.tempLocalized)
.foregroundColor(WidgetColor.Name("PrimaryTextColor")) .foregroundColor(WidgetColor.Name("HourlyText"))
.font(WidgetFont.SFProDisplay.bold(size: 16).font) .font(hourlyWeather.isSelected ? WidgetFont.SFProDisplay.bold(size: 16).font
: WidgetFont.SFProDisplay.regular(size: 16).font)
.padding(.top, 8) .padding(.top, 8)
Spacer() Spacer()
Text("NOW") Text(hourlyWeather.dateString)
.foregroundColor(WidgetColor.Name("PrimaryTextColor")) .foregroundColor(WidgetColor.Name("HourlyText"))
.font(WidgetFont.SFProDisplay.bold(size: 12).font) .font(hourlyWeather.isSelected ? WidgetFont.SFProDisplay.bold(size: 12).font
: WidgetFont.SFProDisplay.regular(size: 12).font)
.padding(.bottom, 8) .padding(.bottom, 8)
} }
.frame(width: 70) .frame(width: 70)
.background(WidgetColor.Name("HourlyContainerBackground")) .modifier(Background(isSelected: hourlyWeather.isSelected))
.compositingGroup() .compositingGroup()
.cornerRadius(12) .cornerRadius(12)
.primaryShadow(for: colorScheme) .modifier(Shadow(isSelected: hourlyWeather.isSelected))
.secondaryShadow(for: colorScheme) .opacity(hourlyWeather.isSelected ? 1 : 0.7)
.modifier(Border(isSelected: hourlyWeather.isSelected))
}
}
@available(iOS 14, *)
private struct Background: ViewModifier {
@Environment(\.colorScheme) private var colorScheme
let isSelected: Bool
func body(content: Content) -> some View {
if isSelected {
content
.background(WidgetColor.Name("HourlyContainerBackground"))
}
else {
let opacity = colorScheme == .dark ? 0.7 : 0.5
content
.background(WidgetColor.Name("HourlyContainerBackground").opacity(opacity))
}
}
}
@available(iOS 14, *)
private struct Border: ViewModifier {
@Environment(\.colorScheme) private var colorScheme
let isSelected: Bool
func body(content: Content) -> some View {
switch colorScheme {
case .light:
if isSelected {
content
}
else {
content
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(WidgetColor.Name("HourlyContainerBorder"),
lineWidth: 1)
)
}
case .dark:
if isSelected {
content
.overlay( .overlay(
RoundedRectangle(cornerRadius: 12) RoundedRectangle(cornerRadius: 12)
.stroke(WidgetColor.Name("HourlyContainerBorder"), .stroke(WidgetColor.Name("HourlyContainerBorder"),
lineWidth: 1) lineWidth: 1)
) )
} }
else {
content
}
default:
fatalError("[OneWeatherUI] Unsopported color scheme")
}
}
} }
//MARK:- UI helpers
@available(iOS 14, *) @available(iOS 14, *)
private extension View { private struct Shadow: ViewModifier {
func primaryShadow(for colorScheme: ColorScheme) -> some View { @Environment(\.colorScheme) private var colorScheme
let isSelected: Bool
func body(content: Content) -> some View {
if isSelected {
switch colorScheme { switch colorScheme {
case .light: case .light:
return self content
.shadow(color: WidgetColor.Name("HourlyContainerPrimaryShadow"), .shadow(color: WidgetColor.Name("HourlyContainerPrimaryShadow"),
radius: 18, x: 5, y: 5) radius: 18, x: 5, y: 5)
case .dark: case .dark:
return self content
.shadow(color: WidgetColor.Name("HourlyContainerPrimaryShadow"), .shadow(color: WidgetColor.Name("HourlyContainerPrimaryShadow"),
radius: 5, x: 5, y: 5) radius: 5, x: 5, y: 5)
default:
fatalError("[OneWeatherUI] Unsopported color scheme")
}
}
func secondaryShadow(for colorScheme: ColorScheme) -> some View {
switch colorScheme {
case .light:
return self
.shadow(color: WidgetColor.Name("HourlyContainerSecondaryShadow"),
radius: 6, x: -4, y: -2)
case .dark:
return self
.shadow(color: WidgetColor.Name("HourlyContainerSecondaryShadow"), .shadow(color: WidgetColor.Name("HourlyContainerSecondaryShadow"),
radius: 6, x: -4, y: -2) radius: 6, x: -4, y: -2)
default: default:
fatalError("[OneWeatherUI] Unsopported color scheme") fatalError("[OneWeatherUI] Unsopported color scheme")
} }
} }
else {
content
}
}
} }
...@@ -11,7 +11,7 @@ import BezierKit ...@@ -11,7 +11,7 @@ import BezierKit
@available(iOS 14, *) @available(iOS 14, *)
struct TemperatureGraphView: View { struct TemperatureGraphView: View {
//Public //Public
let temps: [Int] let hourlyWeather: [WidgetHourlyWeather]
let graphViewModel: GraphViewModel let graphViewModel: GraphViewModel
//Private //Private
...@@ -19,8 +19,8 @@ struct TemperatureGraphView: View { ...@@ -19,8 +19,8 @@ struct TemperatureGraphView: View {
private var linePath = UIBezierPath() private var linePath = UIBezierPath()
private var sections = [CubicCurve]() private var sections = [CubicCurve]()
init(temps: [Int], viewModel: GraphViewModel) { init(hourlyWeather: [WidgetHourlyWeather], viewModel: GraphViewModel) {
self.temps = temps self.hourlyWeather = hourlyWeather
self.graphViewModel = viewModel self.graphViewModel = viewModel
self.calulatePoints() self.calulatePoints()
self.prepareSectionsAndLinePath() self.prepareSectionsAndLinePath()
...@@ -36,6 +36,7 @@ struct TemperatureGraphView: View { ...@@ -36,6 +36,7 @@ struct TemperatureGraphView: View {
lineJoin: .round)) lineJoin: .round))
.frame(width: graphViewModel.graphSize.width, .frame(width: graphViewModel.graphSize.width,
height: graphViewModel.graphSize.height) height: graphViewModel.graphSize.height)
.shadow(color: WidgetColor.Name("GraphShadow"), radius: 3, x: 0, y: 6)
} }
//Tint line //Tint line
...@@ -53,7 +54,8 @@ struct TemperatureGraphView: View { ...@@ -53,7 +54,8 @@ struct TemperatureGraphView: View {
Circle() Circle()
.overlay( .overlay(
Circle() Circle()
.stroke(WidgetColor.Name("GraphLine"), .stroke(hourlyWeather[index].isSelected ? WidgetColor.Name("GraphLineTint")
: WidgetColor.Name("GraphLine"),
lineWidth: 2) lineWidth: 2)
) )
.frame(width: 5, height: 5) .frame(width: 5, height: 5)
...@@ -114,12 +116,12 @@ struct TemperatureGraphView: View { ...@@ -114,12 +116,12 @@ struct TemperatureGraphView: View {
} }
private mutating func calulatePoints() { private mutating func calulatePoints() {
let maxTemp = temps.sorted{$0 > $1}.first ?? 0 let maxTemp = hourlyWeather.map{ $0.temp }.sorted{$0 > $1}.first ?? 0
let minTemp = temps.sorted{$0 < $1}.first ?? 0 let minTemp = hourlyWeather.map{ $0.temp }.sorted{$0 < $1}.first ?? 0
let diff = maxTemp - minTemp let diff = maxTemp - minTemp
for index in 0..<temps.count { for index in 0..<hourlyWeather.count {
let multiply = CGFloat(maxTemp - temps[index]) / CGFloat(diff) let multiply = CGFloat(maxTemp - hourlyWeather[index].temp) / CGFloat(diff)
let space:CGFloat = index > 0 && index <= 3 ? graphViewModel.spacingPerItem : 0 let space:CGFloat = index > 0 && index <= 3 ? graphViewModel.spacingPerItem : 0
if index == 0 { if index == 0 {
......
...@@ -14,6 +14,7 @@ public struct SmallTemperatureWidgetView: View { ...@@ -14,6 +14,7 @@ public struct SmallTemperatureWidgetView: View {
public let widgetViewModel: WidgetViewModel public let widgetViewModel: WidgetViewModel
public init(widgetViewModel: WidgetViewModel?) { public init(widgetViewModel: WidgetViewModel?) {
OneWeatherUI.loadFonts
self.widgetViewModel = widgetViewModel ?? WidgetViewModelMock() self.widgetViewModel = widgetViewModel ?? WidgetViewModelMock()
} }
......
...@@ -8,6 +8,46 @@ ...@@ -8,6 +8,46 @@
import UIKit import UIKit
@available(iOS 14, *) @available(iOS 14, *)
public struct WidgetHourlyWeather {
let dateString: String
let temp: Double
let tempLocalized: String
let windSpeed: Double
let windSpeedLocalized: String
let windDirection: String
let windDegrees: CGFloat
let isSelected: Bool
public init(dateString: String, temp: Double, tempLocalized: String, windSpeed: Double, windSpeedLocalized: String, windDirection: String, windDegrees: CGFloat, isSelected: Bool) {
self.dateString = dateString
self.temp = temp
self.tempLocalized = tempLocalized
self.windSpeed = windSpeed
self.windSpeedLocalized = windSpeedLocalized
self.windDirection = windDirection
self.windDegrees = windDegrees
self.isSelected = isSelected
}
}
@available(iOS 14, *)
public struct WidgetDailyWeather {
let date: Date
let weekDay: String
let isToday: Bool
let minTemp: String
let maxTemp: String
public init(date: Date, weekDay: String, isToday: Bool, minTemp: String, maxTemp: String) {
self.date = date
self.weekDay = weekDay
self.isToday = isToday
self.minTemp = minTemp
self.maxTemp = maxTemp
}
}
@available(iOS 14, *)
public protocol WidgetViewModel { public protocol WidgetViewModel {
var showLastTimeUpdated: Bool { get } var showLastTimeUpdated: Bool { get }
var lastTimeUpdatedText: String { get } var lastTimeUpdatedText: String { get }
...@@ -19,4 +59,6 @@ public protocol WidgetViewModel { ...@@ -19,4 +59,6 @@ public protocol WidgetViewModel {
var lowTemperature: String { get } var lowTemperature: String { get }
var isDeviceLocation: Bool { get } var isDeviceLocation: Bool { get }
var smartText: String { get } var smartText: String { get }
var hourlyWeather: [WidgetHourlyWeather] { get }
var dailyWather: [WidgetDailyWeather] { get }
} }
...@@ -20,4 +20,33 @@ struct WidgetViewModelMock: WidgetViewModel { ...@@ -20,4 +20,33 @@ struct WidgetViewModelMock: WidgetViewModel {
let lowTemperature = "89°" let lowTemperature = "89°"
let isDeviceLocation = true let isDeviceLocation = true
let smartText = "Feels like 101° due to high humidity (77%)." let smartText = "Feels like 101° due to high humidity (77%)."
let hourlyWeather: [WidgetHourlyWeather] = {
var array = [WidgetHourlyWeather]()
for index in 0..<4 {
let hourly = WidgetHourlyWeather(dateString: "11 AM",
temp: 17 + 2 * Double(index),
tempLocalized: "\(17 + 2 * index)°",
windSpeed: 15,
windSpeedLocalized: "8m/s",
windDirection: "SSE",
windDegrees: 25,
isSelected: index == 0)
array.append(hourly)
}
return array
}()
let dailyWather: [WidgetDailyWeather] = {
var array = [WidgetDailyWeather]()
for index in 0..<4 {
let daily = WidgetDailyWeather(date: Date(),
weekDay: "Tuesday",
isToday: false,
minTemp: "\(10 + 2 * index)°",
maxTemp: "\(22 + 2 * index)°")
array.append(daily)
}
return array
}()
} }
...@@ -16,6 +16,8 @@ import CoreDataStorage ...@@ -16,6 +16,8 @@ import CoreDataStorage
class WeatherProvider: TimelineProvider { class WeatherProvider: TimelineProvider {
typealias Entry = WeatherEntry typealias Entry = WeatherEntry
var storage: Storage = CoreDataStorage()
var weatherSource: WeatherSource = WdtWeatherSource()
func placeholder(in context: Context) -> WeatherEntry { func placeholder(in context: Context) -> WeatherEntry {
return WeatherEntry() return WeatherEntry()
...@@ -25,8 +27,6 @@ class WeatherProvider: TimelineProvider { ...@@ -25,8 +27,6 @@ class WeatherProvider: TimelineProvider {
let entry = WeatherEntry() let entry = WeatherEntry()
completion(entry) completion(entry)
} }
var storage: Storage = CoreDataStorage()
var weatherSource: WeatherSource = WdtWeatherSource()
func isFreshEnough(_ location: Location) -> Bool { func isFreshEnough(_ location: Location) -> Bool {
guard let lastTimeUpdated = location.lastWeatherUpdateDate else { guard let lastTimeUpdated = location.lastWeatherUpdateDate else {
......
...@@ -17,9 +17,9 @@ ...@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>9</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key> <key>NSExtension</key>
<dict> <dict>
<key>NSExtensionPointIdentifier</key> <key>NSExtensionPointIdentifier</key>
......
...@@ -15,16 +15,16 @@ struct WidgetPlaceholderView: View { ...@@ -15,16 +15,16 @@ struct WidgetPlaceholderView: View {
var body: some View { var body: some View {
switch family { switch family {
case .systemSmall: case .systemSmall:
SmallTemperatureWidgetView(widgetViewModel: ForecastWidgetViewModel(location: WeatherEntry.defaultLocation)) SmallTemperatureWidgetView(widgetViewModel: nil)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
case .systemMedium: case .systemMedium:
MediumTemperatureWidgetView(widgetViewModel: ForecastWidgetViewModel(location: WeatherEntry.defaultLocation)) MediumTemperatureWidgetView(widgetViewModel: nil)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
case .systemLarge: case .systemLarge:
LargeTemperatureWidgetView(widgetViewModel: ForecastWidgetViewModel(location: WeatherEntry.defaultLocation)) LargeTemperatureWidgetView(widgetViewModel: nil)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
default: default:
SmallTemperatureWidgetView(widgetViewModel: ForecastWidgetViewModel(location: WeatherEntry.defaultLocation)) SmallTemperatureWidgetView(widgetViewModel: nil)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
} }
} }
......
...@@ -9,6 +9,18 @@ import SwiftUI ...@@ -9,6 +9,18 @@ import SwiftUI
import OneWeatherCore import OneWeatherCore
import OneWeatherUI import OneWeatherUI
private struct Formatter {
private static let fmt: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "h a"
return formatter
}()
static func dateString(date: Date) -> String {
return fmt.string(from: date)
}
}
struct ForecastWidgetViewModel: WidgetViewModel { struct ForecastWidgetViewModel: WidgetViewModel {
let showLastTimeUpdated: Bool let showLastTimeUpdated: Bool
let lastTimeUpdatedText: String let lastTimeUpdatedText: String
...@@ -19,9 +31,47 @@ struct ForecastWidgetViewModel: WidgetViewModel { ...@@ -19,9 +31,47 @@ struct ForecastWidgetViewModel: WidgetViewModel {
let highTemperature: String let highTemperature: String
let lowTemperature: String let lowTemperature: String
let isDeviceLocation: Bool let isDeviceLocation: Bool
var smartText: String let smartText: String
let hourlyWeather: [WidgetHourlyWeather]
let dailyWather: [WidgetDailyWeather]
init(location: Location) { init(location: Location) {
func convertToWidgetHourly(modelHourly: [HourlyWeather]) -> [WidgetHourlyWeather] {
var array = [WidgetHourlyWeather]()
for index in 0..<min(modelHourly.count, 4) {
let hourly = modelHourly[index]
let isSelected = Calendar.isNow(fromDate: hourly.date, 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,
tempLocalized: hourly.temp?.settingsConverted.shortString ?? "",
windSpeed: hourly.windSpeed?.settingsConvertedValue ?? 0,
windSpeedLocalized: hourly.windSpeed?.settingsConverted.shortString ?? "",
windDirection: hourly.windDirection?.fullLocalized ?? "",
windDegrees: hourly.windDirection?.degrees ?? 0,
isSelected: isSelected)
array.append(widgetHourly)
}
return array
}
func convertToWidgetDaily(modelDaily: [DailyWeather]) -> [WidgetDailyWeather] {
var array = [WidgetDailyWeather]()
for index in 0..<min(modelDaily.count, 4) {
let daily = modelDaily[index]
let dailyHourly = WidgetDailyWeather(date: daily.date,
weekDay: daily.weekDay.rawValue,
isToday: daily.isToday,
minTemp: daily.minTemp?.settingsConverted.shortString ?? "",
maxTemp: daily.maxTemp?.settingsConverted.shortString ?? "")
array.append(dailyHourly)
}
return array
}
self.cityName = location.cityName ?? "--" self.cityName = location.cityName ?? "--"
self.temperature = "\(location.today?.temp?.shortString ?? "--")" self.temperature = "\(location.today?.temp?.shortString ?? "--")"
self.weatherType = location.today?.type.localized(isDay: location.today?.isDay ?? true) ?? "--" self.weatherType = location.today?.type.localized(isDay: location.today?.isDay ?? true) ?? "--"
...@@ -50,5 +100,8 @@ struct ForecastWidgetViewModel: WidgetViewModel { ...@@ -50,5 +100,8 @@ struct ForecastWidgetViewModel: WidgetViewModel {
} }
let smartTextProvider = SmartTextProvider() let smartTextProvider = SmartTextProvider()
self.smartText = smartTextProvider.smartText(for: location) self.smartText = smartTextProvider.smartText(for: location)
self.hourlyWeather = convertToWidgetHourly(modelHourly: location.hourly)
self.dailyWather = convertToWidgetDaily(modelDaily: location.daily)
} }
} }
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