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
0677389b
Commit
0677389b
authored
Apr 02, 2021
by
Demid Merzlyakov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Network: working with HealthCenter.
parent
6127b794
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
119 additions
and
6 deletions
+119
-6
1Weather/Network/Health/BlendHealthSource.swift
+84
-2
1Weather/Network/Health/Model/BlendHealthModels.swift
+35
-3
1Weather/Network/Weather/WdtWeatherSource.swift
+0
-1
No files found.
1Weather/Network/Health/BlendHealthSource.swift
View file @
0677389b
...
...
@@ -8,7 +8,7 @@
import
Foundation
public
enum
BlendHealthError
:
Error
{
public
enum
BlendHealth
Source
Error
:
Error
{
case
insufficientLocationInfo
case
badUrl
case
networkError
(
Error
?)
...
...
@@ -17,6 +17,88 @@ public enum BlendHealthError: Error {
case
alreadyBeingUpdated
}
public
class
BlendHealthSource
{
public
class
BlendHealthSource
:
HealthSource
{
private
let
log
=
Logger
(
componentName
:
"BlendHealthSource"
)
#warning("Not implemented: staging / prod switching!")
//TODO: Not implemented: staging / prod switching!
private
static
let
healthCenterUrl
=
"http://sta-1w-dataaggregator.onelouder.com/1weather/api/v1/weather/current"
public
var
healthUpdateInterval
:
TimeInterval
=
TimeInterval
(
15
*
60
)
// 15 minutes
/// This queue is needed to synchronize access to locationsBeingUpdated. Also, to make logging more clear.
private
let
internalQueue
:
OperationQueue
=
{
let
queue
=
OperationQueue
()
queue
.
name
=
"BlendHealthSource Queue"
queue
.
maxConcurrentOperationCount
=
1
return
queue
}()
private
var
locationsBeingUpdated
=
Set
<
Location
>
()
public
func
updateHelath
(
for
location
:
Location
,
completion
:
@escaping
HealthSourceCompletion
)
{
internalQueue
.
addOperation
{
[
weak
self
]
in
let
extendedCompletion
:
HealthSourceCompletion
=
{
[
weak
self
]
(
health
,
error
)
in
self
?
.
internalQueue
.
addOperation
{
completion
(
health
,
error
)
self
?
.
locationsBeingUpdated
.
remove
(
location
)
}
}
self
?
.
updateHelathInternal
(
for
:
location
,
completion
:
extendedCompletion
)
}
}
private
func
updateHelathInternal
(
for
location
:
Location
,
completion
:
@escaping
HealthSourceCompletion
)
{
guard
!
locationsBeingUpdated
.
contains
(
location
)
else
{
completion
(
nil
,
BlendHealthSourceError
.
alreadyBeingUpdated
)
return
}
locationsBeingUpdated
.
insert
(
location
)
guard
var
urlComponents
=
URLComponents
(
string
:
BlendHealthSource
.
healthCenterUrl
)
else
{
assertionFailure
(
"Should never happen. The URL should be correct."
)
return
}
var
queryParameters
=
[
String
:
String
]()
if
let
coordinates
=
location
.
coordinates
{
queryParameters
[
"LAT"
]
=
String
(
format
:
"%.5f"
,
coordinates
.
latitude
)
queryParameters
[
"LON"
]
=
String
(
format
:
"%.5f"
,
coordinates
.
longitude
)
}
queryParameters
[
"ZIP"
]
=
location
.
zip
queryParameters
[
"CITY"
]
=
location
.
cityName
queryParameters
[
"STATE"
]
=
location
.
region
queryParameters
[
"COUNTRY"
]
=
location
.
countryName
guard
!
queryParameters
.
isEmpty
else
{
completion
(
nil
,
BlendHealthSourceError
.
insufficientLocationInfo
)
log
.
error
(
"Not enough information about location."
)
return
}
guard
let
url
=
urlComponents
.
url
else
{
completion
(
nil
,
BlendHealthSourceError
.
badUrl
)
return
}
log
.
debug
(
"query params:
\(
queryParameters
)
"
)
let
urlSession
=
URLSession
.
shared
let
dataTask
=
urlSession
.
dataTask
(
with
:
url
)
{
[
weak
self
]
(
data
,
reponse
,
error
)
in
guard
let
self
=
self
else
{
return
}
guard
let
data
=
data
else
{
completion
(
nil
,
BlendHealthSourceError
.
networkError
(
error
))
return
}
let
decoder
=
JSONDecoder
()
let
dateFormatter
=
DateFormatter
()
dateFormatter
.
dateFormat
=
"yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder
.
dateDecodingStrategy
=
.
formatted
(
dateFormatter
)
do
{
let
updatedHealth
=
try
decoder
.
decode
(
BlendHealthCenter
.
self
,
from
:
data
)
let
appHealth
=
updatedHealth
.
toAppModel
()
completion
(
appHealth
,
nil
)
}
catch
{
completion
(
nil
,
BlendHealthSourceError
.
badServerResponse
(
error
))
}
}
dataTask
.
resume
()
}
}
1Weather/Network/Health/Model/BlendHealthModels.swift
View file @
0677389b
...
...
@@ -12,9 +12,9 @@ import UIKit
struct
BlendHealthCenter
:
Codable
{
public
let
s2CellID
:
String
public
let
updatedOn
:
Date
public
let
airQuality
:
BlendAirQuality
public
let
airQuality
:
BlendAirQuality
?
public
let
fire
:
BlendFire
public
let
pollutants
,
pollen
:
[
BlendPoll
]
public
let
pollutants
,
pollen
:
[
BlendPoll
]
?
enum
CodingKeys
:
String
,
CodingKey
{
case
s2CellID
=
"s2_cell_id"
...
...
@@ -22,6 +22,20 @@ struct BlendHealthCenter: Codable {
case
airQuality
=
"air_quality"
case
fire
,
pollutants
,
pollen
}
func
toAppModel
()
->
Health
{
let
airQuality
=
self
.
airQuality
?
.
toAppModel
()
let
pollutants
=
self
.
pollutants
?
.
reduce
([
String
:
Pollutant
]())
{
(
dict
,
blendPoll
)
in
var
dict
=
dict
if
let
pollutant
=
blendPoll
.
toAppModel
()
{
dict
[
pollutant
.
name
]
=
pollutant
}
return
dict
}
let
result
=
Health
(
lastUpdateTime
:
updatedOn
,
airQuality
:
airQuality
,
pollutants
:
pollutants
??
[:])
return
result
}
}
// MARK: - AirQuality
...
...
@@ -39,6 +53,13 @@ struct BlendAirQuality: Codable {
case
color
=
"color_code"
case
imageURL
=
"image_url"
}
func
toAppModel
()
->
AirQuality
{
#warning("Not implemented!")
//TODO: implement health advice groups support (general / sensitive / active)
let
result
=
AirQuality
(
index
:
aqi
,
advice
:
healthAdvice
.
general
)
return
result
}
}
// MARK: - HealthAdvice
...
...
@@ -72,6 +93,17 @@ struct BlendPoll: Codable {
case
status
case
colorCode
=
"color_code"
}
// Note: the same model in Blend is also used for pollen, which, in my opinion, should be
// a different app-level model object.
func
toAppModel
()
->
Pollutant
?
{
// Note: pollutants always seem to have a value. Pollen doesn't.
guard
let
value
=
value
else
{
return
nil
}
let
result
=
Pollutant
(
name
:
name
,
value
:
value
)
return
result
}
}
struct
BlendColor
:
Codable
{
...
...
@@ -85,7 +117,7 @@ struct BlendColor: Codable {
func
encode
(
to
encoder
:
Encoder
)
throws
{
guard
let
hexRepresentation
=
uiColor
.
toHex
()
else
{
throw
BlendHealthError
.
dataEncodingError
(
"BlendColor to HEX conversion failed for color
\(
uiColor
)
"
)
throw
BlendHealth
Source
Error
.
dataEncodingError
(
"BlendColor to HEX conversion failed for color
\(
uiColor
)
"
)
}
var
container
=
encoder
.
singleValueContainer
()
try
container
.
encode
(
"#
\(
hexRepresentation
)
"
)
...
...
1Weather/Network/Weather/WdtWeatherSource.swift
View file @
0677389b
...
...
@@ -31,7 +31,6 @@ public class WdtWeatherSource: WeatherSource {
return
queue
}()
/// This is used
private
var
locationsBeingUpdated
=
Set
<
Location
>
()
public
let
weatherUpdateInterval
=
TimeInterval
(
15
*
60
)
// 15 minutes
...
...
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