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
aced138f
Commit
aced138f
authored
Apr 28, 2021
by
Demid Merzlyakov
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature/push-notifications'
parents
8f30fea7
cf8f3803
Show whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
710 additions
and
24 deletions
+710
-24
1Weather.xcodeproj/project.pbxproj
+28
-0
1Weather/1Weather.entitlements
+17
-0
1Weather/Analytics/AnalyticsParameter.swift
+1
-0
1Weather/AppDelegate.swift
+7
-8
1Weather/Coordinators/AppCoordinator.swift
+29
-0
1Weather/Coordinators/DeeplinksRouter.swift
+186
-0
1Weather/Coordinators/ForecastCoordinator.swift
+6
-0
1Weather/Info.plist
+4
-0
1Weather/Model/DeviceLocationMonitor.swift
+8
-0
1Weather/Model/LocationManager.swift
+48
-5
1Weather/Model/ModelObjects/GeoNamesPlace.swift
+2
-0
1Weather/Model/ModelObjects/Location.swift
+4
-0
1Weather/Model/Protocols/PartialLocation.swift
+2
-0
1Weather/Network/Notifications/BlendFIPSSource.swift
+108
-0
1Weather/Network/Notifications/FIPSSource.swift
+14
-0
1Weather/Network/Notifications/Model/FIPSResponse.swift
+46
-0
1Weather/Network/PushNotificationsManager.swift
+182
-0
1Weather/UI/View controllers/AppTabBarController.swift
+7
-6
1Weather/UI/View controllers/Forecast/ForecastViewController.swift
+9
-3
1Weather/ViewModels/ForecastViewModel.swift
+1
-1
1Weather/ViewModels/TodayViewModel.swift
+1
-1
No files found.
1Weather.xcodeproj/project.pbxproj
View file @
aced138f
...
...
@@ -190,6 +190,7 @@
CE13B97B2626FB11007CBD4D
/* PSMLocationSDK.xcframework in Frameworks */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE13B7DC262478E7007CBD4D
/* PSMLocationSDK.xcframework */
;
};
CE13B97C2626FB11007CBD4D
/* PSMLocationSDK.xcframework in Embed Frameworks */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE13B7DC262478E7007CBD4D
/* PSMLocationSDK.xcframework */
;
settings
=
{
ATTRIBUTES
=
(
CodeSignOnCopy
,
RemoveHeadersOnCopy
,
);
};
};
CE13B98726273236007CBD4D
/* NWSAlert.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE13B98626273236007CBD4D
/* NWSAlert.swift */
;
};
CE14445F2638B6CF008E2162
/* StoreKit.framework in Frameworks */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE14445E2638B6CF008E2162
/* StoreKit.framework */
;
};
CE28474F26159857006C8DC5
/* HealthSource.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE28474E26159857006C8DC5
/* HealthSource.swift */
;
};
CE28475226159A32006C8DC5
/* BlendHealthModels.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE28475126159A32006C8DC5
/* BlendHealthModels.swift */
;
};
CE28475D2615A5B3006C8DC5
/* Health.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE28475C2615A5B3006C8DC5
/* Health.swift */
;
};
...
...
@@ -199,6 +200,8 @@
CE308B2A2637EA8E001ECD8A
/* _CoreNWSAlert.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE308B282637EA8E001ECD8A
/* _CoreNWSAlert.swift */
;
};
CE308B2B2637EA8E001ECD8A
/* _CoreNotifications.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE308B292637EA8E001ECD8A
/* _CoreNotifications.swift */
;
};
CE376C98261EE484000B1159
/* LaunchScreen.storyboard in Resources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE376C97261EE484000B1159
/* LaunchScreen.storyboard */
;
};
CE3A36C72638A77E002CACC3
/* BlendFIPSSource.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE3A36C62638A77E002CACC3
/* BlendFIPSSource.swift */
;
};
CE3A36ED2638A825002CACC3
/* FIPSResponse.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE3A36EC2638A825002CACC3
/* FIPSResponse.swift */
;
};
CE578FD325F7E89400E8B85D
/* DayTimeWeather.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE578FD225F7E89400E8B85D
/* DayTimeWeather.swift */
;
};
CE578FE525FB415F00E8B85D
/* CityCell.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE578FE225FB415F00E8B85D
/* CityCell.swift */
;
};
CE578FE625FB415F00E8B85D
/* LocationViewController.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CE578FE325FB415F00E8B85D
/* LocationViewController.swift */
;
};
...
...
@@ -234,6 +237,9 @@
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 */
;
};
CEBAC2122638968D00A89681
/* FIPSSource.swift in Sources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
CEBAC2112638968D00A89681
/* FIPSSource.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 */
;
};
...
...
@@ -473,6 +479,8 @@
CE13B88C26248A77007CBD4D
/* GoogleService-Info-Production.plist */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
text.plist.xml
;
path
=
"GoogleService-Info-Production.plist"
;
sourceTree
=
"<group>"
;
};
CE13B88D26248A77007CBD4D
/* GoogleService-Info-Staging.plist */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
text.plist.xml
;
path
=
"GoogleService-Info-Staging.plist"
;
sourceTree
=
"<group>"
;
};
CE13B98626273236007CBD4D
/* NWSAlert.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
NWSAlert.swift
;
sourceTree
=
"<group>"
;
};
CE14445D2638B6A8008E2162
/* 1Weather.entitlements */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
text.plist.entitlements
;
path
=
1Weather.entitlements
;
sourceTree
=
"<group>"
;
};
CE14445E2638B6CF008E2162
/* StoreKit.framework */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
wrapper.framework
;
name
=
StoreKit.framework
;
path
=
System/Library/Frameworks/StoreKit.framework
;
sourceTree
=
SDKROOT
;
};
CE28474E26159857006C8DC5
/* HealthSource.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
HealthSource.swift
;
sourceTree
=
"<group>"
;
};
CE28475126159A32006C8DC5
/* BlendHealthModels.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
BlendHealthModels.swift
;
sourceTree
=
"<group>"
;
};
CE28475C2615A5B3006C8DC5
/* Health.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
Health.swift
;
sourceTree
=
"<group>"
;
};
...
...
@@ -482,6 +490,8 @@
CE308B282637EA8E001ECD8A
/* _CoreNWSAlert.swift */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.swift
;
path
=
_CoreNWSAlert.swift
;
sourceTree
=
"<group>"
;
};
CE308B292637EA8E001ECD8A
/* _CoreNotifications.swift */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.swift
;
path
=
_CoreNotifications.swift
;
sourceTree
=
"<group>"
;
};
CE376C97261EE484000B1159
/* LaunchScreen.storyboard */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
file.storyboard
;
path
=
LaunchScreen.storyboard
;
sourceTree
=
"<group>"
;
};
CE3A36C62638A77E002CACC3
/* BlendFIPSSource.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
BlendFIPSSource.swift
;
sourceTree
=
"<group>"
;
};
CE3A36EC2638A825002CACC3
/* FIPSResponse.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
FIPSResponse.swift
;
sourceTree
=
"<group>"
;
};
CE578FD225F7E89400E8B85D
/* DayTimeWeather.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
DayTimeWeather.swift
;
sourceTree
=
"<group>"
;
};
CE578FE225FB415F00E8B85D
/* CityCell.swift */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.swift
;
path
=
CityCell.swift
;
sourceTree
=
"<group>"
;
};
CE578FE325FB415F00E8B85D
/* LocationViewController.swift */
=
{
isa
=
PBXFileReference
;
fileEncoding
=
4
;
lastKnownFileType
=
sourcecode.swift
;
path
=
LocationViewController.swift
;
sourceTree
=
"<group>"
;
};
...
...
@@ -517,6 +527,9 @@
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>"
;
};
CEBAC2112638968D00A89681
/* FIPSSource.swift */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
sourcecode.swift
;
path
=
FIPSSource.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>"
;
};
...
...
@@ -554,6 +567,7 @@
isa
=
PBXFrameworksBuildPhase
;
buildActionMask
=
2147483647
;
files
=
(
CE14445F2638B6CF008E2162
/* StoreKit.framework in Frameworks */
,
CE13B97B2626FB11007CBD4D
/* PSMLocationSDK.xcframework in Frameworks */
,
34EAFD887EF2D1D7449A016C
/* Pods_1Weather.framework in Frameworks */
,
);
...
...
@@ -624,6 +638,7 @@
CD1237C1255D5C5900C98139
/* 1Weather */
=
{
isa
=
PBXGroup
;
children
=
(
CE14445D2638B6A8008E2162
/* 1Weather.entitlements */
,
CE13B78926247474007CBD4D
/* External */
,
CE13B7EC262480B3007CBD4D
/* Ads */
,
CEF959632600C2E300975FAA
/* Analytics */
,
...
...
@@ -689,6 +704,7 @@
CD17C5F925D15B5500EE884E
/* Coordinators */
=
{
isa
=
PBXGroup
;
children
=
(
CEBAC1C72638240800A89681
/* DeeplinksRouter.swift */
,
CD17C60125D15C8500EE884E
/* CoordinatorProtocol.swift */
,
CD17C5FA25D15B6B00EE884E
/* AppCoordinator.swift */
,
CD17C5FE25D15B7C00EE884E
/* TodayCoordinator.swift */
,
...
...
@@ -1222,6 +1238,8 @@
CE13B98026272A13007CBD4D
/* Model */
,
CE0456232629C04C003D252B
/* NWSAlertsManager.swift */
,
CEE0A1A326317A8F0044C257
/* NWSAlertInfoParser.swift */
,
CEBAC2112638968D00A89681
/* FIPSSource.swift */
,
CE3A36C62638A77E002CACC3
/* BlendFIPSSource.swift */
,
);
path
=
Notifications
;
sourceTree
=
"<group>"
;
...
...
@@ -1234,6 +1252,7 @@
CEE0A1A126317A3F0044C257
/* NWSSeverityLevel.swift */
,
CEE0A19F26317A1E0044C257
/* NWSAlertExtendedInfo.swift */
,
CEE0A17A263179E50044C257
/* NWSAlertInfoBlock.swift */
,
CE3A36EC2638A825002CACC3
/* FIPSResponse.swift */
,
);
path
=
Model
;
sourceTree
=
"<group>"
;
...
...
@@ -1381,6 +1400,7 @@
CEAFF09925DFC78200DF4EBF
/* Network */
=
{
isa
=
PBXGroup
;
children
=
(
CEBAC1C52638236D00A89681
/* PushNotificationsManager.swift */
,
87C1724825FF94F400DA3464
/* ConfigManager.swift */
,
87C171F325FF7A4000DA3464
/* PopularCitiesManager.swift */
,
87C171F125FF7A3300DA3464
/* Weather */
,
...
...
@@ -1486,6 +1506,7 @@
DBFD169AA2AA6A3CB5B68BB5
/* Frameworks */
=
{
isa
=
PBXGroup
;
children
=
(
CE14445E2638B6CF008E2162
/* StoreKit.framework */
,
6B543196B99BA697763514F6
/* Pods_1Weather.framework */
,
);
name
=
Frameworks
;
...
...
@@ -1731,6 +1752,7 @@
CD67617C2625A60B0079D273
/* MapLayersDismissAnimator.swift in Sources */
,
CEAFF08C25DFC6BD00DF4EBF
/* DailyWeather.swift in Sources */
,
CEDE4F0B25EFA3A7007457E9
/* UpdatableModelObject.swift in Sources */
,
CE3A36C72638A77E002CACC3
/* BlendFIPSSource.swift in Sources */
,
CE13B814262480B3007CBD4D
/* BRNativeBannerView.swift in Sources */
,
CE13B7E126247BF9007CBD4D
/* UserDefaultsOptionalValue.swift in Sources */
,
CE8962A626175DF500CA274A
/* _CoreHealth.swift in Sources */
,
...
...
@@ -1833,6 +1855,7 @@
CE9F01C1261B3776009BA500
/* CoreDataUtils.swift in Sources */
,
CD67616D262587D30079D273
/* UITabBarController+Hide.swift in Sources */
,
CDC6126225E8DAB800188DA7
/* MoonPhaseCell.swift in Sources */
,
CEBAC2122638968D00A89681
/* FIPSSource.swift in Sources */
,
CD37D3D6260C93B3002669D6
/* MenuCellFactory.swift in Sources */
,
CD8B60AD263819400055CB3F
/* NWSAlertInfoBlockTableViewCell.swift in Sources */
,
87D815AC2636D61D0015A6D1
/* NWSAlertViewModel.swift in Sources */
,
...
...
@@ -1871,6 +1894,7 @@
CD32CE0B260C744A00235081
/* MenuCoordinator.swift in Sources */
,
CD55E0BB2615EE2400CC4DC7
/* PollutantView.swift in Sources */
,
CE308B2A2637EA8E001ECD8A
/* _CoreNWSAlert.swift in Sources */
,
CE3A36ED2638A825002CACC3
/* FIPSResponse.swift in Sources */
,
CD8B60B3263819790055CB3F
/* NWSAlertCell.swift in Sources */
,
CE13B81C262480B3007CBD4D
/* Interstitial.swift in Sources */
,
CDDE8D7C262EED3C00267931
/* MapLegendSevereView.swift in Sources */
,
...
...
@@ -1898,11 +1922,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 */
,
...
...
@@ -2072,6 +2098,7 @@
buildSettings
=
{
ASSETCATALOG_COMPILER_APPICON_NAME
=
AppIcon
;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME
=
AccentColor
;
CODE_SIGN_ENTITLEMENTS
=
1Weather/1Weather.entitlements
;
CODE_SIGN_STYLE
=
Automatic
;
CURRENT_PROJECT_VERSION
=
SET_BY_BUILD_SCRIPT
;
DEBUG_INFORMATION_FORMAT
=
"dwarf-with-dsym"
;
...
...
@@ -2101,6 +2128,7 @@
buildSettings
=
{
ASSETCATALOG_COMPILER_APPICON_NAME
=
AppIcon
;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME
=
AccentColor
;
CODE_SIGN_ENTITLEMENTS
=
1Weather/1Weather.entitlements
;
CODE_SIGN_STYLE
=
Automatic
;
CURRENT_PROJECT_VERSION
=
SET_BY_BUILD_SCRIPT
;
DEBUG_INFORMATION_FORMAT
=
"dwarf-with-dsym"
;
...
...
1Weather/1Weather.entitlements
0 → 100644
View file @
aced138f
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist
version=
"1.0"
>
<dict>
<key>
aps-environment
</key>
<string>
development
</string>
<key>
com.apple.security.application-groups
</key>
<array>
<string>
group.com.onelouder.oneweather.MoEngage
</string>
<string>
group.com.onelouder.oneweather
</string>
</array>
<key>
keychain-access-groups
</key>
<array>
<string>
$(AppIdentifierPrefix)psm.lsdk
</string>
</array>
</dict>
</plist>
1Weather/Analytics/AnalyticsParameter.swift
View file @
aced138f
...
...
@@ -18,4 +18,5 @@ public enum AnalyticsParameter: String {
case
ANALYTICS_KEY_PLACEMENT_NAME
=
"placement_name"
case
ANALYTICS_KEY_AD_UNIT_ID
=
"AD_PLACEMENT_ID"
case
ANALYTICS_KEY_AD_ADAPTER
=
"AD_ADAPTER"
case
ANALYTICS_KEY_PUSH_NOTIFICATION_SOURCE
=
"source"
}
1Weather/AppDelegate.swift
View file @
aced138f
...
...
@@ -52,13 +52,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
.
withCrashReporting
(
false
)
.
withAppVersion
(
appVersion
))
// TODO: Radar Setup
// WDT radar setup
// SwarmManager.sharedManager.authentication = SkywiseAuthentication(
// app_id: WDT_APP_ID,
// app_key: WDT_APP_KEY
// )
//MoEngage setup
var
moEngageConfig
=
MOSDKConfig
(
appID
:
kMoEngageAppId
)
moEngageConfig
.
appGroupID
=
"group.com.onelouder.oneweather.MoEngage"
...
...
@@ -70,11 +63,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
MoEngage
.
sharedInstance
()
.
initializeLive
(
with
:
moEngageConfig
,
andLaunchOptions
:
launchOptions
)
#endif
PushNotificationsManager
.
shared
.
registerForRemoteNotifications
()
return
true
}
func
applicationDidBecomeActive
(
_
application
:
UIApplication
)
{
LocationManager
.
shared
.
updateAllWeatherIfNeeded
()
LocationManager
.
shared
.
updateEverythingIfNeeded
()
}
func
application
(
_
application
:
UIApplication
,
didRegisterForRemoteNotificationsWithDeviceToken
deviceToken
:
Data
)
{
PushNotificationsManager
.
shared
.
set
(
pushToken
:
deviceToken
)
}
}
1Weather/Coordinators/AppCoordinator.swift
View file @
aced138f
...
...
@@ -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
()
{
...
...
@@ -40,6 +45,30 @@ class AppCoordinator: Coordinator {
tabBarController
.
setupTabBar
()
}
public
func
openToday
()
{
tabBarController
.
selectedIndex
=
AppTabBarController
.
AppTab
.
today
.
rawValue
}
public
func
openForecast
(
timePeriod
:
TimePeriod
?)
{
let
forecastIndex
=
AppTabBarController
.
AppTab
.
forecast
.
rawValue
tabBarController
.
selectedIndex
=
forecastIndex
if
let
timePeriod
=
timePeriod
{
if
let
forecastCoordinator
=
childCoordinators
[
forecastIndex
]
as?
ForecastCoordinator
{
forecastCoordinator
.
open
(
timePeriod
:
timePeriod
)
}
}
}
public
func
openRadar
()
{
tabBarController
.
selectedIndex
=
AppTabBarController
.
AppTab
.
radar
.
rawValue
}
public
func
openNotifications
()
{
let
notificationsCoordinator
=
NotificationsCoordinator
(
parentViewController
:
tabBarController
)
notificationsCoordinator
.
parentCoordinator
=
self
notificationsCoordinator
.
start
()
}
func
viewControllerDidEnd
(
controller
:
UIViewController
)
{
//
}
...
...
1Weather/Coordinators/DeeplinksRouter.swift
0 → 100644
View file @
aced138f
//
// 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
}
public
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
openAlerts
()
{
DispatchQueue
.
main
.
async
{
self
.
log
.
info
(
"open Alerts"
)
self
.
appCoordinator
.
openNotifications
()
}
}
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/Coordinators/ForecastCoordinator.swift
View file @
aced138f
...
...
@@ -12,6 +12,7 @@ class ForecastCoordinator: Coordinator {
private
let
forecastViewModel
=
ForecastViewModel
(
locationManager
:
LocationManager
.
shared
)
private
let
navigationController
=
UINavigationController
(
nibName
:
nil
,
bundle
:
nil
)
private
var
tabBarController
:
UITabBarController
?
private
var
forecastViewController
:
ForecastViewController
?
//Public
var
childCoordinators
=
[
Coordinator
]()
...
...
@@ -25,6 +26,11 @@ class ForecastCoordinator: Coordinator {
let
forecastViewController
=
ForecastViewController
(
viewModel
:
forecastViewModel
)
navigationController
.
viewControllers
=
[
forecastViewController
]
tabBarController
?
.
add
(
viewController
:
navigationController
)
self
.
forecastViewController
=
forecastViewController
}
public
func
open
(
timePeriod
:
TimePeriod
)
{
self
.
forecastViewController
?
.
switchTo
(
timePeriod
:
timePeriod
)
}
func
viewControllerDidEnd
(
controller
:
UIViewController
)
{
...
...
1Weather/Info.plist
View file @
aced138f
...
...
@@ -39,6 +39,10 @@
<
/
a
rr
a
y
>
<
k
e
y
>
UIApplicationSupportsIndirectInputEvents
<
/k
e
y
>
<
tru
e
/
>
<
k
e
y
>
UIBackgroundModes
<
/k
e
y
>
<
a
rr
a
y
>
<
string
>
remote-notification
<
/string
>
<
/
a
rr
a
y
>
<
k
e
y
>
UILaunchStoryboardName
<
/k
e
y
>
<
string
>
LaunchScreen
<
/string
>
<
k
e
y
>
UIRequiredDeviceCapabilities
<
/k
e
y
>
...
...
1Weather/Model/DeviceLocationMonitor.swift
View file @
aced138f
...
...
@@ -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 @
aced138f
...
...
@@ -21,7 +21,9 @@ public class LocationManager {
private
let
weatherUpdateSource
:
WeatherSource
private
let
healthSource
:
HealthSource
private
let
fipsSource
:
FIPSSource
public
let
nwsAlertsManager
:
NWSAlertsManager
private
let
pushNotificationsManager
:
PushNotificationsManager
private
let
storage
:
Storage
private
var
defaultLocation
=
Location
(
deviceLocation
:
false
,
coordinates
:
.
init
(
latitude
:
37.3230
,
longitude
:
-
122.0322
),
// Cupertino
...
...
@@ -53,7 +55,7 @@ public class LocationManager {
self
?
.
_locations
=
locations
self
?
.
_selectedLocationIndex
=
selectedIndex
self
?
.
handleLocationsChange
(
locationsChanged
:
true
,
selectedLocationChanged
:
true
)
self
?
.
update
AllWeather
IfNeeded
()
self
?
.
update
Everything
IfNeeded
()
}
}
...
...
@@ -154,15 +156,19 @@ public class LocationManager {
weatherUpdateSource
:
WdtWeatherSource
(),
healthSource
:
BlendHealthSource
(),
nwsAlertsManager
:
NWSAlertsManager
(),
fipsSource
:
BlendFIPSSource
(),
pushNotificationsManager
:
PushNotificationsManager
.
shared
,
storage
:
DelayedSaveStorage
(
storage
:
CoreDataStorage
(),
delay
:
2
))
public
let
maxLocationsCount
=
12
public
init
(
weatherUpdateSource
:
WeatherSource
,
healthSource
:
HealthSource
,
nwsAlertsManager
:
NWSAlertsManager
,
storage
:
Storage
)
{
public
init
(
weatherUpdateSource
:
WeatherSource
,
healthSource
:
HealthSource
,
nwsAlertsManager
:
NWSAlertsManager
,
fipsSource
:
FIPSSource
,
pushNotificationsManager
:
PushNotificationsManager
,
storage
:
Storage
)
{
self
.
weatherUpdateSource
=
weatherUpdateSource
self
.
healthSource
=
healthSource
self
.
deviceLocationMonitor
=
DeviceLocationMonitor
()
self
.
nwsAlertsManager
=
nwsAlertsManager
self
.
fipsSource
=
fipsSource
self
.
pushNotificationsManager
=
pushNotificationsManager
self
.
storage
=
storage
self
.
deviceLocationMonitor
.
delegate
=
self
...
...
@@ -185,7 +191,7 @@ public class LocationManager {
}
}
public
func
update
AllWeather
IfNeeded
()
{
public
func
update
Everything
IfNeeded
()
{
let
locations
=
self
.
locations
guard
locations
.
count
>
0
else
{
log
.
info
(
"Update all: update default location if needed."
)
...
...
@@ -204,6 +210,25 @@ public class LocationManager {
}
updateHealth
(
for
:
location
)
updateNotifications
(
for
:
location
)
getFipsIfNeeded
(
for
:
location
)
}
pushNotificationsManager
.
updateNwsSubscriptions
(
for
:
locations
)
}
public
func
getFipsIfNeeded
(
for
location
:
Location
)
{
if
location
.
fipsCode
==
nil
{
fipsSource
.
getFipsCode
(
for
:
location
)
{
[
weak
self
]
(
fipsCode
)
in
if
let
fipsCode
=
fipsCode
{
self
?
.
makeChanges
(
to
:
location
,
in
:
"getFIPS"
,
changes
:
{
(
location
)
->
Location
in
var
updatedLocation
=
location
updatedLocation
.
fipsCode
=
fipsCode
return
updatedLocation
},
completion
:
{
[
weak
self
]
in
guard
let
self
=
self
else
{
return
}
self
.
pushNotificationsManager
.
updateNwsSubscriptions
(
for
:
self
.
locations
)
})
}
}
}
}
...
...
@@ -321,8 +346,11 @@ public class LocationManager {
}
}
private
func
makeChanges
(
to
location
:
Location
,
in
operation
:
String
,
changes
:
@escaping
(
Location
)
->
Location
)
{
private
func
makeChanges
(
to
location
:
Location
,
in
operation
:
String
,
changes
:
@escaping
(
Location
)
->
Location
,
completion
:
(()
->
())?
=
nil
)
{
DispatchQueue
.
main
.
async
{
defer
{
completion
?()
}
if
let
indexToUpdate
=
self
.
locations
.
firstIndex
(
where
:
{
$0
==
location
})
{
self
.
locations
[
indexToUpdate
]
=
changes
(
self
.
locations
[
indexToUpdate
])
}
...
...
@@ -365,7 +393,7 @@ public class LocationManager {
selectedLocationIndex
=
locations
.
count
-
1
}
}
update
AllWeather
IfNeeded
()
update
Everything
IfNeeded
()
}
public
func
addIfNeeded
(
partialLocation
:
PartialLocation
,
selectLocation
:
Bool
)
{
...
...
@@ -421,6 +449,20 @@ public class LocationManager {
}
private
func
makeLocation
(
from
partialLocation
:
PartialLocation
,
completion
:
@escaping
(
Location
?)
->
())
{
if
let
fipsCode
=
partialLocation
.
fipsCode
{
if
let
existingLocation
=
locations
.
first
(
where
:
{
$0
.
fipsCode
==
fipsCode
})
{
log
.
info
(
"Geo lookup: found location using FIPS code:
\(
existingLocation
)
"
)
completion
(
existingLocation
)
return
}
}
if
let
cityId
=
partialLocation
.
optionalCityId
{
if
let
existingLocation
=
locations
.
first
(
where
:
{
$0
.
cityId
==
cityId
})
{
log
.
info
(
"Geo lookup: found location using city ID:
\(
existingLocation
)
"
)
completion
(
existingLocation
)
return
}
}
guard
let
latStr
=
partialLocation
.
lat
,
let
lonStr
=
partialLocation
.
lon
,
let
lat
=
CLLocationDegrees
(
latStr
),
let
lon
=
CLLocationDegrees
(
lonStr
)
else
{
log
.
error
(
"Geo lookup: no coordinates present:
\(
partialLocation
)
"
)
var
location
:
Location
?
=
nil
...
...
@@ -431,6 +473,7 @@ public class LocationManager {
completion
(
location
)
return
}
let
location
=
CLLocation
(
latitude
:
lat
,
longitude
:
lon
)
let
geocodeCompletion
:
CLGeocodeCompletionHandler
=
{
[
weak
self
]
(
placemarks
,
error
)
in
...
...
1Weather/Model/ModelObjects/GeoNamesPlace.swift
View file @
aced138f
...
...
@@ -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 @
aced138f
...
...
@@ -225,4 +225,8 @@ extension Location: PartialLocation {
return
sb
}
}
public
var
optionalCityId
:
String
?
{
cityId
}
}
1Weather/Model/Protocols/PartialLocation.swift
View file @
aced138f
...
...
@@ -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/Notifications/BlendFIPSSource.swift
0 → 100644
View file @
aced138f
//
// BlendFIPSSource.swift
// 1Weather
//
// Created by Demid Merzlyakov on 28.04.2021.
//
import
Foundation
class
BlendFIPSSource
:
FIPSSource
{
private
let
log
=
Logger
(
componentName
:
"BlendFIPSSource"
)
private
let
baseUrlProduction
=
"https://nwsalert.onelouder.com"
private
let
baseUrlStaging
=
"https://sta-nwsalert.onelouder.com"
private
static
let
blendAPIKeyHeaderName
=
"blend-api-key"
private
static
let
blendAPIKey
=
"0imfnc8mVLWwsAawjYr4Rx-Af50DDqtlx"
#if DEBUG
public
var
useStaging
=
true
#else
public
var
useStaging
=
false
#endif
private
var
baseUrl
:
String
{
useStaging
?
baseUrlStaging
:
baseUrlProduction
}
/// 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
getFipsCode
(
for
location
:
Location
,
completion
:
@escaping
FIPSSourceeCompletion
)
{
internalQueue
.
addOperation
{
[
weak
self
]
in
let
extendedCompletion
:
FIPSSourceeCompletion
=
{
[
weak
self
]
(
fipsCode
)
in
self
?
.
internalQueue
.
addOperation
{
completion
(
fipsCode
)
self
?
.
locationsBeingUpdated
.
remove
(
location
)
}
}
self
?
.
getFipsCodeInternal
(
for
:
location
,
completion
:
extendedCompletion
)
}
}
private
func
getFipsCodeInternal
(
for
location
:
Location
,
completion
:
@escaping
FIPSSourceeCompletion
)
{
guard
!
locationsBeingUpdated
.
contains
(
location
)
else
{
completion
(
nil
)
return
}
locationsBeingUpdated
.
insert
(
location
)
guard
let
url
=
URL
(
string
:
self
.
baseUrl
+
"/1weather/api/v1/location"
)
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
.
countryCode
guard
!
queryParameters
.
isEmpty
else
{
completion
(
nil
)
log
.
error
(
"Not enough information about location."
)
return
}
log
.
debug
(
"Network request (
\(
location
)
):
\(
url
)
"
)
var
request
=
URLRequest
(
url
:
url
)
let
encoder
=
JSONEncoder
()
guard
let
requestBody
=
try
?
encoder
.
encode
(
queryParameters
)
else
{
completion
(
nil
)
log
.
error
(
"Couldn't encode request body:
\(
queryParameters
)
"
)
return
}
request
.
httpBody
=
requestBody
var
headers
=
request
.
allHTTPHeaderFields
??
[
String
:
String
]()
headers
[
BlendFIPSSource
.
blendAPIKeyHeaderName
]
=
BlendFIPSSource
.
blendAPIKey
headers
[
"Content-Type"
]
=
"application/json"
request
.
allHTTPHeaderFields
=
headers
request
.
httpMethod
=
"POST"
let
urlSession
=
URLSession
.
shared
let
dataTask
=
urlSession
.
dataTask
(
with
:
request
)
{
(
data
,
reponse
,
error
)
in
// TODO: check response HTTP code
guard
let
data
=
data
else
{
self
.
log
.
debug
(
"Network response (
\(
location
)
): error
\(
String
(
describing
:
error
)
)
"
)
completion
(
nil
)
return
}
let
responseBodyString
=
String
(
data
:
data
,
encoding
:
.
utf8
)
??
"<couldn't show as string"
self
.
log
.
debug
(
"Network response (
\(
location
)
):
\(
responseBodyString
)
"
)
completion
(
FIPSResponse
(
data
:
data
)?
.
fipsCode
)
}
dataTask
.
resume
()
}
}
1Weather/Network/Notifications/FIPSSource.swift
0 → 100644
View file @
aced138f
//
// FIPSSource.swift
// 1Weather
//
// Created by Demid Merzlyakov on 27.04.2021.
//
import
Foundation
public
typealias
FIPSSourceeCompletion
=
(
String
?)
->
()
public
protocol
FIPSSource
{
func
getFipsCode
(
for
location
:
Location
,
completion
:
@escaping
FIPSSourceeCompletion
)
}
1Weather/Network/Notifications/Model/FIPSResponse.swift
0 → 100644
View file @
aced138f
//
// FIPSResponse.swift
// 1Weather
//
// Created by Demid Merzlyakov on 28.04.2021.
//
import
Foundation
struct
FIPSResponse
:
Codable
{
let
fipsCode
,
s2CellID
:
String
enum
CodingKeys
:
String
,
CodingKey
{
case
fipsCode
=
"fips_code"
case
s2CellID
=
"s2_cell_id"
}
}
// MARK: Convenience initializers
extension
FIPSResponse
{
init
?(
data
:
Data
)
{
guard
let
me
=
try
?
JSONDecoder
()
.
decode
(
FIPSResponse
.
self
,
from
:
data
)
else
{
return
nil
}
self
=
me
}
init
?(
_
json
:
String
,
using
encoding
:
String
.
Encoding
=
.
utf8
)
{
guard
let
data
=
json
.
data
(
using
:
encoding
)
else
{
return
nil
}
self
.
init
(
data
:
data
)
}
init
?(
fromURL
url
:
String
)
{
guard
let
url
=
URL
(
string
:
url
)
else
{
return
nil
}
guard
let
data
=
try
?
Data
(
contentsOf
:
url
)
else
{
return
nil
}
self
.
init
(
data
:
data
)
}
var
jsonData
:
Data
?
{
return
try
?
JSONEncoder
()
.
encode
(
self
)
}
var
json
:
String
?
{
guard
let
data
=
self
.
jsonData
else
{
return
nil
}
return
String
(
data
:
data
,
encoding
:
.
utf8
)
}
}
1Weather/Network/PushNotificationsManager.swift
0 → 100644
View file @
aced138f
//
// PushNotificationsManager.swift
// OneWeather
//
// Created by Demid Merzlyakov on 18.01.2021.
// Copyright © 2021 OneLouder, Inc. All rights reserved.
//
import
Foundation
import
MoEngage
public
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
)
}
}
private
var
lastSetFIPSList
=
""
public
func
updateNwsSubscriptions
(
for
locations
:
[
Location
])
{
let
fipsCodes
=
locations
.
compactMap
{
$0
.
fipsCode
}
let
newFipsList
=
fipsCodes
.
joined
(
separator
:
","
)
if
newFipsList
!=
lastSetFIPSList
{
log
.
info
(
"Set FIPS_LIST to '
\(
newFipsList
)
'"
)
lastSetFIPSList
=
newFipsList
}
MoEngage
.
sharedInstance
()
.
setUserAttribute
(
newFipsList
,
forKey
:
"FIPS_LIST"
)
}
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"
case
alertsScreen
=
"com.handmark.expressweather.ui.activities.alertactivity"
}
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
:
DeeplinksRouter
)
{
guard
let
screenData
=
screenData
else
{
return
}
let
cityId
=
screenData
[
"location"
]
as?
String
let
lat
=
screenData
[
"lat"
]
as?
String
let
lon
=
screenData
[
"lon"
]
as?
String
let
fipsCode
=
screenData
[
"fipsCode"
]
as?
String
guard
(
lat
!=
nil
&&
lon
!=
nil
)
||
cityId
!=
nil
||
fipsCode
!=
nil
else
{
log
.
debug
(
"MoEngage push: no location data found"
)
return
}
let
newLoc
=
GeoNamesPlace
()
newLoc
.
latitude
=
lat
newLoc
.
longitude
=
lon
newLoc
.
optionalCityId
=
cityId
newLoc
.
fipsCode
=
fipsCode
LocationManager
.
shared
.
addIfNeeded
(
partialLocation
:
newLoc
,
selectLocation
:
true
)
log
.
info
(
"MoEngage push: location found:
\(
newLoc
)
"
)
}
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
)
"
)
let
router
=
DeeplinksRouter
()
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
(
timePeriod
:
nil
)
case
"2"
:
router
.
openPrecipitation
()
case
"3"
:
router
.
openRadar
()
case
"4"
:
router
.
openSunMoon
()
default
:
log
.
error
(
"MoEngage push: Unknown launch screen id:
\(
launchScreenId
)
"
)
}
case
.
alertsScreen
:
router
.
openAlerts
()
}
}
}
public
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
handleMoEngageDeeplinks
(
for
:
response
)
completionHandler
()
}
public
func
userNotificationCenter
(
_
center
:
UNUserNotificationCenter
,
willPresent
notification
:
UNNotification
,
withCompletionHandler
completionHandler
:
@escaping
(
UNNotificationPresentationOptions
)
->
Void
)
{
if
UIApplication
.
shared
.
applicationState
==
.
active
{
analytics
(
log
:
.
ANALYTICS_PUSH_RECEIVED
)
}
else
{
analytics
(
log
:
.
ANALYTICS_PUSH_SELECTED
,
params
:
[
.
ANALYTICS_KEY_PUSH_NOTIFICATION_SOURCE
:
"background"
])
}
log
.
debug
(
"Got a push notification:
\(
notification
.
request
.
content
.
userInfo
)
"
);
completionHandler
([
.
sound
,
.
alert
])
}
}
1Weather/UI/View controllers/AppTabBarController.swift
View file @
aced138f
...
...
@@ -7,16 +7,17 @@
import
UIKit
private
enum
AppTab
:
Int
,
CaseIterable
{
case
today
=
0
case
forecast
=
1
case
radar
=
2
case
menu
=
3
}
class
AppTabBarController
:
UITabBarController
{
override
func
viewDidLoad
()
{
super
.
viewDidLoad
()
}
public
enum
AppTab
:
Int
,
CaseIterable
{
case
today
=
0
case
forecast
=
1
case
radar
=
2
case
menu
=
3
}
public
func
setupTabBar
()
{
...
...
1Weather/UI/View controllers/Forecast/ForecastViewController.swift
View file @
aced138f
...
...
@@ -67,13 +67,19 @@ class ForecastViewController: UIViewController {
}
}
public
func
switchTo
(
timePeriod
:
TimePeriod
)
{
if
self
.
timePeriodControl
.
selectedSegmentIndex
!=
timePeriod
.
rawValue
{
self
.
timePeriodControl
.
selectedSegmentIndex
=
timePeriod
.
rawValue
}
forecastCellFactory
.
setTimePeriod
(
timePeriod
:
timePeriod
)
self
.
tableView
.
reloadData
()
}
@objc
private
func
handleSegmentDidChange
()
{
guard
let
timePeriod
=
TimePeriod
(
rawValue
:
self
.
timePeriodControl
.
selectedSegmentIndex
)
else
{
return
}
forecastCellFactory
.
setTimePeriod
(
timePeriod
:
timePeriod
)
self
.
tableView
.
reloadData
()
self
.
switchTo
(
timePeriod
:
timePeriod
)
}
@objc
private
func
handleCityButton
()
{
...
...
1Weather/ViewModels/ForecastViewModel.swift
View file @
aced138f
...
...
@@ -43,7 +43,7 @@ class ForecastViewModel: ViewModelProtocol {
}
public
func
updateWeather
()
{
locationManager
.
update
AllWeather
IfNeeded
()
locationManager
.
update
Everything
IfNeeded
()
}
public
func
selectDailyWeatherAt
(
index
:
Int
)
{
...
...
1Weather/ViewModels/TodayViewModel.swift
View file @
aced138f
...
...
@@ -31,7 +31,7 @@ class TodayViewModel: ViewModelProtocol {
}
public
func
updateWeather
()
{
locationManager
.
update
AllWeather
IfNeeded
()
locationManager
.
update
Everything
IfNeeded
()
}
}
...
...
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