Commit 44b939ed by Dmitriy Stepanets

- Added theme cell to settings view controller.

- Added Settings model
- Added helper types for Settings model
parent d9bea788
No preview for this file type
......@@ -36,6 +36,14 @@
CD37D3D6260C93B3002669D6 /* MenuCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D3D5260C93B3002669D6 /* MenuCellFactory.swift */; };
CD37D3DE260C9E37002669D6 /* MenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D3DD260C9E37002669D6 /* MenuCell.swift */; };
CD37D3E5260CB05C002669D6 /* MenuFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D3E4260CB05C002669D6 /* MenuFooterView.swift */; };
CD37D3EB260DD30F002669D6 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D3EA260DD30F002669D6 /* Settings.swift */; };
CD37D3EF260DF4E6002669D6 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D3EE260DF4E6002669D6 /* SettingsViewController.swift */; };
CD37D3F3260DF4FB002669D6 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D3F2260DF4FB002669D6 /* SettingsCoordinator.swift */; };
CD37D3F6260DF5BA002669D6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D3F5260DF5BA002669D6 /* SettingsViewModel.swift */; };
CD37D3FA260DF714002669D6 /* SettingsThemeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D3F9260DF714002669D6 /* SettingsThemeCell.swift */; };
CD37D3FE260DF726002669D6 /* SettingsCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D3FD260DF726002669D6 /* SettingsCellFactory.swift */; };
CD37D401260DF744002669D6 /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D400260DF744002669D6 /* SettingsCell.swift */; };
CD37D405260DFFDD002669D6 /* CellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD37D404260DFFDD002669D6 /* CellFactory.swift */; };
CD39F2EE25DE858D009FE398 /* NotificationName+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD39F2ED25DE858D009FE398 /* NotificationName+Localization.swift */; };
CD39F2F225DE94C4009FE398 /* CitySunCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD39F2F125DE94C4009FE398 /* CitySunCell.swift */; };
CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD39F2F425DE9571009FE398 /* ArrowButton.swift */; };
......@@ -150,6 +158,14 @@
CD37D3D5260C93B3002669D6 /* MenuCellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCellFactory.swift; sourceTree = "<group>"; };
CD37D3DD260C9E37002669D6 /* MenuCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCell.swift; sourceTree = "<group>"; };
CD37D3E4260CB05C002669D6 /* MenuFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuFooterView.swift; sourceTree = "<group>"; };
CD37D3EA260DD30F002669D6 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
CD37D3EE260DF4E6002669D6 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
CD37D3F2260DF4FB002669D6 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
CD37D3F5260DF5BA002669D6 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
CD37D3F9260DF714002669D6 /* SettingsThemeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsThemeCell.swift; sourceTree = "<group>"; };
CD37D3FD260DF726002669D6 /* SettingsCellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCellFactory.swift; sourceTree = "<group>"; };
CD37D400260DF744002669D6 /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = "<group>"; };
CD37D404260DFFDD002669D6 /* CellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellFactory.swift; sourceTree = "<group>"; };
CD39F2ED25DE858D009FE398 /* NotificationName+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationName+Localization.swift"; sourceTree = "<group>"; };
CD39F2F125DE94C4009FE398 /* CitySunCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CitySunCell.swift; sourceTree = "<group>"; };
CD39F2F425DE9571009FE398 /* ArrowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowButton.swift; sourceTree = "<group>"; };
......@@ -348,6 +364,7 @@
CD17C5FE25D15B7C00EE884E /* TodayCoordinator.swift */,
CDE18DCC25D1666700C80ED9 /* ForecastCoordinator.swift */,
CD32CE0A260C744A00235081 /* MenuCoordinator.swift */,
CD37D3F2260DF4FB002669D6 /* SettingsCoordinator.swift */,
);
path = Coordinators;
sourceTree = "<group>";
......@@ -408,6 +425,25 @@
path = Footer;
sourceTree = "<group>";
};
CD37D3ED260DF4B8002669D6 /* Settings */ = {
isa = PBXGroup;
children = (
CD37D3F8260DF6F6002669D6 /* Cells */,
CD37D3EE260DF4E6002669D6 /* SettingsViewController.swift */,
);
path = Settings;
sourceTree = "<group>";
};
CD37D3F8260DF6F6002669D6 /* Cells */ = {
isa = PBXGroup;
children = (
CD37D3FD260DF726002669D6 /* SettingsCellFactory.swift */,
CD37D3F9260DF714002669D6 /* SettingsThemeCell.swift */,
CD37D400260DF744002669D6 /* SettingsCell.swift */,
);
path = Cells;
sourceTree = "<group>";
};
CD647D0025ED07AF0034578B /* ViewModels */ = {
isa = PBXGroup;
children = (
......@@ -415,6 +451,7 @@
CD8E041125F8F775001785B6 /* ForecastViewModel.swift */,
CD647D0525ED08050034578B /* ViewModelProtocol.swift */,
CD32CE07260C743B00235081 /* MenuViewModel.swift */,
CD37D3F5260DF5BA002669D6 /* SettingsViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
......@@ -425,6 +462,7 @@
CD17C5F425D15B3400EE884E /* Today */,
CDE18DCF25D166DD00C80ED9 /* Forecast */,
CD32CE02260C740600235081 /* Menu */,
CD37D3ED260DF4B8002669D6 /* Settings */,
CD82300925D6B2AF00A05501 /* AppTabBarController.swift */,
);
path = "View controllers";
......@@ -595,6 +633,7 @@
CE9D181825ECB9A70028D9D7 /* Logger.swift */,
CE9D181525ECB8370028D9D7 /* MulticastDelegate.swift */,
CDA5542C25EF7C9700A2E08C /* ReusableCellProtocol.swift */,
CD37D404260DFFDD002669D6 /* CellFactory.swift */,
);
path = Common;
sourceTree = "<group>";
......@@ -606,6 +645,7 @@
CEDE4F0925EFA376007457E9 /* Protocols */,
CEAFF0A025E0FEF100DF4EBF /* ModelObjects */,
CEAFF0A225E0FF0800DF4EBF /* LocationManager.swift */,
CD37D3EA260DD30F002669D6 /* Settings.swift */,
);
path = Model;
sourceTree = "<group>";
......@@ -811,7 +851,9 @@
CD1237F1255D83C500C98139 /* UIColor+Hex.swift in Sources */,
CDD0F1EE25725BCF00CF5017 /* ThemeManager.swift in Sources */,
CD1237F4255D889F00C98139 /* GradientView.swift in Sources */,
CD37D3EF260DF4E6002669D6 /* SettingsViewController.swift in Sources */,
CD593BC926089FC100C93428 /* UITableView+HeaderSize.swift in Sources */,
CD37D3F3260DF4FB002669D6 /* SettingsCoordinator.swift in Sources */,
CD1237C3255D5C5900C98139 /* AppDelegate.swift in Sources */,
CD39F2F225DE94C4009FE398 /* CitySunCell.swift in Sources */,
CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */,
......@@ -829,6 +871,7 @@
CD17C5FF25D15B7C00EE884E /* TodayCoordinator.swift in Sources */,
CD822FF525D6817000A05501 /* CityForecastCell.swift in Sources */,
CD2B2140260A366B00AB918A /* UIView+InterfaceStyle.swift in Sources */,
CD37D401260DF744002669D6 /* SettingsCell.swift in Sources */,
CEDE4E8525EEFD56007457E9 /* WdtHourlySummariesArray.swift in Sources */,
CDC6125725E7AB1A00188DA7 /* CityAirQualityCell.swift in Sources */,
CD593BCF2608A50900C93428 /* ForecastHourlyCell.swift in Sources */,
......@@ -839,6 +882,7 @@
CDE18DD125D166F900C80ED9 /* ForecastViewController.swift in Sources */,
CD39F2F525DE9571009FE398 /* ArrowButton.swift in Sources */,
CEDE4E8325EEFD56007457E9 /* WdtLocationResponse.swift in Sources */,
CD37D3FE260DF726002669D6 /* SettingsCellFactory.swift in Sources */,
CD8E041625F8F91B001785B6 /* ForecastCellFactory.swift in Sources */,
CDC6125325E79C8F00188DA7 /* DayTimeView.swift in Sources */,
CD251EDC26036E5400ED7A65 /* DayTimePrecipitationView.swift in Sources */,
......@@ -851,6 +895,7 @@
CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */,
CDC6126225E8DAB800188DA7 /* CityMoonCell.swift in Sources */,
CD37D3D6260C93B3002669D6 /* MenuCellFactory.swift in Sources */,
CD37D3FA260DF714002669D6 /* SettingsThemeCell.swift in Sources */,
CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */,
CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */,
CD35DFCC260341B000F2138F /* Calendar+TimeZone.swift in Sources */,
......@@ -877,6 +922,9 @@
CD80917B2578E4A8003541A4 /* UIViewController+Alert.swift in Sources */,
CEDE4F0F25EFA3B4007457E9 /* UpdatableModelObjectInTime.swift in Sources */,
CD3F6E6925FA59D4002DB99B /* ForecastDetailPeriodButton.swift in Sources */,
CD37D405260DFFDD002669D6 /* CellFactory.swift in Sources */,
CD37D3F6260DF5BA002669D6 /* SettingsViewModel.swift in Sources */,
CD37D3EB260DD30F002669D6 /* Settings.swift in Sources */,
CD17C5F625D15B4400EE884E /* TodayViewController.swift in Sources */,
CD86245E25E646350097F3FB /* SunUvView.swift in Sources */,
CEAFF08325DFC67F00DF4EBF /* Location.swift in Sources */,
......
//
// CellFactory.swift
// 1Weather
//
// Created by Dmitry Stepanets on 26.03.2021.
//
import UIKit
extension CellFactoryProtocol {
func dequeueReusableCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView, indexPath: IndexPath) -> T {
let cell = tableView.dequeueReusableCell(withIdentifier: T.kIdentifier, for: indexPath) as! T
return cell
}
func registerCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView) {
tableView.register(type, forCellReuseIdentifier: T.kIdentifier)
}
}
public protocol CellFactoryProtocol {
var numberOfSections:Int { get }
func numberOfRows(inSection section:Int) -> Int
func registerCells(on tableView:UITableView)
func cellFromTableView(tableView:UITableView, indexPath:IndexPath) -> UITableViewCell
}
......@@ -39,4 +39,8 @@ class AppCoordinator: Coordinator {
tabBarController.setupTabBar()
}
func viewControllerDidEnd(controller: UIViewController) {
//
}
}
......@@ -8,11 +8,11 @@
import UIKit
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var parentCoordinator:Coordinator? { get set }
func start()
func viewControllerDidEnd(controller:UIViewController)
}
extension Coordinator {
......
......@@ -26,4 +26,8 @@ class ForecastCoordinator: Coordinator {
navigationController.viewControllers = [forecastViewController]
tabBarController?.add(viewController: navigationController)
}
func viewControllerDidEnd(controller: UIViewController) {
//
}
}
......@@ -22,8 +22,19 @@ class MenuCoordinator: Coordinator {
}
func start() {
let menuViewController = MenuViewController(menuViewModel: menuViewModel)
let menuViewController = MenuViewController(coordinator: self, menuViewModel: menuViewModel)
navigationController.viewControllers = [menuViewController]
tabBarController?.add(viewController: navigationController)
}
func viewControllerDidEnd(controller: UIViewController) {
parentCoordinator?.childDidFinish(child: self)
}
func openSettings() {
let settingsCoordinator = SettingsCoordinator(parentViewController: self.navigationController)
settingsCoordinator.parentCoordinator = self
childCoordinators.append(settingsCoordinator)
settingsCoordinator.start()
}
}
//
// SettingsCoordinator.swift
// 1Weather
//
// Created by Dmitry Stepanets on 26.03.2021.
//
import UIKit
class SettingsCoordinator: Coordinator {
//Private
private let settingsViewModel = SettingsViewModel()
private var parentViewController:UIViewController?
//Public
var childCoordinators = [Coordinator]()
var parentCoordinator: Coordinator?
init(parentViewController:UIViewController) {
self.parentViewController = parentViewController
}
func start() {
DispatchQueue.main.async {
let settingsViewController = SettingsViewController(coordinator: self, settingsViewModel: self.settingsViewModel)
let navVC = UINavigationController(rootViewController: settingsViewController)
self.parentViewController?.present(navVC, animated: true)
}
}
func viewControllerDidEnd(controller: UIViewController) {
}
}
......@@ -26,4 +26,8 @@ class TodayCoordinator:Coordinator {
navigationController.viewControllers = [todayViewController]
tabBarController?.add(viewController: navigationController)
}
func viewControllerDidEnd(controller: UIViewController) {
//
}
}
......@@ -50,6 +50,80 @@ public enum WeatherType: String, CaseIterable {
}
}
public enum TemperatureType:String {
case kelvin = "temp.kelvin"
case celsius = "temp.celsius"
case fahrenheit = "temp.fahrenheit"
var unitTemperature:UnitTemperature {
switch self {
case .kelvin:
return .kelvin
case .celsius:
return .celsius
case .fahrenheit:
return .fahrenheit
}
}
}
public enum WindSpeedType:String {
case metersPerSecond = "windSpeed.mps"
case kilometersPerHour = "windSpeed.kph"
case milesPerHour = "windSpeed.mph"
case knots = "windSpeed.knots"
var unitSpeed:UnitSpeed {
switch self {
case .metersPerSecond:
return .metersPerSecond
case .kilometersPerHour:
return .kilometersPerHour
case .milesPerHour:
return .milesPerHour
case .knots:
return .knots
}
}
}
public enum PressureType:String {
case inchesOfMercury = "pressure.inch"
case millibars = "pressure.mb"
case millimetersOfMercury = "pressure.mm"
case atmosphere = "pressure.atm"
case kilopascals = "pressure.kpa"
var unitPressure:UnitPressure {
switch self {
case .inchesOfMercury:
return .inchesOfMercury
case .millibars:
return .millibars
case .millimetersOfMercury:
return .millimetersOfMercury
case .atmosphere:
return UnitPressure(symbol: "atm", converter: UnitConverterLinear(coefficient: 101325))
case .kilopascals:
return .kilopascals
}
}
}
public enum DistanceType:String {
case miles = "distance.mi"
case kilometers = "distance.km"
var unitPressure:UnitLength {
switch self {
case .miles:
return .miles
case .kilometers:
return .kilometers
}
}
}
public enum WeatherConditionType: CaseIterable {
case precipitation
case humidity
......
//
// Settings.swift
// 1Weather
//
// Created by Dmitry Stepanets on 26.03.2021.
//
import UIKit
@propertyWrapper
struct UserDefaultsValue<T> {
var wrappedValue: T {
get {
let value = UserDefaults.standard.value(forKey: key) as? T
return value ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
UserDefaults.standard.synchronize()
}
}
private let key: String
private let defaultValue: T
init(wrappedValue defaultValue:T, key:String) {
self.defaultValue = defaultValue
self.key = key
}
}
class Settings {
static let shared = Settings()
private init() {}
@UserDefaultsValue(key: "app_theme")
var appTheme = AppTheme.system
@UserDefaultsValue(key: "app_theme_auto")
var automaticSwitchTheme = false
@UserDefaultsValue(key: "temperature_type")
var temperatureType = TemperatureType.celsius
@UserDefaultsValue(key: "wind_speed_type")
var windSpeedType = WindSpeedType.milesPerHour
@UserDefaultsValue(key: "pressure_type")
var pressureType = PressureType.millibars
@UserDefaultsValue(key: "distance_type")
var distanceType = DistanceType.miles
}
{
"images" : [
{
"filename" : "tab_forecast_selected.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "tab_menu_selected.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "tab_radar_selected.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "tab_today_selected.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
{
"images" : [
{
"filename" : "theme_sample_dark.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "theme_sample_dark@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "theme_sample_dark@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "theme_sample_light.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "theme_sample_light@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "theme_sample_light@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
......@@ -5,6 +5,10 @@
Created by Dmitry Stepanets on 18.02.2021.
*/
//General
"general.close" = "close";
//Forecast
"forecast.sunny" = "Sunny";
"forecast.cloudy" = "Cloudy";
......@@ -102,3 +106,9 @@
"menu.faq" = "FAQ's";
"menu.privacy" = "Privacy Policy";
"menu.version" = "version";
//Settings
"settings.theme.automatic" = "Automatic";
"settings.theme.automaticDesc" = "Enable light or dark theme based on your device brightness and display settings";
"settings.theme.light" = "Light";
"settings.theme.dark" = "Dark";
......@@ -7,6 +7,12 @@
import UIKit
public enum AppTheme {
case light
case dark
case system
}
public struct ThemeManager {
struct Colors {
static let locationBlue = UIColor(hex: 0x1071F0)
......
......@@ -28,15 +28,19 @@ class AppTabBarController: UITabBarController {
case .today:
tabBar.items?[$0.rawValue].title = "TODAY"
tabBar.items?[$0.rawValue].image = UIImage(named: "tab_today")
tabBar.items?[$0.rawValue].selectedImage = UIImage(named: "tab_today_selected")
case .forecast:
tabBar.items?[$0.rawValue].title = "FORECAST"
tabBar.items?[$0.rawValue].image = UIImage(named: "tab_forecast")
tabBar.items?[$0.rawValue].selectedImage = UIImage(named: "tab_forecast_selected")
case .radar:
tabBar.items?[$0.rawValue].title = "RADAR"
tabBar.items?[$0.rawValue].image = UIImage(named: "tab_radar")
tabBar.items?[$0.rawValue].selectedImage = UIImage(named: "tab_radar_selected")
case .menu:
tabBar.items?[$0.rawValue].title = "MENU"
tabBar.items?[$0.rawValue].image = UIImage(named: "tab_menu")
tabBar.items?[$0.rawValue].selectedImage = UIImage(named: "tab_menu_selected")
}
}
}
......
......@@ -25,13 +25,17 @@ private enum HourlyForecastCellType: Int, CaseIterable {
case wind
}
class ForecastCellFactory {
class ForecastCellFactory: CellFactoryProtocol {
//Private
private let forecastViewModel:ForecastViewModel
private var currentTimePeriod = TimePeriod.daily
//Public
public var numberOfRows:Int {
var numberOfSections: Int {
return 1
}
public func numberOfRows(inSection section: Int) -> Int {
return currentTimePeriod == .daily ? DailyForecastCellType.allCases.count : HourlyForecastCellType.allCases.count
}
......@@ -145,15 +149,6 @@ class ForecastCellFactory {
return cell
}
}
private func registerCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView) {
tableView.register(type, forCellReuseIdentifier: T.kIdentifier)
}
private func dequeueReusableCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView, indexPath: IndexPath) -> T {
let cell = tableView.dequeueReusableCell(withIdentifier: T.kIdentifier, for: indexPath) as! T
return cell
}
}
//MARK:- ForecastTimePeriodCell Delegate
......
......@@ -204,7 +204,7 @@ extension ForecastViewController: UITableViewDelegate {
//MARK:- UITableView DataSource
extension ForecastViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return forecastCellFactory.numberOfRows
return forecastCellFactory.numberOfRows(inSection: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
......@@ -225,7 +225,7 @@ extension ForecastViewController: ForecastViewModelDelegate {
switch timePeriod {
case .daily:
var indexPathToReload = [IndexPath]()
for index in 0..<forecastCellFactory.numberOfRows {
for index in 0..<forecastCellFactory.numberOfRows(inSection: 0) {
if index == 0 { continue }
indexPathToReload.append([0, index])
......
......@@ -12,7 +12,7 @@ private enum Section {
case info
}
private enum Row: String {
public enum MenuRow {
case settings
case about
case ad
......@@ -80,18 +80,23 @@ private enum Row: String {
}
private struct SectionItem {
let rows:[Row]
let type:Section
let rows:[MenuRow]
}
class MenuCellFactory {
class MenuCellFactory<T>: CellFactoryProtocol {
//Private
private let menuViewModel:MenuViewModel
private let sections:[SectionItem] = [SectionItem(rows: [.settings]),
SectionItem(rows: [.about, .ad, .rateUs, .help, .faq, .privacy])]
private let sections:[SectionItem] = [SectionItem(type: .info, rows: [.settings]),
SectionItem(type: .settings, rows: [.about, .ad, .rateUs, .help, .faq, .privacy])]
//Public
static let kSectionHeight:CGFloat = 30
static let kRowHeight:CGFloat = 57
public var kSectionHeight:CGFloat {
return 30
}
public var kRowHeight:CGFloat {
return 57
}
public var numberOfSections:Int {
return sections.count
}
......@@ -114,13 +119,13 @@ class MenuCellFactory {
}
public func registerCells(on tableView:UITableView) {
registerCell(type: MenuCell.self, tableView: tableView)
self.registerCell(type: MenuCell.self, tableView: tableView)
}
public func cellFromTableView(tableView:UITableView, indexPath:IndexPath) -> UITableViewCell {
let cellType = sections[indexPath.section].rows[indexPath.row]
let cell = dequeueReusableCell(type: MenuCell.self, tableView: tableView, indexPath: indexPath)
let cell = self.dequeueReusableCell(type: MenuCell.self, tableView: tableView, indexPath: indexPath)
cell.configure(image: cellType.image,
text: cellType.localized,
roundendCorners: cellType.roundedCorners)
......@@ -129,12 +134,7 @@ class MenuCellFactory {
return cell
}
private func registerCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView) {
tableView.register(type, forCellReuseIdentifier: T.kIdentifier)
}
private func dequeueReusableCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView, indexPath: IndexPath) -> T {
let cell = tableView.dequeueReusableCell(withIdentifier: T.kIdentifier, for: indexPath) as! T
return cell
func cellTypeAt(indexPath: IndexPath) -> MenuRow? {
return sections[indexPath.section].rows[indexPath.row]
}
}
......@@ -9,12 +9,14 @@ import UIKit
class MenuViewController: UIViewController {
//Private
private let coordinator:MenuCoordinator
private let viewModel:MenuViewModel
private let menuHeaderView = MenuHeaderView()
private let menuCellFactory:MenuCellFactory
private let menuCellFactory:MenuCellFactory<MenuRow>
private let tableView = UITableView()
init(menuViewModel: MenuViewModel) {
init(coordinator:MenuCoordinator, menuViewModel: MenuViewModel) {
self.coordinator = coordinator
self.viewModel = menuViewModel
self.menuCellFactory = MenuCellFactory(viewModel: menuViewModel)
super.init(nibName: nil, bundle: nil)
......@@ -62,7 +64,7 @@ private extension MenuViewController {
tableView.separatorStyle = .none
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = MenuCellFactory.kRowHeight
tableView.rowHeight = menuCellFactory.kRowHeight
tableView.tableFooterView = UIView()
tableView.tableHeaderView = menuHeaderView
tableView.tableFooterView = MenuFooterView()
......@@ -97,7 +99,7 @@ extension MenuViewController: UITableViewDelegate {
return 0
}
return MenuCellFactory.kSectionHeight
return menuCellFactory.kSectionHeight
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
......@@ -105,4 +107,14 @@ extension MenuViewController: UITableViewDelegate {
view.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
return view
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let type = menuCellFactory.cellTypeAt(indexPath: indexPath) else {
return
}
if type == .settings {
coordinator.openSettings()
}
}
}
//
// SettingsCell.swift
// 1Weather
//
// Created by Dmitry Stepanets on 26.03.2021.
//
import UIKit
class SettingsCell: UITableViewCell {
}
//
// SettingsCellFactory.swift
// 1Weather
//
// Created by Dmitry Stepanets on 26.03.2021.
//
import UIKit
private enum SettingsRow {
case theme
}
private struct Section {
let rows:[SettingsRow]
}
class SettingsCellFactory: CellFactoryProtocol {
//Private
private let viewModel:SettingsViewModel
private let sections:[Section] = [Section(rows: [.theme])]
//Public
init(viewModel:SettingsViewModel) {
self.viewModel = viewModel
}
public var numberOfSections: Int {
return sections.count
}
public func numberOfRows(inSection section: Int) -> Int {
return sections[section].rows.count
}
public func registerCells(on tableView: UITableView) {
self.registerCell(type: SettingsThemeCell.self, tableView: tableView)
}
public func cellFromTableView(tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
return self.dequeueReusableCell(type: SettingsThemeCell.self, tableView: tableView, indexPath: indexPath)
}
}
//
// SettingsThemeCell.swift
// 1Weather
//
// Created by Dmitry Stepanets on 26.03.2021.
//
import UIKit
class SettingsThemeCell: UITableViewCell {
//Private
private let container = UIView()
private let lightThemeImageView = UIImageView()
private let lightThemeLabel = UILabel()
private let lightThemeButton = UIButton()
private let darkThemeImageView = UIImageView()
private let darkThemeLabel = UILabel()
private let darkThemeButton = UIButton()
private let separatorView = UIView()
private let automaticThemeLabel = UILabel()
private let automaticDescriptionLabel = UILabel()
private let automaticSwitchButton = UISwitch()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
prepareCell()
prepareContainer()
prepareLightTheme()
prepareDarkTheme()
prepareSeparator()
prepareAutomaticViews()
updateUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
//Private
private func updateUI() {
contentView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
container.backgroundColor = ThemeManager.currentTheme.containerBackgroundColor
switch interfaceStyle {
case .light:
lightThemeLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
automaticThemeLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
automaticDescriptionLabel.textColor = ThemeManager.currentTheme.secondaryTextColor
case .dark:
lightThemeLabel.textColor = ThemeManager.currentTheme.primaryTextColor
automaticThemeLabel.textColor = ThemeManager.currentTheme.primaryTextColor
automaticDescriptionLabel.textColor = ThemeManager.currentTheme.primaryTextColor
}
}
@objc private func handleThemeButton(button: UIButton) {
}
@objc private func handleAutomaticSwith() {
}
//Public
public func configure(settings: Settings) {
}
}
//Private
private extension SettingsThemeCell {
func prepareCell() {
selectionStyle = .none
}
func prepareContainer() {
container.layer.cornerRadius = 12
contentView.addSubview(container)
container.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(18)
make.top.bottom.equalToSuperview().inset(8)
}
}
func prepareLightTheme() {
lightThemeImageView.clipsToBounds = true
lightThemeImageView.layer.cornerRadius = 10
lightThemeImageView.contentMode = .scaleAspectFill
lightThemeImageView.image = UIImage(named: "theme_sample_light")
container.addSubview(lightThemeImageView)
lightThemeLabel.textAlignment = .center
lightThemeLabel.setContentHuggingPriority(.fittingSizeLevel, for: .vertical)
lightThemeLabel.font = AppFont.SFPro.regular(size: 16)
lightThemeLabel.text = "settings.theme.light".localized()
container.addSubview(lightThemeLabel)
lightThemeButton.addTarget(self, action: #selector(handleThemeButton(button:)), for: .touchUpInside)
container.addSubview(lightThemeButton)
//Constraints
lightThemeImageView.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 85, height: 154))
make.top.equalToSuperview().inset(24)
make.left.equalToSuperview().inset(57)
}
lightThemeLabel.snp.makeConstraints { (make) in
make.centerX.equalTo(lightThemeImageView)
make.top.equalTo(lightThemeImageView.snp.bottom).offset(8)
}
lightThemeButton.snp.makeConstraints { (make) in
make.width.height.equalTo(22)
make.centerX.equalTo(lightThemeImageView)
make.top.equalTo(lightThemeLabel.snp.bottom).offset(6)
}
}
func prepareDarkTheme() {
darkThemeImageView.layer.cornerRadius = 10
darkThemeImageView.clipsToBounds = true
darkThemeImageView.contentMode = .scaleAspectFill
darkThemeImageView.image = UIImage(named: "theme_sample_dark")
container.addSubview(darkThemeImageView)
darkThemeLabel.textAlignment = .center
darkThemeLabel.font = AppFont.SFPro.regular(size: 16)
darkThemeLabel.text = "settings.theme.dark".localized()
darkThemeLabel.setContentHuggingPriority(.fittingSizeLevel, for: .vertical)
container.addSubview(darkThemeLabel)
darkThemeButton.addTarget(self, action: #selector(handleThemeButton(button:)), for: .touchUpInside)
container.addSubview(darkThemeButton)
//Constraints
darkThemeImageView.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 85, height: 154))
make.top.equalToSuperview().inset(24)
make.right.equalToSuperview().inset(57)
}
darkThemeLabel.snp.makeConstraints { (make) in
make.centerX.equalTo(darkThemeImageView)
make.top.equalTo(darkThemeImageView.snp.bottom).offset(8)
}
darkThemeButton.snp.makeConstraints { (make) in
make.width.height.equalTo(22)
make.centerX.equalTo(darkThemeImageView)
make.top.equalTo(darkThemeLabel.snp.bottom).offset(6)
}
}
func prepareSeparator() {
separatorView.backgroundColor = UIColor(hex: 0x979797)
container.addSubview(separatorView)
separatorView.snp.makeConstraints { (make) in
make.left.right.equalToSuperview().inset(30)
make.height.equalTo(1)
make.top.equalTo(lightThemeButton.snp.bottom).offset(16)
}
}
func prepareAutomaticViews() {
automaticThemeLabel.font = AppFont.SFPro.regular(size: 18)
automaticThemeLabel.text = "settings.theme.automatic".localized()
container.addSubview(automaticThemeLabel)
automaticDescriptionLabel.font = AppFont.SFPro.regular(size: 12)
automaticDescriptionLabel.text = "settings.theme.automaticDesc".localized()
automaticDescriptionLabel.numberOfLines = 0
automaticDescriptionLabel.lineBreakMode = .byWordWrapping
automaticDescriptionLabel.setContentCompressionResistancePriority(.init(rawValue: 249), for: .horizontal)
container.addSubview(automaticDescriptionLabel)
automaticSwitchButton.onTintColor = ThemeManager.currentTheme.graphTintColor
automaticSwitchButton.isOn = false
automaticSwitchButton.addTarget(self, action: #selector(handleAutomaticSwith), for: .valueChanged)
container.addSubview(automaticSwitchButton)
//Constraints
automaticThemeLabel.snp.makeConstraints { (make) in
make.top.equalTo(separatorView.snp.bottom).offset(23)
make.left.equalTo(separatorView)
}
automaticSwitchButton.snp.makeConstraints { (make) in
make.centerY.equalTo(automaticThemeLabel)
make.right.equalToSuperview().inset(18)
}
automaticDescriptionLabel.snp.makeConstraints { (make) in
make.left.equalTo(automaticThemeLabel)
make.top.equalTo(automaticThemeLabel.snp.bottom).inset(-2)
make.right.equalTo(automaticSwitchButton.snp.left).offset(-25)
make.bottom.equalToSuperview().inset(24)
}
}
}
//
// SettingsViewController.swift
// 1Weather
//
// Created by Dmitry Stepanets on 26.03.2021.
//
import UIKit
class SettingsViewController: UIViewController {
//Private
private let coordinator:SettingsCoordinator
private let settingsCellFactory:SettingsCellFactory
private let viewModel:SettingsViewModel
private let tableView = UITableView()
init(coordinator:SettingsCoordinator, settingsViewModel: SettingsViewModel) {
self.coordinator = coordinator
self.viewModel = settingsViewModel
self.settingsCellFactory = SettingsCellFactory(viewModel: viewModel)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
prepareController()
prepareTableView()
updateUI()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateUI()
}
private func updateUI() {
view.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
tableView.backgroundColor = ThemeManager.currentTheme.baseBackgroundColor
}
@objc private func handleCloseButton() {
self.dismiss(animated: true) {
self.coordinator.viewControllerDidEnd(controller: self)
}
}
}
//MARK:- Prepare
private extension SettingsViewController {
func prepareController() {
navigationItem.title = "menu.settings".localized().capitalized
let closeButton = UIBarButtonItem(title: "general.close".localized().capitalized,
style: .done,
target: self,
action: #selector(handleCloseButton))
navigationItem.leftBarButtonItem = closeButton
}
func prepareTableView() {
settingsCellFactory.registerCells(on: tableView)
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.tableFooterView = UIView()
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = UITableView.automaticDimension
view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
}
}
//MARK:- UITableView Data Source
extension SettingsViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return settingsCellFactory.numberOfSections
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return settingsCellFactory.numberOfRows(inSection: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return settingsCellFactory.cellFromTableView(tableView: tableView, indexPath: indexPath)
}
}
......@@ -18,9 +18,13 @@ private enum TodayCellType:Int, CaseIterable {
case moon
}
class TodayCellFactory {
class TodayCellFactory: CellFactoryProtocol {
public var onGetLocation:(() -> Location?)?
public var numberOfRows:Int {
public var numberOfSections: Int {
return 1
}
public func numberOfRows(inSection section: Int) -> Int {
return TodayCellType.allCases.count
}
......@@ -90,14 +94,4 @@ class TodayCellFactory {
break
}
}
//Private
private func registerCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView) {
tableView.register(type, forCellReuseIdentifier: T.kIdentifier)
}
private func dequeueReusableCell<T: ReusableCellProtocol>(type:T.Type, tableView:UITableView, indexPath: IndexPath) -> T {
let cell = tableView.dequeueReusableCell(withIdentifier: T.kIdentifier, for: indexPath) as! T
return cell
}
}
......@@ -110,7 +110,7 @@ private extension TodayViewController {
//MARK:- UITableView Data Source
extension TodayViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.todayCellFactory.numberOfRows
return viewModel.todayCellFactory.numberOfRows(inSection: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
......
//
// SettingsViewModel.swift
// 1Weather
//
// Created by Dmitry Stepanets on 26.03.2021.
//
import UIKit
class SettingsViewModel: ViewModelProtocol {
}
import Foundation
import UIKit
import XMLCoder
public struct WdtLocation: Codable {
public private (set) var lat: String?
public private (set) var lon: String?
public private (set) var city: String?
public private (set) var region: String?
public private (set) var country: String?
public private (set) var zipcode: String?
public var dailySummaries: [WdtDailySummary]
enum CodingKeys: String, CodingKey {
case lat
case lon
case city
case region
case country
case zipcode
case dailySummaries = "daily_summaries"
}
}
public struct WdtDailySummary: Codable {
public var minTempF: Double?
public var maxTempF: Double
public var windSpeedKph: Double?
public var precipitationProbability: Int?
public var sunriseUtc: String?
public var sunsetUtc: String?
public var moonriseUtc: String?
public var moonsetUtc: String?
public var date: String?
enum CodingKeys: String, CodingKey {
case minTempF = "min_temp_F"
case maxTempF = "max_temp_F"
case windSpeedKph = "wnd_spd_kph"
case precipitationProbability = "pop"
case sunriseUtc = "solunar_sunrise_utc"
case sunsetUtc = "solunar_sunset_utc"
case moonriseUtc = "solunar_moonrise_utc"
case moonsetUtc = "solunar_moonset_utc"
case date = "summary_date"
}
}
let xml = """
<!-- https://1weather.onelouder.com/feeds/onelouder/mega.php?LAT=39.95&LON=-75.16&UNITS=all -->
<!-- Philadelphia mega -->
<locations>
<units>all</units>
<language>en</language>
<q />
<location lat="39.95" lon="-75.16" city="Philadelphia" region="PA" country="United States of America" zipcode="19099">
<sfc_ob>
<stn_lat>39.867</stn_lat>
<stn_lon>-75.233</stn_lon>
<ob_time>2021-02-26 04:54:00</ob_time>
<day_of_week_local>Friday</day_of_week_local>
<temp_C>3</temp_C>
<temp_F>37</temp_F>
<dewp_F>21</dewp_F>
<dewp_C>-6</dewp_C>
<rh_pct>52</rh_pct>
<apparent_temp_F>32</apparent_temp_F>
<apparent_temp_C>0</apparent_temp_C>
<wnd_dir>N</wnd_dir>
<wnd_spd_mph>6</wnd_spd_mph>
<wnd_spd_kph>9</wnd_spd_kph>
<press_in>30.36</press_in>
<press_mb>1028.2</press_mb>
<pressure_change>Rising</pressure_change>
<wx>Cloudy</wx>
<wx_code>104</wx_code>
<cld_cover>Cloudy</cld_cover>
<day_night>night</day_night>
<visibility_ft>52800</visibility_ft>
<visibility_m>16093</visibility_m>
<moon_phase>Waxing Gibbous Moon</moon_phase>
<sunrise_local>06:36 am</sunrise_local>
<sunset_local>05:50 pm</sunset_local>
<precip_1hr_mm />
<precip_24hr_mm />
<precip_1hr_in />
<precip_24hr_in />
</sfc_ob>
<daily_summaries>
<daily_summary>
<summary_date>2/26/2021</summary_date>
<day_of_week>Friday</day_of_week>
<max_temp_F>46</max_temp_F>
<max_temp_C>8</max_temp_C>
<min_temp_F>32</min_temp_F>
<min_temp_C>0</min_temp_C>
<wnd_spd_mph>4</wnd_spd_mph>
<wnd_spd_kph>6</wnd_spd_kph>
<wnd_gust_mph>10</wnd_gust_mph>
<wnd_gust_kph>17</wnd_gust_kph>
<wnd_dir>SE</wnd_dir>
<pop>20</pop>
<wx>Partly cloudy</wx>
<wx_code>102</wx_code>
<text_description>Partly cloudy</text_description>
<sunrise_local>06:36 am</sunrise_local>
<sunset_local>05:50 pm</sunset_local>
<moon_phase>Waxing Gibbous Moon</moon_phase>
<early_morning_min_temp_F>32</early_morning_min_temp_F>
<early_morning_min_temp_C>0</early_morning_min_temp_C>
<early_morning_mode_wx>Mostly clear</early_morning_mode_wx>
<early_morning_pop>0</early_morning_pop>
<overnight_min_temp_F>37</overnight_min_temp_F>
<overnight_min_temp_C>3</overnight_min_temp_C>
<overnight_mode_wx>Partly cloudy</overnight_mode_wx>
<overnight_pop>100</overnight_pop>
<solunar_sunrise_utc>2021-02-26 11:37:00</solunar_sunrise_utc>
<solunar_sunset_utc>2021-02-26 22:49:00</solunar_sunset_utc>
<solunar_sun_state />
<solunar_moonrise_utc>2021-02-26 22:08:00</solunar_moonrise_utc>
<solunar_moonset_utc>2021-02-26 11:36:00</solunar_moonset_utc>
<solunar_moon_state />
</daily_summary>
</daily_summaries>
</location>
</locations>
"""
let dailyXml = """
<daily_summary>
<summary_date>2/26/2021</summary_date>
<day_of_week>Friday</day_of_week>
<max_temp_F>46</max_temp_F>
<max_temp_C>8</max_temp_C>
<min_temp_F>32</min_temp_F>
<min_temp_C>0</min_temp_C>
<wnd_spd_mph>4</wnd_spd_mph>
<wnd_spd_kph>6</wnd_spd_kph>
<wnd_gust_mph>10</wnd_gust_mph>
<wnd_gust_kph>17</wnd_gust_kph>
<wnd_dir>SE</wnd_dir>
<pop>20</pop>
<wx>Partly cloudy</wx>
<wx_code>102</wx_code>
<text_description>Partly cloudy</text_description>
<sunrise_local>06:36 am</sunrise_local>
<sunset_local>05:50 pm</sunset_local>
<moon_phase>Waxing Gibbous Moon</moon_phase>
<early_morning_min_temp_F>32</early_morning_min_temp_F>
<early_morning_min_temp_C>0</early_morning_min_temp_C>
<early_morning_mode_wx>Mostly clear</early_morning_mode_wx>
<early_morning_pop>0</early_morning_pop>
<overnight_min_temp_F>37</overnight_min_temp_F>
<overnight_min_temp_C>3</overnight_min_temp_C>
<overnight_mode_wx>Partly cloudy</overnight_mode_wx>
<overnight_pop>100</overnight_pop>
<solunar_sunrise_utc>2021-02-26 11:37:00</solunar_sunrise_utc>
<solunar_sunset_utc>2021-02-26 22:49:00</solunar_sunset_utc>
<solunar_sun_state />
<solunar_moonrise_utc>2021-02-26 22:08:00</solunar_moonrise_utc>
<solunar_moonset_utc>2021-02-26 11:36:00</solunar_moonset_utc>
<solunar_moon_state />
</daily_summary>
"""
let decoder = XMLDecoder()
let data = xml.data(using: .utf8)!
let dataDaily = dailyXml.data(using: .utf8)!
do {
let allLocations = try decoder.decode([WdtLocation].self, from: data)
if let location = allLocations.first {
print("\(location.dailySummaries)")
}
let daily = try decoder.decode(WdtDailySummary.self, from: dataDaily)
}
catch {
print("Error: \(error)")
}
print("Noe daily...")
do {
let daily = try decoder.decode(WdtDailySummary.self, from: dataDaily)
}
catch {
print("Error: \(error)")
}
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