refactor: Create a model to handle realtime push notification
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
65DBFB7425E2BBF20041CBA6 /* GADTMediumTemplateView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 65DBFB6F25E2BBF20041CBA6 /* GADTMediumTemplateView.xib */; };
|
||||
65DBFB7525E2BBF20041CBA6 /* GADTMediumTemplateView.m in Sources */ = {isa = PBXBuildFile; fileRef = 65DBFB7125E2BBF20041CBA6 /* GADTMediumTemplateView.m */; };
|
||||
65DBFB7625E2BBF20041CBA6 /* GADTTemplateView.m in Sources */ = {isa = PBXBuildFile; fileRef = 65DBFB7225E2BBF20041CBA6 /* GADTTemplateView.m */; };
|
||||
65E1226C285C92AA000E294A /* EQNRealtimeAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E1226B285C92AA000E294A /* EQNRealtimeAlert.swift */; };
|
||||
65EA58822A60360D0038EE9D /* EQNRealtimePushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */; };
|
||||
65FFDC95292F672B00EA821B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65FFDC94292F672B00EA821B /* NotificationService.swift */; };
|
||||
65FFDC99292F672B00EA821B /* EQNNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 65FFDC92292F672B00EA821B /* EQNNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
65FFDC9E292F682600EA821B /* Shogun in Frameworks */ = {isa = PBXBuildFile; productRef = 65FFDC9D292F682600EA821B /* Shogun */; };
|
||||
@@ -336,7 +336,7 @@
|
||||
65DBFB7125E2BBF20041CBA6 /* GADTMediumTemplateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADTMediumTemplateView.m; sourceTree = "<group>"; };
|
||||
65DBFB7225E2BBF20041CBA6 /* GADTTemplateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADTTemplateView.m; sourceTree = "<group>"; };
|
||||
65DBFB7325E2BBF20041CBA6 /* GADTMediumTemplateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADTMediumTemplateView.h; sourceTree = "<group>"; };
|
||||
65E1226B285C92AA000E294A /* EQNRealtimeAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNRealtimeAlert.swift; sourceTree = "<group>"; };
|
||||
65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNRealtimePushNotification.swift; sourceTree = "<group>"; };
|
||||
65FFDC92292F672B00EA821B /* EQNNotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = EQNNotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
65FFDC94292F672B00EA821B /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
65FFDC96292F672B00EA821B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -1082,7 +1082,7 @@
|
||||
8C593E89217BA2470008B260 /* EQNSegnalazione.m */,
|
||||
8CF4F4D9216D44930057110B /* EQNPastquakes.h */,
|
||||
8CF4F4DA216D44930057110B /* EQNPastquakes.m */,
|
||||
65E1226B285C92AA000E294A /* EQNRealtimeAlert.swift */,
|
||||
65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */,
|
||||
6552C1452926DBA1008E723C /* AppPreferences.swift */,
|
||||
);
|
||||
path = Models;
|
||||
@@ -1588,6 +1588,7 @@
|
||||
6544416B25E9599000C41714 /* EQNDebugViewController.swift in Sources */,
|
||||
DCF0188A252F055800C783F0 /* AlertsPositionDataTableViewCell.swift in Sources */,
|
||||
DCBB267E24D1EA2000F04559 /* SubscriptionProductTableViewCell.swift in Sources */,
|
||||
65EA58822A60360D0038EE9D /* EQNRealtimePushNotification.swift in Sources */,
|
||||
6525A82625E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift in Sources */,
|
||||
DC99A50524E66E430071BC9F /* EQNAppearanceCommand.swift in Sources */,
|
||||
65DBFB7525E2BBF20041CBA6 /* GADTMediumTemplateView.m in Sources */,
|
||||
@@ -1612,7 +1613,6 @@
|
||||
DCA5B6E7252E4BD8002AEC96 /* EQNBaseTableViewCell.swift in Sources */,
|
||||
8CF66058214C566B009F4314 /* ServerRequest.m in Sources */,
|
||||
DCF9E14D24F6D1AA002B6B1D /* EQNData.swift in Sources */,
|
||||
65E1226C285C92AA000E294A /* EQNRealtimeAlert.swift in Sources */,
|
||||
DC52B8A524FCCD6900ABEBA6 /* AppTheme.swift in Sources */,
|
||||
653C680425F3DF8A00FE52AC /* EQNBaseMapViewController.swift in Sources */,
|
||||
658BC02B2859A4D3009EECAA /* RealtimeAlertView.swift in Sources */,
|
||||
|
||||
@@ -146,8 +146,8 @@
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
|
||||
{
|
||||
// execute common logic and refresh the proper tab
|
||||
NSDictionary *userInfo = notification.request.content.userInfo;
|
||||
[self handlePushNotificationWithUserInfo:userInfo];
|
||||
UNNotificationContent *content = notification.request.content;
|
||||
[self handlePushNotificationWithNotificationContent:content];
|
||||
|
||||
// Change this to your preferred presentation option
|
||||
completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound);
|
||||
@@ -157,8 +157,8 @@
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler
|
||||
{
|
||||
// execute common logic and refresh the proper tab
|
||||
NSDictionary *userInfo = response.notification.request.content.userInfo;
|
||||
[self handlePushNotificationWithUserInfo:userInfo];
|
||||
UNNotificationContent *content = response.notification.request.content;
|
||||
[self handlePushNotificationWithNotificationContent:content];
|
||||
|
||||
completionHandler();
|
||||
}
|
||||
@@ -175,18 +175,27 @@
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)handlePushNotificationWithUserInfo:(NSDictionary *)userInfo
|
||||
- (void)handlePushNotificationWithNotificationContent:(UNNotificationContent *)content
|
||||
{
|
||||
NSString *type = content.userInfo[@"type"];
|
||||
|
||||
EQNTabBarSection section = EQNTabBarSectionAllerte;
|
||||
if ([userInfo[@"type"] isEqualToString:@"eqn"]) {
|
||||
if ([type isEqualToString:@"eqn"]) {
|
||||
NSDate *dataRicezione = [NSDate date];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:dataRicezione forKey:EQNUserDefaultRealTimeAlertDate];
|
||||
|
||||
[EQNUtility storeDictionary:userInfo toUserDefaultForKey:EQNUserDefaultRealTimeAlertPayload];
|
||||
// Store both original payload and modified title/body
|
||||
// This will be usefull to avoid to re-evaluate logic for title display.
|
||||
NSDictionary *notification = @{
|
||||
@"title": content.title,
|
||||
@"body": content.body,
|
||||
@"userInfo": content.userInfo
|
||||
};
|
||||
[EQNRealtimePushNotification storeNotificationWithPayload:notification];
|
||||
section = EQNTabBarSectionAllerte;
|
||||
} else if([userInfo[@"type"] isEqualToString:@"manual"]) {
|
||||
} else if([type isEqualToString:@"manual"]) {
|
||||
section = EQNTabBarSectionSegnalazioni;
|
||||
} else if([userInfo[@"type"] isEqualToString:@"official"]) {
|
||||
} else if([type isEqualToString:@"official"]) {
|
||||
section = EQNTabBarSectionRetiSismiche;
|
||||
}
|
||||
|
||||
|
||||
@@ -116,14 +116,13 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
|
||||
// controlliamo se c'è una notifica in tempo reale da mostrare
|
||||
NSDate *date = [[NSUserDefaults standardUserDefaults] objectForKey:EQNUserDefaultRealTimeAlertDate];
|
||||
NSDictionary *info = [EQNUtility loadDictionaryFromUserDefaultsForKey:EQNUserDefaultRealTimeAlertPayload];
|
||||
if (date && info && [EQNUtility getDifferenceMinute:date] < EQNRealtimeAlertExpiration) {
|
||||
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
||||
if (date && notification && [EQNUtility getDifferenceMinute:date] < EQNRealtimeAlertExpiration) {
|
||||
self.isNotificaAttiva = YES;
|
||||
|
||||
// mostriamo la schermata solo se il countdown non è a zero
|
||||
EQNRealtimeAlert *alert = [[EQNRealtimeAlert alloc] initWithNotification:info];
|
||||
if (alert != nil && ![alert isCountdownExpired]) {
|
||||
RealtimeAlertViewController *controller = [[RealtimeAlertViewController alloc] initWithAlert:alert];
|
||||
if (![notification isCountdownExpired]) {
|
||||
RealtimeAlertViewController *controller = [[RealtimeAlertViewController alloc] initWithNotification:notification];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
controller.modalInPresentation = YES;
|
||||
}
|
||||
@@ -161,7 +160,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
- (void)resetRealtimeAlert
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:EQNUserDefaultRealTimeAlertDate];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:EQNUserDefaultRealTimeAlertPayload];
|
||||
[EQNRealtimePushNotification removeStoredNotification];
|
||||
self.isNotificaAttiva = NO;
|
||||
}
|
||||
|
||||
@@ -265,8 +264,8 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
} else if (tableRow == AllerteTableRowSismiRilevati) {
|
||||
if (self.isNotificaAttiva) {
|
||||
AlertsSeismicNotificationExpandedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationExpandedCell" forIndexPath:indexPath];
|
||||
NSDictionary *info = [EQNUtility loadDictionaryFromUserDefaultsForKey:EQNUserDefaultRealTimeAlertPayload];
|
||||
cell.notification = info;
|
||||
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
||||
cell.notification = notification;
|
||||
|
||||
__weak AllerteViewController *weakSelf = self;
|
||||
cell.onTapClose = ^{
|
||||
|
||||
+15
-34
@@ -14,7 +14,7 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
||||
|
||||
typealias DefaultCompletion = () -> Void
|
||||
|
||||
@objc var notification: [String: Any]? {
|
||||
@objc var notification: EQNRealtimePushNotification? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
@@ -70,50 +70,31 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
||||
notificationDescriptionLabel.text = ""
|
||||
mapView.removeAnnotations(mapView.annotations)
|
||||
|
||||
guard let notification = notification,
|
||||
let aps = notification["aps"] as? [String: Any],
|
||||
let alert = aps["alert"] as? [String: Any] else { return }
|
||||
guard let notification = notification else { return }
|
||||
|
||||
let intensity = notification.integer(forKey: "intensity", orDefault: 0)
|
||||
containerView.backgroundColor = color(for: intensity)
|
||||
containerView.backgroundColor = color(for: notification.intensity)
|
||||
notificationTitleLabel.text = notification.title
|
||||
|
||||
if let title = alert["loc-key"] as? String, let args = alert["loc-args"] as? [String], let arg = args.first {
|
||||
notificationTitleLabel.text = String(format: NSLocalizedString(title, comment: ""), arg)
|
||||
}
|
||||
|
||||
// get coordinate
|
||||
var coordinate: CLLocation?
|
||||
if let latitude = notification.double(forKey: "latitude"),
|
||||
let longitude = notification.double(forKey: "longitude") {
|
||||
if let date = notification.dateTime {
|
||||
|
||||
coordinate = CLLocation(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
|
||||
if let coordinate = coordinate,
|
||||
let counter = notification["counter"],
|
||||
let dateString = notification["datetime"] as? String,
|
||||
let date = EQNUtility.getDateFrom(dateString) {
|
||||
|
||||
let distance = EQNUser.default().lastPosition?.distance(from: coordinate) ?? 0.0
|
||||
let distance = EQNUser.default().lastPosition?.distance(from: notification.coordinate) ?? 0.0
|
||||
let distanceRound = Int(round(distance / 1_000))
|
||||
let difference = Int(NSDate().timeIntervalSince(date) / 60.0)
|
||||
|
||||
notificationDescriptionLabel.text = ""
|
||||
+ NSLocalizedString("official_card_distance", comment: "") + " \(distanceRound) km"
|
||||
+ " - " + EQNUtility.formattedString(forTimeDifference: difference)
|
||||
+ "\n" + String(format: NSLocalizedString("map_number", comment: ""), "\(counter)")
|
||||
+ "\n" + String(format: NSLocalizedString("map_number", comment: ""), "\(notification.counter)")
|
||||
}
|
||||
|
||||
if let coordinate = coordinate {
|
||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||
let region = MKCoordinateRegion(center: coordinate.coordinate, span: span)
|
||||
|
||||
mapView.setCenter(coordinate.coordinate, animated: false)
|
||||
mapView.setRegion(region, animated: true)
|
||||
|
||||
let annotation = EQNMapAnnotationPastquake(title: "", coordinate: coordinate.coordinate, intensity: intensity)
|
||||
mapView.addAnnotation(annotation)
|
||||
}
|
||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||
let region = MKCoordinateRegion(center: notification.coordinate.coordinate, span: span)
|
||||
|
||||
mapView.setCenter(notification.coordinate.coordinate, animated: false)
|
||||
mapView.setRegion(region, animated: true)
|
||||
|
||||
let annotation = EQNMapAnnotationPastquake(title: "", coordinate: notification.coordinate.coordinate, intensity: notification.intensity)
|
||||
mapView.addAnnotation(annotation)
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
|
||||
private let notificationView = RealtimeAlertView()
|
||||
/// Alert to display
|
||||
private let realtimeAlert: EQNRealtimeAlert
|
||||
private let realtimeAlert: EQNRealtimePushNotification
|
||||
/// Timer to constantly update countdown label
|
||||
private var countdownTimer: Timer?
|
||||
/// Refresh time for wave animation
|
||||
@@ -32,8 +32,8 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
// MARK: - Init
|
||||
|
||||
@objc
|
||||
init(alert: EQNRealtimeAlert) {
|
||||
self.realtimeAlert = alert
|
||||
init(notification: EQNRealtimePushNotification) {
|
||||
self.realtimeAlert = notification
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
self.waveAnimationCurrentRadius = currentWavePosition()
|
||||
|
||||
@@ -71,7 +71,7 @@ static NSString * const EQNUserDefaultKeySesmicInformations = @"EQNetwork.Seismi
|
||||
static NSString * const EQNUserDefaultKeyOneShotShowCountry = @"EQNetwork.OneShot.CountrySelection";
|
||||
static NSString * const EQNUserDefaultLastLocation = @"EQNLast_Location";
|
||||
static NSString * const EQNUserDefaultSeismicNetworkCards = @"EQNData.RetiSismiche";
|
||||
static NSString * const EQNUserDefaultRealTimeAlertPayload = @"EQNData.RealtimeAlertPayload";
|
||||
static NSString * const EQNUserDefaultRealTimeAlertPayload = @"EQNData.RealtimePushNotificationPayload";
|
||||
static NSString * const EQNUserDefaultRealTimeAlertDate = @"EQNData.RealtimeAlertDate";
|
||||
static NSString * const EQNUserDefaultUserReportExpandedView = @"EQNData.UserReportExpandedView";
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
//
|
||||
// EQNRealtimeAlert.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 17/06/22.
|
||||
// Copyright © 2022 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
import Shogun
|
||||
|
||||
|
||||
@objc
|
||||
class EQNRealtimeAlert: NSObject {
|
||||
|
||||
typealias NotificationPayload = [String: Any]
|
||||
|
||||
/// Title to display
|
||||
var title: String = ""
|
||||
/// Earthquake coordinate
|
||||
let coordinate: CLLocation
|
||||
/// Earthquake intensity
|
||||
let intensity: Int
|
||||
/// Earthquake wave speed
|
||||
let waveSpeed: Double
|
||||
/// Calculated timestamp for earthquake on user position
|
||||
let impactTimestamp: Date
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
@objc
|
||||
init?(notification: [String: Any]) {
|
||||
guard let alert = Self.getPushAlertPayload(from: notification),
|
||||
let coordinate = Self.getCoordinate(from: notification),
|
||||
let impactTimestamp = EQNUtility.calculateUserSeismicTimestamp(fromUserInfo: notification) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.coordinate = coordinate
|
||||
self.impactTimestamp = impactTimestamp
|
||||
if let title = alert["loc-key"] as? String, let args = alert["loc-args"] as? [String], let arg = args.first {
|
||||
self.title = String(format: NSLocalizedString(title, comment: ""), arg)
|
||||
}
|
||||
self.intensity = notification.integer(forKey: "intensity", orDefault: 0)
|
||||
self.waveSpeed = notification.double(forKey: "wave_speed", orDefault: 0.0) * 1000 // m/s
|
||||
}
|
||||
|
||||
func distanceFromUser() -> CLLocationDistance {
|
||||
EQNUser.default().lastPosition?.distance(from: coordinate) ?? 0.0
|
||||
}
|
||||
|
||||
/// Remaining time before earthquake gets user position
|
||||
func currentCountdown() -> Int {
|
||||
let now = Date()
|
||||
let difference = lround(max(impactTimestamp.timeIntervalSince(now), 0))
|
||||
return difference
|
||||
}
|
||||
|
||||
@objc
|
||||
func isCountdownExpired() -> Bool {
|
||||
currentCountdown() <= 0
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Get `aps.alert` object inside a given notification payload
|
||||
/// - Parameter notification: Notification payload
|
||||
/// - Returns: `aps.alert` object if found, nil otherwise
|
||||
static func getPushAlertPayload(
|
||||
from notification: NotificationPayload
|
||||
) -> NotificationPayload? {
|
||||
guard let aps = notification["aps"] as? [String: Any],
|
||||
let alert = aps["alert"] as? [String: Any] else {
|
||||
return nil
|
||||
}
|
||||
return alert
|
||||
}
|
||||
|
||||
/// Retrieve coordinate of earthquake from the notification payload
|
||||
/// - Parameter notification: Notification payload
|
||||
/// - Returns: Coordinate if found, nil otherwise
|
||||
static func getCoordinate(
|
||||
from notification: NotificationPayload
|
||||
) -> CLLocation? {
|
||||
guard let latitude = notification.double(forKey: "latitude"),
|
||||
let longitude = notification.double(forKey: "longitude") else {
|
||||
return nil
|
||||
}
|
||||
return CLLocation(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
//
|
||||
// EQNRealtimePushNotification.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 13/07/23.
|
||||
// Copyright © 2023 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
import Shogun
|
||||
|
||||
@objc
|
||||
class EQNRealtimePushNotification: NSObject, Codable {
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case intensity
|
||||
case latitude
|
||||
case longitude
|
||||
case counter
|
||||
case dateTime
|
||||
case waveSpeed
|
||||
case impactTimestamp
|
||||
case title
|
||||
case displayTitle
|
||||
case displayBody
|
||||
}
|
||||
|
||||
|
||||
let type: String
|
||||
/// Earthquake intensity
|
||||
let intensity: Int
|
||||
/// Earthquake coordinate
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
/// Number of smartphones that report the earthquake
|
||||
let counter: Int
|
||||
let dateTime: Date?
|
||||
/// Earthquake wave speed
|
||||
let waveSpeed: Double
|
||||
/// Calculated timestamp for earthquake on user position
|
||||
let impactTimestamp: Date?
|
||||
|
||||
// Title received inside `aps.alert`
|
||||
let title: String
|
||||
// Title and body elaborated in NotificationService
|
||||
let displayTitle: String
|
||||
let displayBody: String
|
||||
|
||||
var coordinate: CLLocation {
|
||||
.init(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(
|
||||
type: String,
|
||||
intensity: Int,
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
counter: Int,
|
||||
dateTime: Date?,
|
||||
waveSpeed: Double,
|
||||
impactTimestamp: Date?,
|
||||
title: String,
|
||||
displayTitle: String,
|
||||
displayBody: String
|
||||
) {
|
||||
self.type = type
|
||||
self.intensity = intensity
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.counter = counter
|
||||
self.dateTime = dateTime
|
||||
self.waveSpeed = waveSpeed
|
||||
self.impactTimestamp = impactTimestamp
|
||||
self.title = title
|
||||
self.displayTitle = displayTitle
|
||||
self.displayBody = displayBody
|
||||
}
|
||||
|
||||
func distanceFromUser() -> CLLocationDistance {
|
||||
// Usiamo la posizione salvata, che coincide con quella presnete in EQNUser.
|
||||
// In questo modo non abbiamo dipendenze e possiamo includere questa classe
|
||||
// anche nel target NotificationService
|
||||
EQNUserData.shared.lastLocation?.distance(from: coordinate) ?? 0.0
|
||||
}
|
||||
|
||||
/// Remaining time before earthquake gets user position
|
||||
func currentCountdown() -> Int {
|
||||
guard let impactTimestamp else { return 0 }
|
||||
|
||||
let now = Date()
|
||||
let difference = lround(max(impactTimestamp.timeIntervalSince(now), 0))
|
||||
return difference
|
||||
}
|
||||
|
||||
@objc
|
||||
func isCountdownExpired() -> Bool {
|
||||
currentCountdown() <= 0
|
||||
}
|
||||
|
||||
// MARK: - Class
|
||||
|
||||
/// Remove any saved notification
|
||||
@objc(removeStoredNotification)
|
||||
static func removeStored() {
|
||||
UserDefaults.standard.removeObject(forKey: EQNUserDefaultRealTimeAlertPayload)
|
||||
}
|
||||
|
||||
@objc(storedNotification)
|
||||
static func stored() -> EQNRealtimePushNotification? {
|
||||
guard let data = UserDefaults.standard.object(forKey: EQNUserDefaultRealTimeAlertPayload) as? Data else {
|
||||
print("[ERROR] No notification saved for key '\(EQNUserDefaultRealTimeAlertPayload)'")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let notification = try? JSONDecoder().decode(EQNRealtimePushNotification.self, from: data) else {
|
||||
print("[ERROR] Unable to decode given notification")
|
||||
return nil
|
||||
}
|
||||
|
||||
return notification
|
||||
}
|
||||
|
||||
/// Convert and store a push notification payload.
|
||||
/// Expected payload has the following structure:
|
||||
/// ```
|
||||
/// {
|
||||
/// "title": "Allerta sismica in tempo reale",
|
||||
/// "body": "Previsto uno scuotimento forte",
|
||||
/// "userInfo": {
|
||||
/// "datetime" : "2023-07-13 12:24:04",
|
||||
/// ...
|
||||
/// "aps": {
|
||||
/// "alert" : {
|
||||
/// "loc-key" : "Rilevato sisma forte a",
|
||||
/// "title-loc-key" : "Allerta sismica in tempo reale",
|
||||
/// "loc-args" : [
|
||||
/// "150 km (Test)"
|
||||
/// ]
|
||||
/// },
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// - Parameter payload: Notification payload
|
||||
/// - Returns: `true` if save succeed, `false` otherwise
|
||||
@objc(storeNotificationWithPayload:)
|
||||
@discardableResult
|
||||
static func store(payload: [String: Any]) -> Bool {
|
||||
guard let notification = from(payload: payload) else {
|
||||
print("[ERROR] Unable to convert received notification")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let data = try? JSONEncoder().encode(notification) else {
|
||||
print("[ERROR] Unable to encode given notification")
|
||||
return false
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(data, forKey: EQNUserDefaultRealTimeAlertPayload)
|
||||
return true
|
||||
}
|
||||
|
||||
@objc
|
||||
private static func from(payload: [String: Any]) -> EQNRealtimePushNotification? {
|
||||
guard let userInfo = payload["userInfo"] as? [String: Any],
|
||||
let aps = userInfo["aps"] as? [String: Any],
|
||||
let alert = aps["alert"] as? [String: Any] else {
|
||||
print("[ERROR] Missing required info to parse push notification")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let latitude = userInfo.double(forKey: "latitude"),
|
||||
let longitude = userInfo.double(forKey: "longitude") else {
|
||||
print("[ERROR] Unable to get coordinate from push notification")
|
||||
return nil
|
||||
}
|
||||
|
||||
let type = userInfo.string(forKey: "type", orDefault: "")
|
||||
let intensity = userInfo.integer(forKey: "intensity", orDefault: 0)
|
||||
|
||||
let counter = userInfo.integer(forKey: "counter", orDefault: 0)
|
||||
var dateTime: Date?
|
||||
if let dateString = userInfo.string(forKey: "datetime"), let date = EQNUtility.getDateFrom(dateString) {
|
||||
dateTime = date
|
||||
}
|
||||
let waveSpeed = userInfo.double(forKey: "wave_speed", orDefault: 0.0) * 1000 // m/s
|
||||
var impactTimestamp: Date?
|
||||
if let timestamp = EQNUtility.calculateUserSeismicTimestamp(fromUserInfo: userInfo) {
|
||||
impactTimestamp = timestamp
|
||||
}
|
||||
|
||||
var title: String = ""
|
||||
if let titleKey = alert["loc-key"] as? String, let args = alert["loc-args"] as? [String], let arg = args.first {
|
||||
title = String(format: NSLocalizedString(titleKey, comment: ""), arg)
|
||||
}
|
||||
let displayTitle = payload.string(forKey: "title", orDefault: "")
|
||||
let displayBody = payload.string(forKey: "body", orDefault: "")
|
||||
|
||||
return .init(
|
||||
type: type,
|
||||
intensity: intensity,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
counter: counter,
|
||||
dateTime: dateTime,
|
||||
waveSpeed: waveSpeed,
|
||||
impactTimestamp: impactTimestamp,
|
||||
title: title,
|
||||
displayTitle: displayTitle,
|
||||
displayBody: displayBody
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -46,15 +46,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// @param keyName Key of NSUserDefault to retrieve
|
||||
+ (nullable NSArray *)loadArrayOfClass:(Class)class fromUserDefaultsForKey:(NSString* )keyName;
|
||||
|
||||
/// Store a dictionary to NSUserDefaults
|
||||
/// @param dictionary Dictionary to store
|
||||
/// @param keyName Key of NSUserDefault to use
|
||||
+ (void)storeDictionary:(NSDictionary *)dictionary toUserDefaultForKey:(NSString *)keyName;
|
||||
|
||||
/// Retrieve a saved dictionary from NSUserDefaults.
|
||||
/// @param keyName Key of NSUserDefault to retrieve
|
||||
+ (nullable NSDictionary *)loadDictionaryFromUserDefaultsForKey:(NSString *)keyName;
|
||||
|
||||
/// Calculate impact time from a received push notification
|
||||
/// @param info Payload of a received notification
|
||||
/// @return Impact time
|
||||
|
||||
@@ -73,11 +73,6 @@
|
||||
[self storeRootObject:array toUserDefaultForKey:keyName];
|
||||
}
|
||||
|
||||
+ (void)storeDictionary:(NSDictionary *)dictionary toUserDefaultForKey:(NSString *)keyName
|
||||
{
|
||||
[self storeRootObject:dictionary toUserDefaultForKey:keyName];
|
||||
}
|
||||
|
||||
+ (void)storeRootObject:(id)rootObject toUserDefaultForKey:(NSString *)keyName
|
||||
{
|
||||
NSError *error;
|
||||
@@ -112,27 +107,6 @@
|
||||
return array;
|
||||
}
|
||||
|
||||
+ (NSDictionary *)loadDictionaryFromUserDefaultsForKey:(NSString *)keyName
|
||||
{
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSData *data = [defaults objectForKey:keyName];
|
||||
if (!data) {
|
||||
NSLog(@"[EQNUtility] No dictionary saved for key '%@'", keyName);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:&error];
|
||||
unarchiver.requiresSecureCoding = NO;
|
||||
NSDictionary *dictionary = [unarchiver decodeObjectOfClass:[NSDictionary class] forKey:NSKeyedArchiveRootObjectKey];
|
||||
|
||||
if (error) {
|
||||
NSLog(@"[EQNUtility] Unable to unarchive object for key '%@' (error: %@)", keyName, error.localizedDescription);
|
||||
return nil;
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
#pragma mark - Notifications
|
||||
|
||||
+ (nullable NSDate *)calculateUserSeismicTimestampFromUserInfo:(NSDictionary *)info
|
||||
|
||||
Reference in New Issue
Block a user