Compare commits

...

38 Commits

Author SHA1 Message Date
Andrea Busi 61587a0341 release: Increase version for release 2022-11-24 11:08:22 +01:00
Andrea Busi a56a04a4ad refactor: Recreate NotificationService (in Swift) to solve unpredictable behaviour
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/54
2022-11-24 11:08:22 +01:00
Andrea Busi 7173dc7031 dependency: Update Shogun 2022-11-24 10:52:39 +01:00
Andrea Busi 35dbdbab28 fix: Reduce space 2022-11-18 10:57:08 +01:00
Andrea Busi 14614267d4 feat: Add color in user report total 2022-11-18 10:50:24 +01:00
Andrea Busi 32833010ed dependency: Update Shogun 2022-11-18 10:50:08 +01:00
Andrea Busi ecc77e9f2b fix: Solve runtime warning 2022-11-18 08:34:41 +01:00
Andrea Busi e69747f133 refactor: Migrate preference to AppPreferences class 2022-11-18 08:33:35 +01:00
Andrea Busi 09685fd4a7 feat: Store user selection for compact/expanded view in suer report 2022-11-17 22:21:59 +01:00
Andrea Busi 3e122240dc refactor: Rework image creation 2022-11-17 22:13:18 +01:00
Andrea Busi 95a214403b release: Increase version for release 2022-11-17 15:44:07 +01:00
Andrea Busi a7db3c6fa1 docs: Add some example payloads 2022-11-17 15:42:32 +01:00
Andrea Busi 4805c79ed6 refactor: Merge icons and colors in a single asset, to solve issue with notification extensions
For some reasons, notification extension doesn't support multiple xcasset
2022-11-17 15:42:32 +01:00
Andrea Busi c6ec20e180 refactor: Migrate some extensions usage to Shogun 2022-11-17 15:42:32 +01:00
Andrea Busi f6dfd4a761 dependency: Add Shogun library 2022-11-17 15:42:32 +01:00
Andrea Busi 0212d0a15f fix: Minor UI tweaks in user reports map 2022-11-17 15:42:32 +01:00
Andrea Busi 3176bde5ed release: Increase version for release 2022-11-17 15:42:32 +01:00
Andrea Busi dfdbbd0ed4 feat: Update new user report data in notification extension 2022-11-17 15:42:00 +01:00
Andrea Busi e9986e0fe1 feat: Align user report map to Android app 2022-11-15 22:47:42 +01:00
Andrea Busi beb264f95e refactor: Move create snapshot to an extension method 2022-11-15 22:41:04 +01:00
Andrea Busi 217b5edbcf fix: Properly evaluate if ads is visible 2022-11-15 22:37:32 +01:00
Andrea Busi e13f95aa5d feat: Add color assets for user report 2022-11-15 12:28:54 +01:00
Andrea Busi fc5bdbcc92 fix: Solve issue with new registration logic 2022-11-15 12:28:54 +01:00
Andrea Busi d0ee637449 refactor: Remove send comment in manual report 2022-11-15 12:28:54 +01:00
Andrea Busi 66e9d7035e fix: Increase size for new report earthquake section buttons 2022-11-15 12:28:34 +01:00
Andrea Busi 7db48e381f release: Update changelog 2022-11-15 12:28:34 +01:00
Andrea Busi f9bdb84ad9 feat: Align 'last 24h user reports' card to Android 2022-11-11 15:16:16 +01:00
Andrea Busi bee18f6407 refactor: Use existing category for dictionary parsing 2022-11-11 15:16:16 +01:00
Andrea Busi 96f3a44db4 feat: Reset Firebase token to force a new server registration 2022-11-11 15:16:16 +01:00
Andrea Busi e394259f24 feat: New report earthquake section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/51
2022-11-11 15:16:13 +01:00
Andrea Busi 3b53350969 refactor: Store lastLocation in EQNUserData 2022-11-11 15:15:47 +01:00
Andrea Busi f3c3c19e39 refactor: Store userId in EQNUserData 2022-11-11 15:15:47 +01:00
Andrea Busi 8404d72d8f refactor: Rework user registration to solve double registration
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/50
2022-11-11 15:15:40 +01:00
Andrea Busi a3b0499ed3 dependency: Update Pods 2022-10-28 12:21:29 +02:00
Andrea Busi d923b37fbd refactor: Update to Xcode 14.1 recommended settings 2022-10-28 12:12:26 +02:00
Andrea Busi 282803cf98 release: Increase version for release 2022-07-18 12:54:07 +02:00
Andrea Busi 039e7b82a1 fix: Don't show debug view when tap 5 times on Settings 2022-07-18 12:53:57 +02:00
Andrea Busi b098caf2ef chore: Update push payloads 2022-06-23 11:33:50 +02:00
482 changed files with 2751 additions and 1858 deletions
+15
View File
@@ -1,5 +1,20 @@
# Changelog
## Versione 5.3.1
- Ricreato progetto NotificationService
- Correzioni minori
## Versione 5.3
- Rivista gestione registrazione utente
- Modifica in segnalazioni utente
- Allineata mappa segnalazioni utente ad Android
## Versione 5.2.1
- Disattivata schermata di debug
## Versione 5.2
- Nuova schermata per allerta in tempo reale
+31
View File
@@ -0,0 +1,31 @@
{
"magnitude_range" : "0",
"provider" : "SGC",
"google.c.a.e" : "1",
"google.c.fid" : "d3PS1dEvrUA-tmLLpl5E5f",
"preliminary" : "0",
"longitude" : "-75.5157",
"gcm.message_id" : "1668682445010677",
"latitude" : "4.35306",
"type" : "official",
"google.c.sender.id" : "899482329945",
"difference" : "6",
"data" : "2022-11-17 11:48:00",
"depth" : "26",
"aps" : {
"content-available" : 1,
"alert" : {
"loc-key" : "Sisma rilevato a",
"title-loc-key" : "Segnalazione da rete sismica",
"loc-args" : [
"Cajamarca - Tolima, Colombia - M2.2"
]
},
"mutable-content" : 1,
"sound" : "default"
},
"magnitude" : "2.2",
"magnitude_type" : "M",
"place" : "Cajamarca - Tolima, Colombia",
"pop100" : "6622"
}
@@ -0,0 +1,34 @@
{
"Simulator Target Bundle": "com.finazzi.distquake",
"aps": {
"alert": {
"loc-args": [
"2 km da Foligno"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
},
"category": "notifica_con_mappa",
"content-available": 1,
"mutable-content": 1,
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 19:12:00",
"delay": 4,
"detection_latitude": "45.64664",
"detection_longitude": "9.188540",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"magnitude": 20,
"latitude": "42.958",
"location": "2 km da Foligno",
"longitude": "12.702",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "manual",
"wave_speed": "4.7",
"critical": true
}
+8 -7
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"15 χλμ από το Αίγιο"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,7 +14,8 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 16:15:00",
"datetime": "2022-06-23 10:10:00",
"delay": 4,
"detection_latitude": "37.983810",
"detection_longitude": "23.727539",
"gcm.message_id": "1614708857742608",
@@ -22,12 +23,12 @@
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "38.19",
"location": "150 km (Test)",
"location": "15 χλμ από το Αίγιο",
"longitude": "22.26",
"peak": "-1",
"randcode": 0,
"test": 1,
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
}
}
+7 -6
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"14 km from California City"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,7 +14,8 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 16:15:00",
"datetime": "2022-06-23 07:55:00",
"delay": 4,
"detection_latitude": "37.3229978",
"detection_longitude": "-122.0321823",
"gcm.message_id": "1614708857742608",
@@ -22,11 +23,11 @@
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "35.15",
"location": "150 km (Test)",
"location": "14 km from California City",
"longitude": "-117.78",
"peak": "-1",
"randcode": 0,
"test": 1,
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+7 -6
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"5 km de Las Cujas"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,7 +14,8 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 16:15:00",
"datetime": "2022-06-23 10:19:00",
"delay": 4,
"detection_latitude": "-34.603722",
"detection_longitude": "-58.381592",
"gcm.message_id": "1614708857742608",
@@ -22,11 +23,11 @@
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "-32.57",
"location": "150 km (Test)",
"location": "5 km de Las Cujas",
"longitude": "-71.46",
"peak": "-1",
"randcode": 0,
"test": 1,
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+7 -6
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"15 km de Dieppe"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,7 +14,8 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 16:15:00",
"datetime": "2022-06-23 10:07:00",
"delay": 4,
"detection_latitude": "48.856614",
"detection_longitude": "2.8522219",
"gcm.message_id": "1614708857742608",
@@ -22,11 +23,11 @@
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "49.77",
"location": "150 km (Test)",
"location": "15 km de Dieppe",
"longitude": "1.05",
"peak": "-1",
"randcode": 0,
"test": 1,
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+7 -6
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"5 km od Novog"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,7 +14,8 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 16:15:00",
"datetime": "2022-06-23 10:02:00",
"delay": 4,
"detection_latitude": "45.813177",
"detection_longitude": "15.977048",
"gcm.message_id": "1614708857742608",
@@ -22,11 +23,11 @@
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "45.09",
"location": "150 km (Test)",
"location": "5 km od Novog",
"longitude": "14.87",
"peak": "-1",
"randcode": 0,
"test": 1,
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+7 -6
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"35 km dari Sindanbarang"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,7 +14,8 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 16:15:00",
"datetime": "2022-06-23 10:13:00",
"delay": 4,
"detection_latitude": "-2.548926",
"detection_longitude": "118.0148634",
"gcm.message_id": "1614708857742608",
@@ -22,11 +23,11 @@
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "-7.38",
"location": "150 km (Test)",
"location": "35 km dari Sindanbarang",
"longitude": "106.83",
"peak": "-1",
"randcode": 0,
"test": 1,
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+7 -6
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"2 km da Foligno"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,7 +14,8 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 16:15:00",
"datetime": "2022-06-22 19:12:00",
"delay": 4,
"detection_latitude": "45.64664",
"detection_longitude": "9.188540",
"gcm.message_id": "1614708857742608",
@@ -22,11 +23,11 @@
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "42.958",
"location": "150 km (Test)",
"location": "2 km da Foligno",
"longitude": "12.702",
"peak": "-1",
"randcode": 0,
"test": 1,
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+7 -6
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"45 km de Puebla"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,7 +14,8 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 16:15:00",
"datetime": "2022-06-23 10:16:00",
"delay": 4,
"detection_latitude": "19.432608",
"detection_longitude": "-99.133209",
"gcm.message_id": "1614708857742608",
@@ -22,11 +23,11 @@
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "18.72",
"location": "150 km (Test)",
"location": "45 km de Puebla",
"longitude": "-97.90",
"peak": "-1",
"randcode": 0,
"test": 1,
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+8 -7
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"Bandırma'ya 25 km"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,7 +14,8 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 16:15:00",
"datetime": "2022-06-23 10:22:00",
"delay": 4,
"detection_latitude": "41.008238",
"detection_longitude": "28.978359",
"gcm.message_id": "1614708857742608",
@@ -22,12 +23,12 @@
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "40.33",
"location": "150 km (Test)",
"location": "Bandırma'ya 25 km",
"longitude": "27.66",
"peak": "-1",
"randcode": 0,
"test": 1,
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
}
}
@@ -41,6 +41,7 @@
[super viewDidLoad];
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SmallIdentifier];
}
- (void)didReceiveNotification:(UNNotification *)notification
@@ -67,9 +68,8 @@
[self.mapView addAnnotation:annotation];
} else if ([userInfo[@"type"] isEqualToString:@"manual"]){
EQNMapAnnotationUserReport *annotation = [[EQNMapAnnotationUserReport alloc] initWithTitle:@""
coordinate:coordinate.coordinate
magnitude:[userInfo[@"magnitudo"] intValue]];
EQNMapAnnotationUserReport *annotation = [[EQNMapAnnotationUserReport alloc] initWithMagnitude:[userInfo[@"magnitude"] intValue]
coordinate:coordinate.coordinate];
[self.mapView addAnnotation:annotation];
}
@@ -118,9 +118,9 @@
} else if ([annotation isKindOfClass:[EQNMapAnnotationUserReport class]]) {
EQNMapAnnotationUserReport *report = (EQNMapAnnotationUserReport *)annotation;
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
annotationView.image = report.image;
annotationView.title = report.title;
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SmallIdentifier];
annotationView.image = [report imageWithHeight:EQNCustomAnnotationView.SmallViewImageHeight];
annotationView.title = report.timeDifference;
return annotationView;
}
return nil;
@@ -1,5 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "NotificationService.h"
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.finazzi.distquake</string>
</array>
</dict>
</plist>
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

+1 -24
View File
@@ -2,35 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>EQNNotificationService</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>NotificationService</string>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>
@@ -1,47 +0,0 @@
//
// NotificationService+Extension.swift
// EQNNotificationService
//
// Created by Andrea Busi on 28/05/22.
// Copyright © 2022 Earthquake Network. All rights reserved.
//
import Foundation
import UserNotifications
extension NotificationService {
@objc(removeNotificationsForType:completion:)
func removeNotifications(
for type: String?,
completion: @escaping() -> Void
) {
guard let type = type else {
completion()
return
}
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getDeliveredNotifications { notifications in
let sameTypeNotifications = notifications.filter { notification in
let payload = notification.request.content.userInfo
if let notificationType = payload["type"] as? String {
return notificationType == type
}
return false
}
let identifiers = sameTypeNotifications.map { $0.request.identifier }
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
// !! Note: this is a known issue/bug
// we need to add a delay before invoking the completion, otherwise the notification will not be remved
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion()
}
}
}
}
@@ -1,13 +0,0 @@
//
// NotificationService.h
// EQNNotificationService
//
// Refactored by Andrea Busi
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import <UserNotifications/UserNotifications.h>
@interface NotificationService : UNNotificationServiceExtension
@end
@@ -1,181 +0,0 @@
//
// NotificationService.m
// EQNNotificationService
//
// Refactored by Andrea Busi
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import "NotificationService.h"
#import "EQNAllertaSismica.h"
#import "Costanti.h"
#import "EQNNotificationService-Swift.h"
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
static NSString * const EQNSoundNotificationEQN = @"alert_sound.wav";
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler
{
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Configure the notification's payload.
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = [NSString localizedUserNotificationStringForKey:request.content.title arguments:nil];
content.body = [NSString localizedUserNotificationStringForKey:request.content.body arguments:nil];
self.bestAttemptContent.title = content.title;
self.bestAttemptContent.body = content.body;
// check for required informations
NSDictionary *userInfo = request.content.userInfo;
NSString *notificationType = [userInfo objectForKey:@"type"];
if (userInfo == nil || notificationType == nil) {
[self contentComplete];
return;
}
NSString *iconName = @"";
if ([notificationType isEqualToString:@"eqn"]) {
// check if user has enabled critical alerts
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:EQNUserDefaultAppGroupSuite];
BOOL criticalAlertsEnabled = [sharedDefaults boolForKey:NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS];
// !!WORKAROUND
// this is a workaround to use critical alerts with legacy FCM api
// when the server implementation will be migrated to Firebase v1 APIs, this could be removed
BOOL isCritical = [[userInfo objectForKey:@"critical"] boolValue];
if (isCritical && criticalAlertsEnabled) {
self.bestAttemptContent.sound = [UNNotificationSound criticalSoundNamed:EQNSoundNotificationEQN withAudioVolume:1.0];
} else {
self.bestAttemptContent.sound = [UNNotificationSound soundNamed:EQNSoundNotificationEQN];
}
NSString *intensity = [userInfo objectForKey:@"intensity"];
switch ([intensity intValue]) {
case 0:
iconName = @"star_realtime_white.png";
break;
case 1:
iconName = @"star_realtime_lightblue.png";
break;
case 2:
iconName = @"star_realtime_blue.png";
break;
default:
break;
}
} else if ([notificationType isEqualToString:@"manual"]) {
NSString *intensity = [userInfo objectForKey:@"magnitude"];
switch ([intensity intValue]) {
case 0:
iconName = @"star_green.png";
break;
case 1:
iconName = @"star_yellow.png";
break;
case 2:
iconName = @"star_red.png";
break;
default:
break;
}
} else if ([notificationType isEqualToString:@"official"]) {
NSString *provaider = [userInfo objectForKey:@"provider"];
double intensity = [[userInfo objectForKey:@"magnitude"] doubleValue];
NSString *colore = @"";
if (intensity < 2.0) {
colore = @"_white";
} else if (intensity < 3.5) {
colore = @"_green";
} else if (intensity < 4.5) {
colore = @"_yellow";
} else if (intensity < 5.5) {
colore = @"_red";
} else {
colore = @"_purple";
}
if ([provaider isEqualToString:@"USGS"]) {
iconName = [NSString stringWithFormat:@"star%@.png", colore];
} else if ([provaider isEqualToString:@"SGC"]) {
iconName = [NSString stringWithFormat:@"star3%@.png", colore];
} else if ([provaider isEqualToString:@"CSN"]) {
iconName = [NSString stringWithFormat:@"star3f%@.png", colore];
} else if ([provaider isEqualToString:@"SSN"]) {
iconName = [NSString stringWithFormat:@"star4%@.png", colore];
} else if ([provaider isEqualToString:@"INPRES"]) {
iconName = [NSString stringWithFormat:@"star4r%@.png", colore];
} else if ([provaider isEqualToString:@"FUNVISIS"]) {
iconName = [NSString stringWithFormat:@"star6%@.png", colore];
} else if ([provaider isEqualToString:@"Ineter"]) {
iconName = [NSString stringWithFormat:@"triangle%@.png", colore];
} else if ([provaider isEqualToString:@"RSN"]) {
iconName = [NSString stringWithFormat:@"triangle2%@.png", colore];
} else if ([provaider isEqualToString:@"PHIVOLCS"]) {
iconName = [NSString stringWithFormat:@"triround_inner%@.png", colore];
} else if ([provaider isEqualToString:@"IGEPN"]) {
iconName = [NSString stringWithFormat:@"triround%@.png", colore];
} else if ([provaider isEqualToString:@"INGV"]) {
iconName = [NSString stringWithFormat:@"circle%@.png", colore];
} else if ([provaider isEqualToString:@"EMSC"]) {
iconName = [NSString stringWithFormat:@"dyamond%@.png", colore];
} else if ([provaider isEqualToString:@"IGP"]) {
iconName = [NSString stringWithFormat:@"dyamond_round%@.png", colore];
} else if ([provaider isEqualToString:@"JMA"]) {
iconName = [NSString stringWithFormat:@"esa%@.png", colore];
} else if ([provaider isEqualToString:@"GEONET"]) {
iconName = [NSString stringWithFormat:@"oct%@.png", colore];
} if ([provaider isEqualToString:@"CSI"]) {
iconName = [NSString stringWithFormat:@"penta%@.png", colore];
} else if ([provaider isEqualToString:@"IGN"]) {
iconName = [NSString stringWithFormat:@"square%@.png", colore];
} else if ([provaider isEqualToString:@"UASD"] || [provaider isEqualToString:@"BDTIM"] || [provaider isEqualToString:@"NCS"]) {
iconName = [NSString stringWithFormat:@"thick_star%@.png", colore];
} else if ([provaider isEqualToString:@"RSPR"]) {
iconName = [NSString stringWithFormat:@"star6f%@.png", colore];
}
}
if (![iconName isEqualToString:@""]) {
iconName = [iconName stringByReplacingOccurrencesOfString:@".png" withString:@""];
NSURL *imageUrl = [NSBundle.mainBundle URLForResource:iconName withExtension:@"png"];
NSError *attachmentError = nil;
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:iconName
URL:imageUrl
options:nil
error:&attachmentError];
if (!attachmentError) {
self.bestAttemptContent.attachments = @[attachment];
}
}
// remove same type posted notification
[self removeNotificationsForType:notificationType completion:^{
[self contentComplete];
}];;
}
- (void)serviceExtensionTimeWillExpire
{
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
[self contentComplete];
}
- (void)contentComplete
{
self.contentHandler(self.bestAttemptContent);
}
@end
@@ -0,0 +1,184 @@
//
// NotificationService.swift
// EQNNotificationService
//
// Created by Andrea Busi on 24/11/22.
// Copyright © 2022 Earthquake Network. All rights reserved.
//
import UserNotifications
import Shogun
class NotificationService: UNNotificationServiceExtension {
// riportiamo queste costanti dall'app bundle, per non dover referenziare file in ObjC
private static let EQNUserDefaultAppGroupSuite = "group.com.finazzi.distquake"
private static let NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS = "NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS"
private static let EQNSoundNotification = UNNotificationSoundName("alert_sound.wav")
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
guard let bestAttemptContent = bestAttemptContent else {
print("[NotificationService] Unable to get notification content")
contentComplete()
return
}
let userInfo = request.content.userInfo
guard let notificationType = userInfo.string(forKey: "type") else {
print("[NotificationService] Unable to get notification type")
contentComplete()
return
}
var iconName = ""
switch notificationType.lowercased() {
case "eqn":
// check if user has enabled critical alerts
let sharedDefaults = UserDefaults(suiteName: Self.EQNUserDefaultAppGroupSuite)
let criticalAlertsEnabled = sharedDefaults?.bool(forKey: Self.NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS) ?? false
// !!WORKAROUND
// this is a workaround to use critical alerts with legacy FCM api
// when the server implementation will be migrated to Firebase v1 APIs, this could be removed
let isCritical = userInfo.bool(forKey: "critical", orDefault: false)
if isCritical && criticalAlertsEnabled {
bestAttemptContent.sound = UNNotificationSound.criticalSoundNamed(Self.EQNSoundNotification)
} else {
bestAttemptContent.sound = UNNotificationSound(named: Self.EQNSoundNotification)
}
let intensity = userInfo.integer(forKey: "intensity")
switch intensity {
case 0:
iconName = "star_realtime_white.png"
case 1:
iconName = "star_realtime_lightblue.png"
case 2:
iconName = "star_realtime_blue.png"
default:
break
}
case "manual":
// there are 12 levels, so a customized icon doesn't make sense
// use a generic warning icon instead
iconName = "warning_yellow.png"
case "official":
let provider = userInfo.string(forKey: "provider", orDefault: "")
let intensity = userInfo.double(forKey: "magnitude", orDefault: 0)
let color: String
if intensity < 2.0 {
color = "_white"
} else if intensity < 3.5 {
color = "_green"
} else if intensity < 4.5 {
color = "_yellow"
} else if intensity < 5.5 {
color = "_red"
} else {
color = "_purple"
}
iconName = manualIconName(for: provider, color: color)
default:
break
}
// add the icon as notification attachment
if !iconName.isEmpty {
iconName = iconName.replacingOccurrences(of: ".png", with: "")
if let imageUrl = Bundle(for: NotificationService.self).url(forResource: iconName, withExtension: "png"),
let attachment = try? UNNotificationAttachment(identifier: iconName, url: imageUrl) {
bestAttemptContent.attachments = [ attachment ]
}
}
// remove same type posted notification
removeNotifications(for: notificationType) { [weak self] in
self?.contentComplete()
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
// MARK: - Private
private func contentComplete() {
if let bestAttemptContent {
contentHandler?(bestAttemptContent)
}
}
private func removeNotifications(
for type: String?,
completion: @escaping() -> Void
) {
guard let type = type else {
completion()
return
}
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getDeliveredNotifications { notifications in
let sameTypeNotifications = notifications.filter { notification in
let payload = notification.request.content.userInfo
if let notificationType = payload["type"] as? String {
return notificationType == type
}
return false
}
let identifiers = sameTypeNotifications.map { $0.request.identifier }
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
// !! Note: this is a known issue/bug
// we need to add a delay before invoking the completion, otherwise the notification will not be remved
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion()
}
}
}
// MARK: - Helpers
private func manualIconName(for provider: String, color: String) -> String {
switch provider.uppercased() {
case "USGS": return "star\(color).png"
case "SGC": return "star3\(color).png"
case "CSN": return "star3f\(color).png"
case "SSN": return "star4\(color).png"
case "INPRES": return "star4r\(color).png"
case "FUNVISIS": return "star6\(color).png"
case "INETER": return "triangle\(color).png"
case "RSN": return "triangle2\(color).png"
case "PHIVOLCS": return "triround_inner\(color).png"
case "IGEPN": return "triround\(color).png"
case "INGV": return "circle\(color).png"
case "EMSC": return "dyamond\(color).png"
case "IGP": return "dyamond_round\(color).png"
case "JMA": return "esa\(color).png"
case "GEONET": return "oct\(color).png"
case "CSI": return "penta\(color).png"
case "IGN": return "square\(color).png"
case "UASD", "BDTIM", "NCS": return "thick_star\(color).png"
case "RSPR": return "star6f\(color).png"
default: return ""
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1410"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
@@ -74,6 +74,7 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1410"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
@@ -74,6 +74,7 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -0,0 +1,14 @@
{
"pins" : [
{
"identity" : "shogun",
"kind" : "remoteSourceControl",
"location" : "https://github.com/andreabusi-it/Shogun",
"state" : {
"revision" : "4120014b7e567296a95cd05a8a801a198f4cdac9",
"version" : "0.5.0"
}
}
],
"version" : 2
}
+8 -8
View File
@@ -43,6 +43,11 @@
}];
#endif
// execute commands
EQNStartupCommandsBuilder *builder = [[EQNStartupCommandsBuilder alloc] init];
self.commands = [builder build];
[builder executeWithCommands:self.commands];
[EQNUser defaultUser];
[EQNManager defaultManager];
[FIRApp configure];
@@ -52,11 +57,6 @@
NSString *language = [[NSLocale preferredLanguages] firstObject];
[[FIRCrashlytics crashlytics] setCustomValue:language forKey:@"lang"];
// execute commands
EQNStartupCommandsBuilder *builder = [[EQNStartupCommandsBuilder alloc] init];
self.commands = [builder build];
[builder executeWithCommands:self.commands];
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
[[UNUserNotificationCenter currentNotificationCenter]
@@ -125,8 +125,8 @@
{
NSString *token = [self stringWithDeviceToken:deviceToken];
NSLog(@"[DEBUG] push token: %@", token);
[[NSUserDefaults standardUserDefaults] setObject:token forKey:EQNUserDefaultPushToken];
FIRMessaging.messaging.APNSToken = deviceToken;
}
@@ -202,14 +202,14 @@
- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken
{
NSLog(@"[Firebase] fcmToken %@", fcmToken);
if (![[NSUserDefaults standardUserDefaults] objectForKey:EQNUserDefaultUserFirebaseToken]) {
if (EQNUserData.sharedData.isFirstStart) {
// save default values for notification settings
[EQNAllertaSismica saveDefaultValues];
[EQNNotificheSegnalazioniUtente saveDefaultValues];
[EQNNotificheReteSismiche saveDefaultValues];
}
[EQNUser defaultUser].tokenUser = fcmToken;
[EQNUser.defaultUser registerUserIfNeededWithFirebaseToken:fcmToken];
}
@end
@@ -113,7 +113,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
[super refreshUI];
// `AllerteTableRowReteSmartphone` and `AllerteTableRowDatiPosizione` are hidden bu default, user can show them
BOOL showAllCards = [[NSUserDefaults standardUserDefaults] boolForKey:EQNUserDefaultKeyAlertsShowAllCards];
BOOL showAllCards = AppPreferences.shared.alertsShowAllCards;
self.expandeCollapseButton.image = showAllCards ? [UIImage imageNamed:@"navbar-icon-arrow-collapse"] : [UIImage imageNamed:@"navbar-icon-arrow-expand"];
// controlliamo se c'è una notifica in tempo reale da mostrare
@@ -177,9 +177,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
- (IBAction)collapseExpandTapped:(id)sender
{
// toggle saved value
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL showAll = [defaults boolForKey:EQNUserDefaultKeyAlertsShowAllCards];
[defaults setBool:!showAll forKey:EQNUserDefaultKeyAlertsShowAllCards];
AppPreferences.shared.alertsShowAllCards = !AppPreferences.shared.alertsShowAllCards;
[self refreshUI];
}
@@ -8,6 +8,7 @@
import UIKit
import MapKit
import Shogun
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMapViewDelegate {
@@ -73,7 +74,7 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
let aps = notification["aps"] as? [String: Any],
let alert = aps["alert"] as? [String: Any] else { return }
let intensity = notification.eqn_intValue(for: "intensity") ?? 0
let intensity = notification.integer(forKey: "intensity", orDefault: 0)
containerView.backgroundColor = color(for: intensity)
if let title = alert["loc-key"] as? String, let args = alert["loc-args"] as? [String], let arg = args.first {
@@ -82,8 +83,8 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
// get coordinate
var coordinate: CLLocation?
if let latitude = notification.eqn_doubleValue(for: "latitude"),
let longitude = notification.eqn_doubleValue(for: "longitude") {
if let latitude = notification.double(forKey: "latitude"),
let longitude = notification.double(forKey: "longitude") {
coordinate = CLLocation(latitude: latitude, longitude: longitude)
}
@@ -65,6 +65,8 @@ class PasquakesMapViewController: EQNBaseMapViewController {
}
override func didTapAnnotation(_ annotation: MKAnnotation) {
mapView.deselectAnnotation(annotation, animated: true)
guard let annotation = annotation as? EQNMapAnnotationPastquake, let pastquake = annotation.pastquake else {
return
}
@@ -29,7 +29,7 @@
[[EQNManager defaultManager] avviaManager];
[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
self.testo = [NSString stringWithFormat:@" LOG ID UTENTE %@\n\nTOKEN FIREBASE:\n%@\n\n", [EQNUser defaultUser].user_ID, [EQNUser defaultUser].tokenUser];
self.testo = [NSString stringWithFormat:@" LOG ID UTENTE %@\n\nTOKEN FIREBASE:\n%@\n\n", [EQNUser defaultUser].user_ID, EQNUserData.sharedData.firebaseToken];
self.logView.text = self.testo;
}
@@ -21,7 +21,11 @@
- (BOOL)isBannerVisible
{
#if ADS_ENABLED
return ![EQNPurchaseUtility isProVersionEnabled];
#else
return NO;
#endif
}
#pragma mark - View Lifecycle
@@ -63,7 +63,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"retry", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
// retry server registration
[[EQNUser defaultUser] verificaRegistrazione];
[[EQNUser defaultUser] retryUserRegistration];
}]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"options_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
@@ -136,7 +136,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
}
// tap 5 times on "Settings" to open debug view
if ([controller isKindOfClass:[SettingsViewController class]]) {
if ([controller isKindOfClass:[SettingsViewController class]] && EQNEnableDebugView) {
self.debugTapCounter += 1;
if (self.debugTapCounter == 5) {
self.debugTapCounter = 0;
@@ -8,6 +8,7 @@
import UIKit
import StoreKit
import Shogun
class SubscriptionsViewController: UITableViewController {
@@ -7,13 +7,13 @@
//
import UIKit
import Shogun
class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var mildReportsLabel: UILabel!
@IBOutlet private weak var strongReportsLabel: UILabel!
@IBOutlet private weak var veryStrongReportsLabel: UILabel!
@IBOutlet private weak var reportsLabel: UILabel!
@IBOutlet private weak var reportsDescriptionLabel: UILabel!
@IBOutlet private weak var twitterButton: UIButton! {
didSet {
@@ -38,11 +38,11 @@ class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
// MARK: - Private
private func localizeUI() {
headerLabel.text = NSLocalizedString("main_map", comment: "")
headerLabel.text = NSLocalizedString("tab_manual", comment: "").capitalized
reportsDescriptionLabel.text = NSLocalizedString("main_map", comment: "")
mapButton.setTitle(NSLocalizedString("official_button_map", comment: ""), for: .normal)
}
// MARK: - Public
@objc func updateUI(for smartphoneNetwork: EQNReteSmartphone?) {
@@ -50,8 +50,15 @@ class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
return
}
self.mildReportsLabel.text = "\(smartphoneNetwork.manualGreen)"
self.strongReportsLabel.text = "\(smartphoneNetwork.manualYellow)"
self.veryStrongReportsLabel.text = "\(smartphoneNetwork.manualRed)"
let reports = smartphoneNetwork.manual
self.reportsLabel.text = "\(reports)"
if reports < 100 {
self.reportsLabel.textColor = UIColor(hex6: 0x01b400)
} else if reports < 1000 {
self.reportsLabel.textColor = UIColor(hex6: 0xe8da00)
} else {
self.reportsLabel.textColor = UIColor(hex6: 0xff0000)
}
}
}
@@ -8,6 +8,7 @@
import Foundation
import MapKit
import Shogun
class SegnalazioniMapViewController: EQNBaseMapViewController {
@@ -16,11 +17,74 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
let circle: MKCircle
}
override var isCloseButtonVisible: Bool {
false
}
private let appPreferences = AppPreferences.shared
/// Contains circles and related colors to draw overlays on the map
private var mapCircles = [MapCircle]()
/// Reports currently showned on the map
private var filteredReports = [EQNSegnalazione]()
// MARK: - UI
// app icon and name displayed on the screenshot
private lazy var watermarkView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.isHidden = true
let logo = UIImageView(image: .init(named: "eq_icon_transparent"))
logo.translatesAutoresizingMaskIntoConstraints = false
logo.contentMode = .scaleAspectFit
view.addSubview(logo)
logo.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
logo.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
logo.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
logo.widthAnchor.constraint(equalTo: logo.heightAnchor).isActive = true
let title = UILabel()
title.translatesAutoresizingMaskIntoConstraints = false
title.text = NSLocalizedString("app_name", comment: "") + " App"
title.textColor = AppTheme.Colors.red
title.font = .preferredFont(forTextStyle: .title3, weight: .semibold)
view.addSubview(title)
title.leadingAnchor.constraint(equalTo: logo.trailingAnchor, constant: 10.0).isActive = true
title.centerYAnchor.constraint(equalTo: logo.centerYAnchor).isActive = true
title.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
return view
}()
private lazy var magnitudeLegendView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fillEqually
[20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120].forEach { magnitude in
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = (magnitude / 10).romanNumber()
label.backgroundColor = UIColor(named: "Mercalli \(magnitude)")
label.textAlignment = .center
label.font = .preferredFont(forTextStyle: .callout)
label.textColor = magnitude >= 100 ? .white : .black
stackView.addArrangedSubview(label)
}
view.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
return view
}()
// MARK: - View Lifecycle
override func viewDidLoad() {
@@ -29,8 +93,31 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
// MARK: - Public
override func extraUI() {
view.addSubview(magnitudeLegendView)
view.addSubview(watermarkView)
magnitudeLegendView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
magnitudeLegendView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
magnitudeLegendView.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
magnitudeLegendView.topAnchor.constraint(equalTo: mapView.topAnchor).isActive = true
watermarkView.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 10.0).isActive = true
watermarkView.leadingAnchor.constraint(equalTo: mapView.leadingAnchor, constant: 10.0).isActive = true
watermarkView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
}
override func configureUI() {
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(onTapCloseButton(_:)))
navigationItem.rightBarButtonItems = [
UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), style: .plain, target: self, action: #selector(onTapScreenshotButton(_:))),
UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), style: .plain, target: self, action: #selector(onTapMapDetailStyleButton(_:)))
]
}
override func registerMapAnnotationViews() {
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SmallIdentifier)
}
override func loadDataSource() {
@@ -96,27 +183,41 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
}
}
override func didTapAnnotation(_ annotation: MKAnnotation) {
guard let annotation = annotation as? EQNMapAnnotationUserReport, let report = annotation.report else {
return
// MARK: - Actions
@objc private func onTapCloseButton(_ sender: Any) {
dismiss(animated: true)
}
@objc private func onTapMapDetailStyleButton(_ sender: Any) {
appPreferences.userReportExpandedView.toggle()
loadDataSource()
}
@objc private func onTapScreenshotButton(_ sender: Any) {
let snapshot = createSnapshot()
let controller = UIActivityViewController(activityItems: [snapshot], applicationActivities: [])
present(controller, animated: true)
}
public func createSnapshot() -> UIImage {
// mostriamo il watermark e nascondiamo la legenda
watermarkView.isHidden = false
magnitudeLegendView.isHidden = true
// riduciamo la porzione da salvare alla sola mappa (eliminiamo i filtri)
let size = CGSize(width: view.bounds.width, height: mapView.bounds.maxY)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { ctx in
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
}
let difference = Int(Date().timeIntervalSince(report.date) / 60.0)
let title = EQNUtility.formattedString(forTimeDifference: difference) + " - \(report.intensity.description)"
// torniamo allo stato originale
watermarkView.isHidden = true
magnitudeLegendView.isHidden = false
var message = ""
+ "🏢 " + report.address
+ "\n" + EQNUtility.formattedDate(from: report.date) + " \(NSLocalizedString("share_yourtime", comment: ""))"
if !report.message.isEmpty {
message += "\n💬 \(report.message)"
}
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("main_share", comment: ""), style: .default, handler: { [unowned self] _ in
self.openShareActivity(for: report)
}))
alert.addAction(UIAlertAction(title: NSLocalizedString("official_close", comment: ""), style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
return image
}
// MARK: - Private
@@ -125,7 +226,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
let vector_latitude = reports.map { $0.coordinate.coordinate.latitude }
let vector_longitude = reports.map { $0.coordinate.coordinate.longitude }
let vector_date = reports.map { $0.date }
let vector_state = reports.map { $0.intensity.rawValue }
let vector_state = reports.map { $0.intensity }
let minutes: TimeInterval = filter.minutes
@@ -200,21 +301,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
}
let circles = Array(0..<cluster_code).map { (i) -> MapCircle in
var value_distance = max_distance[i] / 20.0
if value_distance > 1.0 {
value_distance = 1.0
}
let value_intensity = (cluster_intensity[i]-1.0) / 2.0
let value_reference = max(value_distance, value_intensity)
let color: UIColor
if value_reference <= 0.5 {
let red = round(value_reference * 510)
color = UIColor(red: CGFloat(red / 255.0), green: 230.0/255.0, blue: 0.0, alpha: 1.0)
} else {
let green = round(230 - (value_reference - 0.5) * 460)
color = UIColor(red: 255.0, green: CGFloat(green / 255.0), blue: 0.0, alpha: 1.0)
}
let color: UIColor = AppTheme.Colors.darkGray
let centre = CLLocation(latitude: lat_centre[i], longitude: lon_centre[i])
let farest = CLLocation(latitude: lat_farest[i], longitude: lon_farest[i])
@@ -244,25 +331,6 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
Date().timeIntervalSince(date) / 60.0
}
private func openShareActivity(for report: EQNSegnalazione) {
// create message to share
let intensity = report.intensity.description
let difference = Int(Date().timeIntervalSince(report.date) / 60.0)
let time = EQNUtility.formattedString(forTimeDifference: difference)
let message = [
NSLocalizedString("share_hashtag", comment: ""),
intensity,
NSLocalizedString("share_felt", comment: ""),
report.address,
time + ".",
NSLocalizedString("share_notified", comment: "")
].joined(separator: " ")
let controller = UIActivityViewController(activityItems: [message], applicationActivities: [])
present(controller, animated: true)
}
// MARK: - Map
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
@@ -270,10 +338,17 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
return nil
}
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
let identifier = appPreferences.userReportExpandedView ? EQNCustomAnnotationView.SingleLineIdentifier : EQNCustomAnnotationView.SmallIdentifier
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as! EQNCustomAnnotationView
annotationView.image = annotation.image
annotationView.title = annotation.title
let size = appPreferences.userReportExpandedView ? EQNCustomAnnotationView.SingleLineImageHeight : EQNCustomAnnotationView.SmallViewImageHeight
annotationView.image = annotation.image(with: size)
annotationView.title = annotation.timeDifference
annotationView.canShowCallout = true
// Psizioniamo più in alto le segnalazioni con intensità maggiore.
// Valori maggiori di anchorPointZ mettono la view più in basso,
// quindi invertiamo il valore dell'intensità
annotationView.layer.anchorPointZ = (1000 - CGFloat(annotation.report?.intensity ?? 0))
return annotationView
}
@@ -10,15 +10,23 @@ import UIKit
class SegnalazioniSendReportCell: EQNBaseTableViewCell {
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var descriptionLabel: UILabel!
@IBOutlet private weak var mildLabel: UILabel!
@IBOutlet private weak var mildButton: UIButton!
@IBOutlet private weak var strongLabel: UILabel!
@IBOutlet private weak var strongButton: UIButton!
@IBOutlet private weak var veryStrongLabel: UILabel!
@IBOutlet private weak var veryStrongButton: UIButton!
@objc var onTapReport: (_ magnitude: Int) -> Void = { _ in }
// MARK: - UI
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var reportMercalli2: UILabel!
@IBOutlet private weak var reportMercalli3: UILabel!
@IBOutlet private weak var reportMercalli4: UILabel!
@IBOutlet private weak var reportMercalli5: UILabel!
@IBOutlet private weak var reportMercalli6: UILabel!
@IBOutlet private weak var reportMercalli7: UILabel!
@IBOutlet private weak var reportMercalli8: UILabel!
@IBOutlet private weak var reportMercalli9: UILabel!
@IBOutlet private weak var reportMercalli10: UILabel!
@IBOutlet private weak var reportMercalli11: UILabel!
@IBOutlet private weak var reportMercalli12: UILabel!
// MARK: - View Lifecycle
override func awakeFromNib() {
@@ -31,21 +39,23 @@ class SegnalazioniSendReportCell: EQNBaseTableViewCell {
private func localizeUI() {
headerLabel.text = NSLocalizedString("main_feel", comment: "")
descriptionLabel.text = NSLocalizedString("manual_usebutton", comment: "")
mildLabel.text = NSLocalizedString("manual_mild", comment: "")
strongLabel.text = NSLocalizedString("manual_strong", comment: "")
veryStrongLabel.text = NSLocalizedString("manual_verystrong", comment: "")
reportMercalli2.text = NSLocalizedString("mercalli_II", comment: "")
reportMercalli3.text = NSLocalizedString("mercalli_III", comment: "")
reportMercalli4.text = NSLocalizedString("mercalli_IV", comment: "")
reportMercalli5.text = NSLocalizedString("mercalli_V", comment: "")
reportMercalli6.text = NSLocalizedString("mercalli_VI", comment: "")
reportMercalli7.text = NSLocalizedString("mercalli_VII", comment: "")
reportMercalli8.text = NSLocalizedString("mercalli_VIII", comment: "")
reportMercalli9.text = NSLocalizedString("mercalli_IX", comment: "")
reportMercalli10.text = NSLocalizedString("mercalli_X", comment: "")
reportMercalli11.text = NSLocalizedString("mercalli_XI", comment: "")
reportMercalli12.text = NSLocalizedString("mercalli_XII", comment: "")
}
override func layoutSubviews() {
super.layoutSubviews()
let labels: [UILabel] = [mildLabel, strongLabel, veryStrongLabel]
labels.forEach { (label) in
label.layer.borderWidth = AppTheme.shared.buttonBorderWidth
label.layer.borderColor = AppTheme.shared.buttonBorderColor.cgColor
label.layer.cornerRadius = AppTheme.shared.buttonCornerRadius
label.clipsToBounds = true
}
// MARK: - Actions
@IBAction private func onTapReportButton(_ sender: UIButton) {
let magnitude = sender.tag
onTapReport(magnitude)
}
}
@@ -70,14 +70,6 @@
return 2;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row) {
case 0: return 140;
default: return UITableViewAutomaticDimension;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
@@ -87,7 +79,10 @@
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ReportEarthquakeCell" forIndexPath:indexPath];
SegnalazioniSendReportCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ReportEarthquakeCell" forIndexPath:indexPath];
cell.onTapReport = ^(NSInteger magnitude) {
[self sendReportTappedWithMagnitude:magnitude];
};
return cell;
}
@@ -96,7 +91,8 @@
- (IBAction)openMapTapped:(id)sender
{
SegnalazioniMapViewController *controller = [[SegnalazioniMapViewController alloc] init];
[self presentViewController:controller animated:YES completion:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentViewController:navController animated:YES completion:nil];
}
- (IBAction)openTwitterTapped:(id)sender
@@ -117,7 +113,7 @@
[[EQNManager defaultManager] sincronizza];
}
- (IBAction)sendReportTapped:(UIButton *)sender
- (void)sendReportTappedWithMagnitude:(NSInteger)magnitude
{
// check to avoid multiple consecutive reports
if ([self.userDefoult objectForKey:CODE_MESSAGE_EQN]) {
@@ -133,7 +129,7 @@
// ask for user confirmation
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"attention", @"") message:NSLocalizedString(@"manual_sure", nil) preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"manual_yes", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self executeSendReportWithMagnitude:sender.tag];
[self executeSendReportWithMagnitude:magnitude];
}]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"options_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
@@ -165,9 +161,7 @@
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"report", @"")
message:NSLocalizedString(@"manual_ok", @"")
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self sendComment];
}]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
});
} failure:^(NSError * error) {
@@ -177,42 +171,6 @@
}];
}
- (void)sendComment
{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"manual_sendmessage_button" , @"")
message:NSLocalizedString(@"manual_sendmessage", @"")
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
}];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok" ,@"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
UITextField * messaggio = alertController.textFields.firstObject;
NSURL *url = [EQNGeneratoreURLServer urlInvioCommentoTerremoto:messaggio.text codeMessage:[self.userDefoult objectForKey:CODE_MESSAGE_EQN]];
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataCommentoTerremoto success:^(id result) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"manual_sendmessage_button" , @"")
message:NSLocalizedString(@"manual_message_received", @"")
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
});
} failure:^(NSError * error) {
[self showErrorAlertWithMessage:error.localizedDescription];
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self refreshUI];
});
}]];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"options_cancel", @"") style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
#pragma mark - Private
- (void)showErrorAlertWithMessage:(NSString *)errorMessage
@@ -9,6 +9,7 @@
import UIKit
import MapKit
import CoreLocation
import Shogun
protocol SeismicNetworkTableViewCellDelegate: AnyObject {
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell)
@@ -55,7 +56,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
private static let DefaultVerticalSpacing: CGFloat = 6.0
private static let DefaultBodyFont = UIFont.preferredFont(forTextStyle: .body)
private static let DefaultBodyFontLight = UIFont.preferredFont(for: .body, weight: .light)
private static let DefaultBodyFontLight = UIFont.preferredFont(forTextStyle: .body, weight: .light)
/// Seismic to show
private var seismic: EQNSisma?
@@ -89,7 +90,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
private lazy var placeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(for: .title2, weight: .semibold)
label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold)
label.numberOfLines = 3
return label
}()
@@ -203,7 +204,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
private func setupUI() {
selectionStyle = .default
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .clear
// container view
@@ -505,16 +505,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
updateUI()
}
/// Creates a snapshot of the current cell
/// - Returns: Image with the snapshot of the cell
public func createSnapshot() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: contentView.bounds.size)
let image = renderer.image { ctx in
contentView.drawHierarchy(in: contentView.bounds, afterScreenUpdates: true)
}
return image
}
// MARK: - Actions
@objc func shareTapped(_ sender: UIButton) {
@@ -7,6 +7,7 @@
//
import UIKit
import Shogun
protocol SeismicFiltersViewControllerDelegate: AnyObject {
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController)
@@ -8,6 +8,7 @@
import UIKit
import MapKit
import Shogun
protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
func seismicNetworksMapDetailControllerWillUpdateData(_ controller: SeismicNetworksMapDetailViewController, needsDataUpdate: Bool)
@@ -107,6 +108,8 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
}
override func didTapAnnotation(_ annotation: MKAnnotation) {
mapView.deselectAnnotation(annotation, animated: true)
guard let annotation = annotation as? EQNMapAnnotationSeismic else { return }
let viewModel = SeismicNetworkViewModel(seismic: annotation.seismic)
@@ -162,7 +165,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
// MARK: - Actions
@objc override func filtersTapped(_ sender: UIGestureRecognizer) {
let controller = SeismicFiltersViewController.makeController()
let controller = SeismicFiltersViewController.makeViewController()
controller.delegate = self
controller.modalPresentationStyle = .overCurrentContext
controller.modalTransitionStyle = .crossDissolve
@@ -9,6 +9,7 @@
import UIKit
import EventKitUI
import DZNEmptyDataSet
import Shogun
class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@@ -317,7 +318,7 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
// create a snapshot of the cell and share with default share sheet
let snapshot = cell.createSnapshot()
let snapshot = cell.contentView.createSnapshot()
// text to share with the snapshot
let shareHashtag = NSLocalizedString("share_hashtag", comment: "")
@@ -27,6 +27,11 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
!availableFilters.isEmpty
}
/// If `true` the close button will be shown on top of the map view
var isCloseButtonVisible: Bool {
true
}
// MARK: - Internal
/// Annotations displayed on the map
@@ -116,13 +121,14 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
private func setupUI() {
view.backgroundColor = .white
view.addSubview(mapView)
view.addSubview(closeButton)
view.addSubview(containerView)
if isFilterViewVisible {
view.addSubview(filtersView)
}
closeButton.addDefaultConstraint(to: view)
if isCloseButtonVisible {
view.addSubview(closeButton)
closeButton.addDefaultConstraint(to: view)
}
containerView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
@@ -145,6 +151,8 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
mapView.bottomAnchor.constraint(equalTo: (isFilterViewVisible ? filtersView : containerView).topAnchor).isActive = true
extraUI()
}
// MARK: - View Lifecycle
@@ -152,6 +160,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
registerMapAnnotationViews()
loadDataSource()
}
@@ -167,6 +176,16 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
// MARK: - Public
/// Add extra UI not available in the base class
func extraUI() {
// nope, subclass will implement some logic
}
/// Configure UI after view initialization
func configureUI() {
// nope, subclass will implement some logic
}
/// Load data to display on the map
func loadDataSource() {
// nope, subclass will implement some logic
@@ -277,7 +296,5 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let annotation = view.annotation else { return }
didTapAnnotation(annotation)
mapView.deselectAnnotation(annotation, animated: true)
}
}
@@ -8,6 +8,7 @@
import UIKit
import MapKit
import Shogun
class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
@@ -243,7 +244,7 @@ class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
}
private func navigateToSubscriptions() {
let controller = SubscriptionsViewController.makeController()
let controller = SubscriptionsViewController.makeViewController()
let navigationController = UINavigationController(rootViewController: controller)
present(navigationController, animated: true)
}
@@ -1,21 +0,0 @@
//
// Costanti+Extensions.swift
// Earthquake Network
//
// Created by Andrea Busi on 06/03/21.
// Copyright © 2021 Earthquake Network. All rights reserved.
//
import Foundation
extension EQNPastquakeIntensity {
var description: String {
switch self {
case .mild: return NSLocalizedString("widget_mild", comment: "")
case .strong: return NSLocalizedString("widget_strong", comment: "")
case .veryStrong: return NSLocalizedString("widget_verystring", comment: "")
}
}
}
+8 -3
View File
@@ -13,6 +13,8 @@
/// Stampa le risposte delle chiamate al server
static BOOL const EQNDebugPrintResponse = NO;
/// Visualizza schermata con info di debug (ex. push token)
static BOOL const EQNEnableDebugView = NO;
#pragma mark - Urls
@@ -47,15 +49,15 @@ static NSString * const EQNServerUrlUserLocation = @"https://srv.earthquakenetwo
static NSString * const EQNServerUrlCalibration = @"https://srv.earthquakenetwork.it/distquake_upload4.php";
// download rete smartphone
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://srv.earthquakenetwork.it/distquake_count_redis.php";
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://srv.earthquakenetwork.it/distquake_count_redis2.php";
// download area check
static NSString * const EQNServerUrlDownloadAreaCheck = @"https://srv.earthquakenetwork.it/distquake_download_areacheck.php";
// download pastquakes
static NSString * const EQNServerUrlDownloadPastQuakes = @"https://srv.earthquakenetwork.it/distquake_download_pastquakes.php";
// download segnalazioni
static NSString * const EQNServerUrlDownloadUserReports = @"https://srv.earthquakenetwork.it/distquake_download_manual.php";
static NSString * const EQNServerUrlDownloadUserReports = @"https://srv.earthquakenetwork.it/distquake_download_manual3.php";
// Invio segnalazione
static NSString * const EQNServerUrlSendUserReport = @"https://srv.earthquakenetwork.it/distquake_upload_manual3.php";
static NSString * const EQNServerUrlSendUserReport = @"https://srv.earthquakenetwork.it/distquake_upload_manual4.php";
static NSString * const EQNServerUrlSendUserReportMessage = @"https://srv.earthquakenetwork.it/distquake_upload_manual_message.php";
/// Effettua un test delle notifiche push
static NSString * const EQNServerUrlTestAlarm = @"https://srv.earthquakenetwork.it/distquake_upload_testalarm.php";
@@ -73,6 +75,9 @@ static NSString * const EQNUserDefaultLastLocation = @"EQNLast_Location";
static NSString * const EQNUserDefaultSeismicNetworkCards = @"EQNData.RetiSismiche";
static NSString * const EQNUserDefaultRealTimeAlertPayload = @"EQNData.RealtimeAlertPayload";
static NSString * const EQNUserDefaultRealTimeAlertDate = @"EQNData.RealtimeAlertDate";
static NSString * const EQNUserDefaultUserReportExpandedView = @"EQNData.UserReportExpandedView";
static NSString * const EQNUserDefaultMigrationV5_3 = @"EQNUserDefaultMigrationV5_3";
/// Numero di aperture dell'app per sbloccare la versione Pro scontata
static NSString * const EQNUserDefaultProDiscountOpenCounter = @"CONTEGGIO_APERTURE_PER_SCONTO";
@@ -1,31 +0,0 @@
//
// Dictionary+EQNExtensions.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/03/21.
// Copyright © 2021 Earthquake Network. All rights reserved.
//
import Foundation
extension Dictionary {
func eqn_intValue(for key: Key) -> Int? {
if let value = self[key] as? Int {
return value
} else if let stringValue = self[key] as? String, let value = Int(stringValue) {
return value
}
return nil
}
func eqn_doubleValue(for key: Key) -> Double? {
if let value = self[key] as? Double {
return value
} else if let stringValue = self[key] as? String, let value = Double(stringValue) {
return value
}
return nil
}
}
@@ -1,19 +0,0 @@
//
// UIFont+Extensions.swift
// Earthquake Network
//
// Created by Busi Andrea on 25/09/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
import Foundation
extension UIFont {
static func preferredFont(for style: TextStyle, weight: Weight) -> UIFont {
let metrics = UIFontMetrics(forTextStyle: style)
let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style)
let font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight)
return metrics.scaledFont(for: font)
}
}
@@ -22,3 +22,29 @@ extension UIButton {
setTitle(title, for: .normal)
}
}
extension UIImage {
class func circle(
diameter: CGFloat,
color: UIColor,
borderWidth: CGFloat = 0.0,
borderColor: UIColor = .black
) -> UIImage {
let size = CGSize(width: diameter, height: diameter)
let renderer = UIGraphicsImageRenderer(size: size)
let img = renderer.image { ctx in
ctx.cgContext.setFillColor(color.cgColor)
ctx.cgContext.setStrokeColor(borderColor.cgColor)
ctx.cgContext.setLineWidth(borderWidth)
// reduce circle size to keep space for the border
// without this, the image view is cropped
let circleDiameter = diameter - 2*borderWidth
let rectangle = CGRect(x: borderWidth, y: borderWidth, width: circleDiameter, height: circleDiameter)
ctx.cgContext.addEllipse(in: rectangle)
ctx.cgContext.drawPath(using: .fillStroke)
}
return img
}
}
@@ -1,25 +0,0 @@
//
// StoryboardInitializable.swift
//
// Created by Busi Andrea on 10/11/2020.
//
import Foundation
protocol StoryboardInitializable where Self: UIViewController {
static var storyboardName: String { get }
static var storyboardControllerId: String { get }
static func makeController() -> Self
}
extension StoryboardInitializable {
static func makeController() -> Self {
let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle(for: Self.self))
guard let controller = storyboard.instantiateViewController(withIdentifier: storyboardControllerId) as? Self else {
fatalError("Unable to instantiate controller with anem \(storyboardControllerId)")
}
return controller
}
}
@@ -0,0 +1,31 @@
//
// AppPreferences.swift
// Earthquake Network
//
// Created by Andrea Busi on 17/11/22.
// Copyright © 2022 Earthquake Network. All rights reserved.
//
import Foundation
@objc
class AppPreferences: NSObject {
@objc
static let shared = AppPreferences()
// MARK: - Public
/// Defines if time has to be shown on map annotations in User Reports
var userReportExpandedView: Bool {
get { UserDefaults.standard.bool(forKey: EQNUserDefaultUserReportExpandedView) }
set { UserDefaults.standard.set(newValue, forKey: EQNUserDefaultUserReportExpandedView) }
}
@objc
var alertsShowAllCards: Bool {
get { UserDefaults.standard.bool(forKey: EQNUserDefaultKeyAlertsShowAllCards) }
set { UserDefaults.standard.set(newValue, forKey: EQNUserDefaultKeyAlertsShowAllCards) }
}
}
@@ -7,6 +7,7 @@
//
import Foundation
import CoreLocation
public class EQNUserDefaultsCommand: EQNCommandProtocol {
@@ -18,6 +19,8 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
applyDefaultSettings()
saveMissingValues()
migrationV5_3()
}
// MARK: - Private
@@ -37,4 +40,24 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
UserDefaults.standard.set("600", forKey: NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI)
}
}
private func migrationV5_3() {
let migrationPerformed = UserDefaults.standard.bool(forKey: EQNUserDefaultMigrationV5_3)
if migrationPerformed {
print("[EQNUserDefaultsCommand] Migration v5.3 already performed")
return
}
// l'ultima posizione era salvata come array, la trasformiamo in valore singolo
let lastLocations = EQNUtility.loadArray(of: CLLocation.self, fromUserDefaultsForKey: EQNUserDefaultLastLocation) as? [CLLocation]
if let lastLocation = lastLocations?.last {
UserDefaults.standard.removeObject(forKey: EQNUserDefaultLastLocation)
EQNUserData.shared.saveLastLocation(lastLocation)
}
// resettiamo il Firebase token in modo da ri-eseguire la procedura di registrazione corretta
EQNUserData.shared.saveFirebaseToken(nil)
UserDefaults.standard.set(true, forKey: EQNUserDefaultMigrationV5_3)
}
}
@@ -8,6 +8,7 @@
import Foundation
import CoreLocation
import Shogun
@objc
@@ -41,8 +42,8 @@ class EQNRealtimeAlert: NSObject {
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
self.intensity = notification.integer(forKey: "intensity", orDefault: 0)
self.waveSpeed = notification.double(forKey: "wave_speed", orDefault: 0.0) * 1000 // m/s
}
func distanceFromUser() -> CLLocationDistance {
@@ -82,8 +83,8 @@ class EQNRealtimeAlert: NSObject {
static func getCoordinate(
from notification: NotificationPayload
) -> CLLocation? {
guard let latitude = notification.eqn_doubleValue(for: "latitude"),
let longitude = notification.eqn_doubleValue(for: "longitude") else {
guard let latitude = notification.double(forKey: "latitude"),
let longitude = notification.double(forKey: "longitude") else {
return nil
}
return CLLocation(latitude: latitude, longitude: longitude)
@@ -7,6 +7,7 @@
//
import Foundation
import Shogun
@objc
@@ -14,9 +15,7 @@ class EQNReteSmartphone: NSObject {
@objc let counterLastDayAlerts: Int
@objc let counterTotalAlerts: Int
@objc let counterSmartphones: Int
@objc let manualGreen: Int
@objc let manualYellow: Int
@objc let manualRed: Int
@objc let manual: Int
@objc let lastSubscriptionDiff: Int
@objc let subscriptionsDiscounted: Bool
@@ -28,30 +27,14 @@ class EQNReteSmartphone: NSObject {
return result.merging(dictionary, uniquingKeysWith: { (_, new) in new })
}
self.counterLastDayAlerts = Self.getValue(from: allValues, for: "eq")
self.counterTotalAlerts = Self.getValue(from: allValues, for: "eq_p")
self.counterSmartphones = Self.getValue(from: allValues, for: "green")
self.manualGreen = Self.getValue(from: allValues, for: "g_man")
self.manualYellow = Self.getValue(from: allValues, for: "y_man")
self.manualRed = Self.getValue(from: allValues, for: "r_man")
self.lastSubscriptionDiff = Self.getValue(from: allValues, for: "diff")
let subscriptionsDiscounted = Self.getValue(from: allValues, for: "st")
self.counterLastDayAlerts = allValues.integer(forKey: "eq", orDefault: 0)
self.counterTotalAlerts = allValues.integer(forKey: "eq_p", orDefault: 0)
self.counterSmartphones = allValues.integer(forKey: "green", orDefault: 0)
self.manual = allValues.integer(forKey: "man", orDefault: 0)
self.lastSubscriptionDiff = allValues.integer(forKey: "diff", orDefault: 0)
let subscriptionsDiscounted = allValues.integer(forKey: "st", orDefault: 0)
self.subscriptionsDiscounted = subscriptionsDiscounted == 1
super.init()
}
// MARK: - Private
/// This method helps to extract an int value from the received values (where data are both strings and integers).
/// If convertion is not possible, it will return zero.
private static func getValue(from values: [String: Any], for key: String) -> Int {
if let intValue = values[key] as? Int {
return intValue
}
if let stringValue = values[key] as? String, let intValue = Int(stringValue) {
return intValue
}
return 0
}
}
@@ -9,23 +9,15 @@
@import Foundation;
@import CoreLocation;
// Intensità terremoti segnalazioni utente
typedef NS_CLOSED_ENUM(NSInteger, EQNPastquakeIntensity) {
EQNPastquakeIntensityMild = 1,
EQNPastquakeIntensityStrong = 2,
EQNPastquakeIntensityVeryStrong = 3
};
NS_ASSUME_NONNULL_BEGIN
@interface EQNSegnalazione : NSObject
@property (nonatomic, strong) CLLocation *coordinate;
@property (nonatomic, strong) NSString *address;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic) NSInteger difference;
@property (nonatomic) EQNPastquakeIntensity intensity;
@property (nonatomic, strong) NSString *message;
// values are from 20 to 120
@property (nonatomic) NSInteger intensity;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
@@ -17,15 +17,12 @@
{
self = [super init];
if (self) {
double latitude = [dictionary[@"latitude"] doubleValue];
double longitude = [dictionary[@"longitude"] doubleValue];
double latitude = [dictionary[@"la"] doubleValue];
double longitude = [dictionary[@"lo"] doubleValue];
_coordinate = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
_address = dictionary[@"address"];
NSDate *date = [EQNUtility getDateFromString:dictionary[@"date"]];
NSDate *date = [EQNUtility getDateFromString:dictionary[@"dt"]];
_date = date == nil ? [NSDate date] : date;
_difference = [dictionary[@"difference"] integerValue];
_intensity = [dictionary[@"magnitude"] integerValue];
_message = dictionary[@"msg"];
_intensity = [dictionary[@"ma"] integerValue];
}
return self;
}
@@ -34,24 +31,18 @@
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.address forKey:@"address"];
[encoder encodeObject:self.date forKey:@"date"];
[encoder encodeInteger:self.difference forKey:@"difference"];
[encoder encodeObject:self.coordinate forKey:@"coordinate"];
[encoder encodeInteger:self.intensity forKey:@"intensity"];
[encoder encodeObject:self.message forKey:@"message"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self) {
self.address = [decoder decodeObjectForKey:@"address"];
self.date = [decoder decodeObjectForKey:@"date"];
self.difference = [decoder decodeIntegerForKey:@"difference"];
self.coordinate = [decoder decodeObjectForKey:@"coordinate"];
self.intensity = [decoder decodeIntegerForKey:@"intensity"];
self.message = [decoder decodeObjectForKey:@"message"];
}
return self;
}
+2 -6
View File
@@ -15,20 +15,16 @@ NS_ASSUME_NONNULL_BEGIN
@interface EQNUser : NSObject
@property (nonatomic, strong, nullable) NSString *tokenUser;
@property (nonatomic, strong, nullable) NSString *user_ID;
@property (nonatomic, strong, nullable) CLLocation *lastPosition;
@property (nonatomic, assign) CLLocationDistance distanza;
@property (nonatomic, assign) BOOL monitorOn;
@property (nonatomic, assign) BOOL inCarica;
@property (nonatomic, assign) BOOL registrato;
+ (instancetype)defaultUser;
- (void)inviaPosizioneServer;
- (void)saveUserInfo;
- (void)removeUser;
- (void)verificaRegistrazione;
- (void)retryUserRegistration;
- (void)registerUserIfNeededWithFirebaseToken:(NSString *)firebaseToken;
- (void)downloadOfferTimeRemainingWithCompletion:(timeRemainingCompletion)completionHandler;
@end
+121 -81
View File
@@ -8,12 +8,16 @@
#import "EQNUser.h"
#import "Costanti.h"
#import "EQNAccelerometroManager.h"
#import "ServerRequest.h"
#import "EQNAccelerometroManager.h"
#import "EQNGeneratoreURLServer.h"
#import "EQNUtility.h"
#import "EQNManager.h"
@interface EQNUser ()
@property (strong, nonatomic) NSString *currentFirebaseToken;
@property (nonatomic) BOOL registrationInProgress;
@end
@implementation EQNUser
@@ -35,109 +39,61 @@
{
self = [super init];
if (self) {
_tokenUser = [[NSUserDefaults standardUserDefaults] objectForKey:EQNUserDefaultUserFirebaseToken];
id savedUserId = [[NSUserDefaults standardUserDefaults] objectForKey:EQNUserDefaultUserId];
_user_ID = [self convertUserIdIntoString:savedUserId];
self.registrationInProgress = NO;
self.user_ID = EQNUserData.sharedData.userId;
self.lastPosition = EQNUserData.sharedData.lastLocation;
NSArray *lastPosArray = [EQNUtility loadArrayOfClass:[CLLocation class] fromUserDefaultsForKey:EQNUserDefaultLastLocation];
if (lastPosArray.count > 0) {
_lastPosition = [lastPosArray lastObject];
}
_registrato = NO;
if (_user_ID) {
_registrato = YES;
}
[[EQNAccelerometroManager sharedInstance] addObserver:(id)self
forKeyPath:@"currentLocation"
options:NSKeyValueObservingOptionNew
context:nil];
[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
[self registerForLocationUpdates];
}
return self;
}
#pragma mark - Accessories
- (void)setTokenUser:(NSString *)tokenUser
{
// token could be retrieved after some times
// thanks to this, we force the server registration when the token is received
_tokenUser = tokenUser;
[[NSUserDefaults standardUserDefaults] setObject:tokenUser forKey:EQNUserDefaultUserFirebaseToken];
[self verificaRegistrazione];
}
#pragma mark - Public
- (void)saveUserInfo
{
[[NSUserDefaults standardUserDefaults] setObject:self.tokenUser forKey:EQNUserDefaultUserFirebaseToken];
[[NSUserDefaults standardUserDefaults] setObject:self.user_ID forKey:EQNUserDefaultUserId];
if (self.lastPosition) {
NSArray *lastPosiArray = @[self.lastPosition];
[EQNUtility storeArray:lastPosiArray toUserDefaultForKey:EQNUserDefaultLastLocation];
}
[EQNUserData.sharedData saveFirebaseToken:self.currentFirebaseToken];
[EQNUserData.sharedData saveUserId:self.user_ID];
[EQNUserData.sharedData saveLastLocation:self.lastPosition];
}
- (void)removeUser
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:EQNUserDefaultUserFirebaseToken];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:EQNUserDefaultUserId];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:EQNUserDefaultLastLocation];
[[NSUserDefaults standardUserDefaults] synchronize];
self.tokenUser = nil;
self.currentFirebaseToken = nil;
[EQNUserData.sharedData removeAllData];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
#pragma mark - Public
- (void)retryUserRegistration
{
if ([keyPath isEqualToString:@"currentLocation"]) {
// do some stuff
if (self.lastPosition) {
self.distanza = [self.lastPosition distanceFromLocation:[EQNAccelerometroManager sharedInstance].currentLocation];
}
self.lastPosition = [EQNAccelerometroManager sharedInstance].currentLocation;
[self verificaRegistrazione];
if (![EQNManager defaultManager].isBackground)
[[EQNAccelerometroManager sharedInstance] stopUpdatingLocation];
}
[self registerUserIfNeededWithFirebaseToken:self.currentFirebaseToken];
}
- (void)verificaRegistrazione
- (void)registerUserIfNeededWithFirebaseToken:(NSString *)firebaseToken
{
if (!self.user_ID && self.tokenUser) {
[self inviaregistrazioneServer:self.tokenUser withPosition:self.lastPosition];
self.currentFirebaseToken = firebaseToken;
NSString *previousFirebaseToken = EQNUserData.sharedData.firebaseToken;
// dobbiamo effettuare la registrazione se:
// - userId non disponibile (si tratta di prima registrazione)
// - i due token di Firebase sono diversi (il token è stato aggiornato)
if (!self.user_ID) {
// prima registrazione dell'utente
NSLog(@"[EQNUser] perform first registration");
[self performServerRegistrationWithFirebaseToken:firebaseToken existingUserId:nil];
} else if (![previousFirebaseToken isEqualToString:firebaseToken]) {
// token cambiato, effettuiamo una nuova registrazione
NSLog(@"[EQNUser] firebase token is changed, update registration");
[self performServerRegistrationWithFirebaseToken:firebaseToken existingUserId:self.user_ID];
} else {
[self inviaPosizioneServer];
// non serve la registrazione, monitorniamo la posizione
NSLog(@"[EQNUser] user already registered, start location update");
[self registerForLocationUpdates];
}
}
- (void)inviaregistrazioneServer:(NSString *)token withPosition:(CLLocation *)location
{
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[EQNGeneratoreURLServer urlRegistrazione] richiesta:EQNTipoChiamataRegistrazione success:^(id result) {
self.user_ID = [self convertUserIdIntoString:result];
[self saveUserInfo];
} failure:^(NSError *errore) {
NSLog(@"USER_ID Error %@", errore);
[[NSNotificationCenter defaultCenter] postNotificationName:EQNServerRegistrationDidFailNotification object:nil];
}];
}
- (void)inviaPosizioneServer
{
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[EQNGeneratoreURLServer urlPosizione] richiesta:EQNTipoChiamataPosizione success:^(id result) {
NSLog(@"[EQNUtility] Position saved on server");
[self saveUserInfo];
} failure:^(NSError *error) {
NSLog(@"[EQNUtility] Unable to save position. Error: %@", error.localizedDescription);
}];
}
- (void)downloadOfferTimeRemainingWithCompletion:(_Nonnull timeRemainingCompletion)completionHandler
{
NSURL *url = [EQNGeneratoreURLServer urlDownloadOfferTimeRemaining];
@@ -157,8 +113,92 @@
}];
}
#pragma mark - Network
- (void)performServerRegistrationWithFirebaseToken:(NSString *)token existingUserId:(NSString *)userId
{
if (self.registrationInProgress) {
NSLog(@"[EQNUser] Registration already in progress");
return;
}
self.registrationInProgress = YES;
NSURL *url = [EQNGeneratoreURLServer urlRegistrazioneFirebaseToken:token existingUserId:userId];
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataRegistrazione success:^(id result) {
NSLog(@"[EQNUser] User registration completed");
// store userId
self.registrationInProgress = NO;
self.user_ID = [self convertUserIdIntoString:result];
[self saveUserInfo];
// inviamo, se già disponibile, la posizione al server
[self performServerSendLocation];
} failure:^(NSError *errore) {
NSLog(@"[EQNUser] Unable to perform user registration: %@", errore);
self.registrationInProgress = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:EQNServerRegistrationDidFailNotification object:nil];
}];
}
- (void)performServerSendLocation
{
if (!self.user_ID) {
NSLog(@"[EQNUser] User id not available");
return;
}
NSURL *url = [EQNGeneratoreURLServer urlPosizione];
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataPosizione success:^(id result) {
if ([result isKindOfClass:[NSString class]]) {
NSString *stringResult = (NSString *)result;
if ([stringResult isEqualToString:@"reg"]) {
// l'utente non è stato trovato sul server, ri-eseguiamo la registrazione
NSLog(@"[EQNUser] User not found, retry registration");
[self retryUserRegistration];
} else {
NSLog(@"[EQNUser] Position saved on server");
}
}
[self saveUserInfo];
} failure:^(NSError *error) {
NSLog(@"[EQNUser] Unable to save position. Error: %@", error.localizedDescription);
}];
}
#pragma mark - Private
- (void)registerForLocationUpdates
{
[[EQNAccelerometroManager sharedInstance] addObserver:(id)self
forKeyPath:@"currentLocation"
options:NSKeyValueObservingOptionNew
context:nil];
[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"currentLocation"]) {
NSLog(@"[EQNUser] currentLocation changed");
// do some stuff
if (self.lastPosition) {
self.distanza = [self.lastPosition distanceFromLocation:[EQNAccelerometroManager sharedInstance].currentLocation];
}
self.lastPosition = [EQNAccelerometroManager sharedInstance].currentLocation;
[self performServerSendLocation];
if (![EQNManager defaultManager].isBackground) {
[[EQNAccelerometroManager sharedInstance] stopUpdatingLocation];
}
}
}
#pragma mark - Helpers
/// user_id saved as a Number, but is used as a NSString
- (NSString *)convertUserIdIntoString:(id)userId
{
@@ -0,0 +1,93 @@
//
// EQNUserData.swift
// Earthquake Network
//
// Created by Andrea Busi on 04/11/22.
// Copyright © 2022 Earthquake Network. All rights reserved.
//
import Foundation
import CoreLocation
@objc class EQNUserData: NSObject {
@objc(sharedData) static let shared = EQNUserData()
// MARK: - Public
@objc
var isFirstStart: Bool {
firebaseToken == nil
}
// MARK: - Firebase Token
@objc
var firebaseToken: String? {
UserDefaults.standard.object(forKey: EQNUserDefaultUserFirebaseToken) as? String
}
@objc
func saveFirebaseToken(_ token: String?) {
if let token = token {
UserDefaults.standard.set(token, forKey: EQNUserDefaultUserFirebaseToken)
} else {
UserDefaults.standard.removeObject(forKey: EQNUserDefaultUserFirebaseToken)
}
}
// MARK: - User id
@objc
var userId: String? {
let userId = UserDefaults.standard.object(forKey: EQNUserDefaultUserId)
// nel corso delle versioni l'id è stato salvato in diversi modi
// per evitare problemi, cerchiamo di convertirlo in modi diveri
if let userId = userId as? String {
return userId
} else if let userId = userId as? Int {
return "\(userId)"
} else if let userId = userId as? NSNumber {
return userId.stringValue
}
return nil
}
@objc
func saveUserId(_ userId: String) {
UserDefaults.standard.set(userId, forKey: EQNUserDefaultUserId)
}
// MARK: - Last location
@objc
var lastLocation: CLLocation? {
guard let encodedLocation = UserDefaults.standard.object(forKey: EQNUserDefaultLastLocation) as? Data else {
return nil
}
let location = try? NSKeyedUnarchiver.unarchivedObject(ofClass: CLLocation.self, from: encodedLocation)
return location
}
@objc
func saveLastLocation(_ location: CLLocation) {
guard let encodedLocation = try? NSKeyedArchiver.archivedData(withRootObject: location, requiringSecureCoding: false) else {
return
}
UserDefaults.standard.set(encodedLocation, forKey: EQNUserDefaultLastLocation)
}
// MARK: - Public
@objc
func removeAllData() {
UserDefaults.standard.removeObject(forKey: EQNUserDefaultUserFirebaseToken)
UserDefaults.standard.removeObject(forKey: EQNUserDefaultUserId)
UserDefaults.standard.removeObject(forKey: EQNUserDefaultLastLocation)
}
}
@@ -8,6 +8,7 @@
import Foundation
import MapKit
import Shogun
@objc
@@ -15,36 +16,63 @@ class EQNMapAnnotationUserReport: NSObject, MKAnnotation {
@objc var coordinate: CLLocationCoordinate2D
@objc var title: String?
@objc var image: UIImage?
@objc var subtitle: String?
@objc var timeDifference: String?
private let magnitude: Int
var report: EQNSegnalazione?
// MARK: - Init
convenience init(report: EQNSegnalazione) {
let coordinate = CLLocationCoordinate2D(latitude: report.coordinate.coordinate.latitude, longitude: report.coordinate.coordinate.longitude)
let magnitude = report.intensity.rawValue
let title = EQNUtility.formattedTimeDifference(from: report.date)
let coordinate = report.coordinate.coordinate
let magnitude = report.intensity
self.init(title: title, coordinate: coordinate, magnitude: magnitude)
self.init(magnitude: magnitude, coordinate: coordinate)
self.report = report
self.timeDifference = EQNUtility.formattedTimeDifference(from: report.date)
}
@objc init(title: String, coordinate: CLLocationCoordinate2D, magnitude: Int) {
self.title = title
self.image = Self.image(for: magnitude)
@objc init(
magnitude: Int,
coordinate: CLLocationCoordinate2D
) {
self.magnitude = magnitude
self.coordinate = coordinate
super.init()
self.title = Self.title(for: magnitude)
self.subtitle = Self.subtitle(for: magnitude)
}
// MARK: - Public
// MARK: - Helper
private static func image(for magnitute: Int) -> UIImage? {
switch magnitute {
case 1: return UIImage(named: "star_report_green")
case 2: return UIImage(named: "star_report_yellow")
case 3: return UIImage(named: "star_report_red")
default: return nil
@objc(imageWithHeight:)
func image(
with height: CGFloat
) -> UIImage? {
guard let color = UIColor(named: "Mercalli \(magnitude)") else {
print("[EQNMapAnnotationUserReport] Unable to get a color for magnitude: \(magnitude)")
return nil
}
return UIImage.circle(diameter: height, color: color, borderWidth: 1.0, borderColor: .black)
}
private static func title(for magnitude: Int) -> String {
let grade = magnitude / 10
let roman = grade.romanNumber()
return String(format: NSLocalizedString("mercalli_intensity", comment: ""), roman)
}
private static func subtitle(for magnitude: Int) -> String {
let grade = magnitude / 10
let roman = grade.romanNumber()
let string = NSLocalizedString("mercalli_\(roman)", comment: "")
// strings are in the format like "II - Appena percepito"
// so we are going to remove the "II" from the string
let components = string.components(separatedBy: " - ")
if components.count > 1 {
return components[1]
}
return string
}
}
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xCA",
"green" : "0xCA",
"red" : "0xCA"
"blue" : "0.792",
"green" : "0.792",
"red" : "0.792"
}
},
"idiom" : "universal"
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xAA",
"green" : "0xAA",
"red" : "0xAA"
"blue" : "0.667",
"green" : "0.667",
"red" : "0.667"
}
},
"idiom" : "universal"
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x51",
"green" : "0x90",
"red" : "0x00"
"blue" : "0.318",
"green" : "0.565",
"red" : "0.000"
}
},
"idiom" : "universal"
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "250",
"green" : "192",
"red" : "129"
"blue" : "0.980",
"green" : "0.753",
"red" : "0.506"
}
},
"idiom" : "universal"
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.149",
"green" : "0.153",
"red" : "0.792"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.329",
"green" : "0.067",
"red" : "0.867"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.529",
"green" : "0.024",
"red" : "0.792"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.953",
"green" : "0.937",
"red" : "0.929"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.851",
"green" : "0.769",
"red" : "0.749"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.890",
"green" : "0.843",
"red" : "0.671"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.565",
"green" : "0.784",
"red" : "0.596"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.137",
"green" : "0.925",
"red" : "0.961"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.059",
"green" : "0.765",
"red" : "0.937"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.145",
"green" : "0.529",
"red" : "0.867"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.141",
"green" : "0.329",
"red" : "0.820"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -5,9 +5,9 @@
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xCE",
"red" : "0xA3"
"blue" : "1.000",
"green" : "0.808",
"red" : "0.639"
}
},
"idiom" : "universal"
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.149",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "eq_icon_transparent.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Some files were not shown because too many files have changed in this diff Show More