Commit 7d111caa by Demid Merzlyakov

DeeplinksRouter.

parent bdca0e18
......@@ -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 */,
......
......@@ -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() {
......
//
// 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()
}
}
}
......@@ -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) -> ()
......
......@@ -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
......
......@@ -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()
......
......@@ -225,4 +225,8 @@ extension Location: PartialLocation {
return sb
}
}
public var optionalCityId: String? {
cityId
}
}
......@@ -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 }
}
//
// 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])
}
}
*/
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