Commit 1e355fe6 by Dmitriy Stepanets

Finished UI / UX WidgetPromotionController

parent cb127a45
......@@ -111,6 +111,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
#endif
logAppLaunchEvents(launchOptions: launchOptions)
if let widgetPromotionTriggerCount = Settings.shared.widgetPromotionTriggerCount {
if widgetPromotionTriggerCount >= 0 {
Settings.shared.widgetPromotionTriggerCount = widgetPromotionTriggerCount - 1
}
}
else {
if CCPAHelper.shared.isNewUser {
Settings.shared.widgetPromotionTriggerCount = 2
}
else {
Settings.shared.widgetPromotionTriggerCount = 0
}
}
return true
}
......
......@@ -44,12 +44,14 @@ class TodayCoordinator: Coordinator {
public func openLocationsSearch() {
let searchCoordinator = LocationSearchCoordinator(parentViewController: navigationController)
searchCoordinator.parentCoordinator = self
childCoordinators.append(searchCoordinator)
searchCoordinator.start()
}
public func openNotificationsScreen() {
let notificationsCoordinator = NotificationsCoordinator(parentViewController: navigationController)
notificationsCoordinator.parentCoordinator = self
childCoordinators.append(notificationsCoordinator)
notificationsCoordinator.start()
}
......@@ -59,6 +61,7 @@ class TodayCoordinator: Coordinator {
}
let onboardingCoordinator = OnboardingCoordinator(parentViewController: todayViewController, locationManager: LocationManager.shared) // get rid of singleton
onboardingCoordinator.parentCoordinator = self
childCoordinators.append(onboardingCoordinator)
onboardingCoordinator.start()
}
......@@ -66,6 +69,20 @@ class TodayCoordinator: Coordinator {
parentCoordinator?.childDidFinish(child: self)
}
@available(iOS 14, *)
func openWidgetPromotionIfNeeded() {
guard Settings.shared.widgetPromotionTriggerCount == 0 else {
return
}
guard let todayController = todayViewController else { return }
let promotionCoordinator = WidgetPromotionCoordinator(parentViewController: todayController)
promotionCoordinator.parentCoordinator = self
childCoordinators.append(promotionCoordinator)
promotionCoordinator.start()
}
func childDidFinish(child: Coordinator) {
self.delegate?.childCoordinatorDidFinish(in: self)
......
//
// WidgetPromotionCoordinator.swift
// 1Weather
//
// Created by Dmitry Stepanets on 02.07.2021.
//
import UIKit
import OneWeatherCore
@available(iOS 14, *)
class WidgetPromotionCoordinator: Coordinator {
//Private
private let parentViewController: UIViewController
//Public
var childCoordinators = [Coordinator]()
var parentCoordinator: Coordinator?
init(parentViewController: UIViewController) {
self.parentViewController = parentViewController
}
func start() {
let controller = WidgetPromotionController(coordinator: self)
parentViewController.present(controller, animated: true)
Settings.shared.widgetPromotionTriggerCount = -1
}
func viewControllerDidEnd(controller: UIViewController) {
self.parentCoordinator?.childDidFinish(child: self)
}
}
//
// UIApplication+Version.swift
// 1Weather
//
// Created by Dmitry Stepanets on 02.07.2021.
//
import UIKit
extension UIApplication {
struct AppVersion {
static var versionString: String? {
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
}
static var buildNumber: String? {
Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
}
static var major: Int {
guard let appVersion = self.versionString else {
return 0
}
guard let majorString = appVersion.components(separatedBy: ".").first else {
return 0
}
return Int(majorString) ?? 0
}
static var minor: Int {
guard let appVersion = self.versionString else {
return 0
}
let components = appVersion.components(separatedBy: ".")
guard components.count >= 2 else { return 0 }
return Int(components[1]) ?? 0
}
static var patch: Int {
guard let appVersion = self.versionString else {
return 0
}
guard let patchString = appVersion.components(separatedBy: ".").last else {
return 0
}
return Int(patchString) ?? 0
}
}
}
......@@ -34,6 +34,8 @@
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-7118865896152167~5937109115</string>
<key>GADNativeAdValidatorEnabled</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
......@@ -43,6 +45,8 @@
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Enabling location services helps us to provide you more accurate weather forecasts, weather alerts &amp; ads even when the app is closed or not in use. Heres our Privacy Policy: 1weatherapp.com/privacy</string>
<key>NSUserTrackingUsageDescription</key>
<string>This will be used to deliver personalized ads to you.</string>
<key>SKAdNetworkItems</key>
<array>
<dict>
......@@ -422,9 +426,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>GADNativeAdValidatorEnabled</key>
<false/>
<key>NSUserTrackingUsageDescription</key>
<string>This will be used to deliver personalized ads to you.</string>
</dict>
</plist>
......@@ -283,3 +283,7 @@
"widget.promotion.medium" = "Medium";
"widget.promotion.large" = "Large";
"widget.promotion.learn" = "Learn to add widget";
//Widget
"widget.small.title" = "Temperature Forecast";
"widget.small.description" = "";
......@@ -47,6 +47,14 @@ class TodayViewController: UIViewController {
viewModel.updateWeather()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 14, *) {
coordinator.openWidgetPromotionIfNeeded()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
viewModel.showOnboardingOrPrivacyNoticeIfNeeded()
......@@ -65,11 +73,7 @@ class TodayViewController: UIViewController {
}
@objc private func handleNotificationButton() {
if #available(iOS 14, *) {
self.present(WidgetPromotionController(), animated: true)
}
// self.coordinator.openNotificationsScreen()
self.coordinator.openNotificationsScreen()
}
}
......
......@@ -6,9 +6,12 @@
//
import UIKit
import SwiftUI
import OneWeatherUI
@available(iOS 14, *)
class PromotionSmallWidgetView: UIView {
private let widgetPlaceholder = UIView()
private let widgetViewController = UIHostingController(rootView: SmallTemperatureWidgetView(widgetViewModel: nil))
private let topLabel = UILabel()
private let horizontalLineView = UIView()
private let verticalLineView = UIView()
......@@ -18,7 +21,7 @@ class PromotionSmallWidgetView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
preparePlaceholder()
prepareWidget()
prepareTopLabel()
prepareHorizontalLine()
prepareSizesLabel()
......@@ -53,12 +56,13 @@ class PromotionSmallWidgetView: UIView {
}
//MARK:- Prepare
@available(iOS 14, *)
private extension PromotionSmallWidgetView {
func preparePlaceholder() {
widgetPlaceholder.backgroundColor = .lightGray
addSubview(widgetPlaceholder)
func prepareWidget() {
widgetViewController.view.layer.cornerRadius = 12
addSubview(widgetViewController.view)
widgetPlaceholder.snp.makeConstraints { make in
widgetViewController.view.snp.makeConstraints { make in
make.width.height.equalTo(158)
make.left.equalToSuperview().inset(18)
make.top.equalToSuperview().inset(16)
......@@ -84,8 +88,8 @@ private extension PromotionSmallWidgetView {
addSubview(topLabel)
topLabel.snp.makeConstraints { make in
make.left.equalTo(widgetPlaceholder.snp.right).offset(14)
make.top.equalTo(widgetPlaceholder)
make.left.equalTo(widgetViewController.view.snp.right).offset(14)
make.top.equalTo(widgetViewController.view)
}
}
......
......@@ -12,6 +12,7 @@ class WidgetPromotionController: UIViewController {
//Private
private let kPanTopInset: CGFloat = 10
private let kScrollViewTopInset: CGFloat = 36
private let coordinator: WidgetPromotionCoordinator
private let controllerPanView = UIView()
private let closeButton = UIButton()
private let scrollView = UIScrollView()
......@@ -35,7 +36,8 @@ class WidgetPromotionController: UIViewController {
return pan + kScrollViewTopInset + stackHeight + scrollViewBottomInset + footerHeight + safeAreaInset
}
init() {
init(coordinator: WidgetPromotionCoordinator) {
self.coordinator = coordinator
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .custom
......@@ -58,12 +60,23 @@ class WidgetPromotionController: UIViewController {
fatalError("init(coder:) has not been implemented")
}
private func close() {
self.dismiss(animated: true) {
self.coordinator.viewControllerDidEnd(controller: self)
}
}
@objc private func handleCloseButton() {
self.dismiss(animated: true)
close()
}
@objc private func handleLearnButton() {
guard let learnURL = URL(string: "https://support.apple.com/HT207122") else { return }
if UIApplication.shared.canOpenURL(learnURL) {
UIApplication.shared.open(learnURL, options: [:])
close()
}
}
@objc private func handlePanGesture(sender: UIPanGestureRecognizer) {
......@@ -79,7 +92,7 @@ class WidgetPromotionController: UIViewController {
}
case .cancelled, .ended:
if touchPoint.y - initialTouchPoint.y > 80 {
self.dismiss(animated: true, completion: nil)
close()
} else {
UIView.animate(withDuration: 0.25, animations: {
self.view.frame.origin.y = originalOffsetY
......
......@@ -78,10 +78,10 @@ public class Settings {
}
@UserDefaultsBasicValue(key: "pinnedLayers")
public var pinnedLayerIds:[String] = DefaultSettingsFactory().getSettings().pinnedLayerIds
public var pinnedLayerIds: [String] = DefaultSettingsFactory().getSettings().pinnedLayerIds
@UserDefaultsBasicValue(key: "selectedLayer")
public var selectedLayerId:String = DefaultSettingsFactory().getSettings().selectedLayerId
public var selectedLayerId: String = DefaultSettingsFactory().getSettings().selectedLayerId
@UserDefaultsOptionalValue("userQualifiedDate")
public var userQualifiedDate: Date?
......@@ -93,7 +93,10 @@ public class Settings {
public var d3RetentionDate: Date?
@UserDefaultsBasicValue(key: "locationDidAdded")
public var locationDidAdded:Bool = false
public var locationDidAdded: Bool = false
@UserDefaultsOptionalValue("widgetPromotionTriggerCount")
public var widgetPromotionTriggerCount: Int?
#warning("Not implemented!")
//TODO: implement store in UserDefaults and configure via UI in debug builds.
......
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x34",
"green" : "0x23",
"red" : "0x21"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xFF",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF0",
"green" : "0x71",
"red" : "0x10"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF0",
"green" : "0x71",
"red" : "0x10"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"colors" : [
"images" : [
{
"filename" : "location_arrow.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"colors" : [
"images" : [
{
"filename" : "partlyCloudyDay.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "original"
}
}
//
// OneWeatherUI+Global.swift
// OneWeatherUI
//
// Created by Dmitry Stepanets on 02.07.2021.
//
import Foundation
struct OneWeatherUI {}
extension OneWeatherUI {
private class OneWeatherUIClass {}
static let frameworkBundle = Bundle(for: OneWeatherUIClass.self)
}
//
// UIColor+Color.swift
// OneWeatherWidgetExtension
// OneWeatherUI
//
// Created by Dmitry Stepanets on 04.06.2021.
//
......@@ -8,8 +8,9 @@
import SwiftUI
import UIKit
@available(iOS 14, *)
extension UIColor {
var color:Color {
var color: Color {
return Color(self)
}
}
//
// UIFont+Font.swift
// OneWeatherWidgetExtension
//
// Created by Dmitry Stepanets on 04.06.2021.
//
import SwiftUI
import UIKit
@available(iOS 14, *)
public extension UIFont {
var font: Font {
return Font(self)
}
internal static func loadFontWith(name: String, fileExtension: String) {
guard let pathForResource = OneWeatherUI.frameworkBundle.path(forResource: name, ofType: fileExtension) else {
print("[OneWeatherUI] Failed to load font \(name).\(fileExtension)")
return
}
guard let fontData = NSData(contentsOfFile: pathForResource) else {
print("[OneWeatherUI] Invalid font data")
return
}
guard let dataProvider = CGDataProvider(data: fontData) else {
print("[OneWeatherUI] Invalid font data")
return
}
guard let fontRef = CGFont(dataProvider) else {
print("[OneWeatherUI] Invalid CGFont reference")
return
}
var errorRef: Unmanaged<CFError>? = nil
if CTFontManagerRegisterGraphicsFont(fontRef, &errorRef) == false {
print("[OneWeatherUI] Failed to register font, this font may have already been registered in the main bundle.")
}
}
static let loadOneWeatherUI: () = {
loadFontWith(name: "SF-Pro-Display-Regular", fileExtension: "otf")
loadFontWith(name: "SF-Pro-Display-Light", fileExtension: "otf")
}()
}
//
// UIImage+Resize.swift
// OneWeatherUI
//
// Created by Dmitry Stepanets on 02.07.2021.
//
import UIKit
extension UIImage {
func scalePreservingAspectRatio(targetSize: CGSize) -> UIImage {
// Determine the scale factor that preserves aspect ratio
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
let scaleFactor = min(widthRatio, heightRatio)
// Compute the new image size that preserves aspect ratio
let scaledImageSize = CGSize(
width: size.width * scaleFactor,
height: size.height * scaleFactor
)
// Draw and return the resized UIImage
let renderer = UIGraphicsImageRenderer(
size: scaledImageSize
)
let scaledImage = renderer.image { _ in
self.draw(in: CGRect(
origin: .zero,
size: scaledImageSize
))
}
return scaledImage
}
}
......@@ -7,6 +7,7 @@
import SwiftUI
@available(iOS 14, *)
struct CityNameView: View {
@State public var cityName: String
@State public var isDeviceLocation: Bool
......@@ -15,15 +16,24 @@ struct CityNameView: View {
HStack(spacing: 4) {
Text(cityName)
.multilineTextAlignment(.leading)
.font(AppFont.SFProDisplay.regular(size: 12).font)
.foregroundColor(ThemeManager.currentTheme.graphTintColor.color)
Image("location_arrow")
.resizable()
.font(WidgetFont.SFProDisplay.regular(size: 12).font)
.foregroundColor(Color("PrimaryTintColor", bundle: OneWeatherUI.frameworkBundle))
arrowImage()
.renderingMode(.template)
.frame(width: 8, height: 8, alignment: .center)
.aspectRatio(contentMode: .fit)
.foregroundColor(Color(ThemeManager.currentTheme.graphTintColor.cgColor))
.foregroundColor(Color("PrimaryTintColor", bundle: OneWeatherUI.frameworkBundle))
.opacity(isDeviceLocation ? 1 : 0)
}
}
}
@available(iOS 14, *)
private extension CityNameView {
func arrowImage() -> Image {
guard let arrow = UIImage(named: "location_arrow", in: OneWeatherUI.frameworkBundle, compatibleWith: nil) else {
return Image("")
}
return Image(uiImage: arrow.scalePreservingAspectRatio(targetSize: .init(width: 8, height: 8)))
}
}
......@@ -7,6 +7,7 @@
import SwiftUI
@available(iOS 14, *)
struct HighLowTemperatureView: View {
@State public var highTemperature: String
@State public var lowTemperature: String
......@@ -14,11 +15,11 @@ struct HighLowTemperatureView: View {
var body: some View {
HStack {
Text("H \(highTemperature)")
.font(AppFont.SFProDisplay.regular(size: 12).font)
.foregroundColor(Color("HighLowTemperatureColor"))
.font(WidgetFont.SFProDisplay.regular(size: 12).font)
.foregroundColor(Color("HighLowTemperatureColor", bundle: OneWeatherUI.frameworkBundle))
Text("L \(lowTemperature)")
.font(AppFont.SFProDisplay.regular(size: 12).font)
.foregroundColor(Color("HighLowTemperatureColor"))
.font(WidgetFont.SFProDisplay.regular(size: 12).font)
.foregroundColor(Color("HighLowTemperatureColor", bundle: OneWeatherUI.frameworkBundle))
}
}
}
......@@ -7,25 +7,20 @@
import SwiftUI
import WidgetKit
import OneWeatherCore
struct SmallTemperatureWidgetView: View {
@available(iOS 14, *)
public struct SmallTemperatureWidgetView: View {
//Public
let widgetViewModel: ForecastWidgetViewModel
public let widgetViewModel: WidgetViewModel
public init(widgetViewModel: WidgetViewModel?) {
self.widgetViewModel = widgetViewModel ?? WidgetViewModelMock()
}
//Private
@Environment(\.colorScheme) private var colorScheme
private var interfaceStyle: AppInterfaceStyle {
if #available(iOS 13, *) {
return colorScheme == .light ? .light : .dark
}
else {
return .light
}
}
var body: some View {
public var body: some View {
GeometryReader { geometry in
VStack {
HStack(spacing: 4) {
......@@ -39,23 +34,23 @@ struct SmallTemperatureWidgetView: View {
HStack(spacing: 0) {
VStack(alignment: .leading){
Text(widgetViewModel.temperature)
.font(AppFont.SFProDisplay.light(size: 32).font)
.font(WidgetFont.SFProDisplay.light(size: 32).font)
.padding(.leading, 10)
.padding(.trailing, 4)
.textColor(for: colorScheme)
.foregroundColor(Color("PrimaryTextColor",
bundle: OneWeatherUI.frameworkBundle))
Text(widgetViewModel.weatherType)
.font(AppFont.SFProDisplay.regular(size: 12).font)
.font(WidgetFont.SFProDisplay.regular(size: 12).font)
.padding(.leading, 10)
.padding(.trailing, 4)
.textColor(for: colorScheme)
.foregroundColor(Color("PrimaryTextColor",
bundle: OneWeatherUI.frameworkBundle))
Spacer(minLength: 0)
}
.frame(height: 70)
Spacer(minLength: 0)
Image(uiImage: widgetViewModel.weatherIcon)
.resizable()
.aspectRatio(contentMode: .fit)
weatherImage(uiImage: widgetViewModel.weatherIcon)
.frame(width: 70, height: 70, alignment: .center)
.shadow(for: colorScheme)
.padding(.trailing, 4)
......@@ -74,6 +69,7 @@ struct SmallTemperatureWidgetView: View {
}
//MARK: Appearence extension
@available(iOS 14, *)
private extension View {
func shadow(for colorScheme: ColorScheme) -> some View {
switch colorScheme {
......@@ -86,22 +82,15 @@ private extension View {
}
}
func textColor(for colorScheme: ColorScheme) -> some View {
switch colorScheme {
case .light:
return self.foregroundColor(ThemeManager.currentTheme.secondaryTextColor.color)
case .dark:
return self.foregroundColor(ThemeManager.currentTheme.primaryTextColor.color)
@unknown default:
return self.foregroundColor(ThemeManager.currentTheme.secondaryTextColor.color)
}
func weatherImage(uiImage: UIImage) -> Image {
return Image(uiImage: uiImage.scalePreservingAspectRatio(targetSize: .init(width: 70, height: 70)))
}
}
struct SmallTemperatureWidgetView_Preview: PreviewProvider {
static var previews: some View {
SmallTemperatureWidgetView(widgetViewModel: .init(location: WeatherEntry.defaultLocation))
.previewContext(WidgetPreviewContext(family: .systemSmall))
@available(iOS 14, *)
public struct SmallTemperatureWidgetView_Preview: PreviewProvider {
public static var previews: some View {
SmallTemperatureWidgetView(widgetViewModel: WidgetViewModelMock())
.frame(width: 158, height: 158)
}
}
//
// WidgetFont.swift
// OneWeatherUI
//
// Created by Dmitry Stepanets on 02.07.2021.
//
import UIKit
@available(iOS 14, *)
struct WidgetFont {
private static var fontsLoaded = false
init() {
if !WidgetFont.fontsLoaded {
UIFont.loadOneWeatherUI
WidgetFont.fontsLoaded = true
}
}
private static func fontDescriptor(family: String, size:CGFloat, weight:UIFont.Weight) -> UIFontDescriptor {
let traitsDict = [UIFontDescriptor.TraitKey.weight: weight]
let descriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.size : size,
UIFontDescriptor.AttributeName.family : family,
UIFontDescriptor.AttributeName.traits : traitsDict
])
return descriptor
}
struct SFProDisplay {
private static var fontCache = [UIFont.Weight: [CGFloat: UIFont]]()
static func font(weigth: UIFont.Weight, size: CGFloat) -> UIFont {
if let cached = fontCache[weigth]?[size] {
return cached
}
let descriptor = WidgetFont.fontDescriptor(family: "SF Pro Display", size: size, weight: weigth)
let font = UIFont(descriptor: descriptor, size: size)
if fontCache[weigth] == nil {
fontCache[weigth] = [size: font]
}
else {
fontCache[weigth]![size] = font
}
return font
}
static func light(size: CGFloat) -> UIFont {
font(weigth: .light, size: size)
}
static func regular(size: CGFloat) -> UIFont {
font(weigth: .regular, size: size)
}
}
}
//
// WidgetViewModel.swift
// OneWeatherUI
//
// Created by Dmitry Stepanets on 02.07.2021.
//
import UIKit
@available(iOS 14, *)
public protocol WidgetViewModel {
var cityName: String { get }
var temperature: String { get }
var weatherType: String { get }
var weatherIcon: UIImage { get }
var highTemperature: String { get }
var lowTemperature: String { get }
var isDeviceLocation: Bool { get }
}
//
// WidgetViewModelMock.swift
// OneWeatherUI
//
// Created by Dmitry Stepanets on 02.07.2021.
//
import UIKit
@available(iOS 14, *)
struct WidgetViewModelMock: WidgetViewModel {
private class OneWeatherUIClass {}
let cityName = "New York"
let temperature = "96°"
let weatherType = "Partly Cloudy"
let weatherIcon = UIImage(named: "partlyCloudyDay", in: Bundle(for: OneWeatherUIClass.self), compatibleWith: nil)!
let highTemperature = "97°"
let lowTemperature = "89°"
let isDeviceLocation = true
}
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
//
// UIFont+Font.swift
// OneWeatherWidgetExtension
//
// Created by Dmitry Stepanets on 04.06.2021.
//
import SwiftUI
import UIKit
extension UIFont {
var font:Font {
return Font(self)
}
}
//
// VectorImage.swift
// OneWeatherWidgetExtension
//
// Created by Dmitry Stepanets on 07.06.2021.
//
import SwiftUI
import WidgetKit
struct VectorImage: UIViewRepresentable {
typealias UIViewType = UIImageView
var name: String
var contentMode: UIView.ContentMode = .scaleAspectFit
var tintColor:UIColor = .black
private var image: UIImage?
init(image:UIImage, contentMode: UIView.ContentMode = .scaleAspectFit, tintColor:UIColor = .black) {
self.image = image
self.contentMode = contentMode
self.tintColor = tintColor
self.name = ""
}
init(name:String, contentMode: UIView.ContentMode = .scaleAspectFit, tintColor:UIColor = .black) {
self.name = name
self.contentMode = contentMode
self.tintColor = tintColor
}
func makeUIView(context: Context) -> UIViewType {
let imageView = UIImageView()
imageView.setContentCompressionResistancePriority(.fittingSizeLevel,
for: .vertical)
imageView.image = image
imageView.contentMode = contentMode
imageView.tintColor = tintColor
return imageView
}
func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.contentMode = contentMode
uiView.tintColor = tintColor
if self.image != nil {
uiView.image = self.image
}
else {
uiView.image = UIImage(named: name)
}
}
}
struct VectorImage_Preview: PreviewProvider {
static var previews: some View {
VectorImage(image: UIImage())
.previewLayout(.fixed(width: 200, height: 200))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
......@@ -7,6 +7,7 @@
import SwiftUI
import WidgetKit
import OneWeatherUI
struct WidgetPlaceholderView: View {
@Environment(\.widgetFamily) var family
......@@ -14,10 +15,10 @@ struct WidgetPlaceholderView: View {
var body: some View {
switch family {
case .systemSmall:
SmallTemperatureWidgetView(widgetViewModel: .init(location: WeatherEntry.defaultLocation))
SmallTemperatureWidgetView(widgetViewModel: ForecastWidgetViewModel(location: WeatherEntry.defaultLocation))
.redacted(reason: .placeholder)
default:
SmallTemperatureWidgetView(widgetViewModel: .init(location: WeatherEntry.defaultLocation))
SmallTemperatureWidgetView(widgetViewModel: ForecastWidgetViewModel(location: WeatherEntry.defaultLocation))
.redacted(reason: .placeholder)
}
}
......
......@@ -7,8 +7,9 @@
import SwiftUI
import OneWeatherCore
import OneWeatherUI
struct ForecastWidgetViewModel {
struct ForecastWidgetViewModel: WidgetViewModel {
let cityName: String
let temperature: String
let weatherType: String
......
......@@ -7,6 +7,8 @@
import WidgetKit
import SwiftUI
import OneWeatherUI
import Localize_Swift
struct SmallTemperatureWidget: Widget {
private let kind = "com.onelouder.oneweather.widget.small"
......@@ -14,8 +16,18 @@ struct SmallTemperatureWidget: Widget {
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
SmallTemperatureWidgetView(widgetViewModel: .init(location: weatherEntry.location))
SmallTemperatureWidgetView(widgetViewModel: ForecastWidgetViewModel(location: weatherEntry.location))
}
.configurationDisplayName("widget.small.title".localized())
.description("widget.small.description".localized())
.supportedFamilies([.systemSmall])
}
}
struct SmallTemperatureWidgetView_Preview: PreviewProvider {
public static var previews: some View {
SmallTemperatureWidgetView(widgetViewModel: nil)
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
......@@ -71,6 +71,11 @@ target 'OneWeatherCore' do
core_pods
end
#UI
target 'OneWeatherUI' do
project 'OneWeatherUI/OneWeatherUI.project'
end
#CoreDataStorage
target 'CoreDataStorage' do
project 'CoreDataStorage/CoreDataStorage.project'
......@@ -105,6 +110,10 @@ target 'OneWeatherNotificationServiceExtension' do
pod 'MORichNotification'
end
target 'OneWeatherWidgetExtension' do
pod 'Localize-Swift'
end
#post_install do |installer|
# applicationTargets = [
# 'Pods-1Weather'
......
......@@ -290,6 +290,6 @@ SPEC CHECKSUMS:
Swarm: 95393cd52715744c94e3a8475bc20b4de5d79f35
XMLCoder: f884dfa894a6f8b7dce465e4f6c02963bf17e028
PODFILE CHECKSUM: bbdc2e45e39585408b836ebe285837b990c3bb9f
PODFILE CHECKSUM: 659661f4f40b1e968a316ebeaf2afc8bc6f0190a
COCOAPODS: 1.10.1
......@@ -290,6 +290,6 @@ SPEC CHECKSUMS:
Swarm: 95393cd52715744c94e3a8475bc20b4de5d79f35
XMLCoder: f884dfa894a6f8b7dce465e4f6c02963bf17e028
PODFILE CHECKSUM: bbdc2e45e39585408b836ebe285837b990c3bb9f
PODFILE CHECKSUM: 659661f4f40b1e968a316ebeaf2afc8bc6f0190a
COCOAPODS: 1.10.1
This source diff could not be displayed because it is too large. You can view the blob instead.
APPLICATION_EXTENSION_API_ONLY = YES
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
......
APPLICATION_EXTENSION_API_ONLY = YES
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
# Acknowledgements
This application makes use of the following third party libraries:
Generated by CocoaPods - https://cocoapods.org
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>FooterText</key>
<string>This application makes use of the following third party libraries:</string>
<key>Title</key>
<string>Acknowledgements</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Generated by CocoaPods - https://cocoapods.org</string>
<key>Title</key>
<string></string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
</array>
<key>StringsTable</key>
<string>Acknowledgements</string>
<key>Title</key>
<string>Acknowledgements</string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_Pods_OneWeatherUI : NSObject
@end
@implementation PodsDummy_Pods_OneWeatherUI
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double Pods_OneWeatherUIVersionNumber;
FOUNDATION_EXPORT const unsigned char Pods_OneWeatherUIVersionString[];
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
PODS_ROOT = ${SRCROOT}/../Pods
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
framework module Pods_OneWeatherUI {
umbrella header "Pods-OneWeatherUI-umbrella.h"
export *
module * { export * }
}
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
PODS_ROOT = ${SRCROOT}/../Pods
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
# Acknowledgements
This application makes use of the following third party libraries:
## Localize-Swift
Copyright (c) 2015 Roy Marmelstein (http://roysapps.com/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Generated by CocoaPods - https://cocoapods.org
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>FooterText</key>
<string>This application makes use of the following third party libraries:</string>
<key>Title</key>
<string>Acknowledgements</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2015 Roy Marmelstein (http://roysapps.com/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>Localize-Swift</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Generated by CocoaPods - https://cocoapods.org</string>
<key>Title</key>
<string></string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
</array>
<key>StringsTable</key>
<string>Acknowledgements</string>
<key>Title</key>
<string>Acknowledgements</string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_Pods_OneWeatherWidgetExtension : NSObject
@end
@implementation PodsDummy_Pods_OneWeatherWidgetExtension
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double Pods_OneWeatherWidgetExtensionVersionNumber;
FOUNDATION_EXPORT const unsigned char Pods_OneWeatherWidgetExtensionVersionString[];
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift/Localize_Swift.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "Localize_Swift"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
framework module Pods_OneWeatherWidgetExtension {
umbrella header "Pods-OneWeatherWidgetExtension-umbrella.h"
export *
module * { export * }
}
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Localize-Swift/Localize_Swift.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "Localize_Swift"
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
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