Compare commits

..

32 Commits

Author SHA1 Message Date
Andrea Busi 9bf6b75dac release: Increase version for release 2025-05-02 10:31:57 +02:00
Andrea Busi 69b83e9944 feat: Add settings to disable alert sound for mild quakes
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/89
2025-05-02 10:31:57 +02:00
Andrea Busi 5061e36a45 refactor: Reorganize some code 2025-05-02 10:31:57 +02:00
Andrea Busi 8919f3c08f refactor: Remove some unused @objc 2025-05-02 10:31:57 +02:00
Andrea Busi 9cf9ef8a64 feat: Use cache endpoint and round values to use server cache
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/90
2025-05-02 10:31:57 +02:00
Andrea Busi 9ee3a478f0 dependency: Update Firebase 2025-05-02 10:31:57 +02:00
Andrea Busi 8744595b56 release: Increase version for release 2025-04-25 22:32:54 +02:00
Andrea Busi fa05d6b5c4 fix: Rework init to avoid crash when server returns null values 2025-04-25 22:31:44 +02:00
Andrea Busi 471ccc9e4a release: Increase version for release 2025-04-24 12:49:25 +02:00
Andrea Busi 55de6f5ba0 refactor: Use project to set version and build number 2025-04-24 12:49:08 +02:00
Andrea Busi b12a9cc476 fix: Solve wrong sort in subscriptions 2025-04-24 12:46:53 +02:00
Andrea Busi a2f740b0a8 release: Increase version for release 2025-03-07 17:57:54 +01:00
Andrea Busi 9cf93e652b fix: Reset cell informations 2025-03-07 15:38:51 +01:00
Andrea Busi 2d23056ba8 release: Increase version for release 2025-03-07 14:10:47 +01:00
Andrea Busi cb6ecca774 refactor: Hide description label when taking screenshot in intensity map 2025-03-07 14:10:47 +01:00
Andrea Busi 96286a49f6 feat: Upgrade Xcode project version 2025-03-07 14:00:28 +01:00
Andrea Busi 481e8a28c0 fix: Solve crash with a single shakemape curve 2025-03-07 14:00:16 +01:00
Andrea Busi 286a4062f5 release: Increase version for release 2025-03-07 11:16:35 +01:00
Andrea Busi 01a8ad7419 fix: Solve wrong calculation in scroll indicator 2025-03-07 11:16:07 +01:00
Andrea Busi 6e97e9bd2c release: Increase version for release 2025-03-07 09:56:03 +01:00
Andrea Busi af6e94efcb fix: Solve non working intensity map in minimal cell 2025-03-07 09:45:45 +01:00
Andrea Busi 5387758449 fix: Select yearly as default subscription 2025-03-07 09:42:26 +01:00
Andrea Busi 054603b42d release: Increase version for release 2025-03-06 17:57:41 +01:00
Andrea Busi caf0e3b7cc feat: Add new minimal card in seismics list
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/87
2025-03-06 17:57:07 +01:00
Andrea Busi 4c35c38cc5 refactor: Move card informations to AppPreferences 2025-03-06 15:49:26 +01:00
Andrea Busi 521254c8c1 refactor: Remove unused constant 2025-03-06 13:01:20 +01:00
Andrea Busi 78a1710584 refactor: Replace deprecated methods 2025-03-06 10:59:36 +01:00
Andrea Busi b2a54a544c feat: Update layout in Seismic List section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/86
2025-03-06 10:45:02 +01:00
Andrea Busi 0f5ad24744 feat: Update layout in Subscriptions section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/85
2025-03-06 10:19:14 +01:00
Andrea Busi 0296cd50cd feat: Update layout in Reports section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/84
2025-03-06 10:18:44 +01:00
Andrea Busi 7551988b4e feat: Update layout in Alerts section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/83
2025-03-06 10:17:42 +01:00
Andrea Busi 5edcaaad99 feat: Change layout for base container card (center title) 2025-03-06 10:17:08 +01:00
45 changed files with 950 additions and 422 deletions
+8
View File
@@ -1,5 +1,13 @@
# Changelog
## 5.10
- Usato endpoint cache per distquake_download_areacheck
- Aggiunta impostazione per non riprodurre suono notifiche per simsi deboli
## Versione 5.9.1
- Corretto ordinamento in sottoscrizioni attive (prima Top10k)
- Modificato parsing per risolvere crash con valori nulli
## Versione 5.9
- Aggiunta barra laterale in lista sismi
- Aggiunto filtro "sismi percepiti"
@@ -51,6 +51,43 @@ class NotificationService: UNNotificationServiceExtension {
bestAttemptContent.sound = UNNotificationSound(named: Self.EQNSoundNotification)
}
// evaluate intensity and get proper string to display
guard let latitude = userInfo.double(forKey: "latitude"),
let longitude = userInfo.double(forKey: "longitude"),
let peak = userInfo.double(forKey: "peak") else {
print("[NotificationService] Unable to get base info for intensity calculation")
return
}
let magnitude = userInfo.double(forKey: "mag") ?? 0
let location = CLLocation(latitude: latitude, longitude: longitude)
guard let distance = EQNUserData.shared.lastLocation?.distance(from: location) else {
print("[NotificationService] Unable to calculate distance or get last location")
return
}
let distanceKm = distance / 1_000
// If the shake is mild, user can disale sound and use default notification sound
// This logic is done here and not with the rest because distance and other informations are needed
let mildQuakeSoundDisabled = UserDefaults.appGroup?.bool(forKey: UserDefaults.AllertaSismicaSuonoDisabilitatoSismaDebole) ?? true
let isMildQuake = isMildQuake(magnitude: magnitude, distance: distanceKm)
if isMildQuake && mildQuakeSoundDisabled {
bestAttemptContent.sound = UNNotificationSound.default
}
let intensita = peak * exp(-distanceKm/peak/250)
let stringSuffix = if intensita < 0.004 {
"no_shaking"
} else if intensita < 0.30 {
"mild"
} else if intensita < 0.70 {
"moderate"
} else {
"strong"
}
bestAttemptContent.body = "alert_intensity_\(stringSuffix)".localized
let intensity = userInfo.integer(forKey: "intensity")
switch intensity {
case 0:
@@ -63,35 +100,6 @@ class NotificationService: UNNotificationServiceExtension {
break
}
// evaluate intensity and get proper string to display
guard let latitude = userInfo.double(forKey: "latitude"),
let longitude = userInfo.double(forKey: "longitude"),
let peak = userInfo.double(forKey: "peak") else {
print("[NotificationService] Unable to get base info for intensity calculation")
return
}
let location = CLLocation(latitude: latitude, longitude: longitude)
guard let distance = EQNUserData.shared.lastLocation?.distance(from: location) else {
print("[NotificationService] Unable to calculate distance or get last location")
return
}
let distanceKm = distance / 1_000
let intensita = peak * exp(-distanceKm/peak/250)
let stringSuffix: String
if intensita < 0.004 {
stringSuffix = "no_shaking"
} else if intensita < 0.30 {
stringSuffix = "mild"
} else if intensita < 0.70 {
stringSuffix = "moderate"
} else {
stringSuffix = "strong"
}
bestAttemptContent.body = "alert_intensity_\(stringSuffix)".localized
case "manual":
// there are 12 levels, so a customized icon doesn't make sense
// use a generic warning icon instead
@@ -159,7 +167,7 @@ class NotificationService: UNNotificationServiceExtension {
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
// we need to add a delay before invoking the completion, otherwise the notification will not be removed
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion()
@@ -167,6 +175,20 @@ class NotificationService: UNNotificationServiceExtension {
}
}
private func isMildQuake(
magnitude: Double,
distance: Double
) -> Bool {
var intensity_at_location: Double = 0
if distance > 0 {
let R: Double = 6371
let eq_depth: Double = 10.0
let hyp_distance = sqrt(pow(eq_depth, 2) + 4 * R * (R - eq_depth) * pow(sin(distance / (2 * R)), 2))
intensity_at_location = -2.15 * log10(hyp_distance) + 1.0 * magnitude + 2.31
}
return intensity_at_location < 3
}
// MARK: - Helpers
private func manualIconName(for provider: String, color: String) -> String {
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
@@ -18,6 +18,8 @@
6514FF6D2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */; };
65172F532C25C496006D2A5C /* EQNSeismicAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */; };
651901B925F5358700CAFF20 /* EQNMapAnnotationSeismic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */; };
652247242D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */; };
652247262D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */; };
6525A82625E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */; };
652A3C6B2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652A3C6A2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift */; };
65355FFF25F38D3300BB57D2 /* SegnalazioniMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65355FFE25F38D3300BB57D2 /* SegnalazioniMapViewController.swift */; };
@@ -320,6 +322,8 @@
6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapPinStyle.swift; sourceTree = "<group>"; };
65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNSeismicAnnotationView.swift; sourceTree = "<group>"; };
651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNMapAnnotationSeismic.swift; sourceTree = "<group>"; };
652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkMinimalTableViewCell.swift; sourceTree = "<group>"; };
652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkBaseTableViewCell.swift; sourceTree = "<group>"; };
6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkAdvertiseTableViewCell.swift; sourceTree = "<group>"; };
652A3C6A2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgrounPositionDebugViewController.swift; sourceTree = "<group>"; };
65355FFE25F38D3300BB57D2 /* SegnalazioniMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegnalazioniMapViewController.swift; sourceTree = "<group>"; };
@@ -949,7 +953,9 @@
isa = PBXGroup;
children = (
65DBFB7D25E2CB020041CBA6 /* Ad templates */,
652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */,
DC28142F2519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift */,
652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */,
6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */,
);
path = Cells;
@@ -1348,7 +1354,6 @@
};
};
buildConfigurationList = 8CBD3DBD2149B9AD0070C963 /* Build configuration list for PBXProject "Earthquake Network" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -1372,6 +1377,7 @@
65B16E282BDFB39B0020527E /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */,
65E6AC6C2C2DB3B60073F8FE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 8CBD3DC32149B9AD0070C963 /* Products */;
projectDirPath = "";
projectReferences = (
@@ -1595,8 +1601,10 @@
650247122A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift in Sources */,
65CB83432915720400EE1E35 /* EQNUserData.swift in Sources */,
654D18C425F93C0600BB6DB0 /* PasquakesMapViewController.swift in Sources */,
652247242D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift in Sources */,
8C483CBC21FDACE500259FD2 /* EQNInAppProducts.swift in Sources */,
8C483CB821FDACD300259FD2 /* IAPHelper.swift in Sources */,
652247262D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift in Sources */,
6562C80725FFA6B100C85273 /* SeismicNetworkViewModel.swift in Sources */,
DCDE0BD924E58CCE00209778 /* EQNMainTabBarController.m in Sources */,
8C4E344B2152EE5B008B0D2A /* EQNGeneratoreURLServer.m in Sources */,
@@ -1810,7 +1818,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 146;
CURRENT_PROJECT_VERSION = "$(inherited)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -1827,7 +1835,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.9.0;
MARKETING_VERSION = "$(inherited)";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice;
@@ -1852,7 +1860,7 @@
CODE_SIGN_IDENTITY = "Apple Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 146;
CURRENT_PROJECT_VERSION = "$(inherited)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC;
GENERATE_INFOPLIST_FILE = YES;
@@ -1865,7 +1873,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.9.0;
MARKETING_VERSION = "$(inherited)";
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1915,6 +1923,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 154;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = WJA4MR4CPC;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -1935,6 +1944,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 5.10.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -1977,6 +1987,7 @@
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 154;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = WJA4MR4CPC;
ENABLE_NS_ASSERTIONS = NO;
@@ -1991,6 +2002,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 5.10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -2006,7 +2018,7 @@
CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 146;
CURRENT_PROJECT_VERSION = "$(inherited)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = WJA4MR4CPC;
@@ -2018,7 +2030,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 5.9.0;
MARKETING_VERSION = "$(inherited)";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -2094,7 +2106,7 @@
CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements";
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 146;
CURRENT_PROJECT_VERSION = "$(inherited)";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = WJA4MR4CPC;
ENABLE_MODULE_VERIFIER = YES;
@@ -2105,7 +2117,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 5.9.0;
MARKETING_VERSION = "$(inherited)";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -2179,7 +2191,7 @@
CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 146;
CURRENT_PROJECT_VERSION = "$(inherited)";
DEVELOPMENT_TEAM = WJA4MR4CPC;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -2194,7 +2206,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.9.0;
MARKETING_VERSION = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - Development";
@@ -2213,7 +2225,7 @@
CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 146;
CURRENT_PROJECT_VERSION = "$(inherited)";
DEVELOPMENT_TEAM = WJA4MR4CPC;
GCC_PREPROCESSOR_DEFINITIONS = (
"ADS_ENABLED=0",
@@ -2226,7 +2238,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.9.0;
MARKETING_VERSION = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - AppStore";
@@ -6,8 +6,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "194a6706acbd25e4ef639bcaddea16e8758a3e27",
"version" : "1.2024011602.0"
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
@@ -42,8 +42,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
"state" : {
"revision" : "075679d6b25b28f4cb167f1d7769b58fb556fb30",
"version" : "11.8.0"
"revision" : "fbd463894af94d90eb4d6a4e54080459a8179519",
"version" : "11.12.0"
}
},
{
@@ -51,8 +51,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "be0881ff728eca210ccb628092af400c086abda3",
"version" : "11.7.0"
"revision" : "f7460ea630bddf172115c28493ae8b3798d95ce3",
"version" : "11.12.0"
}
},
{
@@ -78,8 +78,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "f56d8fc3162de9a498377c7b6cea43431f4f5083",
"version" : "1.65.1"
"revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
"version" : "1.69.0"
}
},
{
@@ -96,8 +96,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "2d12673670417654f08f5f90fdd62926dc3a2648",
"version" : "100.0.0"
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
@@ -17,6 +17,7 @@ extension UserDefaults {
// Impostazioni della sezione `Allerta in tempo reale`
static let AllertaSismicaAbilitato = "NOTIFICHE_ALLERA_SISMICA_ABILITATO"
static let AllertaSismicaSuonoDisabilitatoSismaDebole = "NOTIFICHE_ALLERA_SISMICA_SUONO_DISABILITATO_SISMA_DEBOLE"
static let AllertaSismicaCriticalAlerts = "NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS"
static let AllertaSismicaSismiDaNotificare = "NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE"
static let AllertaSismicaRaggioSismiLievi = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI"
@@ -58,6 +59,10 @@ extension UserDefaults {
static let AlertsShowCardOptions = "EQNetwork.AlertsShowAllCards"
/// Indica lo stile di pin da visualizzare nelle mappe
static let MapPinStyle = "EQNetwork.MapPinStyle"
/// Indica le informazioni da visualizzare nelle card `small` e `full` nella Lista Sismi
static let SeismicNetworksCardInformations = "EQNetwork.SeismicInformations";
/// Indica la tipologia di card da visualizzare nella Lista Sismi
static let SeismicNetworksCardStyle = "EQNetwork.SeismicNetworksCardStyle"
// Migrazioni
static let AppMigrationV5_3 = "EQNUserDefaultMigrationV5_3"
@@ -65,6 +70,7 @@ extension UserDefaults {
static let AppMigrationV5_8 = "EQNUserDefaultMigrationV5_8"
static let AppMigrationV5_8_2 = "EQNUserDefaultMigrationV5_8_2"
static let AppMigrationV5_9 = "EQNUserDefaultMigrationV5_9"
static let AppMigrationV5_10 = "EQNUserDefaultMigrationV5_10"
static let SettingsSeismicNetworkNotificationMigrationV5_8 = "EQNUserDefaultSettingsSeismicNetworkNotificationMigrationV5_8"
static let SettingsUserReportNotificationMigrationV5_8 = "EQNUserDefaultSettingsUserReportNotificationMigrationV5_8"
@@ -100,8 +100,10 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
- (void)setupUI
{
self.title = [NSLocalizedString(@"tab_network", nil) capitalizedString];
self.tableView.estimatedRowHeight = 200.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets;
[self.tableView registerClass:[AlertsSmartphoneNetworkTableViewCell class] forCellReuseIdentifier:@"SmartphoneNetworkCell"];
[self.tableView registerClass:[AlertsPriorityServiceTableViewCell class] forCellReuseIdentifier:@"PriorityCell"];
[self.tableView registerClass:[AlertsNoLocationTableViewCell class] forCellReuseIdentifier:@"NoLocationCell"];
@@ -147,7 +149,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
[self.tableItems addObject:@(AllerteTableRowReteSmartphone)];
}
// check if locations is enabled
if (CLLocationManager.authorizationStatus != kCLAuthorizationStatusAuthorizedAlways) {
if (EQNUserData.sharedData.locationAuthorizationStatus != kCLAuthorizationStatusAuthorizedAlways) {
[self.tableItems addObject:@(AllerteTableRowLocationPermission)];
}
@@ -233,7 +235,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
- (void)actionTestPush
{
CLAuthorizationStatus status = CLLocationManager.authorizationStatus;
CLAuthorizationStatus status = EQNUserData.sharedData.locationAuthorizationStatus;
if (status != kCLAuthorizationStatusAuthorizedAlways && status != kCLAuthorizationStatusAuthorizedWhenInUse) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"attention", nil)
message:NSLocalizedString(@"liveview_unknown_location", nil)
@@ -271,7 +273,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
if (tableRow == AllerteTableRowLocationPermission) {
AlertsNoLocationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NoLocationCell" forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell updateWith:CLLocationManager.authorizationStatus];
[cell updateWith:EQNUserData.sharedData.locationAuthorizationStatus];
return cell;
} else if (tableRow == AllerteTableRowSismiRilevati) {
@@ -7,7 +7,7 @@
//
import UIKit
import Shogun
@objc
class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell {
@@ -22,7 +22,7 @@ class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.Colors.green
label.font = .preferredFont(forTextStyle: .largeTitle)
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
label.textAlignment = .center
return label
}()
@@ -39,7 +39,7 @@ class SubscriptionDetailsViewController: UITableViewController {
products: [EQNInAppProducts]
) {
self.products = products
self.selectedProduct = products.first(where: { $0.plan == .monthly }) ?? products.first!
self.selectedProduct = products.first(where: { $0.plan == .yearly }) ?? products.first!
super.init(style: .plain)
}
@@ -67,6 +67,7 @@ class SubscriptionDetailsViewController: UITableViewController {
tableView.estimatedRowHeight = 2000.0
tableView.separatorStyle = .none
tableView.backgroundColor = .systemGroupedBackground
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
tableView.registerCell(for: SubscriptionDetailsTableViewCell.self)
}
@@ -18,6 +18,7 @@ class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .title2)
label.textColor = AppTheme.Colors.darkGray
label.textAlignment = .center
return label
}()
@@ -48,6 +49,7 @@ class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
headerTitleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
headerTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .cardPadding).isActive = true
headerTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .cardPadding.negative).isActive = true
loadingActivityIndicator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .cardPadding.negative).isActive = true
loadingActivityIndicator.centerYAnchor.constraint(equalTo: headerTitleLabel.centerYAnchor).isActive = true
}
@@ -91,6 +91,11 @@ class SubscriptionsViewController: UITableViewController {
tableView.estimatedRowHeight = 600.0
tableView.separatorStyle = .none
tableView.backgroundColor = .systemGroupedBackground
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
if #available(iOS 15.0, *) {
// remove extra padding on top of each section header
tableView.sectionHeaderTopPadding = 0.0
}
tableView.registerCell(for: SubscriptionsActiveTableViewCell.self)
tableView.registerCell(for: SubscriptionsDescriptionTableViewCell.self)
tableView.registerCell(for: SubscriptionProductTableViewCell.self)
@@ -114,14 +119,21 @@ class SubscriptionsViewController: UITableViewController {
let isSubscription = product.isSubscription
return isPurchased && isSubscription
}.sorted { lProduct, rProduct in
// if user has more than one subscriptions,
// show first the Top10k
let lIs10k = lProduct.isTop100k
let rIs10k = rProduct.isTop100k
if lIs10k {
return lIs10k
// If user has more than one subscriptions,
// show first the Top10k.
let lIs10k = lProduct.isTop10k
let rIs10k = rProduct.isTop10k
// If left item is Top10k, order first
if lIs10k && !rIs10k {
return true
} else if !lProduct.isTop10k && rProduct.isTop10k {
// right product is Top10k, left no
return false
} else {
// both products Top10k or Top100k, keep existing order
return false
}
return rIs10k
}
self.productSubscribed = purchased.first
self.products = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
@@ -190,17 +202,22 @@ class SubscriptionsViewController: UITableViewController {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let tableSection = sections[section]
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
view.update(isLoading: isLoading, title: tableSection.sectionTitle)
return view
switch tableSection.sectionTitle {
case .some(let title):
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
view.update(isLoading: isLoading, title: title)
return view
case .none:
return nil
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let tableSection = sections[section]
if tableSection.sectionTitle != nil {
return 50
return switch tableSection.sectionTitle {
case .some: 50.0
case .none: 0.0
}
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@@ -16,7 +16,7 @@ class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
@objc var onTapMap: (() -> Void)?
@objc var onTapTelegram: (() -> Void)?
override var headerText: String { NSLocalizedString("tab_manual", comment: "") }
override var isHeaderVisible: Bool { false }
// MARK: - UI
@@ -24,7 +24,7 @@ class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.Colors.red
label.font = .preferredFont(forTextStyle: .largeTitle)
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
label.textAlignment = .center
label.numberOfLines = 0
return label
@@ -43,7 +43,7 @@ class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
private lazy var twitterButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(twitterButtonTapped(_:)))
button.imageView?.contentMode = .scaleAspectFit
button.setImage(.init(named: "twitter_icon"), for: .normal)
button.setImage(.init(named: "xcorp_icon"), for: .normal)
return button
}()
@@ -38,8 +38,10 @@
- (void)setupUI
{
self.title = [NSLocalizedString(@"tab_manual", nil) capitalizedString];
self.tableView.estimatedRowHeight = 500.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets;
[self.tableView registerClass:[SegnalazioniLast24HoursCell class] forCellReuseIdentifier:@"Last24HCell"];
[self.tableView registerClass:[SegnalazioniSendReportCell class] forCellReuseIdentifier:@"ReportEarthquakeCell"];
}
@@ -0,0 +1,127 @@
//
// SeismicNetworkBaseTableViewCell.swift
// Earthquake Network
//
// Created by Andrea Busi on 06/03/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import UIKit
import Shogun
protocol SeismicNetworkBaseTableViewCellDelegate: AnyObject {
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell)
}
class SeismicNetworkBaseTableViewCell: UITableViewCell {
/// Delegate
weak var delegate: SeismicNetworkBaseTableViewCellDelegate?
/// Available informations to display inside the cell
enum InformationType: Int {
case preliminary
case time
case distance
case coordinate
case population
case realtimeSmartphones
case reportUsers
case intensityMap
case buttons
}
// MARL: - Internal
static let DefaultButtonHeight: CGFloat = 34.0
static let VerticalSpacingDefault: CGFloat = 6.0
static let VerticalSpacingSmall: CGFloat = 2.0
static let HorizontalSpacingDefault: CGFloat = 4.0
// MARK: - UI Components
lazy var containerView: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.clipsToBounds = true
return view
}()
lazy var gradientView: UIImageView = {
// Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine
// creata ad-hoc con il gradiente desiderato.
// Le prove fatte utilizzando una view normale sono fallite perchè al momento di
// disegnare la view non abbiamo le misure corrette.
let view = UIImageView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleToFill
return view
}()
// MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
// MARK: - View Lifecycle
override func layoutSubviews() {
super.layoutSubviews()
containerView.eqn_applyShadowAndRoundedCorners()
gradientView.eqn_applyRoundedCorners()
}
// MARK: - Setup
func setupUI() {
selectionStyle = .default
backgroundColor = .clear
// container view
contentView.addSubview(containerView)
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0).isActive = true
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true
containerView.addSubview(gradientView)
gradientView.constraint(to: containerView)
}
func recreateUI() {
// remove all subviews and recreate the required components
containerView.subviews.forEach({ $0.removeFromSuperview() })
setupUI()
}
@discardableResult
func addSeparator(constraintTo: NSLayoutYAxisAnchor, constanst: CGFloat = 8.0) -> UIView {
let separator = UIView()
separator.translatesAutoresizingMaskIntoConstraints = false
separator.backgroundColor = .lightGray
containerView.addSubview(separator)
separator.topAnchor.constraint(equalTo: constraintTo, constant: constanst).isActive = true
separator.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
separator.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
return separator
}
}
@@ -0,0 +1,233 @@
//
// SeismicNetworkMinimalTableViewCell.swift
// Earthquake Network
//
// Created by Andrea Busi on 06/03/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import UIKit
import Shogun
class SeismicNetworkMinimalTableViewCell: SeismicNetworkBaseTableViewCell {
// MARK: - UI
private lazy var magnitudeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .largeTitle)
label.textColor = .red
label.textAlignment = .center
return label
}()
private lazy var placeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold)
label.numberOfLines = 3
return label
}()
private lazy var timeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 2
return label
}()
private lazy var distanceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 2
return label
}()
private lazy var smartphonesLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
label.numberOfLines = 2
return label
}()
private lazy var alertsLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
label.numberOfLines = 2
return label
}()
// MARK: - Internal
/// Seismic to show
private var seismic: EQNSisma?
private var isPushSelected = false
private var informationTypes: Set<InformationType> = []
// MARK: - Setup
override func setupUI() {
super.setupUI()
// this variable is used to keep track of the previous view, in order to attach proper constraints
var previousView: UIView = containerView
// preliminary banner on top of the cell
if informationTypes.contains(.preliminary) {
let preliminaryLabel = UILabel()
preliminaryLabel.translatesAutoresizingMaskIntoConstraints = false
preliminaryLabel.text = NSLocalizedString("official_prelimiary", comment: "").uppercased()
preliminaryLabel.textAlignment = .center
preliminaryLabel.backgroundColor = .red
preliminaryLabel.textColor = .yellow
containerView.addSubview(preliminaryLabel)
preliminaryLabel.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
preliminaryLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
preliminaryLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
preliminaryLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
previousView = preliminaryLabel
}
containerView.addSubview(magnitudeLabel)
containerView.addSubview(placeLabel)
let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor
let stackViewInformations = UIStackView(arrangedSubviews: [timeLabel, distanceLabel])
stackViewInformations.translatesAutoresizingMaskIntoConstraints = false
stackViewInformations.axis = .horizontal
stackViewInformations.distribution = .fillEqually
stackViewInformations.spacing = Self.HorizontalSpacingDefault
containerView.addSubview(stackViewInformations)
let stackViewRight = UIStackView(arrangedSubviews: [placeLabel, stackViewInformations])
stackViewRight.translatesAutoresizingMaskIntoConstraints = false
stackViewRight.axis = .vertical
stackViewRight.distribution = .equalSpacing
stackViewRight.spacing = Self.VerticalSpacingDefault
let stackViewMain = UIStackView(arrangedSubviews: [magnitudeLabel, stackViewRight])
stackViewMain.translatesAutoresizingMaskIntoConstraints = false
stackViewMain.axis = .horizontal
stackViewMain.distribution = .fill
stackViewMain.spacing = Self.HorizontalSpacingDefault
containerView.addSubview(stackViewMain)
stackViewMain.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
stackViewMain.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
stackViewMain.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
magnitudeLabel.widthAnchor.constraint(equalToConstant: 60.0).isActive = true
previousView = stackViewMain
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) || informationTypes.contains(.intensityMap) {
let separator = addSeparator(constraintTo: previousView.bottomAnchor, constanst: Self.VerticalSpacingDefault)
let stackViewReports = UIStackView()
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
stackViewReports.axis = .vertical
stackViewReports.distribution = .equalSpacing
stackViewReports.alignment = .center
stackViewReports.spacing = Self.VerticalSpacingDefault
if informationTypes.contains(.realtimeSmartphones) {
stackViewReports.addArrangedSubview(smartphonesLabel)
}
if informationTypes.contains(.reportUsers) {
stackViewReports.addArrangedSubview(alertsLabel)
}
if informationTypes.contains(.intensityMap) {
let buttonMap = EQNRoundedButton.make(title: "🎯 \(NSLocalizedString("shakemap", comment: ""))", target: self, action: #selector(intensityMapTapped(_:)))
stackViewReports.addArrangedSubview(buttonMap)
buttonMap.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
buttonMap.leadingAnchor.constraint(equalTo: stackViewReports.leadingAnchor).isActive = true
buttonMap.trailingAnchor.constraint(equalTo: stackViewReports.trailingAnchor).isActive = true
}
containerView.addSubview(stackViewReports)
stackViewReports.topAnchor.constraint(equalTo: separator.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
previousView = stackViewReports
}
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
containerView.eqn_applyShadowAndRoundedCorners()
gradientView.eqn_applyRoundedCorners()
}
private func updateUI() {
guard let seismic = seismic else { return }
let viewModel = SeismicNetworkMinimalViewModel(seismic: seismic)
gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
placeLabel.text = viewModel.place
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
magnitudeLabel.textColor = viewModel.colors.textColor
magnitudeLabel.text = viewModel.magnitude
timeLabel.text = "🕗 \(viewModel.time)"
distanceLabel.text = "📐 \(viewModel.distance)"
if !viewModel.smartphones.isEmpty {
smartphonesLabel.text = "🚨 \(viewModel.smartphones)"
}
if !viewModel.users.isEmpty {
alertsLabel.text = "⚠️ \(viewModel.users)"
}
}
// MARK: - Public
/// Configure the cell to display a seismic
/// - Parameters:
/// - seismic: Seismic to display
/// - type: Type of cell
/// - informations: Informations to show
public func configure(
with seismic: EQNSisma,
isPushSelected: Bool
) {
self.seismic = seismic
self.isPushSelected = isPushSelected
self.informationTypes.removeAll()
if seismic.preliminary.intValue > 0 {
informationTypes.insert(.preliminary)
}
if seismic.smartphoneNumber.intValue > 0 {
informationTypes.insert(.realtimeSmartphones)
}
if seismic.userNumber.intValue > 0 {
informationTypes.insert(.reportUsers)
}
if seismic.isoCode != "0" {
informationTypes.insert(.intensityMap)
}
recreateUI()
updateUI()
}
// MARK: - Actions
@objc private func intensityMapTapped(_ sender: Any) {
delegate?.seismicNetworkCellDidTapIntensityMapDetail(self)
}
}
@@ -11,32 +11,8 @@ import MapKit
import CoreLocation
import Shogun
protocol SeismicNetworkTableViewCellDelegate: AnyObject {
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell)
}
class SeismicNetworkTableViewCell: UITableViewCell {
static let Identifier = "SeismicNetworkTableViewCell"
/// Available informations to display inside the cell
enum InformationType: Int {
case preliminary
case time
case distance
case coordinate
case population
case realtimeSmartphones
case reportUsers
case intensityMap
case buttons
}
class SeismicNetworkTableViewCell: SeismicNetworkBaseTableViewCell {
/// Available cell type
enum DisplayType {
@@ -45,14 +21,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
/// Cell with map visible
case mapExpanded
}
/// Delegate
weak var delegate: SeismicNetworkTableViewCellDelegate?
// MARK: - Internal
private static let DefaultButtonHeight: CGFloat = 30.0
private static let DefaultVerticalSpacing: CGFloat = 6.0
/// Seismic to show
private var seismic: EQNSisma?
@@ -61,26 +29,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
private var isPushSelected = false
// MARK: - UI Components
private lazy var containerView: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.clipsToBounds = true
return view
}()
private lazy var gradientView: UIImageView = {
// Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine
// creata ad-hoc con il gradiente desiderato.
// Le prove fatte utilizzando una view normale sono fallite perchè al momento di
// disegnare la view non abbiamo le misure corrette.
let view = UIImageView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleToFill
return view
}()
private lazy var placeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
@@ -89,6 +38,14 @@ class SeismicNetworkTableViewCell: UITableViewCell {
return label
}()
private lazy var shareButton: UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "share_icon"), for: .normal)
button.addTarget(self, action: #selector(shareTapped(_:)), for: .touchUpInside)
return button
}()
private lazy var networkLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
@@ -199,20 +156,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
// MARK: - Setup
private func setupUI() {
selectionStyle = .default
backgroundColor = .clear
// container view
contentView.addSubview(containerView)
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0).isActive = true
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true
override func setupUI() {
super.setupUI()
containerView.addSubview(gradientView)
gradientView.constraint(to: containerView)
// this variable is used to keep track of the previous view, in order to attach proper constraints
var previousView: UIView = containerView
@@ -233,41 +179,27 @@ class SeismicNetworkTableViewCell: UITableViewCell {
previousView = preliminaryLabel
}
// title (bell icon, place label, seismic network and share button)
let titleComponentsHeight: CGFloat = 30.0
let stackViewTitle = UIStackView()
stackViewTitle.translatesAutoresizingMaskIntoConstraints = false
stackViewTitle.axis = .horizontal
stackViewTitle.distribution = .fill
stackViewTitle.alignment = .center
stackViewTitle.spacing = 4
let shareButton = UIButton(type: .custom)
shareButton.setImage(UIImage(named: "share_icon"), for: .normal)
shareButton.addTarget(self, action: #selector(shareTapped(_:)), for: .touchUpInside)
stackViewTitle.addArrangedSubview(placeLabel)
stackViewTitle.addArrangedSubview(shareButton)
placeLabel.setContentHuggingPriority(.init(200), for: .horizontal)
placeLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
shareButton.widthAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true
shareButton.widthAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
containerView.addSubview(placeLabel)
containerView.addSubview(shareButton)
let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor
containerView.addSubview(stackViewTitle)
stackViewTitle.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
stackViewTitle.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
stackViewTitle.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
placeLabel.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
placeLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
placeLabel.trailingAnchor.constraint(equalTo: shareButton.leadingAnchor, constant: .cardPadding.negative).isActive = true
shareButton.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
shareButton.centerYAnchor.constraint(equalTo: placeLabel.centerYAnchor).isActive = true
shareButton.heightAnchor.constraint(equalToConstant: 24.0).isActive = true
shareButton.heightAnchor.constraint(equalTo: shareButton.widthAnchor, multiplier: 1.0).isActive = true
let separator1 = addSeparator(constraintTo: stackViewTitle.bottomAnchor)
let separator1 = addSeparator(constraintTo: placeLabel.bottomAnchor)
let informationsLeadingAnchor = separator1.leadingAnchor
let informationsTrailingAnchor = separator1.trailingAnchor
// magnitude information
containerView.addSubview(magnitudeLabel)
magnitudeLabel.topAnchor.constraint(equalTo: separator1.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
magnitudeLabel.topAnchor.constraint(equalTo: separator1.bottomAnchor, constant: Self.VerticalSpacingSmall).isActive = true
magnitudeLabel.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
if !informationTypes.contains(.preliminary) {
@@ -297,27 +229,27 @@ class SeismicNetworkTableViewCell: UITableViewCell {
}
containerView.addSubview(stackViewInformations)
stackViewInformations.topAnchor.constraint(equalTo: magnitudeLabel.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
stackViewInformations.topAnchor.constraint(equalTo: magnitudeLabel.bottomAnchor, constant: Self.VerticalSpacingSmall).isActive = true
stackViewInformations.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
stackViewInformations.trailingAnchor.constraint(equalTo: informationsTrailingAnchor, constant: -14).isActive = true
previousView = stackViewInformations
// network
containerView.addSubview(networkLabel)
networkLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
networkLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingSmall).isActive = true
networkLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
networkLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
previousView = networkLabel
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) || informationTypes.contains(.intensityMap) {
let separator2 = addSeparator(constraintTo: previousView.bottomAnchor)
let separator2 = addSeparator(constraintTo: previousView.bottomAnchor, constanst: Self.VerticalSpacingSmall)
let stackViewReports = UIStackView()
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
stackViewReports.axis = .vertical
stackViewReports.distribution = .equalSpacing
stackViewReports.alignment = .center
stackViewReports.spacing = Self.DefaultVerticalSpacing
stackViewReports.spacing = Self.VerticalSpacingDefault
if informationTypes.contains(.realtimeSmartphones) {
stackViewReports.addArrangedSubview(smartphonesLabel)
@@ -334,15 +266,17 @@ class SeismicNetworkTableViewCell: UITableViewCell {
}
containerView.addSubview(stackViewReports)
stackViewReports.topAnchor.constraint(equalTo: separator2.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
stackViewReports.topAnchor.constraint(equalTo: separator2.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
let separator3 = addSeparator(constraintTo: stackViewReports.bottomAnchor)
previousView = separator3
previousView = stackViewReports
}
if informationTypes.contains(.buttons) {
let separator3 = addSeparator(constraintTo: previousView.bottomAnchor)
previousView = separator3
// buttons
let stackViewButtons = UIStackView()
stackViewButtons.translatesAutoresizingMaskIntoConstraints = false
@@ -359,7 +293,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
containerView.addSubview(stackViewButtons)
stackViewButtons.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
stackViewButtons.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
stackViewButtons.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
@@ -369,7 +303,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
if displayType == .mapExpanded {
containerView.addSubview(mapView)
mapView.heightAnchor.constraint(equalToConstant: 140.0).isActive = true
mapView.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
mapView.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
mapView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
mapView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
@@ -381,7 +315,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
containerView.addSubview(buttonClose)
buttonClose.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
buttonClose.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
buttonClose.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
buttonClose.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
@@ -394,12 +328,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
gradientView.eqn_applyRoundedCorners()
}
private func recreateUI() {
// remove all subviews and recreate the required components
containerView.subviews.forEach({ $0.removeFromSuperview() })
setupUI()
}
private func updateUI() {
guard let seismic = seismic else { return }
@@ -430,7 +358,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
alertsLabel.text = "⚠️ \(viewModel.users)"
}
if displayType == .mapExpanded {
// zoom based on population involved
let longitudeSpan = mapSpanLongitude(population: seismic.population100km)
@@ -491,52 +418,37 @@ class SeismicNetworkTableViewCell: UITableViewCell {
// MARK: - Actions
@objc func shareTapped(_ sender: UIButton) {
@objc private func shareTapped(_ sender: UIButton) {
delegate?.seismicNetworkCellDidTapShare(self)
}
@objc func mapTapped(_ sender: UIButton) {
@objc private func mapTapped(_ sender: UIButton) {
if displayType != .mapExpanded {
delegate?.seismicNetworkCellDidTapMap(self)
}
}
@objc func calendarTapped(_ sender: UIButton) {
@objc private func calendarTapped(_ sender: UIButton) {
delegate?.seismicNetworkCellDidTapCalendar(self)
}
@objc func settingsTapped(_ sender: UIButton) {
@objc private func settingsTapped(_ sender: UIButton) {
delegate?.seismicNetworkCellDidTapSettings(self)
}
@objc func closeTapped(_ sender: UIButton) {
@objc private func closeTapped(_ sender: UIButton) {
delegate?.seismicNetworkCellDidTapClose(self)
}
@objc func mapDetailTapped(_ sender: Any) {
@objc private func mapDetailTapped(_ sender: Any) {
delegate?.seismicNetworkCellDidTapMapDetail(self)
}
@objc func intensityMapTapped(_ sender: Any) {
@objc private func intensityMapTapped(_ sender: Any) {
delegate?.seismicNetworkCellDidTapIntensityMapDetail(self)
}
// MARK: - Helpers
@discardableResult
private func addSeparator(constraintTo: NSLayoutYAxisAnchor, constanst: CGFloat = 8.0) -> UIView {
let separator = UIView()
separator.translatesAutoresizingMaskIntoConstraints = false
separator.backgroundColor = .lightGray
containerView.addSubview(separator)
separator.topAnchor.constraint(equalTo: constraintTo, constant: constanst).isActive = true
separator.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
separator.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
return separator
}
/// Determines the zoom for the map, based on the involved population
private func mapSpanLongitude(population: Double) -> CLLocationDegrees {
@@ -29,17 +29,16 @@ class SeismicCardSettingsViewController: UIViewController {
@IBOutlet private weak var informationPopulationSwitch: UISwitch!
@IBOutlet private weak var closeButton: UIButton!
private var informations = [SeismicNetworkTableViewCell.InformationType]()
private var informations: [SeismicNetworkTableViewCell.InformationType] {
get { AppPreferences.shared.seismicNetworksInformations }
set { AppPreferences.shared.seismicNetworksInformations = newValue }
}
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
if let saved = UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) as? [Int] {
informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
}
setupUI()
updateUI()
}
@@ -84,7 +83,6 @@ class SeismicCardSettingsViewController: UIViewController {
toggle(information: .population)
}
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
updateUI()
}
@@ -47,7 +47,7 @@ class SeismicNetworkScrollIndicatorView: UIView {
seismics.enumerated().forEach { index, seismic in
// Disegniamo un rettangolo per ogni sisma, quello evidenziato deve avere un contorno rosso.
// Ci sono situazioni in cui ci sono molti sismi da mostrare, quindi in quel caso facciamo alcune modifiche:
// - usiamo un'altezza minma per il sisma evidenziato
// - usiamo un'altezza minima per il sisma evidenziato
// - per il sisma evidenziato, anche il contenuto è rosso (e non solo il bordo)
// - negli altri sismi, non mostriamo il bordo
@@ -74,9 +74,22 @@ class SeismicNetworkScrollIndicatorView: UIView {
// Dobbiamo eventualmente calcolare un offset aggiuntivo,
// perchè il sisma evidenziato ha un'altezza maggiore (se i rettangoli sono piccoli)
let rectHeight = rectStandardHeight
let offset: CGFloat = (index > highlightIndex && smallRectangles) ? rectHighlightedMinHeight : 0
let yPosition = CGFloat(index) * rectHeight + offset
let rectangle = CGRect(x: 0, y: yPosition, width: rectStandardWidth, height: rectHeight)
var offset: CGFloat = 0
if index > highlightIndex && smallRectangles {
// calcoliamo l'offset prima del rettangolo evidenziato
let preOffset = CGFloat(highlightIndex - 1) * rectStandardHeight
// offset diverso dovuto all'altezza diversa del rettangolo evidenziato
let highlightOffset = rectHighlightedMinHeight
// calcoliamo l'offset tra il rettangolo evidenziato e quello corrente
let postOffset = CGFloat(index - highlightIndex) * rectStandardHeight
offset = preOffset + highlightOffset + postOffset
} else {
// siamo prima del rettangolo evidenziato, non abbiamo calcoli da fare
offset = CGFloat(index) * rectHeight
}
let rectangle = CGRect(x: 0, y: offset, width: rectStandardWidth, height: rectHeight)
let fillColor = seismic.colors.textColor.withAlphaComponent(0.3)
context?.setFillColor(fillColor.cgColor)
@@ -87,8 +100,7 @@ class SeismicNetworkScrollIndicatorView: UIView {
let borderWidth: CGFloat = 0.5
context?.setStrokeColor(AppTheme.Colors.gray.cgColor)
context?.setLineWidth(borderWidth) // Spessore del bordo
context?.stroke(rectangle) // Evita che il bordo venga tagliato
context?.stroke(rectangle)
}
}
}
@@ -8,21 +8,62 @@
import Foundation
struct MagnitudeColors {
let textColor: UIColor
let startColor: UIColor
let endColor: UIColor
}
struct SeismicNetworkMinimalViewModel {
private let seismic: EQNSisma
let place: String
let isPreliminary: Bool
let magnitude: String
let time: String
let distance: String
let smartphones: String
let users: String
let colors: MagnitudeColors
// MARK: - Init
init(seismic: EQNSisma) {
self.seismic = seismic
self.place = seismic.place
let isPreliminary = seismic.preliminary.intValue > 0
self.isPreliminary = isPreliminary
self.magnitude = String(format: "%.1f", seismic.magnitude.doubleValue)
let time = EQNUtility.formattedString(forTimeDifference: Int(seismic.timeDifference))
self.time = time
let distanceRounded = Int(round(seismic.userDistance))
self.distance = "\(distanceRounded) km"
if seismic.smartphoneNumber.intValue > 0 {
self.smartphones = String(format: NSLocalizedString("official_smartphones", comment: ""), seismic.smartphoneNumber)
} else {
self.smartphones = ""
}
if seismic.userNumber.intValue > 0 {
self.users = String(format: NSLocalizedString("official_reports", comment: ""), seismic.userNumber)
} else {
self.users = ""
}
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
}
}
struct SeismicNetworkViewModel {
struct MagnitudeColors {
let textColor: UIColor
let startColor: UIColor
let endColor: UIColor
}
private let seismic: EQNSisma
let place: String
let network: String
let isPreliminary: Bool
let magnitude: String
let rawMagnitude: Double
let depth: String
let time: String
let distance: String
@@ -38,7 +79,6 @@ struct SeismicNetworkViewModel {
self.seismic = seismic
self.place = seismic.place
self.network = seismic.provider
self.rawMagnitude = seismic.magnitude.doubleValue
let isPreliminary = seismic.preliminary.intValue > 0
self.isPreliminary = isPreliminary
@@ -67,7 +107,7 @@ struct SeismicNetworkViewModel {
let coordinateText = EQNUtility.coordinateString(coordinate: seismic.coordinate.coordinate)
self.coordinate = "\(coordinateText)"
let population = Self.formatPopulation(seismic.population100km)
let population = formatPopulation(seismic.population100km)
self.population = String(format: NSLocalizedString("share_radius100", comment: ""), population)
if seismic.smartphoneNumber.intValue > 0 {
@@ -81,78 +121,8 @@ struct SeismicNetworkViewModel {
self.users = ""
}
self.colors = Self.calculateColors(for: seismic.magnitude.doubleValue)
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
}
// MARK: - Private
/// Format population value (ex. 1.5M, 2.4k)
private static func formatPopulation(_ population: Double) -> String {
var populationString = ""
if population > 999_999 {
let roundedPopulation = round(population / 100_000) / 10
populationString = "\(roundedPopulation)M"
} else if population > 999 {
let roundedPopulation = round(population / 100) / 10
populationString = "\(roundedPopulation)K"
} else {
let roundedPopulation = round(population)
populationString = "\(roundedPopulation)"
}
return populationString
}
/// Calculate colors to use for text and background of the cell
private static func calculateColors(for magnitude: Double) -> MagnitudeColors {
var textColor = UIColor.black
var r = 0, g = 0, b = 0
if (magnitude < 2.0) {
let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0)
r = Int(round(200.0 + (255.0 - 200.0) * fraction))
g = Int(round(226.0 + (255.0 - 226.0) * fraction))
b = Int(round(196.0 + (255.0 - 196.0) * fraction))
textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 160.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 2.0 && magnitude < 3.5) {
let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2)
r = Int(round(136.0 + (200.0 - 136.0) * fraction))
g = Int(round(175.0 + (226.0 - 175.0) * fraction))
b = Int(round(131.0 + (196.0 - 131.0) * fraction))
textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 3.5 && magnitude < 4.5) {
let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5)
r = 252
g = Int(round(233.0 + (253.0 - 233.0) * fraction))
b = Int(round(179.0 + (209.0 - 179.0) * fraction))
textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 4.5 && magnitude < 5.5) {
let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5)
r = 252
g = Int(round(159.0 + (197.0 - 159.0) * fraction))
b = Int(round(161.0 + (197.0 - 161.0) * fraction))
textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 5.5) {
let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5)
r = Int(round(190.0 + (254.0 - 190.0) * fraction))
g = Int(round(124.0 + (219.0 - 124.0) * fraction))
b = 255
textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0)
}
let r2 = min(r + 30, 255)
let g2 = min(g + 30, 255)
let b2 = min(b + 30, 255)
let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0)
let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0)
return .init(textColor: textColor, startColor: startColor, endColor: endColor)
}
}
extension SeismicNetworkViewModel: Equatable {
@@ -160,3 +130,72 @@ extension SeismicNetworkViewModel: Equatable {
return lhs.seismic == rhs.seismic
}
}
// MARK: - Helpers
/// Calculate colors to use for text and background of the cell
private func calculateColors(for magnitude: Double) -> MagnitudeColors {
var textColor = UIColor.black
var r = 0, g = 0, b = 0
if (magnitude < 2.0) {
let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0)
r = Int(round(200.0 + (255.0 - 200.0) * fraction))
g = Int(round(226.0 + (255.0 - 226.0) * fraction))
b = Int(round(196.0 + (255.0 - 196.0) * fraction))
textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 160.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 2.0 && magnitude < 3.5) {
let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2)
r = Int(round(136.0 + (200.0 - 136.0) * fraction))
g = Int(round(175.0 + (226.0 - 175.0) * fraction))
b = Int(round(131.0 + (196.0 - 131.0) * fraction))
textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 3.5 && magnitude < 4.5) {
let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5)
r = 252
g = Int(round(233.0 + (253.0 - 233.0) * fraction))
b = Int(round(179.0 + (209.0 - 179.0) * fraction))
textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 4.5 && magnitude < 5.5) {
let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5)
r = 252
g = Int(round(159.0 + (197.0 - 159.0) * fraction))
b = Int(round(161.0 + (197.0 - 161.0) * fraction))
textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 5.5) {
let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5)
r = Int(round(190.0 + (254.0 - 190.0) * fraction))
g = Int(round(124.0 + (219.0 - 124.0) * fraction))
b = 255
textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0)
}
let r2 = min(r + 30, 255)
let g2 = min(g + 30, 255)
let b2 = min(b + 30, 255)
let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0)
let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0)
return .init(textColor: textColor, startColor: startColor, endColor: endColor)
}
/// Format population value (ex. 1.5M, 2.4k)
private func formatPopulation(_ population: Double) -> String {
var populationString = ""
if population > 999_999 {
let roundedPopulation = round(population / 100_000) / 10
populationString = "\(roundedPopulation)M"
} else if population > 999 {
let roundedPopulation = round(population / 100) / 10
populationString = "\(roundedPopulation)K"
} else {
let roundedPopulation = round(population)
populationString = "\(roundedPopulation)"
}
return populationString
}
@@ -21,6 +21,30 @@ class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
override var isFilterViewVisible: Bool { false }
override var isCloseButtonVisible: Bool { false }
// MARK: - UI
lazy var descriptionView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = AppTheme.Colors.pureBlue
let descriptionLabel = UILabel()
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.numberOfLines = 0
descriptionLabel.textColor = .white
descriptionLabel.font = .preferredFont(forTextStyle: .subheadline)
descriptionLabel.textAlignment = .center
descriptionLabel.text = NSLocalizedString("shakemap_description", comment: "")
view.addSubview(descriptionLabel)
descriptionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 2.0).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -2.0).isActive = true
descriptionLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 2.0).isActive = true
descriptionLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -2.0).isActive = true
return view
}()
// MARK: - Init
init(
@@ -39,27 +63,10 @@ class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
override func extraUI() {
super.extraUI()
let descriptionView = UIView()
descriptionView.translatesAutoresizingMaskIntoConstraints = false
descriptionView.backgroundColor = AppTheme.Colors.pureBlue
view.addSubview(descriptionView)
descriptionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
descriptionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
descriptionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
let descriptionLabel = UILabel()
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.numberOfLines = 0
descriptionLabel.textColor = .white
descriptionLabel.font = .preferredFont(forTextStyle: .subheadline)
descriptionLabel.textAlignment = .center
descriptionLabel.text = NSLocalizedString("shakemap_description", comment: "")
descriptionView.addSubview(descriptionLabel)
descriptionLabel.leadingAnchor.constraint(equalTo: descriptionView.leadingAnchor, constant: 2.0).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: descriptionView.trailingAnchor, constant: -2.0).isActive = true
descriptionLabel.topAnchor.constraint(equalTo: descriptionView.topAnchor, constant: 2.0).isActive = true
descriptionLabel.bottomAnchor.constraint(equalTo: descriptionView.bottomAnchor, constant: -2.0).isActive = true
}
override func configureUI() {
@@ -138,7 +145,11 @@ class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
}
private func shareScreenshot() {
let screenshot = createSnapshot()
let screenshot = createSnapshot(prepare: {
descriptionView.isHidden = true
}, restore: {
descriptionView.isHidden = false
})
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
present(controller, animated: true)
@@ -199,7 +210,11 @@ class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
let minIntensity = shakemaps.map { $0.intensity }.min() ?? 0
let maxIntensity = shakemaps.map { $0.intensity }.max() ?? 255
let indexColor = Int(round((intensity-minIntensity)/(maxIntensity-minIntensity)*255))
let indexColor = if minIntensity == maxIntensity {
0
} else {
Int(round((intensity-minIntensity)/(maxIntensity-minIntensity)*255))
}
let lineColor = UIColor(hexString: shakemapColors[indexColor]) ?? .white
let textColor: UIColor = indexColor < 65 ? .white : .black
@@ -18,6 +18,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
case advertise(NativeAd)
}
enum CardDisplayType: Int, CaseIterable {
case small
case full
case minimal
}
private static let SegueIdentifierFilters = "ShowFilters"
private static let SegueIdentifierCardSettings = "ShowCardSettings"
@@ -34,9 +40,17 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
/// Cells to display (must be seismics or ad banners)
private var rows = [CellType]()
/// Type of cards to show
private var cardDisplayType: CardDisplayType {
get { AppPreferences.shared.seismicNetworksCardStyle }
set { AppPreferences.shared.seismicNetworksCardStyle = newValue }
}
private var seismicViewModels = [SeismicNetworkViewModel]()
/// Informations to display on a single cell
private var informations = [SeismicNetworkTableViewCell.InformationType]()
private var informations: [SeismicNetworkTableViewCell.InformationType] {
get { AppPreferences.shared.seismicNetworksInformations }
set { AppPreferences.shared.seismicNetworksInformations = newValue }
}
/// Index path of row with map expanded
private var openMapIndexPath: IndexPath?
/// Push notification opened by the user
@@ -53,7 +67,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
// MARK: - UI
@IBOutlet private weak var expandeCollapseButton: UIBarButtonItem!
@IBOutlet private weak var displayModeButton: UIBarButtonItem!
@IBOutlet private weak var sortButton: UIBarButtonItem!
private var tableViewTopConstraint: NSLayoutConstraint?
@@ -182,9 +196,11 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
tableView.estimatedRowHeight = 300.0
tableView.rowHeight = UITableView.automaticDimension
tableView.registerCell(for: SeismicNetworkTableViewCell.self)
tableView.registerCell(for: SeismicNetworkMinimalTableViewCell.self)
tableView.registerCell(for: SeismicNetworkAdvertiseTableViewCell.self)
tableView.emptyDataSetSource = self
tableView.separatorStyle = .none
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
setupSortMenu()
}
@@ -259,14 +275,13 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
private func refreshUI() {
elaborateData()
if let saved = UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) as? [Int] {
informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
}
if informations.contains(.buttons) {
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-collapse")
} else {
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-expand")
switch cardDisplayType {
case .small:
displayModeButton.image = UIImage(systemName: "1.square")
case .full:
displayModeButton.image = UIImage(systemName: "2.square")
case .minimal:
displayModeButton.image = UIImage(systemName: "3.square")
}
tableView.reloadData()
@@ -631,13 +646,17 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
}
@IBAction func collapseExpandTapped(_ sender: Any) {
if informations.contains(.buttons) {
cardDisplayType.next()
switch cardDisplayType {
case .small:
informations.removeAll(where: { $0 == .buttons })
} else {
case .full:
informations.append(.buttons)
case .minimal:
break
}
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
refreshUI()
}
@@ -651,17 +670,26 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
let row = rows[indexPath.row]
switch row {
case .seismic(let seismic):
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkTableViewCell.self, for: indexPath)
var type = SeismicNetworkTableViewCell.DisplayType.normal
if openMapIndexPath == indexPath {
type = .mapExpanded
switch cardDisplayType {
case .small, .full:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkTableViewCell.self, for: indexPath)
var type = SeismicNetworkTableViewCell.DisplayType.normal
if openMapIndexPath == indexPath {
type = .mapExpanded
}
let isPushSelected = isSeismicToHighlight(seismic: seismic)
cell.configure(with: seismic, type: type, informations: informations, isPushSelected: isPushSelected)
cell.delegate = self
return cell
case .minimal:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkMinimalTableViewCell.self, for: indexPath)
let isPushSelected = isSeismicToHighlight(seismic: seismic)
cell.configure(with: seismic, isPushSelected: isPushSelected)
cell.delegate = self
return cell
}
let isPushSelected = isSeismicToHighlight(seismic: seismic)
cell.configure(with: seismic, type: type, informations: informations, isPushSelected: isPushSelected)
cell.delegate = self
return cell
case .advertise(let nativeAd):
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkAdvertiseTableViewCell.self, for: indexPath)
cell.loadNativeAd(nativeAd)
@@ -754,9 +782,9 @@ extension SeismicNetworksViewController: NativeAdLoaderDelegate {
}
}
extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
extension SeismicNetworksViewController: SeismicNetworkBaseTableViewCellDelegate {
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) {
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkBaseTableViewCell) {
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
@@ -773,7 +801,7 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
present(controller, animated: true)
}
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) {
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell) {
guard let index = tableView.indexPath(for: cell) else { return }
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
@@ -782,29 +810,29 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
tableView.reloadRows(at: indexToReloads, with: .automatic)
}
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell) {
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell) {
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
showMapDetail(for: seismic)
}
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkTableViewCell) {
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkBaseTableViewCell) {
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
showIntensityMap(for: seismic)
}
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) {
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkBaseTableViewCell) {
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
openCalendar(for: seismic)
}
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell) {
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkBaseTableViewCell) {
performSegue(withIdentifier: Self.SegueIdentifierCardSettings, sender: nil)
}
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell) {
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell) {
guard let index = tableView.indexPath(for: cell) else { return }
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
@@ -13,14 +13,17 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
private enum RowIdentifier: Int {
case abilitaNotifiche
case disabilitaSuonoAllerta
case abilitaCriticalAlerts
}
private var isNotificationEnabled = false
private var isMildQuakeSoundDisabled = false
private var isCriticalAlertsEnabled = false
private let settings: [SettingItem] = [
.init(type: .enable, title: NSLocalizedString("options_notification_enable_alarm", comment: "")),
.init(type: .enable, title: NSLocalizedString("options_notification_disable_sound", comment: "")),
.init(type: .enable, title: NSLocalizedString("critical_alerts_setting", comment: ""))
]
@@ -56,6 +59,7 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
let saved = EQNSettingRealTimeAlert.shared
isNotificationEnabled = saved.isAbilitato
isMildQuakeSoundDisabled = saved.isMildQuakeSoundDisabled
isCriticalAlertsEnabled = saved.isCriticalAlertsEnabled
}
@@ -93,6 +97,11 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
cell.valueChanged = { [weak self] enabled in
self?.onChangeNotificationEnabled(enabled)
}
case .disabilitaSuonoAllerta:
cell.toggleSwitch.isOn = isMildQuakeSoundDisabled
cell.valueChanged = { [weak self] enabled in
self?.onChangeDisableSoundEnabled(enabled)
}
case .abilitaCriticalAlerts:
cell.toggleSwitch.isOn = isCriticalAlertsEnabled
cell.valueChanged = { [weak self] enabled in
@@ -116,6 +125,14 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
tableView.reloadData()
}
private func onChangeDisableSoundEnabled(_ enabled: Bool) {
isMildQuakeSoundDisabled = enabled
EQNSettingRealTimeAlert.shared.isMildQuakeSoundDisabled = isMildQuakeSoundDisabled
EQNSettingRealTimeAlert.shared.saveUserInfo()
tableView.reloadData()
}
private func onChangeCriticalAlertsEnabled(_ enabled: Bool) {
if enabled {
askForCriticalAlertsPermission()
+1 -3
View File
@@ -49,7 +49,7 @@ static NSString * const EQNServerUrlCalibration = @"https://srv.earthquakenetwor
// download rete smartphone
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://cache.earthquakenetwork.it/distquake_count_redis3.php";
// download area check
static NSString * const EQNServerUrlDownloadAreaCheck = @"https://srv.earthquakenetwork.it/distquake_download_areacheck.php";
static NSString * const EQNServerUrlDownloadAreaCheck = @"https://cache.earthquakenetwork.it/distquake_download_areacheck.php";
// download pastquakes
static NSString * const EQNServerUrlDownloadPastQuakes = @"https://srv.earthquakenetwork.it/distquake_download_pastquakes.php";
// download segnalazioni
@@ -64,8 +64,6 @@ static NSString * const EQNServerUrlAlertSimulator = @"https://srv.earthquakenet
#pragma mark - UserDefaults Keys
static NSString * const EQNUserDefaultKeySesmicInformations = @"EQNetwork.SeismicInformations";
static NSString * const EQNUserDefaultKeyOneShotShowCountry = @"EQNetwork.OneShot.CountrySelection";
static NSString * const EQNUserDefaultSeismicNetworkCards = @"EQNData.RetiSismiche";
#pragma mark - NSNotification
@@ -37,3 +37,13 @@ extension CGFloat {
self*2.0
}
}
extension CaseIterable where Self: Equatable {
mutating func next() {
let all = Self.allCases
let idx = all.firstIndex(of: self)!
let next = all.index(after: idx)
let newValue = all[next == all.endIndex ? all.startIndex : next]
self = newValue
}
}
@@ -39,4 +39,27 @@ class AppPreferences: NSObject {
UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaults.MapPinStyle)
}
}
var seismicNetworksInformations: [SeismicNetworkTableViewCell.InformationType] {
get {
if let saved = UserDefaults.standard.array(forKey: UserDefaults.SeismicNetworksCardInformations) as? [Int] {
let informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
return informations
}
return [.buttons, .distance, .coordinate, .population, .intensityMap]
}
set {
UserDefaults.standard.set(newValue.map { $0.rawValue }, forKey: UserDefaults.SeismicNetworksCardInformations)
}
}
var seismicNetworksCardStyle: SeismicNetworksViewController.CardDisplayType {
get {
let saved = UserDefaults.standard.integer(forKey: UserDefaults.SeismicNetworksCardStyle)
return SeismicNetworksViewController.CardDisplayType(rawValue: saved) ?? .small
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaults.SeismicNetworksCardStyle)
}
}
}
@@ -17,25 +17,15 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
func execute() {
print("[EQNUserDefaultsCommand] Start execute")
applyDefaultSettings()
migrationV5_8()
migrationFirstAppStat()
migrationCriticalAlerts()
migrationV5_9()
migrationV5_10()
}
// MARK: - Private
private func applyDefaultSettings() {
// seismic card settings
if UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) == nil {
let informations: [SeismicNetworkTableViewCell.InformationType] = [.buttons, .distance, .coordinate, .population, .intensityMap]
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
}
}
private func migrationV5_8() {
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_8)
if migrationPerformed {
@@ -117,15 +107,32 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
return
}
if let saved = userDefaults.array(forKey: EQNUserDefaultKeySesmicInformations) as? [Int] {
var informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
if !informations.contains(.intensityMap) {
informations.append(.intensityMap)
print("[EQNUserDefaultsCommand] Add intensityMap to seismic informations")
}
userDefaults.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
// add new intensity map
var informations = AppPreferences.shared.seismicNetworksInformations
if !informations.contains(.intensityMap) {
informations.append(.intensityMap)
print("[EQNUserDefaultsCommand] Add intensityMap to seismic informations")
}
AppPreferences.shared.seismicNetworksInformations = informations
let cardDisplayType: SeismicNetworksViewController.CardDisplayType = informations.contains(.buttons) ? .full : .small
AppPreferences.shared.seismicNetworksCardStyle = cardDisplayType
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_9)
}
private func migrationV5_10() {
let userDefaults = UserDefaults.standard
let migrationPerformed = userDefaults.bool(forKey: UserDefaults.AppMigrationV5_10)
if migrationPerformed {
print("[EQNUserDefaultsCommand] Migration v5.10 already performed")
return
}
print("[EQNUserDefaultsCommand] Save default value for mildQuakeSoundDisabled")
EQNSettingRealTimeAlert.shared.isMildQuakeSoundDisabled = true
EQNSettingRealTimeAlert.shared.saveUserInfo()
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_10)
}
}
+14 -3
View File
@@ -61,8 +61,14 @@
- (void)scaricaDatiReteSmartphone
{
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:EQNServerUrlDownloadSmartphoneNetwork] richiesta:EQNTipoChiamataDownloadDati success:^(id result) {
self.rete_smartphone = [[EQNReteSmartphone alloc] initWithInfo:result];
// Parsiamo la risposta (assicurandoci che non contenga valori nulli)
EQNReteSmartphone *rete = [EQNReteSmartphone fromResponse:result];
if (rete != nil) {
self.rete_smartphone = rete;
} else {
NSLog(@"[EQNManager] Impossibile parsare la risposta di DownloadSmartphoneNetwork");
}
[self performSelectorOnMainThread:@selector(scaricaAreaCheck) withObject:nil waitUntilDone:YES];
} failure:^(NSError * error) {
@@ -72,7 +78,12 @@
- (void)scaricaAreaCheck
{
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?lat=%f&lon=%f", EQNServerUrlDownloadAreaCheck, [EQNUser defaultUser].lastPosition.coordinate.latitude, [EQNUser defaultUser].lastPosition.coordinate.longitude]] richiesta:EQNTipoChiamataAreaCheck success:^(id result) {
// Quantizziamo le coordinate, in modo che venga sfruttata la cache lato server
CLLocation *lastPosition = [EQNUser defaultUser].lastPosition;
double latitude = round(lastPosition.coordinate.latitude * 5.0) / 5.0;
double longitude = round(lastPosition.coordinate.longitude * 5.0) / 5.0;
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?lat=%f&lon=%f", EQNServerUrlDownloadAreaCheck, latitude, longitude]] richiesta:EQNTipoChiamataAreaCheck success:^(id result) {
self.area_check = [[EQNAreaCheck alloc] initWithInfo:result];
@@ -21,13 +21,27 @@ class EQNReteSmartphone: NSObject {
let top10kAvailable: Int
let top100kAvailable: Int
// MARK: - Class
// Sometimes the response returns a broken response, with all values to null.
// In order to avoid crashes due to ObjC-Swift bridging, we need to take a generic nullable object,
// and then cast to the expected type (a dictionary).
@objc static func from(response: Any?) -> EQNReteSmartphone? {
if let info = response as? [[String: Any]?] {
return .init(info: info)
}
return nil
}
// MARK: - Init
@objc init(info: [[String: Any]]) {
private init(info: [[String: Any]?]) {
// merge array in a single dictionary
let allValues = info.reduce([:]) { (result, dictionary) -> [String: Any] in
return result.merging(dictionary, uniquingKeysWith: { (_, new) in new })
}
let allValues = info
.compactMap { $0 }
.reduce([:]) { (result, dictionary) -> [String: Any] in
return result.merging(dictionary, uniquingKeysWith: { (_, new) in new })
}
self.counterLastDayAlerts = allValues.integer(forKey: "eq", orDefault: 0)
self.counterTotalAlerts = allValues.integer(forKey: "eq_p", orDefault: 0)
@@ -23,6 +23,13 @@ import CoreLocation
return !firstAppStartExecuted
}
// MARK: - Permissions
@objc
var locationAuthorizationStatus: CLAuthorizationStatus {
CLLocationManager().authorizationStatus
}
// MARK: - Firebase Token
@objc
@@ -12,12 +12,4 @@ enum MapPinStyle: Int, CaseIterable {
case circle
case light
case full
mutating func next() {
let all = Self.allCases
let idx = all.firstIndex(of: self)!
let next = all.index(after: idx)
let newValue = all[next == all.endIndex ? all.startIndex : next]
self = newValue
}
}
@@ -16,11 +16,12 @@ class EQNSettingRealTimeAlert: NSObject {
static let shared = EQNSettingRealTimeAlert()
@objc var isAbilitato: Bool
@objc var isCriticalAlertsEnabled: Bool
var isMildQuakeSoundDisabled: Bool
var isCriticalAlertsEnabled: Bool
@objc var sismiDaNotificare: String
var sismiDaNotificare: String
@objc var raggioSismiLievi: String
@objc var raggioSismiForti: String
var raggioSismiForti: String
private static let DefaultSismiDaNotificare = "0"
private static let DefaultRaggioSismiLievi = "250"
@@ -38,6 +39,7 @@ class EQNSettingRealTimeAlert: NSObject {
let sharedDefaults = UserDefaults.appGroup
isCriticalAlertsEnabled = sharedDefaults?.bool(forKey: UserDefaults.AllertaSismicaCriticalAlerts) ?? false
isMildQuakeSoundDisabled = sharedDefaults?.bool(forKey: UserDefaults.AllertaSismicaSuonoDisabilitatoSismaDebole) ?? true
}
// MARK: - Public
@@ -50,12 +52,14 @@ class EQNSettingRealTimeAlert: NSObject {
defaults.set(raggioSismiForti, forKey: UserDefaults.AllertaSismicaRaggioSismiForti)
if let sharedDefaults = UserDefaults.appGroup {
sharedDefaults.set(isCriticalAlertsEnabled, forKey: UserDefaults.AllertaSismicaCriticalAlerts)
sharedDefaults.set(isMildQuakeSoundDisabled, forKey: UserDefaults.AllertaSismicaSuonoDisabilitatoSismaDebole)
}
}
@objc class func saveDefaultValues() {
shared.isAbilitato = true
shared.isCriticalAlertsEnabled = false
shared.isMildQuakeSoundDisabled = true
shared.sismiDaNotificare = Self.DefaultSismiDaNotificare
shared.raggioSismiLievi = Self.DefaultRaggioSismiLievi
shared.raggioSismiForti = Self.DefaultRaggioSismiForti
@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "twitter_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "xcorp_icon.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
@@ -0,0 +1 @@
<svg id="vector" xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><path fill="#000000" d="M321.8,373.1h36.6L190,137.5H153.4ZM391,389.9H310.6L237,285.1 144.8,389.9H121L226.4,270 121,120h80.4l69.7,99.2L358.4,120h23.8L281.7,234.3Z" id="path_0"/></svg>

After

Width:  |  Height:  |  Size: 281 B

@@ -21,9 +21,9 @@
</view>
<navigationItem key="navigationItem" id="6kD-jk-5Aw">
<rightBarButtonItems>
<barButtonItem image="navbar-icon-refresh" id="ZJh-jF-ILm">
<barButtonItem image="1.square" catalog="system" id="HTN-07-s5p">
<connections>
<action selector="refreshDataTapped:" destination="tVM-DH-fmv" id="qs5-jS-0Op"/>
<action selector="collapseExpandTapped:" destination="tVM-DH-fmv" id="EnD-92-5ZX"/>
</connections>
</barButtonItem>
<barButtonItem image="navbar-icon-filters" id="vOM-Np-CIk">
@@ -32,15 +32,15 @@
</connections>
</barButtonItem>
<barButtonItem image="navbar-icon-sort" id="LyU-KI-3Mb"/>
<barButtonItem image="navbar-icon-arrow-collapse" id="HTN-07-s5p">
<barButtonItem image="navbar-icon-refresh" id="ZJh-jF-ILm">
<connections>
<action selector="collapseExpandTapped:" destination="tVM-DH-fmv" id="EnD-92-5ZX"/>
<action selector="refreshDataTapped:" destination="tVM-DH-fmv" id="qs5-jS-0Op"/>
</connections>
</barButtonItem>
</rightBarButtonItems>
</navigationItem>
<connections>
<outlet property="expandeCollapseButton" destination="HTN-07-s5p" id="lxP-Im-NME"/>
<outlet property="displayModeButton" destination="HTN-07-s5p" id="Lhc-Od-MvL"/>
<outlet property="sortButton" destination="LyU-KI-3Mb" id="969-Zg-YBB"/>
<segue destination="6LP-zk-O1z" kind="presentation" identifier="ShowFilters" modalPresentationStyle="overCurrentContext" modalTransitionStyle="crossDissolve" id="Nzu-iH-UgB"/>
<segue destination="Rfp-kt-2Kx" kind="presentation" identifier="ShowCardSettings" modalPresentationStyle="overCurrentContext" modalTransitionStyle="crossDissolve" id="VWw-16-xGw"/>
@@ -621,6 +621,7 @@
</scene>
</scenes>
<resources>
<image name="1.square" catalog="system" width="128" height="114"/>
<image name="navbar-icon-arrow-collapse" width="24" height="24"/>
<image name="navbar-icon-filters" width="24" height="24"/>
<image name="navbar-icon-refresh" width="24" height="24"/>
@@ -20,8 +20,12 @@ extension CGFloat {
static let cardVerticalSpacing: CGFloat = 10.0
}
@objc
class EQNBaseContainerTableViewCell: UITableViewCell {
/// Inset to apply to enclosing table view. Used to adjust spacing around the cells (for instance, top space is smaller that the space between the cards)
@objc static let EdgeInsets: UIEdgeInsets = .init(top: CGFloat.cardHorizontalMargin - CGFloat.cardVerticalMargin, left: 0, bottom: 0, right: 0)
// MARK: - UI
private lazy var internalContainerView: UIView = {
@@ -46,11 +50,11 @@ class EQNBaseContainerTableViewCell: UITableViewCell {
}()
private lazy var headerLabel: UILabel = {
let label = EQNEdgeInsetLabel()
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = AppTheme.Colors.lightBlue
label.font = .systemFont(ofSize: 17.0, weight: .medium)
label.textColor = .white
label.textAlignment = .center
label.font = .preferredFont(forTextStyle: .title2)
label.textColor = AppTheme.shared.cardTextColor
return label
}()
@@ -136,9 +140,9 @@ class EQNBaseContainerTableViewCell: UITableViewCell {
if isHeaderVisible {
containerView.addSubview(headerLabel)
headerLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
headerLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: .cardPadding).isActive = true
headerLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
headerLabel.heightAnchor.constraint(equalToConstant: 26.0).isActive = true
headerLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
} else {
containerView.addSubview(emptyTopView)
emptyTopView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
@@ -77,6 +77,7 @@
"status_cancel" = "ألغي";
"options_alarms" = "تنبيه بوقت فعلي";
"options_notification_enable_alarm" = "قم بتفعيل الإنذار عند اكتشاف زلزال في الوقت الفعلي بواسطة شبكة الهواتف الذكية";
"options_notification_disable_sound" = "قم بتعطيل صوت المنبه إذا كان الاهتزاز في موقعك خفيفًا";
"options_notification_official" = "إخطارات الشبكة الزلزالية";
"options_notification_enable_official" = "تلقي إشعارات بشأن الزلازل التي اكتشفتها شبكات الزلازل الوطنية والدولية";
"options_official_type" = "مرشح الإشعارات";
@@ -77,6 +77,7 @@
"status_cancel" = "Διαγραφή";
"options_alarms" = "Ειδοποίηση σε πραγματικό χρόνο";
"options_notification_enable_alarm" = "Να ακούγεται συναγερμός όταν ανιχνεύεται σεισμός σε πραγματικό χρόνο από το δίκτυο smartphone";
"options_notification_disable_sound" = "Απενεργοποιήστε τον ήχο συναγερμού εάν το κούνημα στην τοποθεσία σας είναι ήπιο";
"options_notification_official" = "Κοινοποιήσεις σεισμικών δικτύων";
"options_notification_enable_official" = "Λήψη ειδοποιήσεων για τους σεισμούς που εντοπίζονται από τα εθνικά και διεθνή σεισμικά δίκτυα";
"options_official_type" = "Φίλτρο ειδοποιήσεων";
@@ -77,6 +77,7 @@
"status_cancel" = "Cancel";
"options_alarms" = "Real time alert";
"options_notification_enable_alarm" = "Activate an alarm when a quake is detected in real time by the network of smartphones";
"options_notification_disable_sound" = "Disable the alarm sound if the shaking at your location is mild";
"options_notification_official" = "Seismic network notifications";
"options_notification_enable_official" = "Receive notifications for the earthquakes detected by the national and international seismic networks";
"options_official_type" = "Notification filter";
@@ -77,6 +77,7 @@
"status_cancel" = "Clara";
"options_alarms" = "Alerta en tiempo real";
"options_notification_enable_alarm" = "Suena una alarma cuando se detecta un sismo en tiempo real por la red de los teléfonos inteligentes";
"options_notification_disable_sound" = "Desactive el sonido de la alarma si el sismo en tu ubicación es leve";
"options_notification_official" = "Notificaciones redes sísmicas";
"options_notification_enable_official" = "Recibe las notificaciones de los sismos detectados por las redes sísmicas nacionales e internacionales";
"options_official_type" = "Filtro de notificaciones";
@@ -77,6 +77,7 @@
"status_cancel" = "Supprimer";
"options_alarms" = "Alerte en temps réel";
"options_notification_enable_alarm" = "Une alarme retentit lorsqu'un séisme est détecté par le réseau des smartphones";
"options_notification_disable_sound" = "Désactivez le son de l'alarme si les secousses à votre emplacement sont légères";
"options_notification_official" = "Notifications de réseaux sismiquex";
"options_notification_enable_official" = "Recevez les notifications des séismes détectés par les réseaux sismiques nationaux et internationaux";
"options_official_type" = "Filtre de notificatio";
@@ -77,6 +77,7 @@
"status_cancel" = "Poništi";
"options_alarms" = "Upozorenje u stvarnom vremenu";
"options_notification_enable_alarm" = "Aktiviraj alarm kada mreža pametnih telefona u stvarnom vremenu otkrije potres";
"options_notification_disable_sound" = "Onemogućite zvuk alarma ako je podrhtavanje na vašoj lokaciji blago";
"options_notification_official" = "Obavijesti o seizmološkim mrežama";
"options_notification_enable_official" = "Primajte obavijesti o potresima koje su otkrile nacionalne i međunarodne seizmičke mreže";
"options_official_type" = "Filter obavijesti";
@@ -77,6 +77,7 @@
"status_cancel" = "Batal";
"options_alarms" = "Peringatan real time";
"options_notification_enable_alarm" = "Nyalakan alarm saat gempa terdeteksi secara real time oleh jaringan smartphone";
"options_notification_disable_sound" = "Nonaktifkan suara alarm jika guncangan di lokasi Anda ringan";
"options_notification_official" = "Pemberitahuan jaringan seismik";
"options_notification_enable_official" = "Terima pemberitahuan untuk gempa bumi yang terdeteksi oleh jaringan seismik nasional dan internasional";
"options_official_type" = "Filter notifikasi";
@@ -77,6 +77,7 @@
"status_cancel" = "Cancella";
"options_alarms" = "Allerta in tempo reale";
"options_notification_enable_alarm" = "Attiva un allarme quando un sisma è rilevato dalla rete smartphone";
"options_notification_disable_sound" = "Disabilita il suono di allerta se l'intensità del sisma nella tua zona è debole";
"options_notification_official" = "Notifiche da reti sismiche";
"options_notification_enable_official" = "Ricevi notifiche per i sismi rilevati dalle reti sismiche nazionali e internazionali";
"options_official_type" = "Filtro notifiche";
@@ -77,6 +77,7 @@
"status_cancel" = "İptal et";
"options_alarms" = "Gerçek zamanlı uyarı";
"options_notification_enable_alarm" = "Bir deprem akıllı telefon ağı tarafından gerçek zamanlı olarak tespit edildiğinde bir alarm çal";
"options_notification_disable_sound" = "Bulunduğunuz yerdeki sarsıntı hafifse alarm sesini devre dışı bırakın";
"options_notification_official" = "Sismik ağ bildirimleri";
"options_notification_enable_official" = "Ulusal ve uluslararası sismik ağlar tarafından tespit edilen depremler için bildirim alın";
"options_official_type" = "Bildirim filtresi";