refactor: Create a model to handle realtime push notification

This commit is contained in:
Andrea Busi
2023-07-13 16:35:50 +02:00
parent 2b98a4e292
commit b44a0a2e27
10 changed files with 265 additions and 186 deletions
@@ -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 */,
+18 -9
View File
@@ -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 = ^{
@@ -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? {
@@ -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()
+1 -1
View File
@@ -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