diff --git a/Sources/Earthquake Network.xcodeproj/project.pbxproj b/Sources/Earthquake Network.xcodeproj/project.pbxproj index e24d049..9c0feab 100644 --- a/Sources/Earthquake Network.xcodeproj/project.pbxproj +++ b/Sources/Earthquake Network.xcodeproj/project.pbxproj @@ -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 = ""; }; 65DBFB7225E2BBF20041CBA6 /* GADTTemplateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADTTemplateView.m; sourceTree = ""; }; 65DBFB7325E2BBF20041CBA6 /* GADTMediumTemplateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADTMediumTemplateView.h; sourceTree = ""; }; - 65E1226B285C92AA000E294A /* EQNRealtimeAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNRealtimeAlert.swift; sourceTree = ""; }; + 65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNRealtimePushNotification.swift; sourceTree = ""; }; 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 = ""; }; 65FFDC96292F672B00EA821B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -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 */, diff --git a/Sources/Earthquake Network/AppDelegate.m b/Sources/Earthquake Network/AppDelegate.m index 4ab4023..6a6bf68 100644 --- a/Sources/Earthquake Network/AppDelegate.m +++ b/Sources/Earthquake Network/AppDelegate.m @@ -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; } diff --git a/Sources/Earthquake Network/Controllers/Alerts/AllerteViewController.m b/Sources/Earthquake Network/Controllers/Alerts/AllerteViewController.m index f51af26..21cfaf2 100644 --- a/Sources/Earthquake Network/Controllers/Alerts/AllerteViewController.m +++ b/Sources/Earthquake Network/Controllers/Alerts/AllerteViewController.m @@ -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 = ^{ diff --git a/Sources/Earthquake Network/Controllers/Alerts/Cells/AlertsSeismicNotificationExpandedTableViewCell.swift b/Sources/Earthquake Network/Controllers/Alerts/Cells/AlertsSeismicNotificationExpandedTableViewCell.swift index df0867c..de400d5 100644 --- a/Sources/Earthquake Network/Controllers/Alerts/Cells/AlertsSeismicNotificationExpandedTableViewCell.swift +++ b/Sources/Earthquake Network/Controllers/Alerts/Cells/AlertsSeismicNotificationExpandedTableViewCell.swift @@ -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? { diff --git a/Sources/Earthquake Network/Controllers/Realtime Alert/RealtimeAlertViewController.swift b/Sources/Earthquake Network/Controllers/Realtime Alert/RealtimeAlertViewController.swift index 94721cf..ea8b85f 100644 --- a/Sources/Earthquake Network/Controllers/Realtime Alert/RealtimeAlertViewController.swift +++ b/Sources/Earthquake Network/Controllers/Realtime Alert/RealtimeAlertViewController.swift @@ -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() diff --git a/Sources/Earthquake Network/Costanti.h b/Sources/Earthquake Network/Costanti.h index a6bf2f4..46212ee 100644 --- a/Sources/Earthquake Network/Costanti.h +++ b/Sources/Earthquake Network/Costanti.h @@ -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"; diff --git a/Sources/Earthquake Network/Models/EQNRealtimeAlert.swift b/Sources/Earthquake Network/Models/EQNRealtimeAlert.swift deleted file mode 100644 index 91384db..0000000 --- a/Sources/Earthquake Network/Models/EQNRealtimeAlert.swift +++ /dev/null @@ -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) - } -} diff --git a/Sources/Earthquake Network/Models/EQNRealtimePushNotification.swift b/Sources/Earthquake Network/Models/EQNRealtimePushNotification.swift new file mode 100644 index 0000000..052a3bb --- /dev/null +++ b/Sources/Earthquake Network/Models/EQNRealtimePushNotification.swift @@ -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 + ) + } +} diff --git a/Sources/Earthquake Network/Models/EQNUtility.h b/Sources/Earthquake Network/Models/EQNUtility.h index fbedcd3..a584309 100644 --- a/Sources/Earthquake Network/Models/EQNUtility.h +++ b/Sources/Earthquake Network/Models/EQNUtility.h @@ -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 diff --git a/Sources/Earthquake Network/Models/EQNUtility.m b/Sources/Earthquake Network/Models/EQNUtility.m index 014e35c..eb2dcc1 100644 --- a/Sources/Earthquake Network/Models/EQNUtility.m +++ b/Sources/Earthquake Network/Models/EQNUtility.m @@ -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