Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
1
1weather
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Dmitriy Stepanets
1weather
Commits
5d1529f8
Commit
5d1529f8
authored
Mar 04, 2021
by
Demid Merzlyakov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Working network request parsing.
parent
f77af241
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
103 additions
and
160 deletions
+103
-160
1Weather/AppDelegate.swift
+3
-0
1Weather/Coordinators/TodayCoordinator.swift
+1
-1
1Weather/Model/LocationManager.swift
+35
-6
1Weather/Model/ModelObjects/Location.swift
+2
-15
1Weather/Network/Model/HelperObjects/WdtWeatherCode.swift
+0
-2
1Weather/Network/Model/WdtDailySummary.swift
+3
-3
1Weather/Network/Model/WdtHourlySummary.swift
+3
-3
1Weather/Network/Model/WdtLocation.swift
+11
-1
1Weather/Network/Model/WdtSurfaceObservation.swift
+3
-10
1Weather/Network/WdtWeatherSource.swift
+9
-25
1Weather/UI/View controllers/Today/TodayViewController.swift
+9
-0
1Weather/ViewModels/TodayViewModel.swift
+24
-94
No files found.
1Weather/AppDelegate.swift
View file @
5d1529f8
...
...
@@ -6,6 +6,7 @@
//
import
UIKit
import
CoreLocation
@main
class
AppDelegate
:
UIResponder
,
UIApplicationDelegate
{
...
...
@@ -14,6 +15,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func
application
(
_
application
:
UIApplication
,
didFinishLaunchingWithOptions
launchOptions
:
[
UIApplication
.
LaunchOptionsKey
:
Any
]?)
->
Bool
{
ThemeManager
.
refreshAppearance
()
LocationManager
.
shared
.
currentLocation
=
Location
(
coordinates
:
CLLocationCoordinate2D
(
latitude
:
40.730610
,
longitude
:
-
73.935242
),
timeZone
:
TimeZone
(
identifier
:
"EST"
)
!
)
self
.
window
=
UIWindow
(
frame
:
UIScreen
.
main
.
bounds
)
let
appCoordinator
=
AppCoordinator
(
window
:
self
.
window
!
)
...
...
1Weather/Coordinators/TodayCoordinator.swift
View file @
5d1529f8
...
...
@@ -9,7 +9,7 @@ import UIKit
class
TodayCoordinator
:
Coordinator
{
//Private
private
let
todayViewModel
=
TodayViewModel
(
)
private
let
todayViewModel
=
TodayViewModel
(
locationManager
:
LocationManager
.
shared
)
// TODO: get rid of singleton maybe?
private
let
navigationController
=
UINavigationController
(
nibName
:
nil
,
bundle
:
nil
)
private
var
tabBarController
:
UITabBarController
?
...
...
1Weather/Model/LocationManager.swift
View file @
5d1529f8
...
...
@@ -14,18 +14,47 @@ public protocol LocationManagerDelegate: class {
public
class
LocationManager
{
private
let
log
=
Logger
(
componentName
:
"LocationManager"
)
private
let
delegates
=
MulticastDelegate
<
LocationManagerDelegate
>
()
private
let
weatherUpdateSource
:
WeatherSource
public
static
let
shared
=
LocationManager
(
weatherUpdateSource
:
WdtWeatherSource
())
public
init
(
weatherUpdateSource
:
WeatherSource
)
{
self
.
weatherUpdateSource
=
weatherUpdateSource
}
public
var
currentLocation
:
Location
?
{
didSet
{
if
currentLocation
!=
oldValue
{
if
oldValue
?
.
description
!=
currentLocation
?
.
description
{
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
)
}
log
.
info
(
"Location updated."
)
delegates
.
invoke
{
[
weak
self
]
(
delegate
)
in
guard
let
self
=
self
else
{
return
}
delegate
.
locationManager
(
self
,
changedCurrentLocation
:
currentLocation
)
}
}
}
public
func
updateWeather
()
{
guard
let
location
=
currentLocation
else
{
log
.
warning
(
"Update weather: no location."
)
return
}
log
.
info
(
"Update weather for location:
\(
location
)
"
)
weatherUpdateSource
.
updateWeather
(
for
:
location
)
{
[
weak
self
]
(
updatedLocation
,
error
)
in
guard
let
self
=
self
else
{
return
}
guard
let
updatedLocation
=
updatedLocation
else
{
if
let
error
=
error
{
self
.
log
.
error
(
"Update weather error:
\(
error
)
"
)
}
else
{
self
.
log
.
error
(
"Update weather error: unknown error"
)
}
return
}
else
{
log
.
debug
(
"location updated without changing"
)
DispatchQueue
.
main
.
async
{
self
.
currentLocation
=
updatedLocation
}
}
}
...
...
1Weather/Model/ModelObjects/Location.swift
View file @
5d1529f8
...
...
@@ -8,7 +8,7 @@
import
Foundation
import
CoreLocation
public
struct
Location
:
CustomStringConvertible
,
Equatable
{
public
struct
Location
:
CustomStringConvertible
{
// MARK: - Data fields
public
var
coordinates
:
CLLocationCoordinate2D
?
public
var
imageName
:
String
?
=
"ny_bridge"
//we'll possibly need to switch to URL
...
...
@@ -39,6 +39,7 @@ public struct Location: CustomStringConvertible, Equatable {
public
var
zip
:
String
?
public
var
fipsCode
:
String
?
public
var
timeZone
:
TimeZone
public
var
today
:
CurrentWeather
?
public
var
daily
:
[
DailyWeather
]
=
[
DailyWeather
]()
...
...
@@ -57,20 +58,6 @@ public struct Location: CustomStringConvertible, Equatable {
return
"
\(
cityId
)
(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
}
}
// MARK: - UpdatableModelObject implementation
...
...
1Weather/Network/Model/HelperObjects/WdtWeatherCode.swift
View file @
5d1529f8
...
...
@@ -117,8 +117,6 @@ enum WdtWeatherCode: String, Codable {
case
.
mostlyCloudy
,
.
overcast
:
//TODO: handle mostly cloudy night
return
.
mostlyCloudyDay
default
:
return
.
unknown
}
}
}
1Weather/Network/Model/WdtDailySummary.swift
View file @
5d1529f8
...
...
@@ -46,7 +46,7 @@ struct WdtDailySummary: Codable {
dateFormatter
.
dateFormat
=
"MM/dd/YYYY"
dateFormatter
.
timeZone
=
timeZone
guard
let
date
=
dateFormatter
.
date
(
from
:
dateLocal
)
else
{
throw
WdtWeatherSourceError
.
dataMergeError
throw
WdtWeatherSourceError
.
dataMergeError
(
"daily.date:
\(
dateLocal
)
"
)
}
var
weekDay
=
WeekDay
(
rawValue
:
self
.
weekDay
)
...
...
@@ -56,7 +56,7 @@ struct WdtDailySummary: Codable {
weekDay
=
WeekDay
(
grigorianWeekDayNumber
:
calendar
.
component
(
.
weekday
,
from
:
date
))
}
guard
let
weekDayUnwrapped
=
weekDay
else
{
throw
WdtWeatherSourceError
.
dataMergeError
throw
WdtWeatherSourceError
.
dataMergeError
(
"daily.weekday:
\(
self
.
weekDay
)
"
)
}
var
result
=
DailyWeather
(
date
:
date
,
timeZone
:
timeZone
,
weekDay
:
weekDayUnwrapped
)
...
...
@@ -75,7 +75,7 @@ struct WdtDailySummary: Codable {
let
sunMoonDateFormatter
=
DateFormatter
()
sunMoonDateFormatter
.
timeZone
=
TimeZone
(
identifier
:
"UTC"
)
sunMoonDateFormatter
.
dateFormat
=
"YYYY-MM-dd
hh
:mm:ss"
sunMoonDateFormatter
.
dateFormat
=
"YYYY-MM-dd
HH
:mm:ss"
if
let
sunriseUtc
=
sunriseUtc
{
result
.
sunrise
=
sunMoonDateFormatter
.
date
(
from
:
sunriseUtc
)
}
...
...
1Weather/Network/Model/WdtHourlySummary.swift
View file @
5d1529f8
...
...
@@ -36,11 +36,11 @@ struct WdtHourlySummary: Codable {
func
toAppModel
(
timeZone
:
TimeZone
)
throws
->
HourlyWeather
{
let
dateFormatter
=
DateFormatter
()
dateFormatter
.
dateFormat
=
"YYYY-MM-dd
hh
:mm:ss"
dateFormatter
.
dateFormat
=
"YYYY-MM-dd
HH
:mm:ss"
dateFormatter
.
timeZone
=
timeZone
guard
let
date
=
dateFormatter
.
date
(
from
:
self
.
dateTimeLocal
)
else
{
throw
WdtWeatherSourceError
.
dataMergeError
throw
WdtWeatherSourceError
.
dataMergeError
(
"hourly.date:
\(
self
.
dateTimeLocal
)
"
)
}
var
weekDay
=
WeekDay
(
rawValue
:
self
.
weekDay
)
...
...
@@ -50,7 +50,7 @@ struct WdtHourlySummary: Codable {
weekDay
=
WeekDay
(
grigorianWeekDayNumber
:
calendar
.
component
(
.
weekday
,
from
:
date
))
}
guard
let
weekDayUnwrapped
=
weekDay
else
{
throw
WdtWeatherSourceError
.
dataMergeError
throw
WdtWeatherSourceError
.
dataMergeError
(
"hourly.weekday:
\(
self
.
weekDay
)
"
)
}
let
isDay
=
dayNight
.
trimmingCharacters
(
in
:
.
whitespacesAndNewlines
)
.
lowercased
()
==
"day"
...
...
1Weather/Network/Model/WdtLocation.swift
View file @
5d1529f8
...
...
@@ -37,10 +37,19 @@ struct WdtLocation: Codable {
if
let
lat
=
CLLocationDegrees
(
lat
??
""
),
let
lon
=
CLLocationDegrees
(
lon
??
""
)
{
coordinates
=
CLLocationCoordinate2D
(
latitude
:
lat
,
longitude
:
lon
)
}
let
today
=
try
surfaceObservation
.
toAppModel
(
timeZone
:
timeZone
)
var
today
=
try
surfaceObservation
.
toAppModel
(
timeZone
:
timeZone
)
let
dailyWeather
=
try
dailySummaries
?
.
toAppModel
(
timeZone
:
timeZone
)
??
[
DailyWeather
]()
let
hourlyWeather
=
try
hourlySummaries
?
.
toAppModel
(
timeZone
:
timeZone
)
??
[
HourlyWeather
]()
if
let
firstDay
=
dailyWeather
.
first
{
today
.
minTemp
=
firstDay
.
minTemp
today
.
maxTemp
=
firstDay
.
maxTemp
today
.
precipitationProbability
=
firstDay
.
precipitationProbability
today
.
sunState
=
firstDay
.
sunState
today
.
moonState
=
firstDay
.
moonState
today
.
moonPhase
=
firstDay
.
moonPhase
}
return
Location
(
coordinates
:
coordinates
,
imageName
:
nil
,
countryCode
:
nil
,
...
...
@@ -50,6 +59,7 @@ struct WdtLocation: Codable {
nickname
:
nil
,
zip
:
zipcode
,
fipsCode
:
nil
,
timeZone
:
timeZone
,
today
:
today
,
daily
:
dailyWeather
,
hourly
:
hourlyWeather
)
...
...
1Weather/Network/Model/WdtSurfaceObservation.swift
View file @
5d1529f8
...
...
@@ -42,11 +42,11 @@ struct WdtSurfaceObservation: Codable {
public
func
toAppModel
(
timeZone
:
TimeZone
)
throws
->
CurrentWeather
{
let
dateFormatter
=
DateFormatter
()
dateFormatter
.
dateFormat
=
"YYYY-MM-dd
hh
:mm:ss"
dateFormatter
.
dateFormat
=
"YYYY-MM-dd
HH
:mm:ss"
dateFormatter
.
timeZone
=
timeZone
guard
let
date
=
dateFormatter
.
date
(
from
:
self
.
dateTimeLocal
)
else
{
throw
WdtWeatherSourceError
.
dataMergeError
throw
WdtWeatherSourceError
.
dataMergeError
(
"today.date:
\(
dateTimeLocal
)
"
)
}
var
weekDay
=
WeekDay
(
rawValue
:
self
.
weekDay
)
if
weekDay
==
nil
{
...
...
@@ -55,7 +55,7 @@ struct WdtSurfaceObservation: Codable {
weekDay
=
WeekDay
(
grigorianWeekDayNumber
:
calendar
.
component
(
.
weekday
,
from
:
date
))
}
guard
let
weekDayUnwrapped
=
weekDay
else
{
throw
WdtWeatherSourceError
.
dataMergeError
throw
WdtWeatherSourceError
.
dataMergeError
(
"today.weekday:
\(
self
.
weekDay
)
"
)
}
let
isDay
=
dayNight
.
trimmingCharacters
(
in
:
.
whitespacesAndNewlines
)
.
lowercased
()
==
"day"
...
...
@@ -64,13 +64,6 @@ struct WdtSurfaceObservation: Codable {
if
let
weatherCode
=
self
.
weatherCode
{
result
.
type
=
WdtWeatherCode
(
rawValue
:
weatherCode
)?
.
toAppModel
()
??
.
unknown
}
// TODO: gotta fill up values from daily:
// minTemp
// maxTemp
// precipitationProbability
// sunState
// moonState
// moonPhase
result
.
windSpeed
=
WindSpeed
(
valueString
:
self
.
windSpeedKph
,
unit
:
.
kilometersPerHour
)
result
.
windDirection
=
WindDirection
(
rawValue
:
windDirection
??
""
)
...
...
1Weather/Network/WdtWeatherSource.swift
View file @
5d1529f8
...
...
@@ -13,7 +13,7 @@ public enum WdtWeatherSourceError: Error {
case
badUrl
case
networkError
(
Error
?)
case
badServerResponse
(
Error
?)
case
dataMergeError
case
dataMergeError
(
String
)
}
public
class
WdtWeatherSource
:
WeatherSource
{
...
...
@@ -58,10 +58,11 @@ public class WdtWeatherSource: WeatherSource {
let
decoder
=
XMLDecoder
()
do
{
let
locationResponse
=
try
decoder
.
decode
(
WdtLocationResponse
.
self
,
from
:
data
)
guard
let
newLocation
=
self
.
update
(
location
:
location
,
from
:
locationResponse
)
else
{
guard
let
newLocation
=
try
self
.
update
(
location
:
location
,
from
:
locationResponse
)
else
{
completion
(
nil
,
WdtWeatherSourceError
.
badServerResponse
(
error
))
return
}
completion
(
newLocation
,
nil
)
}
catch
{
...
...
@@ -71,29 +72,12 @@ public class WdtWeatherSource: WeatherSource {
dataTask
.
resume
()
}
func
update
(
location
:
Location
,
from
locationResponse
:
WdtLocationResponse
)
->
Location
?
{
// guard let wdtLocation = locationResponse.locations.first else {
// return nil
// }
var
updatedLocation
=
location
// if updatedLocation.coordinates == nil {
// if let lat = CLLocationDegrees(wdtLocation.lat ?? ""), let lon = CLLocationDegrees(wdtLocation.lon ?? "") {
// location.coordinates = CLLocationCoordinate2D(latitude: lat, longitude: lon)
// }
// }
// if updatedLocation.cityName == nil {
// updatedLocation.cityName = wdtLocation.city
// }
// if updatedLocation.countryName == nil {
// updatedLocation.countryName = wdtLocation.country
// }
// if updatedLocation.region == nil {
// updatedLocation.
// }
//
// TOOD: implement
#warning("Not implemented!")
func
update
(
location
:
Location
,
from
locationResponse
:
WdtLocationResponse
)
throws
->
Location
?
{
guard
let
wdtLocation
=
locationResponse
.
locations
.
first
else
{
return
nil
}
let
incrementalChanges
=
try
wdtLocation
.
toAppModel
(
timeZone
:
location
.
timeZone
)
let
updatedLocation
=
location
.
mergedWith
(
incrementalChanges
:
incrementalChanges
)
return
updatedLocation
}
}
1Weather/UI/View controllers/Today/TodayViewController.swift
View file @
5d1529f8
...
...
@@ -35,6 +35,9 @@ class TodayViewController: UIViewController {
prepareViewController
()
prepareNavigationBar
()
prepareTableView
()
viewModel
.
delegate
=
self
viewModel
.
updateWeather
()
}
override
func
viewWillTransition
(
to
size
:
CGSize
,
with
coordinator
:
UIViewControllerTransitionCoordinator
)
{
...
...
@@ -120,3 +123,9 @@ extension TodayViewController: UITableViewDelegate {
viewModel
.
todayCellFactory
.
willDisplay
(
cell
:
cell
)
}
}
extension
TodayViewController
:
ViewModelDelegate
{
func
viewModelDidChange
<
P
>
(
model
:
P
)
where
P
:
ViewModelProtocol
{
tableView
.
reloadData
()
}
}
1Weather/ViewModels/TodayViewModel.swift
View file @
5d1529f8
...
...
@@ -6,108 +6,38 @@
//
import
UIKit
class
TodayViewModel
{
class
TodayViewModel
:
ViewModelProtocol
{
//Public
public
let
todayCellFactory
=
TodayCellFactory
()
public
weak
var
delegate
:
ViewModelDelegate
?
public
private(set)
var
location
:
Location
public
private(set)
var
location
:
Location
?
private
var
locationManager
:
LocationManager
init
()
{
func
weekDay
(
byIndex
:
Int
)
->
WeekDay
{
switch
byIndex
{
case
0
:
return
.
monday
case
1
:
return
.
tuesday
case
2
:
return
.
wednesday
case
3
:
return
.
thursday
case
4
:
return
.
friday
case
5
:
return
.
saturday
default
:
return
.
sunday
}
}
let
today
=
CurrentWeather
(
date
:
Date
(),
timeZone
:
.
current
,
weekDay
:
.
monday
,
type
:
.
heavyRain
,
isDay
:
true
,
minTemp
:
.
init
(
value
:
-
4
,
unit
:
.
celsius
),
maxTemp
:
.
init
(
value
:
9
,
unit
:
.
celsius
),
windSpeed
:
.
init
(
value
:
5
,
unit
:
.
milesPerHour
),
windDirection
:
.
north
,
precipitationProbability
:
.
some
(
30
),
temp
:
.
init
(
value
:
4
,
unit
:
.
celsius
),
apparentTemp
:
.
init
(
value
:
5
,
unit
:
.
celsius
),
humidity
:
.
some
(
30
),
visibility
:
.
init
(
value
:
8
,
unit
:
.
miles
),
pressure
:
.
init
(
value
:
29.7
,
unit
:
.
inchesOfMercury
),
sunrise
:
.
init
(
hours
:
6
,
minutes
:
26
),
sunset
:
.
init
(
hours
:
17
,
minutes
:
46
),
sunState
:
.
alwaysUp
,
moonrise
:
.
init
(
hours
:
20
,
minutes
:
00
),
moonset
:
.
init
(
hours
:
5
,
minutes
:
30
),
moonState
:
.
normal
)
var
daily
=
[
DailyWeather
]()
var
dailyStartDate
=
Date
()
-
TimeInterval
(
3600
*
24
)
for
index
in
0
..<
7
{
let
day
=
DailyWeather
(
date
:
dailyStartDate
,
timeZone
:
.
current
,
weekDay
:
weekDay
(
byIndex
:
index
),
type
:
WeatherType
.
allCases
.
randomElement
()
??
.
unknown
,
minTemp
:
.
init
(
value
:
Double
.
random
(
in
:
-
3
..<
5
),
unit
:
.
celsius
),
maxTemp
:
.
init
(
value
:
Double
.
random
(
in
:
5
..<
10
),
unit
:
.
celsius
),
windSpeed
:
.
init
(
value
:
Double
.
random
(
in
:
0
..<
10
),
unit
:
.
milesPerHour
),
windDirection
:
WindDirection
.
allCases
.
randomElement
()
??
.
east
,
precipitationProbability
:
.
some
(
UInt
.
random
(
in
:
0
..<
100
)),
sunrise
:
nil
,
sunset
:
nil
,
sunState
:
.
normal
,
moonrise
:
nil
,
moonset
:
nil
,
moonState
:
.
normal
)
dailyStartDate
=
dailyStartDate
+
TimeInterval
(
3600
*
24
)
daily
.
append
(
day
)
}
var
hourly
=
[
HourlyWeather
]()
var
startDate
=
Date
()
-
TimeInterval
(
3600
)
for
_
in
0
..<
24
{
let
hour
=
HourlyWeather
(
date
:
startDate
,
timeZone
:
TimeZone
(
abbreviation
:
"EST"
)
??
.
current
,
weekDay
:
.
monday
,
type
:
WeatherType
.
allCases
.
randomElement
()
??
.
unknown
,
isDay
:
true
,
temp
:
.
init
(
value
:
Double
.
random
(
in
:
-
3
..<
10
),
unit
:
.
celsius
),
windSpeed
:
.
init
(
value
:
Double
.
random
(
in
:
0
..<
10
),
unit
:
.
milesPerHour
),
windDirection
:
WindDirection
.
allCases
.
randomElement
()
??
.
east
,
precipitationProbability
:
.
some
(
UInt
.
random
(
in
:
0
..<
100
)),
humidity
:
.
some
(
UInt
.
random
(
in
:
0
..<
100
)))
startDate
=
startDate
+
TimeInterval
(
3600
)
hourly
.
append
(
hour
)
}
self
.
location
=
Location
(
coordinates
:
nil
,
imageName
:
"ny_bridge"
,
countryCode
:
"us"
,
countryName
:
"United States"
,
region
:
"New York"
,
cityName
:
"New York"
,
nickname
:
"ny"
,
fipsCode
:
"36"
,
today
:
today
,
daily
:
daily
,
hourly
:
hourly
)
public
init
(
locationManager
:
LocationManager
)
{
self
.
locationManager
=
locationManager
locationManager
.
add
(
delegate
:
self
)
//Setup factory callback
todayCellFactory
.
onGetLocation
=
{[
weak
self
]
in
return
self
?
.
location
}
}
public
func
updateWeather
()
{
locationManager
.
updateWeather
()
}
deinit
{
self
.
locationManager
.
remove
(
delegate
:
self
)
}
}
extension
TodayViewModel
:
LocationManagerDelegate
{
func
locationManager
(
_
locationManager
:
LocationManager
,
changedCurrentLocation
newLocation
:
Location
?)
{
DispatchQueue
.
main
.
async
{
print
(
"TVM"
)
self
.
location
=
newLocation
self
.
delegate
?
.
viewModelDidChange
(
model
:
self
)
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment