Compare commits
201 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bf6b75dac | |||
| 69b83e9944 | |||
| 5061e36a45 | |||
| 8919f3c08f | |||
| 9cf9ef8a64 | |||
| 9ee3a478f0 | |||
| 8744595b56 | |||
| fa05d6b5c4 | |||
| 471ccc9e4a | |||
| 55de6f5ba0 | |||
| b12a9cc476 | |||
| a2f740b0a8 | |||
| 9cf93e652b | |||
| 2d23056ba8 | |||
| cb6ecca774 | |||
| 96286a49f6 | |||
| 481e8a28c0 | |||
| 286a4062f5 | |||
| 01a8ad7419 | |||
| 6e97e9bd2c | |||
| af6e94efcb | |||
| 5387758449 | |||
| 054603b42d | |||
| caf0e3b7cc | |||
| 4c35c38cc5 | |||
| 521254c8c1 | |||
| 78a1710584 | |||
| b2a54a544c | |||
| 0f5ad24744 | |||
| 0296cd50cd | |||
| 7551988b4e | |||
| 5edcaaad99 | |||
| b12f83680a | |||
| ee827c41ae | |||
| d0d06394f0 | |||
| b933b900ed | |||
| 0e7de44332 | |||
| 547bb794f0 | |||
| 9b1f1f12d2 | |||
| 7fc324367d | |||
| 3cb712f709 | |||
| 993e2924c7 | |||
| a167c989cc | |||
| 1b50f4fd17 | |||
| 0003b4607c | |||
| 85c9f333ce | |||
| 217cbfd4e3 | |||
| 5d8de1fb36 | |||
| f23bb78ceb | |||
| 0d91954614 | |||
| 49f5fa91fe | |||
| 68e560768b | |||
| 3e9c319b50 | |||
| d35e0e1b4a | |||
| 6ede137ef7 | |||
| c94195d48e | |||
| 28919d7b72 | |||
| a239534b91 | |||
| 226342f36c | |||
| ca6afbec5f | |||
| 465d3e8013 | |||
| a7e88b43f5 | |||
| 57ef877846 | |||
| c44d97b9fb | |||
| fd4ed7f66f | |||
| ef5db97854 | |||
| ce0e17a0c5 | |||
| 2a46f1d2d6 | |||
| 93871f0358 | |||
| 3e8fe0680d | |||
| 6be5f72360 | |||
| ccd1b9de59 | |||
| 5737eb5b02 | |||
| c549bb6ea5 | |||
| ff80905033 | |||
| dad2bc5648 | |||
| 10c74e278e | |||
| 96dbf960d2 | |||
| 81bfdd02a6 | |||
| 2ab3267981 | |||
| 48b6941ed5 | |||
| 669cb3c4f3 | |||
| 638d819d35 | |||
| a9884d8a8d | |||
| 2ef3560011 | |||
| 05093bb7a4 | |||
| 55f84ab46d | |||
| 03b4d0ddd6 | |||
| 3c5f26bc94 | |||
| 8c79d45b19 | |||
| 931d04c5e1 | |||
| 4d62fbbbd3 | |||
| 1c7065ece7 | |||
| 6dfa51e013 | |||
| b8b21d1458 | |||
| 88317f79e8 | |||
| 4e1147e782 | |||
| 579969d507 | |||
| 4d991d9a10 | |||
| 41491b5ee7 | |||
| 197b375c28 | |||
| f41e6b50ec | |||
| 796e4b5895 | |||
| e43a93979d | |||
| ef1aaa7d71 | |||
| 22d78baa8a | |||
| e4588aa731 | |||
| 07764f91ed | |||
| a0a238e384 | |||
| e61a45f78f | |||
| 0fdc60b938 | |||
| 5f02e2b8bb | |||
| b17a57b98e | |||
| 2379077272 | |||
| 78f0cfb2fa | |||
| f6bfe3fca0 | |||
| d5ab49b807 | |||
| b8bd547d65 | |||
| 547c503726 | |||
| 234622bcfd | |||
| 589466c8c6 | |||
| 2e7742951e | |||
| 3ed77ff1af | |||
| c98530fc54 | |||
| 98cc7e7c4c | |||
| 4db0bb6316 | |||
| 8c3f2dad6d | |||
| e0f346a4dc | |||
| eac0f8249e | |||
| d7c691101c | |||
| 49edbe1a14 | |||
| c5b3750ee7 | |||
| 98fb65a640 | |||
| c20041127b | |||
| 3995c29b22 | |||
| dfa07d0d10 | |||
| ce6fbb24ff | |||
| 382dcfa794 | |||
| d46a2e1559 | |||
| b0d1cde42b | |||
| d8612e33a3 | |||
| 73826d7520 | |||
| 3f57ac9b96 | |||
| 54c78aac0f | |||
| 975f5ed5bc | |||
| 52142486cf | |||
| b4b676ca8d | |||
| dd9ef878e2 | |||
| 5b978e535c | |||
| 242c15ba58 | |||
| a224837dcb | |||
| a21c16a01c | |||
| 1496f25251 | |||
| ad6eb6619c | |||
| f9a8dffad5 | |||
| a708a0f79a | |||
| 49431a760c | |||
| a57e883409 | |||
| b373dc1d60 | |||
| 01f1df9c01 | |||
| 72441d0532 | |||
| a4afb84e6d | |||
| 45a59e30ba | |||
| dac13acb9e | |||
| a9e264d666 | |||
| e64aaf2469 | |||
| 30c7536d4c | |||
| 70e82a67b1 | |||
| f020ac70a1 | |||
| dc4ccd796d | |||
| f66d6558b5 | |||
| 536ed32fb9 | |||
| 2e1a2a8e04 | |||
| 527132b7eb | |||
| 8cf69a9d12 | |||
| 6cba42994d | |||
| fd7821c083 | |||
| 5fab419d0e | |||
| befe46465b | |||
| 5e6ee892ce | |||
| 79d4b3b3bd | |||
| 357bdd47e3 | |||
| 1e4dd507da | |||
| bdfcb7a5c4 | |||
| 40fcb4707f | |||
| 37f9a856b1 | |||
| f42b9f1b53 | |||
| 4fd9966435 | |||
| df2c0a94a4 | |||
| cdd1a8d875 | |||
| 31f1cb5f35 | |||
| d426f15c8e | |||
| 037a74061d | |||
| ea6172226d | |||
| ec94db29b9 | |||
| 91a9bce03c | |||
| f54f4a2312 | |||
| a959df7cd9 | |||
| e95a93ff2c | |||
| b7c1f7379d | |||
| 0f71e0fea9 |
@@ -1,5 +1,34 @@
|
||||
# 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"
|
||||
- Aggiunta "Mappa intensità"
|
||||
|
||||
## Versione 5.8.1
|
||||
- Corrette traduzioni errate (causavano crash)
|
||||
- Aggiunto ordinamento in sottoscrizioni utente (prima Top10k)
|
||||
|
||||
## Versione 5.8
|
||||
- Modifica algoritmo filtro lista (issue #70)
|
||||
- Modifica impostazioni "Notifiche da reti sismiche" (issue #66)
|
||||
- Modifica invio impostazioni app per notifiche (issue #68)
|
||||
- Modifica impostazioni "Notifiche segnalazioni utente" (issue #67)
|
||||
- Modifica tab Reti Sismiche (issue #69)
|
||||
|
||||
## Versione 5.7
|
||||
- Aumentato target ad iOS 13
|
||||
- Disattivata logica calibrazione/monitoraggio
|
||||
- Aggiunto invio posizione in background
|
||||
|
||||
## Versione 5.6
|
||||
- Aggiunta lingua araba
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"magnitude_range" : "0",
|
||||
"google.c.a.e" : "1",
|
||||
"provider" : "INGV",
|
||||
"google.c.fid" : "fFjFx_Em8E-op_zHYXZpSr",
|
||||
"preliminary" : "0",
|
||||
"longitude" : "15.2917",
|
||||
"gcm.message_id" : "1719991146578422",
|
||||
"latitude" : "40.7738",
|
||||
"google.c.sender.id" : "899482329945",
|
||||
"type" : "official",
|
||||
"magnitude" : "1.4",
|
||||
"difference" : "13",
|
||||
"depth" : "14.6",
|
||||
"aps" : {
|
||||
"mutable-content" : 1,
|
||||
"alert" : {
|
||||
"body" : "Sisma rilevato a",
|
||||
"title-loc-key" : "Segnalazione da rete sismica",
|
||||
"title" : "Segnalazione da rete sismica",
|
||||
"loc-key" : "Sisma rilevato a",
|
||||
"action-loc-key" : "",
|
||||
"loc-args" : [
|
||||
"2 km SW Laviano (SA) - M1.4"
|
||||
]
|
||||
},
|
||||
"content-available" : 1,
|
||||
"sound" : "default"
|
||||
},
|
||||
"data" : "2024-07-03 09:05:52",
|
||||
"magnitude_type" : "ML",
|
||||
"place" : "2 km SW Laviano (SA)",
|
||||
"pop100" : "6824"
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"magnitude_range" : "0",
|
||||
"provider" : "SGC",
|
||||
"google.c.a.e" : "1",
|
||||
"google.c.fid" : "d3PS1dEvrUA-tmLLpl5E5f",
|
||||
"preliminary" : "0",
|
||||
"longitude" : "-75.5157",
|
||||
"gcm.message_id" : "1668682445010677",
|
||||
"latitude" : "4.35306",
|
||||
"type" : "official",
|
||||
"google.c.sender.id" : "899482329945",
|
||||
"difference" : "6",
|
||||
"data" : "2022-11-17 11:48:00",
|
||||
"depth" : "26",
|
||||
"aps" : {
|
||||
"content-available" : 1,
|
||||
"alert" : {
|
||||
"loc-key" : "Sisma rilevato a",
|
||||
"title-loc-key" : "Segnalazione da rete sismica",
|
||||
"loc-args" : [
|
||||
"Cajamarca - Tolima, Colombia - M2.2"
|
||||
]
|
||||
},
|
||||
"mutable-content" : 1,
|
||||
"sound" : "default"
|
||||
},
|
||||
"magnitude" : "2.2",
|
||||
"magnitude_type" : "M",
|
||||
"place" : "Cajamarca - Tolima, Colombia",
|
||||
"pop100" : "6622"
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"loc-args": [
|
||||
"2 km da Foligno"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"loc-key": "Sisma segnalato da utente a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
},
|
||||
"category": "notifica_con_mappa",
|
||||
|
||||
@@ -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,57 +100,13 @@ 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
|
||||
iconName = "warning_yellow.png"
|
||||
case "official":
|
||||
let provider = userInfo.string(forKey: "provider", orDefault: "")
|
||||
let intensity = userInfo.double(forKey: "magnitude", orDefault: 0)
|
||||
|
||||
let color: String
|
||||
if intensity < 2.0 {
|
||||
color = "_white"
|
||||
} else if intensity < 3.5 {
|
||||
color = "_green"
|
||||
} else if intensity < 4.5 {
|
||||
color = "_yellow"
|
||||
} else if intensity < 5.5 {
|
||||
color = "_red"
|
||||
} else {
|
||||
color = "_purple"
|
||||
}
|
||||
|
||||
iconName = manualIconName(for: provider, color: color)
|
||||
// don't show any images
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -174,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()
|
||||
@@ -182,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 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -2,6 +2,6 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:Earthquake Network.xcodeproj">
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
{
|
||||
"originHash" : "898a30d298491e1ce821191ebfa2e7d28e7c9ea4c119cbfdfb1b245bc94ac6c3",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "abseil-cpp-binary",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/abseil-cpp-binary.git",
|
||||
"state" : {
|
||||
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
|
||||
"version" : "1.2024072200.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "app-check",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/app-check.git",
|
||||
"state" : {
|
||||
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
|
||||
"version" : "11.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dznemptydataset",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/dzenbot/DZNEmptyDataSet",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "9bffa69a83a9fa58a14b3cf43cb6dd8a63774179"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "facebook-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/facebook/facebook-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "b28dde427715b45a26ebebf697929f4a81b15e04",
|
||||
"version" : "18.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "firebase-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
|
||||
"state" : {
|
||||
"revision" : "fbd463894af94d90eb4d6a4e54080459a8179519",
|
||||
"version" : "11.12.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googleappmeasurement",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "f7460ea630bddf172115c28493ae8b3798d95ce3",
|
||||
"version" : "11.12.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googledatatransport",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleDataTransport.git",
|
||||
"state" : {
|
||||
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
|
||||
"version" : "10.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googleutilities",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
|
||||
"version" : "8.0.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "grpc-binary",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/grpc-binary.git",
|
||||
"state" : {
|
||||
"revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
|
||||
"version" : "1.69.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "gtm-session-fetcher",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/gtm-session-fetcher.git",
|
||||
"state" : {
|
||||
"revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
|
||||
"version" : "3.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "interop-ios-for-google-sdks",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
|
||||
"state" : {
|
||||
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
|
||||
"version" : "101.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "leveldb",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/leveldb.git",
|
||||
"state" : {
|
||||
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
|
||||
"version" : "1.22.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "nanopb",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/nanopb.git",
|
||||
"state" : {
|
||||
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
|
||||
"version" : "2.30910.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "promises",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/promises.git",
|
||||
"state" : {
|
||||
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
|
||||
"version" : "2.4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "shogun",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/andreabusi-it/Shogun",
|
||||
"state" : {
|
||||
"revision" : "ad890190d6be90f7712c2e56a38ef0937d9f8c1a",
|
||||
"version" : "1.8.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "solar",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/ceeK/Solar.git",
|
||||
"state" : {
|
||||
"revision" : "c2b96f2d5fb7f835b91cefac5e83101f54643901",
|
||||
"version" : "3.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-package-manager-google-mobile-ads",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git",
|
||||
"state" : {
|
||||
"revision" : "aa24c7dc03bca62c42747314dc8537f15587b50d",
|
||||
"version" : "12.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-package-manager-google-user-messaging-platform",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git",
|
||||
"state" : {
|
||||
"revision" : "9b68aa69fb508f0274853e226c734151a973c7b7",
|
||||
"version" : "2.4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-protobuf",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||
"state" : {
|
||||
"revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
|
||||
"version" : "1.26.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1410"
|
||||
LastUpgradeVersion = "1620"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1410"
|
||||
LastUpgradeVersion = "1620"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
@@ -15,7 +15,7 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8C4B0B7921CACE3F00AED489"
|
||||
BlueprintIdentifier = "65FFDC91292F672B00EA821B"
|
||||
BuildableName = "EQNNotificationService.appex"
|
||||
BlueprintName = "EQNNotificationService"
|
||||
ReferencedContainer = "container:Earthquake Network.xcodeproj">
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1410"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Earthquake Network.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "shogun",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/andreabusi-it/Shogun",
|
||||
"state" : {
|
||||
"revision" : "3ffa7cfbdcbfb9868c853900f63d7e3248db797e",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -8,15 +8,9 @@
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "Costanti.h"
|
||||
#import "ServerRequest.h"
|
||||
#import "EQNGeneratoreURLServer.h"
|
||||
#import "EQNUser.h"
|
||||
#import "EQNAccelerometroManager.h"
|
||||
#import "EQNManager.h"
|
||||
#import "EQNUtility.h"
|
||||
#import "EQNAllertaSismica.h"
|
||||
#import "EQNNotificheSegnalazioniUtente.h"
|
||||
#import "EQNNotificheReteSismiche.h"
|
||||
#import "EQNMainTabBarController.h"
|
||||
#import "NSDictionary+EQNExtensions.h"
|
||||
|
||||
@@ -56,6 +50,9 @@
|
||||
[EQNManager defaultManager];
|
||||
[self configureFirebase];
|
||||
[self configureFacebookSDKWithApplication:application andOptions:launchOptions];
|
||||
// schedule background tasks
|
||||
[BackgroundTaskManager.shared registerTasks];
|
||||
[BackgroundTaskManager.shared scheduleUpdateServerPosition];
|
||||
|
||||
// add some generic logs for Crashlytics
|
||||
NSString *language = [[NSLocale preferredLanguages] firstObject];
|
||||
@@ -63,7 +60,6 @@
|
||||
|
||||
[self configurePushNotifications];
|
||||
|
||||
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
|
||||
[application registerForRemoteNotifications];
|
||||
|
||||
return YES;
|
||||
@@ -84,8 +80,10 @@
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
[[EQNManager defaultManager] avviaManager];
|
||||
[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
|
||||
|
||||
// disabilitiamo logica calibrazione/monitoraggio perchè attualmente non utilizzata dal server
|
||||
//[[EQNManager defaultManager] avviaManager];
|
||||
//[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
|
||||
|
||||
NSUInteger counter = [[NSUserDefaults standardUserDefaults] integerForKey:NSUserDefaults.UserDataProDiscountOpenCounter];
|
||||
counter += 1;
|
||||
@@ -151,7 +149,7 @@
|
||||
[self handlePushNotificationWithNotificationContent:content];
|
||||
|
||||
// Change this to your preferred presentation option
|
||||
completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound);
|
||||
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionSound);
|
||||
}
|
||||
|
||||
// Handle notification messages after display notification is tapped by the user.
|
||||
@@ -166,36 +164,28 @@
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
|
||||
{
|
||||
NSURL *url = [EQNGeneratoreURLServer urlPosizione];
|
||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataPosizione success:^(id result) {
|
||||
completionHandler(UIBackgroundFetchResultNewData);
|
||||
} failure:^(NSError *error) {
|
||||
completionHandler(UIBackgroundFetchResultFailed);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)handlePushNotificationWithNotificationContent:(UNNotificationContent *)content
|
||||
{
|
||||
NSString *type = content.userInfo[@"type"];
|
||||
|
||||
// Store both original payload and modified title/body
|
||||
// This will be usefull to avoid to re-evaluate logic for title display.
|
||||
NSDictionary *notification = @{
|
||||
@"title": content.title,
|
||||
@"body": content.body,
|
||||
@"userInfo": content.userInfo
|
||||
};
|
||||
|
||||
EQNTabBarSection section = EQNTabBarSectionAllerte;
|
||||
if ([type isEqualToString:@"eqn"]) {
|
||||
// Store both original payload and modified title/body
|
||||
// This will be usefull to avoid to re-evaluate logic for title display.
|
||||
NSDictionary *notification = @{
|
||||
@"title": content.title,
|
||||
@"body": content.body,
|
||||
@"userInfo": content.userInfo
|
||||
};
|
||||
if ([type isEqualToString:@"eqn"]) {
|
||||
[EQNRealtimePushNotification storeNotificationWithPayload:notification];
|
||||
section = EQNTabBarSectionAllerte;
|
||||
} else if([type isEqualToString:@"manual"]) {
|
||||
section = EQNTabBarSectionSegnalazioni;
|
||||
} else if([type isEqualToString:@"official"]) {
|
||||
[EQNOfficialPushNotification storeNotificationWithPayload:notification];
|
||||
section = EQNTabBarSectionRetiSismiche;
|
||||
}
|
||||
|
||||
@@ -242,8 +232,7 @@
|
||||
- (void)configureFacebookSDKWithApplication:(UIApplication *)application andOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[FBSDKApplicationDelegate.sharedInstance application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
[FBSDKSettings.sharedSettings setIsAdvertiserIDCollectionEnabled:YES];
|
||||
[FBSDKSettings.sharedSettings setIsAdvertiserIDCollectionEnabled:YES];
|
||||
FBSDKSettings.sharedSettings.isAdvertiserIDCollectionEnabled = YES;
|
||||
}
|
||||
|
||||
#pragma mark - FIRMessagingDelegate
|
||||
@@ -253,9 +242,9 @@
|
||||
NSLog(@"[Firebase] fcmToken %@", fcmToken);
|
||||
if (EQNUserData.sharedData.isFirstStart) {
|
||||
// save default values for notification settings
|
||||
[EQNAllertaSismica saveDefaultValues];
|
||||
[EQNNotificheSegnalazioniUtente saveDefaultValues];
|
||||
[EQNNotificheReteSismiche saveDefaultValues];
|
||||
[EQNSettingRealTimeAlert saveDefaultValues];
|
||||
[EQNSettingUserReportNotification saveDefaultValues];
|
||||
[EQNSettingSeismicNetworkNotification saveDefaultValues];
|
||||
}
|
||||
|
||||
[EQNUser.defaultUser registerUserIfNeededWithFirebaseToken:fcmToken];
|
||||
|
||||
@@ -17,24 +17,17 @@ 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"
|
||||
static let AllertaSismicaRaggioSismiForti = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI"
|
||||
static let AllertaSismicaImpostaVolume = "NOTIFICHE_ALLERA_SISMICA_IMPOSTA_VOLUME"
|
||||
static let AllertaSismicaTestaAllarma = "NOTIFICHE_ALLERA_SISMICA_TESTA_ALLARME"
|
||||
static let AllertaSismicaAbilitaIntervallo = "NOTIFICHE_ALLERA_SISMICA_ABILITA_INTERVALLO"
|
||||
static let AllertaSismicaOraInizio = "NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
|
||||
static let AllertaSismicaOraFine = "NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
|
||||
|
||||
// Impostazioni della sezione `Notifiche da reti sismiche`
|
||||
static let NotificheRetiSismicheAbilitato = "NOTIFICHE_ATTIVA_RETI_SISMICHE"
|
||||
static let NotificheRetiSismicheViciniAbilitato = "NOTIFICHE_ATTIVA_RETI_SISMICHE_VICINE"
|
||||
static let NotificheRetiSismicheTerremotiFortiAbilitato = "NOTIFICHE_ATTIVA_RETI_TERREMOTI_FORTI"
|
||||
static let NotificheRetiSismicheDistanzaPosizione = "NOTIFICHE_DISTANZA_POSIZIONE_RETI_SISMICHE"
|
||||
static let NotificheRetiSismicheEnergiaSisma = "NOTIFICHE_ATTIVA_RETI_ENERGIA_SISMI"
|
||||
static let NotificheRetiSismicheEnergiaTerremotiForti = "NOTIFICHE_ATTIVA_RETI_ENERGIA_FORTI"
|
||||
static let NotificheRetiSismicheListaEnti = "NOTIFICHE_ATTIVA_RETI_LISTA_ENTI"
|
||||
static let NotificheRetiSismicheMagnitudoMinima = "NOTIFICHE_ATTIVA_RETI_ENERGIA_SISMI"
|
||||
static let NotificheRetiSismicheDistanzaMassima = "NOTIFICHE_DISTANZA_POSIZIONE_RETI_SISMICHE"
|
||||
static let NotificheRetiSismicheFiltroNotifiche = "NOTIFICHE_FILTRO_NOTIFICHE_RETI_SISMICHE"
|
||||
|
||||
// Impostazioni della sezione `Notifiche segnalazioni utente`
|
||||
static let NotificheSegnalazioniUtenteAbilitato = "NOTIFICHE_SU_ATTIVA_SEGNALAZIONE_UTENTE"
|
||||
@@ -45,6 +38,7 @@ extension UserDefaults {
|
||||
static let UserReportCodeStatus = "CODE_MESSAGE_EQN"
|
||||
|
||||
// Proprietà e preferenze dell'utente
|
||||
static let FirstAppStartExecuted = "EQNUserDefaultFirstAppStartExecuted"
|
||||
/// Ultima posizione conosciuta dell'utente
|
||||
static let UserDataLastLocation = "EQNLast_Location"
|
||||
/// Token Firebase dell'utente corrente
|
||||
@@ -59,12 +53,63 @@ extension UserDefaults {
|
||||
static let UserDataProDiscountOpenCounter = "CONTEGGIO_APERTURE_PER_SCONTO"
|
||||
/// Prezzo scontato per la versione pro scaduto
|
||||
static let UserDataProDiscountExpired = "PREZZO_SCONTATO_SCADUTO"
|
||||
/// Se `true` visualizza il tempo nelle annotazioni della mappa segnalazioni utente
|
||||
static let UserReportExpandedView = "EQNData.UserReportExpandedView"
|
||||
/// Se `true` visualizza le opzioni nella singole card in reti sismiche
|
||||
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"
|
||||
static let AppMigrationV5_4 = "EQNUserDefaultMigrationV5_4"
|
||||
|
||||
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"
|
||||
static let SismicFiltersMigrationV5_8 = "EQNUserDefaultSismicFiltersMigrationV5_8"
|
||||
static let SaveSettingsNotificationMigrationV5_8 = "EQNUserDefaultSaveSettingsNotificationMigrationV5_8"
|
||||
|
||||
// Notifica allerta salvata
|
||||
static let RealTimeAlertPayload = "EQNData.RealtimePushNotificationPayload"
|
||||
static let RealTimeAlertDate = "EQNData.RealtimeAlertDate"
|
||||
// Notifica rete sismica aperta
|
||||
static let OfficialAlertPayload = "EQNData.OfficialPushNotificationPayload"
|
||||
|
||||
// Filtri sezioni reti sismiche
|
||||
static let SeismicFilterOption = "EQN_SISMI_TIPOLOGIA_FILTRO"
|
||||
static let SeismicSort = "EQN_SISMI_TIPOLOGIA_ORDINAMENTO"
|
||||
static let SeismicMagnitudoMinima = "EQN_MAGNITUDO_MINIMA"
|
||||
static let SeismicDistanzaMassima = "EQN_DISTANZA_MASSIMA"
|
||||
}
|
||||
|
||||
extension UserDefaults {
|
||||
|
||||
/// Get a generic stored values
|
||||
/// - Parameters:
|
||||
/// - key: A key in the current user‘s defaults database.
|
||||
/// - defaultValue: Default value to return if the key is not found
|
||||
/// - Returns: The object associated with the specified key, or `defaultValue` if the key was not found.
|
||||
func object<T>(forKey key: String, or defaultValue: T) -> T {
|
||||
if let value = UserDefaults.standard.object(forKey: key) as? T {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func enumObject<T: RawRepresentable>(forKey key: String, or defaultValue: T) -> T {
|
||||
if let rawValue = UserDefaults.standard.object(forKey: key) as? T.RawValue,
|
||||
let value = T.init(rawValue: rawValue) {
|
||||
return value
|
||||
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
@implementation AllerteViewController
|
||||
|
||||
static NSString * const SegueIdentifierPrioritySubscriptions = @"ShowPrioritySubscriptions";
|
||||
|
||||
/// Sections inside the app
|
||||
typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
AllerteTableRowLocationPermission = 0,
|
||||
@@ -102,8 +100,21 @@ 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"];
|
||||
[self.tableView registerClass:[AlertsPastEartquakesTableViewCell class] forCellReuseIdentifier:@"PastEarthquakesCell"];
|
||||
[self.tableView registerClass:[AlertsSeismicNotificationCompactTableViewCell class] forCellReuseIdentifier:@"SeismicNotificationCompactCell"];
|
||||
[self.tableView registerClass:[AlertsSeismicNotificationExpandedTableViewCell class] forCellReuseIdentifier:@"SeismicNotificationExpandedCell"];
|
||||
[self.tableView registerClass:[AlertsPositionDataTableViewCell class] forCellReuseIdentifier:@"PositionDataCell"];
|
||||
|
||||
if (EQNBackgroundPositionDebugHelper.shared.isEnabled) {
|
||||
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:@selector(backgroundPositionDebugTapped:)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshUI
|
||||
@@ -122,9 +133,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
// mostriamo la schermata solo se il countdown non è a zero
|
||||
if (![notification isCountdownExpired]) {
|
||||
RealtimeAlertViewController *controller = [[RealtimeAlertViewController alloc] initWithNotification:notification];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
controller.modalInPresentation = YES;
|
||||
}
|
||||
controller.modalInPresentation = YES;
|
||||
[self presentViewController:controller animated:YES completion:nil];
|
||||
}
|
||||
} else {
|
||||
@@ -140,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)];
|
||||
}
|
||||
|
||||
@@ -177,6 +186,13 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
[self refreshUI];
|
||||
}
|
||||
|
||||
- (IBAction)backgroundPositionDebugTapped:(id)sender
|
||||
{
|
||||
EQNBackgroundPositionDebugViewController *controller = [[EQNBackgroundPositionDebugViewController alloc] init];
|
||||
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
|
||||
[self presentViewController:navController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)actionCloseNotification
|
||||
{
|
||||
[self resetRealtimeAlert];
|
||||
@@ -219,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)
|
||||
@@ -256,14 +272,16 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
|
||||
if (tableRow == AllerteTableRowLocationPermission) {
|
||||
AlertsNoLocationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NoLocationCell" forIndexPath:indexPath];
|
||||
cell.status = CLLocationManager.authorizationStatus;
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
[cell updateWith:EQNUserData.sharedData.locationAuthorizationStatus];
|
||||
return cell;
|
||||
|
||||
} else if (tableRow == AllerteTableRowSismiRilevati) {
|
||||
if (self.isNotificaAttiva) {
|
||||
AlertsSeismicNotificationExpandedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationExpandedCell" forIndexPath:indexPath];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
||||
cell.notification = notification;
|
||||
[cell updateWith:notification];
|
||||
|
||||
__weak AllerteViewController *weakSelf = self;
|
||||
cell.onTapClose = ^{
|
||||
@@ -282,7 +300,8 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
return cell;
|
||||
}
|
||||
|
||||
AlertsSeismicNotificationCompactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationCell" forIndexPath:indexPath];
|
||||
AlertsSeismicNotificationCompactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationCompactCell" forIndexPath:indexPath];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
|
||||
__weak AllerteViewController *weakSelf = self;
|
||||
cell.onTapAlertTest = ^{
|
||||
@@ -302,8 +321,9 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
|
||||
} else if (tableRow == AllerteTableRowAllertePassate) {
|
||||
AlertsPastEartquakesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PastEarthquakesCell" forIndexPath:indexPath];
|
||||
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
|
||||
cell.onTapMapButton = ^{
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
|
||||
cell.onTapMap = ^{
|
||||
PasquakesMapViewController *controller = [[PasquakesMapViewController alloc] init];
|
||||
[self presentViewController:controller animated:YES completion:nil];
|
||||
};
|
||||
@@ -311,7 +331,8 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
|
||||
} else if (tableRow == AllerteTableRowReteSmartphone) {
|
||||
AlertsSmartphoneNetworkTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SmartphoneNetworkCell" forIndexPath:indexPath];
|
||||
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
|
||||
cell.onTapButton = ^{
|
||||
[self visualizzaCopertura];
|
||||
};
|
||||
@@ -319,12 +340,13 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
|
||||
} else if (tableRow == AllerteTableRowServizioPriorita) {
|
||||
AlertsPriorityServiceTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PriorityCell" forIndexPath:indexPath];
|
||||
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
|
||||
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
|
||||
return cell;
|
||||
|
||||
} else if (tableRow == AllerteTableRowDatiPosizione) {
|
||||
AlertsPositionDataTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PositionDataCell" forIndexPath:indexPath];
|
||||
cell.position = [EQNUser defaultUser].lastPosition;
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
[cell updateWith:[EQNUser defaultUser].lastPosition];
|
||||
return cell;
|
||||
}
|
||||
|
||||
@@ -337,9 +359,10 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
|
||||
AllerteTableRow tableRow = [self.tableItems[indexPath.row] integerValue];
|
||||
switch (tableRow) {
|
||||
case AllerteTableRowServizioPriorita:
|
||||
[self performSegueWithIdentifier:SegueIdentifierPrioritySubscriptions sender:nil];
|
||||
break;
|
||||
case AllerteTableRowServizioPriorita: {
|
||||
SubscriptionsViewController *controller = [[SubscriptionsViewController alloc] init];
|
||||
[self.navigationController pushViewController:controller animated:YES];
|
||||
}; break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
+51
-22
@@ -9,41 +9,70 @@
|
||||
import UIKit
|
||||
import CoreLocation
|
||||
|
||||
class AlertsNoLocationTableViewCell: EQNBaseTableViewCell {
|
||||
|
||||
@objc var status: CLAuthorizationStatus = .notDetermined {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
@objc
|
||||
class AlertsNoLocationTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
override var isHeaderVisible: Bool { false }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var messageLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var actionButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(title: NSLocalizedString("permission_location_no_background_solve", comment: ""), target: self, action: #selector(solveTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
containerView.addSubview(messageLabel)
|
||||
containerView.addSubview(actionButton)
|
||||
|
||||
messageLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
messageLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
messageLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
actionButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
|
||||
actionButton.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: .cardPadding).isActive = true
|
||||
actionButton.leadingAnchor.constraint(equalTo: messageLabel.leadingAnchor).isActive = true
|
||||
actionButton.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor).isActive = true
|
||||
actionButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
|
||||
}
|
||||
|
||||
@IBOutlet private weak var messageLabel: UILabel!
|
||||
@IBOutlet private weak var actionButton: UIButton!
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
actionButton.backgroundColor = AppTheme.Colors.lightGray
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
// MARK: - Public
|
||||
|
||||
private func updateUI() {
|
||||
var message = ""
|
||||
switch status {
|
||||
case .authorizedAlways:
|
||||
message = ""
|
||||
case .authorizedWhenInUse:
|
||||
message = NSLocalizedString("permission_location_no_background", comment: "")
|
||||
default:
|
||||
message = NSLocalizedString("permission_location_no", comment: "")
|
||||
@objc
|
||||
func update(with status: CLAuthorizationStatus) {
|
||||
messageLabel.text = switch status {
|
||||
case .authorizedAlways: ""
|
||||
case .authorizedWhenInUse: NSLocalizedString("permission_location_no_background", comment: "")
|
||||
default: NSLocalizedString("permission_location_no", comment: "")
|
||||
}
|
||||
messageLabel.text = message
|
||||
actionButton.setLocalizedTitle(key: "permission_location_no_background_solve")
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction private func solveTapped(_ sender: UIButton) {
|
||||
@objc private func solveTapped(_ sender: UIButton) {
|
||||
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
|
||||
return
|
||||
}
|
||||
|
||||
UIApplication.shared.open(settingsUrl, options: [:], completionHandler: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+63
-26
@@ -8,50 +8,87 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class AlertsPastEartquakesTableViewCell: EQNBaseTableViewCell {
|
||||
|
||||
@objc var smartphoneNetwork: EQNReteSmartphone? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
@objc
|
||||
class AlertsPastEartquakesTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
@objc var onTapMapButton: (() -> Void)?
|
||||
@objc var onTapMap: (() -> Void)?
|
||||
|
||||
override var headerText: String { NSLocalizedString("main_past_quakes", comment: "") }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var last24hLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .title3)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
|
||||
private lazy var from2013Label: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .title3)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var mapButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(mapTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var last24hLabel: UILabel!
|
||||
@IBOutlet private weak var from2013Label: UILabel!
|
||||
@IBOutlet private weak var mapButton: UIButton!
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
containerView.addSubview(last24hLabel)
|
||||
containerView.addSubview(from2013Label)
|
||||
containerView.addSubview(mapButton)
|
||||
|
||||
last24hLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
last24hLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
last24hLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
from2013Label.topAnchor.constraint(equalTo: last24hLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
from2013Label.leadingAnchor.constraint(equalTo: last24hLabel.leadingAnchor).isActive = true
|
||||
from2013Label.trailingAnchor.constraint(equalTo: last24hLabel.trailingAnchor).isActive = true
|
||||
|
||||
mapButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
|
||||
mapButton.topAnchor.constraint(equalTo: from2013Label.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
mapButton.leadingAnchor.constraint(equalTo: from2013Label.leadingAnchor).isActive = true
|
||||
mapButton.trailingAnchor.constraint(equalTo: from2013Label.trailingAnchor).isActive = true
|
||||
mapButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
headerLabel.text = NSLocalizedString("main_past_quakes", comment: "")
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
last24hLabel.text = NSLocalizedString("main_recent_quakes_initial", comment: "")
|
||||
from2013Label.text = NSLocalizedString("main_total_quakes_initial", comment: "")
|
||||
mapButton.setLocalizedTitle(key: "official_button_map", uppercased: true, emoji: "🗺")
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
guard let smartphoneNetwork = smartphoneNetwork else { return }
|
||||
// MARK: - Public
|
||||
|
||||
@objc
|
||||
func update(with smartphoneNetwork: EQNReteSmartphone?) {
|
||||
guard let smartphoneNetwork else { return }
|
||||
|
||||
last24hLabel.text = String(format: NSLocalizedString("main_recent_quakes", comment: ""), smartphoneNetwork.counterLastDayAlerts)
|
||||
from2013Label.text = String(format: NSLocalizedString("main_total_quakes", comment: ""), smartphoneNetwork.counterTotalAlerts)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func mapTapped(_ sender: UIButton) {
|
||||
onTapMapButton?()
|
||||
@objc private func mapTapped(_ sender: UIButton) {
|
||||
onTapMap?()
|
||||
}
|
||||
}
|
||||
|
||||
+107
-25
@@ -9,20 +9,11 @@
|
||||
import UIKit
|
||||
import Solar
|
||||
|
||||
class AlertsPositionDataTableViewCell: EQNBaseTableViewCell {
|
||||
|
||||
@objc var position: CLLocation? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
@objc
|
||||
class AlertsPositionDataTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
// MARK: - Internal
|
||||
override var headerText: String { NSLocalizedString("weather_location", comment: "") }
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var positionLabel: UILabel!
|
||||
@IBOutlet private weak var sunriseTimeLabel: UILabel!
|
||||
@IBOutlet private weak var sunsetTimeLabel: UILabel!
|
||||
private lazy var dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .none
|
||||
@@ -30,26 +21,117 @@ class AlertsPositionDataTableViewCell: EQNBaseTableViewCell {
|
||||
return formatter
|
||||
}()
|
||||
|
||||
// MARK: - Private
|
||||
// MARK: - UI
|
||||
|
||||
private func updateUI() {
|
||||
headerLabel.text = NSLocalizedString("weather_location", comment: "")
|
||||
private lazy var positionImage: UIImageView = {
|
||||
let imageView = UIImageView(image: .init(named: "world_old"))
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var positionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var sunriseImage: UIImageView = {
|
||||
let imageView = UIImageView(image: .init(named: "sunrise"))
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var sunriseTimeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var sunsetImage: UIImageView = {
|
||||
let imageView = UIImageView(image: .init(named: "sunset"))
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var sunsetTimeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
containerView.addSubview(positionImage)
|
||||
containerView.addSubview(positionLabel)
|
||||
containerView.addSubview(sunriseImage)
|
||||
containerView.addSubview(sunriseTimeLabel)
|
||||
containerView.addSubview(sunsetImage)
|
||||
containerView.addSubview(sunsetTimeLabel)
|
||||
|
||||
positionImage.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
positionImage.centerYAnchor.constraint(equalTo: positionLabel.centerYAnchor).isActive = true
|
||||
positionImage.trailingAnchor.constraint(equalTo: positionLabel.leadingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
positionLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
positionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
sunriseImage.leadingAnchor.constraint(equalTo: positionImage.leadingAnchor).isActive = true
|
||||
sunriseImage.centerYAnchor.constraint(equalTo: sunriseTimeLabel.centerYAnchor).isActive = true
|
||||
sunriseImage.trailingAnchor.constraint(equalTo: sunriseTimeLabel.leadingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
sunriseTimeLabel.topAnchor.constraint(equalTo: positionLabel.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
|
||||
sunriseTimeLabel.trailingAnchor.constraint(equalTo: positionLabel.trailingAnchor).isActive = true
|
||||
|
||||
sunsetImage.leadingAnchor.constraint(equalTo: sunriseImage.leadingAnchor).isActive = true
|
||||
sunsetImage.centerYAnchor.constraint(equalTo: sunsetTimeLabel.centerYAnchor).isActive = true
|
||||
sunsetImage.trailingAnchor.constraint(equalTo: sunsetTimeLabel.leadingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
sunsetTimeLabel.topAnchor.constraint(equalTo: sunriseTimeLabel.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
|
||||
sunsetTimeLabel.trailingAnchor.constraint(equalTo: sunriseTimeLabel.trailingAnchor).isActive = true
|
||||
sunsetTimeLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
|
||||
}
|
||||
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
positionLabel.text = "n.d."
|
||||
sunriseTimeLabel.text = "n.d."
|
||||
sunsetTimeLabel.text = "n.d."
|
||||
|
||||
guard let position = position else { return }
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc
|
||||
func update(with position: CLLocation?) {
|
||||
guard let position else { return }
|
||||
|
||||
positionLabel.text = EQNUtility.coordinateString(coordinate: position.coordinate)
|
||||
|
||||
if let solar = Solar(coordinate: position.coordinate) {
|
||||
let timeZone = TimeZone.current.localizedName(for: .generic, locale: .current) ?? TimeZone.current.identifier
|
||||
if let sunrise = solar.sunrise {
|
||||
sunriseTimeLabel.text = dateFormatter.string(from: sunrise) + " \(timeZone)"
|
||||
}
|
||||
if let sunset = solar.sunset {
|
||||
sunsetTimeLabel.text = dateFormatter.string(from: sunset) + " \(timeZone)"
|
||||
}
|
||||
guard let solar = Solar(coordinate: position.coordinate) else { return }
|
||||
let timeZone = TimeZone.current.localizedName(for: .generic, locale: .current) ?? TimeZone.current.identifier
|
||||
if let sunrise = solar.sunrise {
|
||||
sunriseTimeLabel.text = dateFormatter.string(from: sunrise) + " \(timeZone)"
|
||||
}
|
||||
if let sunset = solar.sunset {
|
||||
sunsetTimeLabel.text = dateFormatter.string(from: sunset) + " \(timeZone)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+47
-22
@@ -8,37 +8,62 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class AlertsPriorityServiceTableViewCell: EQNBaseTableViewCell {
|
||||
|
||||
@objc var smartphoneNetwork: EQNReteSmartphone? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
@objc
|
||||
class AlertsPriorityServiceTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
override var headerText: String { NSLocalizedString("inapp_list", comment: "") }
|
||||
override var isRightArrowVisbile: Bool { true }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var descriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.Colors.darkGray
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var lastSubscriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.Colors.pureRed
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
||||
@IBOutlet private weak var lastSubscriptionLabel: UILabel!
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
containerView.addSubview(descriptionLabel)
|
||||
containerView.addSubview(lastSubscriptionLabel)
|
||||
|
||||
descriptionLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
descriptionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
lastSubscriptionLabel.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: .cardVerticalSpacing/2.0).isActive = true
|
||||
lastSubscriptionLabel.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
|
||||
lastSubscriptionLabel.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
|
||||
lastSubscriptionLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
headerLabel.text = NSLocalizedString("inapp_list", comment: "")
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
backgroundColor = AppTheme.Colors.cardBackgroundOrange
|
||||
descriptionLabel.text = NSLocalizedString("inapp_adv", comment: "")
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
guard let smartphoneNetwork = smartphoneNetwork else { return }
|
||||
// MARK: - Public
|
||||
|
||||
@objc
|
||||
func update(with smartphoneNetwork: EQNReteSmartphone?) {
|
||||
guard let smartphoneNetwork else { return }
|
||||
|
||||
lastSubscriptionLabel.text = subscriptionText(for: smartphoneNetwork.lastSubscriptionDiff)
|
||||
}
|
||||
|
||||
+77
-19
@@ -9,54 +9,112 @@
|
||||
import UIKit
|
||||
|
||||
|
||||
class AlertsSeismicNotificationCompactTableViewCell: EQNBaseTableViewCell {
|
||||
|
||||
@objc
|
||||
class AlertsSeismicNotificationCompactTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
typealias DefaultCompletion = () -> Void
|
||||
|
||||
@objc var onTapAlertTest: DefaultCompletion?
|
||||
@objc var onTapSimulator: DefaultCompletion?
|
||||
@objc var onTapHowItWorks: DefaultCompletion?
|
||||
@objc var onTapShareApp: DefaultCompletion?
|
||||
|
||||
override var isHeaderVisible: Bool { false }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var descriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.Colors.green
|
||||
label.font = .preferredFont(forTextStyle: .title3)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
||||
@IBOutlet private weak var testAlertButton: UIButton!
|
||||
@IBOutlet private weak var simulatorAlertButton: UIButton!
|
||||
@IBOutlet private weak var howItWorksAlertButton: UIButton!
|
||||
@IBOutlet private weak var shareAppButton: UIButton!
|
||||
private lazy var testAlertButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(testAlertTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
private lazy var simulatorAlertButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(simulatorTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
private lazy var howItWorksAlertButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(howItWorksTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var shareAppButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(shareAppTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
containerView.addSubview(descriptionLabel)
|
||||
containerView.addSubview(testAlertButton)
|
||||
containerView.addSubview(simulatorAlertButton)
|
||||
containerView.addSubview(howItWorksAlertButton)
|
||||
containerView.addSubview(shareAppButton)
|
||||
|
||||
descriptionLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardPadding).isActive = true
|
||||
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
descriptionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
testAlertButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
|
||||
simulatorAlertButton.heightAnchor.constraint(equalTo: testAlertButton.heightAnchor).isActive = true
|
||||
howItWorksAlertButton.heightAnchor.constraint(equalTo: testAlertButton.heightAnchor).isActive = true
|
||||
shareAppButton.heightAnchor.constraint(equalTo: testAlertButton.heightAnchor).isActive = true
|
||||
|
||||
testAlertButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
testAlertButton.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
|
||||
testAlertButton.trailingAnchor.constraint(equalTo: simulatorAlertButton.leadingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
simulatorAlertButton.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
|
||||
simulatorAlertButton.centerYAnchor.constraint(equalTo: testAlertButton.centerYAnchor).isActive = true
|
||||
simulatorAlertButton.widthAnchor.constraint(equalTo: testAlertButton.widthAnchor).isActive = true
|
||||
|
||||
howItWorksAlertButton.topAnchor.constraint(equalTo: testAlertButton.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
howItWorksAlertButton.leadingAnchor.constraint(equalTo: testAlertButton.leadingAnchor).isActive = true
|
||||
howItWorksAlertButton.trailingAnchor.constraint(equalTo: shareAppButton.leadingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
shareAppButton.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
|
||||
shareAppButton.centerYAnchor.constraint(equalTo: howItWorksAlertButton.centerYAnchor).isActive = true
|
||||
shareAppButton.widthAnchor.constraint(equalTo: howItWorksAlertButton.widthAnchor).isActive = true
|
||||
shareAppButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
backgroundColor = AppTheme.Colors.cardBackgroundGreen
|
||||
descriptionLabel.text = NSLocalizedString("main_nodetection", comment: "")
|
||||
testAlertButton.setLocalizedTitle(key: "main_alerttest", uppercased: true, emoji: "🚨")
|
||||
simulatorAlertButton.setLocalizedTitle(key: "main_simulator", uppercased: true, emoji: "⏱")
|
||||
howItWorksAlertButton.setLocalizedTitle(key: "main_how_it_work", uppercased: true, emoji: "💡")
|
||||
shareAppButton.setLocalizedTitle(key: "main_share_app", uppercased: true, emoji: "👥")
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction private func testAlertTapped() {
|
||||
@objc private func testAlertTapped(_ sender: UIButton) {
|
||||
onTapAlertTest?()
|
||||
}
|
||||
|
||||
@IBAction private func simulatorTapped() {
|
||||
@objc private func simulatorTapped(_ sender: UIButton) {
|
||||
onTapSimulator?()
|
||||
}
|
||||
|
||||
@IBAction private func howItWorksTapped() {
|
||||
@objc private func howItWorksTapped(_ sender: UIButton) {
|
||||
onTapHowItWorks?()
|
||||
}
|
||||
|
||||
@IBAction private func shareAppTapped() {
|
||||
@objc private func shareAppTapped(_ sender: UIButton) {
|
||||
onTapShareApp?()
|
||||
}
|
||||
}
|
||||
|
||||
+152
-58
@@ -10,78 +10,173 @@ import UIKit
|
||||
import MapKit
|
||||
import Shogun
|
||||
|
||||
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMapViewDelegate {
|
||||
|
||||
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseContainerTableViewCell, MKMapViewDelegate {
|
||||
|
||||
override var isHeaderVisible: Bool { false }
|
||||
|
||||
typealias DefaultCompletion = () -> Void
|
||||
|
||||
@objc var notification: EQNRealtimePushNotification? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
@objc var onTapOpenTwitter: DefaultCompletion?
|
||||
@objc var onTapRateApp: DefaultCompletion?
|
||||
@objc var onTapClose: DefaultCompletion?
|
||||
@objc var onTapShareApp: DefaultCompletion?
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var notificationTitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .title1)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var notificationIntensityLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .title1)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var notificationDescriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var mapView: MKMapView = {
|
||||
let mapView = MKMapView()
|
||||
mapView.translatesAutoresizingMaskIntoConstraints = false
|
||||
mapView.delegate = self
|
||||
mapView.isScrollEnabled = false
|
||||
mapView.isZoomEnabled = false
|
||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||
return mapView
|
||||
}()
|
||||
|
||||
private lazy var shareButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(shareAppTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var rateAppButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(rateAppTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var viewOnTwitterButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(viewInTwitterTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var closeButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(closeTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var descriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
@IBOutlet weak var notificationTitleLabel: UILabel!
|
||||
@IBOutlet weak var notificationDescriptionLabel: UILabel!
|
||||
@IBOutlet weak var notificationIntensityLabel: UILabel!
|
||||
@IBOutlet weak var waveTimeLabel: UILabel!
|
||||
@IBOutlet weak var mapView: MKMapView! {
|
||||
didSet {
|
||||
mapView.delegate = self
|
||||
mapView.isScrollEnabled = false
|
||||
mapView.isZoomEnabled = false
|
||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||
}
|
||||
}
|
||||
@IBOutlet private weak var shareButton: UIButton!
|
||||
@IBOutlet private weak var rateAppButton: UIButton!
|
||||
@IBOutlet private weak var viewOnTwitterButton: UIButton!
|
||||
@IBOutlet private weak var closeButton: UIButton!
|
||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
||||
private var impactTimestamp: Date?
|
||||
private var countdownTimer: Timer?
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
setUI()
|
||||
let stackView = UIStackView(arrangedSubviews: [shareButton, rateAppButton])
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .horizontal
|
||||
stackView.distribution = .fillEqually
|
||||
stackView.spacing = .cardVerticalSpacing
|
||||
|
||||
containerView.addSubview(notificationTitleLabel)
|
||||
containerView.addSubview(notificationIntensityLabel)
|
||||
containerView.addSubview(notificationDescriptionLabel)
|
||||
containerView.addSubview(mapView)
|
||||
containerView.addSubview(stackView)
|
||||
containerView.addSubview(viewOnTwitterButton)
|
||||
containerView.addSubview(descriptionLabel)
|
||||
containerView.addSubview(closeButton)
|
||||
|
||||
notificationTitleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: .cardPadding).isActive = true
|
||||
notificationTitleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
notificationTitleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
notificationIntensityLabel.topAnchor.constraint(equalTo: notificationTitleLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
notificationIntensityLabel.leadingAnchor.constraint(equalTo: notificationTitleLabel.leadingAnchor).isActive = true
|
||||
notificationIntensityLabel.trailingAnchor.constraint(equalTo: notificationTitleLabel.trailingAnchor).isActive = true
|
||||
|
||||
notificationDescriptionLabel.topAnchor.constraint(equalTo: notificationIntensityLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
notificationDescriptionLabel.leadingAnchor.constraint(equalTo: notificationTitleLabel.leadingAnchor).isActive = true
|
||||
notificationDescriptionLabel.trailingAnchor.constraint(equalTo: notificationTitleLabel.trailingAnchor).isActive = true
|
||||
|
||||
mapView.topAnchor.constraint(equalTo: notificationDescriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
mapView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
|
||||
mapView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
|
||||
mapView.heightAnchor.constraint(greaterThanOrEqualToConstant: 240.0).isActive = true
|
||||
|
||||
shareButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
|
||||
rateAppButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
|
||||
viewOnTwitterButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
|
||||
closeButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
|
||||
stackView.topAnchor.constraint(equalTo: mapView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
stackView.leadingAnchor.constraint(equalTo: notificationDescriptionLabel.leadingAnchor).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: notificationDescriptionLabel.trailingAnchor).isActive = true
|
||||
|
||||
viewOnTwitterButton.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
viewOnTwitterButton.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
|
||||
viewOnTwitterButton.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
|
||||
|
||||
descriptionLabel.topAnchor.constraint(equalTo: viewOnTwitterButton.bottomAnchor, constant: .cardPadding).isActive = true
|
||||
descriptionLabel.leadingAnchor.constraint(equalTo: viewOnTwitterButton.leadingAnchor).isActive = true
|
||||
descriptionLabel.trailingAnchor.constraint(equalTo: viewOnTwitterButton.trailingAnchor).isActive = true
|
||||
|
||||
closeButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
closeButton.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
|
||||
closeButton.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
|
||||
closeButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
shareButton.setLocalizedTitle(key: "main_share_app")
|
||||
rateAppButton.setLocalizedTitle(key: "main_vote")
|
||||
viewOnTwitterButton.setLocalizedTitle(key: "main_twitter_see")
|
||||
closeButton.setLocalizedTitle(key: "official_close")
|
||||
descriptionLabel.text = NSLocalizedString("map_smartphone_magnitude", comment: "")
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
private func setUI() {
|
||||
shareButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
||||
rateAppButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
||||
viewOnTwitterButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
||||
closeButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
@objc
|
||||
func update(with notification: EQNRealtimePushNotification?) {
|
||||
// clearn any other previous notifications
|
||||
notificationTitleLabel.text = ""
|
||||
notificationDescriptionLabel.text = ""
|
||||
notificationTitleLabel.text = "Sisma rilevato a 150km (TEST)"
|
||||
notificationIntensityLabel.text = "Previsto uno scuotimento forte"
|
||||
notificationDescriptionLabel.text = "Distanza 150 km - 13 minuti fa"
|
||||
mapView.removeAnnotations(mapView.annotations)
|
||||
|
||||
guard let notification = notification else { return }
|
||||
|
||||
containerView.backgroundColor = backgroundColor(for: notification.relativeIntensity())
|
||||
|
||||
backgroundColor = backgroundColor(for: notification.relativeIntensity())
|
||||
notificationTitleLabel.text = notification.title
|
||||
notificationIntensityLabel.text = notification.displayBody
|
||||
notificationIntensityLabel.textColor = notification.relativeIntensityColor
|
||||
@@ -95,7 +190,6 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
||||
notificationDescriptionLabel.text = ""
|
||||
+ NSLocalizedString("official_card_distance", comment: "") + " \(distanceRound) km"
|
||||
+ " - " + EQNUtility.formattedString(forTimeDifference: difference)
|
||||
+ "\n" + String(format: NSLocalizedString("map_number", comment: ""), "\(notification.counter)")
|
||||
}
|
||||
|
||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||
@@ -118,37 +212,37 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
||||
annotationView.title = annotation.title
|
||||
return annotationView
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction private func shareAppTapped(_ sender: UIButton) {
|
||||
@objc private func shareAppTapped(_ sender: UIButton) {
|
||||
onTapShareApp?()
|
||||
}
|
||||
|
||||
@IBAction private func rateAppTapped(_ sender: UIButton) {
|
||||
@objc private func rateAppTapped(_ sender: UIButton) {
|
||||
onTapRateApp?()
|
||||
}
|
||||
|
||||
@IBAction private func viewInTwitterTapped(_ sender: UIButton) {
|
||||
@objc private func viewInTwitterTapped(_ sender: UIButton) {
|
||||
onTapOpenTwitter?()
|
||||
}
|
||||
|
||||
@IBAction private func closeTapped(_ sender: UIButton) {
|
||||
@objc private func closeTapped(_ sender: UIButton) {
|
||||
onTapClose?()
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
// MARK: - Private
|
||||
|
||||
private func backgroundColor(for intensity: Double) -> UIColor {
|
||||
switch intensity {
|
||||
case _ where intensity < 0.004:
|
||||
return UIColor(named: "Gray (card background)")!
|
||||
return AppTheme.Colors.cardBackgroundGray
|
||||
case _ where intensity < 0.30:
|
||||
return UIColor(named: "Green (card background)")!
|
||||
return AppTheme.Colors.cardBackgroundGreen
|
||||
case _ where intensity < 0.70:
|
||||
return UIColor(named: "Yellow (card background)")!
|
||||
return AppTheme.Colors.cardBackgroundYellow
|
||||
default:
|
||||
return UIColor(named: "Red (card background)")!
|
||||
return AppTheme.Colors.cardBackgroundRed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-30
@@ -7,49 +7,83 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
class AlertsSmartphoneNetworkTableViewCell: EQNBaseTableViewCell {
|
||||
|
||||
@objc var smartphoneNetwork: EQNReteSmartphone? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
@objc
|
||||
class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
@objc var onTapButton: (() -> Void)?
|
||||
|
||||
override var headerText: String { NSLocalizedString("main_network", comment: "") }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var counterLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.Colors.green
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var descriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var coverageButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(localCovergeTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var smartphoneCounterLabel: UILabel!
|
||||
@IBOutlet private weak var coverageDescriptionLabel: UILabel!
|
||||
@IBOutlet private weak var localCoverageButton: UIButton!
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
containerView.addSubview(counterLabel)
|
||||
containerView.addSubview(descriptionLabel)
|
||||
containerView.addSubview(coverageButton)
|
||||
|
||||
counterLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
counterLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
counterLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
descriptionLabel.topAnchor.constraint(equalTo: counterLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
descriptionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
coverageButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
|
||||
coverageButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
coverageButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
coverageButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
coverageButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
headerLabel.text = NSLocalizedString("main_network", comment: "")
|
||||
coverageDescriptionLabel.text = NSLocalizedString("main_monitoring_currently2", comment: "")
|
||||
localCoverageButton.setLocalizedTitle(key: "main_coverage", uppercased: true, emoji: "🗺")
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
coverageButton.setLocalizedTitle(key: "main_coverage", uppercased: true, emoji: "🗺")
|
||||
descriptionLabel.text = NSLocalizedString("main_monitoring_currently2", comment: "")
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
guard let smartphoneNetwork = smartphoneNetwork else { return }
|
||||
|
||||
smartphoneCounterLabel.text = "\(smartphoneNetwork.counterSmartphones)"
|
||||
// MARK: - Public
|
||||
|
||||
@objc
|
||||
func update(with smartphoneNetwork: EQNReteSmartphone?) {
|
||||
guard let smartphoneNetwork else { return }
|
||||
counterLabel.text = "\(smartphoneNetwork.counterSmartphones)"
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction private func localCovergeTapped(_ sender: UIButton) {
|
||||
@objc private func localCovergeTapped(_ sender: UIButton) {
|
||||
onTapButton?()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,8 +98,8 @@ class PasquakesMapViewController: EQNBaseMapViewController {
|
||||
.first
|
||||
|
||||
// controlliamo che sia inferiore al raggio massimo impostato per le notifiche
|
||||
if let radiusLow = Double(EQNAllertaSismica.shared().raggioSismiLievi),
|
||||
let radiusStrong = Double(EQNAllertaSismica.shared().raggioSismiForti),
|
||||
if let radiusLow = Double(EQNSettingRealTimeAlert.shared.raggioSismiLievi),
|
||||
let radiusStrong = Double(EQNSettingRealTimeAlert.shared.raggioSismiForti),
|
||||
let nearestPastquake = nearestPastquake {
|
||||
let radius = max(radiusLow, radiusStrong)
|
||||
if abs(nearestPastquake.coordinate.distance(from: userPosition)) < radius {
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// EQNBackgrounPositionDebugViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 14/08/23.
|
||||
// Copyright © 2023 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreLocation
|
||||
|
||||
|
||||
class EQNBackgroundPositionDebugViewController: UITableViewController {
|
||||
|
||||
private var positions = [EQNBackgroundPosition]()
|
||||
private let formatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.timeStyle = .medium
|
||||
formatter.dateStyle = .medium
|
||||
return formatter
|
||||
}()
|
||||
private let helper = EQNBackgroundPositionDebugHelper()
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
convenience init() {
|
||||
self.init(style: .insetGrouped)
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
configureUI()
|
||||
loadData()
|
||||
}
|
||||
|
||||
private func configureUI() {
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 60.0
|
||||
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(onTapDeleteButton(_:)))
|
||||
}
|
||||
|
||||
private func loadData() {
|
||||
positions = helper.loadPosition()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
positions.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let position = positions[indexPath.row]
|
||||
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "PositionCell")
|
||||
cell.textLabel?.text = formatter.string(from: position.date)
|
||||
cell.detailTextLabel?.text = String(format: "Lat: %.4f - Lon: %.4f", position.coordinate.latitude, position.coordinate.longitude)
|
||||
var imageView: UIImageView?
|
||||
switch position.request {
|
||||
case .none:
|
||||
imageView = nil
|
||||
case .some(true):
|
||||
imageView = .init(image: .init(systemName: "checkmark.circle.fill"))
|
||||
imageView?.tintColor = AppTheme.Colors.green
|
||||
case .some(false):
|
||||
imageView = .init(image: .init(systemName: "x.circle.fill"))
|
||||
imageView?.tintColor = AppTheme.Colors.red
|
||||
}
|
||||
cell.accessoryView = imageView
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func onTapDeleteButton(_ sender: UIBarButtonItem) {
|
||||
helper.resetPositions()
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
@@ -92,12 +92,9 @@
|
||||
}
|
||||
|
||||
// Determine the view width to use for the ad width.
|
||||
CGRect frame = self.view.frame;
|
||||
// Here safe area is taken into account, hence the view frame is used after
|
||||
// the view has been laid out.
|
||||
if (@available(iOS 11.0, *)) {
|
||||
frame = UIEdgeInsetsInsetRect(self.view.frame, self.view.safeAreaInsets);
|
||||
}
|
||||
CGRect frame = UIEdgeInsetsInsetRect(self.view.frame, self.view.safeAreaInsets);
|
||||
CGFloat viewWidth = frame.size.width;
|
||||
|
||||
// Step 3 - Get Adaptive GADAdSize and set the ad view.
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#import "EQNMainTabBarController.h"
|
||||
#import "AppDelegate.h"
|
||||
#import "EQNBaseViewController.h"
|
||||
#import "SettingsBaseViewController.h"
|
||||
#import "EQNManager.h"
|
||||
#import "ServerRequest.h"
|
||||
|
||||
@@ -20,9 +19,6 @@
|
||||
|
||||
@implementation EQNMainTabBarController
|
||||
|
||||
static NSString * const SegueIdentifierSettings = @"ShowSettings";
|
||||
static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
|
||||
- (void)viewDidLoad
|
||||
@@ -41,6 +37,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
||||
object:nil];
|
||||
|
||||
[self sincronizza];
|
||||
[self migrationV5_8];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
@@ -53,6 +50,23 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
||||
self.tabBar.items[EQNTabBarSectionImpostazioni].title = [NSLocalizedString(@"drawer_main_settings", comment: "") capitalizedString];
|
||||
}
|
||||
|
||||
- (void)migrationV5_8
|
||||
{
|
||||
// forziamo il salvataggio delle impostazioni di notifica, perchè i vari valori devono essere migrati
|
||||
BOOL alreadyMigrated = [NSUserDefaults.standardUserDefaults boolForKey:NSUserDefaults.SaveSettingsNotificationMigrationV5_8];
|
||||
if (alreadyMigrated) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[MIGRATION] perform notification settings save");
|
||||
[SettingsBaseTableViewController saveSettingsWithCompletion:^(BOOL success) {
|
||||
if (success) {
|
||||
NSLog(@"[MIGRATION] settings saved");
|
||||
[NSUserDefaults.standardUserDefaults setBool:true forKey:NSUserDefaults.SaveSettingsNotificationMigrationV5_8];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Notification
|
||||
|
||||
- (void)serverRegistrationFailedNotification:(NSNotification *)notification
|
||||
@@ -119,7 +133,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
||||
// if user switch from settings page, we need to force a settings save
|
||||
UIViewController *controller = [self getTopControllerFromController:tabBarController.selectedViewController];
|
||||
if ([controller isKindOfClass:[SettingsViewController class]]) {
|
||||
[SettingsBaseViewController saveSettings];
|
||||
[SettingsBaseTableViewController saveSettings];
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
//
|
||||
// SubscriptionDetailViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 29/07/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SafariServices
|
||||
import StoreKit
|
||||
|
||||
|
||||
class SubscriptionDetailViewController: UIViewController {
|
||||
|
||||
/// Enable this allows shake to enable the current subscription
|
||||
private static let ShakeToEnableSubscription = false
|
||||
|
||||
var product: SKProduct? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet private weak var containerView: UIView!
|
||||
@IBOutlet private weak var productTitleLabel: UILabel!
|
||||
@IBOutlet private weak var productImageView: UIImageView!
|
||||
@IBOutlet private weak var productDescriptionLabel: UILabel!
|
||||
@IBOutlet private weak var subscriptionDetailsLabel: UILabel!
|
||||
@IBOutlet private weak var openPrivacyButton: UIButton!
|
||||
@IBOutlet private weak var openTermsButton: UIButton!
|
||||
@IBOutlet private weak var purchaseRecapLabel: UILabel!
|
||||
@IBOutlet private weak var productPriceLabel: UILabel!
|
||||
@IBOutlet private weak var purchaseButton: UIButton!
|
||||
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseNotification(_:)),
|
||||
name: .EQNInAppPurchaseDidComplete,
|
||||
object: nil)
|
||||
|
||||
updateUI()
|
||||
setupUI()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupUI() {
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
guard let product = product, isViewLoaded else { return }
|
||||
|
||||
productImageView.image = VersioneProProducts.image(for: product.productIdentifier)
|
||||
productTitleLabel.text = product.localizedTitle
|
||||
productDescriptionLabel.text = product.localizedDescription
|
||||
|
||||
var purchaseRecapString = ""
|
||||
var subscriptionDetailsString = ""
|
||||
switch product.productIdentifier {
|
||||
case VersioneProProducts.Identifier.Subscription10kMonthly,
|
||||
VersioneProProducts.Identifier.Subscription100kMonthly:
|
||||
purchaseRecapString = "inapp_monthly_payment"
|
||||
subscriptionDetailsString = "inapp_detail_description"
|
||||
case VersioneProProducts.Identifier.Subscription100kYearly,
|
||||
VersioneProProducts.Identifier.Subscription100kYearlyDiscounted,
|
||||
VersioneProProducts.Identifier.Subscription10kYearly,
|
||||
VersioneProProducts.Identifier.Subscription10kYearlyDiscounted:
|
||||
purchaseRecapString = "inapp_yearly_payment"
|
||||
subscriptionDetailsString = "inapp_detail_description"
|
||||
case VersioneProProducts.Identifier.Subscription10kPerpetual,
|
||||
VersioneProProducts.Identifier.Subscription100kPerpetual:
|
||||
purchaseRecapString = "inapp_lifetime_payment"
|
||||
subscriptionDetailsString = "inapp_lifetime_detail_description"
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
subscriptionDetailsLabel.text = NSLocalizedString(subscriptionDetailsString, comment: "")
|
||||
openPrivacyButton.setTitle(NSLocalizedString("network_pro_privacy_disclaimer", comment: ""), for: .normal)
|
||||
openTermsButton.setTitle(NSLocalizedString("network_pro_terms_conditions", comment: ""), for: .normal)
|
||||
|
||||
purchaseRecapLabel.text = "\(product.localizedDescription), \(NSLocalizedString(purchaseRecapString, comment: ""))"
|
||||
|
||||
priceFormatter.locale = product.priceLocale
|
||||
productPriceLabel.text = priceFormatter.string(from: product.price)
|
||||
purchaseButton.setTitle(NSLocalizedString("inapp_purchase", comment: ""), for: .normal)
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func handlePurchaseNotification(_ notification: Notification) {
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func openExternalLinkTapped(_ sender: UIButton) {
|
||||
var linkUrl: URL?
|
||||
if sender == openPrivacyButton {
|
||||
linkUrl = URL(string: "\(EQNWebsiteAddress)/privacy/")
|
||||
} else if sender == openTermsButton {
|
||||
linkUrl = URL(string: "\(EQNWebsiteAddress)/terms-conditions/")
|
||||
|
||||
}
|
||||
|
||||
if let url = linkUrl {
|
||||
let controller = SFSafariViewController(url: url)
|
||||
present(controller, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func subscribeTapped(_ sender: UIButton) {
|
||||
guard let product = product else { return }
|
||||
|
||||
VersioneProProducts.store.buyProduct(product)
|
||||
}
|
||||
|
||||
// MARK: - Helper
|
||||
|
||||
private var priceFormatter: NumberFormatter = {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.formatterBehavior = .behavior10_4
|
||||
formatter.numberStyle = .currency
|
||||
return formatter
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
extension SubscriptionDetailViewController {
|
||||
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
||||
guard let product = product, event?.subtype == .motionShake, Self.ShakeToEnableSubscription else {
|
||||
return
|
||||
}
|
||||
|
||||
let alert = UIAlertController(title: "🧑💻", message: "Please select an action", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Reset all purchases", style: .default) { action in
|
||||
EQNPurchaseUtility.resetInAppPurchases()
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Activate this subscription", style: .default) { action in
|
||||
EQNPurchaseUtility.simulateProPurchase(identifier: product.productIdentifier)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
//
|
||||
// SubscriptionDetailsTableViewCell.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 18/06/24.
|
||||
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
class SubscriptionDetailsTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
var onTapPrivacy: () -> Void = { }
|
||||
var onTapTerms: () -> Void = { }
|
||||
var onTapPurchase: () -> Void = { }
|
||||
var onChangePlan: (_ type: EQNInAppProducts.Plan) -> Void = { _ in }
|
||||
|
||||
override var isHeaderVisible: Bool { false }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
lazy var planSegmentedControl: UISegmentedControl = {
|
||||
let control = UISegmentedControl(items: EQNInAppProducts.Plan.allCases.map(\.localizedTitle))
|
||||
control.translatesAutoresizingMaskIntoConstraints = false
|
||||
control.addTarget(self, action: #selector(onChangeSegmentedControl(_:)), for: .valueChanged)
|
||||
return control
|
||||
}()
|
||||
|
||||
lazy var productTitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .title1)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var productImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: .init(named: "top_100k"))
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.heightAnchor.constraint(greaterThanOrEqualToConstant: 50.0).isActive = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
lazy var subscriptionDetailsLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .justified
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var openPrivacyButton: UIButton = {
|
||||
let button = UIButton(type: .system)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.addTarget(self, action: #selector(onTapOpenPrivacyButton(_:)), for: .touchUpInside)
|
||||
button.contentHorizontalAlignment = .leading
|
||||
return button
|
||||
}()
|
||||
|
||||
lazy var openTermsButton: UIButton = {
|
||||
let button = UIButton(type: .system)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.addTarget(self, action: #selector(onTapOpenTermsButton(_:)), for: .touchUpInside)
|
||||
button.contentHorizontalAlignment = .leading
|
||||
return button
|
||||
}()
|
||||
|
||||
lazy var purchaseRecapLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .headline)
|
||||
label.numberOfLines = 0
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var productPriceLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||
label.numberOfLines = 0
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var purchaseButton: UIButton = {
|
||||
let button = UIButton(type: .system)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.addTarget(self, action: #selector(onTapPurchaseButton(_:)), for: .touchUpInside)
|
||||
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
|
||||
button.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
|
||||
button.backgroundColor = .systemGroupedBackground
|
||||
button.eqn_applyShadowAndRoundedCorners()
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
containerView.addSubview(planSegmentedControl)
|
||||
containerView.addSubview(productTitleLabel)
|
||||
containerView.addSubview(productImageView)
|
||||
containerView.addSubview(subscriptionDetailsLabel)
|
||||
containerView.addSubview(openPrivacyButton)
|
||||
containerView.addSubview(openTermsButton)
|
||||
containerView.addSubview(purchaseRecapLabel)
|
||||
containerView.addSubview(productPriceLabel)
|
||||
containerView.addSubview(purchaseButton)
|
||||
|
||||
let leading: NSLayoutXAxisAnchor = planSegmentedControl.leadingAnchor
|
||||
let trailing: NSLayoutXAxisAnchor = planSegmentedControl.trailingAnchor
|
||||
planSegmentedControl.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
planSegmentedControl.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
planSegmentedControl.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
productTitleLabel.topAnchor.constraint(equalTo: planSegmentedControl.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
productTitleLabel.leadingAnchor.constraint(equalTo: leading, constant: .cardPadding).isActive = true
|
||||
productTitleLabel.trailingAnchor.constraint(equalTo: trailing, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
productImageView.topAnchor.constraint(equalTo: productTitleLabel.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
|
||||
productImageView.leadingAnchor.constraint(equalTo: leading).isActive = true
|
||||
productImageView.trailingAnchor.constraint(equalTo: trailing).isActive = true
|
||||
|
||||
purchaseRecapLabel.topAnchor.constraint(equalTo: productImageView.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
|
||||
purchaseRecapLabel.leadingAnchor.constraint(equalTo: leading).isActive = true
|
||||
purchaseRecapLabel.trailingAnchor.constraint(equalTo: trailing).isActive = true
|
||||
productPriceLabel.topAnchor.constraint(equalTo: purchaseRecapLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
productPriceLabel.leadingAnchor.constraint(equalTo: leading).isActive = true
|
||||
productPriceLabel.trailingAnchor.constraint(equalTo: trailing).isActive = true
|
||||
|
||||
purchaseButton.topAnchor.constraint(equalTo: productPriceLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
purchaseButton.leadingAnchor.constraint(equalTo: leading).isActive = true
|
||||
purchaseButton.trailingAnchor.constraint(equalTo: trailing).isActive = true
|
||||
|
||||
subscriptionDetailsLabel.topAnchor.constraint(equalTo: purchaseButton.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
|
||||
subscriptionDetailsLabel.leadingAnchor.constraint(equalTo: leading).isActive = true
|
||||
subscriptionDetailsLabel.trailingAnchor.constraint(equalTo: trailing).isActive = true
|
||||
|
||||
openPrivacyButton.topAnchor.constraint(equalTo: subscriptionDetailsLabel.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
|
||||
openPrivacyButton.leadingAnchor.constraint(equalTo: leading).isActive = true
|
||||
openPrivacyButton.trailingAnchor.constraint(equalTo: trailing).isActive = true
|
||||
|
||||
openTermsButton.topAnchor.constraint(equalTo: openPrivacyButton.bottomAnchor, constant: .cardPadding).isActive = true
|
||||
openTermsButton.leadingAnchor.constraint(equalTo: leading).isActive = true
|
||||
openTermsButton.trailingAnchor.constraint(equalTo: trailing).isActive = true
|
||||
openTermsButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.x2.negative).isActive = true
|
||||
}
|
||||
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
openPrivacyButton.setTitle(NSLocalizedString("network_pro_privacy_disclaimer", comment: ""), for: .normal)
|
||||
openTermsButton.setTitle(NSLocalizedString("network_pro_terms_conditions", comment: ""), for: .normal)
|
||||
purchaseButton.setTitle(NSLocalizedString("inapp_purchase", comment: ""), for: .normal)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func onTapOpenPrivacyButton(_ sender: UIButton) {
|
||||
onTapPrivacy()
|
||||
}
|
||||
|
||||
@objc private func onTapOpenTermsButton(_ sender: UIButton) {
|
||||
onTapTerms()
|
||||
}
|
||||
|
||||
@objc private func onTapPurchaseButton(_ sender: UIButton) {
|
||||
onTapPurchase()
|
||||
}
|
||||
|
||||
@objc private func onChangeSegmentedControl(_ sender: UISegmentedControl) {
|
||||
let type: EQNInAppProducts.Plan = .from(index: sender.selectedSegmentIndex)
|
||||
onChangePlan(type)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension EQNInAppProducts.Plan {
|
||||
var index: Int {
|
||||
switch self {
|
||||
case .monthly: 0
|
||||
case .yearly: 1
|
||||
case .perpetual: 2
|
||||
}
|
||||
}
|
||||
|
||||
static func from(index: Int) -> Self {
|
||||
switch index {
|
||||
case 0: .monthly
|
||||
case 1: .yearly
|
||||
default: .perpetual
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// SubscriptionDetailsViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 18/06/24.
|
||||
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import StoreKit
|
||||
import SafariServices
|
||||
import Shogun
|
||||
|
||||
|
||||
class SubscriptionDetailsViewController: UITableViewController {
|
||||
|
||||
/// Enable this allows shake to enable the current subscription
|
||||
private static let ShakeToEnableSubscription = false
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
private let products: [EQNInAppProducts]
|
||||
private var selectedProduct: EQNInAppProducts {
|
||||
didSet {
|
||||
onProductSelected()
|
||||
}
|
||||
}
|
||||
|
||||
private var priceFormatter: NumberFormatter = {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.formatterBehavior = .behavior10_4
|
||||
formatter.numberStyle = .currency
|
||||
return formatter
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(
|
||||
products: [EQNInAppProducts]
|
||||
) {
|
||||
self.products = products
|
||||
self.selectedProduct = products.first(where: { $0.plan == .yearly }) ?? products.first!
|
||||
super.init(style: .plain)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("Please use init(products:) instead.")
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
configureUI()
|
||||
addObservers()
|
||||
}
|
||||
|
||||
private func addObservers() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseNotification(_:)),
|
||||
name: .EQNInAppPurchaseDidComplete,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
private func configureUI() {
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 2000.0
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = .systemGroupedBackground
|
||||
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
|
||||
tableView.registerCell(for: SubscriptionDetailsTableViewCell.self)
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc private func handlePurchaseNotification(_ notification: Notification) {
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Table view delegate & data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
1
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionDetailsTableViewCell.self, for: indexPath)
|
||||
cell.selectionStyle = .none
|
||||
cell.productTitleLabel.text = selectedProduct.product.localizedTitle
|
||||
cell.productImageView.image = selectedProduct.category.image
|
||||
|
||||
var purchaseRecapString = ""
|
||||
var subscriptionDetailsString = ""
|
||||
switch selectedProduct.productIdentifier {
|
||||
case EQNInAppProducts.Identifier.Subscription10kMonthly,
|
||||
EQNInAppProducts.Identifier.Subscription100kMonthly:
|
||||
purchaseRecapString = "inapp_monthly_payment"
|
||||
subscriptionDetailsString = "inapp_detail_description"
|
||||
case EQNInAppProducts.Identifier.Subscription100kYearly,
|
||||
EQNInAppProducts.Identifier.Subscription100kYearlyDiscounted,
|
||||
EQNInAppProducts.Identifier.Subscription10kYearly,
|
||||
EQNInAppProducts.Identifier.Subscription10kYearlyDiscounted:
|
||||
purchaseRecapString = "inapp_yearly_payment"
|
||||
subscriptionDetailsString = "inapp_detail_description"
|
||||
case EQNInAppProducts.Identifier.Subscription10kPerpetual,
|
||||
EQNInAppProducts.Identifier.Subscription100kPerpetual:
|
||||
purchaseRecapString = "inapp_lifetime_payment"
|
||||
subscriptionDetailsString = "inapp_lifetime_detail_description"
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
cell.subscriptionDetailsLabel.text = NSLocalizedString(subscriptionDetailsString, comment: "")
|
||||
cell.onTapPrivacy = { [weak self] in
|
||||
self?.openExternalLink("\(EQNWebsiteAddress)/privacy/")
|
||||
}
|
||||
cell.onTapTerms = { [weak self] in
|
||||
self?.openExternalLink("\(EQNWebsiteAddress)/terms-conditions/")
|
||||
}
|
||||
cell.onTapPurchase = { [weak self] in
|
||||
self?.purchaseSelectedProduct()
|
||||
}
|
||||
cell.onChangePlan = { [weak self] type in
|
||||
if let product = self?.productFromProductType(type) {
|
||||
self?.selectedProduct = product
|
||||
}
|
||||
}
|
||||
cell.planSegmentedControl.selectedSegmentIndex = selectedProduct.plan.index
|
||||
cell.purchaseRecapLabel.text = "\(selectedProduct.product.localizedDescription), \(NSLocalizedString(purchaseRecapString, comment: ""))"
|
||||
cell.productPriceLabel.text = priceFormatter.string(from: selectedProduct.product.price)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func onProductSelected() {
|
||||
priceFormatter.locale = selectedProduct.product.priceLocale
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func openExternalLink(_ stringUrl: String) {
|
||||
if let url = URL(string: stringUrl) {
|
||||
let controller = SFSafariViewController(url: url)
|
||||
present(controller, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func purchaseSelectedProduct() {
|
||||
EQNInAppProducts.store.buyProduct(selectedProduct.product)
|
||||
}
|
||||
|
||||
private func productFromProductType(_ type: EQNInAppProducts.Plan) -> EQNInAppProducts? {
|
||||
let product: EQNInAppProducts?
|
||||
switch type {
|
||||
case .monthly:
|
||||
product = products.first { $0.plan == .monthly }
|
||||
case .yearly:
|
||||
product = products.first { $0.plan == .yearly }
|
||||
case .perpetual:
|
||||
product = products.first { $0.plan == .perpetual }
|
||||
}
|
||||
return product
|
||||
}
|
||||
}
|
||||
|
||||
extension SubscriptionDetailsViewController {
|
||||
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
||||
guard event?.subtype == .motionShake, Self.ShakeToEnableSubscription else {
|
||||
return
|
||||
}
|
||||
|
||||
let alert = UIAlertController(title: "🧑💻", message: "Please select an action", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Reset all purchases", style: .default) { action in
|
||||
EQNPurchaseUtility.resetInAppPurchases()
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Activate this subscription", style: .default) { action in
|
||||
EQNPurchaseUtility.simulateProPurchase(identifier: self.selectedProduct.productIdentifier)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
+68
-44
@@ -9,57 +9,81 @@
|
||||
import UIKit
|
||||
import StoreKit
|
||||
|
||||
class SubscriptionProductTableViewCell: UITableViewCell {
|
||||
class SubscriptionProductTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
override var isHeaderVisible: Bool { false }
|
||||
override var isRightArrowVisbile: Bool { true }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var productImageView: UIImageView = {
|
||||
let imageView = UIImageView(frame: .zero)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
return imageView
|
||||
}()
|
||||
|
||||
var product: SKProduct? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
var availability: EQNPurchaseAvailability? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
private lazy var productTitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .headline)
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
@IBOutlet private weak var productImageView: UIImageView!
|
||||
@IBOutlet private weak var productTitleLabel: UILabel!
|
||||
@IBOutlet private weak var productDescriptionLabel: UILabel?
|
||||
@IBOutlet private weak var productInfoLabel: UILabel!
|
||||
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
// force an inset to have the same style of EQNBaseTableViewCell
|
||||
override var frame: CGRect {
|
||||
get {
|
||||
return super.frame
|
||||
}
|
||||
set (newFrame) {
|
||||
let inset: CGFloat = 8
|
||||
var frame = newFrame
|
||||
frame.origin.x += inset
|
||||
frame.size.width -= 2 * inset
|
||||
super.frame = frame
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func updateUI() {
|
||||
guard let product = product else { return }
|
||||
private lazy var productInfoLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
productImageView.image = VersioneProProducts.image(for: product.productIdentifier)
|
||||
productTitleLabel.text = product.localizedTitle
|
||||
productDescriptionLabel?.text = product.localizedDescription
|
||||
// MARK: - Internal
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
let infoKey = VersioneProProducts.is100kSubscription(for: product.productIdentifier) ? "inapp_available_100k" : "inapp_available_10k"
|
||||
let counter = availability(for: product.productIdentifier)
|
||||
containerView.addSubview(productImageView)
|
||||
containerView.addSubview(productTitleLabel)
|
||||
containerView.addSubview(productInfoLabel)
|
||||
|
||||
productImageView.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
|
||||
productImageView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
|
||||
|
||||
productTitleLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
productTitleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
productImageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
productImageView.trailingAnchor.constraint(equalTo: productTitleLabel.leadingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
productImageView.centerYAnchor.constraint(equalTo: productTitleLabel.centerYAnchor).isActive = true
|
||||
|
||||
productInfoLabel.topAnchor.constraint(equalTo: productTitleLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
productInfoLabel.leadingAnchor.constraint(equalTo: productImageView.leadingAnchor).isActive = true
|
||||
productInfoLabel.trailingAnchor.constraint(equalTo: productTitleLabel.trailingAnchor).isActive = true
|
||||
productInfoLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func update(
|
||||
category: EQNInAppProducts.Category,
|
||||
availability: EQNPurchaseAvailability?
|
||||
) {
|
||||
productImageView.image = category.image
|
||||
productTitleLabel.text = category.localizedTitle
|
||||
|
||||
let infoKey = category == .top100k ? "inapp_available_100k" : "inapp_available_10k"
|
||||
let counter = availabilityCounter(for: category, availability: availability)
|
||||
productInfoLabel.text = String(format: NSLocalizedString(infoKey, comment: ""), counter)
|
||||
}
|
||||
|
||||
private func availability(for productIdentifier: String) -> Int {
|
||||
if VersioneProProducts.is100kSubscription(for: productIdentifier) {
|
||||
private func availabilityCounter(
|
||||
for category: EQNInAppProducts.Category,
|
||||
availability: EQNPurchaseAvailability?
|
||||
) -> Int {
|
||||
if category == .top100k {
|
||||
return availability?.top100kAvailable ?? 0
|
||||
}
|
||||
return availability?.top10kAvailable ?? 0
|
||||
|
||||
+69
-20
@@ -9,38 +9,87 @@
|
||||
import UIKit
|
||||
import StoreKit
|
||||
|
||||
class SubscriptionsActiveTableViewCell: EQNBaseTableViewCell {
|
||||
|
||||
var product: SKProduct? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
class SubscriptionsActiveTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
override var headerText: String { NSLocalizedString("inapp_active", comment: "") }
|
||||
|
||||
var onTapRestore: () -> Void = { }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var noSubscriptionsLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var noSubscriptionsLabel: UILabel!
|
||||
@IBOutlet private weak var activeSubscriptionImageView: UIImageView!
|
||||
private lazy var activeSubscriptionImageView: UIImageView = {
|
||||
let imageView = UIImageView(frame: .zero)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
return imageView
|
||||
}()
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
private lazy var restoreButton: UIButton = {
|
||||
let button = UIButton(type: .system)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.addTarget(self, action: #selector(restoreSubscriptionsTapped(_:)), for: .touchUpInside)
|
||||
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
|
||||
button.backgroundColor = .systemGroupedBackground
|
||||
button.eqn_applyShadowAndRoundedCorners()
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
let stackView = UIStackView(arrangedSubviews: [ activeSubscriptionImageView, noSubscriptionsLabel, restoreButton ])
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.alignment = .center
|
||||
stackView.distribution = .equalSpacing
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = 20.0
|
||||
containerView.addSubview(stackView)
|
||||
|
||||
activeSubscriptionImageView.widthAnchor.constraint(equalToConstant: 150.0).isActive = true
|
||||
activeSubscriptionImageView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
|
||||
restoreButton.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
|
||||
restoreButton.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
|
||||
restoreButton.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
|
||||
|
||||
stackView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
headerLabel.text = NSLocalizedString("inapp_active", comment: "")
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
noSubscriptionsLabel.text = NSLocalizedString("inapp_nosub", comment: "")
|
||||
restoreButton.setTitle(NSLocalizedString("purchase_pro_restore", comment: ""), for: .normal)
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
if let productIdentifier = product?.productIdentifier {
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func restoreSubscriptionsTapped(_ sender: UIButton) {
|
||||
onTapRestore()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func update(with product: EQNInAppProducts?) {
|
||||
if let product {
|
||||
noSubscriptionsLabel.isHidden = true
|
||||
activeSubscriptionImageView.isHidden = false
|
||||
activeSubscriptionImageView.image = VersioneProProducts.image(for: productIdentifier)
|
||||
activeSubscriptionImageView.image = product.category.image
|
||||
} else {
|
||||
noSubscriptionsLabel.isHidden = false
|
||||
activeSubscriptionImageView.isHidden = true
|
||||
|
||||
+29
-10
@@ -8,21 +8,40 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SubscriptionsDescriptionTableViewCell: EQNBaseTableViewCell {
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
||||
class SubscriptionsDescriptionTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
override var headerText: String { NSLocalizedString("inapp_list", comment: "") }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var descriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
label.textAlignment = .justified
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
containerView.addSubview(descriptionLabel)
|
||||
|
||||
descriptionLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
descriptionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
descriptionLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
headerLabel.text = NSLocalizedString("inapp_list", comment: "")
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
descriptionLabel.text = NSLocalizedString("inapp_description", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
+42
-13
@@ -8,26 +8,55 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class SubscriptionsHeaderTableViewCell: UITableViewCell {
|
||||
|
||||
var isLoading = false {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var headerTitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = UIFont.preferredFont(forTextStyle: .title2)
|
||||
label.textColor = AppTheme.Colors.darkGray
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var loadingActivityIndicator: UIActivityIndicatorView = {
|
||||
let spinner = UIActivityIndicatorView(style: .medium)
|
||||
spinner.translatesAutoresizingMaskIntoConstraints = false
|
||||
spinner.hidesWhenStopped = true
|
||||
return spinner
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
override init(reuseIdentifier: String?) {
|
||||
super.init(reuseIdentifier: reuseIdentifier)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
var title: String? = nil {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
@IBOutlet private weak var headerTitleLabel: UILabel!
|
||||
@IBOutlet private weak var loadingActivityIndicator: UIActivityIndicatorView!
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func updateUI() {
|
||||
private func setupUI() {
|
||||
contentView.addSubview(headerTitleLabel)
|
||||
contentView.addSubview(loadingActivityIndicator)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func update(isLoading: Bool, title: String?) {
|
||||
headerTitleLabel.text = title
|
||||
|
||||
if isLoading && title != nil {
|
||||
|
||||
@@ -10,38 +10,29 @@ import UIKit
|
||||
import StoreKit
|
||||
import Shogun
|
||||
|
||||
|
||||
@objc
|
||||
class SubscriptionsViewController: UITableViewController {
|
||||
|
||||
private static let SegueIdentifierSubscriptionDetail = "ShowSubscriptionDetail"
|
||||
private static let CellHeightDescription: CGFloat = 320.0
|
||||
|
||||
// sezioni
|
||||
private enum TableSection: CaseIterable {
|
||||
case active
|
||||
case description
|
||||
case monthly
|
||||
case yearly
|
||||
case perpetual
|
||||
case products
|
||||
|
||||
var sectionTitle: String? {
|
||||
switch self {
|
||||
case .monthly: return NSLocalizedString("inapp_monthly_subscriptions", comment: "")
|
||||
case .yearly: return NSLocalizedString("inapp_yearly_subscriptions", comment: "")
|
||||
case .perpetual: return NSLocalizedString("inapp_lifetime_subscriptions", comment: "")
|
||||
default: return nil
|
||||
case .products: NSLocalizedString("subscriptions_available", comment: "")
|
||||
default: nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let sections = TableSection.allCases
|
||||
|
||||
private var allProducts = [SKProduct]()
|
||||
private var monthlyProducts = [SKProduct]()
|
||||
private var yearlyProducts = [SKProduct]()
|
||||
private var perpetualProducts = [SKProduct]()
|
||||
/// All products retrieved from AppStore
|
||||
private var products = [EQNInAppProducts]()
|
||||
/// Product already bought by the user
|
||||
private var subscribedProduct: SKProduct?
|
||||
private var productSubscribed: EQNInAppProducts?
|
||||
/// Availability for subscriptions
|
||||
private var availability: EQNPurchaseAvailability?
|
||||
/// Tells if products are loading
|
||||
@@ -49,6 +40,13 @@ class SubscriptionsViewController: UITableViewController {
|
||||
/// Tells if a restore is in progress
|
||||
private var isRestorePurchase = false
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
@objc
|
||||
convenience init() {
|
||||
self.init(style: .plain)
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
@@ -64,15 +62,7 @@ class SubscriptionsViewController: UITableViewController {
|
||||
loadData()
|
||||
checkAvailabilities()
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == Self.SegueIdentifierSubscriptionDetail,
|
||||
let controller = segue.destination as? SubscriptionDetailViewController,
|
||||
let product = sender as? SKProduct {
|
||||
controller.product = product
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func addObservers() {
|
||||
@@ -90,75 +80,64 @@ class SubscriptionsViewController: UITableViewController {
|
||||
}
|
||||
|
||||
private func configureUI() {
|
||||
let restoreButton = UIBarButtonItem(title: NSLocalizedString("purchase_pro_restore", comment: ""),
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(restoreTapped(_:)))
|
||||
navigationItem.rightBarButtonItem = restoreButton
|
||||
|
||||
// if is presented in Simulator, add done button
|
||||
if navigationController?.viewControllers.first == self {
|
||||
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closeTapped(_:)))
|
||||
navigationItem.leftBarButtonItem = doneButton
|
||||
}
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = Self.CellHeightDescription;
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
monthlyProducts.removeAll()
|
||||
yearlyProducts.removeAll()
|
||||
perpetualProducts.removeAll()
|
||||
|
||||
// creates list to show
|
||||
let isDiscountAvailable = checkDiscountPrice()
|
||||
allProducts.forEach { (product) in
|
||||
if isDiscountAvailable {
|
||||
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kMonthly ||
|
||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kMonthly {
|
||||
monthlyProducts.append(product)
|
||||
} else if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kYearlyDiscounted ||
|
||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kYearlyDiscounted {
|
||||
yearlyProducts.append(product)
|
||||
}
|
||||
} else {
|
||||
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kMonthly ||
|
||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kMonthly {
|
||||
monthlyProducts.append(product)
|
||||
}
|
||||
else if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kYearly ||
|
||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kYearly {
|
||||
yearlyProducts.append(product)
|
||||
}
|
||||
}
|
||||
// perpetual scribuscriptions doesn't have discounted version
|
||||
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kPerpetual ||
|
||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kPerpetual {
|
||||
perpetualProducts.append(product)
|
||||
}
|
||||
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.reloadData()
|
||||
tableView.registerCell(for: SubscriptionsActiveTableViewCell.self)
|
||||
tableView.registerCell(for: SubscriptionsDescriptionTableViewCell.self)
|
||||
tableView.registerCell(for: SubscriptionProductTableViewCell.self)
|
||||
tableView.registerHeaderFooterView(for: SubscriptionsHeaderTableViewCell.self)
|
||||
}
|
||||
|
||||
private func loadData() {
|
||||
isLoading = true
|
||||
|
||||
VersioneProProducts.store.requestProducts{ [weak self] success, products in
|
||||
EQNInAppProducts.store.requestProducts { [weak self] success, storeProducts in
|
||||
self?.isLoading = false
|
||||
|
||||
guard let self = self, let products = products, success == true else { return }
|
||||
guard let self = self, let storeProducts, success == true else { return }
|
||||
|
||||
let purchased = products.filter { (product) -> Bool in
|
||||
let isPurchased = VersioneProProducts.store.isProductPurchased(product.productIdentifier)
|
||||
let isSubscription = VersioneProProducts.isSubscription(for: product.productIdentifier)
|
||||
return isPurchased && isSubscription
|
||||
}
|
||||
self.subscribedProduct = purchased.first
|
||||
self.allProducts = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
|
||||
let products = storeProducts.compactMap { EQNInAppProducts.from(product: $0) }
|
||||
|
||||
self.updateUI()
|
||||
let purchased = products
|
||||
.filter { (product) -> Bool in
|
||||
// filter for subscriptions
|
||||
let isPurchased = EQNInAppProducts.store.isProductPurchased(product.productIdentifier)
|
||||
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.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
|
||||
}
|
||||
}
|
||||
self.productSubscribed = purchased.first
|
||||
self.products = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,18 +150,13 @@ class SubscriptionsViewController: UITableViewController {
|
||||
EQNPurchaseUtility.availableSubscriptions { (availability) in
|
||||
DispatchQueue.main.async {
|
||||
self.availability = availability
|
||||
self.updateUI()
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func restoreTapped(_ sender: AnyObject) {
|
||||
isRestorePurchase = true
|
||||
VersioneProProducts.store.restorePurchases()
|
||||
}
|
||||
|
||||
@objc func closeTapped(_ sender: AnyObject) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
@@ -190,7 +164,7 @@ class SubscriptionsViewController: UITableViewController {
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func fail(_ notification: Notification){
|
||||
VersioneProProducts.store.loadPurchase()
|
||||
EQNInAppProducts.store.loadPurchase()
|
||||
}
|
||||
|
||||
@objc func handlePurchaseNotification(_ notification: Notification) {
|
||||
@@ -209,7 +183,7 @@ class SubscriptionsViewController: UITableViewController {
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
VersioneProProducts.store.loadPurchase()
|
||||
EQNInAppProducts.store.loadPurchase()
|
||||
loadData()
|
||||
}
|
||||
|
||||
@@ -228,20 +202,22 @@ class SubscriptionsViewController: UITableViewController {
|
||||
|
||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let tableSection = sections[section]
|
||||
if let cell = tableView.dequeueReusableCell(withIdentifier: "SectionHeaderCell") as? SubscriptionsHeaderTableViewCell {
|
||||
cell.title = tableSection.sectionTitle
|
||||
cell.isLoading = isLoading
|
||||
return cell
|
||||
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
|
||||
}
|
||||
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 {
|
||||
@@ -249,96 +225,59 @@ class SubscriptionsViewController: UITableViewController {
|
||||
switch tableSection {
|
||||
case .active: return 1
|
||||
case .description: return 1
|
||||
case .monthly,
|
||||
.yearly,
|
||||
.perpetual:
|
||||
return availableProducts(for: tableSection).count
|
||||
case .products: return products.isEmpty ? 0 : 2
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
let tableSection = sections[indexPath.section]
|
||||
if tableSection == .description {
|
||||
// autolayout in description doesn't work 🤷♂️
|
||||
return Self.CellHeightDescription
|
||||
}
|
||||
return UITableView.automaticDimension
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
let tableSection = sections[indexPath.section]
|
||||
if tableSection == .active || tableSection == .description {
|
||||
return
|
||||
}
|
||||
|
||||
// add round borders to first and last row in products cells
|
||||
let cornerRadius = AppTheme.shared.cardCornerRadius
|
||||
var corners: UIRectCorner = []
|
||||
|
||||
if indexPath.row == 0 {
|
||||
corners.update(with: .topLeft)
|
||||
corners.update(with: .topRight)
|
||||
}
|
||||
|
||||
if indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 {
|
||||
corners.update(with: .bottomLeft)
|
||||
corners.update(with: .bottomRight)
|
||||
}
|
||||
|
||||
let maskLayer = CAShapeLayer()
|
||||
maskLayer.path = UIBezierPath(roundedRect: cell.bounds,
|
||||
byRoundingCorners: corners,
|
||||
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)).cgPath
|
||||
cell.layer.mask = maskLayer
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let tableSection = sections[indexPath.section]
|
||||
if tableSection == .active {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "ActiveSubscriptionsCell", for: indexPath) as! SubscriptionsActiveTableViewCell
|
||||
cell.product = subscribedProduct
|
||||
return cell
|
||||
}
|
||||
if tableSection == .description {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "DescriptionCell", for: indexPath) as! SubscriptionsDescriptionTableViewCell
|
||||
return cell
|
||||
}
|
||||
|
||||
let products = availableProducts(for: tableSection)
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "SubscriptionCell", for: indexPath) as! SubscriptionProductTableViewCell
|
||||
cell.product = products[indexPath.row]
|
||||
cell.availability = availability
|
||||
return cell
|
||||
switch tableSection {
|
||||
case .active:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionsActiveTableViewCell.self, for: indexPath)
|
||||
cell.selectionStyle = .none
|
||||
cell.update(with: productSubscribed)
|
||||
cell.onTapRestore = { [weak self] in
|
||||
guard let self else { return }
|
||||
|
||||
self.isRestorePurchase = true
|
||||
EQNInAppProducts.store.restorePurchases()
|
||||
}
|
||||
return cell
|
||||
case .description:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionsDescriptionTableViewCell.self, for: indexPath)
|
||||
cell.selectionStyle = .none
|
||||
return cell
|
||||
case .products:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionProductTableViewCell.self, for: indexPath)
|
||||
let category: EQNInAppProducts.Category = switch indexPath.row {
|
||||
case 0: .top10k
|
||||
case 1: .top100k
|
||||
default: .top100k
|
||||
}
|
||||
cell.update(category: category, availability: availability)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let tableSection = sections[indexPath.section]
|
||||
let products = availableProducts(for: tableSection)
|
||||
let products = availableProducts(for: indexPath)
|
||||
if !products.isEmpty {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierSubscriptionDetail, sender: products[indexPath.row])
|
||||
let controller = SubscriptionDetailsViewController(products: products)
|
||||
navigationController?.pushViewController(controller, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func availableProducts(for section: TableSection) -> [SKProduct] {
|
||||
switch section {
|
||||
case .monthly: return monthlyProducts
|
||||
case .yearly: return yearlyProducts
|
||||
case .perpetual: return perpetualProducts
|
||||
private func availableProducts(for indexPath: IndexPath) -> [EQNInAppProducts] {
|
||||
let section = sections[indexPath.section]
|
||||
switch (section, indexPath.row) {
|
||||
case (.products, 0): return products.filter { $0.isTop10k }
|
||||
case (.products, 1): return products.filter { $0.isTop100k }
|
||||
default: return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SubscriptionsViewController: StoryboardInitializable {
|
||||
static var storyboardName: String {
|
||||
"Main"
|
||||
}
|
||||
|
||||
static var storyboardControllerId: String {
|
||||
"subscriptionsController"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ class RealtimeAlertContainerView: UIView {
|
||||
lazy var alertView: RealtimeAlertView = {
|
||||
let view = RealtimeAlertView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.eqn_applyRoundedCorners()
|
||||
view.clipsToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
@@ -78,10 +80,11 @@ class RealtimeAlertView: UIView {
|
||||
let waveTimeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .title2)
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), 0)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 2
|
||||
label.isHidden = true
|
||||
return label
|
||||
}()
|
||||
@@ -89,7 +92,7 @@ class RealtimeAlertView: UIView {
|
||||
let intensityLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .title3)
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 2
|
||||
|
||||
@@ -9,46 +9,97 @@
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
|
||||
@objc
|
||||
class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var reportsLabel: UILabel!
|
||||
@IBOutlet private weak var reportsDescriptionLabel: UILabel!
|
||||
@objc var onTapTwitter: (() -> Void)?
|
||||
@objc var onTapMap: (() -> Void)?
|
||||
@objc var onTapTelegram: (() -> Void)?
|
||||
|
||||
override var isHeaderVisible: Bool { false }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var reportsLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var reportsDescriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 0
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
@IBOutlet private weak var twitterButton: UIButton! {
|
||||
didSet {
|
||||
twitterButton.imageView?.contentMode = .scaleAspectFit
|
||||
}
|
||||
}
|
||||
@IBOutlet private weak var telegramButton: UIButton! {
|
||||
didSet {
|
||||
telegramButton.imageView?.contentMode = .scaleAspectFit
|
||||
}
|
||||
}
|
||||
@IBOutlet private weak var mapButton: UIButton!
|
||||
private lazy var twitterButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(twitterButtonTapped(_:)))
|
||||
button.imageView?.contentMode = .scaleAspectFit
|
||||
button.setImage(.init(named: "xcorp_icon"), for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var mapButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(title: NSLocalizedString("official_button_map", comment: ""), target: self, action: #selector(mapButtonTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var telegramButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(telegramButtonTapped(_:)))
|
||||
button.imageView?.contentMode = .scaleAspectFit
|
||||
button.setImage(.init(named: "telegram_icon"), for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
// MARK: - Internal
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
containerView.addSubview(reportsLabel)
|
||||
containerView.addSubview(reportsDescriptionLabel)
|
||||
|
||||
let stackView = UIStackView(arrangedSubviews: [twitterButton, mapButton, telegramButton])
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .horizontal
|
||||
stackView.spacing = .cardVerticalSpacing
|
||||
stackView.distribution = .fillEqually
|
||||
containerView.addSubview(stackView)
|
||||
|
||||
reportsLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardPadding).isActive = true
|
||||
reportsLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
reportsLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
reportsDescriptionLabel.topAnchor.constraint(equalTo: reportsLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
reportsDescriptionLabel.leadingAnchor.constraint(equalTo: reportsLabel.leadingAnchor).isActive = true
|
||||
reportsDescriptionLabel.trailingAnchor.constraint(equalTo: reportsLabel.trailingAnchor).isActive = true
|
||||
|
||||
stackView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
|
||||
stackView.topAnchor.constraint(equalTo: reportsDescriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
stackView.leadingAnchor.constraint(equalTo: reportsDescriptionLabel.leadingAnchor).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: reportsDescriptionLabel.trailingAnchor).isActive = true
|
||||
stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
headerLabel.text = NSLocalizedString("tab_manual", comment: "").capitalized
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
reportsDescriptionLabel.text = NSLocalizedString("main_map", comment: "")
|
||||
mapButton.setTitle(NSLocalizedString("official_button_map", comment: ""), for: .normal)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc func updateUI(for smartphoneNetwork: EQNReteSmartphone?) {
|
||||
guard let smartphoneNetwork = smartphoneNetwork else {
|
||||
return
|
||||
}
|
||||
@objc
|
||||
func update(with smartphoneNetwork: EQNReteSmartphone?) {
|
||||
guard let smartphoneNetwork = smartphoneNetwork else { return }
|
||||
|
||||
let reports = smartphoneNetwork.manual
|
||||
self.reportsLabel.text = "\(reports)"
|
||||
@@ -61,4 +112,18 @@ class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
|
||||
self.reportsLabel.textColor = UIColor(hex6: 0xff0000)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func twitterButtonTapped(_ sender: UIButton) {
|
||||
onTapTwitter?()
|
||||
}
|
||||
|
||||
@objc private func mapButtonTapped(_ sender: UIButton) {
|
||||
onTapMap?()
|
||||
}
|
||||
|
||||
@objc private func telegramButtonTapped(_ sender: UIButton) {
|
||||
onTapTelegram?()
|
||||
}
|
||||
}
|
||||
|
||||
+12
-62
@@ -28,35 +28,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
private var filteredReports = [EQNSegnalazione]()
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
// app icon and name displayed on the screenshot
|
||||
private lazy var watermarkView: UIView = {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.isHidden = true
|
||||
|
||||
let logo = UIImageView(image: .init(named: "eq_icon_transparent"))
|
||||
logo.translatesAutoresizingMaskIntoConstraints = false
|
||||
logo.contentMode = .scaleAspectFit
|
||||
view.addSubview(logo)
|
||||
logo.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||
logo.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
logo.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
||||
logo.widthAnchor.constraint(equalTo: logo.heightAnchor).isActive = true
|
||||
|
||||
let title = UILabel()
|
||||
title.translatesAutoresizingMaskIntoConstraints = false
|
||||
title.text = NSLocalizedString("app_name", comment: "") + " App"
|
||||
title.textColor = AppTheme.Colors.red
|
||||
title.font = .preferredFont(forTextStyle: .title3, weight: .semibold)
|
||||
view.addSubview(title)
|
||||
title.leadingAnchor.constraint(equalTo: logo.trailingAnchor, constant: 10.0).isActive = true
|
||||
title.centerYAnchor.constraint(equalTo: logo.centerYAnchor).isActive = true
|
||||
title.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var magnitudeLegendView: UIView = {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
@@ -95,16 +67,11 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
|
||||
override func extraUI() {
|
||||
view.addSubview(magnitudeLegendView)
|
||||
view.addSubview(watermarkView)
|
||||
|
||||
magnitudeLegendView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
magnitudeLegendView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
magnitudeLegendView.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
|
||||
magnitudeLegendView.topAnchor.constraint(equalTo: mapView.topAnchor).isActive = true
|
||||
|
||||
watermarkView.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 10.0).isActive = true
|
||||
watermarkView.leadingAnchor.constraint(equalTo: mapView.leadingAnchor, constant: 10.0).isActive = true
|
||||
watermarkView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
|
||||
}
|
||||
|
||||
override func configureUI() {
|
||||
@@ -153,7 +120,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
.first
|
||||
|
||||
// controlliamo che sia inferiore al raggio impostato per le notifiche
|
||||
if let radius = Double(EQNNotificheSegnalazioniUtente.shared().distanzaPosizione),
|
||||
if let radius = Double(EQNSettingUserReportNotification.shared.distanzaMassima),
|
||||
let nearestCluser = nearestCluser,
|
||||
abs(nearestCluser.distance(from: userPosition)) < radius {
|
||||
centerLocation = nearestCluser
|
||||
@@ -191,33 +158,20 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
|
||||
@objc private func onTapMapDetailStyleButton(_ sender: Any) {
|
||||
appPreferences.userReportExpandedView.toggle()
|
||||
loadDataSource()
|
||||
reloadMap()
|
||||
}
|
||||
|
||||
@objc private func onTapScreenshotButton(_ sender: Any) {
|
||||
let snapshot = createSnapshot()
|
||||
|
||||
let controller = UIActivityViewController(activityItems: [snapshot], applicationActivities: [])
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
public func createSnapshot() -> UIImage {
|
||||
// mostriamo il watermark e nascondiamo la legenda
|
||||
watermarkView.isHidden = false
|
||||
magnitudeLegendView.isHidden = true
|
||||
|
||||
// riduciamo la porzione da salvare alla sola mappa (eliminiamo i filtri)
|
||||
let size = CGSize(width: view.bounds.width, height: mapView.bounds.maxY)
|
||||
let renderer = UIGraphicsImageRenderer(size: size)
|
||||
let image = renderer.image { ctx in
|
||||
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
|
||||
let screenshot = createSnapshot {
|
||||
// nascondiamo la legenda
|
||||
magnitudeLegendView.isHidden = true
|
||||
} restore: {
|
||||
// ri-visualizziamo la legenda
|
||||
magnitudeLegendView.isHidden = false
|
||||
}
|
||||
|
||||
// torniamo allo stato originale
|
||||
watermarkView.isHidden = true
|
||||
magnitudeLegendView.isHidden = false
|
||||
|
||||
return image
|
||||
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -233,10 +187,10 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
var cluster_code = 0
|
||||
var vector_cluster = [Int](repeating: 0, count: vector_latitude.count)
|
||||
for i in 0..<vector_latitude.count {
|
||||
let deltaMinute_i = getDeltaMinute(vector_date[i])
|
||||
let deltaMinute_i = EQNUtility.getDeltaMinute(vector_date[i])
|
||||
if vector_cluster[i] == 0 && deltaMinute_i <= minutes {
|
||||
for j in 0..<vector_latitude.count {
|
||||
let deltaMinute_j = getDeltaMinute(vector_date[j])
|
||||
let deltaMinute_j = EQNUtility.getDeltaMinute(vector_date[j])
|
||||
if i != j && deltaMinute_j <= minutes {
|
||||
if abs(vector_latitude[i] - vector_latitude[j]) < 4 && abs(vector_longitude[i] - vector_longitude[j]) < 4 && abs(deltaMinute_i - deltaMinute_j) <= 20 {
|
||||
if vector_cluster[j] > 0 {
|
||||
@@ -327,10 +281,6 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
mapView.addOverlays(overlays)
|
||||
}
|
||||
|
||||
private func getDeltaMinute(_ date: Date) -> TimeInterval {
|
||||
Date().timeIntervalSince(date) / 60.0
|
||||
}
|
||||
|
||||
// MARK: - Map
|
||||
|
||||
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
||||
|
||||
@@ -7,49 +7,100 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
class SegnalazioniSendReportCell: EQNBaseTableViewCell {
|
||||
class SegnalazioniSendReportCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
private struct Report: Equatable {
|
||||
let magnitude: Int
|
||||
let text: String
|
||||
let color: UIColor
|
||||
|
||||
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
lhs.magnitude == rhs.magnitude
|
||||
}
|
||||
}
|
||||
|
||||
@objc var onTapReport: (_ magnitude: Int) -> Void = { _ in }
|
||||
|
||||
override var headerText: String { NSLocalizedString("main_feel", comment: "") }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var reportMercalli2: UILabel!
|
||||
@IBOutlet private weak var reportMercalli3: UILabel!
|
||||
@IBOutlet private weak var reportMercalli4: UILabel!
|
||||
@IBOutlet private weak var reportMercalli5: UILabel!
|
||||
@IBOutlet private weak var reportMercalli6: UILabel!
|
||||
@IBOutlet private weak var reportMercalli7: UILabel!
|
||||
@IBOutlet private weak var reportMercalli8: UILabel!
|
||||
@IBOutlet private weak var reportMercalli9: UILabel!
|
||||
@IBOutlet private weak var reportMercalli10: UILabel!
|
||||
@IBOutlet private weak var reportMercalli11: UILabel!
|
||||
@IBOutlet private weak var reportMercalli12: UILabel!
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
private let reports: [Report] = [
|
||||
.init(magnitude: 20, text: NSLocalizedString("mercalli_II", comment: ""), color: .init(named: "Mercalli 20")!),
|
||||
.init(magnitude: 30, text: NSLocalizedString("mercalli_III", comment: ""), color: .init(named: "Mercalli 30")!),
|
||||
.init(magnitude: 40, text: NSLocalizedString("mercalli_IV", comment: ""), color: .init(named: "Mercalli 40")!),
|
||||
.init(magnitude: 50, text: NSLocalizedString("mercalli_V", comment: ""), color: .init(named: "Mercalli 50")!),
|
||||
.init(magnitude: 60, text: NSLocalizedString("mercalli_VI", comment: ""), color: .init(named: "Mercalli 60")!),
|
||||
.init(magnitude: 70, text: NSLocalizedString("mercalli_VII", comment: ""), color: .init(named: "Mercalli 70")!),
|
||||
.init(magnitude: 80, text: NSLocalizedString("mercalli_VIII", comment: ""), color: .init(named: "Mercalli 80")!),
|
||||
.init(magnitude: 90, text: NSLocalizedString("mercalli_IX", comment: ""), color: .init(named: "Mercalli 90")!),
|
||||
.init(magnitude: 100, text: NSLocalizedString("mercalli_X", comment: ""), color: .init(named: "Mercalli 100")!),
|
||||
.init(magnitude: 110, text: NSLocalizedString("mercalli_XI", comment: ""), color: .init(named: "Mercalli 110")!),
|
||||
.init(magnitude: 120, text: NSLocalizedString("mercalli_XII", comment: ""), color: .init(named: "Mercalli 120")!)
|
||||
]
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
// MARK: - Internal
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
var previousView = topView
|
||||
reports.enumerated().forEach { index, report in
|
||||
let view = createContentView(magnitude: report.magnitude, text: report.text, color: report.color)
|
||||
containerView.addSubview(view)
|
||||
|
||||
view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
|
||||
view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
|
||||
|
||||
let padding: CGFloat = report == reports.first ? .cardPadding : 0
|
||||
view.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: padding).isActive = true
|
||||
|
||||
if report == reports.last {
|
||||
view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
previousView = view
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
headerLabel.text = NSLocalizedString("main_feel", comment: "")
|
||||
reportMercalli2.text = NSLocalizedString("mercalli_II", comment: "")
|
||||
reportMercalli3.text = NSLocalizedString("mercalli_III", comment: "")
|
||||
reportMercalli4.text = NSLocalizedString("mercalli_IV", comment: "")
|
||||
reportMercalli5.text = NSLocalizedString("mercalli_V", comment: "")
|
||||
reportMercalli6.text = NSLocalizedString("mercalli_VI", comment: "")
|
||||
reportMercalli7.text = NSLocalizedString("mercalli_VII", comment: "")
|
||||
reportMercalli8.text = NSLocalizedString("mercalli_VIII", comment: "")
|
||||
reportMercalli9.text = NSLocalizedString("mercalli_IX", comment: "")
|
||||
reportMercalli10.text = NSLocalizedString("mercalli_X", comment: "")
|
||||
reportMercalli11.text = NSLocalizedString("mercalli_XI", comment: "")
|
||||
reportMercalli12.text = NSLocalizedString("mercalli_XII", comment: "")
|
||||
private func createContentView(
|
||||
magnitude: Int,
|
||||
text: String,
|
||||
color: UIColor
|
||||
) -> UIView {
|
||||
let view = UIView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = color
|
||||
|
||||
let label = UILabel(frame: .zero)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.numberOfLines = 0
|
||||
label.text = text
|
||||
|
||||
let button = UIButton(type: .system)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.backgroundColor = .clear
|
||||
button.tag = magnitude
|
||||
button.addTarget(self, action: #selector(onTapMagnitudeButton(_:)), for: .touchUpInside)
|
||||
|
||||
view.addSubview(label)
|
||||
view.addSubview(button)
|
||||
|
||||
// use a custom vertical spacing to make single lines bigger
|
||||
let verticalSpacing: CGFloat = 15.0
|
||||
label.topAnchor.constraint(equalTo: view.topAnchor, constant: verticalSpacing).isActive = true
|
||||
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -verticalSpacing).isActive = true
|
||||
button.constraint(to: view)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
@@ -58,4 +109,9 @@ class SegnalazioniSendReportCell: EQNBaseTableViewCell {
|
||||
let magnitude = sender.tag
|
||||
onTapReport(magnitude)
|
||||
}
|
||||
|
||||
@objc private func onTapMagnitudeButton(_ sender: UIButton) {
|
||||
let magnitude = sender.tag
|
||||
onTapReport(magnitude)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,12 @@
|
||||
- (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"];
|
||||
}
|
||||
|
||||
- (void)refreshUI
|
||||
@@ -74,8 +78,17 @@
|
||||
{
|
||||
if (indexPath.row == 0) {
|
||||
SegnalazioniLast24HoursCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Last24HCell" forIndexPath:indexPath];
|
||||
EQNReteSmartphone *reteSmartPhone = [EQNManager defaultManager].rete_smartphone;
|
||||
[cell updateUIFor:reteSmartPhone];
|
||||
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
|
||||
__weak SegnalazioniViewController *weakSelf = self;
|
||||
cell.onTapMap = ^{
|
||||
[weakSelf openMap];
|
||||
};
|
||||
cell.onTapTwitter = ^{
|
||||
[weakSelf openTwitter];
|
||||
};
|
||||
cell.onTapTelegram = ^{
|
||||
[weakSelf openTelegram];
|
||||
};
|
||||
return cell;
|
||||
}
|
||||
|
||||
@@ -88,21 +101,21 @@
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)openMapTapped:(id)sender
|
||||
- (void)openMap
|
||||
{
|
||||
SegnalazioniMapViewController *controller = [[SegnalazioniMapViewController alloc] init];
|
||||
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
|
||||
[self presentViewController:navController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (IBAction)openTwitterTapped:(id)sender
|
||||
- (void)openTwitter
|
||||
{
|
||||
NSURL *twitterUrl = [NSURL URLWithString:EQNTwitterProfileUrl];
|
||||
SFSafariViewController *controller = [[SFSafariViewController alloc] initWithURL:twitterUrl];
|
||||
[self presentViewController:controller animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (IBAction)openTelegramTapped:(id)sender
|
||||
- (void)openTelegram
|
||||
{
|
||||
NSURL *telegramUrl = [NSURL URLWithString:EQNTelegramUrl];
|
||||
[[UIApplication sharedApplication] openURL:telegramUrl options:@{} completionHandler:nil];
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ class SeismicNetworkAdvertiseTableViewCell: UITableViewCell {
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var bannerView: GADNativeAdView = {
|
||||
private lazy var bannerView: NativeAdView = {
|
||||
let view = GADTMediumTemplateView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
return view
|
||||
@@ -71,7 +71,7 @@ class SeismicNetworkAdvertiseTableViewCell: UITableViewCell {
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func loadNativeAd(_ nativeAd: GADNativeAd) {
|
||||
func loadNativeAd(_ nativeAd: NativeAd) {
|
||||
bannerView.nativeAd = nativeAd
|
||||
}
|
||||
}
|
||||
|
||||
+127
@@ -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
|
||||
}
|
||||
}
|
||||
+233
@@ -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)
|
||||
}
|
||||
}
|
||||
+116
-258
@@ -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 seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell)
|
||||
}
|
||||
|
||||
class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
|
||||
static let Identifier = "SeismicNetworkTableViewCell"
|
||||
|
||||
typealias MagnitudeColors = (textColor: UIColor, startColor: UIColor, endColor: UIColor)
|
||||
|
||||
/// 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 buttons
|
||||
}
|
||||
class SeismicNetworkTableViewCell: SeismicNetworkBaseTableViewCell {
|
||||
|
||||
/// Available cell type
|
||||
enum DisplayType {
|
||||
@@ -45,45 +21,15 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
/// Cell with map visible
|
||||
case mapExpanded
|
||||
}
|
||||
|
||||
/// Delegate
|
||||
weak var delegate: SeismicNetworkTableViewCellDelegate?
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
private static let DefaultVerticalSpacing: CGFloat = 6.0
|
||||
private static let DefaultBodyFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
private static let DefaultBodyFontLight = UIFont.preferredFont(forTextStyle: .body, weight: .light)
|
||||
|
||||
/// Seismic to show
|
||||
private var seismic: EQNSisma?
|
||||
private(set) var displayType = DisplayType.normal
|
||||
private var informationTypes = [InformationType]()
|
||||
|
||||
private var colors: MagnitudeColors?
|
||||
private var isPushSelected = false
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
private lazy var containerView: UIView = {
|
||||
let view = UIView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
||||
view.layer.masksToBounds = false
|
||||
|
||||
// add shadow
|
||||
view.layer.shadowColor = UIColor.black.cgColor
|
||||
view.layer.shadowOpacity = 0.5
|
||||
view.layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
view.layer.shadowRadius = 2
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var titleImageView: UIImageView = {
|
||||
let imageView = UIImageView(frame: .zero)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return imageView
|
||||
}()
|
||||
|
||||
|
||||
private lazy var placeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
@@ -92,11 +38,20 @@ 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
|
||||
label.backgroundColor = UIColor.white.withAlphaComponent(0.5)
|
||||
label.textAlignment = .center
|
||||
label.textAlignment = .right
|
||||
label.font = .preferredFont(forTextStyle: .subheadline)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
@@ -111,35 +66,40 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
private lazy var depthLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var timeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var distanceLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var coordinateLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var populationLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
@@ -147,8 +107,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.font = Self.DefaultBodyFont
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
@@ -156,8 +117,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.font = Self.DefaultBodyFont
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
@@ -183,18 +145,19 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
setupUI()
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
gradientView.eqn_applyRoundedCorners()
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
// this variable is used to keep track of the previous view, in order to attach proper constraints
|
||||
var previousView: UIView = containerView
|
||||
@@ -216,48 +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(titleImageView)
|
||||
stackViewTitle.addArrangedSubview(placeLabel)
|
||||
stackViewTitle.addArrangedSubview(networkLabel)
|
||||
stackViewTitle.addArrangedSubview(shareButton)
|
||||
|
||||
titleImageView.heightAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true
|
||||
titleImageView.widthAnchor.constraint(equalTo: titleImageView.heightAnchor).isActive = true
|
||||
networkLabel.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
|
||||
networkLabel.setContentHuggingPriority(.init(800), for: .horizontal)
|
||||
networkLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
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) {
|
||||
@@ -287,20 +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
|
||||
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) {
|
||||
let separator2 = addSeparator(constraintTo: stackViewInformations.bottomAnchor)
|
||||
|
||||
// network
|
||||
containerView.addSubview(networkLabel)
|
||||
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, 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)
|
||||
@@ -308,34 +257,43 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
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: separator2.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor, constant: 20.0).isActive = true
|
||||
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor, constant: -20.0).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
|
||||
stackViewButtons.axis = .horizontal
|
||||
stackViewButtons.distribution = .fillEqually
|
||||
stackViewButtons.spacing = 4
|
||||
stackViewButtons.spacing = 8
|
||||
|
||||
let buttonMap = createRoundedButton(title: "🗺", action: #selector(mapTapped(_:)))
|
||||
let buttonMap = EQNRoundedButton.make(title: "🗺", target: self, action: #selector(mapTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonMap)
|
||||
let buttonCalendar = createRoundedButton(title: "📆", action: #selector(calendarTapped(_:)))
|
||||
let buttonCalendar = EQNRoundedButton.make(title: "📆", target: self, action: #selector(calendarTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonCalendar)
|
||||
let buttonSettings = createRoundedButton(title: "🔧", action: #selector(settingsTapped(_:)))
|
||||
let buttonSettings = EQNRoundedButton.make(title: "🔧", target: self, action: #selector(settingsTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonSettings)
|
||||
|
||||
containerView.addSubview(stackViewButtons)
|
||||
stackViewButtons.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
||||
stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
stackViewButtons.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).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
|
||||
|
||||
@@ -345,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
|
||||
|
||||
@@ -353,10 +311,11 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
}
|
||||
|
||||
if (displayType == .mapExpanded) {
|
||||
let buttonClose = createRoundedButton(title: NSLocalizedString("official_close", comment: "").uppercased(), action: #selector(closeTapped(_:)))
|
||||
let buttonClose = EQNRoundedButton.make(title: NSLocalizedString("official_close", comment: "").uppercased(), target: self, action: #selector(closeTapped(_:)))
|
||||
|
||||
containerView.addSubview(buttonClose)
|
||||
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
buttonClose.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).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
|
||||
@@ -364,12 +323,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
else {
|
||||
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
private func recreateUI() {
|
||||
// remove all subviews and recreate the required components
|
||||
containerView.subviews.forEach({ $0.removeFromSuperview() })
|
||||
setupUI()
|
||||
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
gradientView.eqn_applyRoundedCorners()
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
@@ -377,15 +333,13 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
|
||||
let viewModel = SeismicNetworkViewModel(seismic: seismic)
|
||||
|
||||
containerView.backgroundColor = colors?.startColor
|
||||
|
||||
let notified = couldBeNotified(for: seismic)
|
||||
titleImageView.image = notified ? UIImage(named: "bell") : UIImage(named: "bell_disabled")
|
||||
gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
|
||||
|
||||
// update seismic data
|
||||
placeLabel.text = viewModel.place
|
||||
networkLabel.text = viewModel.network + " " // add some padding
|
||||
magnitudeLabel.textColor = colors?.textColor
|
||||
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
|
||||
networkLabel.text = String(format: NSLocalizedString("official_provider", comment: ""), viewModel.network)
|
||||
magnitudeLabel.textColor = viewModel.colors.textColor
|
||||
magnitudeLabel.text = viewModel.magnitude
|
||||
depthLabel.text = viewModel.depth
|
||||
timeLabel.text = "🕗 \(viewModel.time)"
|
||||
@@ -404,7 +358,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
alertsLabel.text = "⚠️ \(viewModel.users)"
|
||||
}
|
||||
|
||||
|
||||
if displayType == .mapExpanded {
|
||||
// zoom based on population involved
|
||||
let longitudeSpan = mapSpanLongitude(population: seismic.population100km)
|
||||
@@ -431,11 +384,16 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
/// - seismic: Seismic to display
|
||||
/// - type: Type of cell
|
||||
/// - informations: Informations to show
|
||||
public func configure(with seismic: EQNSisma, type: DisplayType, informations: [InformationType]) {
|
||||
public func configure(
|
||||
with seismic: EQNSisma,
|
||||
type: DisplayType,
|
||||
informations: [InformationType],
|
||||
isPushSelected: Bool
|
||||
) {
|
||||
self.seismic = seismic
|
||||
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
|
||||
self.displayType = type
|
||||
self.informationTypes = informations
|
||||
self.isPushSelected = isPushSelected
|
||||
|
||||
if !informations.contains(.time) {
|
||||
self.informationTypes += [.time]
|
||||
@@ -450,6 +408,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
if seismic.userNumber.intValue > 0 && !informations.contains(.reportUsers) {
|
||||
self.informationTypes += [.reportUsers]
|
||||
}
|
||||
if seismic.isoCode == "0" && informations.contains(.intensityMap) {
|
||||
self.informationTypes.removeAll { $0 == .intensityMap }
|
||||
}
|
||||
|
||||
recreateUI()
|
||||
updateUI()
|
||||
@@ -457,90 +418,38 @@ 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 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
|
||||
}
|
||||
|
||||
private func createRoundedButton(title: String, action: Selector) -> EQNRoundedButton {
|
||||
let button = EQNRoundedButton(frame: .zero)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.addTarget(self, action: action, for: .touchUpInside)
|
||||
button.setTitle(title, for: .normal)
|
||||
button.setTitleColor(AppTheme.Colors.darkGray, for: .normal)
|
||||
button.backgroundColor = UIColor.white.withAlphaComponent(0.5)
|
||||
return button
|
||||
}
|
||||
|
||||
/// Check if the user could be received a notification for this seismic
|
||||
private func couldBeNotified(for seismic: EQNSisma) -> Bool {
|
||||
let settings = EQNNotificheReteSismiche.shared()
|
||||
|
||||
if !settings.isAbilitato {
|
||||
return false
|
||||
}
|
||||
|
||||
if !settings.listaEnti.contains(seismic.provider) {
|
||||
return false
|
||||
}
|
||||
|
||||
var notified = true
|
||||
if let radius = Double(settings.distanzaPosizione), seismic.userDistance > radius {
|
||||
notified = false
|
||||
}
|
||||
if let magnitude = Double(settings.energiaSisma), seismic.magnitude.doubleValue < magnitude {
|
||||
notified = false
|
||||
}
|
||||
|
||||
if settings.isAbilitaVicini, seismic.userDistance < 50 {
|
||||
notified = true
|
||||
}
|
||||
|
||||
if settings.isTerremortiForti, let strongMagnitude = Double(settings.energiaTerremotiForti), seismic.magnitude.doubleValue >= strongMagnitude {
|
||||
notified = true
|
||||
}
|
||||
|
||||
return notified
|
||||
}
|
||||
|
||||
/// Determines the zoom for the map, based on the involved population
|
||||
private func mapSpanLongitude(population: Double) -> CLLocationDegrees {
|
||||
var zoom: CLLocationDegrees = 1
|
||||
@@ -553,55 +462,4 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
}
|
||||
return zoom
|
||||
}
|
||||
|
||||
/// 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 (textColor: textColor, startColor: startColor, endColor: endColor)
|
||||
}
|
||||
}
|
||||
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
//
|
||||
// FiltersViewModel.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 22/03/21.
|
||||
// Copyright © 2021 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
struct FiltersViewModel {
|
||||
let magnitude: String
|
||||
let distance: String
|
||||
let timeframe: String
|
||||
|
||||
init() {
|
||||
let magnitudoMinima = EQNData.magitudoDebole(for: EQNSeismic.shared.magnitudoMinima)
|
||||
self.magnitude = Self.formattedMagnitude(magnitudoMinima.value)
|
||||
|
||||
let distanzaMassima = EQNData.raggioSisma(for: EQNSeismic.shared.distanzaMassima)
|
||||
self.distance = Self.formattedDistance(distanzaMassima.value)
|
||||
|
||||
let periodoTemporale = EQNData.periodoTemporale(for: EQNSeismic.shared.periodoTemporale)
|
||||
self.timeframe = Self.formattedTimeframe(periodoTemporale.value)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private static func formattedMagnitude(_ magnitude: String) -> String {
|
||||
return magnitude
|
||||
}
|
||||
|
||||
private static func formattedDistance(_ distance: String) -> String {
|
||||
if distance == EQNData.MaxRaggioSisma {
|
||||
return "∞"
|
||||
}
|
||||
return "\(distance)km"
|
||||
}
|
||||
|
||||
private static func formattedTimeframe(_ timeframe: String) -> String {
|
||||
let time = Int(timeframe) ?? 0
|
||||
if time < 60 {
|
||||
return "\(time)m"
|
||||
}
|
||||
return "\(time/60)h"
|
||||
}
|
||||
}
|
||||
+87
-115
@@ -16,13 +16,12 @@ protocol SeismicFiltersViewControllerDelegate: AnyObject {
|
||||
class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
private enum RowIdentifier: Int {
|
||||
case magnitudoMinima
|
||||
case sismiNelRaggio
|
||||
case distanzaMassima
|
||||
case periodoTemporale
|
||||
case sismiFortiAbilita
|
||||
case sismiFortiDistanza
|
||||
case sismiQualsiasiMagnitudo
|
||||
case modificaImpostazioni
|
||||
case magnitudoMinima
|
||||
case sismiRilevanti
|
||||
case sismiTutti
|
||||
case sismiPercepiti
|
||||
}
|
||||
|
||||
weak var delegate: SeismicFiltersViewControllerDelegate?
|
||||
@@ -37,29 +36,21 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
||||
@IBOutlet private weak var closeButton: UIButton!
|
||||
|
||||
private var settings = [
|
||||
SettingItem(type: .slider, title: NSLocalizedString("filter_magnitude", comment: "")),
|
||||
SettingItem(type: .slider, title: NSLocalizedString("filter_distance", comment: "")),
|
||||
SettingItem(type: .slider, title: NSLocalizedString("filter_timeframe", comment: "")),
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_strong", comment: "")),
|
||||
SettingItem(type: .slider, title: NSLocalizedString("options_strong_magnitude", comment: "")),
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_near", comment: "")),
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_reflect", comment: ""))
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_show_area", comment: "")),
|
||||
SettingItem(type: .slider, title: ""),
|
||||
SettingItem(type: .slider, title: NSLocalizedString("filter_minimum_magnitude", comment: "")),
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_show_relevant", comment: "")),
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_show_all", comment: "")),
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_show_felt", comment: ""))
|
||||
]
|
||||
private let dataSourceMagnitudoMinima = EQNData.magitudoDeboli()
|
||||
private let dataSourceDistanzaMassima = EQNData.raggioSismi()
|
||||
private let dataSourcePeriodoTemporale = EQNData.periodiTemporali()
|
||||
private let dataSourceSismiForti = EQNData.magitudoForti()
|
||||
|
||||
private var initialMagnitudoMinima: EQNGenericValue?
|
||||
private var initialQualsiasiMagnitudo: Bool?
|
||||
private let initialFilterType = EQNSeismic.shared.filterOption
|
||||
private var currentFilterType = EQNSeismic.FilterType.inRadius
|
||||
private var currentMaximumDistance = EQNData.DefaultFilterRadius
|
||||
private var currentMinimumMagnitude = EQNData.DefaultFilterMagnitude
|
||||
|
||||
private var currentMagnitudoMinima = EQNData.DefaultMagitudoDebole
|
||||
private var currentDistanzaMassima = EQNData.DefaultRaggioSisma
|
||||
private var currentPeriodoTemporale = EQNData.DefaultPeriodoTemporale
|
||||
private var currentSismiFortiAbilitati = false
|
||||
private var currentSismiFortiDistanza = EQNData.DefaultMagitudoForte
|
||||
private var currentSismiQualsiasiMagnitudo = false
|
||||
private var currentModificaImpostazioni = false
|
||||
private let dataSourceMaximumDistance = EQNData.filterRadius
|
||||
private let dataSourceMinimumMagnitude = EQNData.filterMagnitude
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
@@ -86,19 +77,9 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
||||
}
|
||||
|
||||
private func loadDataSource() {
|
||||
currentMagnitudoMinima = EQNData.magitudoDebole(for: EQNSeismic.shared.magnitudoMinima)
|
||||
if initialMagnitudoMinima == nil {
|
||||
initialMagnitudoMinima = currentMagnitudoMinima
|
||||
}
|
||||
currentDistanzaMassima = EQNData.raggioSisma(for: EQNSeismic.shared.distanzaMassima)
|
||||
currentPeriodoTemporale = EQNData.periodoTemporale(for: EQNSeismic.shared.periodoTemporale)
|
||||
currentSismiFortiAbilitati = EQNSeismic.shared.sismiFortiAbilitati
|
||||
currentSismiFortiDistanza = EQNData.magitudoForte(for: EQNSeismic.shared.sismiFortiMagnitudo)
|
||||
currentSismiQualsiasiMagnitudo = EQNSeismic.shared.sismiQualsiasiAbilitati
|
||||
if initialQualsiasiMagnitudo == nil {
|
||||
initialQualsiasiMagnitudo = currentSismiQualsiasiMagnitudo
|
||||
}
|
||||
currentModificaImpostazioni = EQNSeismic.shared.modificaImpostazioniAbilitato
|
||||
currentFilterType = EQNSeismic.shared.filterOption
|
||||
currentMaximumDistance = EQNData.filterRadius(for: EQNSeismic.shared.maximumDistance)
|
||||
currentMinimumMagnitude = EQNData.filterMagnitude(for: EQNSeismic.shared.minimumMagnitude)
|
||||
}
|
||||
|
||||
// MARK: - Table view delegate and data source
|
||||
@@ -108,45 +89,36 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
|
||||
let setting = settings[indexPath.row]
|
||||
let isLocationAvailable = EQNUser.default().lastPosition != nil
|
||||
|
||||
switch setting.type {
|
||||
case .slider:
|
||||
let cell = SettingSliderTableViewCell(style: .default, reuseIdentifier: nil)
|
||||
cell.titleLabel.text = setting.displayTitle
|
||||
|
||||
if indexPath.row == RowIdentifier.magnitudoMinima.rawValue {
|
||||
cell.configureSlider(with: dataSourceMagnitudoMinima, current: currentMagnitudoMinima)
|
||||
cell.valueChanged = { [unowned self] value in
|
||||
currentMagnitudoMinima = value
|
||||
EQNSeismic.shared.magnitudoMinima = value.value
|
||||
EQNSeismic.shared.saveFilters()
|
||||
let isFilterInRadiusEnabled = currentFilterType == .inRadius && isLocationAvailable
|
||||
switch identifier {
|
||||
case .distanzaMassima:
|
||||
cell.isDisabled = !isFilterInRadiusEnabled
|
||||
cell.isUserInteractionEnabled = isFilterInRadiusEnabled
|
||||
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
|
||||
cell.valueChanged = { [weak self] value in
|
||||
self?.onChangeMaximumDistance(value)
|
||||
}
|
||||
cell.dragEnded = { [unowned self] in
|
||||
showWarningAlertIfNeeded(for: currentMagnitudoMinima)
|
||||
}
|
||||
} else if indexPath.row == RowIdentifier.distanzaMassima.rawValue {
|
||||
cell.configureSlider(with: dataSourceDistanzaMassima, current: currentDistanzaMassima)
|
||||
cell.valueChanged = { [unowned self] value in
|
||||
currentDistanzaMassima = value
|
||||
EQNSeismic.shared.distanzaMassima = value.value
|
||||
EQNSeismic.shared.saveFilters()
|
||||
}
|
||||
} else if indexPath.row == RowIdentifier.periodoTemporale.rawValue {
|
||||
cell.configureSlider(with: dataSourcePeriodoTemporale, current: currentPeriodoTemporale)
|
||||
cell.valueChanged = { [unowned self] value in
|
||||
currentPeriodoTemporale = value
|
||||
EQNSeismic.shared.periodoTemporale = value.value
|
||||
EQNSeismic.shared.saveFilters()
|
||||
}
|
||||
} else if indexPath.row == RowIdentifier.sismiFortiDistanza.rawValue {
|
||||
cell.isDisabled = !currentSismiFortiAbilitati
|
||||
cell.configureSlider(with: dataSourceSismiForti, current: currentSismiFortiDistanza)
|
||||
cell.valueChanged = { [unowned self] value in
|
||||
currentSismiFortiDistanza = value
|
||||
EQNSeismic.shared.sismiFortiMagnitudo = value.value
|
||||
EQNSeismic.shared.saveFilters()
|
||||
case .magnitudoMinima:
|
||||
cell.isDisabled = !isFilterInRadiusEnabled
|
||||
cell.isUserInteractionEnabled = isFilterInRadiusEnabled
|
||||
cell.configureSlider(with: dataSourceMinimumMagnitude, current: currentMinimumMagnitude)
|
||||
cell.valueChanged = { [weak self] value in
|
||||
self?.onChangeMinimumMagnitude(value)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return cell
|
||||
@@ -155,30 +127,37 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
||||
cell.titleLabel.text = setting.displayTitle
|
||||
cell.detailTextLabel?.text = setting.subtitle
|
||||
|
||||
if indexPath.row == RowIdentifier.sismiFortiAbilita.rawValue {
|
||||
cell.toggleSwitch.isOn = currentSismiFortiAbilitati
|
||||
cell.valueChanged = { [unowned self] value in
|
||||
currentSismiFortiAbilitati = value
|
||||
EQNSeismic.shared.sismiFortiAbilitati = value
|
||||
EQNSeismic.shared.saveFilters()
|
||||
|
||||
loadDataSource()
|
||||
tableView.reloadData()
|
||||
switch identifier {
|
||||
case .sismiNelRaggio:
|
||||
let isCurrentFilter = currentFilterType == .inRadius
|
||||
cell.isDisabled = !isLocationAvailable
|
||||
cell.toggleSwitch.isOn = isCurrentFilter
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeFilterOption(enabled, filter: .inRadius)
|
||||
}
|
||||
} else if indexPath.row == RowIdentifier.sismiQualsiasiMagnitudo.rawValue {
|
||||
cell.toggleSwitch.isOn = currentSismiQualsiasiMagnitudo
|
||||
cell.valueChanged = { [unowned self] value in
|
||||
currentSismiQualsiasiMagnitudo = value
|
||||
EQNSeismic.shared.sismiQualsiasiAbilitati = value
|
||||
EQNSeismic.shared.saveFilters()
|
||||
cell.errorLabel.text = !isLocationAvailable ? NSLocalizedString("filter_nolocation", comment: "") : nil
|
||||
case .sismiRilevanti:
|
||||
let isCurrentFilter = currentFilterType == .positionRelevant
|
||||
cell.isDisabled = !isLocationAvailable
|
||||
cell.toggleSwitch.isOn = isCurrentFilter
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeFilterOption(enabled, filter: .positionRelevant)
|
||||
}
|
||||
} else if indexPath.row == RowIdentifier.modificaImpostazioni.rawValue {
|
||||
cell.toggleSwitch.isOn = currentModificaImpostazioni
|
||||
cell.valueChanged = { [unowned self] value in
|
||||
currentModificaImpostazioni = value
|
||||
EQNSeismic.shared.modificaImpostazioniAbilitato = value
|
||||
EQNSeismic.shared.saveFilters()
|
||||
cell.errorLabel.text = !isLocationAvailable ? NSLocalizedString("filter_nolocation", comment: "") : nil
|
||||
case .sismiTutti:
|
||||
let isCurrentFilter = currentFilterType == .worldWide
|
||||
cell.toggleSwitch.isOn = isCurrentFilter
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeFilterOption(enabled, filter: .worldWide)
|
||||
}
|
||||
case .sismiPercepiti:
|
||||
let isCurrentFilter = currentFilterType == .userFelt
|
||||
cell.toggleSwitch.isOn = isCurrentFilter
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeFilterOption(enabled, filter: .userFelt)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return cell
|
||||
@@ -191,41 +170,34 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
||||
|
||||
@IBAction func exitTapped(_ sender: UIButton) {
|
||||
// data needs to be re-downloaded if (or conditions):
|
||||
// a) new magnitude is lower than the previous one and new value is less than 2.0
|
||||
// b) show any near earthquake is active and value is changed
|
||||
if let initialMagnitude = Float(initialMagnitudoMinima?.value ?? "10.0"), let currentMagnitude = Float(currentMagnitudoMinima.value) {
|
||||
needsDataUpdate = currentMagnitude < 2.0 && initialMagnitude > currentMagnitude
|
||||
}
|
||||
if let initialQualsiasiMagnitudo = initialQualsiasiMagnitudo, currentSismiQualsiasiMagnitudo == true, initialQualsiasiMagnitudo != currentSismiQualsiasiMagnitudo {
|
||||
needsDataUpdate = true
|
||||
}
|
||||
|
||||
// a) filter type is changed
|
||||
needsDataUpdate = initialFilterType != currentFilterType
|
||||
delegate?.seismicFiltersControllerDidUpdateFilters(self)
|
||||
updateNotificationSettingsIfNeeded()
|
||||
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func showWarningAlertIfNeeded(for value: EQNGenericValue) {
|
||||
guard let magnitude = Double(value.value), magnitude < 2.0 else { return }
|
||||
private func onChangeFilterOption(_ enabled: Bool, filter: EQNSeismic.FilterType) {
|
||||
currentFilterType = filter
|
||||
EQNSeismic.shared.filterOption = filter
|
||||
EQNSeismic.shared.saveFilters()
|
||||
|
||||
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""), message: NSLocalizedString("options_low_magnitude", comment: ""), preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("main_understood", comment: ""), style: .default, handler: nil))
|
||||
present(alert, animated: true, completion: nil)
|
||||
loadDataSource()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
|
||||
currentMaximumDistance = item
|
||||
EQNSeismic.shared.maximumDistance = item.value
|
||||
EQNSeismic.shared.saveFilters()
|
||||
}
|
||||
|
||||
private func updateNotificationSettingsIfNeeded() {
|
||||
// if the switch is enabled, update also the settings notification
|
||||
guard currentModificaImpostazioni == true else { return }
|
||||
|
||||
// update notification settings with current filters
|
||||
EQNNotificheReteSismiche.shared().energiaSisma = EQNSeismic.shared.magnitudoMinima;
|
||||
EQNNotificheReteSismiche.shared().distanzaPosizione = EQNSeismic.shared.distanzaMassima
|
||||
EQNNotificheReteSismiche.shared().isAbilitaVicini = EQNSeismic.shared.sismiQualsiasiAbilitati
|
||||
EQNNotificheReteSismiche.shared().isTerremortiForti = EQNSeismic.shared.sismiFortiAbilitati
|
||||
EQNNotificheReteSismiche.shared().energiaTerremotiForti = EQNSeismic.shared.sismiFortiMagnitudo
|
||||
private func onChangeMinimumMagnitude(_ item: EQNGenericValue) {
|
||||
currentMinimumMagnitude = item
|
||||
EQNSeismic.shared.minimumMagnitude = item.value
|
||||
EQNSeismic.shared.saveFilters()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-6
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// SeismicNetworkData.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 31/01/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// SeismicNetworkScrollIndicatorView.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 31/01/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreGraphics
|
||||
|
||||
|
||||
class SeismicNetworkScrollIndicatorView: UIView {
|
||||
|
||||
private static let HighlightColor: UIColor = .red
|
||||
|
||||
var seismics: [SeismicNetworkViewModel] = [] {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
var highlighted: SeismicNetworkViewModel? {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private var numberOfRectangles: Int {
|
||||
seismics.count
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard numberOfRectangles > 0 else { return }
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
let rectStandardWidth = rect.width
|
||||
let rectStandardHeight = rect.height / CGFloat(numberOfRectangles)
|
||||
let rectHighlightedMinHeight: CGFloat = 4
|
||||
|
||||
let smallRectangles = rectStandardHeight < 10
|
||||
let highlightIndex = seismics.firstIndex(where: { $0 == highlighted }) ?? 100_000
|
||||
|
||||
|
||||
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 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
|
||||
|
||||
if highlightIndex == index {
|
||||
// Stiamo disegnando il sisma evidenziato.
|
||||
// Valutiamo se utilizzare l'altezza minima.
|
||||
let rectHeight = smallRectangles ? rectHighlightedMinHeight : rectStandardHeight
|
||||
let yPosition = CGFloat(index) * rectStandardHeight
|
||||
let rectangle = CGRect(x: 0, y: yPosition, width: rectStandardWidth, height: rectHeight)
|
||||
|
||||
let fillColor = smallRectangles ? Self.HighlightColor : seismic.colors.textColor.withAlphaComponent(0.3)
|
||||
context?.setFillColor(fillColor.cgColor)
|
||||
context?.fill(rectangle)
|
||||
|
||||
if !smallRectangles {
|
||||
// disegniamo il bordo solo se i rettangoli non sono piccoli
|
||||
let borderWidth: CGFloat = 2.0
|
||||
context?.setStrokeColor(Self.HighlightColor.cgColor)
|
||||
context?.setLineWidth(borderWidth) // Spessore del bordo
|
||||
context?.stroke(rectangle.insetBy(dx: borderWidth / 2, dy: borderWidth / 2)) // Evita che il bordo venga tagliato
|
||||
}
|
||||
} else {
|
||||
// Stiamo disegnando i sismi non evidenziati, utilizziamo sempre l'altezza predefinita
|
||||
// Dobbiamo eventualmente calcolare un offset aggiuntivo,
|
||||
// perchè il sisma evidenziato ha un'altezza maggiore (se i rettangoli sono piccoli)
|
||||
let rectHeight = rectStandardHeight
|
||||
|
||||
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)
|
||||
context?.fill(rectangle)
|
||||
|
||||
if !smallRectangles {
|
||||
// altrimenti un bordo grigio
|
||||
let borderWidth: CGFloat = 0.5
|
||||
context?.setStrokeColor(AppTheme.Colors.gray.cgColor)
|
||||
context?.setLineWidth(borderWidth) // Spessore del bordo
|
||||
context?.stroke(rectangle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+141
-31
@@ -8,24 +8,75 @@
|
||||
|
||||
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 {
|
||||
|
||||
var place: String
|
||||
var network: String
|
||||
var isPreliminary: Bool
|
||||
var magnitude: String
|
||||
var depth: String
|
||||
var time: String
|
||||
var distance: String
|
||||
var coordinate: String
|
||||
var population: String
|
||||
var smartphones: String
|
||||
var users: String
|
||||
private let seismic: EQNSisma
|
||||
let place: String
|
||||
let network: String
|
||||
let isPreliminary: Bool
|
||||
let magnitude: String
|
||||
let depth: String
|
||||
let time: String
|
||||
let distance: String
|
||||
let coordinate: String
|
||||
let population: String
|
||||
let smartphones: String
|
||||
let users: String
|
||||
let colors: MagnitudeColors
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(seismic: EQNSisma) {
|
||||
self.seismic = seismic
|
||||
self.place = seismic.place
|
||||
self.network = seismic.provider
|
||||
|
||||
@@ -38,7 +89,7 @@ struct SeismicNetworkViewModel {
|
||||
self.depth = ""
|
||||
} else {
|
||||
self.magnitude = String(format: "%.1f%@", seismic.magnitude.doubleValue, seismic.magnitudeType)
|
||||
self.depth = String(format: "%@ %.1f km", NSLocalizedString("official_depth", comment: ""), seismic.depth.doubleValue)
|
||||
self.depth = String(format: "%.1f km ↓", seismic.depth.doubleValue)
|
||||
}
|
||||
|
||||
// we need to check agains null values, because sometimes WS returns invalid dates
|
||||
@@ -56,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 {
|
||||
@@ -69,23 +120,82 @@ struct SeismicNetworkViewModel {
|
||||
} else {
|
||||
self.users = ""
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworkViewModel: Equatable {
|
||||
static func == (lhs: SeismicNetworkViewModel, rhs: SeismicNetworkViewModel) -> Bool {
|
||||
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
|
||||
}
|
||||
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
//
|
||||
// SeismicNetworksIntensityMapViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 27/02/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
|
||||
class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
|
||||
|
||||
private let seismic: EQNSisma
|
||||
private var shakemaps: [EQNShakemap] = []
|
||||
private var pinStyle: MapPinStyle {
|
||||
get { AppPreferences.shared.mapPinStyle }
|
||||
set { AppPreferences.shared.mapPinStyle = newValue }
|
||||
}
|
||||
|
||||
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(
|
||||
seismic: EQNSisma
|
||||
) {
|
||||
self.seismic = seismic
|
||||
super.init()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("Plase use init(seismic:) instead")
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func extraUI() {
|
||||
super.extraUI()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
override func configureUI() {
|
||||
super.configureUI()
|
||||
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: .init(handler: { [weak self] _ in
|
||||
self?.dismiss(animated: true)
|
||||
}))
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), primaryAction: .init(handler: { [weak self] _ in
|
||||
self?.shareScreenshot()
|
||||
})),
|
||||
UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), primaryAction: .init(handler: { [weak self] _ in
|
||||
self?.nextPinStyle()
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
override func registerMapAnnotationViews() {
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full))
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light))
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle))
|
||||
}
|
||||
|
||||
override func loadDataSource() {
|
||||
Task {
|
||||
let result = try await APIService.shared.fetchShakemap(isoCode: seismic.isoCode)
|
||||
elaborateShakemaps(result)
|
||||
}
|
||||
}
|
||||
|
||||
override func elaborateMapCenter() {
|
||||
setMapCenter(for: seismic.coordinate, span: MKCoordinateSpan(latitudeDelta: 2, longitudeDelta: 2))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func elaborateShakemaps(_ shakemaps: [EQNShakemap]) {
|
||||
self.shakemaps = shakemaps
|
||||
|
||||
var shakemapPolyline = [MKPolyline]()
|
||||
var shakemapAnnotations: [MKAnnotation] = []
|
||||
for shakemap in shakemaps {
|
||||
// create coordinates for current shakemap
|
||||
let coordinates = zip(shakemap.lat, shakemap.lon).map { lat, lon in
|
||||
CLLocationCoordinate2D(latitude: Double(lat) / 10_000.0, longitude: Double(lon) / 10_000.0)
|
||||
}
|
||||
|
||||
let intensityColors = getColors(for: shakemap.intensity)
|
||||
|
||||
// create line to show on map
|
||||
let polyline = ShakemapPolyline(coordinates: coordinates, count: coordinates.count)
|
||||
polyline.intensity = shakemap.intensity
|
||||
polyline.intensityColor = intensityColors.lineColor
|
||||
shakemapPolyline.append(polyline)
|
||||
|
||||
// create annotation to show on top of the line
|
||||
let middlePoint = coordinates[coordinates.count / 2]
|
||||
let annotation = EQNMapAnnotationShakemap(coordinate: middlePoint, shakemap: shakemap)
|
||||
annotation.intensityColor = intensityColors.lineColor
|
||||
annotation.intensityTextColor = intensityColors.textColor
|
||||
shakemapAnnotations.append(annotation)
|
||||
}
|
||||
|
||||
let seismicAnnotation = EQNMapAnnotationSeismic(seismic: seismic)
|
||||
shakemapAnnotations.append(seismicAnnotation)
|
||||
|
||||
// draw lines
|
||||
mapView.addOverlays(shakemapPolyline)
|
||||
updateMap(with: shakemapAnnotations)
|
||||
}
|
||||
|
||||
private func nextPinStyle() {
|
||||
pinStyle.next()
|
||||
reloadMap()
|
||||
}
|
||||
|
||||
private func shareScreenshot() {
|
||||
let screenshot = createSnapshot(prepare: {
|
||||
descriptionView.isHidden = true
|
||||
}, restore: {
|
||||
descriptionView.isHidden = false
|
||||
})
|
||||
|
||||
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - MKMapViewDelegate
|
||||
|
||||
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
||||
switch annotation {
|
||||
case let shakemapAnnotation as EQNMapAnnotationShakemap:
|
||||
return shakemapAnnotation.toAnnotationView(mapView: mapView, style: .light)
|
||||
case let seismicAnnotation as EQNMapAnnotationSeismic:
|
||||
return seismicAnnotation.toAnnotationView(mapView: mapView, style: pinStyle)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
if let polyline = overlay as? ShakemapPolyline {
|
||||
let renderer = MKPolylineRenderer(polyline: polyline)
|
||||
renderer.strokeColor = polyline.intensityColor
|
||||
renderer.lineWidth = 6.0
|
||||
return renderer
|
||||
}
|
||||
return MKOverlayRenderer()
|
||||
}
|
||||
|
||||
private func getColors(for intensity: Float) -> (textColor: UIColor, lineColor: UIColor) {
|
||||
let shakemapColors: [String] = [
|
||||
"#3E26A8","#3E27AC","#3F28AF","#3F29B2","#402AB4","#402BB7","#412CBA","#412DBD","#422EBF","#422FC2",
|
||||
"#4330C5","#4331C8","#4332CA","#4433CD","#4434D0","#4535D2","#4537D5","#4538D7","#4639D9","#463ADC",
|
||||
"#463BDE","#463DE0","#473EE1","#473FE3","#4741E5","#4742E6","#4744E8","#4745E9","#4746EB","#4848EC",
|
||||
"#4849ED","#484BEE","#484CF0","#484EF1","#484FF2","#4850F3","#4852F4","#4853F5","#4854F6","#4756F7",
|
||||
"#4757F7","#4759F8","#475AF9","#475BFA","#475DFA","#465EFB","#4660FB","#4661FC","#4562FC","#4564FD",
|
||||
"#4465FD","#4367FD","#4368FE","#426AFE","#416BFE","#406DFE","#3F6EFF","#3E70FF","#3C71FF","#3B73FF",
|
||||
"#3974FF","#3876FE","#3677FE","#3579FD","#337AFD","#327CFC","#317DFC","#307FFB","#2F80FA","#2F82FA",
|
||||
"#2E83F9","#2E84F8","#2E86F8","#2E87F7","#2D88F6","#2D8AF5","#2D8BF4","#2D8CF3","#2D8EF2","#2C8FF1",
|
||||
"#2C90F0","#2B91EF","#2A93EE","#2994ED","#2895EC","#2797EB","#2798EA","#2699E9","#269AE8","#259BE8",
|
||||
"#259CE7","#249EE6","#249FE5","#23A0E5","#23A1E4","#22A2E4","#21A3E3","#20A5E3","#1FA6E2","#1EA7E1",
|
||||
"#1DA8E1","#1DA9E0","#1CAADF","#1BABDE","#1AACDD","#19ADDC","#17AEDA","#16AFD9","#14B0D8","#12B1D6",
|
||||
"#10B2D5","#0EB3D4","#0BB3D2","#08B4D1","#06B5CF","#04B6CE","#02B7CC","#01B7CA","#00B8C9","#00B9C7",
|
||||
"#00BAC6","#01BAC4","#02BBC2","#04BBC1","#06BCBF","#09BDBD","#0DBDBC","#10BEBA","#14BEB8","#17BFB6",
|
||||
"#1AC0B5","#1DC0B3","#20C1B1","#23C1AF","#25C2AE","#27C2AC","#29C3AA","#2BC3A8","#2CC4A6","#2EC4A5",
|
||||
"#2FC5A3","#31C5A1","#32C69F","#33C79D","#35C79B","#36C899","#38C896","#39C994","#3BC992","#3DCA90",
|
||||
"#40CA8D","#42CA8B","#45CB89","#48CB86","#4BCB84","#4ECC81","#51CC7F","#54CC7C","#57CC7A","#5ACC77",
|
||||
"#5ECD74","#61CD72","#64CD6F","#67CD6C","#6BCD69","#6ECD66","#72CD64","#76CC61","#79CC5E","#7DCC5B",
|
||||
"#81CC59","#84CC56","#88CB53","#8BCB51","#8FCB4E","#93CA4B","#96CA48","#9AC946","#9DC943","#A1C840",
|
||||
"#A4C83E","#A7C73B","#ABC739","#AEC637","#B2C635","#B5C533","#B8C431","#BBC42F","#BEC32D","#C2C32C",
|
||||
"#C5C22A","#C8C129","#CBC128","#CEC027","#D0BF27","#D3BF27","#D6BE27","#D9BE28","#DBBD28","#DEBC29",
|
||||
"#E1BC2A","#E3BC2B","#E6BB2D","#E8BB2E","#EABA30","#ECBA32","#EFBA35","#F1BA37","#F3BA39","#F5BA3B",
|
||||
"#F7BA3D","#F9BA3E","#FBBB3E","#FCBC3E","#FEBD3D","#FEBE3C","#FEC03B","#FEC13A","#FEC239","#FEC438",
|
||||
"#FEC537","#FEC735","#FEC834","#FECA33","#FDCB32","#FDCD31","#FDCE31","#FCD030","#FBD22F","#FBD32E",
|
||||
"#FAD52E","#F9D62D","#F9D82C","#F8D92B","#F7DB2A","#F7DD2A","#F6DE29","#F6E028","#F5E128","#F5E327",
|
||||
"#F5E526","#F5E626","#F5E825","#F5E924","#F5EB23","#F5EC22","#F5EE21","#F6EF20","#F6F11F","#F6F21E",
|
||||
"#F7F41C","#F7F51B","#F8F71A","#F8F818","#F9F916","#F9FB15"
|
||||
]
|
||||
|
||||
let minIntensity = shakemaps.map { $0.intensity }.min() ?? 0
|
||||
let maxIntensity = shakemaps.map { $0.intensity }.max() ?? 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
|
||||
|
||||
return (textColor: textColor, lineColor: lineColor)
|
||||
}
|
||||
}
|
||||
|
||||
extension EQNMapAnnotationShakemap {
|
||||
func toAnnotationView(
|
||||
mapView: MKMapView,
|
||||
style: MapPinStyle,
|
||||
isUserSelection: Bool = false
|
||||
) -> MKAnnotationView? {
|
||||
switch style {
|
||||
case .full, .light:
|
||||
let identifier = EQNSeismicAnnotationView.identifier(for: style)
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
|
||||
annotationView.magnitude = String(format: "%.1f", shakemap.intensity)
|
||||
annotationView.magnitudeTextColor = intensityTextColor ?? .black
|
||||
annotationView.magnitudeBackgroundColor = intensityColor
|
||||
annotationView.canShowCallout = true
|
||||
return annotationView
|
||||
case .circle:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class ShakemapPolyline: MKPolyline {
|
||||
var intensity: Float = 0
|
||||
var intensityColor: UIColor = .white
|
||||
}
|
||||
+120
-49
@@ -15,11 +15,18 @@ protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
|
||||
}
|
||||
|
||||
class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
|
||||
|
||||
private var pinStyle: MapPinStyle {
|
||||
get { AppPreferences.shared.mapPinStyle }
|
||||
set { AppPreferences.shared.mapPinStyle = newValue }
|
||||
}
|
||||
private let eqnSeismic = EQNSeismic.shared
|
||||
|
||||
// MARK: - State
|
||||
|
||||
override var isCloseButtonVisible: Bool { false }
|
||||
override var isFilterViewVisible: Bool {
|
||||
// a custom filter view id displayed
|
||||
// a custom filter view is displayed
|
||||
true
|
||||
}
|
||||
weak var delegate: SeismicNetworksMapDetailViewControllerDelegate?
|
||||
@@ -42,10 +49,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
seismicsFilterLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
||||
seismicsFilterLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
|
||||
seismicsFilterLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
|
||||
|
||||
// tap recognizer
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(filtersTapped(_:)))
|
||||
view.addGestureRecognizer(tapRecognizer)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
@@ -76,6 +80,24 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
fatalError("init(coder:) is not available, please use init(seismic:allSeismics:)")
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func configureUI() {
|
||||
super.configureUI()
|
||||
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: .init(handler: { [weak self] _ in
|
||||
self?.dismiss(animated: true)
|
||||
}))
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), primaryAction: .init(handler: { [weak self] _ in
|
||||
self?.shareScreenshot()
|
||||
})),
|
||||
UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), primaryAction: .init(handler: { [weak self] _ in
|
||||
self?.nextPinStyle()
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func updateSeismics(_ seismics: [EQNSisma]) {
|
||||
@@ -84,7 +106,9 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
override func registerMapAnnotationViews() {
|
||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.DoubleLineIdentifier)
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full))
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light))
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle))
|
||||
}
|
||||
|
||||
override func loadDataSource() {
|
||||
@@ -92,14 +116,14 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
|
||||
updateMap(with: annotations)
|
||||
|
||||
// if the given seismic is still in the data source, show circles
|
||||
// otherwise just remove any other circles already on the map
|
||||
if allSeismics.contains(seismic) {
|
||||
addCircles(for: seismic.coordinate)
|
||||
// if the filter is "in radius",
|
||||
// show a circle with selected radius
|
||||
if eqnSeismic.filterOption == .inRadius, let distance = Double(eqnSeismic.maximumDistance) {
|
||||
addCircle(center: EQNUser.default().lastPosition, radius: distance * 1_000)
|
||||
} else {
|
||||
addCircles(for: nil)
|
||||
addCircle(center: nil, radius: 0)
|
||||
}
|
||||
|
||||
|
||||
loadFiltersRecap()
|
||||
}
|
||||
|
||||
@@ -127,31 +151,61 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadFiltersRecap() {
|
||||
let filters = FiltersViewModel()
|
||||
|
||||
let recap = "\(NSLocalizedString("filter_filter", comment: "")): "
|
||||
+ "M≥\(filters.magnitude) "
|
||||
+ "D≤\(filters.distance) "
|
||||
+ "T≤\(filters.timeframe)"
|
||||
seismicsFilterLabel.text = recap
|
||||
override func zPriority(for annotation: MKAnnotation) -> MKAnnotationViewZPriority {
|
||||
guard let annotation = annotation as? EQNMapAnnotationSeismic else {
|
||||
return .min
|
||||
}
|
||||
|
||||
// il sisma cliccato dall'utente sta sopra a tutti
|
||||
if annotation.seismic == seismic {
|
||||
return .max
|
||||
}
|
||||
|
||||
// Ordiniamo le annotazioni in base all amagnitudo, quelle con valore maggiore devono stare sopra.
|
||||
// La `zPriority` viene calcolata utilizzando la posizione nella lista
|
||||
let index = mapAnnotations
|
||||
.compactMap { $0 as? EQNMapAnnotationSeismic }
|
||||
.sorted(by: { $0.seismic.magnitude.doubleValue < $1.seismic.magnitude.doubleValue })
|
||||
.firstIndex(where: { $0 == annotation })
|
||||
guard let index else {
|
||||
return .min
|
||||
}
|
||||
|
||||
let priority = Float(index) / Float(mapAnnotations.count)
|
||||
return .init(priority)
|
||||
}
|
||||
|
||||
private func addCircles(for location: CLLocation?) {
|
||||
// MARK: - Private
|
||||
|
||||
private func nextPinStyle() {
|
||||
pinStyle.next()
|
||||
reloadMap()
|
||||
}
|
||||
|
||||
private func loadFiltersRecap() {
|
||||
let filter = EQNSeismic.shared.filterOption
|
||||
|
||||
let text = switch filter {
|
||||
case .inRadius: NSLocalizedString("filter_area", comment: "")
|
||||
case .positionRelevant: NSLocalizedString("filter_relevant", comment: "")
|
||||
case .worldWide: NSLocalizedString("filter_all", comment: "")
|
||||
case .userFelt: NSLocalizedString("filter_felt", comment: "")
|
||||
}
|
||||
seismicsFilterLabel.text = text
|
||||
}
|
||||
|
||||
private func addCircle(
|
||||
center location: CLLocation?,
|
||||
radius: Double
|
||||
) {
|
||||
// remove any previous circles
|
||||
mapView.removeOverlays(mapCircles)
|
||||
mapCircles.removeAll()
|
||||
|
||||
// aggiungiamo 3 cerchi concentrici per il punto specificato (se disponibile)
|
||||
// li inseriamo a a 50, 100 e 200 km.
|
||||
guard let location = location else { return }
|
||||
|
||||
let circles = [
|
||||
MKCircle(center: location.coordinate, radius: 25_000),
|
||||
MKCircle(center: location.coordinate, radius: 100_000),
|
||||
MKCircle(center: location.coordinate, radius: 200_000)
|
||||
MKCircle(center: location.coordinate, radius: radius),
|
||||
]
|
||||
|
||||
// !!note: is important to assign here the circles
|
||||
@@ -162,16 +216,13 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
mapView.addOverlays(circles)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc override func filtersTapped(_ sender: UIGestureRecognizer) {
|
||||
let controller = SeismicFiltersViewController.makeViewController()
|
||||
controller.delegate = self
|
||||
controller.modalPresentationStyle = .overCurrentContext
|
||||
controller.modalTransitionStyle = .crossDissolve
|
||||
present(controller, animated: true, completion: nil)
|
||||
private func shareScreenshot() {
|
||||
let screenshot = createSnapshot()
|
||||
|
||||
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Map
|
||||
|
||||
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
||||
@@ -179,15 +230,8 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
return nil
|
||||
}
|
||||
|
||||
let viewModel = SeismicNetworkViewModel(seismic: annotation.seismic)
|
||||
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.DoubleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
|
||||
|
||||
annotationView.image = annotation.image
|
||||
annotationView.title = annotation.title
|
||||
annotationView.subtitle = viewModel.magnitude
|
||||
|
||||
return annotationView
|
||||
let isUserSelection = annotation.seismic == seismic
|
||||
return annotation.toAnnotationView(mapView: mapView, style: pinStyle, isUserSelection: isUserSelection)
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
@@ -196,12 +240,39 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
let circle = MKCircleRenderer(overlay: circleOverlay)
|
||||
circle.strokeColor = AppTheme.Colors.darkGray
|
||||
circle.lineWidth = 1.0
|
||||
circle.strokeColor = AppTheme.Colors.red
|
||||
circle.lineWidth = 2.0
|
||||
return circle
|
||||
}
|
||||
}
|
||||
|
||||
extension EQNMapAnnotationSeismic {
|
||||
func toAnnotationView(
|
||||
mapView: MKMapView,
|
||||
style: MapPinStyle,
|
||||
isUserSelection: Bool = false
|
||||
) -> MKAnnotationView {
|
||||
switch style {
|
||||
case .full, .light:
|
||||
let identifier = EQNSeismicAnnotationView.identifier(for: style)
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
|
||||
annotationView.title = self.title
|
||||
annotationView.subtitle = self.subtitle
|
||||
annotationView.magnitude = String(format: "M%.1f", self.seismic.magnitude.doubleValue)
|
||||
annotationView.magnitudeTextColor = self.textColor
|
||||
annotationView.magnitudeBackgroundColor = .white
|
||||
annotationView.isUserSelection = isUserSelection
|
||||
return annotationView
|
||||
case .circle:
|
||||
let identifier = EQNSeismicAnnotationView.identifier(for: style)
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
|
||||
annotationView.image = image(height: EQNSeismicAnnotationView.CircleViewHeight,
|
||||
isUserSelection: isUserSelection)
|
||||
return annotationView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworksMapDetailViewController: SeismicFiltersViewControllerDelegate {
|
||||
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
|
||||
delegate?.seismicNetworksMapDetailControllerWillUpdateData(self, needsDataUpdate: controller.needsDataUpdate)
|
||||
|
||||
+565
-97
@@ -15,23 +15,23 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
|
||||
private enum CellType {
|
||||
case seismic(EQNSisma)
|
||||
case advertise(GADNativeAd)
|
||||
case advertise(NativeAd)
|
||||
}
|
||||
|
||||
enum CardDisplayType: Int, CaseIterable {
|
||||
case small
|
||||
case full
|
||||
case minimal
|
||||
}
|
||||
|
||||
private static let SegueIdentifierFilters = "ShowFilters"
|
||||
private static let SegueIdentifierSettings = "ShowSettings"
|
||||
private static let SegueIdentifierSeismicNetworks = "ShowSeismicNetworks"
|
||||
private static let SegueIdentifierCardSettings = "ShowCardSettings"
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
@IBOutlet private weak var tableView: UITableView?
|
||||
@IBOutlet private weak var expandeCollapseButton: UIBarButtonItem!
|
||||
weak var currentMapController: SeismicNetworksMapDetailViewController?
|
||||
|
||||
/// The ad loader
|
||||
private lazy var adLoader: GADAdLoader = {
|
||||
let adLoader = GADAdLoader(
|
||||
private lazy var adLoader: AdLoader = {
|
||||
let adLoader = AdLoader(
|
||||
adUnitID: EQNAdMobAppIdNativeBanner, rootViewController: self,
|
||||
adTypes: [.native], options: nil)
|
||||
adLoader.delegate = self
|
||||
@@ -40,25 +40,88 @@ 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
|
||||
private var openedPushNotification: EQNOfficialPushNotification? {
|
||||
didSet {
|
||||
scrollToOpenedSeismic = true
|
||||
}
|
||||
}
|
||||
private var scrollToOpenedSeismic = false
|
||||
/// Current displayed controller with map
|
||||
private weak var currentMapController: SeismicNetworksMapDetailViewController?
|
||||
/// Keep track of the current cell at the center
|
||||
private var currentCenteredIndexPath: IndexPath?
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
@IBOutlet private weak var displayModeButton: UIBarButtonItem!
|
||||
@IBOutlet private weak var sortButton: UIBarButtonItem!
|
||||
private var tableViewTopConstraint: NSLayoutConstraint?
|
||||
|
||||
private lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .plain)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.showsVerticalScrollIndicator = false
|
||||
return tableView
|
||||
}()
|
||||
|
||||
private lazy var filterChangedView: UIView = {
|
||||
let view = UIView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = AppTheme.Colors.pureBlue
|
||||
|
||||
view.addSubview(filterChangedLabel)
|
||||
filterChangedLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
|
||||
filterChangedLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
|
||||
filterChangedLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
|
||||
filterChangedLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var filterChangedLabel: UILabel = {
|
||||
let label = UILabel(frame: .zero)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = .white
|
||||
label.font = .preferredFont(forTextStyle: .subheadline)
|
||||
label.numberOfLines = 0
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var scrollIndicatorView: SeismicNetworkScrollIndicatorView = {
|
||||
let view = SeismicNetworkScrollIndicatorView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = .clear
|
||||
view.layer.borderColor = AppTheme.Colors.gray.cgColor
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
configureUI()
|
||||
checkForLocation()
|
||||
refreshUI()
|
||||
|
||||
// only the first time, show the popup for country selection
|
||||
let alreadyPresented = UserDefaults.standard.bool(forKey: EQNUserDefaultKeyOneShotShowCountry)
|
||||
if !alreadyPresented {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierSettings, sender: nil)
|
||||
UserDefaults.standard.setValue(true, forKey: EQNUserDefaultKeyOneShotShowCountry)
|
||||
}
|
||||
configureFilterView(isVisible: false)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveDownloadCompleteNotification(_:)), name: .EQNDownloadDataDidComplete, object: nil)
|
||||
}
|
||||
@@ -67,16 +130,103 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
loadData(forced: false)
|
||||
|
||||
// check for a push to manage
|
||||
if let notification = EQNOfficialPushNotification.stored() {
|
||||
manageFilter(for: notification)
|
||||
self.openedPushNotification = notification
|
||||
EQNOfficialPushNotification.removeStored()
|
||||
} else {
|
||||
configureFilterView(isVisible: false)
|
||||
self.openedPushNotification = nil
|
||||
}
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupUI() {
|
||||
view.addSubview(scrollIndicatorView)
|
||||
view.addSubview(tableView)
|
||||
|
||||
scrollIndicatorView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true
|
||||
scrollIndicatorView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
scrollIndicatorView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
|
||||
scrollIndicatorView.widthAnchor.constraint(equalToConstant: 10.0).isActive = true
|
||||
|
||||
tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: view.topAnchor)
|
||||
tableViewTopConstraint?.isActive = true
|
||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
tableView.trailingAnchor.constraint(equalTo: scrollIndicatorView.leadingAnchor).isActive = true
|
||||
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
private func setupFilterView(isVisible: Bool) {
|
||||
if isVisible && filterChangedView.superview == nil {
|
||||
view.addSubview(filterChangedView)
|
||||
tableViewTopConstraint?.isActive = false
|
||||
filterChangedView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
filterChangedView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
filterChangedView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||
tableViewTopConstraint = filterChangedView.bottomAnchor.constraint(equalTo: tableView.topAnchor)
|
||||
tableViewTopConstraint?.isActive = true
|
||||
} else {
|
||||
filterChangedView.removeFromSuperview()
|
||||
tableViewTopConstraint?.isActive = false
|
||||
tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: view.topAnchor)
|
||||
tableViewTopConstraint?.isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
private func configureFilterView(isVisible: Bool) {
|
||||
setupFilterView(isVisible: isVisible)
|
||||
if isVisible {
|
||||
filterChangedLabel.text = NSLocalizedString("official_filter_changed", comment: "")
|
||||
filterChangedView.backgroundColor = AppTheme.Colors.pureBlue
|
||||
} else {
|
||||
filterChangedLabel.text = nil
|
||||
filterChangedView.backgroundColor = .white
|
||||
}
|
||||
}
|
||||
|
||||
private func configureUI() {
|
||||
title = NSLocalizedString("tab_official", comment: "").capitalized
|
||||
|
||||
tableView?.estimatedRowHeight = 300.0
|
||||
tableView?.rowHeight = UITableView.automaticDimension
|
||||
tableView?.register(SeismicNetworkTableViewCell.self, forCellReuseIdentifier: SeismicNetworkTableViewCell.Identifier)
|
||||
tableView?.register(SeismicNetworkAdvertiseTableViewCell.self, forCellReuseIdentifier: SeismicNetworkAdvertiseTableViewCell.Identifier)
|
||||
tableView?.emptyDataSetSource = self
|
||||
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()
|
||||
}
|
||||
|
||||
private func setupSortMenu() {
|
||||
let currentSort = EQNSeismic.shared.sort
|
||||
sortButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
|
||||
UIAction(title: NSLocalizedString("sort_date", comment: ""), image: UIImage(systemName: "calendar"), state: currentSort == .time ? .on : .off) { [weak self ] _ in
|
||||
self?.changeSort(to: .time)
|
||||
},
|
||||
UIAction(title: NSLocalizedString("sort_position", comment: ""), image: UIImage(systemName: "ruler"), state: currentSort == .position ? .on : .off) { [weak self] _ in
|
||||
self?.changeSort(to: .position)
|
||||
},
|
||||
UIAction(title: NSLocalizedString("sort_magnitude", comment: ""), image: UIImage(systemName: "thermometer"), state: currentSort == .magnitude ? .on : .off) { [weak self] _ in
|
||||
self?.changeSort(to: .magnitude)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
private func checkForLocation() {
|
||||
// check if a valid location is available,
|
||||
// otherwise change the filter settings
|
||||
if !isLocationAvailable() {
|
||||
EQNSeismic.shared.filterOption = .worldWide
|
||||
EQNSeismic.shared.saveFilters()
|
||||
}
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
@@ -85,14 +235,6 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
if let controller = segue.destination as? SeismicFiltersViewController {
|
||||
controller.delegate = self
|
||||
}
|
||||
case Self.SegueIdentifierSettings:
|
||||
if let controller = segue.destination as? SeismicSettingsViewController {
|
||||
controller.delegate = self
|
||||
}
|
||||
case Self.SegueIdentifierSeismicNetworks:
|
||||
if let navController = segue.destination as? UINavigationController, let controller = navController.viewControllers.first as? SeismicSettingsNetworksViewController {
|
||||
controller.delegate = self
|
||||
}
|
||||
case Self.SegueIdentifierCardSettings:
|
||||
if let controller = segue.destination as? SeismicCardSettingsViewController {
|
||||
controller.delegate = self
|
||||
@@ -106,11 +248,18 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
let seismics = getSeismics()
|
||||
let controller = SeismicNetworksMapDetailViewController(seismic: seismic, allSeismics: seismics)
|
||||
controller.delegate = self
|
||||
present(controller, animated: true, completion: nil)
|
||||
let navController = UINavigationController(rootViewController: controller)
|
||||
present(navController, animated: true, completion: nil)
|
||||
|
||||
self.currentMapController = controller
|
||||
}
|
||||
|
||||
private func showIntensityMap(for seismic: EQNSisma) {
|
||||
let controller = SeismicNetworksIntensityMapViewController(seismic: seismic)
|
||||
let navController = UINavigationController(rootViewController: controller)
|
||||
present(navController, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func didReceiveDownloadCompleteNotification(_ sender: Notification) {
|
||||
@@ -126,21 +275,26 @@ 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) }
|
||||
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")
|
||||
}
|
||||
|
||||
if informations.contains(.buttons) {
|
||||
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-collapse")
|
||||
} else {
|
||||
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-expand")
|
||||
}
|
||||
tableView.reloadData()
|
||||
updateCenterCellIndexPath()
|
||||
|
||||
tableView?.reloadData()
|
||||
if scrollToOpenedSeismic, let index = getSeismics().firstIndex(where: { isSeismicToHighlight(seismic: $0) }) {
|
||||
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
|
||||
scrollToOpenedSeismic = false
|
||||
}
|
||||
}
|
||||
|
||||
private func loadAd() {
|
||||
adLoader.load(GADRequest())
|
||||
adLoader.load(Request())
|
||||
}
|
||||
|
||||
private func loadData(forced: Bool) {
|
||||
@@ -152,7 +306,8 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
let allSeismics = EQNManager.manager().retiSismiche
|
||||
let filteredSeismics = EQNSeismic.shared.filterSeismicList(allSeismics ?? [])
|
||||
rows = filteredSeismics.map { .seismic($0) }
|
||||
|
||||
seismicViewModels = filteredSeismics.map(SeismicNetworkViewModel.init)
|
||||
|
||||
#if ADS_ENABLED
|
||||
// if is not a pro user, show an advertise
|
||||
if !EQNPurchaseUtility.isProVersionEnabled() {
|
||||
@@ -164,6 +319,9 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
if let mapController = currentMapController {
|
||||
mapController.updateSeismics(filteredSeismics)
|
||||
}
|
||||
|
||||
scrollIndicatorView.seismics = seismicViewModels
|
||||
currentCenteredIndexPath = nil
|
||||
}
|
||||
|
||||
private func getSeismics() -> [EQNSisma] {
|
||||
@@ -176,6 +334,307 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
return seismics
|
||||
}
|
||||
|
||||
private func changeSort(to sort: EQNSeismic.Sort) {
|
||||
EQNSeismic.shared.sort = sort
|
||||
EQNSeismic.shared.saveFilters()
|
||||
|
||||
setupSortMenu()
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
private func manageFilter(
|
||||
for notification: EQNOfficialPushNotification
|
||||
) {
|
||||
//gestisco i filtri solo se la posizione dell'utente è nota
|
||||
guard let userPosition = EQNUser.default().lastPosition else {
|
||||
return
|
||||
}
|
||||
|
||||
var filter_type = EQNSeismic.shared.filterOption
|
||||
var filter_radius = Double(EQNSeismic.shared.maximumDistance) ?? 0
|
||||
var filter_min_magnitude = Double(EQNSeismic.shared.minimumMagnitude) ?? 0
|
||||
|
||||
var filter_changed = false
|
||||
|
||||
//recupero i dati del sisma notificato
|
||||
let notification_magnitude = notification.magnitude
|
||||
let notification_latitude = notification.coordinate.coordinate.latitude
|
||||
let notification_longitude = notification.coordinate.coordinate.longitude
|
||||
|
||||
//distanza tra smartphone utente e sisma notificato
|
||||
let locationNotification = CLLocation(latitude: notification_latitude, longitude: notification_longitude)
|
||||
let distance = userPosition.distance(from: locationNotification) / 1_000
|
||||
|
||||
//verifico se il sisma è significativo in base alla definizione di significativo
|
||||
var is_significant = true
|
||||
if notification_magnitude < 7.0 && distance > 2000 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 6.5 && distance > 1600 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 6.0 && distance > 1300 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 5.5 && distance > 1000 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 5.0 && distance > 700 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 4.5 && distance > 500 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 4.0 && distance > 350 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 3.5 && distance > 200 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 3.0 && distance > 125 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 2.5 && distance > 70 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 2.0 && distance > 35 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 1.5 && distance > 20 {
|
||||
is_significant = false
|
||||
}
|
||||
|
||||
//verifico se devo modificare il filtro scelto dall'utente
|
||||
if filter_type == .inRadius { //filter_type=0 è il filtro basato su raggio e magnitudo
|
||||
if distance > 2000 && is_significant {
|
||||
filter_type = .positionRelevant //passo al filtro che mostra i sismi significativi (perché il raggio massimo del filtro basato sul raggio è 2000)
|
||||
updateFilter(type: filter_type)
|
||||
filter_changed = true
|
||||
}
|
||||
else if distance > 2000 && notification_magnitude >= 2.0 {
|
||||
filter_type = .worldWide //passo al filtro che mostra tutti i sismi nel mondo
|
||||
updateFilter(type: filter_type)
|
||||
filter_changed = true
|
||||
}
|
||||
else {
|
||||
//verifico se devo cambiare il raggio del filtro
|
||||
if distance > filter_radius {
|
||||
if distance > 1500 {
|
||||
filter_radius = 2000
|
||||
} else if distance > 1000 {
|
||||
filter_radius = 1500
|
||||
} else if distance > 750 {
|
||||
filter_radius = 1000
|
||||
} else if distance > 500 {
|
||||
filter_radius = 750
|
||||
} else if distance > 250 {
|
||||
filter_radius = 500
|
||||
} else if distance > 100 {
|
||||
filter_radius = 250
|
||||
}
|
||||
updateFilter(radius: filter_radius)
|
||||
|
||||
filter_changed = true
|
||||
}
|
||||
//verifico se devo cambiare la mgnitudo del filtro
|
||||
if notification_magnitude < filter_min_magnitude {
|
||||
if notification_magnitude < 1.0 {
|
||||
filter_min_magnitude = 0.0
|
||||
} else if notification_magnitude < 2.0 {
|
||||
filter_min_magnitude = 1.0
|
||||
} else if notification_magnitude < 3.0 {
|
||||
filter_min_magnitude = 2.0
|
||||
} else if notification_magnitude < 4.0 {
|
||||
filter_min_magnitude = 3.0
|
||||
} else if notification_magnitude < 5.0 {
|
||||
filter_min_magnitude = 4.0
|
||||
} else if notification_magnitude < 6.0 {
|
||||
filter_min_magnitude = 5.0
|
||||
}
|
||||
|
||||
filter_changed = true
|
||||
updateFilter(magnitude: filter_min_magnitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filter_type == .positionRelevant && !is_significant && distance <= 2000 {
|
||||
filter_type = .inRadius //passo a filtro basato su raggio e magnitudo
|
||||
updateFilter(type: filter_type)
|
||||
|
||||
if distance > filter_radius {
|
||||
if distance>1500 {
|
||||
filter_radius = 2000
|
||||
}
|
||||
else if distance > 1000 {
|
||||
filter_radius = 1500
|
||||
}
|
||||
else if distance > 750 {
|
||||
filter_radius = 1000
|
||||
}
|
||||
else if distance > 500 {
|
||||
filter_radius = 750
|
||||
}
|
||||
else if distance > 250 {
|
||||
filter_radius = 500
|
||||
}
|
||||
else if distance > 100 {
|
||||
filter_radius = 250
|
||||
}
|
||||
updateFilter(radius: filter_radius)
|
||||
}
|
||||
if notification_magnitude < filter_min_magnitude {
|
||||
if notification_magnitude < 1.0 {
|
||||
filter_min_magnitude = 0.0
|
||||
}
|
||||
else if notification_magnitude < 2.0 {
|
||||
filter_min_magnitude = 1.0
|
||||
}
|
||||
else if notification_magnitude < 3.0 {
|
||||
filter_min_magnitude = 2.0
|
||||
}
|
||||
else if notification_magnitude < 4.0 {
|
||||
filter_min_magnitude = 3.0
|
||||
}
|
||||
else if notification_magnitude < 5.0 {
|
||||
filter_min_magnitude = 4.0
|
||||
}
|
||||
else if notification_magnitude < 6.0 {
|
||||
filter_min_magnitude = 5.0
|
||||
}
|
||||
updateFilter(magnitude: filter_min_magnitude)
|
||||
}
|
||||
|
||||
filter_changed = true
|
||||
}
|
||||
|
||||
if filter_type == .positionRelevant && !is_significant && distance > 2000 && notification_magnitude >= 2.0 {
|
||||
filter_type = .worldWide //passo a filtro che mostra tutti i sismi nel mondo
|
||||
updateFilter(type: filter_type)
|
||||
filter_changed = true
|
||||
}
|
||||
|
||||
if filter_type == .worldWide && notification_magnitude < 2.0 && is_significant {
|
||||
filter_type = .positionRelevant //passo a filtro sismi significativi
|
||||
updateFilter(type: filter_type)
|
||||
filter_changed = true
|
||||
}
|
||||
|
||||
if filter_type == .worldWide && notification_magnitude < 2.0 && distance <= 2000 && !is_significant {
|
||||
filter_type = .inRadius
|
||||
updateFilter(type: filter_type)
|
||||
|
||||
if distance > filter_radius {
|
||||
if distance > 1500 {
|
||||
filter_radius = 2000
|
||||
}
|
||||
else if distance > 1000 {
|
||||
filter_radius = 1500
|
||||
}
|
||||
else if distance > 750 {
|
||||
filter_radius = 1000
|
||||
}
|
||||
else if distance > 500 {
|
||||
filter_radius = 750
|
||||
}
|
||||
else if distance > 250 {
|
||||
filter_radius = 500
|
||||
}
|
||||
else if distance > 100 {
|
||||
filter_radius = 250
|
||||
}
|
||||
updateFilter(radius: filter_radius)
|
||||
}
|
||||
if notification_magnitude < filter_min_magnitude {
|
||||
if notification_magnitude < 1.0 {
|
||||
filter_min_magnitude = 0.0
|
||||
}
|
||||
else if notification_magnitude < 2.0 {
|
||||
filter_min_magnitude = 1.0
|
||||
}
|
||||
else if notification_magnitude < 3.0 {
|
||||
filter_min_magnitude = 2.0
|
||||
}
|
||||
else if notification_magnitude < 4.0 {
|
||||
filter_min_magnitude = 3.0
|
||||
}
|
||||
else if notification_magnitude < 5.0 {
|
||||
filter_min_magnitude = 4.0
|
||||
}
|
||||
else if notification_magnitude < 6.0 {
|
||||
filter_min_magnitude = 5.0
|
||||
}
|
||||
updateFilter(magnitude: filter_min_magnitude)
|
||||
}
|
||||
|
||||
filter_changed = true
|
||||
}
|
||||
|
||||
//mostro all'utente un messaggio per avvisarlo che i filtri sono stati modificati
|
||||
configureFilterView(isVisible: filter_changed)
|
||||
if filter_changed {
|
||||
loadData(forced: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateFilter(
|
||||
type: EQNSeismic.FilterType? = nil,
|
||||
radius: Double? = nil,
|
||||
magnitude: Double? = nil
|
||||
) {
|
||||
if let type {
|
||||
EQNSeismic.shared.filterOption = type
|
||||
}
|
||||
if let radius {
|
||||
EQNSeismic.shared.maximumDistance = String(format: "%.0f", radius)
|
||||
}
|
||||
if let magnitude {
|
||||
EQNSeismic.shared.minimumMagnitude = String(format: "%.1f", magnitude)
|
||||
}
|
||||
EQNSeismic.shared.saveFilters()
|
||||
}
|
||||
|
||||
private func isLocationAvailable() -> Bool {
|
||||
EQNUser.default().lastPosition != nil
|
||||
}
|
||||
|
||||
private func isSeismicToHighlight(seismic: EQNSisma) -> Bool {
|
||||
guard let notification = openedPushNotification else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let seismicDate = seismic.date, let notificationDate = notification.date else { return false }
|
||||
|
||||
let deltaTime = abs(seismicDate.timeIntervalSince(notificationDate))
|
||||
let magnitudeRatio = seismic.magnitude.doubleValue / notification.magnitude
|
||||
let latitudeDiff = abs(seismic.coordinate.coordinate.latitude - notification.coordinate.coordinate.latitude)
|
||||
let longitudeDiff = abs(seismic.coordinate.coordinate.longitude - notification.coordinate.coordinate.longitude)
|
||||
if deltaTime <= 120 && magnitudeRatio > 0.8 && magnitudeRatio < 1.2 && latitudeDiff < 1 && longitudeDiff < 1 { // secondi?
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func getCenterCellIndexPath() -> IndexPath? {
|
||||
let centerPoint = CGPoint(x: tableView.bounds.midX, y: tableView.bounds.midY)
|
||||
if let indexPath = tableView.indexPathForRow(at: centerPoint) {
|
||||
return indexPath
|
||||
}
|
||||
|
||||
// Se il metodo diretto fallisce, cerchiamo la cella più vicina
|
||||
if let visibleIndexPaths = tableView.indexPathsForVisibleRows {
|
||||
return visibleIndexPaths.min(by: { (indexPath1, indexPath2) -> Bool in
|
||||
let rect1 = tableView.rectForRow(at: indexPath1)
|
||||
let rect2 = tableView.rectForRow(at: indexPath2)
|
||||
let distance1 = abs(rect1.midY - centerPoint.y)
|
||||
let distance2 = abs(rect2.midY - centerPoint.y)
|
||||
return distance1 < distance2
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func updateCenterCellIndexPath() {
|
||||
if let centerIndexPath = getCenterCellIndexPath(), centerIndexPath != currentCenteredIndexPath {
|
||||
currentCenteredIndexPath = centerIndexPath
|
||||
|
||||
let row = rows[centerIndexPath.row]
|
||||
if case .seismic = row, seismicViewModels.count > centerIndexPath.row {
|
||||
scrollIndicatorView.highlighted = seismicViewModels[centerIndexPath.row]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func refreshDataTapped(_ sender: Any) {
|
||||
@@ -185,19 +644,19 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
@IBAction func openFilterTapped(_ sender: Any) {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierFilters, sender: nil)
|
||||
}
|
||||
|
||||
@IBAction func openSettingsTapped(_ sender: Any) {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierSettings, sender: nil)
|
||||
}
|
||||
|
||||
|
||||
@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()
|
||||
}
|
||||
|
||||
@@ -211,18 +670,28 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
let row = rows[indexPath.row]
|
||||
switch row {
|
||||
case .seismic(let seismic):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: SeismicNetworkTableViewCell.Identifier, for: indexPath) as! SeismicNetworkTableViewCell
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
cell.configure(with: seismic, type: type, informations: informations)
|
||||
cell.delegate = self
|
||||
return cell
|
||||
case .advertise(let nativeAd):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: SeismicNetworkAdvertiseTableViewCell.Identifier, for: indexPath) as! SeismicNetworkAdvertiseTableViewCell
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkAdvertiseTableViewCell.self, for: indexPath)
|
||||
cell.loadNativeAd(nativeAd)
|
||||
return cell
|
||||
}
|
||||
@@ -237,6 +706,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIScrollViewDelegate
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
updateCenterCellIndexPath()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func openCalendar(for seismic: EQNSisma) {
|
||||
@@ -292,25 +767,25 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworksViewController: GADNativeAdLoaderDelegate {
|
||||
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADNativeAd) {
|
||||
print("[GADAdLoader] didReceive")
|
||||
extension SeismicNetworksViewController: NativeAdLoaderDelegate {
|
||||
func adLoader(_ adLoader: AdLoader, didReceive nativeAd: NativeAd) {
|
||||
print("[AdLoader] didReceive")
|
||||
|
||||
let adPosition = min(3, rows.count)
|
||||
rows.insert(.advertise(nativeAd), at: adPosition)
|
||||
tableView?.reloadData()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: Error) {
|
||||
func adLoader(_ adLoader: AdLoader, didFailToReceiveAdWithError error: Error) {
|
||||
// nope
|
||||
print("[GADAdLoader] didFailToReceiveAdWithError: \(error.localizedDescription)")
|
||||
print("[AdLoader] didFailToReceiveAdWithError: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
||||
extension SeismicNetworksViewController: SeismicNetworkBaseTableViewCellDelegate {
|
||||
|
||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
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
|
||||
let snapshot = cell.contentView.createSnapshot()
|
||||
@@ -326,38 +801,44 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell) else { return }
|
||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
guard let index = tableView.indexPath(for: cell) else { return }
|
||||
|
||||
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
||||
|
||||
openMapIndexPath = index
|
||||
tableView?.reloadRows(at: indexToReloads, with: .automatic)
|
||||
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
|
||||
showMapDetail(for: seismic)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
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: 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) {
|
||||
guard let index = tableView?.indexPath(for: cell) else { return }
|
||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
guard let index = tableView.indexPath(for: cell) else { return }
|
||||
|
||||
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
||||
|
||||
openMapIndexPath = nil
|
||||
tableView?.reloadRows(at: indexToReloads, with: .automatic)
|
||||
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,22 +856,6 @@ extension SeismicNetworksViewController: SeismicFiltersViewControllerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworksViewController: SeismicSettingsViewControllerDelegate {
|
||||
func seismicSettingsControllerDidComplete(_ controller: SeismicSettingsViewController) {
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
func seismicSettingsControllerWillOpenProviders(_ controller: SeismicSettingsViewController) {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierSeismicNetworks, sender: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworksViewController: SeismicSettingsNetworksViewControllerDelegate {
|
||||
func seismicSettingsNetworksControllerDidComplete(_ controller: SeismicSettingsNetworksViewController) {
|
||||
refreshUI()
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelegate {
|
||||
func seismicCardSettingsDidComplete(_ controller: SeismicCardSettingsViewController) {
|
||||
refreshUI()
|
||||
@@ -398,9 +863,12 @@ extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelega
|
||||
}
|
||||
|
||||
extension SeismicNetworksViewController: DZNEmptyDataSetSource {
|
||||
func title(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! {
|
||||
func title(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
|
||||
let text = EQNSeismic.shared.filterOption == .positionRelevant
|
||||
? NSLocalizedString("filter_empty_relevant", comment: "")
|
||||
: NSLocalizedString("filter_empty_area", comment: "")
|
||||
let attributes = [ NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body) ]
|
||||
let string = NSAttributedString(string: NSLocalizedString("filter_empty", comment: ""), attributes: attributes)
|
||||
let string = NSAttributedString(string: text, attributes: attributes)
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
//
|
||||
// SeismicSettingsNetworksViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 14/09/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
protocol SeismicSettingsNetworksViewControllerDelegate: AnyObject {
|
||||
func seismicSettingsNetworksControllerDidComplete(_ controller: SeismicSettingsNetworksViewController)
|
||||
}
|
||||
|
||||
class SeismicSettingsNetworksViewController: UITableViewController {
|
||||
|
||||
weak var delegate: SeismicSettingsNetworksViewControllerDelegate?
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var networks = [EQNSeismicNetwork]()
|
||||
private var savedNetworks = [String]()
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
tableView.register(SettingDetailTableViewCell.self, forCellReuseIdentifier: SettingDetailTableViewCell.Identifier)
|
||||
tableView.register(SettingSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: SettingSectionHeaderView.Identifier)
|
||||
|
||||
loadData()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadData() {
|
||||
networks = EQNData.seismicNetworks().sorted(by: { $0.acronym < $1.acronym })
|
||||
|
||||
// load saved selected networks or fill with all available networks
|
||||
let savedNetworks = EQNUserData.shared.seismicNetworksSelected()
|
||||
if !savedNetworks.isEmpty {
|
||||
self.savedNetworks = savedNetworks
|
||||
} else {
|
||||
self.savedNetworks = EQNData.seismicNetworkAcronyms()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SettingSectionHeaderView.Identifier) as! SettingSectionHeaderView
|
||||
headerView.titleLabel.text = NSLocalizedString("options_agencies", comment: "");
|
||||
return headerView
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
CGFloat(SettingSectionHeaderView.Height)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
networks.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let network = networks[indexPath.row]
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: SettingDetailTableViewCell.Identifier, for: indexPath) as! SettingDetailTableViewCell
|
||||
cell.textLabel?.text = "\(network.acronym) (\(network.country))"
|
||||
|
||||
if savedNetworks.contains(network.acronym) {
|
||||
cell.accessoryType = .checkmark
|
||||
} else {
|
||||
cell.accessoryType = .none
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let network = networks[indexPath.row]
|
||||
if let index = savedNetworks.firstIndex(of: network.acronym) {
|
||||
savedNetworks.remove(at: index)
|
||||
} else {
|
||||
savedNetworks.append(network.acronym)
|
||||
}
|
||||
|
||||
// reload all rows with the given acronym
|
||||
let indexes = networks
|
||||
.enumerated()
|
||||
.filter { $0.element.acronym == network.acronym }
|
||||
.map { IndexPath(row: $0.offset, section: 0) }
|
||||
tableView.reloadRows(at: indexes, with: .automatic)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func cancelTapped(_ sender: Any) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func saveTapped(_ sender: Any) {
|
||||
// save selected networks
|
||||
EQNUserData.shared.saveSelectedSeismicNetworks(savedNetworks)
|
||||
|
||||
// se solo un'ente è selezionato, salviamolo anche come nazione
|
||||
if savedNetworks.count == 1 {
|
||||
UserDefaults.standard.set(savedNetworks.first!, forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE)
|
||||
} else {
|
||||
UserDefaults.standard.removeObject(forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE)
|
||||
}
|
||||
|
||||
delegate?.seismicSettingsNetworksControllerDidComplete(self)
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
//
|
||||
// SeismicSettingsViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 13/09/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
protocol SeismicSettingsViewControllerDelegate: AnyObject {
|
||||
func seismicSettingsControllerDidComplete(_ controller: SeismicSettingsViewController)
|
||||
func seismicSettingsControllerWillOpenProviders(_ controller: SeismicSettingsViewController)
|
||||
}
|
||||
|
||||
|
||||
class SeismicSettingsViewController: UIViewController {
|
||||
|
||||
weak var delegate: SeismicSettingsViewControllerDelegate?
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@IBOutlet private weak var containerView: UIView!
|
||||
@IBOutlet private weak var titleLabel: UILabel!
|
||||
@IBOutlet private weak var countryTextField: UITextField!
|
||||
@IBOutlet private weak var confirmButton: UIButton!
|
||||
@IBOutlet private weak var otherwiseLabel: UILabel!
|
||||
@IBOutlet private weak var manageNetworksButton: UIButton!
|
||||
@IBOutlet private weak var cancelButton: UIButton!
|
||||
|
||||
|
||||
private let networks = EQNData.seismicNetworks().sorted(by: { $0.country < $1.country })
|
||||
private let picker = EQNGenericPickerViewController()
|
||||
private var selectedNetwork: EQNSeismicNetwork?
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupUI() {
|
||||
containerView.layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
||||
containerView.layer.masksToBounds = true
|
||||
|
||||
// localize
|
||||
titleLabel.text = NSLocalizedString("official_select_country", comment: "")
|
||||
countryTextField.placeholder = NSLocalizedString("official_select_country_placeholder", comment: "")
|
||||
confirmButton.setLocalizedTitle(key: "official_select_confirm", uppercased: false)
|
||||
otherwiseLabel.text = NSLocalizedString("official_select_or", comment: "")
|
||||
manageNetworksButton.setLocalizedTitle(key: "official_select_networks", uppercased: false)
|
||||
cancelButton.setLocalizedTitle(key: "status_cancel", uppercased: false)
|
||||
|
||||
// load saved country (if exists)
|
||||
let savedCountry = UserDefaults.standard.object(forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE) as? String
|
||||
selectedNetwork = EQNData.seismic(for: savedCountry)
|
||||
|
||||
countryTextField.text = selectedNetwork?.country
|
||||
countryTextField.inputView = picker.view
|
||||
|
||||
let selectedIndex: Int? = selectedNetwork != nil ? networks.firstIndex(of: selectedNetwork!) : nil
|
||||
picker.configure(with: networks, selectedIndex: selectedIndex) { [unowned self] (network) in
|
||||
guard let network = network as? EQNSeismicNetwork else { return }
|
||||
|
||||
self.view.endEditing(true)
|
||||
self.selectedNetwork = network
|
||||
self.countryTextField.text = self.selectedNetwork?.country
|
||||
}
|
||||
picker.onCancel = { [unowned self] in
|
||||
self.view.endEditing(true)
|
||||
}
|
||||
}
|
||||
|
||||
private func performSave(for network: EQNSeismicNetwork) {
|
||||
// salviamo la sigla dell'ente selezionato
|
||||
UserDefaults.standard.set(network.acronym, forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE)
|
||||
|
||||
// gli enti selezionati conterranno solo l'ente della nazione selezionata
|
||||
let selectedNetworks = [network.acronym]
|
||||
EQNUserData.shared.saveSelectedSeismicNetworks(selectedNetworks)
|
||||
|
||||
// aggiorniamo le impostazioni di notifica
|
||||
EQNNotificheReteSismiche.shared().listaEnti = selectedNetworks
|
||||
EQNNotificheReteSismiche.shared().saveUserInfo()
|
||||
SettingsBaseViewController.saveSettings()
|
||||
|
||||
// informiamo il delegato
|
||||
delegate?.seismicSettingsControllerDidComplete(self)
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func confirmCountryTapped(_ sender: UIButton) {
|
||||
guard let network = selectedNetwork else {
|
||||
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
|
||||
message: NSLocalizedString("official_no_country_selected", comment: ""),
|
||||
preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .cancel, handler: { [unowned self] (action) in
|
||||
self.countryTextField.becomeFirstResponder()
|
||||
}))
|
||||
present(alert, animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
|
||||
// ask confirm to change settings for notifications
|
||||
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
|
||||
message: NSLocalizedString("official_select_message", comment: ""),
|
||||
preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("status_cancel", comment: ""), style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("official_select_confirm", comment: ""), style: .default, handler: { [unowned self] (action) in
|
||||
self.performSave(for: network)
|
||||
}))
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func selectNetworksTapped(_ sender: UIButton) {
|
||||
delegate?.seismicSettingsControllerWillOpenProviders(self)
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func cancelTapped(_ sender: UIButton) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -11,27 +11,27 @@ import Foundation
|
||||
|
||||
class SettingDateTableViewCell: UITableViewCell {
|
||||
|
||||
@objc static let Identifier = "DateCell"
|
||||
static let Identifier = "DateCell"
|
||||
|
||||
@objc var isDisabled: Bool = false {
|
||||
var isDisabled: Bool = false {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
|
||||
@objc var isPickerVisible: Bool = false {
|
||||
var isPickerVisible: Bool = false {
|
||||
didSet {
|
||||
if oldValue != isPickerVisible {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
@objc private(set) var date = Date()
|
||||
@objc var valueChanged: ((Date) -> Void)?
|
||||
private(set) var date = Date()
|
||||
var valueChanged: ((Date) -> Void)?
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@objc lazy var titleLabel: UILabel = {
|
||||
lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
@@ -40,7 +40,7 @@ class SettingDateTableViewCell: UITableViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
@objc lazy var valuesLabel: UILabel = {
|
||||
lazy var valuesLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
@@ -58,7 +58,7 @@ class SettingDateTableViewCell: UITableViewCell {
|
||||
return picker
|
||||
}()
|
||||
|
||||
@objc lazy var stackView: UIStackView = {
|
||||
lazy var stackView: UIStackView = {
|
||||
let stackView = UIStackView()
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
@@ -81,7 +81,7 @@ class SettingDateTableViewCell: UITableViewCell {
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc public func updateDate(_ date: Date) {
|
||||
public func updateDate(_ date: Date) {
|
||||
self.date = date
|
||||
datePicker.setDate(date, animated: true)
|
||||
}
|
||||
|
||||
+3
-4
@@ -10,17 +10,16 @@ import UIKit
|
||||
|
||||
class SettingDetailTableViewCell: UITableViewCell {
|
||||
|
||||
@objc static let Identifier = "DetailCell"
|
||||
static let Identifier = "DetailCell"
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
// Initialization code
|
||||
}
|
||||
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
|
||||
|
||||
// Configure the view for the selected state
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-7
@@ -10,10 +10,10 @@ import UIKit
|
||||
|
||||
class SettingEnableTableViewCell: UITableViewCell {
|
||||
|
||||
@objc static let Identifier = "EnableCell"
|
||||
static let Identifier = "EnableCell"
|
||||
|
||||
@objc var valueChanged: ((Bool) -> Void)?
|
||||
@objc var isDisabled: Bool = false {
|
||||
var valueChanged: ((Bool) -> Void)?
|
||||
var isDisabled: Bool = false {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class SettingEnableTableViewCell: UITableViewCell {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@objc lazy var titleLabel: UILabel = {
|
||||
lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
@@ -29,7 +29,7 @@ class SettingEnableTableViewCell: UITableViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
@objc lazy var descriptionLabel: UILabel = {
|
||||
lazy var descriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
@@ -37,7 +37,7 @@ class SettingEnableTableViewCell: UITableViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
@objc lazy var toggleSwitch: UISwitch = {
|
||||
lazy var toggleSwitch: UISwitch = {
|
||||
let toggle = UISwitch()
|
||||
toggle.setContentHuggingPriority(.required, for: .horizontal)
|
||||
toggle.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
@@ -45,6 +45,15 @@ class SettingEnableTableViewCell: UITableViewCell {
|
||||
return toggle
|
||||
}()
|
||||
|
||||
lazy var errorLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.font = UIFont.preferredFont(forTextStyle: .subheadline)
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.text = nil
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
@@ -75,6 +84,7 @@ class SettingEnableTableViewCell: UITableViewCell {
|
||||
|
||||
contentView.addSubview(stackView)
|
||||
contentView.addSubview(descriptionLabel)
|
||||
contentView.addSubview(errorLabel)
|
||||
|
||||
stackView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
@@ -83,7 +93,12 @@ class SettingEnableTableViewCell: UITableViewCell {
|
||||
descriptionLabel.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 8).isActive = true
|
||||
descriptionLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
|
||||
descriptionLabel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
|
||||
descriptionLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
//descriptionLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
|
||||
errorLabel.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 8.0).isActive = true
|
||||
errorLabel.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
|
||||
errorLabel.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
|
||||
errorLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
|
||||
+4
-4
@@ -11,9 +11,9 @@ import Foundation
|
||||
|
||||
class SettingMultivaluesTableViewCell: UITableViewCell {
|
||||
|
||||
@objc static let Identifier = "MultivaluesCell"
|
||||
static let Identifier = "MultivaluesCell"
|
||||
|
||||
@objc var isDisabled: Bool = false {
|
||||
var isDisabled: Bool = false {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class SettingMultivaluesTableViewCell: UITableViewCell {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@objc lazy var titleLabel: UILabel = {
|
||||
lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
@@ -30,7 +30,7 @@ class SettingMultivaluesTableViewCell: UITableViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
@objc lazy var valuesLabel: UILabel = {
|
||||
lazy var valuesLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
|
||||
+3
-6
@@ -10,18 +10,17 @@ import UIKit
|
||||
|
||||
class SettingSectionHeaderView: UITableViewHeaderFooterView {
|
||||
|
||||
@objc static let Identifier = "SectionHeaderView"
|
||||
@objc static let Height = 50.0
|
||||
static let Identifier = "SectionHeaderView"
|
||||
static let Height = 50.0
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@objc lazy var titleLabel: UILabel = {
|
||||
lazy var titleLabel: UILabel = {
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
|
||||
titleLabel.textColor = AppTheme.Colors.lightBlue
|
||||
return titleLabel
|
||||
}()
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
@@ -34,7 +33,6 @@ class SettingSectionHeaderView: UITableViewHeaderFooterView {
|
||||
super.init(coder: coder)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@@ -45,6 +43,5 @@ class SettingSectionHeaderView: UITableViewHeaderFooterView {
|
||||
titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
|
||||
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
|
||||
titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -11,19 +11,19 @@ import Foundation
|
||||
|
||||
class SettingSegmentedTableViewCell: UITableViewCell {
|
||||
|
||||
@objc static let Identifier = "SegmentedCell"
|
||||
static let Identifier = "SegmentedCell"
|
||||
|
||||
@objc var isDisabled: Bool = false {
|
||||
var isDisabled: Bool = false {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
@objc var valueChanged: ((EQNGenericValue) -> Void)?
|
||||
var valueChanged: ((EQNGenericValue) -> Void)?
|
||||
private var items = [EQNGenericValue]()
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@objc lazy var titleLabel: UILabel = {
|
||||
lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
@@ -32,7 +32,7 @@ class SettingSegmentedTableViewCell: UITableViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
@objc lazy var segmentedControl: UISegmentedControl = {
|
||||
lazy var segmentedControl: UISegmentedControl = {
|
||||
let segmented = UISegmentedControl()
|
||||
segmented.translatesAutoresizingMaskIntoConstraints = false
|
||||
segmented.addTarget(self, action: #selector(segmentedControlChanged(_:)), for: .valueChanged)
|
||||
|
||||
+7
-7
@@ -10,21 +10,21 @@ import UIKit
|
||||
|
||||
class SettingSliderTableViewCell: UITableViewCell {
|
||||
|
||||
@objc static let Identifier = "SliderCell"
|
||||
static let Identifier = "SliderCell"
|
||||
|
||||
@objc var isDisabled: Bool = false {
|
||||
var isDisabled: Bool = false {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
@objc var valueChanged: ((EQNGenericValue) -> Void)?
|
||||
@objc var dragEnded: (() -> Void)?
|
||||
var valueChanged: ((EQNGenericValue) -> Void)?
|
||||
var dragEnded: (() -> Void)?
|
||||
private var items = [EQNGenericValue]()
|
||||
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@objc lazy var titleLabel: UILabel = {
|
||||
lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
@@ -33,7 +33,7 @@ class SettingSliderTableViewCell: UITableViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
@objc lazy var valueLabel: UILabel = {
|
||||
lazy var valueLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
@@ -42,7 +42,7 @@ class SettingSliderTableViewCell: UITableViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
@objc lazy var slider: UISlider = {
|
||||
lazy var slider: UISlider = {
|
||||
let slider = UISlider()
|
||||
slider.isContinuous = true
|
||||
slider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// SettingsBaseTableViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 10/06/24.
|
||||
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc
|
||||
class SettingsBaseTableViewController: UITableViewController {
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
if isMovingFromParent {
|
||||
Self.saveSettings()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Class
|
||||
|
||||
@objc class func saveSettings() {
|
||||
saveSettings { _ in
|
||||
// nope
|
||||
}
|
||||
}
|
||||
|
||||
@objc class func saveSettings(
|
||||
completion: @escaping (_ success: Bool) -> Void
|
||||
) {
|
||||
|
||||
let url = EQNGeneratoreURLServer.urlInvioImpostazioniNotifiche()
|
||||
ServerRequest.default().inviaInformazioniAlServer(with: url, richiesta: .impostazioniNotifiche) { _ in
|
||||
print("[SETTINGS] Settings saved successfully")
|
||||
completion(true)
|
||||
} failure: { error in
|
||||
print("[SETTINGS] Settings saved failed. Error: \(error?.localizedDescription ?? "n.d.")")
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// SettingsBaseViewController.h
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 30/08/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsBaseViewController : UITableViewController
|
||||
|
||||
+ (void)saveSettings;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// SettingsBaseViewController.m
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 30/08/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SettingsBaseViewController.h"
|
||||
#import "ServerRequest.h"
|
||||
#import "EQNGeneratoreURLServer.h"
|
||||
|
||||
@interface SettingsBaseViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation SettingsBaseViewController
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated
|
||||
{
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
// when controller is dismissed, save settings
|
||||
if (self.isMovingFromParentViewController) {
|
||||
[SettingsBaseViewController saveSettings];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
+ (void)saveSettings
|
||||
{
|
||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[EQNGeneratoreURLServer urlInvioImpostazioniNotifiche] richiesta:EQNTipoChiamataImpostazioniNotifiche success:^(id result){
|
||||
NSLog(@"Settings saved successfully");
|
||||
} failure:^(NSError *error){
|
||||
NSLog(@"Settings saved failed. Error: %@", error.localizedDescription);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// AletaSismiTableViewController.h
|
||||
// Earthquake Network
|
||||
//
|
||||
// Refactored by Andrea Busi 25/08/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "SettingsBaseViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsRealTimeAlertsViewController : SettingsBaseViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
//
|
||||
// AletaSismiTableViewController.m
|
||||
// Earthquake Network
|
||||
//
|
||||
// Refactored by Andrea Busi 25/08/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SettingsRealTimeAlertsViewController.h"
|
||||
#import "EQNAllertaSismica.h"
|
||||
@import UserNotifications;
|
||||
|
||||
@interface SettingsRealTimeAlertsViewController () <UITextFieldDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
|
||||
|
||||
@property (nonatomic, strong) NSDateFormatter *dateFormatter;
|
||||
|
||||
@property (nonatomic, assign) BOOL notificationEnabled;
|
||||
@property (nonatomic, assign) BOOL criticalAlertsEnabled;
|
||||
@end
|
||||
|
||||
@implementation SettingsRealTimeAlertsViewController
|
||||
|
||||
typedef NS_ENUM(NSInteger, RowIdentifier) {
|
||||
RowIdentifierAbilitaNotifiche = 0,
|
||||
RowIdentifierAbilitaCriticalAlerts
|
||||
};
|
||||
|
||||
#pragma mark - Accessories
|
||||
|
||||
- (NSDateFormatter *)dateFormatter
|
||||
{
|
||||
if (!_dateFormatter) {
|
||||
_dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[_dateFormatter setDateFormat:@"HH:mm"];
|
||||
}
|
||||
return _dateFormatter;
|
||||
}
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
[self setupUI];
|
||||
|
||||
self.settings = @[
|
||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_alarm", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"critical_alerts_setting", @"")]
|
||||
];
|
||||
|
||||
[self loadDataSource];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)setupUI
|
||||
{
|
||||
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
|
||||
|
||||
self.tableView.estimatedRowHeight = 200.0;
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
|
||||
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
|
||||
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
|
||||
[self.tableView registerClass:[SettingMultivaluesTableViewCell class] forCellReuseIdentifier:SettingMultivaluesTableViewCell.Identifier];
|
||||
[self.tableView registerClass:[SettingSegmentedTableViewCell class] forCellReuseIdentifier:SettingSegmentedTableViewCell.Identifier];
|
||||
[self.tableView registerClass:[SettingDateTableViewCell class] forCellReuseIdentifier:SettingDateTableViewCell.Identifier];
|
||||
}
|
||||
|
||||
- (void)loadDataSource
|
||||
{
|
||||
self.notificationEnabled = [EQNAllertaSismica sharedInstance].isAbilitato;
|
||||
self.criticalAlertsEnabled = [EQNAllertaSismica sharedInstance].isCriticalAlertsEnabled;
|
||||
|
||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return self.settings.count;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
|
||||
headerView.titleLabel.text = NSLocalizedString(@"options_alarms", @"");
|
||||
return headerView;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return SettingSectionHeaderView.Height;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
SettingItem *setting = self.settings[indexPath.row];
|
||||
|
||||
if (setting.type == SettingTypeEnable) {
|
||||
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.titleLabel.text = setting.displayTitle;
|
||||
cell.descriptionLabel.text = setting.subtitle;
|
||||
|
||||
if (indexPath.row == RowIdentifierAbilitaNotifiche) {
|
||||
cell.toggleSwitch.on = self.notificationEnabled;
|
||||
cell.valueChanged = ^(BOOL enabled) {
|
||||
self.notificationEnabled = enabled;
|
||||
[EQNAllertaSismica sharedInstance].isAbilitato = self.notificationEnabled;
|
||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
||||
[self.tableView reloadData];
|
||||
};
|
||||
} else if (indexPath.row == RowIdentifierAbilitaCriticalAlerts) {
|
||||
cell.toggleSwitch.on = self.criticalAlertsEnabled;
|
||||
cell.valueChanged = ^(BOOL enabled) {
|
||||
if (enabled) {
|
||||
[self askForCriticalAlertsPermission];
|
||||
}
|
||||
|
||||
self.criticalAlertsEnabled = enabled;
|
||||
[EQNAllertaSismica sharedInstance].isCriticalAlertsEnabled = self.criticalAlertsEnabled;
|
||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
||||
[self.tableView reloadData];
|
||||
};
|
||||
}
|
||||
|
||||
return cell;
|
||||
} else if (setting.type == SettingTypeSegmented) {
|
||||
SettingSegmentedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSegmentedTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.titleLabel.text = setting.displayTitle;
|
||||
return cell;
|
||||
} else if (setting.type == SettingTypeSlider) {
|
||||
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.titleLabel.text = setting.displayTitle;
|
||||
return cell;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)updateSismicToNotify:(EQNGenericValue *)seismic
|
||||
{
|
||||
[EQNAllertaSismica sharedInstance].sismiDaNotificare = seismic.value;
|
||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
||||
|
||||
[self loadDataSource];
|
||||
}
|
||||
|
||||
- (void)askForCriticalAlertsPermission
|
||||
{
|
||||
UNAuthorizationOptions authOptions = UNAuthorizationOptionCriticalAlert;
|
||||
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError *error) {
|
||||
// nope
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
//
|
||||
// SettingsRealTimeAlertsViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 10/06/24.
|
||||
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
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: ""))
|
||||
]
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
loadDataSource()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupUI() {
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
|
||||
tableView.estimatedRowHeight = 200.0
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||
tableView.registerCell(for: SettingEnableTableViewCell.self)
|
||||
}
|
||||
|
||||
private func loadDataSource() {
|
||||
let saved = EQNSettingRealTimeAlert.shared
|
||||
|
||||
isNotificationEnabled = saved.isAbilitato
|
||||
isMildQuakeSoundDisabled = saved.isMildQuakeSoundDisabled
|
||||
isCriticalAlertsEnabled = saved.isCriticalAlertsEnabled
|
||||
}
|
||||
|
||||
// MARK: - Table view delegate and data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||
view.titleLabel.text = NSLocalizedString("options_alarms", comment: "")
|
||||
return view
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
SettingSectionHeaderView.Height
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return settings.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
|
||||
let setting = settings[indexPath.row]
|
||||
switch setting.type {
|
||||
case .enable:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
|
||||
cell.titleLabel.text = setting.displayTitle
|
||||
cell.descriptionLabel.text = setting.subtitle
|
||||
|
||||
switch identifier {
|
||||
case .abilitaNotifiche:
|
||||
cell.toggleSwitch.isOn = isNotificationEnabled
|
||||
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
|
||||
self?.onChangeCriticalAlertsEnabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
return cell
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||
isNotificationEnabled = enabled
|
||||
EQNSettingRealTimeAlert.shared.isAbilitato = isNotificationEnabled
|
||||
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
isCriticalAlertsEnabled = enabled
|
||||
EQNSettingRealTimeAlert.shared.isCriticalAlertsEnabled = isCriticalAlertsEnabled
|
||||
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func askForCriticalAlertsPermission() {
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [ .criticalAlert ]) { granted, error in
|
||||
// nope
|
||||
}
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// SettingsSeismicNetworkAlertsViewController.h
|
||||
// Earthquake Network
|
||||
//
|
||||
// Refactored by Andrea Busi 25/08/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "SettingsBaseViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsSeismicNetworkAlertsViewController : SettingsBaseViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
//
|
||||
// SettingsSeismicNetworkAlertsViewController.m
|
||||
// Earthquake Network
|
||||
//
|
||||
// Refactored by Andrea Busi 25/08/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SettingsSeismicNetworkAlertsViewController.h"
|
||||
#import "EQNNotificheReteSismiche.h"
|
||||
|
||||
@interface SettingsSeismicNetworkAlertsViewController ()
|
||||
|
||||
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
|
||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceRaggioSisma;
|
||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceMagnitudoDeboli;
|
||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceMagnitudoForti;
|
||||
|
||||
@property (nonatomic, assign) BOOL notificationEnabled;
|
||||
@property (nonatomic, assign) BOOL notificationNearEarthquakeEnabled;
|
||||
@property (nonatomic, assign) BOOL notificationStrongEarthquakeEnabled;
|
||||
|
||||
@property (nonatomic, strong) EQNGenericValue *currentUserPositionRadius;
|
||||
@property (nonatomic, strong) EQNGenericValue *currentSeismicEnergy;
|
||||
@property (nonatomic, strong) EQNGenericValue *currentStrongEarthquakeDistance;
|
||||
@end
|
||||
|
||||
@implementation SettingsSeismicNetworkAlertsViewController
|
||||
|
||||
static NSString * const SegueIdentifierListaEnti = @"ShowListaEnti";
|
||||
|
||||
|
||||
typedef NS_ENUM(NSInteger, RowIdentifier) {
|
||||
RowIdentifierAbilitaNotifiche = 0,
|
||||
RowIdentifierRetiSismiche,
|
||||
RowIdentifierRaggioPosizione,
|
||||
RowIdentifierEnergiaSisma,
|
||||
RowIdentifierTerremotiVicini,
|
||||
RowIdentifierTerremotiForti,
|
||||
RowIdentifierTerremotiFortiDistanza
|
||||
};
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
[self setupUI];
|
||||
|
||||
self.settings = @[
|
||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_official", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeMultiValues title:NSLocalizedString(@"options_agencies", @"") segue:SegueIdentifierListaEnti],
|
||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_energy", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_near", @"") subtitle:NSLocalizedString(@"options_near_alert", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_strong", @"") subtitle:NSLocalizedString(@"options_strong_alert", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_strong_magnitude", @"")]
|
||||
];
|
||||
|
||||
self.dataSourceMagnitudoDeboli = [EQNData magitudoDeboli];
|
||||
self.dataSourceRaggioSisma = [EQNData raggioSismi];
|
||||
self.dataSourceMagnitudoForti = [EQNData magitudoForti];
|
||||
|
||||
[self loadDataSource];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[self loadDataSource];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)setupUI
|
||||
{
|
||||
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
|
||||
|
||||
self.tableView.estimatedRowHeight = 200.0;
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
|
||||
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
|
||||
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
|
||||
[self.tableView registerClass:[SettingMultivaluesTableViewCell class] forCellReuseIdentifier:SettingMultivaluesTableViewCell.Identifier];
|
||||
}
|
||||
|
||||
- (void)loadDataSource
|
||||
{
|
||||
self.notificationEnabled = [EQNNotificheReteSismiche sharedInstance].isAbilitato;
|
||||
self.notificationNearEarthquakeEnabled = [EQNNotificheReteSismiche sharedInstance].isAbilitaVicini;
|
||||
self.notificationStrongEarthquakeEnabled = [EQNNotificheReteSismiche sharedInstance].isTerremortiForti;
|
||||
|
||||
// raggio dalla tua posizione
|
||||
EQNGenericValue *raggioSisma = [EQNData raggioSismaFor:[EQNNotificheReteSismiche sharedInstance].distanzaPosizione];
|
||||
self.currentUserPositionRadius = raggioSisma;
|
||||
|
||||
// energia sisma
|
||||
EQNGenericValue *energiaSisma = [EQNData magitudoDeboleFor:[EQNNotificheReteSismiche sharedInstance].energiaSisma];
|
||||
self.currentSeismicEnergy = energiaSisma;
|
||||
|
||||
// terremoti forti
|
||||
EQNGenericValue *terremotiForti = [EQNData magitudoForteFor:[EQNNotificheReteSismiche sharedInstance].energiaTerremotiForti];
|
||||
self.currentStrongEarthquakeDistance = terremotiForti;
|
||||
|
||||
// enti
|
||||
if (![EQNNotificheReteSismiche sharedInstance].listaEnti) {
|
||||
[EQNNotificheReteSismiche sharedInstance].listaEnti = [EQNData.seismicNetworkAcronyms copy];
|
||||
}
|
||||
|
||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
|
||||
headerView.titleLabel.text = NSLocalizedString(@"options_notification_official", @"titolo impostazioni notifiche");
|
||||
return headerView;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return SettingSectionHeaderView.Height;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return self.settings.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
SettingItem *setting = self.settings[indexPath.row];
|
||||
|
||||
if (setting.type == SettingTypeEnable) {
|
||||
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.titleLabel.text = setting.displayTitle;
|
||||
cell.descriptionLabel.text = setting.subtitle;
|
||||
|
||||
if (indexPath.row == RowIdentifierAbilitaNotifiche) {
|
||||
cell.toggleSwitch.on = self.notificationEnabled;
|
||||
cell.valueChanged = ^(BOOL enabled) {
|
||||
self.notificationEnabled = enabled;
|
||||
[EQNNotificheReteSismiche sharedInstance].isAbilitato = self.notificationEnabled;
|
||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
||||
[self.tableView reloadData];
|
||||
};
|
||||
} else if (indexPath.row == RowIdentifierTerremotiVicini) {
|
||||
cell.toggleSwitch.on = self.notificationNearEarthquakeEnabled;
|
||||
cell.isDisabled = !self.notificationEnabled;
|
||||
cell.valueChanged = ^(BOOL enabled) {
|
||||
self.notificationNearEarthquakeEnabled = enabled;
|
||||
[EQNNotificheReteSismiche sharedInstance].isAbilitaVicini = self.notificationNearEarthquakeEnabled;
|
||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
||||
[self.tableView reloadData];
|
||||
};
|
||||
} else if (indexPath.row == RowIdentifierTerremotiForti) {
|
||||
cell.toggleSwitch.on = self.notificationStrongEarthquakeEnabled;
|
||||
cell.isDisabled = !self.notificationEnabled;
|
||||
cell.valueChanged = ^(BOOL enabled) {
|
||||
self.notificationStrongEarthquakeEnabled = enabled;
|
||||
[EQNNotificheReteSismiche sharedInstance].isTerremortiForti = self.notificationStrongEarthquakeEnabled;
|
||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
||||
[self.tableView reloadData];
|
||||
};
|
||||
}
|
||||
|
||||
return cell;
|
||||
} else if (setting.type == SettingTypeMultiValues) {
|
||||
SettingMultivaluesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingMultivaluesTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
cell.isDisabled = !self.notificationEnabled;
|
||||
cell.userInteractionEnabled = self.notificationEnabled;
|
||||
cell.titleLabel.text = setting.title;
|
||||
|
||||
if (indexPath.row == RowIdentifierRetiSismiche) {
|
||||
cell.valuesLabel.text = [self stringOfSelectedNetworks];
|
||||
}
|
||||
|
||||
return cell;
|
||||
} else if (setting.type == SettingTypeSlider) {
|
||||
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.titleLabel.text = setting.displayTitle;
|
||||
|
||||
if (indexPath.row == RowIdentifierRaggioPosizione) {
|
||||
cell.isDisabled = !self.notificationEnabled;
|
||||
[cell configureSliderWith:self.dataSourceRaggioSisma current:self.currentUserPositionRadius];
|
||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
||||
[self updateUserPositionRadius:item];
|
||||
};
|
||||
} else if (indexPath.row == RowIdentifierEnergiaSisma) {
|
||||
cell.isDisabled = !self.notificationEnabled;
|
||||
[cell configureSliderWith:self.dataSourceMagnitudoDeboli current:self.currentSeismicEnergy];
|
||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
||||
[self updateSeismicEnergy:item];
|
||||
};
|
||||
} else if (indexPath.row == RowIdentifierTerremotiFortiDistanza) {
|
||||
cell.isDisabled = !self.notificationEnabled || !self.notificationStrongEarthquakeEnabled;
|
||||
[cell configureSliderWith:self.dataSourceMagnitudoForti current:self.currentStrongEarthquakeDistance];
|
||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
||||
[self updateStrongEarthquakeEnergy:item];
|
||||
};
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
SettingItem *setting = self.settings[indexPath.row];
|
||||
if (setting.segue != nil) {
|
||||
[self performSegueWithIdentifier:setting.segue sender:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)updateUserPositionRadius:(EQNGenericValue *)radius
|
||||
{
|
||||
[EQNNotificheReteSismiche sharedInstance].distanzaPosizione = radius.value;
|
||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
||||
|
||||
[self loadDataSource];
|
||||
}
|
||||
|
||||
- (void)updateSeismicEnergy:(EQNGenericValue *)energy
|
||||
{
|
||||
[EQNNotificheReteSismiche sharedInstance].energiaSisma = energy.value;
|
||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
||||
|
||||
[self loadDataSource];
|
||||
}
|
||||
|
||||
- (void)updateStrongEarthquakeEnergy:(EQNGenericValue *)energy
|
||||
{
|
||||
[EQNNotificheReteSismiche sharedInstance].energiaTerremotiForti = energy.value;
|
||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
||||
|
||||
[self loadDataSource];
|
||||
}
|
||||
|
||||
- (NSString *)stringOfSelectedNetworks
|
||||
{
|
||||
NSArray *networks = [EQNNotificheReteSismiche sharedInstance].listaEnti;
|
||||
networks = [networks sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
||||
return [networks componentsJoinedByString:@", "];
|
||||
}
|
||||
|
||||
@end
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// SettingsSeismicNetworkNotificationsViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 06/06/24.
|
||||
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
class SettingsSeismicNetworkNotificationsViewController: SettingsBaseTableViewController {
|
||||
|
||||
private enum RowIdentifier: Int {
|
||||
case abilitaNotifiche
|
||||
case magnitudoMinima
|
||||
case distanzaMassima
|
||||
}
|
||||
|
||||
private var isNotificationEnabled = false
|
||||
private var currentMinimumMagnitude = EQNData.DefaultSettingSeismicNetworkNotificationMagitude
|
||||
private var currentMaximumDistance = EQNData.DefaultSettingSeismicNetworkNotificationRadius
|
||||
private let dataSourceMinimumMagnitude = EQNData.settingSeismicNetworkNotificationMagnitudes
|
||||
private let dataSourceMaximumDistance = EQNData.settingSeismicNetworkNotificationRadius
|
||||
|
||||
private let settings: [SettingItem] = [
|
||||
.init(type: .enable, title: NSLocalizedString("options_notification_enable_official", comment: "")),
|
||||
.init(type: .slider, title: NSLocalizedString("options_official_minmag", comment: "")),
|
||||
.init(type: .slider, title: NSLocalizedString("options_official_maxdist", comment: ""))
|
||||
]
|
||||
|
||||
// MARK: - View Liefcycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
loadDataSource()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupUI() {
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
|
||||
tableView.estimatedRowHeight = 200.0
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||
tableView.registerCell(for: SettingEnableTableViewCell.self)
|
||||
tableView.registerCell(for: SettingSliderTableViewCell.self)
|
||||
tableView.registerCell(for: SettingMultivaluesTableViewCell.self)
|
||||
}
|
||||
|
||||
private func loadDataSource() {
|
||||
let saved = EQNSettingSeismicNetworkNotification.shared
|
||||
|
||||
isNotificationEnabled = saved.isAbilitato
|
||||
|
||||
// magnitudo minima
|
||||
let magnitudoMinima = EQNData.getSettingSeismicNetworkNotificationMagnitudes(for: saved.magnitudoMinima)
|
||||
currentMinimumMagnitude = magnitudoMinima
|
||||
|
||||
// raggio dalla tua posizione
|
||||
let distanzaMassima = EQNData.getSettingSeismicNetworkNotificationRadius(for: saved.distanzaMassima)
|
||||
currentMaximumDistance = distanzaMassima
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||
view.titleLabel.text = NSLocalizedString("options_notification_official", comment: "")
|
||||
return view
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
SettingSectionHeaderView.Height
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return settings.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
|
||||
let setting = settings[indexPath.row]
|
||||
switch setting.type {
|
||||
case .enable:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
|
||||
cell.titleLabel.text = setting.displayTitle
|
||||
cell.descriptionLabel.text = setting.subtitle
|
||||
|
||||
switch identifier {
|
||||
case .abilitaNotifiche:
|
||||
cell.toggleSwitch.isOn = isNotificationEnabled
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeNotificationEnabled(enabled)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return cell
|
||||
|
||||
case .slider:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingSliderTableViewCell.self, for: indexPath)
|
||||
cell.titleLabel.text = setting.displayTitle
|
||||
|
||||
let filtersEnabled = isNotificationEnabled
|
||||
switch identifier {
|
||||
case .magnitudoMinima:
|
||||
cell.isDisabled = !filtersEnabled
|
||||
cell.configureSlider(with: dataSourceMinimumMagnitude, current: currentMinimumMagnitude)
|
||||
cell.valueChanged = { [weak self] item in
|
||||
self?.onChangeMinimumMagnitude(item)
|
||||
}
|
||||
case .distanzaMassima:
|
||||
cell.isDisabled = !filtersEnabled
|
||||
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
|
||||
cell.valueChanged = { [weak self] item in
|
||||
self?.onChangeMaximumDistance(item)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return cell
|
||||
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||
isNotificationEnabled = enabled
|
||||
EQNSettingSeismicNetworkNotification.shared.isAbilitato = isNotificationEnabled
|
||||
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func onChangeMinimumMagnitude(_ item: EQNGenericValue) {
|
||||
EQNSettingSeismicNetworkNotification.shared.magnitudoMinima = item.value
|
||||
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||
|
||||
loadDataSource()
|
||||
}
|
||||
|
||||
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
|
||||
EQNSettingSeismicNetworkNotification.shared.distanzaMassima = item.value
|
||||
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||
|
||||
loadDataSource()
|
||||
}
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
//
|
||||
// SettingsSeismicNetworksViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 26/08/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SettingsSeismicNetworksViewController: UITableViewController {
|
||||
|
||||
var networks = [EQNSeismicNetwork]()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
tableView.register(SettingDetailTableViewCell.self, forCellReuseIdentifier: SettingDetailTableViewCell.Identifier)
|
||||
tableView.register(SettingSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: SettingSectionHeaderView.Identifier)
|
||||
|
||||
networks = EQNData.seismicNetworks().sorted(by: { $0.acronym < $1.acronym })
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SettingSectionHeaderView.Identifier) as! SettingSectionHeaderView
|
||||
headerView.titleLabel.text = NSLocalizedString("options_agencies", comment: "");
|
||||
return headerView
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
CGFloat(SettingSectionHeaderView.Height)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
networks.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let network = networks[indexPath.row]
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: SettingDetailTableViewCell.Identifier, for: indexPath) as! SettingDetailTableViewCell
|
||||
cell.textLabel?.text = "\(network.acronym) (\(network.country))"
|
||||
|
||||
if EQNNotificheReteSismiche.shared().listaEnti.contains(network.acronym) {
|
||||
cell.accessoryType = .checkmark
|
||||
} else {
|
||||
cell.accessoryType = .none
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let network = networks[indexPath.row]
|
||||
|
||||
var savedNetworks = EQNNotificheReteSismiche.shared().listaEnti
|
||||
if let index = savedNetworks.firstIndex(where: { $0 == network.acronym }) {
|
||||
savedNetworks.remove(at: index)
|
||||
} else {
|
||||
savedNetworks.append(network.acronym)
|
||||
}
|
||||
|
||||
EQNNotificheReteSismiche.shared().listaEnti = savedNetworks
|
||||
EQNNotificheReteSismiche.shared().saveUserInfo()
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// SettingsUserReportAlertsViewController.h
|
||||
// Earthquake Network
|
||||
//
|
||||
// Refactored by Andrea Busi 25/08/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "SettingsBaseViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SettingsUserReportAlertsViewController : SettingsBaseViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
//
|
||||
// SettingsUserReportAlertsViewController.m
|
||||
// Earthquake Network
|
||||
//
|
||||
// Refactored by Andrea Busi 25/08/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SettingsUserReportAlertsViewController.h"
|
||||
#import "EQNNotificheSegnalazioniUtente.h"
|
||||
|
||||
@interface SettingsUserReportAlertsViewController ()
|
||||
|
||||
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
|
||||
@property (nonatomic, assign) BOOL notificationsEnabled;
|
||||
@property (nonatomic, strong) EQNGenericValue *currentRadius;
|
||||
@end
|
||||
|
||||
@implementation SettingsUserReportAlertsViewController
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
[self setupUI];
|
||||
|
||||
self.settings = @[
|
||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_manual", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius", @"")]
|
||||
];
|
||||
|
||||
[self updateUI];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)setupUI
|
||||
{
|
||||
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
|
||||
|
||||
self.tableView.estimatedRowHeight = 100.0;
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
|
||||
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
|
||||
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
|
||||
}
|
||||
|
||||
- (void)updateUI
|
||||
{
|
||||
self.notificationsEnabled = [EQNNotificheSegnalazioniUtente sharedInstance].isAbilitato;
|
||||
|
||||
EQNGenericValue *distanzaPosizione = [EQNData raggioSismaFor:[EQNNotificheSegnalazioniUtente sharedInstance].distanzaPosizione];
|
||||
self.currentRadius = distanzaPosizione;
|
||||
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
|
||||
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)updateRadius:(EQNGenericValue *)radius
|
||||
{
|
||||
self.currentRadius = radius;
|
||||
[EQNNotificheSegnalazioniUtente sharedInstance].distanzaPosizione = radius.value;
|
||||
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
|
||||
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
|
||||
headerView.titleLabel.text = NSLocalizedString(@"options_notification_manual", @"titolo impostazioni notifiche");
|
||||
return headerView;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return SettingSectionHeaderView.Height;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return self.settings.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
SettingItem *setting = self.settings[indexPath.row];
|
||||
|
||||
if (setting.type == SettingTypeEnable) {
|
||||
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.toggleSwitch.on = self.notificationsEnabled;
|
||||
cell.titleLabel.text = setting.displayTitle;
|
||||
cell.descriptionLabel.text = setting.subtitle;
|
||||
cell.valueChanged = ^(BOOL enabled) {
|
||||
self.notificationsEnabled = enabled;
|
||||
[EQNNotificheSegnalazioniUtente sharedInstance].isAbilitato = self.notificationsEnabled;
|
||||
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
|
||||
[self.tableView reloadData];
|
||||
};
|
||||
return cell;
|
||||
} else if (setting.type == SettingTypeSlider) {
|
||||
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.isDisabled = !self.notificationsEnabled;
|
||||
cell.titleLabel.text = setting.displayTitle;
|
||||
[cell configureSliderWith:[EQNData raggioSismi] current:self.currentRadius];
|
||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
||||
[self updateRadius:item];
|
||||
};
|
||||
return cell;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// SettingsUserReportNotificationsViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 10/06/24.
|
||||
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
class SettingsUserReportNotificationsViewController: SettingsBaseTableViewController {
|
||||
|
||||
private enum RowIdentifier: Int {
|
||||
case abilitaNotifiche
|
||||
case distanzaMassima
|
||||
}
|
||||
|
||||
private var isNotificationEnabled = false
|
||||
private var currentMaximumDistance = EQNData.DefaultSettingUserReportNotificationRadius
|
||||
private let dataSourceMaximumDistance = EQNData.settingUserReportNotificationRadius
|
||||
|
||||
private let settings: [SettingItem] = [
|
||||
.init(type: .enable, title: NSLocalizedString("options_notification_enable_manual", comment: "")),
|
||||
.init(type: .slider, title: NSLocalizedString("options_radius", comment: ""))
|
||||
]
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
loadDataSource()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupUI() {
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
|
||||
tableView.estimatedRowHeight = 200.0
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||
tableView.registerCell(for: SettingEnableTableViewCell.self)
|
||||
tableView.registerCell(for: SettingSliderTableViewCell.self)
|
||||
}
|
||||
|
||||
private func loadDataSource() {
|
||||
let saved = EQNSettingUserReportNotification.shared
|
||||
|
||||
isNotificationEnabled = saved.isAbilitato
|
||||
|
||||
let distanzaMassima = EQNData.getSettingUserReportNotificationRadius(for: saved.distanzaMassima)
|
||||
currentMaximumDistance = distanzaMassima
|
||||
}
|
||||
|
||||
// MARK: - Table view delegate and data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||
view.titleLabel.text = NSLocalizedString("options_notification_manual", comment: "")
|
||||
return view
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
SettingSectionHeaderView.Height
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return settings.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
|
||||
let setting = settings[indexPath.row]
|
||||
switch setting.type {
|
||||
case .enable:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
|
||||
cell.titleLabel.text = setting.displayTitle
|
||||
cell.descriptionLabel.text = setting.subtitle
|
||||
|
||||
switch identifier {
|
||||
case .abilitaNotifiche:
|
||||
cell.toggleSwitch.isOn = isNotificationEnabled
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeNotificationEnabled(enabled)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return cell
|
||||
|
||||
case .slider:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingSliderTableViewCell.self, for: indexPath)
|
||||
cell.titleLabel.text = setting.displayTitle
|
||||
|
||||
switch identifier {
|
||||
case .distanzaMassima:
|
||||
cell.isDisabled = !isNotificationEnabled
|
||||
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
|
||||
cell.valueChanged = { [weak self] item in
|
||||
self?.onChangeMaximumDistance(item)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return cell
|
||||
|
||||
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||
isNotificationEnabled = enabled
|
||||
EQNSettingUserReportNotification.shared.isAbilitato = isNotificationEnabled
|
||||
EQNSettingUserReportNotification.shared.saveUserInfo()
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
|
||||
EQNSettingUserReportNotification.shared.distanzaMassima = item.value
|
||||
EQNSettingUserReportNotification.shared.saveUserInfo()
|
||||
|
||||
loadDataSource()
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
// MARK: - Internal
|
||||
|
||||
/// Annotations displayed on the map
|
||||
private var mapAnnotations = [MKAnnotation]()
|
||||
private(set) var mapAnnotations = [MKAnnotation]()
|
||||
/// If `true`, the initial filter has been already evaluated
|
||||
private var initialFilterEvaluated = false
|
||||
|
||||
@@ -104,6 +104,34 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
return label
|
||||
}()
|
||||
|
||||
// app icon and name displayed on the screenshot
|
||||
private lazy var watermarkView: UIView = {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
let logo = UIImageView(image: .init(named: "eq_icon_transparent"))
|
||||
logo.translatesAutoresizingMaskIntoConstraints = false
|
||||
logo.contentMode = .scaleAspectFit
|
||||
view.addSubview(logo)
|
||||
logo.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||
logo.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
logo.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
||||
logo.widthAnchor.constraint(equalTo: logo.heightAnchor).isActive = true
|
||||
|
||||
let title = UILabel()
|
||||
title.translatesAutoresizingMaskIntoConstraints = false
|
||||
title.text = NSLocalizedString("app_name", comment: "") + " App"
|
||||
title.textColor = AppTheme.Colors.red
|
||||
title.font = .preferredFont(forTextStyle: .title3, weight: .semibold)
|
||||
view.addSubview(title)
|
||||
title.leadingAnchor.constraint(equalTo: logo.trailingAnchor, constant: 10.0).isActive = true
|
||||
title.centerYAnchor.constraint(equalTo: logo.centerYAnchor).isActive = true
|
||||
title.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init() {
|
||||
@@ -155,6 +183,18 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
extraUI()
|
||||
}
|
||||
|
||||
private func addWatermarkView() {
|
||||
view.addSubview(watermarkView)
|
||||
|
||||
watermarkView.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 10.0).isActive = true
|
||||
watermarkView.leadingAnchor.constraint(equalTo: mapView.leadingAnchor, constant: 10.0).isActive = true
|
||||
watermarkView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
|
||||
}
|
||||
|
||||
private func removeWatermarkView() {
|
||||
watermarkView.removeFromSuperview()
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
@@ -238,6 +278,12 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
elaborateMapCenter()
|
||||
}
|
||||
|
||||
func reloadMap() {
|
||||
// remove and re-add annotations, to force redrawn
|
||||
mapView.removeAnnotations(mapAnnotations)
|
||||
mapView.addAnnotations(mapAnnotations)
|
||||
}
|
||||
|
||||
/// Changes the center coordinate of the map to a given location
|
||||
func setMapCenter(for location: CLLocation, span: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 8, longitudeDelta: 8)) {
|
||||
let region = MKCoordinateRegion(center: location.coordinate, span: span)
|
||||
@@ -249,6 +295,31 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
// nope, subclass will implement logic
|
||||
}
|
||||
|
||||
func zPriority(for annotation: MKAnnotation) -> MKAnnotationViewZPriority {
|
||||
// subclass will impelement its own logic to define annotation priority
|
||||
.min
|
||||
}
|
||||
|
||||
func createSnapshot(
|
||||
prepare: () -> Void = { },
|
||||
restore: () -> Void = { }
|
||||
) -> UIImage {
|
||||
prepare()
|
||||
addWatermarkView()
|
||||
|
||||
// riduciamo la porzione da salvare alla sola mappa (eliminiamo i filtri)
|
||||
let size = CGSize(width: view.bounds.width, height: mapView.bounds.maxY)
|
||||
let renderer = UIGraphicsImageRenderer(size: size)
|
||||
let image = renderer.image { ctx in
|
||||
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
|
||||
}
|
||||
|
||||
restore()
|
||||
removeWatermarkView()
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func updateUI() {
|
||||
@@ -286,6 +357,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
}
|
||||
|
||||
let annotationView = setupAnnotationView(for: annotation, on: mapView)
|
||||
annotationView?.zPriority = zPriority(for: annotation)
|
||||
return annotationView
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
|
||||
}
|
||||
|
||||
private func navigateToSubscriptions() {
|
||||
let controller = SubscriptionsViewController.makeViewController()
|
||||
let controller = SubscriptionsViewController()
|
||||
let navigationController = UINavigationController(rootViewController: controller)
|
||||
present(navigationController, animated: true)
|
||||
}
|
||||
|
||||
@@ -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,11 +64,7 @@ static NSString * const EQNServerUrlAlertSimulator = @"https://srv.earthquakenet
|
||||
|
||||
#pragma mark - UserDefaults Keys
|
||||
|
||||
static NSString * const EQNUserDefaultKeyAlertsShowAllCards = @"EQNetwork.AlertsShowAllCards";
|
||||
static NSString * const EQNUserDefaultKeySesmicInformations = @"EQNetwork.SeismicInformations";
|
||||
static NSString * const EQNUserDefaultKeyOneShotShowCountry = @"EQNetwork.OneShot.CountrySelection";
|
||||
static NSString * const EQNUserDefaultSeismicNetworkCards = @"EQNData.RetiSismiche";
|
||||
static NSString * const EQNUserDefaultUserReportExpandedView = @"EQNData.UserReportExpandedView";
|
||||
|
||||
#pragma mark - NSNotification
|
||||
|
||||
@@ -173,14 +169,4 @@ typedef enum : NSInteger {
|
||||
// Sigla della rete sismica selezionata
|
||||
#define IMPOSTAZIONE_NAZIONE_RETI_SISMICHE @"IMPOSTAIONE_NAZIONE_RETI_SISMICHE"
|
||||
|
||||
|
||||
// FILTRO ENTI
|
||||
#define EQN_MAGNITUDO_MINIMA @"EQN_MAGNITUDO_MINIMA"
|
||||
#define EQN_DISTANZA_MASSIMA @"EQN_DISTANZA_MASSIMA"
|
||||
#define EQN_ETA_MASSIMA @"EQN_ETA_MASSIMA"
|
||||
#define EQN_SISMI_FORTI_ABILITATI @"EQN_SISMI_FORTI_ABILITATI"
|
||||
#define EQN_SISMI_FORTI @"EQN_SISMI_FORTI"
|
||||
#define EQN_SISMI_QUALSIASI_MAGNITUDO @"EQN_SISMI_QUALSIASI_MAGNITUDO"
|
||||
#define EQN_SISMI_MODIFICA_IMPOSTAZIONI @"EQN_SISMI_MODIFICA_IMPOSTAZIONI"
|
||||
|
||||
#endif /* Costanti_h */
|
||||
|
||||
@@ -6,16 +6,12 @@
|
||||
#import "Costanti.h"
|
||||
#import "EQNUser.h"
|
||||
#import "EQNManager.h"
|
||||
#import "EQNNotificheReteSismiche.h"
|
||||
#import "EQNNotificheSegnalazioniUtente.h"
|
||||
#import "EQNSisma.h"
|
||||
#import "EQNBaseViewController.h"
|
||||
#import "SettingsBaseViewController.h"
|
||||
#import "EQNGeneratoreURLServer.h"
|
||||
#import "ServerRequest.h"
|
||||
#import "EQNSegnalazione.h"
|
||||
#import "EQNPastquakes.h"
|
||||
#import "EQNAllertaSismica.h"
|
||||
|
||||
#import "GADTTemplateView.h"
|
||||
#import "GADTMediumTemplateView.h"
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>com.finazzi.distquake.update_server_position</string>
|
||||
</array>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
@@ -18,23 +22,42 @@
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>fb1444404982546319</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>FacebookAdvertiserIDCollectionEnabled</key>
|
||||
<true/>
|
||||
<key>FacebookAppID</key>
|
||||
<string>1444404982546319</string>
|
||||
<key>FacebookAutoLogAppEventsEnabled</key>
|
||||
<true/>
|
||||
<key>FacebookClientToken</key>
|
||||
<string>46c7a338b2bbd2186b2f1c12865b4004</string>
|
||||
<key>FacebookDisplayName</key>
|
||||
<string>Earthquake Network</string>
|
||||
<key>GADApplicationIdentifier</key>
|
||||
<string>ca-app-pub-0053870219990922~2021960172</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>googlechromes</string>
|
||||
<string>comgooglemaps</string>
|
||||
<string>fbapi</string>
|
||||
<string>fb-messenger-share-api</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>L'accesso al calendario è richiesto per poter salvare le informazioni dei sismi di interesse</string>
|
||||
<string>L'accesso al calendario è richiesto per poter salvare le informazioni dei sismi di interesse</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>L'accesso ai contatti è richiesto per poter aggiungere persone agli eventi creati</string>
|
||||
<string>L'accesso ai contatti è richiesto per poter aggiungere persone agli eventi creati</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
@@ -42,7 +65,9 @@
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>L'accesso alla libreria è richiesto per poter salvare le immagini generate dall'app</string>
|
||||
<string>L'accesso alla libreria è richiesto per poter salvare le immagini generate dall'app</string>
|
||||
<key>NSUserTrackingUsageDescription</key>
|
||||
<string>Il tracciamento serve a capire se la pubblicità dell'app è efficace</string>
|
||||
<key>SKAdNetworkItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -55,6 +80,7 @@
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
<string>location</string>
|
||||
<string>processing</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
@@ -78,31 +104,7 @@
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>fb1444404982546319</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>FacebookAppID</key>
|
||||
<string>1444404982546319</string>
|
||||
<key>FacebookClientToken</key>
|
||||
<string>46c7a338b2bbd2186b2f1c12865b4004</string>
|
||||
<key>FacebookDisplayName</key>
|
||||
<string>Earthquake Network</string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>fbapi</string>
|
||||
<string>fb-messenger-share-api</string>
|
||||
</array>
|
||||
<key>FacebookAutoLogAppEventsEnabled</key>
|
||||
<true/>
|
||||
<key>FacebookAdvertiserIDCollectionEnabled</key>
|
||||
<true/>
|
||||
<key>NSUserTrackingUsageDescription</key>
|
||||
<string>Il tracciamento serve a capire se la pubblicità dell'app è efficace</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -26,3 +26,24 @@ extension NSDate {
|
||||
return (self as Date).isBeforeInterval(interval)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension CGFloat {
|
||||
var negative: CGFloat {
|
||||
-self
|
||||
}
|
||||
|
||||
var x2: 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ import UIKit
|
||||
extension UIView {
|
||||
|
||||
func eqn_applyShadowAndRoundedCorners() {
|
||||
// rounded corners
|
||||
layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
||||
layer.masksToBounds = false
|
||||
eqn_applyRoundedCorners()
|
||||
|
||||
// apply a shadow to the current view
|
||||
layer.shadowColor = UIColor.black.cgColor
|
||||
@@ -22,4 +20,10 @@ extension UIView {
|
||||
layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
layer.shadowRadius = 2
|
||||
}
|
||||
|
||||
func eqn_applyRoundedCorners() {
|
||||
// rounded corners
|
||||
layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
||||
layer.masksToBounds = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// Log.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 27/02/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
|
||||
/// Use this protocol to have a base TAG in a Swift class
|
||||
public protocol Loggable {
|
||||
static var TAG: String { get }
|
||||
}
|
||||
|
||||
public extension Loggable {
|
||||
static var TAG: String {
|
||||
String(describing: Self.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController: Loggable { }
|
||||
|
||||
|
||||
public class Log {
|
||||
|
||||
private static let dumpDateFormatter: DateFormatter = {
|
||||
// create the default date formatter using ISO8601 date format
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private static let shared = Log()
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let maxNumberOfLogsInDump: Int
|
||||
private let logsLifespanMillis: Int
|
||||
/// Subsystem for OSLog
|
||||
private let subsystem: String
|
||||
/// Logging in everything in a single "APP" category
|
||||
private let appCategory: String = "APP"
|
||||
|
||||
private lazy var logger: os.Logger = {
|
||||
os.Logger(subsystem: subsystem, category: appCategory)
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
@objc
|
||||
public init(
|
||||
subsystem: String = Bundle.main.bundleIdentifier!,
|
||||
maxNumberOfLogsInDump: Int = 5000,
|
||||
logsLifespanMillis: Int = 3 * 24 * 3600 * 1000
|
||||
) {
|
||||
self.subsystem = subsystem
|
||||
self.maxNumberOfLogsInDump = maxNumberOfLogsInDump
|
||||
self.logsLifespanMillis = logsLifespanMillis
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
public static func error(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||
shared.log(level: .fault, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||
}
|
||||
|
||||
public static func warning(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||
shared.log(level: .error, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||
}
|
||||
|
||||
public static func info(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||
shared.log(level: .info, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||
}
|
||||
|
||||
public static func debug(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||
shared.log(level: .debug, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||
}
|
||||
|
||||
public static func verbose(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||
shared.log(level: .debug, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
public func dumpLog() async -> String {
|
||||
return (try? await getLogEntries()) ?? ""
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func log(level: OSLogType, tag: String, message: String, functionName: String) {
|
||||
let formattedMessage = "[\(tag)] \(functionName): \(message)"
|
||||
switch level {
|
||||
case .fault: logger.fault("\(formattedMessage, privacy: .public)")
|
||||
case .error: logger.error("\(formattedMessage, privacy: .public)")
|
||||
case .default: logger.notice("\(formattedMessage, privacy: .public)")
|
||||
case .info: logger.info("\(formattedMessage, privacy: .public)")
|
||||
default: logger.debug("\(formattedMessage, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve log entries from a specified time.
|
||||
/// - Returns: String of log entries, newlines separated
|
||||
@available(iOS 15.0, *)
|
||||
private func getLogEntries() async throws -> String {
|
||||
let logTask = Task.init(priority: .utility) { () -> String in
|
||||
let logs = try retrieveLogEntries()
|
||||
let text = logs
|
||||
.compactMap { "\(Self.dumpDateFormatter.string(from: $0.date)) [\($0.level)] \($0.composedMessage)" }
|
||||
.joined(separator: "\n")
|
||||
return text
|
||||
}
|
||||
return try await logTask.value
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
private func retrieveLogEntries() throws -> [OSLogEntryLog] {
|
||||
// Open the log store.
|
||||
let logStore = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
|
||||
// Fetch log objects from the given time interval
|
||||
let intervalPosition = logStore.position(date: Date().addingTimeInterval(TimeInterval(-logsLifespanMillis / 1000)))
|
||||
let allEntries = try logStore.getEntries(at: intervalPosition)
|
||||
|
||||
// Filter the log to be relevant for our specific subsystem
|
||||
// and remove other elements (signposts, etc).
|
||||
return allEntries
|
||||
.compactMap { $0 as? OSLogEntryLog }
|
||||
.filter { $0.subsystem == subsystem }
|
||||
.suffix(maxNumberOfLogsInDump)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
extension OSLogEntryLog.Level: @retroactive CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .fault: return "FAULT"
|
||||
case .error: return "ERROR"
|
||||
case .notice: return "WARNING"
|
||||
case .info: return "INFO"
|
||||
case .debug: return "DEBUG"
|
||||
case .undefined: return "UNDEFINED"
|
||||
@unknown default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,47 @@ class AppPreferences: NSObject {
|
||||
|
||||
/// Defines if time has to be shown on map annotations in User Reports
|
||||
var userReportExpandedView: Bool {
|
||||
get { UserDefaults.standard.bool(forKey: EQNUserDefaultUserReportExpandedView) }
|
||||
set { UserDefaults.standard.set(newValue, forKey: EQNUserDefaultUserReportExpandedView) }
|
||||
get { UserDefaults.standard.bool(forKey: UserDefaults.UserReportExpandedView) }
|
||||
set { UserDefaults.standard.set(newValue, forKey: UserDefaults.UserReportExpandedView) }
|
||||
}
|
||||
|
||||
/// Defines if options has to be shown on seismic cards
|
||||
@objc
|
||||
var alertsShowAllCards: Bool {
|
||||
get { UserDefaults.standard.bool(forKey: EQNUserDefaultKeyAlertsShowAllCards) }
|
||||
set { UserDefaults.standard.set(newValue, forKey: EQNUserDefaultKeyAlertsShowAllCards) }
|
||||
get { UserDefaults.standard.bool(forKey: UserDefaults.AlertsShowCardOptions) }
|
||||
set { UserDefaults.standard.set(newValue, forKey: UserDefaults.AlertsShowCardOptions) }
|
||||
}
|
||||
|
||||
var mapPinStyle: MapPinStyle {
|
||||
get {
|
||||
let saved = UserDefaults.standard.integer(forKey: UserDefaults.MapPinStyle)
|
||||
return MapPinStyle(rawValue: saved) ?? .circle
|
||||
}
|
||||
set {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// BackgroundTaskIdentifiable.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 11/09/23.
|
||||
// Copyright © 2023 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import BackgroundTasks
|
||||
|
||||
|
||||
protocol BackgroundTaskIdentifiable {
|
||||
typealias TaskCompletion = (_ success: Bool) -> Void
|
||||
|
||||
static var identifier: String { get }
|
||||
static var interval: TimeInterval { get }
|
||||
|
||||
init()
|
||||
|
||||
func handle(_ task: BGTask, completion: @escaping TaskCompletion)
|
||||
func exipration()
|
||||
}
|
||||
|
||||
extension BackgroundTaskIdentifiable {
|
||||
func exipration() { }
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// BackgroundTaskManager.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 16/08/23.
|
||||
// Copyright © 2023 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import BackgroundTasks
|
||||
|
||||
|
||||
@objc
|
||||
class BackgroundTaskManager: NSObject {
|
||||
|
||||
@objc
|
||||
static let shared = BackgroundTaskManager()
|
||||
|
||||
private let identifiers: [BackgroundTaskIdentifiable.Type] = [UpdateUserLocationTask.self]
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc
|
||||
func registerTasks() {
|
||||
identifiers
|
||||
.forEach { taskIdentifiable in
|
||||
BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifiable.identifier, using: DispatchQueue.global()) { [weak self] task in
|
||||
guard let appTask = task as? BGAppRefreshTask else { return }
|
||||
|
||||
self?.handleTask(appTask, with: taskIdentifiable)
|
||||
self?.scheduleTaskRequest(for: taskIdentifiable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc
|
||||
func scheduleUpdateServerPosition() {
|
||||
scheduleTaskRequest(for: UpdateUserLocationTask.self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func scheduleTaskRequest(
|
||||
for taskIdentifiable: BackgroundTaskIdentifiable.Type
|
||||
) {
|
||||
let request = BGAppRefreshTaskRequest(identifier: taskIdentifiable.identifier)
|
||||
// Fetch no earlier than X minutes from now
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: taskIdentifiable.interval)
|
||||
//request.requiresNetworkConnectivity = true
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
print("[BackgroundTaskManager] Background task scheduler submitted")
|
||||
} catch {
|
||||
print("[BackgroundTaskManager] Could not schedule background taksk. Error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func handleTask(
|
||||
_ appTask: BGAppRefreshTask,
|
||||
with taskIdentifiable: BackgroundTaskIdentifiable.Type
|
||||
) {
|
||||
// create a task
|
||||
let task = taskIdentifiable.init()
|
||||
|
||||
// Provide the background task with an expiration handler that cancels the operation.
|
||||
appTask.expirationHandler = {
|
||||
task.exipration()
|
||||
}
|
||||
|
||||
// Handle workload
|
||||
task.handle(appTask) { success in
|
||||
// Inform the system that the background task is complete
|
||||
// when the operation completes.
|
||||
appTask.setTaskCompleted(success: success)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// EQNBackgroundPosition.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 16/08/23.
|
||||
// Copyright © 2023 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
|
||||
|
||||
struct EQNBackgroundPosition: Codable {
|
||||
let date: Date
|
||||
private let latitude: Double
|
||||
private let longitude: Double
|
||||
var coordinate: CLLocationCoordinate2D {
|
||||
.init(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
let request: Bool?
|
||||
|
||||
init(
|
||||
date: Date,
|
||||
coordinate: CLLocationCoordinate2D,
|
||||
request: Bool?
|
||||
) {
|
||||
self.date = date
|
||||
self.latitude = coordinate.latitude
|
||||
self.longitude = coordinate.longitude
|
||||
self.request = request
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// EQNDebugHelper.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 14/07/23.
|
||||
// Copyright © 2023 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@objc
|
||||
class EQNBackgroundPositionDebugHelper: NSObject {
|
||||
|
||||
@objc
|
||||
static let shared = EQNBackgroundPositionDebugHelper()
|
||||
|
||||
private var timers: [String: Timer] = [:]
|
||||
|
||||
@objc
|
||||
var isEnabled: Bool { false }
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc
|
||||
func printPositions(
|
||||
interval: TimeInterval = 2.0,
|
||||
repeats: Bool = true
|
||||
) {
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { timer in
|
||||
let current = EQNUserData.shared.lastLocation?.coordinate
|
||||
print("[EQNDebugHelper] Current | lat: \(current?.latitude ?? 0) - lon: \(current?.longitude ?? 0)")
|
||||
|
||||
let saved = EQNUser.default().lastPosition?.coordinate
|
||||
print("[EQNDebugHelper] Saved | lat: \(saved?.latitude ?? 0) - lon: \(saved?.longitude ?? 0)")
|
||||
}
|
||||
timers["positions"] = timer
|
||||
}
|
||||
|
||||
// MARK: - Class
|
||||
|
||||
func savePosition(
|
||||
coordinate: CLLocationCoordinate2D,
|
||||
requestSuccess: Bool
|
||||
) {
|
||||
var positions = loadPosition()
|
||||
positions.append(.init(date: Date(), coordinate: coordinate, request: requestSuccess))
|
||||
|
||||
if let data = try? JSONEncoder().encode(positions) {
|
||||
UserDefaults.standard.set(data, forKey: "BackgroundPositions")
|
||||
}
|
||||
}
|
||||
|
||||
func resetPositions() {
|
||||
UserDefaults.standard.removeObject(forKey: "BackgroundPositions")
|
||||
}
|
||||
|
||||
func loadPosition() -> [EQNBackgroundPosition] {
|
||||
guard let data = UserDefaults.standard.object(forKey: "BackgroundPositions") as? Data else {
|
||||
return []
|
||||
}
|
||||
|
||||
let positions = try? JSONDecoder().decode([EQNBackgroundPosition].self, from: data)
|
||||
return positions ?? []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// UpdateUserLocationTask.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 16/08/23.
|
||||
// Copyright © 2023 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import BackgroundTasks
|
||||
import CoreLocation
|
||||
|
||||
|
||||
final class UpdateUserLocationTask: NSObject, BackgroundTaskIdentifiable {
|
||||
|
||||
static var identifier: String {
|
||||
"com.finazzi.distquake.update_server_position"
|
||||
}
|
||||
|
||||
static var interval: TimeInterval {
|
||||
5 * 60
|
||||
}
|
||||
|
||||
static let shared = UpdateUserLocationTask()
|
||||
private let debugHelper = EQNBackgroundPositionDebugHelper()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
private lazy var locationManager: CLLocationManager = {
|
||||
let manager = CLLocationManager()
|
||||
manager.delegate = self
|
||||
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
|
||||
manager.allowsBackgroundLocationUpdates = true
|
||||
manager.pausesLocationUpdatesAutomatically = false
|
||||
return manager
|
||||
}()
|
||||
|
||||
var appTaskCompletion: BackgroundTaskIdentifiable.TaskCompletion?
|
||||
|
||||
func handle(_ task: BGTask, completion: @escaping (_ success: Bool) -> Void) {
|
||||
self.appTaskCompletion = completion
|
||||
|
||||
// ricaviamo la posizione corrente dell'utente
|
||||
if let location = locationManager.location {
|
||||
complete(with: location)
|
||||
} else {
|
||||
locationManager.requestLocation()
|
||||
}
|
||||
}
|
||||
|
||||
func exipration() {
|
||||
locationManager.stopUpdatingLocation()
|
||||
failed()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func complete(with location: CLLocation) {
|
||||
// send position to cloud
|
||||
let url = EQNGeneratoreURLServer.urlPosizione(withLocation: location.coordinate)
|
||||
ServerRequest.default().inviaInformazioniAlServer(with: url, richiesta: .posizione) { result in
|
||||
if self.debugHelper.isEnabled {
|
||||
self.debugHelper.savePosition(coordinate: location.coordinate, requestSuccess: true)
|
||||
}
|
||||
self.appTaskCompletion?(true)
|
||||
} failure: { error in
|
||||
if self.debugHelper.isEnabled {
|
||||
self.debugHelper.savePosition(coordinate: location.coordinate, requestSuccess: false)
|
||||
}
|
||||
self.appTaskCompletion?(false)
|
||||
}
|
||||
}
|
||||
|
||||
private func failed() {
|
||||
appTaskCompletion?(false)
|
||||
}
|
||||
}
|
||||
|
||||
extension UpdateUserLocationTask: CLLocationManagerDelegate {
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
if let location = locations.first {
|
||||
complete(with: location)
|
||||
} else {
|
||||
failed()
|
||||
}
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
print("[UpdateUserLocationTask] Location manager failed. Error: \(error.localizedDescription)")
|
||||
|
||||
// nope, but mandatory
|
||||
failed()
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public class EQNAppearanceCommand: EQNCommandProtocol {
|
||||
// MARK: - Public
|
||||
|
||||
func execute() {
|
||||
print("EQNAppearanceCommand: start execute")
|
||||
print("[EQNAppearanceCommand] Start execute")
|
||||
|
||||
applyAppearance()
|
||||
}
|
||||
@@ -24,30 +24,21 @@ public class EQNAppearanceCommand: EQNCommandProtocol {
|
||||
private func applyAppearance() {
|
||||
// UINavigationBar
|
||||
let proxyNavBar = UINavigationBar.appearance(whenContainedInInstancesOf: [UINavigationController.self])
|
||||
if #available(iOS 13.0, *) {
|
||||
let navAppearance = UINavigationBarAppearance()
|
||||
navAppearance.configureWithOpaqueBackground()
|
||||
navAppearance.titleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: AppTheme.Colors.darkGray
|
||||
]
|
||||
navAppearance.largeTitleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: AppTheme.Colors.darkGray
|
||||
]
|
||||
navAppearance.backgroundColor = AppTheme.Colors.primary
|
||||
navAppearance.shadowColor = UIColor.clear
|
||||
let navAppearance = UINavigationBarAppearance()
|
||||
navAppearance.configureWithOpaqueBackground()
|
||||
navAppearance.titleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: AppTheme.Colors.darkGray
|
||||
]
|
||||
navAppearance.largeTitleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: AppTheme.Colors.darkGray
|
||||
]
|
||||
navAppearance.backgroundColor = AppTheme.Colors.navBar
|
||||
navAppearance.shadowColor = UIColor.clear
|
||||
|
||||
proxyNavBar.isTranslucent = false
|
||||
proxyNavBar.tintColor = AppTheme.Colors.darkGray
|
||||
proxyNavBar.standardAppearance = navAppearance
|
||||
proxyNavBar.scrollEdgeAppearance = navAppearance
|
||||
} else {
|
||||
proxyNavBar.tintColor = AppTheme.Colors.darkGray
|
||||
proxyNavBar.isTranslucent = false
|
||||
proxyNavBar.barTintColor = AppTheme.Colors.primary
|
||||
proxyNavBar.titleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: UIColor.white
|
||||
]
|
||||
}
|
||||
proxyNavBar.isTranslucent = false
|
||||
proxyNavBar.tintColor = AppTheme.Colors.darkGray
|
||||
proxyNavBar.standardAppearance = navAppearance
|
||||
proxyNavBar.scrollEdgeAppearance = navAppearance
|
||||
|
||||
let proxyTabBar = UITabBar.appearance()
|
||||
proxyTabBar.tintColor = AppTheme.Colors.red
|
||||
|
||||
@@ -15,67 +15,124 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
|
||||
// MARK: - Public
|
||||
|
||||
func execute() {
|
||||
print("EQNUserDefaultsCommand: start execute")
|
||||
print("[EQNUserDefaultsCommand] Start execute")
|
||||
|
||||
applyDefaultSettings()
|
||||
saveMissingValues()
|
||||
|
||||
migrationV5_3()
|
||||
migrationV5_4()
|
||||
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]
|
||||
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
|
||||
}
|
||||
}
|
||||
|
||||
private func saveMissingValues() {
|
||||
// `raggio sismi forti` was not saved before v2.3
|
||||
if UserDefaults.standard.object(forKey: UserDefaults.AllertaSismicaRaggioSismiForti) == nil {
|
||||
UserDefaults.standard.set("600", forKey: UserDefaults.AllertaSismicaRaggioSismiForti)
|
||||
}
|
||||
}
|
||||
|
||||
private func migrationV5_3() {
|
||||
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_3)
|
||||
private func migrationV5_8() {
|
||||
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_8)
|
||||
if migrationPerformed {
|
||||
print("[EQNUserDefaultsCommand] Migration v5.3 already performed")
|
||||
print("[EQNUserDefaultsCommand] Migration v5.8 already performed")
|
||||
return
|
||||
}
|
||||
|
||||
// l'ultima posizione era salvata come array, la trasformiamo in valore singolo
|
||||
let lastLocations = EQNUtility.loadArray(of: CLLocation.self, fromUserDefaultsForKey: UserDefaults.UserDataLastLocation) as? [CLLocation]
|
||||
if let lastLocation = lastLocations?.last {
|
||||
UserDefaults.standard.removeObject(forKey: UserDefaults.UserDataLastLocation)
|
||||
EQNUserData.shared.saveLastLocation(lastLocation)
|
||||
}
|
||||
|
||||
// resettiamo il Firebase token in modo da ri-eseguire la procedura di registrazione corretta
|
||||
EQNUserData.shared.saveFirebaseToken(nil)
|
||||
|
||||
UserDefaults.standard.set(true, forKey: UserDefaults.AppMigrationV5_3)
|
||||
}
|
||||
|
||||
private func migrationV5_4() {
|
||||
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_4)
|
||||
if migrationPerformed {
|
||||
print("[EQNUserDefaultsCommand] Migration v5.4 already performed")
|
||||
return
|
||||
}
|
||||
|
||||
// migriamo l'ultima posizione negli user defaults condivisi
|
||||
|
||||
// delete old notification settings
|
||||
let userDefaults = UserDefaults.standard
|
||||
let groupUserDefaults = UserDefaults.appGroup
|
||||
if let encodedLocation = userDefaults.object(forKey: UserDefaults.UserDataLastLocation) as? Data {
|
||||
groupUserDefaults?.set(encodedLocation, forKey: UserDefaults.UserDataLastLocation)
|
||||
[
|
||||
"NOTIFICHE_ATTIVA_RETI_SISMICHE_VICINE", "NOTIFICHE_ATTIVA_RETI_TERREMOTI_FORTI",
|
||||
"NOTIFICHE_ATTIVA_RETI_ENERGIA_FORTI", "NOTIFICHE_ATTIVA_RETI_LISTA_ENTI"
|
||||
].forEach { key in
|
||||
userDefaults.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_4)
|
||||
// delete old filter values
|
||||
[
|
||||
"EQN_ETA_MASSIMA", "EQN_SISMI_FORTI_ABILITATI", "EQN_SISMI_FORTI",
|
||||
"EQN_SISMI_QUALSIASI_MAGNITUDO", "EQN_SISMI_MODIFICA_IMPOSTAZIONI"
|
||||
].forEach { key in
|
||||
userDefaults.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
// delete old "real time alert" settings
|
||||
[
|
||||
"NOTIFICHE_ALLERA_SISMICA_IMPOSTA_VOLUME", "NOTIFICHE_ALLERA_SISMICA_TESTA_ALLARME",
|
||||
"NOTIFICHE_ALLERA_SISMICA_ABILITA_INTERVALLO", "NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO",
|
||||
"NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
|
||||
].forEach { key in
|
||||
userDefaults.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_8)
|
||||
}
|
||||
|
||||
private func migrationFirstAppStat() {
|
||||
// before v5.8.2, first app start was defined using Firebase Token
|
||||
let userDefaults = UserDefaults.standard
|
||||
let firstAppStartExecuted = userDefaults.bool(forKey: UserDefaults.FirstAppStartExecuted)
|
||||
if firstAppStartExecuted {
|
||||
print("[EQNUserDefaultsCommand] First app start already executed")
|
||||
return
|
||||
}
|
||||
|
||||
let firebaseToken = userDefaults.object(forKey: UserDefaults.UserDataFirebaseToken) as? String
|
||||
if firebaseToken != nil {
|
||||
print("[EQNUserDefaultsCommand] First app start migrated")
|
||||
userDefaults.set(true, forKey: UserDefaults.FirstAppStartExecuted)
|
||||
}
|
||||
}
|
||||
|
||||
private func migrationCriticalAlerts() {
|
||||
let userDefaults = UserDefaults.standard
|
||||
let migrationPerformed = userDefaults.bool(forKey: UserDefaults.AppMigrationV5_8_2)
|
||||
if migrationPerformed {
|
||||
print("[EQNUserDefaultsCommand] Migration v5.8.2 already performed")
|
||||
return
|
||||
}
|
||||
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
if settings.criticalAlertSetting != .enabled {
|
||||
print("[EQNUserDefaultsCommand] Critical alerts not enabled, disable settings")
|
||||
EQNSettingRealTimeAlert.shared.isCriticalAlertsEnabled = false
|
||||
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||
} else {
|
||||
print("[EQNUserDefaultsCommand] Critical alerts enabled, do nothing")
|
||||
}
|
||||
}
|
||||
|
||||
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_8_2)
|
||||
}
|
||||
|
||||
private func migrationV5_9() {
|
||||
let userDefaults = UserDefaults.standard
|
||||
let migrationPerformed = userDefaults.bool(forKey: UserDefaults.AppMigrationV5_9)
|
||||
if migrationPerformed {
|
||||
print("[EQNUserDefaultsCommand] Migration v5.9 already performed")
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,89 +10,121 @@ import Foundation
|
||||
|
||||
|
||||
@objc class EQNData: NSObject {
|
||||
@objc public static let MaxRaggioSisma = "100000"
|
||||
@objc public static let DefaultRaggioSisma = EQNGenericValue(value:MaxRaggioSisma, display:"radius_any_distance")
|
||||
@objc public static let DefaultMagitudoDebole = EQNGenericValue(value:"2.0", display:"official_magnitude_value_20")
|
||||
@objc public static let DefaultMagitudoForte = EQNGenericValue(value:"5.5", display:"official_magnitude_value_55")
|
||||
@objc public static let DefaultPeriodoTemporale = EQNGenericValue(value: "1440", display: "report_timeframe_one_day")
|
||||
static let MaxRaggioSisma = "100000"
|
||||
static let DefaultSettingSeismicNetworkNotificationRadius = EQNGenericValue(value:"500", display:"500 km")
|
||||
static let DefaultSettingSeismicNetworkNotificationMagitude = EQNGenericValue(value:"2.0", display:"official_magnitude_value_20")
|
||||
static let DefaultSettingUserReportNotificationRadius = EQNGenericValue(value:"1000", display:"1000 km")
|
||||
static let DefaultFilterRadius = EQNGenericValue(value:"250", display:"250 km")
|
||||
static let DefaultFilterMagnitude = EQNGenericValue(value:"0.0", display:"official_magnitude_value_00")
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc class func raggioSismi() -> [EQNGenericValue] {
|
||||
[
|
||||
EQNGenericValue(value:"50", display:"50 km"),
|
||||
EQNGenericValue(value:"100", display:"100 km"),
|
||||
EQNGenericValue(value:"200", display:"200 km"),
|
||||
EQNGenericValue(value:"300", display:"300 km"),
|
||||
EQNGenericValue(value:"400", display:"400 km"),
|
||||
EQNGenericValue(value:"500", display:"500 km"),
|
||||
EQNGenericValue(value:"600", display:"600 km"),
|
||||
EQNGenericValue(value:"800", display:"800 km"),
|
||||
EQNGenericValue(value:"1000", display:"1000 km"),
|
||||
EQNGenericValue(value:"2000", display:"2000 km"),
|
||||
EQNGenericValue(value:"4000", display:"4000 km"),
|
||||
EQNGenericValue(value:Self.MaxRaggioSisma, display:"radius_any_distance"),
|
||||
]
|
||||
}
|
||||
// Distances for "seismic network notifications"
|
||||
static let settingSeismicNetworkNotificationRadius: [EQNGenericValue] = [
|
||||
EQNGenericValue(value:"100", display:"100 km"),
|
||||
EQNGenericValue(value:"250", display:"250 km"),
|
||||
EQNGenericValue(value:"500", display:"500 km"),
|
||||
EQNGenericValue(value:"1000", display:"1000 km")
|
||||
]
|
||||
|
||||
// Magnitudes for "seismic network notifications"
|
||||
static let settingSeismicNetworkNotificationMagnitudes: [EQNGenericValue] = [
|
||||
EQNGenericValue(value:"0.0", display:"0.0"),
|
||||
EQNGenericValue(value:"0.5", display:"0.5"),
|
||||
EQNGenericValue(value:"1.0", display:"1.0"),
|
||||
EQNGenericValue(value:"1.5", display:"1.5"),
|
||||
EQNGenericValue(value:"2.0", display:"2.0"),
|
||||
EQNGenericValue(value:"2.5", display:"2.5"),
|
||||
EQNGenericValue(value:"3.0", display:"3.0"),
|
||||
EQNGenericValue(value:"3.5", display:"3.5"),
|
||||
EQNGenericValue(value:"4.0", display:"4.0"),
|
||||
EQNGenericValue(value:"4.5", display:"4.5"),
|
||||
EQNGenericValue(value:"5.0", display:"5.0"),
|
||||
EQNGenericValue(value:"5.5", display:"5.5")
|
||||
]
|
||||
|
||||
// Distances for "user report notifications"
|
||||
static let settingUserReportNotificationRadius: [EQNGenericValue] = [
|
||||
EQNGenericValue(value:"100", display:"100 km"),
|
||||
EQNGenericValue(value:"250", display:"250 km"),
|
||||
EQNGenericValue(value:"500", display:"500 km"),
|
||||
EQNGenericValue(value:"1000", display:"1000 km")
|
||||
]
|
||||
|
||||
// Misure raggio utilizzate nei filtri
|
||||
static let filterRadius: [EQNGenericValue] = [
|
||||
EQNGenericValue(value:"100", display:"100 km"),
|
||||
EQNGenericValue(value:"250", display:"250 km"),
|
||||
EQNGenericValue(value:"500", display:"500 km"),
|
||||
EQNGenericValue(value:"750", display:"750 km"),
|
||||
EQNGenericValue(value:"1000", display:"1000 km"),
|
||||
EQNGenericValue(value:"1500", display:"1500 km"),
|
||||
EQNGenericValue(value:"2000", display:"2000 km")
|
||||
]
|
||||
|
||||
// Magnitudo utilizzate nei filtri
|
||||
static let filterMagnitude: [EQNGenericValue] = [
|
||||
EQNGenericValue(value:"0.0", display:"0.0"),
|
||||
EQNGenericValue(value:"1.0", display:"1.0"),
|
||||
EQNGenericValue(value:"2.0", display:"2.0"),
|
||||
EQNGenericValue(value:"3.0", display:"3.0"),
|
||||
EQNGenericValue(value:"4.0", display:"4.0"),
|
||||
EQNGenericValue(value:"5.0", display:"5.0"),
|
||||
EQNGenericValue(value:"6.0", display:"6.0")
|
||||
]
|
||||
|
||||
/// Returns the EQNGenericValue for the given value, or the default one
|
||||
/// - Parameter value: Sisma value to search
|
||||
/// - Parameter value: Sisma radius to search
|
||||
/// - Returns: Found value or default
|
||||
@objc class func raggioSisma(for value: String?) -> EQNGenericValue {
|
||||
if let value = value, let genericValue = Self.raggioSismi().first(where: { $0.value == value }) {
|
||||
@objc(getSettingSeismicNetworkAlertRadiusForValue:)
|
||||
class func getSettingSeismicNetworkNotificationRadius(for value: String?) -> EQNGenericValue {
|
||||
if let value = value, let genericValue = settingSeismicNetworkNotificationRadius.first(where: { $0.value == value }) {
|
||||
return genericValue
|
||||
}
|
||||
return Self.DefaultRaggioSisma
|
||||
}
|
||||
|
||||
@objc class func magitudoDeboli() -> [EQNGenericValue] {
|
||||
[
|
||||
EQNGenericValue(value:"0.0", display:"official_magnitude_value_00"),
|
||||
EQNGenericValue(value:"0.5", display:"official_magnitude_value_05"),
|
||||
EQNGenericValue(value:"1.0", display:"official_magnitude_value_10"),
|
||||
EQNGenericValue(value:"1.5", display:"official_magnitude_value_15"),
|
||||
EQNGenericValue(value:"2.0", display:"official_magnitude_value_20"),
|
||||
EQNGenericValue(value:"2.5", display:"official_magnitude_value_25"),
|
||||
EQNGenericValue(value:"3.0", display:"official_magnitude_value_30"),
|
||||
EQNGenericValue(value:"3.5", display:"official_magnitude_value_35"),
|
||||
EQNGenericValue(value:"4.0", display:"official_magnitude_value_40"),
|
||||
EQNGenericValue(value:"4.5", display:"official_magnitude_value_45"),
|
||||
EQNGenericValue(value:"5.0", display:"official_magnitude_value_50"),
|
||||
EQNGenericValue(value:"5.5", display:"official_magnitude_value_55")
|
||||
]
|
||||
return DefaultSettingSeismicNetworkNotificationRadius
|
||||
}
|
||||
|
||||
/// Returns the EQNGenericValue for the given value, or the default one
|
||||
/// - Parameter value: Magnitudo value to search
|
||||
/// - Returns: Found value or default
|
||||
@objc class func magitudoDebole(for value: String?) -> EQNGenericValue {
|
||||
if let value = value, let genericValue = Self.magitudoDeboli().first(where: { $0.value == value }) {
|
||||
class func getSettingSeismicNetworkNotificationMagnitudes(for value: String?) -> EQNGenericValue {
|
||||
if let value = value, let genericValue = settingSeismicNetworkNotificationMagnitudes.first(where: { $0.value == value }) {
|
||||
return genericValue
|
||||
}
|
||||
return Self.DefaultMagitudoDebole
|
||||
}
|
||||
|
||||
@objc class func magitudoForti() -> [EQNGenericValue] {
|
||||
[
|
||||
EQNGenericValue(value:"5.5", display:"official_magnitude_value_55"),
|
||||
EQNGenericValue(value:"6.0", display:"official_magnitude_value_60"),
|
||||
EQNGenericValue(value:"6.5", display:"official_magnitude_value_65"),
|
||||
EQNGenericValue(value:"7.0", display:"official_magnitude_value_70"),
|
||||
EQNGenericValue(value:"7.5", display:"official_magnitude_value_75")
|
||||
]
|
||||
return DefaultSettingSeismicNetworkNotificationMagitude
|
||||
}
|
||||
|
||||
/// Returns the EQNGenericValue for the given value, or the default one
|
||||
/// - Parameter value: Magnitudo value to search
|
||||
/// - Parameter value: Sisma radius to search
|
||||
/// - Returns: Found value or default
|
||||
@objc class func magitudoForte(for value: String?) -> EQNGenericValue {
|
||||
if let value = value, let genericValue = Self.magitudoForti().first(where: { $0.value == value }) {
|
||||
class func getSettingUserReportNotificationRadius(for value: String?) -> EQNGenericValue {
|
||||
if let value = value, let genericValue = settingUserReportNotificationRadius.first(where: { $0.value == value }) {
|
||||
return genericValue
|
||||
}
|
||||
return Self.DefaultMagitudoForte
|
||||
return DefaultSettingUserReportNotificationRadius
|
||||
}
|
||||
|
||||
@objc class func seismicNetworks() -> [EQNSeismicNetwork] {
|
||||
/// Returns the EQNGenericValue for the given value, or the default one
|
||||
/// - Parameter value: Sisma radius to search
|
||||
/// - Returns: Found value or default
|
||||
class func filterRadius(for value: String?) -> EQNGenericValue {
|
||||
if let value = value, let genericValue = filterRadius.first(where: { $0.value == value }) {
|
||||
return genericValue
|
||||
}
|
||||
return DefaultFilterRadius
|
||||
}
|
||||
|
||||
/// Returns the EQNGenericValue for the given value, or the default one
|
||||
/// - Parameter value: Magnitudo value to search
|
||||
/// - Returns: Found value or default
|
||||
class func filterMagnitude(for value: String?) -> EQNGenericValue {
|
||||
if let value = value, let genericValue = filterMagnitude.first(where: { $0.value == value }) {
|
||||
return genericValue
|
||||
}
|
||||
return DefaultFilterMagnitude
|
||||
}
|
||||
|
||||
class func seismicNetworks() -> [EQNSeismicNetwork] {
|
||||
[
|
||||
EQNSeismicNetwork(acronym: "USGS", country: NSLocalizedString("configuration_countries_united_states", comment: ""), extended: ""),
|
||||
EQNSeismicNetwork(acronym: "INGV", country: NSLocalizedString("configuration_countries_italy", comment: ""), extended: ""),
|
||||
@@ -120,37 +152,16 @@ import Foundation
|
||||
]
|
||||
}
|
||||
|
||||
@objc class func seismicNetworkAcronyms() -> [String] {
|
||||
class func seismicNetworkAcronyms() -> [String] {
|
||||
Self.seismicNetworks().map { $0.acronym }
|
||||
}
|
||||
|
||||
@objc class func seismicNetworkCountries() -> [String] {
|
||||
class func seismicNetworkCountries() -> [String] {
|
||||
Self.seismicNetworks().map { $0.country }
|
||||
}
|
||||
|
||||
@objc class func seismic(for acronym: String?) -> EQNSeismicNetwork? {
|
||||
class func seismic(for acronym: String?) -> EQNSeismicNetwork? {
|
||||
guard let acronym = acronym else { return nil }
|
||||
return Self.seismicNetworks().first(where: { $0.acronym == acronym })
|
||||
}
|
||||
|
||||
@objc class func periodiTemporali() -> [EQNGenericValue] {
|
||||
[
|
||||
EQNGenericValue(value: "10", display: "10 minuti"),
|
||||
EQNGenericValue(value: "60", display: "report_timeframe_one_hour"),
|
||||
EQNGenericValue(value: "120", display: "report_timeframe_two_hours"),
|
||||
EQNGenericValue(value: "360", display: "report_timeframe_six_hours"),
|
||||
EQNGenericValue(value: "720", display: "report_timeframe_twelve_hours"),
|
||||
EQNGenericValue(value: "1440", display: "report_timeframe_one_day")
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns the EQNGenericValue for the given value, or the default one
|
||||
/// - Parameter value: Temporal unit value to search
|
||||
/// - Returns: Found value or default
|
||||
@objc class func periodoTemporale(for value: String?) -> EQNGenericValue {
|
||||
if let value = value, let genericValue = Self.periodiTemporali().first(where: { $0.value == value }) {
|
||||
return genericValue
|
||||
}
|
||||
return Self.DefaultPeriodoTemporale
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// EQNDebugHelper.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 14/07/23.
|
||||
// Copyright © 2023 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@objc
|
||||
class EQNDebugHelper: NSObject {
|
||||
|
||||
@objc
|
||||
static let shared = EQNDebugHelper()
|
||||
|
||||
private var timers: [String: Timer] = [:]
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@objc
|
||||
func printPositions(
|
||||
interval: TimeInterval = 2.0,
|
||||
repeats: Bool = true
|
||||
) {
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { timer in
|
||||
let current = EQNUserData.shared.lastLocation?.coordinate
|
||||
print("[EQNDebugHelper] Current | lat: \(current?.latitude ?? 0) - lon: \(current?.longitude ?? 0)")
|
||||
|
||||
let saved = EQNUser.default().lastPosition?.coordinate
|
||||
print("[EQNDebugHelper] Saved | lat: \(saved?.latitude ?? 0) - lon: \(saved?.longitude ?? 0)")
|
||||
}
|
||||
timers["positions"] = timer
|
||||
}
|
||||
}
|
||||
+103
-34
@@ -27,10 +27,110 @@
|
||||
/// THE SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
import StoreKit
|
||||
|
||||
public struct VersioneProProducts {
|
||||
public struct EQNInAppProducts {
|
||||
|
||||
public enum Identifier {
|
||||
enum Plan: CaseIterable {
|
||||
case monthly
|
||||
case yearly
|
||||
case perpetual
|
||||
|
||||
var localizedTitle: String {
|
||||
switch self {
|
||||
case .monthly: NSLocalizedString("subscription_plan_monthly", comment: "")
|
||||
case .yearly: NSLocalizedString("subscription_plan_yearly", comment: "")
|
||||
case .perpetual: NSLocalizedString("subscription_plan_perpetual", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Category {
|
||||
case pro
|
||||
case top10k
|
||||
case top100k
|
||||
|
||||
var image: UIImage? {
|
||||
switch self {
|
||||
case .pro: nil
|
||||
case .top10k: UIImage(named: "top_10k")
|
||||
case .top100k: UIImage(named: "top_100k")
|
||||
}
|
||||
}
|
||||
|
||||
var localizedTitle: String {
|
||||
switch self {
|
||||
case .pro: return NSLocalizedString("network_pro", comment: "")
|
||||
case .top10k: return "Top 10k"
|
||||
case .top100k: return "Top 100k"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let plan: Plan
|
||||
let category: Category
|
||||
let isDiscounted: Bool
|
||||
let product: SKProduct
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
private init(plan: Plan, category: Category, isDiscounted: Bool, product: SKProduct) {
|
||||
self.plan = plan
|
||||
self.category = category
|
||||
self.isDiscounted = isDiscounted
|
||||
self.product = product
|
||||
}
|
||||
|
||||
// MARK: - Accessories
|
||||
|
||||
var isTop10k: Bool {
|
||||
category == .top10k
|
||||
}
|
||||
|
||||
var isTop100k: Bool {
|
||||
category == .top100k
|
||||
}
|
||||
|
||||
var isSubscription: Bool {
|
||||
category != .pro
|
||||
}
|
||||
|
||||
var productIdentifier: String {
|
||||
product.productIdentifier
|
||||
}
|
||||
|
||||
// MARK: - Static
|
||||
|
||||
static func from(product: SKProduct) -> EQNInAppProducts? {
|
||||
switch product.productIdentifier {
|
||||
case Identifier.ProVersionFullPrice:
|
||||
.init(plan: .perpetual, category: .pro, isDiscounted: false, product: product)
|
||||
case Identifier.ProVersionDiscounted:
|
||||
.init(plan: .perpetual, category: .pro, isDiscounted: true, product: product)
|
||||
|
||||
case Identifier.Subscription10kMonthly:
|
||||
.init(plan: .monthly, category: .top10k, isDiscounted: false, product: product)
|
||||
case Identifier.Subscription10kYearly:
|
||||
.init(plan: .yearly, category: .top10k, isDiscounted: false, product: product)
|
||||
case Identifier.Subscription10kYearlyDiscounted:
|
||||
.init(plan: .yearly, category: .top10k, isDiscounted: true, product: product)
|
||||
case Identifier.Subscription10kPerpetual:
|
||||
.init(plan: .perpetual, category: .top10k, isDiscounted: false, product: product)
|
||||
|
||||
case Identifier.Subscription100kMonthly:
|
||||
.init(plan: .monthly, category: .top100k, isDiscounted: false, product: product)
|
||||
case Identifier.Subscription100kYearly:
|
||||
.init(plan: .yearly, category: .top100k, isDiscounted: false, product: product)
|
||||
case Identifier.Subscription100kYearlyDiscounted:
|
||||
.init(plan: .yearly, category: .top100k, isDiscounted: true, product: product)
|
||||
case Identifier.Subscription100kPerpetual:
|
||||
.init(plan: .perpetual, category: .top100k, isDiscounted: false, product: product)
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
enum Identifier {
|
||||
static let ProVersionFullPrice = "com.finazzi.distquake.ProPrezzoPieno"
|
||||
static let ProVersionDiscounted = "com.finazzi.distquake.VersioneProScontata"
|
||||
|
||||
@@ -66,40 +166,9 @@ public struct VersioneProProducts {
|
||||
static let identifiersForTop100k: Set<ProductIdentifier> = [
|
||||
Subscription100kMonthly, Subscription100kYearly, Subscription100kYearlyDiscounted, Subscription100kPerpetual
|
||||
]
|
||||
|
||||
static let identifierForSubscriptions: Set<ProductIdentifier> = [
|
||||
Subscription10kMonthly, Subscription100kMonthly,
|
||||
Subscription10kYearly, Subscription10kYearlyDiscounted,
|
||||
Subscription100kYearly, Subscription100kYearlyDiscounted,
|
||||
Subscription10kPerpetual, Subscription100kPerpetual
|
||||
]
|
||||
}
|
||||
|
||||
static func isSubscription(for identifier: String) -> Bool {
|
||||
Identifier.identifierForSubscriptions.contains(identifier)
|
||||
}
|
||||
|
||||
static func is10kSubscription(for identifier: String) -> Bool {
|
||||
Identifier.identifiersForTop10k.contains(identifier)
|
||||
}
|
||||
|
||||
static func is100kSubscription(for identifier: String) -> Bool {
|
||||
Identifier.identifiersForTop100k.contains(identifier)
|
||||
}
|
||||
|
||||
static func image(for productIdentifier: String) -> UIImage? {
|
||||
if is100kSubscription(for: productIdentifier){
|
||||
return UIImage(named: "top_100k")
|
||||
}
|
||||
|
||||
if is10kSubscription(for: productIdentifier) {
|
||||
return UIImage(named: "top_10k")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
public static let store = IAPHelper(productIds: VersioneProProducts.Identifier.identifiers)
|
||||
public static let store = IAPHelper(productIds: EQNInAppProducts.Identifier.identifiers)
|
||||
}
|
||||
|
||||
func resourceNameForProductIdentifier(_ productIdentifier: String) -> String? {
|
||||
@@ -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];
|
||||
|
||||
@@ -120,19 +131,15 @@
|
||||
|
||||
- (void)scaricaReteSismica
|
||||
{
|
||||
// L'endpoint per lo scaricamento dei dati prende due parametri: pro per il provider selezionato, mag per la
|
||||
// magnitudo minima. Se l'utente ha selezionato più di un provider, inviamo ALL. Mentre la magnitudo
|
||||
// deve avere sempre una cifra decimale (es 2.0).
|
||||
|
||||
NSString *filterProvider = @"";
|
||||
NSArray<NSString *> *networks = [EQNUserData.sharedData seismicNetworksSelected];
|
||||
if (networks.count == 1) {
|
||||
filterProvider = [networks firstObject];
|
||||
} else {
|
||||
filterProvider = @"ALL";
|
||||
}
|
||||
// L'endpoint per lo scaricamento dei dati prende due parametri:
|
||||
// - `pro` per il provider selezionato,
|
||||
// - `mag` per la magnitudo minima.
|
||||
// Dalla v5.8 non esiste più la selezione delle reti, quindi passiamo sempre "ALL".
|
||||
// Per la magnitudo minima, invece, passiamo 0 per i filtri "raggio" e "rilevanti,
|
||||
// altrimenti passiamo 2 per il filtro "mondo"
|
||||
|
||||
NSString *filterMagnitude = [EQNSeismic shared].magnitudoMinima;
|
||||
NSString *filterProvider = @"ALL";
|
||||
NSString *filterMagnitude = [EQNSeismic shared].filterOption == FilterTypeWorldWide ? @"2.0" : @"0.0";
|
||||
|
||||
NSString *queryString = [NSString stringWithFormat:@"?pro=%@&mag=%@", filterProvider, filterMagnitude];
|
||||
NSString *urlString = [NSString stringWithFormat:EQNServerUrlDownloadRetiSismiche, queryString];
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// EQNOfficialPushNotification.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 29/06/24.
|
||||
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Shogun
|
||||
|
||||
|
||||
@objc
|
||||
class EQNOfficialPushNotification: NSObject, Codable {
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case latitude
|
||||
case longitude
|
||||
case magnitude
|
||||
case date
|
||||
}
|
||||
|
||||
private let latitude: Double
|
||||
private let longitude: Double
|
||||
let magnitude: Double
|
||||
let date: Date?
|
||||
|
||||
var coordinate: CLLocation {
|
||||
.init(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
magnitude: Double,
|
||||
date: Date?
|
||||
) {
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.magnitude = magnitude
|
||||
self.date = date
|
||||
}
|
||||
|
||||
// MARK: - Class
|
||||
|
||||
/// Remove any saved notification
|
||||
static func removeStored() {
|
||||
UserDefaults.standard.removeObject(forKey: UserDefaults.OfficialAlertPayload)
|
||||
}
|
||||
|
||||
/// Retrieve stored notification (if any)
|
||||
static func stored() -> EQNOfficialPushNotification? {
|
||||
guard let data = UserDefaults.standard.object(forKey: UserDefaults.OfficialAlertPayload) as? Data else {
|
||||
print("[EQNOfficialPushNotification] No notification saved for key '\(UserDefaults.OfficialAlertPayload)'")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let notification = try? JSONDecoder().decode(EQNOfficialPushNotification.self, from: data) else {
|
||||
print("[EQNOfficialPushNotification] Unable to decode given notification")
|
||||
return nil
|
||||
}
|
||||
|
||||
return notification
|
||||
}
|
||||
|
||||
/// Convert and store a push notification payload.
|
||||
/// Expected payload has the following structure:
|
||||
/// ```
|
||||
/// {
|
||||
/// "title": "Segnalazione da rete sismica",
|
||||
/// "body": "Sisma rilevato a 4km S Valfabbrica (PG)",
|
||||
/// "userInfo": {
|
||||
/// "data" : "2024-06-29 11:21:30",
|
||||
/// ...
|
||||
/// "aps": {
|
||||
/// "alert" : {
|
||||
/// "loc-key" : "Sisma rilevato a",
|
||||
/// "title-loc-key" : "Segnalazione da rete sismica",
|
||||
/// "title" : "Segnalazione da rete sismica",
|
||||
/// "action-loc-key" : "",
|
||||
/// "loc-args" : [
|
||||
/// "6 km S Acate (RG) - M1.9"
|
||||
/// ]
|
||||
/// },
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// - Parameter payload: Notification payload
|
||||
/// - Returns: `true` if save succeed, `false` otherwise
|
||||
@objc(storeNotificationWithPayload:)
|
||||
@discardableResult
|
||||
static func store(payload: [String: Any]) -> Bool {
|
||||
guard let notification = from(payload: payload) else {
|
||||
print("[EQNOfficialPushNotification] Unable to convert received notification")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let data = try? JSONEncoder().encode(notification) else {
|
||||
print("[EQNOfficialPushNotification] Unable to encode given notification")
|
||||
return false
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(data, forKey: UserDefaults.OfficialAlertPayload)
|
||||
return true
|
||||
}
|
||||
|
||||
@objc
|
||||
private static func from(payload: [String: Any]) -> EQNOfficialPushNotification? {
|
||||
guard let userInfo = payload["userInfo"] as? [String: Any] else {
|
||||
print("[EQNOfficialPushNotification] Missing required info to parse push notification")
|
||||
return nil
|
||||
}
|
||||
|
||||
let latitude = userInfo.double(forKey: "latitude") ?? 0
|
||||
let longitude = userInfo.double(forKey: "longitude") ?? 0
|
||||
let magnitude = userInfo.double(forKey: "magnitude") ?? 0
|
||||
let dateString = userInfo.string(forKey: "data") ?? ""
|
||||
let date = EQNUtility.getDateFrom(dateString)
|
||||
|
||||
return .init(
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
magnitude: magnitude,
|
||||
date: date
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -57,21 +57,21 @@ public class EQNPurchaseUtility: NSObject {
|
||||
/// Check if user has bought pro app version
|
||||
/// Pro version is enabled also if a yearly subscription is enabled
|
||||
@objc public static func isProVersionEnabled() -> Bool {
|
||||
VersioneProProducts.Identifier.identifierForProVersion.reduce(false) { (result, identifier) -> Bool in
|
||||
EQNInAppProducts.Identifier.identifierForProVersion.reduce(false) { (result, identifier) -> Bool in
|
||||
return result || UserDefaults.standard.bool(forKey: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if user has bought Top 10k subscription
|
||||
@objc public static func isTop10kEnabled() -> Bool {
|
||||
VersioneProProducts.Identifier.identifiersForTop10k.reduce(false) { (result, identifier) -> Bool in
|
||||
EQNInAppProducts.Identifier.identifiersForTop10k.reduce(false) { (result, identifier) -> Bool in
|
||||
return result || UserDefaults.standard.bool(forKey: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if user has bought Top 100k subscription
|
||||
@objc public static func isTop100kEnabled() -> Bool {
|
||||
VersioneProProducts.Identifier.identifiersForTop100k.reduce(false) { (result, identifier) -> Bool in
|
||||
EQNInAppProducts.Identifier.identifiersForTop100k.reduce(false) { (result, identifier) -> Bool in
|
||||
return result || UserDefaults.standard.bool(forKey: identifier)
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public class EQNPurchaseUtility: NSObject {
|
||||
/// Remove saved in-app purchases flags.
|
||||
/// Used only during development
|
||||
@objc public static func resetInAppPurchases() {
|
||||
VersioneProProducts.Identifier.identifiers.forEach { (identifier) in
|
||||
EQNInAppProducts.Identifier.identifiers.forEach { (identifier) in
|
||||
UserDefaults.standard.removeObject(forKey: identifier)
|
||||
}
|
||||
NotificationCenter.default.post(name: .EQNInAppPurchaseDidComplete, object: nil)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user