Commit f0abd1dc by Demid Merzlyakov

Merge branch 'new-model'

# Conflicts:
#	1Weather.xcodeproj/project.pbxproj
#	Podfile
#	Podfile.lock
#	Pods/Manifest.lock
#	Pods/Pods.xcodeproj/project.pbxproj
#	Pods/Target Support Files/Pods-1Weather/Pods-1Weather.debug.xcconfig
#	Pods/Target Support Files/Pods-1Weather/Pods-1Weather.release.xcconfig
parents fc2ee488 6872d700
//
// Logger.swift
// OneWeather
//
// Created by Demid Merzlyakov on 18.01.2021.
// Copyright © 2021 OneLouder, Inc. All rights reserved.
//
import Foundation
final class Logger {
enum LogLevel {
case debug
case info
case warning
case error
}
static let prefix = "psm_1w"
var componentName: String
init(componentName: String) {
self.componentName = componentName
}
func log(level: LogLevel, message: String) {
#if DEBUG
NSLog("\(Logger.prefix) \(componentName) [\(logLevelString(level: level))]: \(message)")
#endif
}
func debug(_ message: String) {
log(level: .debug, message: message)
}
func info(_ message: String) {
log(level: .info, message: message)
}
func warning(_ message: String) {
log(level: .warning, message: message)
}
func error(_ message: String) {
log(level: .error, message: message)
}
private func logLevelString(level: LogLevel) -> String {
switch level {
case .debug:
return "DEBUG"
case .info:
return "INFO"
case .warning:
return "WARN"
case .error:
return "ERROR"
}
}
}
//
// MulticastDelegate.swift
// OneWeather
//
// Created by Demid Merzlyakov on 11.01.2021.
// Copyright © 2021 OneLouder, Inc. All rights reserved.
//
import Foundation
public class MulticastDelegate <T> {
public init() {}
private let delegates: NSHashTable<AnyObject> = NSHashTable.weakObjects()
public func add(delegate: T) {
if delegates.contains(delegate as AnyObject) {
return
}
delegates.add(delegate as AnyObject)
}
public func remove(delegate: T) {
for oneDelegate in delegates.allObjects.reversed() {
if oneDelegate === delegate as AnyObject {
delegates.remove(oneDelegate)
}
}
}
public func invoke(invocation: (T) -> ()) {
for delegate in delegates.allObjects.reversed() {
invocation(delegate as! T)
}
}
}
public func += <T: AnyObject> (left: MulticastDelegate<T>, right: T) {
left.add(delegate: right)
}
public func -= <T: AnyObject> (left: MulticastDelegate<T>, right: T) {
left.remove(delegate: right)
}
//
// LocationManager.swift
// 1Weather
//
// Created by Demid Merzlyakov on 20.02.2021.
//
import Foundation
public protocol LocationManagerDelegate: class {
func locationManager(_ locationManager: LocationManager, changedCurrentLocation newLocation: Location?)
}
public class LocationManager {
private let log = Logger(componentName: "LocationManager")
private let delegates = MulticastDelegate<LocationManagerDelegate>()
public var currentLocation: Location? {
didSet {
if currentLocation != oldValue {
log.info("Current location changed to: \(currentLocation?.description ?? "nil")")
delegates.invoke { [weak self] (delegate) in
guard let self = self else { return }
delegate.locationManager(self, changedCurrentLocation: currentLocation)
}
}
else {
log.debug("location updated without changing")
}
}
}
public func add(delegate: LocationManagerDelegate) {
delegates.add(delegate: delegate)
}
public func remove(delegate: LocationManagerDelegate) {
delegates.remove(delegate: delegate)
}
}
//
// CurrentWeather.swift
// 1Weather
//
// Created by Demid Merzlyakov on 19.02.2021.
//
import Foundation
public struct CurrentWeather {
public var date: Date
public var timeZone: TimeZone
public var weekDay: WeekDay
public var type: WeatherType = .unknown
public var minTemp: Temperature?
public var maxTemp: Temperature?
public var windSpeed: WindSpeed?
public var windDirection: WindDirection?
public var precipitationProbability: Percent?
public var temp: Temperature?
public var apparentTemp: Temperature?
public var humidity: Percent?
public var visibility: Visibility?
public var pressure: Pressure?
public var sunrise: Time?
public var sunset: Time?
public var sunState: CelestialState = .normal
public var moonrise: Time?
public var moonset: Time?
public var moonState: CelestialState = .normal
}
//
// DailyWeather.swift
// 1Weather
//
// Created by Demid Merzlyakov on 19.02.2021.
//
import Foundation
public struct DailyWeather {
public var date: Date
public var timeZone: TimeZone
public var weekDay: WeekDay
public var type: WeatherType = .unknown
public var minTemp: Temperature?
public var maxTemp: Temperature?
public var windSpeed: WindSpeed?
public var windDirection: WindDirection?
public var precipitationProbability: Percent?
public var sunrise: Date
public var sunset: Date
public var sunState: CelestialState = .normal
public var moonrise: Date
public var moonset: Date
public var moonState: CelestialState = .normal
}
//
// WindDirection.swift
// 1Weather
//
// Created by Demid Merzlyakov on 19.02.2021.
//
import Foundation
public enum WeatherType: String {
case clearDay = "clearDay"
case partlyCloudyDay = "partlyCloudyDay"
case snowyDay = "snowyDay"
case snowyNight = "snowyNight"
case clearNight = "clearNight"
case partlyCloudyNight = "partlyCloudyNight"
case thunderstorm = "thunderstorm"
case heavySnow = "heavySnow"
case lightSnow = "lightSnow"
case freezingRain = "freezingRain"
case freezingFog = "freezingFog"
case lightHail = "lightHail"
case blowingDust = "blowingDust"
case haze = "haze"
case lightFog = "lightFog"
case mostlyCloudyDay = "mostlyCloudyDay"
case mostlyCloudyNight = "mostlyCloudyNight"
case lightDrizzle = "lightDrizzle"
case heavyRain = "heavyRain"
case smoke = "smoke"
case unknown = "unknown"
}
public enum WindDirection: String, Codable {
case north = "N"
case northNorthEast = "NNE"
case northEast = "NE"
case eastNorthEast = "ENE"
case east = "E"
case eastSouthEast = "ESE"
case southEast = "SE"
case southSouthEast = "SSE"
case south = "S"
case southSouthWest = "SSW"
case southWest = "SW"
case westSouthWest = "WSW"
case west = "W"
case westNorthWest = "WNW"
case northWest = "NW"
case northNorthWest = "NNW"
}
/// Not everybody uses a 7 day week, but not a lot of people.
public enum WeekDay: String, Codable {
case monday = "Monday"
case tuesday = "Tuesday"
case wednesday = "Wednesday"
case thursday = "Thursday"
case friday = "Friday"
case saturday = "Saturday"
case sunday = "Sunday"
}
public enum CelestialState {
case normal
case alwaysUp
case neverUp
}
public struct Time: CustomStringConvertible, Codable {
public let hours: UInt8
public let minutes: UInt8
public var description: String {
var amOrPm = "am"
var convertedHours = hours
if convertedHours > 12 {
amOrPm = "pm"
convertedHours -= 12
}
if convertedHours == 0 {
convertedHours = 12
}
return "\(String(format: "%02d", convertedHours)):\(minutes) \(amOrPm)"
}
public init(from decoder: Decoder) throws {
let valueContainer = try decoder.singleValueContainer()
let value = try valueContainer.decode(String.self).trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
var isAm: Bool?
if value.contains("am") {
isAm = true
}
else if value.contains("pm") {
isAm = false
}
let timeRegEx = try! NSRegularExpression(pattern: "(\\d\\d?):(\\d\\d?)") //it's a correct regexp, otherwise it'd better crash during development.
let allMatches = timeRegEx.matches(in: value, range: NSRangeFromString(value))
let nsValue = value as NSString
guard let match = allMatches.first else {
throw DecodingError.dataCorruptedError(in: valueContainer, debugDescription: "Couldn't parse time info from string \(value).")
}
let parsedHours = UInt8(nsValue.substring(with: match.range(at: 1)))! // we know if will get parsed, because regexp guarantees it's two digits without a sign.
let parsedMinutes = UInt8(nsValue.substring(with: match.range(at: 1)))! // same here
guard parsedMinutes >= 0 && parsedMinutes <= 59 else {
throw DecodingError.dataCorruptedError(in: valueContainer, debugDescription: "Minute value is not acceptible: \(parsedMinutes)")
}
minutes = parsedMinutes
if let isAm = isAm {
guard parsedHours >= 1 && parsedHours <= 12 else {
throw DecodingError.dataCorruptedError(in: valueContainer, debugDescription: "Hour value is not acceptible for a 12h format: \(parsedHours)")
}
if parsedHours == 12 {
if isAm {
hours = 0
}
else {
hours = 12
}
}
else {
if !isAm {
hours = parsedHours + 12
}
else {
hours = parsedHours
}
}
}
else {
guard parsedHours >= 0 && parsedHours <= 23 else {
throw DecodingError.dataCorruptedError(in: valueContainer, debugDescription: "Hour value is not acceptible for a 24h format: \(parsedHours)")
}
hours = parsedHours
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.description)
}
}
public typealias Temperature = Measurement<UnitTemperature>
public typealias WindSpeed = Measurement<UnitSpeed>
public typealias Percent = UInt
public typealias Visibility = Measurement<UnitLength>
public typealias Pressure = Measurement<UnitPressure>
//
// HourlyWeather.swift
// 1Weather
//
// Created by Demid Merzlyakov on 19.02.2021.
//
import Foundation
public struct HourlyWeather {
public var date: Date
public var timeZone: TimeZone
public var weekDay: WeekDay
public var type: WeatherType = .unknown
public var minTemp: Temperature?
public var maxTemp: Temperature?
public var windSpeed: WindSpeed?
public var windDirection: WindDirection?
public var precipitationProbability: Percent?
public var humidity: Percent?
}
//
// Location.swift
// 1Weather
//
// Created by Demid Merzlyakov on 19.02.2021.
//
import Foundation
import CoreLocation
public struct Location: CustomStringConvertible, Equatable {
// MARK: - Data fields
public var coordinates: CLLocation?
public var imageName = "ny_bridge" //we'll possibly need to switch to URL
public var countryCode: String?
public var countryName: String?
public var region: String? // also, state
public var cityName: String?
public var nickname: String? // nickname given to this location by the user
public var fipsCode: String?
public var today: CurrentWeather?
public var daily: [DailyWeather] = [DailyWeather]()
public var hourly: [HourlyWeather] = [HourlyWeather]()
// MARK: - Derived fields
public var cityId: String {
return "\(self.countryCode ?? ""):\(self.region ?? ""):\(self.cityName ?? "")"
}
public var description: String {
return "\(cityId) (\(coordinates?.description ?? "no coordinates"))"
}
public static func == (lhs: Location, rhs: Location) -> Bool {
// Should we compare the weather here, too?
// TODO: implement
#warning("Not implemented!")
#if DEBUG
//TODO: REMOVE THIS'
return false
#warning("THIS IS A DEBUG-ONLY PIECE OF CODE! Not even temporary! Remove before making a production build.")
#else
#error("THIS IS A DEBUG-ONLY PIECE OF CODE! Not even temporary! Remove before making a production build.")
#endif
}
}
//
// WdtDailySummary.swift
// 1Weather
//
// Created by Demid Merzlyakov on 26.02.2021.
//
import Foundation
public struct WdtDailySummary: Codable {
public var minTempF: Double?
public var maxTempF: Double
public var windSpeedKph: Double?
public var windDirection: WindDirection?
public var weekDay: WeekDay
public var weatherCode: WdtWeatherCode?
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 windDirection = "wnd_dir"
case weekDay = "day_of_week"
case weatherCode = "wx_code"
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"
}
}
//
// WdtHourlySummary.swift
// 1Weather
//
// Created by Demid Merzlyakov on 26.02.2021.
//
import Foundation
struct WdtHourlySummary: Codable {
// TODO: gotta parse date from local + current date at the time of the request.
}
//
// WdtLocation.swift
// 1Weather
//
// Created by Demid Merzlyakov on 25.02.2021.
//
import Foundation
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 surfaceObservation: WdtSurfaceObservation
enum CodingKeys: String, CodingKey {
case lat
case lon
case city
case region
case country
case zipcode
case surfaceObservation = "sfc_ob"
}
}
//
// WdtSurfaceObservation.swift
// 1Weather
//
// Created by Demid Merzlyakov on 25.02.2021.
//
import Foundation
public struct WdtSurfaceObservation: Codable {
public var tempF: Double?
public var apparentTempF: Double?
public var windSpeedKph: Double?
public var windDirection: WindDirection?
public var pressureMb: Double?
public var visibilityFt: Double
public var weekDay: WeekDay?
public var weatherCode: WdtWeatherCode?
public var observationLocalTime: String?
enum CodingKeys: String, CodingKey {
case tempF = "temp_F"
case apparentTempF = "apparent_temp_F"
case windSpeedKph = "wnd_spd_kph"
case windDirection = "wnd_dir"
case pressureMb = "press_mb"
case visibilityFt = "visibility_ft"
case weekDay = "day_of_week_local"
case weatherCode = "wx_code"
case observationLocalTime = "ob_time"
}
}
//
// WdtWeatherCode.swift
// 1Weather
//
// Created by Demid Merzlyakov on 19.02.2021.
//
import Foundation
public enum WdtWeatherCode: String, Codable {
// "ic_static_smoke"
case smoke = "3"
// "ic_static_hazy"
case haze = "5"
// "ic_static_sandstorm"
case blowingDust = "7"
case duststorm = "31"
case severeDuststorm = "34"
// "ic_static_fog"
case lightFog = "10"
case patchyFog = "41"
case fog = "45"
// "ic_static_snow_moderate"
case squall = "18"
case driftingSnow = "36"
case blowingSnow = "38"
// "ic_static_ice_fog"
case freezingFog = "49"
// "ic_static_mild_rain"
case lightDrizzle = "51"
case drizzle = "53"
case heavyDrizzle = "55"
case lightRain = "61"
case lightRainShower = "80"
// "ic_static_freezing_rain"
case freezingDrizzle = "57"
case lightFreezingRain = "66"
case lightRainAndSnow = "68"
case rainAndSnow = "69"
case lightRainAndSnowShower = "83"
case rainAndSnowShower = "84"
case freezingRain = "67"
case sleet = "79"
// "ic_static_moderate_rain"
case rain = "63"
case rainShower = "81"
// "ic_static_heavy_rain"
case heavyRain = "65"
// "ic_static_snow_flurry"
case lightSnow = "71"
case lightSnowShower = "85"
// "ic_static_snow_moderate"
case snow = "73"
case snowShower = "86"
// "ic_static_snowstorm"
case heavySnow = "75"
// "ic_static_hail"
case lightHail = "89"
case hail = "90"
// "ic_static_severe_thunderstorms"
case thunderstorm = "95"
case heavyThunderstorm = "97"
// isDay ? "ic_static_sunny" : "ic_static_clear_night"
case clear = "100"
// isDay ? "ic_static_partly_cloudy" : "ic_static_partly_cloudy_night"
case mostlyClear = "101"
case partlyCloudy = "102"
// isDay ? "ic_static_heavy_cloudy" : "ic_static_heavy_cloudy_night"
case mostlyCloudy = "103"
case overcast = "104"
// "ic_static_no_report"
}
//
// WdtWeatherSource.swift
// 1Weather
//
// Created by Demid Merzlyakov on 25.02.2021.
//
import Foundation
public class WdtWeatherSource: WeatherSource {
static let updateUrlMega = "https://1weather.onelouder.com/feeds/onelouder/mega.php"
static let updateUrlMicro = "https://1weather.onelouder.com/feeds/onelouder2/fm.php"
public func updateWeather(for location: Location, completion: (Location) -> ()) {
}
}
//
// WeatherSource.swift
// 1Weather
//
// Created by Demid Merzlyakov on 25.02.2021.
//
import Foundation
public typealias WeatherSourceCompletion = (Location) -> ()
public protocol WeatherSource {
func updateWeather(for location: Location, completion: WeatherSourceCompletion)
}
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "blowingDust.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "blowingDust@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "blowingDust@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "clearDay.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "clearDay@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "clearDay@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "clearNight.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "clearNight@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "clearNight@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "freezingFog.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "freezingFog@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "freezingFog@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "freezingRain.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "freezingRain@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "freezingRain@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "haze.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "haze@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "haze@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "heavyRain.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "heavyRain@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "heavyRain@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "heavySnow.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "heavySnow@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "heavySnow@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "lightDrizzle.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "lightDrizzle@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "lightDrizzle@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "lightFog.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "lightFog@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "lightFog@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "lightHail.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "lightHail@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "lightHail@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "lightSnow.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "lightSnow@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "lightSnow@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "mostlyCloudyDay.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "mostlyCloudyDay@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "mostlyCloudyDay@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "mostlyCloudyNight.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "mostlyCloudyNight@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "mostlyCloudyNight@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "partlyCloudyDay.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "partlyCloudyDay@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "partlyCloudyDay@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "partlyCloudyNight.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "partlyCloudyNight@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "partlyCloudyNight@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "smoke.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "smoke@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "smoke@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "snowyDay.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "snowyDay@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "snowyDay@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "snowyNight.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "snowyNight@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "snowyNight@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "thunderstorm.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "thunderstorm@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "thunderstorm@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "unknown.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "unknown@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "unknown@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
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