refactor: Create a model for the realtime alert
This commit is contained in:
@@ -140,6 +140,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 */; };
|
||||
65E1B19B260F980600A0ACBA /* Dictionary+EQNExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E1B19A260F980600A0ACBA /* Dictionary+EQNExtensions.swift */; };
|
||||
8C10B0B92281FE7F00125C9F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8C10B0BD2281FE7F00125C9F /* Localizable.strings */; };
|
||||
8C10B0BA2281FE7F00125C9F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8C10B0BD2281FE7F00125C9F /* Localizable.strings */; };
|
||||
@@ -430,6 +431,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>"; };
|
||||
65E1B19A260F980600A0ACBA /* Dictionary+EQNExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+EQNExtensions.swift"; sourceTree = "<group>"; };
|
||||
8C10B0BC2281FE7F00125C9F /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8C10B0BE2281FE9E00125C9F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@@ -1084,6 +1086,7 @@
|
||||
8C593E89217BA2470008B260 /* EQNSegnalazione.m */,
|
||||
8CF4F4D9216D44930057110B /* EQNPastquakes.h */,
|
||||
8CF4F4DA216D44930057110B /* EQNPastquakes.m */,
|
||||
65E1226B285C92AA000E294A /* EQNRealtimeAlert.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -1592,6 +1595,7 @@
|
||||
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 */,
|
||||
|
||||
@@ -120,8 +120,9 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
if (date && [EQNUtility getDifferenceMinute:date] < TEMPO_VISUALIZZAZIONE_NOTIFICA) {
|
||||
self.isNotificaAttiva = YES;
|
||||
NSDictionary *info = [EQNUtility loadDictionaryFromUserDefaultsForKey:NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA];
|
||||
RealtimeAlertViewController *controller = [[RealtimeAlertViewController alloc] initWithNotification:info];
|
||||
if (controller) {
|
||||
EQNRealtimeAlert *alert = [[EQNRealtimeAlert alloc] initWithNotification:info];
|
||||
if (alert) {
|
||||
RealtimeAlertViewController *controller = [[RealtimeAlertViewController alloc] initWithAlert:alert];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
controller.modalInPresentation = YES;
|
||||
}
|
||||
|
||||
+22
-78
@@ -16,17 +16,9 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
typealias NotificationPayload = [String: Any]
|
||||
|
||||
private let notificationView = RealtimeAlertView()
|
||||
// complete push payload
|
||||
private let notification: NotificationPayload
|
||||
// aps.alert field of given push notification
|
||||
private let alert: NotificationPayload
|
||||
/// Coordinate of the earthquake
|
||||
private let coordinate: CLLocation
|
||||
/// Calculated timestamp for earthquake on user position
|
||||
private let impactTimestamp: Date
|
||||
/// Alert to display
|
||||
private let realtimeAlert: EQNRealtimeAlert
|
||||
/// Timer to constantly update countdown label
|
||||
private var countdownTimer: Timer?
|
||||
/// Refresh time for wave animation
|
||||
@@ -37,30 +29,15 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
/// Timer to simulate animation for the wave
|
||||
private var waveAnimationTimer: Timer?
|
||||
|
||||
private var currentCountdown: Int {
|
||||
let now = Date()
|
||||
let difference = lround(max(impactTimestamp.timeIntervalSince(now), 0))
|
||||
return difference
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
|
||||
@objc
|
||||
init?(notification: NotificationPayload) {
|
||||
guard let alert = Self.getPushAlertPayload(from: notification),
|
||||
let coordinate = Self.getCoordinate(from: notification),
|
||||
let impactTimestamp = EQNUtility.calculateUserSeismicTimestamp(fromUserInfo: notification) else {
|
||||
return nil
|
||||
}
|
||||
self.notification = notification
|
||||
self.alert = alert
|
||||
self.coordinate = coordinate
|
||||
self.impactTimestamp = impactTimestamp
|
||||
|
||||
init(alert: EQNRealtimeAlert) {
|
||||
self.realtimeAlert = alert
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
self.waveAnimationCurrentRadius = currentWavePosition()
|
||||
self.waveAnimationVelocity = evaluateWaveVelocity()
|
||||
self.waveAnimationVelocity = evaluateWaveAnimationVelocity()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -90,39 +67,35 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
if let title = alert["loc-key"] as? String, let args = alert["loc-args"] as? [String], let arg = args.first {
|
||||
notificationView.descriptionLabel.text = String(format: NSLocalizedString(title, comment: ""), arg)
|
||||
}
|
||||
notificationView.descriptionLabel.text = realtimeAlert.title
|
||||
|
||||
// update title with distance from earthquake
|
||||
let distance = EQNUser.default().lastPosition?.distance(from: coordinate) ?? 0.0
|
||||
let distanceRound = Int(round(distance / 1_000))
|
||||
let distanceRound = Int(round(realtimeAlert.distanceFromUser() / 1_000))
|
||||
notificationView.descriptionLabel.text = (notificationView.descriptionLabel.text ?? "")
|
||||
+ ".\n"
|
||||
+ String(format: NSLocalizedString("timer_message2_other", comment: ""), distanceRound)
|
||||
|
||||
// center map on the earthquake coordinate
|
||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||
let region = MKCoordinateRegion(center: coordinate.coordinate, span: span)
|
||||
notificationView.mapView.setCenter(coordinate.coordinate, animated: false)
|
||||
let region = MKCoordinateRegion(center: realtimeAlert.coordinate.coordinate, span: span)
|
||||
notificationView.mapView.setCenter(realtimeAlert.coordinate.coordinate, animated: false)
|
||||
notificationView.mapView.setRegion(region, animated: true)
|
||||
|
||||
// aggiungiamo annotation con epicentro sisma
|
||||
let intensity = notification.eqn_intValue(for: "intensity") ?? 0
|
||||
notificationView.addMapAnnotation(center: coordinate.coordinate, intensity: intensity)
|
||||
notificationView.addMapAnnotation(center: realtimeAlert.coordinate.coordinate, intensity: realtimeAlert.intensity)
|
||||
|
||||
// simuliamo animazione dell'onda sismica
|
||||
notificationView.addMapCircle(center: coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
||||
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
||||
|
||||
// aggiungiamo un segmento tra la posizione del sisma e quella dell'utente
|
||||
if let lastPosition = EQNUser.default().lastPosition {
|
||||
notificationView.addMapLine(coordinates: [coordinate.coordinate, lastPosition.coordinate])
|
||||
notificationView.addMapLine(coordinates: [realtimeAlert.coordinate.coordinate, lastPosition.coordinate])
|
||||
}
|
||||
}
|
||||
|
||||
private func startCountdown() {
|
||||
// show countdown only if time is less than 300 seconds
|
||||
if currentCountdown < 300 {
|
||||
if realtimeAlert.currentCountdown() < 300 {
|
||||
// start a timer for the countdown label
|
||||
notificationView.waveTimeLabel.isHidden = false
|
||||
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdownTimerFired(_:)), userInfo: nil, repeats: true)
|
||||
@@ -146,9 +119,10 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
// MARK: - Timer
|
||||
|
||||
@objc private func countdownTimerFired(_ sender: Timer) {
|
||||
notificationView.waveTimeLabel.text = String(format: NSLocalizedString("alert_wave", comment: ""), currentCountdown)
|
||||
let countdown = realtimeAlert.currentCountdown()
|
||||
notificationView.waveTimeLabel.text = String(format: NSLocalizedString("alert_wave", comment: ""), countdown)
|
||||
|
||||
if currentCountdown <= 0 {
|
||||
if countdown <= 0 {
|
||||
// stop the countdown
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
@@ -157,57 +131,27 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
|
||||
@objc private func mapWaveAnimationFired(_ sender: Timer) {
|
||||
waveAnimationCurrentRadius += waveAnimationVelocity
|
||||
notificationView.addMapCircle(center: coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
||||
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// 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.eqn_doubleValue(for: "latitude"),
|
||||
let longitude = notification.eqn_doubleValue(for: "longitude") else {
|
||||
return nil
|
||||
}
|
||||
return CLLocation(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Evaluate current position for the wave
|
||||
/// Used to define initial position for the wave circle
|
||||
/// - Returns: Distance of the wave from the original earthquake point
|
||||
private func currentWavePosition() -> Double {
|
||||
// velocità onda
|
||||
let waveSpeed = (notification.eqn_doubleValue(for: "wave_speed") ?? 0) * 1000 // m/s
|
||||
// distanza tra utente e terremoto
|
||||
let distance = EQNUser.default().lastPosition?.distance(from: coordinate) ?? 0.0 // m
|
||||
let distance = realtimeAlert.distanceFromUser()
|
||||
|
||||
// calcoliamo la distanza rimanente da mostrare, perchè la schermata potrebbe anche essere aperta in ritardo
|
||||
let remainingDistance = waveSpeed * Double(currentCountdown)
|
||||
let remainingDistance = realtimeAlert.waveSpeed * Double(realtimeAlert.currentCountdown())
|
||||
return distance - remainingDistance
|
||||
}
|
||||
|
||||
/// Evaluate wave velocity based on push notification data
|
||||
/// - Returns: Wave velocity, used for animation
|
||||
private func evaluateWaveVelocity() -> Double {
|
||||
let waveSpeed = notification.eqn_doubleValue(for: "wave_speed") ?? 0 // km/s
|
||||
let velocity = waveSpeed * 1000
|
||||
|
||||
private func evaluateWaveAnimationVelocity() -> Double {
|
||||
let velocity = realtimeAlert.waveSpeed
|
||||
return velocity * waveAnimationRefreshRate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// EQNRealtimeAlert.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 17/06/22.
|
||||
// Copyright © 2022 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
|
||||
|
||||
@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.eqn_intValue(for: "intensity") ?? 0
|
||||
self.waveSpeed = (notification.eqn_doubleValue(for: "wave_speed") ?? 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.eqn_doubleValue(for: "latitude"),
|
||||
let longitude = notification.eqn_doubleValue(for: "longitude") else {
|
||||
return nil
|
||||
}
|
||||
return CLLocation(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user