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
7d111caa
Commit
7d111caa
authored
Apr 27, 2021
by
Demid Merzlyakov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
DeeplinksRouter.
parent
bdca0e18
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
392 additions
and
0 deletions
+392
-0
1Weather.xcodeproj/project.pbxproj
+8
-0
1Weather/Coordinators/AppCoordinator.swift
+5
-0
1Weather/Coordinators/DeeplinksRouter.swift
+179
-0
1Weather/Model/DeviceLocationMonitor.swift
+8
-0
1Weather/Model/LocationManager.swift
+13
-0
1Weather/Model/ModelObjects/GeoNamesPlace.swift
+2
-0
1Weather/Model/ModelObjects/Location.swift
+4
-0
1Weather/Model/Protocols/PartialLocation.swift
+2
-0
1Weather/Network/PushNotificationsManager.swift
+171
-0
No files found.
1Weather.xcodeproj/project.pbxproj
View file @
7d111caa
...
...
@@ -234,6 +234,8 @@
CEAFF08F25DFC6ED00DF4EBF
/* HourlyWeather.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEAFF08E25DFC6ED00DF4EBF
/* HourlyWeather.swift */
;
};
CEAFF09225DFC71D00DF4EBF
/* HelperTypes.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEAFF09125DFC71D00DF4EBF
/* HelperTypes.swift */
;
};
CEAFF0A325E0FF0800DF4EBF
/* LocationManager.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEAFF0A225E0FF0800DF4EBF
/* LocationManager.swift */
;
};
CEBAC1C62638236D00A89681
/* PushNotificationsManager.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEBAC1C52638236D00A89681
/* PushNotificationsManager.swift */
;
};
CEBAC1C82638240800A89681
/* DeeplinksRouter.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEBAC1C72638240800A89681
/* DeeplinksRouter.swift */
;
};
CEC526FA25E7959A00DA58A5
/* WeatherSource.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEC526F925E7959A00DA58A5
/* WeatherSource.swift */
;
};
CEC526FD25E795F700DA58A5
/* WdtWeatherSource.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEC526FC25E795F700DA58A5
/* WdtWeatherSource.swift */
;
};
CEC5270025E7BACB00DA58A5
/* WdtLocation.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEC526FF25E7BACB00DA58A5
/* WdtLocation.swift */
;
};
...
...
@@ -517,6 +519,8 @@
CEAFF08E25DFC6ED00DF4EBF
/* HourlyWeather.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
HourlyWeather.swift
;
sourceTree
=
"<group>"
;
};
CEAFF09125DFC71D00DF4EBF
/* HelperTypes.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
HelperTypes.swift
;
sourceTree
=
"<group>"
;
};
CEAFF0A225E0FF0800DF4EBF
/* LocationManager.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
LocationManager.swift
;
sourceTree
=
"<group>"
;
};
CEBAC1C52638236D00A89681
/* PushNotificationsManager.swift */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.swift
;
path
=
PushNotificationsManager.swift
;
sourceTree
=
"<group>"
;
};
CEBAC1C72638240800A89681
/* DeeplinksRouter.swift */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.swift
;
path
=
DeeplinksRouter.swift
;
sourceTree
=
"<group>"
;
};
CEC526F925E7959A00DA58A5
/* WeatherSource.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
WeatherSource.swift
;
sourceTree
=
"<group>"
;
};
CEC526FC25E795F700DA58A5
/* WdtWeatherSource.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
WdtWeatherSource.swift
;
sourceTree
=
"<group>"
;
};
CEC526FF25E7BACB00DA58A5
/* WdtLocation.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
WdtLocation.swift
;
sourceTree
=
"<group>"
;
};
...
...
@@ -689,6 +693,7 @@
CD17C5F925D15B5500EE884E
/* Coordinators */
=
{
isa
=
PBXGroup
;
children
=
(
CEBAC1C72638240800A89681
/* DeeplinksRouter.swift */
,
CD17C60125D15C8500EE884E
/* CoordinatorProtocol.swift */
,
CD17C5FA25D15B6B00EE884E
/* AppCoordinator.swift */
,
CD17C5FE25D15B7C00EE884E
/* TodayCoordinator.swift */
,
...
...
@@ -1381,6 +1386,7 @@
CEAFF09925DFC78200DF4EBF
/* Network */
=
{
isa
=
PBXGroup
;
children
=
(
CEBAC1C52638236D00A89681
/* PushNotificationsManager.swift */
,
87C1724825FF94F400DA3464
/* ConfigManager.swift */
,
87C171F325FF7A4000DA3464
/* PopularCitiesManager.swift */
,
87C171F125FF7A3300DA3464
/* Weather */
,
...
...
@@ -1898,11 +1904,13 @@
CD80917B2578E4A8003541A4
/* UIViewController+Alert.swift in Sources */
,
CEF959932600C63500975FAA
/* Analytics.swift in Sources */
,
CEE0A1A426317A8F0044C257
/* NWSAlertInfoParser.swift in Sources */
,
CEBAC1C62638236D00A89681
/* PushNotificationsManager.swift in Sources */
,
CE13B821262480B3007CBD4D
/* Scheduler.swift in Sources */
,
CEDE4F0F25EFA3B4007457E9
/* UpdatableModelObjectInTime.swift in Sources */
,
CE13B81E262480B3007CBD4D
/* AdCacheManager.swift in Sources */
,
CE13B72B26245D0D007CBD4D
/* HealthStatus.swift in Sources */
,
CE8962A426175DF500CA274A
/* _CorePollutant.swift in Sources */
,
CEBAC1C82638240800A89681
/* DeeplinksRouter.swift in Sources */
,
CD3F6E6925FA59D4002DB99B
/* ForecastDetailPeriodButton.swift in Sources */
,
CD37D405260DFFDD002669D6
/* CellFactory.swift in Sources */
,
CD37D3F6260DF5BA002669D6
/* SettingsViewModel.swift in Sources */
,
...
...
1Weather/Coordinators/AppCoordinator.swift
View file @
7d111caa
...
...
@@ -12,12 +12,17 @@ class AppCoordinator: Coordinator {
private
let
tabBarController
=
AppTabBarController
()
//Public
static
var
instance
:
AppCoordinator
!
var
parentCoordinator
:
Coordinator
?
var
childCoordinators
=
[
Coordinator
]()
init
(
window
:
UIWindow
)
{
window
.
rootViewController
=
tabBarController
window
.
makeKeyAndVisible
()
//TODO: this is very bad, fix it
AppCoordinator
.
instance
=
self
}
func
start
()
{
...
...
1Weather/Coordinators/DeeplinksRouter.swift
0 → 100644
View file @
7d111caa
//
// DeeplinksRouter.swift
// OneWeather
//
// Created by Demid Merzlyakov on 28.01.2021.
// Copyright © 2021 OneLouder, Inc. All rights reserved.
//
import
Foundation
class
DeeplinksRouter
{
static
let
urlScheme
=
"oneweather"
enum
UrlPathComponent
:
String
{
case
home
=
"home"
case
today
=
"today"
case
forecast
=
"forecast"
case
hourly
=
"hourly"
case
daily
=
"daily"
case
precipitation
=
"precipitation"
case
radar
=
"radar"
case
sunMoon
=
"sun-moon"
}
enum
UrlQueryParam
:
String
{
case
cityId
=
"cityid"
case
lat
=
"lat"
case
lon
=
"lon"
case
fipsCode
=
"fipsCode"
}
private
var
appCoordinator
:
AppCoordinator
{
AppCoordinator
.
instance
}
private
let
log
=
Logger
(
componentName
:
"Router"
)
private
var
locationManager
:
LocationManager
{
return
LocationManager
.
shared
}
public
init
()
{
}
private
func
is1WUrl
(
_
url
:
URL
)
->
Bool
{
if
let
scheme
=
url
.
scheme
{
if
scheme
.
lowercased
()
==
"oneweather"
{
return
true
}
}
if
let
host
=
url
.
host
{
if
host
==
"1weatherapp.com"
||
host
==
"oneweatherapp.com"
{
return
true
}
}
return
false
}
private
func
parseLocation
(
from
url
:
URL
)
->
PartialLocation
?
{
guard
let
queryItems
=
URLComponents
(
url
:
url
,
resolvingAgainstBaseURL
:
false
)?
.
queryItems
else
{
return
nil
}
var
queryItemsLookup
=
[
UrlQueryParam
:
String
](
minimumCapacity
:
queryItems
.
count
)
for
item
:
URLQueryItem
in
queryItems
{
if
let
knownParam
=
UrlQueryParam
(
rawValue
:
item
.
name
.
lowercased
())
{
queryItemsLookup
[
knownParam
]
=
item
.
value
}
}
guard
let
cityId
=
queryItemsLookup
[
.
cityId
]
else
{
return
nil
}
let
result
=
GeoNamesPlace
()
result
.
latitude
=
queryItemsLookup
[
.
lat
]
result
.
longitude
=
queryItemsLookup
[
.
lon
]
result
.
fipsCode
=
queryItemsLookup
[
.
fipsCode
]
result
.
optionalCityId
=
cityId
return
result
}
public
func
open
(
url
:
URL
)
{
guard
self
.
is1WUrl
(
url
)
else
{
log
.
info
(
"not 1Weather URL, ignore:
\(
url
)
"
)
return
}
log
.
info
(
"open URL:
\(
url
)
"
)
var
pathComponents
=
url
.
pathComponents
while
let
currentComponent
=
pathComponents
.
popLast
()
{
if
let
parsedComponent
=
UrlPathComponent
(
rawValue
:
currentComponent
.
lowercased
())
{
log
.
debug
(
"Parsed path:
\(
parsedComponent
.
rawValue
)
"
)
if
let
location
=
parseLocation
(
from
:
url
)
{
log
.
debug
(
"Location found:
\(
location
)
"
)
locationManager
.
addIfNeeded
(
partialLocation
:
location
,
selectLocation
:
true
)
}
else
{
log
.
debug
(
"No location."
)
}
switch
parsedComponent
{
case
.
home
:
openToday
()
case
.
today
:
openToday
()
case
.
forecast
:
openForecast
(
timePeriod
:
nil
)
case
.
hourly
:
openForecast
(
timePeriod
:
.
hourly
)
case
.
daily
:
openForecast
(
timePeriod
:
.
daily
)
case
.
precipitation
:
openPrecipitation
()
case
.
radar
:
openRadar
()
case
.
sunMoon
:
openSunMoon
()
}
break
}
else
{
log
.
debug
(
"Skip unknown path component:
\(
currentComponent
)
"
)
}
}
}
public
func
openToday
()
{
DispatchQueue
.
main
.
async
{
self
.
log
.
info
(
"open Today"
)
self
.
appCoordinator
.
openToday
()
}
}
public
func
openForecast
(
timePeriod
:
TimePeriod
?)
{
DispatchQueue
.
main
.
async
{
self
.
log
.
info
(
"open Forecast"
)
self
.
appCoordinator
.
openForecast
(
timePeriod
:
timePeriod
)
}
}
public
func
openPrecipitation
()
{
DispatchQueue
.
main
.
async
{
self
.
log
.
info
(
"open Precipitation"
)
//not implemented
self
.
openToday
()
}
}
public
func
openRadar
()
{
DispatchQueue
.
main
.
async
{
self
.
log
.
info
(
"open Radar"
)
self
.
appCoordinator
.
openRadar
()
}
}
public
func
openSunMoon
()
{
DispatchQueue
.
main
.
async
{
self
.
log
.
info
(
"open SunMoon"
)
//not implemented
self
.
openToday
()
}
}
public
func
openVideo
()
{
DispatchQueue
.
main
.
async
{
self
.
log
.
info
(
"open Video"
)
//not implemented
self
.
openToday
()
}
}
public
func
openWeatherDetail
()
{
DispatchQueue
.
main
.
async
{
self
.
log
.
info
(
"open WeatherDetail"
)
//not implemented
self
.
openToday
()
}
}
}
1Weather/Model/DeviceLocationMonitor.swift
View file @
7d111caa
...
...
@@ -103,6 +103,14 @@ internal class DeviceLocationMonitor: NSObject {
// this should never be called.
return
""
}
public
var
fipsCode
:
String
?
{
nil
}
public
var
optionalCityId
:
String
?
{
return
nil
}
}
public
typealias
CurrentLocationCompletion
=
(
LocationRequestResult
)
->
()
...
...
1Weather/Model/LocationManager.swift
View file @
7d111caa
...
...
@@ -431,6 +431,19 @@ public class LocationManager {
completion
(
location
)
return
}
if
let
fipsCode
=
partialLocation
.
fipsCode
{
if
let
existingLocation
=
locations
.
first
(
where
:
{
$0
.
fipsCode
==
fipsCode
})
{
completion
(
existingLocation
)
return
}
}
if
let
cityId
=
partialLocation
.
optionalCityId
{
if
let
existingLocation
=
locations
.
first
(
where
:
{
$0
.
cityId
==
cityId
})
{
completion
(
existingLocation
)
return
}
}
let
location
=
CLLocation
(
latitude
:
lat
,
longitude
:
lon
)
let
geocodeCompletion
:
CLGeocodeCompletionHandler
=
{
[
weak
self
]
(
placemarks
,
error
)
in
...
...
1Weather/Model/ModelObjects/GeoNamesPlace.swift
View file @
7d111caa
...
...
@@ -19,6 +19,8 @@ final class GeoNamesPlace: NSObject {
var
countryCode
:
String
?
var
fcodeName
:
String
?
// airport if airport
var
toponymName
:
String
?
// airport name if fcodeName is airport
var
fipsCode
:
String
?
var
optionalCityId
:
String
?
func
detailName
()
->
String
{
var
sb
=
String
()
...
...
1Weather/Model/ModelObjects/Location.swift
View file @
7d111caa
...
...
@@ -225,4 +225,8 @@ extension Location: PartialLocation {
return
sb
}
}
public
var
optionalCityId
:
String
?
{
cityId
}
}
1Weather/Model/Protocols/PartialLocation.swift
View file @
7d111caa
...
...
@@ -15,6 +15,8 @@ public protocol PartialLocation {
var
countryName
:
String
?
{
get
}
var
region
:
String
?
{
get
}
var
cityName
:
String
?
{
get
}
var
fipsCode
:
String
?
{
get
}
var
optionalCityId
:
String
?
{
get
}
var
nameForDisplay
:
String
{
get
}
}
1Weather/Network/PushNotificationsManager.swift
0 → 100644
View file @
7d111caa
//
// PushNotificationsManager.swift
// OneWeather
//
// Created by Demid Merzlyakov on 18.01.2021.
// Copyright © 2021 OneLouder, Inc. All rights reserved.
//
import
Foundation
import
MoEngage
/*
class PushNotificationsManager: NSObject {
// MARK: - Private
private let log = Logger(componentName: "PushNotificationsManager")
// MARK: - Public
public override init() {
super.init()
UNUserNotificationCenter.current().delegate = self
}
public static let shared = PushNotificationsManager()
// TODO: forced re-register on timeout
public func registerForRemoteNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self]
(granted, error) in
guard let self = self else { return }
if let error = error {
self.log.error("Error: \(error)")
}
else {
self.log.info("Granted: \(granted)")
}
MoEngage.sharedInstance().registerForRemoteNotification(withCategories: nil, withUserNotificationCenterDelegate: self)
}
}
public func set(pushToken: Data) {
let tokenString = pushToken.map { String(format: "%02.2hhx", $0) }.joined()
log.info("Got new APNS token: \(tokenString)")
MoEngage.sharedInstance().setPushToken(pushToken)
}
}
extension PushNotificationsManager: UNUserNotificationCenterDelegate {
private enum MoEngageScreenName: String {
case mainScreen = "com.handmark.expressweather.ui.activities.mainactivity"
case detailsScreen = "com.handmark.expressweather.ui.activities.weatherdetailsactivity"
case videosScreen = "com.handmark.expressweather.ui.activities.videodetailsactivity"
}
private func makeMoEngageDeeplinkUrl(from response: UNNotificationResponse) -> URL? {
guard let appExtra = response.notification.request.content.userInfo["app_extra"] as? [String: Any] else {
return nil
}
if let urlString = appExtra["moe_deeplink"] as? String, let url = URL(string: urlString) {
return url
}
return nil
}
private func switchLocationIfNeeded(parsing screenData: [String: Any]?, using router: Router) {
guard let screenData = screenData else {
return
}
if let cityId = screenData["location"] as? String {
let lat = screenData["lat"] as? String
let lon = screenData["lon"] as? String
if let location = WeatherUpdateManager.shared.locationFrom(cityId: cityId, lat: lat, lon: lon) {
log.info("MoEngage push: location found: \(location)")
WeatherUpdateManager.shared.addLocation(location)
}
else {
log.error("MoEngage push:Location found but couldn't be parsed.")
}
}
else {
log.debug("MoEngage push: no cityId found")
}
}
private func handleMoEngageDeeplinks(for response: UNNotificationResponse) {
let userInfo = response.notification.request.content.userInfo
guard userInfo["moengage"] as? [String: Any] != nil else {
log.debug("No MoEngage data found.")
return
}
log.info("MoEngage push received: \(userInfo)")
guard let tabBar = TabBarController.viewController else {
log.error("No tab bar found to initialize Router")
return
}
let router = Router(tabBar: tabBar)
if let moEngageUrl = makeMoEngageDeeplinkUrl(from: response) {
router.open(url: moEngageUrl)
}
else {
guard let appExtra = userInfo["app_extra"] as? [String: Any] else {
log.error("MoEngage push: no app_extra found.")
return
}
guard let screenNameStr = (appExtra["screenName"] as? String)?.trim().lowercased(),
let screenName = MoEngageScreenName(rawValue: screenNameStr) else {
log.error("MoEngage push: screenName not found or is not correct.")
return
}
let screenData = appExtra["screenData"] as? [String: Any]
switchLocationIfNeeded(parsing: screenData, using: router)
switch screenName {
case .detailsScreen:
router.openWeatherDetail()
case .videosScreen:
router.openVideo()
case .mainScreen:
guard let launchScreenId = screenData?["LaunchScreenID"] as? String else {
log.error("MoEngage push: LaunchScreenID not found for a MainActivity screen.")
return
}
switch launchScreenId {
case "0":
router.openToday()
case "1":
router.openForecast(detailType: nil)
case "2":
router.openPrecipitation()
case "3":
router.openRadar()
case "4":
router.openSunMoon()
default:
log.error("MoEngage push: Unknown launch screen id: \(launchScreenId)")
}
}
}
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
MoEngage.sharedInstance().userNotificationCenter(center, didReceive: response) // not sure we should call it for PushPin notifications, too
guard !pushPinManager.processPushPinNotification(response) else { return }
handleMoEngageDeeplinks(for: response)
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
if UIApplication.shared.applicationState == .active {
analyticsLogEvent(ANALYTICS_PUSH_RECEIVED)
} else {
analyticsLogEvent(ANALYTICS_PUSH_SELECTED, params: ["source": "background"])
}
log.debug("Got a push notification: \(notification.request.content.userInfo)");
completionHandler([.sound,.alert])
}
}
*/
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