Commit 66f0c4e5 by Demid Merzlyakov

Removed unused files.

parent 2bfd5b55
//
// PopularCitiesManager.swift
// 1Weather
//
// Created by Dmitry Stepanets on 03.12.2020.
//
import UIKit
class PopularCitiesManager {
func fetchPopularCities(completion:@escaping (_ result: Result<[GeoNamesPlace], Error>) -> Void) {
let ny = GeoNamesPlace()
ny.city = "New York"
ny.stateCode = "00001"
ny.state = "NY"
ny.country = "USA"
let chicago = GeoNamesPlace()
chicago.city = "Chicago"
chicago.stateCode = "00002"
chicago.state = "IL"
chicago.country = "USA"
let wg = GeoNamesPlace()
wg.city = "Washington DC"
wg.stateCode = "00003"
wg.state = "DC"
wg.country = "USA"
let boston = GeoNamesPlace()
boston.city = "Boston"
boston.stateCode = "00004"
boston.state = "MA"
boston.country = "USA"
completion(.success([ny, chicago, wg, boston]))
}
}
//
// GeoNamesPlace.swift
// OneWeather
//
// Created by Steven G Pint on 8/21/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import Foundation
class GeoNamesPlace: NSObject {
var latitude: String?
var longitude : String?
var city : String?
var state : String?
var stateCode : String?
var country : String?
var countryCode : String?
var fcodeName : String? // airport if airport
var toponymName : String? // airport name if fcodeName is airport
init(location: WdtLocation? = nil) {
guard let location = location else { return }
city = location.city
stateCode = location.region
countryCode = location.country
country = location.countryName
latitude = location.latitude(6)
longitude = location.longitude(6)
}
func detailName() -> String {
var sb = String()
if let state = self.state {
if state.count > 0 {
sb.append(state)
sb.append(", ")
}
}
if let country = self.country {
if country.count > 0 {
sb.append(country)
}
} else if let code = self.countryCode {
if code.count > 0 {
sb.append(code)
}
}
return sb
}
var fullName: String {
get {
var sb = String()
if city?.count ?? 0 > 0 {
sb.append(city!)
sb.append(", ")
}
if state?.count ?? 0 > 0 {
sb.append(state!)
sb.append(", ")
}
if country?.count ?? 0 > 0 {
sb.append(country!)
sb.append(", ")
}
if (sb.count > 0) {
sb = sb.trim()
}
return sb
}
}
override func isEqual(_ object: Any?) -> Bool {
if let object = object as? GeoNamesPlace {
return fullName == object.fullName
} else {
return false
}
}
override var hash: Int {
return fullName.hashValue
}
}
//
// WdtCondition.swift
// OneWeather
//
// Created by Steven G Pint on 8/13/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import UIKit
class WdtCondition: NSObject, NSCoding {
let TAG = "SfcOb"
var time = ""
var weekDay = ""
var tempC = ""
var tempF = ""
var temp: String {
get {
return isMetric() ? "\(tempC)\(DEGREES)" : "\(tempF)\(DEGREES)"
}
}
var dewC = ""
var dewF = ""
var dew: String {
get {
if dewF.count > 0 {
return isMetric() ? "\(dewC)\(DEGREES)" : "\(dewF)\(DEGREES)"
}
return "NA"
}
}
var humidity = ""
var humidityPercent: String {
get {
if humidity.count > 0 {
return "\(humidity)%"
}
return "NA"
}
}
var apparentTempF = ""
var apparentTempC = ""
var apparentTemp: String {
get {
return isMetric() ? "\(apparentTempC)\(DEGREES)" : "\(apparentTempF)\(DEGREES)"
}
}
var windDir = ""
var windSpeedMph = ""
var windSpeedKph = ""
var windSpeed: String {
get {
if windSpeedMph.count == 0 {
return "NA"
}
return windUnits().windSpeed(windSpeedMph, kph: windSpeedKph)
}
}
var wind: String {
get {
if windSpeed.count == 0 {
return "NA"
}
return "\(windDir) \(windSpeed)"
}
}
var pressureIn = ""
var pressureMb = ""
var pressureTendency = ""
var pressure: String {
get {
if pressureIn.isEmpty {
return "NA"
}
return pressureUnits().pressure(pressureIn, mb: pressureMb, tendency: pressureTendency)
}
}
var pressureAccessibility: String {
get {
if pressureIn.isEmpty {
return "Not Available".localized
}
return pressureUnits().pressure(pressureIn, mb: pressureMb, tendency: pressureTendency, accessibility: true)
}
}
var weatherDesc = ""
var weatherCode = ""
var cloudCoverDesc = ""
var isDay = false // assuming day|night are options
var moonPhase = ""
var moonPhaseDesc: String {
get {
switch moonPhase {
case "New Moon":
return "New\nMoon".localized
case "Waxing Crescent Moon":
return "Waxing\nCrescent".localized
case "Quarter Moon":
return "Quarter".localized
case "Waxing Gibbous Moon":
return "Waxing\nGibbous".localized
case "Full Moon":
return "Full".localized
case "Waning Gibbous Moon":
return "Waning\nGibbous".localized
case "Last Quarter Moon":
return "Last Quarter".localized
case "Waning Crescent Moon":
return "Waning\nCrescent".localized
default:
return "Unknown"
}
}
}
var sunriseTime = ""
var sunriseTimeDesc: String {
get {
let date = Date(dateString: sunriseTime, format: "h:mm a")
return date.getTimePartFull().uppercased()
}
}
var sunsetTime = ""
var sunsetTimeDesc: String {
get {
let date = Date(dateString: sunsetTime, format: "h:mm a")
return date.getTimePartFull().uppercased()
}
}
var precipHourIn = ""
var precipHourMm = ""
var precipHour: String {
get {
if precipHourIn == "0" || precipHourIn.count == 0 {
return ""
}
return isMetric() ? "\(precipHourMm)\(MILLIMETERS)" : "\(precipHourIn)\(INCHES)"
}
}
var precipDayIn = ""
var precipDayMm = ""
var precipDay: String {
get {
if precipDayIn == "0" || precipDayIn.count == 0 {
return ""
}
return isMetric() ? "\(precipDayMm)\(MILLIMETERS)" : "\(precipDayIn)\(INCHES)"
}
}
var visibilityFt = ""
var visibility: String {
get {
if visibilityFt.count > 0 && visibilityFt.lowercased() != "nan" {
let num: Double = distanceUnits() == DistanceUnits.kilometers ? 1000 : 5280
let desc = distanceUnits() == DistanceUnits.kilometers ? "KM" : "MI"
if let dvalue = Double(visibilityFt) {
let str = String(format:"%.0f", (dvalue / num).roundTo(0))
return "\(str) \(desc)"
}
}
return "NA"
}
}
override init() {
super.init()
}
// MARK: NSCoding
required init(coder decoder: NSCoder) {
// let version = decoder.decodeIntForKey("version")
time = decoder.decodeObject(forKey: "time") as! String
weekDay = decoder.decodeObject(forKey: "weekDay") as! String
tempC = decoder.decodeObject(forKey: "tempC") as! String
tempF = decoder.decodeObject(forKey: "tempF") as! String
dewC = decoder.decodeObject(forKey: "dewC") as! String
dewF = decoder.decodeObject(forKey: "dewF") as! String
humidity = decoder.decodeObject(forKey: "humidity") as! String
apparentTempF = decoder.decodeObject(forKey: "apparentTempF") as! String
apparentTempC = decoder.decodeObject(forKey: "apparentTempC") as! String
windDir = decoder.decodeObject(forKey: "windDir") as! String
windSpeedMph = decoder.decodeObject(forKey: "windSpeedMph") as! String
windSpeedKph = decoder.decodeObject(forKey: "windSpeedKph") as! String
pressureIn = decoder.decodeObject(forKey: "pressureIn") as! String
pressureMb = decoder.decodeObject(forKey: "pressureMb") as! String
pressureTendency = decoder.decodeObject(forKey: "pressureTendency") as! String
weatherDesc = decoder.decodeObject(forKey: "weatherDesc") as! String
weatherCode = decoder.decodeObject(forKey: "weatherCode") as! String
cloudCoverDesc = decoder.decodeObject(forKey: "cloudCoverDesc") as! String
isDay = decoder.decodeBool(forKey: "isDay")
moonPhase = decoder.decodeObject(forKey: "moonPhase") as! String
sunriseTime = decoder.decodeObject(forKey: "sunriseTime") as! String
sunsetTime = decoder.decodeObject(forKey: "sunsetTime") as! String
precipHourIn = decoder.decodeObject(forKey: "precipHourIn") as! String
precipHourMm = decoder.decodeObject(forKey: "precipHourMm") as! String
precipDayIn = decoder.decodeObject(forKey: "precipDayIn") as! String
precipDayMm = decoder.decodeObject(forKey: "precipDayMm") as! String
visibilityFt = decoder.decodeObject(forKey: "visibilityFt") as! String
}
func encode(with coder: NSCoder) {
coder.encodeCInt(1, forKey: "version")
coder.encode(self.time, forKey: "time")
coder.encode(self.weekDay, forKey: "weekDay")
coder.encode(self.tempC, forKey: "tempC")
coder.encode(self.tempF, forKey: "tempF")
coder.encode(self.dewC, forKey: "dewC")
coder.encode(self.dewF, forKey: "dewF")
coder.encode(self.humidity, forKey: "humidity")
coder.encode(self.apparentTempF, forKey: "apparentTempF")
coder.encode(self.apparentTempC, forKey: "apparentTempC")
coder.encode(self.windDir, forKey: "windDir")
coder.encode(self.windSpeedMph, forKey: "windSpeedMph")
coder.encode(self.windSpeedKph, forKey: "windSpeedKph")
coder.encode(self.pressureIn, forKey: "pressureIn")
coder.encode(self.pressureMb, forKey: "pressureMb")
coder.encode(self.pressureTendency, forKey: "pressureTendency")
coder.encode(self.weatherDesc, forKey: "weatherDesc")
coder.encode(self.weatherCode, forKey: "weatherCode")
coder.encode(self.cloudCoverDesc, forKey: "cloudCoverDesc")
coder.encode(self.isDay, forKey: "isDay")
coder.encode(self.moonPhase, forKey: "moonPhase")
coder.encode(self.sunriseTime, forKey: "sunriseTime")
coder.encode(self.sunsetTime, forKey: "sunsetTime")
coder.encode(self.precipHourIn, forKey: "precipHourIn")
coder.encode(self.precipHourMm, forKey: "precipHourMm")
coder.encode(self.precipDayIn, forKey: "precipDayIn")
coder.encode(self.precipDayMm, forKey: "precipDayMm")
coder.encode(self.visibilityFt, forKey: "visibilityFt")
}
}
//
// WdtHourSummary.swift
// OneWeather
//
// Created by Steven G Pint on 8/13/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import UIKit
enum DayPhase: Int {
case morn, noon, eve, night
var description: String {
switch self {
case .morn:
return "MORN".localized
case .noon:
return "NOON".localized
case .eve:
return "EVE".localized
case .night:
return "NIGHT".localized
}
}
var accessibilityDescription: String {
switch self {
case .morn:
return "Morning"
case .noon:
return "Noon"
case .eve:
return "Evening"
case .night:
return "Night"
}
}
static let daySegments = [7, 12, 18, 23]
}
class WdtHourSummary: NSObject, NSCoding {
let TAG = "WdtHourSummary"
var day = ""
var time = ""
// var time = "" {
// didSet(value) {
// if value.length > 0 {
// _date = NSDate(dateString: value)
// time = value
// }
// }
// }
fileprivate var _date: Date?
var date: Date {
get {
if _date == nil {
_date = Date(dateString: self.time)
}
return _date!
}
}
var hour: Int {
get {
return Calendar.current.component(.hour, from: date)
}
}
var dayPhase: DayPhase = .morn
var dayPart: String {
get {
if self.time.count > 0 {
return date.getDayPart().uppercased()
} else {
return "NA"
}
}
}
var timePart: String {
get {
if self.time.count > 0 {
return date.getTimePart()
} else {
return "NA"
}
}
}
var tempC = ""
var tempF = ""
var temp: String {
get {
return isMetric() ? "\(tempC)\(DEGREES)" : "\(tempF)\(DEGREES)"
}
}
var tempNum: Double {
get {
return (isMetric() ? Double(tempC) : Double(tempF))!
}
}
var isFreezing: Bool {
get {
if isMetric() {
if let f = Int(tempC) {
return f <= 0
}
} else {
if let f = Int(tempF) {
return f <= 32
}
}
return false
}
}
var dewPointC = ""
var dewPointF = ""
var apparentTempC = ""
var apparentTempF = ""
var apparentTemp: String {
get {
return isMetric() ? apparentTempC : apparentTempF
}
}
var tempFeels: String {
get {
return "\(temp) Feels \(apparentTemp)" // degree symbol
}
}
var humidityPercent = ""
var pressureIn = ""
var pressureMb = ""
var weatherDesc = ""
var weatherCode = ""
var precip = ""
var precipPercent: String {
get {
return "\(precip)%"
}
}
var precipDouble: Double? {
get {
return Double(precip)
}
}
var windDir = ""
var windSpeedKph = ""
var windSpeedMph = ""
var windSpeed: String {
get {
return windUnits().windSpeed(windSpeedMph, kph: windSpeedKph)
}
}
var wind: String {
get {
return "\(windDir) \(windSpeed)"
}
}
override init() {
super.init()
}
required init(coder decoder: NSCoder) {
// let version = decoder.decodeIntForKey("version")
day = decoder.decodeObject(forKey: "day") as! String
time = decoder.decodeObject(forKey: "time") as! String
tempC = decoder.decodeObject(forKey: "tempC") as! String
tempF = decoder.decodeObject(forKey: "tempF") as! String
dewPointC = decoder.decodeObject(forKey: "dewPointC") as! String
dewPointF = decoder.decodeObject(forKey: "dewPointF") as! String
apparentTempC = decoder.decodeObject(forKey: "apparentTempC") as! String
apparentTempF = decoder.decodeObject(forKey: "apparentTempF") as! String
humidityPercent = decoder.decodeObject(forKey: "humidityPercent") as! String
pressureIn = decoder.decodeObject(forKey: "pressureIn") as! String
pressureMb = decoder.decodeObject(forKey: "pressureMb") as! String
weatherDesc = decoder.decodeObject(forKey: "weatherDesc") as! String
weatherCode = decoder.decodeObject(forKey: "weatherCode") as! String
precip = decoder.decodeObject(forKey: "precip") as! String
windDir = decoder.decodeObject(forKey: "windDir") as! String
windSpeedKph = decoder.decodeObject(forKey: "windSpeedKph") as! String
windSpeedMph = decoder.decodeObject(forKey: "windSpeedMph") as! String
}
func encode(with coder: NSCoder) {
coder.encodeCInt(1, forKey: "version")
coder.encode(self.day, forKey: "day")
coder.encode(self.time, forKey: "time")
coder.encode(self.tempC, forKey: "tempC")
coder.encode(self.tempF, forKey: "tempF")
coder.encode(self.dewPointC, forKey: "dewPointC")
coder.encode(self.dewPointF, forKey: "dewPointF")
coder.encode(self.apparentTempC, forKey: "apparentTempC")
coder.encode(self.apparentTempF, forKey: "apparentTempF")
coder.encode(self.humidityPercent, forKey: "humidityPercent")
coder.encode(self.pressureIn, forKey: "pressureIn")
coder.encode(self.pressureMb, forKey: "pressureMb")
coder.encode(self.weatherDesc, forKey: "weatherDesc")
coder.encode(self.weatherCode, forKey: "weatherCode")
coder.encode(self.precip, forKey: "precip")
coder.encode(self.windDir, forKey: "windDir")
coder.encode(self.windSpeedKph, forKey: "windSpeedKph")
coder.encode(self.windSpeedMph, forKey: "windSpeedMph")
}
var isDay: Bool {
get {
if (hour > 6 && hour < 19) {
return true
}
return false
}
}
}
//
// WdtLocation.swift
// OneWeather
//
// Created by Steven G Pint on 8/5/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import UIKit
class WdtLocation: NSObject, NSCoding {
static let LOCATION = "location"
static let CONDITIONS = "conditions"
static let DAYSUMMARIES = "daysummaries"
static let HOURSUMMARIES = "hoursummaries"
static let LONGRANGEFORECASTS = "longrangeforecasts"
static let NWSALERTS = "nwsalerts"
static let NWSDISCUSSIONS = "nwsdiscussions"
let DEFAULT_LOCATION = "default_location"
let STALE_THRESHOLD: TimeInterval = 15 * 60 // 15 minutes
let ALERT_STALE_THRESHOLD: TimeInterval = 60 // 60 seconds
let MY_LOCATION_FILE_NAME = "_default"
var city: String?
var region: String? // aka state
var country: String? // actually countryCode (i.e US)
var countryName: String? // actually country (i.e United States)
var zip: String?
fileprivate var _nickName: String?
var nickName: String?
var fullName: String {
get {
var sb = String()
if city?.count ?? 0 > 0 {
sb.append(city!)
sb.append(", ")
}
if region?.count ?? 0 > 0 {
sb.append(region!)
sb.append(", ")
}
if country?.count ?? 0 > 0 {
sb.append(country!)
sb.append(", ")
}
if (sb.count > 0) {
sb = sb.trim(", ") // trim comma and space
}
return sb
}
}
var cityName: String {
get {
if let nickName = self.nickName, nickName.count > 0 {
return nickName
}
return city ?? ""
}
}
var cityStateName: String {
get {
if let nickName = self.nickName, nickName.count > 0 {
return nickName
}
var sb = String()
if city?.count ?? 0 > 0 {
sb.append(city!)
sb.append(", ")
}
if region?.count ?? 0 > 0 {
sb.append(region!)
sb.append(", ")
} else if country?.count ?? 0 > 0 {
if !(myLocation && isUSA) {
sb.append(country!)
sb.append(", ")
}
}
sb = sb.trim(", ")
return sb
}
}
var cityState: String
var myLocation = false
var selectedLocation = false
var geoPointLat: Double = -1
var geoPointLong: Double = -1
var lastUpdateTime: TimeInterval = 0
var lastPartialUpdateTime: TimeInterval = 0 // background updates will no longer update all data
var lastUpdateAttemptedTime: TimeInterval = 0
var lastNwsDiscussionUpdateTime: TimeInterval = 0
// Not cached, used to make sure we don't check for new alerts too often
var lastAlertUpdateTime: TimeInterval = 0
fileprivate var _currentConditions: WdtCondition?
var currentConditions: WdtCondition?
var uvIndex: String?
var uvDesc: String
var _timezone: String?
var timezone: String?
var _timezoneOffset: Double?
var timezoneOffset: String?
// NWS ALERTS
// NWS region code
var fips: String?
// private ArrayList<NwsAfdSection> afdSections
func latitude(_ maxFraction: Int = 3) -> String {
let p = pow(10.0, Double(maxFraction))
let y = Double(round(p * geoPointLat) / p)
return String(y)
}
func longitude(_ maxFraction: Int = 3) -> String {
let p = pow(10.0, Double(maxFraction))
let y = Double(round(p * geoPointLong) / p)
return String(y)
}
// add a single alert - e.g. from a push event
// base weather class data is saved in Documents directory so it doesn't get deleted by the system
/*
* for location that should follow you
*/
override init() {
cityState = ""
uvDesc = ""
detailName = ""
locationId = "us"
fileName = "us"
simpleFileName = "us"
isUSA = true
super.init()
myLocation = true
}
init(zip: String?, city: String?, region: String?, countryCode: String?, country: String?, lat: String?, lng: String?) {
cityState = ""
uvDesc = ""
detailName = ""
locationId = "us"
fileName = "us"
simpleFileName = "us"
isUSA = true
super.init()
self.zip = zip
self.city = city
self.region = region
self.countryName = country
if let co = countryCode {
switch co.count {
case 3 where countryCode == "USA":
self.country = "US"
default:
self.country = co
}
}
if let lat1 = lat {
setLatitude(lat1)
}
if let lng1 = lng {
setLongitude(lng1)
}
}
convenience init(place: GeoNamesPlace) {
self.init(zip: nil, city: place.city, region: place.stateCode, countryCode: place.countryCode, country: place.country, lat: place.latitude, lng: place.longitude)
}
// MARK: NSCoding
required init(coder decoder: NSCoder) {
// let version = decoder.decodeIntForKey("version")
cityState = ""
uvDesc = ""
detailName = ""
locationId = ""
fileName = ""
simpleFileName = ""
isUSA = true
self.city = decoder.decodeObject(forKey: "city") as? String
self._nickName = decoder.decodeObject(forKey: "nickName") as? String
self.country = decoder.decodeObject(forKey: "country") as? String
self.countryName = decoder.decodeObject(forKey: "countryName") as? String
self.region = decoder.decodeObject(forKey: "region") as? String
self.geoPointLat = decoder.decodeDouble(forKey: "geoPointLat")
self.geoPointLong = decoder.decodeDouble(forKey: "geoPointLong")
self.lastUpdateTime = decoder.decodeDouble(forKey: "lastUpdateTime")
self.lastPartialUpdateTime = decoder.decodeDouble(forKey: "lastPartialUpdateTime")
self.lastUpdateAttemptedTime = decoder.decodeDouble(forKey: "lastUpdateAttemptedTime")
self.uvIndex = decoder.decodeObject(forKey: "uvIndex") as? String
self.myLocation = decoder.decodeBool(forKey: "myLocation")
self.selectedLocation = decoder.decodeBool(forKey: "selectedLocation")
self._timezone = decoder.decodeObject(forKey: "timezone") as? String
self._timezoneOffset = decoder.decodeDouble(forKey: "timezoneOffset")
self.fips = decoder.decodeObject(forKey: "fips") as? String
self.lastNwsDiscussionUpdateTime = decoder.decodeDouble(forKey: "lastNwsDiscussionUpdateTime")
//print("************** decode city: \(city) nickName: \(self._nickName)")
}
func encode(with coder: NSCoder) {
coder.encodeCInt(1, forKey: "version")
coder.encode(self.city, forKey: "city")
coder.encode(self._nickName, forKey: "nickName")
coder.encode(self.country, forKey: "country")
coder.encode(self.countryName, forKey: "countryName")
coder.encode(self.region, forKey: "region")
coder.encode(self.geoPointLat, forKey: "geoPointLat")
coder.encode(self.geoPointLong, forKey: "geoPointLong")
coder.encode(self.lastUpdateTime, forKey: "lastUpdateTime")
coder.encode(self.lastPartialUpdateTime, forKey: "lastPartialUpdateTime")
coder.encode(self.lastUpdateAttemptedTime, forKey: "lastUpdateAttemptedTime")
coder.encode(self.uvIndex, forKey: "uvIndex")
coder.encode(self.myLocation, forKey: "myLocation")
coder.encode(self.selectedLocation, forKey: "selectedLocation")
coder.encode(self._timezone, forKey: "timezone")
if self._timezoneOffset != nil {
coder.encode(self._timezoneOffset!, forKey: "timezoneOffset")
}
coder.encode(self.fips, forKey: "fips")
coder.encode(self.lastNwsDiscussionUpdateTime, forKey: "lastNwsDiscussionUpdateTime")
//print("*************** encode city: \(city) nickName: \(self._nickName)")
}
// Since multiple existing icon condition lookups use WDT condition codes, easiest to
// translate Weather2020 codes -> WDT
var detailName: String
var isDay: Bool = false
// Calculates totale minutes of the day based on dayTimeString (i.e 07:40 am, 06:10 pm
var locationId: String
var fileName: String
var simpleFileName: String
// remove non letter/digits
func setLatitude(_ latitude: String?) {
if let l = latitude, !l.isEmpty, let value = Double(l) {
geoPointLat = value
}
}
func setLongitude(_ longitude: String?) {
if let l = longitude, !l.isEmpty, let value = Double(l) {
geoPointLong = value
}
}
override var hash: Int {
if myLocation {
return "\(geoPointLat.roundTo(2)),\(geoPointLong.roundTo(2))".hash
} else {
return fullName.hash
}
}
var isUSA: Bool
}
//
// WeatherUpdateManager.swift
// OneWeather
//
// Created by Steven G Pint on 9/28/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import Foundation
import UserNotifications
class WeatherUpdateManager: NSObject {
static let shared = WeatherUpdateManager()
private override init() {
super.init()
let sf = WdtLocation(zip: "000001",
city: "San-Francisco",
region: "CA",
countryCode: "US",
country: "USA",
lat: "0",
lng: "0")
sf.currentConditions = WdtCondition()
sf.currentConditions?.tempC = "30"
sf.selectedLocation = true
let seattle = WdtLocation(zip: "000002",
city: "Seattle",
region: "WA",
countryCode: "US",
country: "USA",
lat: "0",
lng: "0")
seattle.currentConditions = WdtCondition()
seattle.currentConditions?.tempC = "17"
seattle.selectedLocation = false
self.allWdtLocations = [sf, seattle]
}
var allLocationsCount: Int {
return allWdtLocations?.count ?? 0
}
// set private var so we don't have to access disk all the time
private(set) var allWdtLocations: [WdtLocation]?
// Adds the location if it's not a duplicate, makes flurry event, registered for PP notifications, sets this as selected location
// Return true if it added as a new location, false if it didn't add it or it was a dup
func addLocation(_ location: WdtLocation) -> Bool {
print("Add location \(location)")
allWdtLocations?.forEach{$0.selectedLocation = false}
if let index = (allWdtLocations?.firstIndex{$0.hash == location.hash}) {
allWdtLocations?[index].selectedLocation = true
}
else {
location.selectedLocation = true
allWdtLocations?.append(location)
}
return true
}
// Deletes the location if found, register/deregister for PP notifications, change selected location to first item if it's the same as deleted location
// Return true if it added as a new location, false if it didn't add it or it was a dup
func deleteLocation(_ location: WdtLocation) -> Bool {
print("Delete location \(location)")
if let locationToDeleteIndex = (allWdtLocations?.firstIndex{location.hash == $0.hash}) {
allWdtLocations?.remove(at: locationToDeleteIndex)
if location.selectedLocation {
allWdtLocations?.first?.selectedLocation = true
}
}
return true
}
var defaultWdtLocation: WdtLocation? {
get {
if let locs = allWdtLocations, locs.first?.myLocation ?? false {
return locs.first
}
return nil
}
}
var selectedWdtLocation: WdtLocation?
}
//
// CityCell.swift
// 1Weather
//
// Created by Dmitry Stepanets on 30.11.2020.
//
import UIKit
class CityCell: UITableViewCell {
//Public
static let kIdentifier = "cityCell"
var onSelect:(() -> Void)?
var onAdd:(() -> Void)?
//Private
private let cityLabel = UILabel()
private let selectedButton = UIButton()
private let addButton = UIButton()
private let temperatureLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
prepareCell()
prepareCityLabel()
prepareAddButton()
prepareSelectButton()
prepareTemperatureLabel()
}
func configure(wdtLocation:WdtLocation, mode:LocationsViewModelDisplayMode) {
cityLabel.text = wdtLocation.cityStateName
temperatureLabel.text = wdtLocation.currentConditions?.temp ?? "--"
switch mode {
case .savedCities:
addButton.isHidden = true
selectedButton.isHidden = false
UIView.performWithoutAnimation {
self.selectedButton.tintColor = wdtLocation.selectedLocation ? ThemeManager.Colors.citySelected : ThemeManager.Colors.cityNoSelected
}
case .popularCities, .searchResults:
addButton.isHidden = false
selectedButton.isHidden = true
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleAddButton() {
onAdd?()
}
@objc private func handleSelectButton() {
onSelect?()
}
}
//MARK: Prepare
private extension CityCell {
func prepareCell() {
backgroundColor = ThemeManager.currentTheme.navigationBarBackgroundColor
contentView.backgroundColor = ThemeManager.currentTheme.navigationBarBackgroundColor
selectionStyle = .none
}
func prepareCityLabel() {
cityLabel.font = AppFont.SFPro.regular(size: 14)
cityLabel.numberOfLines = 1
cityLabel.lineBreakMode = .byTruncatingTail
cityLabel.textColor = ThemeManager.currentTheme.primaryTextColor
cityLabel.setContentHuggingPriority(.init(900), for: .horizontal)
contentView.addSubview(cityLabel)
cityLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(24)
make.centerY.equalToSuperview()
}
}
func prepareTemperatureLabel() {
let container = UIView()
container.backgroundColor = ThemeManager.Colors.temperatureLabelBG
container.clipsToBounds = true
container.layer.cornerRadius = 5
container.setContentHuggingPriority(.init(1000), for: .horizontal)
temperatureLabel.text = "27°"
temperatureLabel.font = AppFont.SFPro.semibold(size: 16)
temperatureLabel.textColor = ThemeManager.currentTheme.primaryTextColor
temperatureLabel.setContentHuggingPriority(.init(1000), for: .horizontal)
temperatureLabel.setContentCompressionResistancePriority(.init(1000), for: .horizontal)
container.addSubview(temperatureLabel)
temperatureLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(6)
make.right.equalToSuperview().inset(6)
make.top.equalToSuperview().inset(4)
make.bottom.equalToSuperview().inset(4)
}
contentView.addSubview(container)
container.snp.makeConstraints { (make) in
make.left.equalTo(cityLabel.snp.right).offset(14)
make.centerY.equalTo(cityLabel)
make.right.lessThanOrEqualTo(addButton.snp.left).offset(-14)
}
}
func prepareAddButton() {
addButton.isHidden = false
addButton.setTitle("+ ADD", for: .normal)
addButton.titleLabel?.font = AppFont.SFPro.regular(size: 12)
addButton.setTitleColor(ThemeManager.Colors.locationBlue, for: .normal)
addButton.backgroundColor = ThemeManager.Colors.cityAddButtonBG
addButton.layer.cornerRadius = 12
addButton.addTarget(self, action: #selector(handleAddButton), for: .touchUpInside)
addButton.layer.shadowOffset = .init(width: 0, height: 2)
addButton.layer.shadowColor = UIColor.black.cgColor
addButton.layer.shadowRadius = 2
addButton.layer.shadowOpacity = 0.5
addButton.layer.shadowPath = UIBezierPath(roundedRect: .init(origin: .zero, size: .init(width: 55, height: 24)),
cornerRadius: 12).cgPath
contentView.addSubview(addButton)
addButton.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 55, height: 24))
make.right.equalToSuperview().inset(24)
make.centerY.equalToSuperview()
}
}
func prepareSelectButton() {
selectedButton.isHidden = true
selectedButton.imageView?.contentMode = .scaleAspectFit
selectedButton.setImage(UIImage(named: "city_checkmark"), for: .normal)
selectedButton.tintColor = ThemeManager.Colors.cityNoSelected
selectedButton.addTarget(self, action: #selector(handleAddButton), for: .touchUpInside)
contentView.addSubview(selectedButton)
selectedButton.snp.makeConstraints { (make) in
make.right.equalToSuperview().inset(24)
make.size.equalTo(CGSize(width: 24, height: 24))
make.centerY.equalToSuperview()
}
}
}
//
// LocationsViewModel.swift
// 1Weather
//
// Created by Dmitry Stepanets on 30.11.2020.
//
import UIKit
import AlgoliaSearchClient
protocol LocationsViewModelDelegate:class {
func viewModelDidChange(model:LocationsViewModel)
func viewModelDisplayModeDidChange(model: LocationsViewModel)
func viewModelError(model:LocationsViewModel, title:String?, message:String?)
}
enum LocationsViewModelDisplayMode {
case savedCities
case popularCities
case searchResults
}
class LocationsViewModel {
//Public
weak var delegate:LocationsViewModelDelegate?
var cities:[WdtLocation] {
switch self.displayMode {
case .savedCities:
return WeatherUpdateManager.shared.allWdtLocations ?? [WdtLocation]()
case .popularCities:
return self.popularCities
case .searchResults:
return self.fetchedCities
}
}
var displayMode:LocationsViewModelDisplayMode = .savedCities {
didSet {
if oldValue == .searchResults {
clean()
}
self.delegate?.viewModelDisplayModeDidChange(model: self)
}
}
//Private
private let popularCitiesManager = PopularCitiesManager()
private var popularCities = [WdtLocation]() {
didSet {
assert(Thread.isMainThread)
self.delegate?.viewModelDidChange(model: self)
}
}
private var fetchedCities = [WdtLocation]() {
didSet {
assert(Thread.isMainThread)
self.delegate?.viewModelDidChange(model: self)
}
}
//MARK:- ViewModel life cycle
func clean() {
fetchedCities.removeAll()
}
func fetchPopularCities() {
self.popularCitiesManager.fetchPopularCities {[weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success(let popularCities):
DispatchQueue.main.async {
strongSelf.popularCities = popularCities.map{return WdtLocation(place: $0)}
}
case .failure(let error):
strongSelf.delegate?.viewModelError(model: strongSelf, title: "Error", message: error.localizedDescription)
}
}
}
func fetchCities(query:String) {
// This is a test location that can be interpreted on the server to return all alerts
guard query != "1wville" else {
let fakePlace = GeoNamesPlace()
fakePlace.latitude = "67.25639"
fakePlace.longitude = "-150.18417"
fakePlace.city = "1WVille"
fakePlace.state = "Alaska"
fakePlace.stateCode = "AK"
fakePlace.country = "United States"
fakePlace.countryCode = "US"
fakePlace.fcodeName = "populated place"
fakePlace.toponymName = "1WVille"
DispatchQueue.main.async {
self.fetchedCities = []
}
return
}
Logger.minSeverityLevel = .warning
var algoliaQuery = PlacesQuery(query)
algoliaQuery.aroundLatLngViaIP = true
algoliaQuery.type = .city
let placesClient: PlacesClient = PlacesClient(appID: ApplicationID (rawValue: kAlgoliaAppId),
apiKey: APIKey(rawValue: kAlgoliaAPIKey))
let language: Language = Language(rawValue: NSLocale.preferredLanguages.first?.components(separatedBy: "-").first ?? "en")
print("Search: using language \(language.rawValue)")
var filteredPlaces = [GeoNamesPlace]()
placesClient.search(query: algoliaQuery, language: language, requestOptions: nil) {[weak self] (result: Result<PlacesClient.SingleLanguageResponse, Error>) in
guard let strongSelf = self else { return }
switch (result) {
case .success(let response):
print("Search: got \(response.nbHits) results")
for hit: Hit<Place> in response.hits {
let place = GeoNamesPlace()
if hit.object.localeNames?.first == nil {
print("Search: skip 1 object.");
continue
}
place.city = hit.object.localeNames?.first
place.state = hit.object.administrative?.first
place.country = hit.object.country ?? "" // ?? "" part is from the Android code, may be not needed on iOS
place.countryCode = hit.object.countryCode?.rawValue.uppercased() ?? "" // ?? "" part is from the Android code, may be not needed on iOS
if let geolocation = hit.geolocation {
switch geolocation {
case .single(let location):
place.latitude = "\(location.latitude)"
place.longitude = "\(location.longitude)"
case .list(let locations):
if let firstLat = locations.first?.latitude,
let firstLong = locations.first?.longitude
{
place.latitude = "\(firstLat)"
place.longitude = "\(firstLong)"
}
else {
place.latitude = ""
place.longitude = ""
}
}
}
else {
place.latitude = ""
place.longitude = ""
}
filteredPlaces.append(place)
}
DispatchQueue.main.async {
strongSelf.fetchedCities = filteredPlaces.map{return WdtLocation(place: $0)}
}
break
case .failure(let error):
DispatchQueue.main.async {
strongSelf.delegate?.viewModelError(model: strongSelf, title: "Error", message: error.localizedDescription)
}
break
}
}
}
//MARK:- City CUD methods
func add(city:WdtLocation) {
// TODO: Just update self.cities, it will trigger a viewModelDidChange call
if WeatherUpdateManager.shared.allLocationsCount >= 12 {
self.delegate?.viewModelError(model: self, title: nil, message: "To keep your 1Weather running in tip-top shape, please limit the number of locations to 12.".localized)
return
}
if WeatherUpdateManager.shared.addLocation(city) {
self.displayMode = .savedCities
}
else {
self.delegate?.viewModelError(model: self, title: "Error", message: "Failed to add location")
}
}
func delete(city:WdtLocation) {
// TODO: Just update self.cities, it will trigger a viewModelDidChange call
if WeatherUpdateManager.shared.deleteLocation(city) {
self.delegate?.viewModelDidChange(model: self)
}
else {
self.delegate?.viewModelError(model: self, title: "Error", message: "Failed to delete location")
}
}
func select(city:WdtLocation) {
// TODO: Tell delegate to refresh UI
if WeatherUpdateManager.shared.addLocation(city) {
self.delegate?.viewModelDidChange(model: self)
}
else {
self.delegate?.viewModelError(model: self, title: "Error", message: "Failed to add location")
}
}
}
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