Compare commits
274 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 92de4c534c | |||
| 3c237c5b18 | |||
| 16dc2410bc | |||
| 3c83cb97cb | |||
| 4796e3d5a7 | |||
| 56f53550da | |||
| d52b980959 | |||
| b9f87c130d | |||
| b7acbc70df | |||
| 093b6471e8 | |||
| 8ca814561b | |||
| 359667b659 | |||
| 468659ee9f | |||
| ff50abd58a | |||
| 4770578ae3 | |||
| f0fe102901 | |||
| 9dacb33736 | |||
| aed78e44cd | |||
| e531088c86 | |||
| 7e0112bf94 | |||
| 5d12a86cfe | |||
| 09e7786e65 | |||
| 8671533e91 | |||
| 9bd94def0f | |||
| 29c325b7e2 | |||
| 8366f2eabb | |||
| 7e26fee45b | |||
| 76d551b847 | |||
| 8f55553759 | |||
| b44a0a2e27 | |||
| 2b98a4e292 | |||
| 44a27536ad | |||
| 76dcabdc5e | |||
| 2405be895c | |||
| 89ca785864 | |||
| 60678d0839 | |||
| 88b36a501d | |||
| c5b4448830 | |||
| 58b8960e21 | |||
| 49d210eca1 | |||
| 068e457297 | |||
| 9796a40e0e | |||
| 01e8996572 | |||
| 19c6b3d642 | |||
| 0c63a59f19 | |||
| f0c56584b8 | |||
| e9961af792 | |||
| 806b4b67bf | |||
| 851ece0a3b | |||
| 0a76768f88 | |||
| b15efe83e0 | |||
| d84dc8657a | |||
| bac5e909bb | |||
| 15088b744f | |||
| ac03a0cccb | |||
| 76a26e3100 | |||
| cac6ed67ac | |||
| 094c682dbd | |||
| c44f46ca46 | |||
| 5e8c3d0796 | |||
| 61ce27ed4b | |||
| 3aea60e560 | |||
| 84b61fd7e2 | |||
| a5a8c6f5c5 | |||
| 61587a0341 | |||
| a56a04a4ad | |||
| 7173dc7031 | |||
| 35dbdbab28 | |||
| 14614267d4 | |||
| 32833010ed | |||
| ecc77e9f2b | |||
| e69747f133 | |||
| 09685fd4a7 | |||
| 3e122240dc | |||
| 95a214403b | |||
| a7db3c6fa1 | |||
| 4805c79ed6 | |||
| c6ec20e180 | |||
| f6dfd4a761 | |||
| 0212d0a15f | |||
| 3176bde5ed | |||
| dfdbbd0ed4 | |||
| e9986e0fe1 | |||
| beb264f95e | |||
| 217b5edbcf | |||
| e13f95aa5d | |||
| fc5bdbcc92 | |||
| d0ee637449 | |||
| 66e9d7035e | |||
| 7db48e381f | |||
| f9bdb84ad9 | |||
| bee18f6407 | |||
| 96f3a44db4 | |||
| e394259f24 | |||
| 3b53350969 | |||
| f3c3c19e39 | |||
| 8404d72d8f | |||
| a3b0499ed3 | |||
| d923b37fbd | |||
| 282803cf98 | |||
| 039e7b82a1 | |||
| b098caf2ef | |||
| 73caf9647c | |||
| 8700e200f9 | |||
| e99845ff1b | |||
| a0161e8f4c | |||
| 1b9944a7ca | |||
| 4c00e4ef6a | |||
| 63592e6cfb | |||
| 2877dff23c | |||
| f2386a1abb | |||
| 5e4a500f03 | |||
| 2b8f2db7c5 | |||
| 11d994696d | |||
| cd6e20c1b2 | |||
| af5371571c | |||
| 6291b22df0 | |||
| 2a7cfd3079 |
@@ -1,3 +1,6 @@
|
||||
# MacOS files
|
||||
.DS_Store
|
||||
|
||||
# Exclude Pods
|
||||
Sources/Pods
|
||||
|
||||
|
||||
@@ -1,5 +1,75 @@
|
||||
# Changelog
|
||||
|
||||
## Versione 5.9
|
||||
- Aggiunta barra laterale in lista sismi
|
||||
|
||||
## 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
|
||||
|
||||
## Versione 5.5
|
||||
|
||||
- Aggiornata integrazione Firebase
|
||||
- Migrati alcuni endpooint a cache.earthquakenetwork.it
|
||||
- Rimossa funzionalità meteo
|
||||
- Aggiunta rete UOA
|
||||
- Rimosse stringhe non utilizzate
|
||||
- Rimosse sezioni non utilizzate (es PRO)
|
||||
- Aggiunta gestione localizzata plurali
|
||||
- Rimossi filtri intensità da notifiche in tempo reale
|
||||
- Rivista gestione notifiche push allerte
|
||||
- Migrate costanti in nuova struttura Swift
|
||||
|
||||
## Versione 5.4
|
||||
|
||||
- Aggiunto SDK Facebook
|
||||
|
||||
## Versione 5.3.2
|
||||
|
||||
- Corretto problema con notifiche critiche
|
||||
|
||||
## Versione 5.3.1
|
||||
|
||||
- Ricreato progetto NotificationService
|
||||
- Correzioni minori
|
||||
|
||||
## Versione 5.3
|
||||
|
||||
- Rivista gestione registrazione utente
|
||||
- Modifica in segnalazioni utente
|
||||
- Allineata mappa segnalazioni utente ad Android
|
||||
|
||||
## Versione 5.2.1
|
||||
|
||||
- Disattivata schermata di debug
|
||||
|
||||
## Versione 5.2
|
||||
|
||||
- Nuova schermata per allerta in tempo reale
|
||||
|
||||
## Versione 5.1
|
||||
|
||||
### Build (91)
|
||||
- Corretto problema con selezioni reti EMSC
|
||||
|
||||
### Build (90)
|
||||
- Rimozione notifiche dello stesso tipo
|
||||
|
||||
## Versione 5.0.3
|
||||
|
||||
### Build (89)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"Simulator Target Bundle": "com.finazzi.distquake",
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"2 km da Foligno"
|
||||
],
|
||||
"loc-key": "Sisma segnalato da utente a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
},
|
||||
"category": "notifica_con_mappa",
|
||||
"content-available": 1,
|
||||
"mutable-content": 1,
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2022-06-22 19:12:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "45.64664",
|
||||
"detection_longitude": "9.188540",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"magnitude": 20,
|
||||
"latitude": "42.958",
|
||||
"location": "2 km da Foligno",
|
||||
"longitude": "12.702",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "manual",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"Simulator Target Bundle": "com.finazzi.distquake",
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"14 km Al Qunfudhah"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
},
|
||||
"category": "notifica_con_mappa",
|
||||
"content-available": 1,
|
||||
"mutable-content": 1,
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2023-08-10 15:57:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "18.65",
|
||||
"detection_longitude": "42.76",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "18.65",
|
||||
"location": "14 km Al Qunfudhah",
|
||||
"longitude": "42.76",
|
||||
"peak": "1.2",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"150 km (Test)"
|
||||
"15 χλμ από το Αίγιο"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
@@ -14,20 +14,21 @@
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2021-04-15 21:34:50",
|
||||
"datetime": "2022-06-23 10:10:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "37.983810",
|
||||
"detection_longitude": "23.727539",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "37.683810",
|
||||
"location": "150 km (Test)",
|
||||
"longitude": "23.327539",
|
||||
"peak": "-1",
|
||||
"randcode": 0,
|
||||
"test": 1,
|
||||
"latitude": "38.19",
|
||||
"location": "15 χλμ από το Αίγιο",
|
||||
"longitude": "22.26",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"150 km (Test)"
|
||||
"14 km from California City"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
@@ -14,19 +14,20 @@
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2021-04-16 12:41:20",
|
||||
"datetime": "2022-06-23 07:55:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "37.3229978",
|
||||
"detection_longitude": "-122.0321823",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "37.9229978",
|
||||
"location": "150 km (Test)",
|
||||
"longitude": "-121.0321823",
|
||||
"peak": "-1",
|
||||
"randcode": 0,
|
||||
"test": 1,
|
||||
"latitude": "35.15",
|
||||
"location": "14 km from California City",
|
||||
"longitude": "-117.78",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"150 km (Test)"
|
||||
"5 km de Las Cujas"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
@@ -14,19 +14,20 @@
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2021-04-15 20:39:00",
|
||||
"datetime": "2022-06-23 10:19:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "-34.603722",
|
||||
"detection_longitude": "-58.381592",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "-34.103722",
|
||||
"location": "150 km (Test)",
|
||||
"longitude": "-58.781592",
|
||||
"peak": "-1",
|
||||
"randcode": 0,
|
||||
"test": 1,
|
||||
"latitude": "-32.57",
|
||||
"location": "5 km de Las Cujas",
|
||||
"longitude": "-71.46",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"150 km (Test)"
|
||||
"15 km de Dieppe"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
@@ -14,19 +14,20 @@
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2021-04-15 21:37:40",
|
||||
"datetime": "2022-06-23 10:07:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "48.856614",
|
||||
"detection_longitude": "2.8522219",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "48.456614",
|
||||
"location": "150 km (Test)",
|
||||
"longitude": "2.3522219",
|
||||
"peak": "-1",
|
||||
"randcode": 0,
|
||||
"test": 1,
|
||||
"latitude": "49.77",
|
||||
"location": "15 km de Dieppe",
|
||||
"longitude": "1.05",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"150 km (Test)"
|
||||
"5 km od Novog"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
@@ -14,19 +14,20 @@
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2021-04-15 21:39:50",
|
||||
"datetime": "2022-06-23 10:02:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "45.813177",
|
||||
"detection_longitude": "15.977048",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "45.413177",
|
||||
"location": "150 km (Test)",
|
||||
"longitude": "15.677048",
|
||||
"peak": "-1",
|
||||
"randcode": 0,
|
||||
"test": 1,
|
||||
"latitude": "45.09",
|
||||
"location": "5 km od Novog",
|
||||
"longitude": "14.87",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"150 km (Test)"
|
||||
"35 km dari Sindanbarang"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
@@ -14,19 +14,20 @@
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2021-04-15 21:42:40",
|
||||
"datetime": "2022-06-23 10:13:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "-2.548926",
|
||||
"detection_longitude": "118.0148634",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "-2.948926",
|
||||
"location": "150 km (Test)",
|
||||
"longitude": "118.6148634",
|
||||
"peak": "-1",
|
||||
"randcode": 0,
|
||||
"test": 1,
|
||||
"latitude": "-7.38",
|
||||
"location": "35 km dari Sindanbarang",
|
||||
"longitude": "106.83",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"150 km (Test)"
|
||||
"2 km da Foligno"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
@@ -14,19 +14,20 @@
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2021-04-15 20:32:20",
|
||||
"datetime": "2022-06-22 19:12:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "45.64664",
|
||||
"detection_longitude": "9.188540",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "45.164664",
|
||||
"location": "150 km (Test)",
|
||||
"longitude": "9.788540",
|
||||
"peak": "-1",
|
||||
"randcode": 0,
|
||||
"test": 1,
|
||||
"latitude": "42.958",
|
||||
"location": "2 km da Foligno",
|
||||
"longitude": "12.702",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"150 km (Test)"
|
||||
"45 km de Puebla"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
@@ -14,19 +14,20 @@
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2021-04-16 12:39:40",
|
||||
"datetime": "2022-06-23 10:16:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "19.432608",
|
||||
"detection_longitude": "-99.133209",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "19.932608",
|
||||
"location": "150 km (Test)",
|
||||
"longitude": "-98.033209",
|
||||
"peak": "-1",
|
||||
"randcode": 0,
|
||||
"test": 1,
|
||||
"latitude": "18.72",
|
||||
"location": "45 km de Puebla",
|
||||
"longitude": "-97.90",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"aps": {
|
||||
"alert": {
|
||||
"loc-args": [
|
||||
"150 km (Test)"
|
||||
"Bandırma'ya 25 km"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
@@ -14,20 +14,21 @@
|
||||
"sound": "alert_star_trek.wav"
|
||||
},
|
||||
"counter": 10,
|
||||
"datetime": "2021-04-15 21:44:30",
|
||||
"datetime": "2022-06-23 10:22:00",
|
||||
"delay": 4,
|
||||
"detection_latitude": "41.008238",
|
||||
"detection_longitude": "28.978359",
|
||||
"gcm.message_id": "1614708857742608",
|
||||
"google.c.a.e": 1,
|
||||
"google.c.sender.id": "899482329945",
|
||||
"intensity": 2,
|
||||
"latitude": "41.608238",
|
||||
"location": "150 km (Test)",
|
||||
"longitude": "28.678359",
|
||||
"peak": "-1",
|
||||
"randcode": 0,
|
||||
"test": 1,
|
||||
"latitude": "40.33",
|
||||
"location": "Bandırma'ya 25 km",
|
||||
"longitude": "27.66",
|
||||
"peak": "0.4",
|
||||
"randcode": 100,
|
||||
"test": 0,
|
||||
"type": "eqn",
|
||||
"wave_speed": "4.7",
|
||||
"critical": true
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@
|
||||
[super viewDidLoad];
|
||||
|
||||
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
|
||||
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SmallIdentifier];
|
||||
}
|
||||
|
||||
- (void)didReceiveNotification:(UNNotification *)notification
|
||||
@@ -67,9 +68,8 @@
|
||||
[self.mapView addAnnotation:annotation];
|
||||
|
||||
} else if ([userInfo[@"type"] isEqualToString:@"manual"]){
|
||||
EQNMapAnnotationUserReport *annotation = [[EQNMapAnnotationUserReport alloc] initWithTitle:@""
|
||||
coordinate:coordinate.coordinate
|
||||
magnitude:[userInfo[@"magnitudo"] intValue]];
|
||||
EQNMapAnnotationUserReport *annotation = [[EQNMapAnnotationUserReport alloc] initWithMagnitude:[userInfo[@"magnitude"] intValue]
|
||||
coordinate:coordinate.coordinate];
|
||||
[self.mapView addAnnotation:annotation];
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
NSTimeInterval difference = MAX([self.userSeismicTimestamp timeIntervalSinceDate:now], 0);
|
||||
NSInteger seconds = (int)lround(difference);
|
||||
|
||||
self.waveLabel.text = [NSString stringWithFormat:NSLocalizedString(@"alert_wave", @""), seconds];
|
||||
self.waveLabel.text = [NSString localizedStringWithFormat:NSLocalizedString(@"alert_wave", @""), seconds];
|
||||
|
||||
if (difference <= 0) {
|
||||
// stop the countdown
|
||||
@@ -118,9 +118,9 @@
|
||||
} else if ([annotation isKindOfClass:[EQNMapAnnotationUserReport class]]) {
|
||||
EQNMapAnnotationUserReport *report = (EQNMapAnnotationUserReport *)annotation;
|
||||
|
||||
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
|
||||
annotationView.image = report.image;
|
||||
annotationView.title = report.title;
|
||||
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SmallIdentifier];
|
||||
annotationView.image = [report imageWithHeight:EQNCustomAnnotationView.SmallViewImageHeight];
|
||||
annotationView.title = report.timeDifference;
|
||||
return annotationView;
|
||||
}
|
||||
return nil;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "NotificationService.h"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
@@ -2,35 +2,12 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>EQNNotificationService</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.usernotifications.service</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>NotificationService</string>
|
||||
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// NotificationService+Extension.swift
|
||||
// EQNNotificationService
|
||||
//
|
||||
// Created by Andrea Busi on 28/05/22.
|
||||
// Copyright © 2022 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
|
||||
extension NotificationService {
|
||||
|
||||
@objc(removeNotificationsForType:completion:)
|
||||
func removeNotifications(
|
||||
for type: String?,
|
||||
completion: @escaping() -> Void
|
||||
) {
|
||||
guard let type = type else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
let notificationCenter = UNUserNotificationCenter.current()
|
||||
|
||||
notificationCenter.getDeliveredNotifications { notifications in
|
||||
let sameTypeNotifications = notifications.filter { notification in
|
||||
let payload = notification.request.content.userInfo
|
||||
if let notificationType = payload["type"] as? String {
|
||||
return notificationType == type
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
let identifiers = sameTypeNotifications.map { $0.request.identifier }
|
||||
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
|
||||
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// NotificationService.h
|
||||
// EQNNotificationService
|
||||
//
|
||||
// Refactored by Andrea Busi
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
@interface NotificationService : UNNotificationServiceExtension
|
||||
|
||||
@end
|
||||
@@ -1,181 +0,0 @@
|
||||
//
|
||||
// NotificationService.m
|
||||
// EQNNotificationService
|
||||
//
|
||||
// Refactored by Andrea Busi
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NotificationService.h"
|
||||
#import "EQNAllertaSismica.h"
|
||||
#import "Costanti.h"
|
||||
#import "EQNNotificationService-Swift.h"
|
||||
|
||||
|
||||
@interface NotificationService ()
|
||||
|
||||
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
|
||||
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
|
||||
@end
|
||||
|
||||
@implementation NotificationService
|
||||
|
||||
static NSString * const EQNSoundNotificationEQN = @"alert_sound.wav";
|
||||
|
||||
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler
|
||||
{
|
||||
self.contentHandler = contentHandler;
|
||||
self.bestAttemptContent = [request.content mutableCopy];
|
||||
|
||||
// Configure the notification's payload.
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = [NSString localizedUserNotificationStringForKey:request.content.title arguments:nil];
|
||||
content.body = [NSString localizedUserNotificationStringForKey:request.content.body arguments:nil];
|
||||
|
||||
self.bestAttemptContent.title = content.title;
|
||||
self.bestAttemptContent.body = content.body;
|
||||
|
||||
// check for required informations
|
||||
NSDictionary *userInfo = request.content.userInfo;
|
||||
NSString *notificationType = [userInfo objectForKey:@"type"];
|
||||
if (userInfo == nil || notificationType == nil) {
|
||||
[self contentComplete];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
NSString *iconName = @"";
|
||||
if ([notificationType isEqualToString:@"eqn"]) {
|
||||
// check if user has enabled critical alerts
|
||||
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:EQNUserDefaultAppGroupSuite];
|
||||
BOOL criticalAlertsEnabled = [sharedDefaults boolForKey:NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS];
|
||||
|
||||
// !!WORKAROUND
|
||||
// this is a workaround to use critical alerts with legacy FCM api
|
||||
// when the server implementation will be migrated to Firebase v1 APIs, this could be removed
|
||||
BOOL isCritical = [[userInfo objectForKey:@"critical"] boolValue];
|
||||
if (isCritical && criticalAlertsEnabled) {
|
||||
self.bestAttemptContent.sound = [UNNotificationSound criticalSoundNamed:EQNSoundNotificationEQN withAudioVolume:1.0];
|
||||
} else {
|
||||
self.bestAttemptContent.sound = [UNNotificationSound soundNamed:EQNSoundNotificationEQN];
|
||||
}
|
||||
|
||||
NSString *intensity = [userInfo objectForKey:@"intensity"];
|
||||
switch ([intensity intValue]) {
|
||||
case 0:
|
||||
iconName = @"star_realtime_white.png";
|
||||
break;
|
||||
case 1:
|
||||
iconName = @"star_realtime_lightblue.png";
|
||||
break;
|
||||
case 2:
|
||||
iconName = @"star_realtime_blue.png";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if ([notificationType isEqualToString:@"manual"]) {
|
||||
NSString *intensity = [userInfo objectForKey:@"magnitude"];
|
||||
switch ([intensity intValue]) {
|
||||
case 0:
|
||||
iconName = @"star_green.png";
|
||||
break;
|
||||
case 1:
|
||||
iconName = @"star_yellow.png";
|
||||
break;
|
||||
case 2:
|
||||
iconName = @"star_red.png";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if ([notificationType isEqualToString:@"official"]) {
|
||||
NSString *provaider = [userInfo objectForKey:@"provider"];
|
||||
double intensity = [[userInfo objectForKey:@"magnitude"] doubleValue];
|
||||
|
||||
NSString *colore = @"";
|
||||
if (intensity < 2.0) {
|
||||
colore = @"_white";
|
||||
} else if (intensity < 3.5) {
|
||||
colore = @"_green";
|
||||
} else if (intensity < 4.5) {
|
||||
colore = @"_yellow";
|
||||
} else if (intensity < 5.5) {
|
||||
colore = @"_red";
|
||||
} else {
|
||||
colore = @"_purple";
|
||||
}
|
||||
|
||||
if ([provaider isEqualToString:@"USGS"]) {
|
||||
iconName = [NSString stringWithFormat:@"star%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"SGC"]) {
|
||||
iconName = [NSString stringWithFormat:@"star3%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"CSN"]) {
|
||||
iconName = [NSString stringWithFormat:@"star3f%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"SSN"]) {
|
||||
iconName = [NSString stringWithFormat:@"star4%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"INPRES"]) {
|
||||
iconName = [NSString stringWithFormat:@"star4r%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"FUNVISIS"]) {
|
||||
iconName = [NSString stringWithFormat:@"star6%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"Ineter"]) {
|
||||
iconName = [NSString stringWithFormat:@"triangle%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"RSN"]) {
|
||||
iconName = [NSString stringWithFormat:@"triangle2%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"PHIVOLCS"]) {
|
||||
iconName = [NSString stringWithFormat:@"triround_inner%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"IGEPN"]) {
|
||||
iconName = [NSString stringWithFormat:@"triround%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"INGV"]) {
|
||||
iconName = [NSString stringWithFormat:@"circle%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"EMSC"]) {
|
||||
iconName = [NSString stringWithFormat:@"dyamond%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"IGP"]) {
|
||||
iconName = [NSString stringWithFormat:@"dyamond_round%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"JMA"]) {
|
||||
iconName = [NSString stringWithFormat:@"esa%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"GEONET"]) {
|
||||
iconName = [NSString stringWithFormat:@"oct%@.png", colore];
|
||||
} if ([provaider isEqualToString:@"CSI"]) {
|
||||
iconName = [NSString stringWithFormat:@"penta%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"IGN"]) {
|
||||
iconName = [NSString stringWithFormat:@"square%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"UASD"] || [provaider isEqualToString:@"BDTIM"] || [provaider isEqualToString:@"NCS"]) {
|
||||
iconName = [NSString stringWithFormat:@"thick_star%@.png", colore];
|
||||
} else if ([provaider isEqualToString:@"RSPR"]) {
|
||||
iconName = [NSString stringWithFormat:@"star6f%@.png", colore];
|
||||
}
|
||||
}
|
||||
|
||||
if (![iconName isEqualToString:@""]) {
|
||||
iconName = [iconName stringByReplacingOccurrencesOfString:@".png" withString:@""];
|
||||
NSURL *imageUrl = [NSBundle.mainBundle URLForResource:iconName withExtension:@"png"];
|
||||
NSError *attachmentError = nil;
|
||||
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:iconName
|
||||
URL:imageUrl
|
||||
options:nil
|
||||
error:&attachmentError];
|
||||
if (!attachmentError) {
|
||||
self.bestAttemptContent.attachments = @[attachment];
|
||||
}
|
||||
}
|
||||
|
||||
// remove same type posted notification
|
||||
[self removeNotificationsForType:notificationType completion:^{
|
||||
[self contentComplete];
|
||||
}];;
|
||||
}
|
||||
|
||||
- (void)serviceExtensionTimeWillExpire
|
||||
{
|
||||
// Called just before the extension will be terminated by the system.
|
||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||
[self contentComplete];
|
||||
}
|
||||
|
||||
- (void)contentComplete
|
||||
{
|
||||
self.contentHandler(self.bestAttemptContent);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// NotificationService.swift
|
||||
// EQNNotificationService
|
||||
//
|
||||
// Created by Andrea Busi on 24/11/22.
|
||||
// Copyright © 2022 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UserNotifications
|
||||
import CoreLocation
|
||||
import Shogun
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
private static let EQNSoundNotification = UNNotificationSoundName("alert_sound.wav")
|
||||
|
||||
|
||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
self.contentHandler = contentHandler
|
||||
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||
|
||||
guard let bestAttemptContent = bestAttemptContent else {
|
||||
print("[NotificationService] Unable to get notification content")
|
||||
contentComplete()
|
||||
return
|
||||
}
|
||||
|
||||
let userInfo = request.content.userInfo
|
||||
guard let notificationType = userInfo.string(forKey: "type") else {
|
||||
print("[NotificationService] Unable to get notification type")
|
||||
contentComplete()
|
||||
return
|
||||
}
|
||||
|
||||
var iconName = ""
|
||||
switch notificationType.lowercased() {
|
||||
case "eqn":
|
||||
// check if user has enabled critical alerts
|
||||
let criticalAlertsEnabled = UserDefaults.appGroup?.bool(forKey: UserDefaults.AllertaSismicaCriticalAlerts) ?? false
|
||||
|
||||
// !!WORKAROUND
|
||||
// this is a workaround to use critical alerts with legacy FCM api
|
||||
// when the server implementation will be migrated to Firebase v1 APIs, this could be removed
|
||||
let isCritical = userInfo.integer(forKey: "critical", orDefault: 0) == 1
|
||||
if isCritical && criticalAlertsEnabled {
|
||||
bestAttemptContent.sound = UNNotificationSound.criticalSoundNamed(Self.EQNSoundNotification)
|
||||
} else {
|
||||
bestAttemptContent.sound = UNNotificationSound(named: Self.EQNSoundNotification)
|
||||
}
|
||||
|
||||
let intensity = userInfo.integer(forKey: "intensity")
|
||||
switch intensity {
|
||||
case 0:
|
||||
iconName = "star_realtime_white.png"
|
||||
case 1:
|
||||
iconName = "star_realtime_lightblue.png"
|
||||
case 2:
|
||||
iconName = "star_realtime_blue.png"
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
// 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":
|
||||
// don't show any images
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
// add the icon as notification attachment
|
||||
if !iconName.isEmpty {
|
||||
iconName = iconName.replacingOccurrences(of: ".png", with: "")
|
||||
|
||||
if let imageUrl = Bundle(for: NotificationService.self).url(forResource: iconName, withExtension: "png"),
|
||||
let attachment = try? UNNotificationAttachment(identifier: iconName, url: imageUrl) {
|
||||
bestAttemptContent.attachments = [ attachment ]
|
||||
}
|
||||
}
|
||||
|
||||
// remove same type posted notification
|
||||
removeNotifications(for: notificationType) { [weak self] in
|
||||
self?.contentComplete()
|
||||
}
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
// Called just before the extension will be terminated by the system.
|
||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func contentComplete() {
|
||||
if let bestAttemptContent {
|
||||
contentHandler?(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
|
||||
private func removeNotifications(
|
||||
for type: String?,
|
||||
completion: @escaping() -> Void
|
||||
) {
|
||||
guard let type = type else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
let notificationCenter = UNUserNotificationCenter.current()
|
||||
|
||||
notificationCenter.getDeliveredNotifications { notifications in
|
||||
let sameTypeNotifications = notifications.filter { notification in
|
||||
let payload = notification.request.content.userInfo
|
||||
if let notificationType = payload["type"] as? String {
|
||||
return notificationType == type
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
let identifiers = sameTypeNotifications.map { $0.request.identifier }
|
||||
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
|
||||
|
||||
// !! Note: this is a known issue/bug
|
||||
// we need to add a delay before invoking the completion, otherwise the notification will not be remved
|
||||
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func manualIconName(for provider: String, color: String) -> String {
|
||||
switch provider.uppercased() {
|
||||
case "USGS": return "star\(color).png"
|
||||
case "SGC": return "star3\(color).png"
|
||||
case "CSN": return "star3f\(color).png"
|
||||
case "SSN": return "star4\(color).png"
|
||||
case "INPRES": return "star4r\(color).png"
|
||||
case "FUNVISIS": return "star6\(color).png"
|
||||
case "INETER": return "triangle\(color).png"
|
||||
case "RSN": return "triangle2\(color).png"
|
||||
case "PHIVOLCS": return "triround_inner\(color).png"
|
||||
case "IGEPN": return "triround\(color).png"
|
||||
case "INGV": return "circle\(color).png"
|
||||
case "EMSC": return "dyamond\(color).png"
|
||||
case "IGP": return "dyamond_round\(color).png"
|
||||
case "JMA": return "esa\(color).png"
|
||||
case "GEONET": return "oct\(color).png"
|
||||
case "CSI": return "penta\(color).png"
|
||||
case "IGN": return "square\(color).png"
|
||||
case "UASD", "BDTIM", "NCS": return "thick_star\(color).png"
|
||||
case "RSPR": return "star6f\(color).png"
|
||||
case "UOA": return "triangle\(color).png"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
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" : "194a6706acbd25e4ef639bcaddea16e8758a3e27",
|
||||
"version" : "1.2024011602.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" : "075679d6b25b28f4cb167f1d7769b58fb556fb30",
|
||||
"version" : "11.8.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googleappmeasurement",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "be0881ff728eca210ccb628092af400c086abda3",
|
||||
"version" : "11.7.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" : "f56d8fc3162de9a498377c7b6cea43431f4f5083",
|
||||
"version" : "1.65.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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" : "2d12673670417654f08f5f90fdd62926dc3a2648",
|
||||
"version" : "100.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
|
||||
}
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "1620"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
@@ -74,6 +74,7 @@
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
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">
|
||||
@@ -74,6 +74,7 @@
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
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>
|
||||
@@ -8,21 +8,19 @@
|
||||
|
||||
#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"
|
||||
|
||||
@import Firebase;
|
||||
@import FirebaseCrashlytics;
|
||||
@import UserNotifications;
|
||||
@import AppTrackingTransparency;
|
||||
@import FirebaseCore;
|
||||
@import FirebaseMessaging;
|
||||
@import FirebaseCrashlytics;
|
||||
@import GoogleMobileAds;
|
||||
@import FBSDKCoreKit;
|
||||
|
||||
@interface AppDelegate () <FIRMessagingDelegate, UNUserNotificationCenterDelegate>
|
||||
@property (strong, nonatomic) NSArray<id<EQNCommandProtocol>> *commands;
|
||||
@@ -43,29 +41,25 @@
|
||||
}];
|
||||
#endif
|
||||
|
||||
[EQNUser defaultUser];
|
||||
[EQNManager defaultManager];
|
||||
[FIRApp configure];
|
||||
[FIRMessaging messaging].delegate = self;
|
||||
|
||||
// add some generic logs for Crashlytics
|
||||
NSString *language = [[NSLocale preferredLanguages] firstObject];
|
||||
[[FIRCrashlytics crashlytics] setCustomValue:language forKey:@"lang"];
|
||||
|
||||
// execute commands
|
||||
EQNStartupCommandsBuilder *builder = [[EQNStartupCommandsBuilder alloc] init];
|
||||
self.commands = [builder build];
|
||||
[builder executeWithCommands:self.commands];
|
||||
|
||||
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
|
||||
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
|
||||
[[UNUserNotificationCenter currentNotificationCenter]
|
||||
requestAuthorizationWithOptions:authOptions
|
||||
completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
||||
[self registraNotifica];
|
||||
}];
|
||||
[EQNUser defaultUser];
|
||||
[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];
|
||||
[[FIRCrashlytics crashlytics] setCustomValue:language forKey:@"lang"];
|
||||
|
||||
[self configurePushNotifications];
|
||||
|
||||
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
|
||||
[application registerForRemoteNotifications];
|
||||
|
||||
return YES;
|
||||
@@ -86,12 +80,14 @@
|
||||
- (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];
|
||||
|
||||
NSUInteger counter = [[NSUserDefaults standardUserDefaults] integerForKey:EQNUserDefaultProDiscountOpenCounter];
|
||||
// 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;
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:counter forKey:EQNUserDefaultProDiscountOpenCounter];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:counter forKey:NSUserDefaults.UserDataProDiscountOpenCounter];
|
||||
}
|
||||
|
||||
|
||||
@@ -125,8 +121,8 @@
|
||||
{
|
||||
NSString *token = [self stringWithDeviceToken:deviceToken];
|
||||
NSLog(@"[DEBUG] push token: %@", token);
|
||||
[[NSUserDefaults standardUserDefaults] setObject:token forKey:EQNUserDefaultPushToken];
|
||||
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setObject:token forKey:NSUserDefaults.UserDataPushToken];
|
||||
FIRMessaging.messaging.APNSToken = deviceToken;
|
||||
}
|
||||
|
||||
@@ -149,47 +145,47 @@
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
|
||||
{
|
||||
// execute common logic and refresh the proper tab
|
||||
NSDictionary *userInfo = notification.request.content.userInfo;
|
||||
[self handlePushNotificationWithUserInfo:userInfo];
|
||||
UNNotificationContent *content = notification.request.content;
|
||||
[self handlePushNotificationWithNotificationContent:content];
|
||||
|
||||
// Change this to your preferred presentation option
|
||||
completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound);
|
||||
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionSound);
|
||||
}
|
||||
|
||||
// Handle notification messages after display notification is tapped by the user.
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler
|
||||
{
|
||||
NSLog(@"[DEBUG] push payload\n%@", [response.notification.request.content.userInfo eqn_jsonStringWithPrettyPrint:YES]);
|
||||
|
||||
// execute common logic and refresh the proper tab
|
||||
NSDictionary *userInfo = response.notification.request.content.userInfo;
|
||||
[self handlePushNotificationWithUserInfo:userInfo];
|
||||
UNNotificationContent *content = response.notification.request.content;
|
||||
[self handlePushNotificationWithNotificationContent:content];
|
||||
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
- (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)handlePushNotificationWithUserInfo:(NSDictionary *)userInfo
|
||||
- (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 ([userInfo[@"type"] isEqualToString:@"eqn"]) {
|
||||
NSDate *dataRicezione = [NSDate date];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:dataRicezione forKey:NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA];
|
||||
|
||||
[EQNUtility storeDictionary:userInfo toUserDefaultForKey:NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA];
|
||||
if ([type isEqualToString:@"eqn"]) {
|
||||
[EQNRealtimePushNotification storeNotificationWithPayload:notification];
|
||||
section = EQNTabBarSectionAllerte;
|
||||
} else if([userInfo[@"type"] isEqualToString:@"manual"]) {
|
||||
} else if([type isEqualToString:@"manual"]) {
|
||||
section = EQNTabBarSectionSegnalazioni;
|
||||
} else if([userInfo[@"type"] isEqualToString:@"official"]) {
|
||||
} else if([type isEqualToString:@"official"]) {
|
||||
[EQNOfficialPushNotification storeNotificationWithPayload:notification];
|
||||
section = EQNTabBarSectionRetiSismiche;
|
||||
}
|
||||
|
||||
@@ -197,19 +193,61 @@
|
||||
[self.mainTabBarController selectSection:section];
|
||||
}
|
||||
|
||||
#pragma mark - Configurations
|
||||
|
||||
- (void)configurePushNotifications
|
||||
{
|
||||
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
|
||||
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
|
||||
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
||||
[self registraNotifica];
|
||||
[self configureAppTracking];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)configureAppTracking
|
||||
{
|
||||
if (@available(iOS 14, *)) {
|
||||
// add a delay otherwise the alert will not be displayed
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
|
||||
if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
|
||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = YES;
|
||||
} else {
|
||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = NO;
|
||||
}
|
||||
}];
|
||||
});
|
||||
} else {
|
||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configureFirebase
|
||||
{
|
||||
[FIRApp configure];
|
||||
[FIRMessaging messaging].delegate = self;
|
||||
}
|
||||
|
||||
- (void)configureFacebookSDKWithApplication:(UIApplication *)application andOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[FBSDKApplicationDelegate.sharedInstance application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
FBSDKSettings.sharedSettings.isAdvertiserIDCollectionEnabled = YES;
|
||||
}
|
||||
|
||||
#pragma mark - FIRMessagingDelegate
|
||||
|
||||
- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken
|
||||
{
|
||||
NSLog(@"[Firebase] fcmToken %@", fcmToken);
|
||||
if (![[NSUserDefaults standardUserDefaults] objectForKey:EQNUserDefaultUserFirebaseToken]) {
|
||||
if (EQNUserData.sharedData.isFirstStart) {
|
||||
// save default values for notification settings
|
||||
[EQNAllertaSismica saveDefaultValues];
|
||||
[EQNNotificheSegnalazioniUtente saveDefaultValues];
|
||||
[EQNNotificheReteSismiche saveDefaultValues];
|
||||
[EQNSettingRealTimeAlert saveDefaultValues];
|
||||
[EQNSettingUserReportNotification saveDefaultValues];
|
||||
[EQNSettingSeismicNetworkNotification saveDefaultValues];
|
||||
}
|
||||
|
||||
[EQNUser defaultUser].tokenUser = fcmToken;
|
||||
[EQNUser.defaultUser registerUserIfNeededWithFirebaseToken:fcmToken];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// Constants.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 14/07/23.
|
||||
// Copyright © 2023 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@objc
|
||||
extension UserDefaults {
|
||||
// UserDefaults condivisi con l'AppGroup
|
||||
@objc(appGroupUserDefaults)
|
||||
static let appGroup = UserDefaults(suiteName: "group.com.finazzi.distquake")
|
||||
|
||||
// Impostazioni della sezione `Allerta in tempo reale`
|
||||
static let AllertaSismicaAbilitato = "NOTIFICHE_ALLERA_SISMICA_ABILITATO"
|
||||
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"
|
||||
|
||||
// Impostazioni della sezione `Notifiche da reti sismiche`
|
||||
static let NotificheRetiSismicheAbilitato = "NOTIFICHE_ATTIVA_RETI_SISMICHE"
|
||||
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"
|
||||
static let NotificheSegnalazioniUtenteDistanzaPosizione = "NOTIFICHE_SU_DISTANZA_POSIZIONE"
|
||||
|
||||
// Messaggio in segnalazione utente
|
||||
static let UserReportMessage = "DATA_MESSAGE_EQN"
|
||||
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
|
||||
static let UserDataFirebaseToken = "EQNToken_User"
|
||||
/// Server user ID dell'utente corrente
|
||||
static let UserDataUserId = "EQNUSER_ID"
|
||||
/// Reti sismiche selezionate
|
||||
static let UserDataSelectedSeismicNetworks = "IMPOSTAZIONE_ENTI_RETI_SISMICHEI"
|
||||
/// Token delle notifiche push
|
||||
static let UserDataPushToken = "EQNetwork.PushToken"
|
||||
/// Numero di aperture dell'app per sbloccare la versione Pro scontata
|
||||
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"
|
||||
|
||||
// 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 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,9 +26,6 @@
|
||||
|
||||
@implementation AllerteViewController
|
||||
|
||||
static NSString * const SegueIdentifierProVersion = @"ShowProVersion";
|
||||
static NSString * const SegueIdentifierPrioritySubscriptions = @"ShowPrioritySubscriptions";
|
||||
|
||||
/// Sections inside the app
|
||||
typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
AllerteTableRowLocationPermission = 0,
|
||||
@@ -36,7 +33,6 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
AllerteTableRowAllertePassate,
|
||||
AllerteTableRowReteSmartphone,
|
||||
AllerteTableRowServizioPriorita,
|
||||
AllerteTableRowVersionePro, // non più visualizzata con passaggio dell'app a pagamento
|
||||
AllerteTableRowDatiPosizione
|
||||
};
|
||||
|
||||
@@ -106,6 +102,17 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
self.title = [NSLocalizedString(@"tab_network", nil) capitalizedString];
|
||||
self.tableView.estimatedRowHeight = 200.0;
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
[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
|
||||
@@ -113,18 +120,22 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
[super refreshUI];
|
||||
|
||||
// `AllerteTableRowReteSmartphone` and `AllerteTableRowDatiPosizione` are hidden bu default, user can show them
|
||||
BOOL showAllCards = [[NSUserDefaults standardUserDefaults] boolForKey:EQNUserDefaultKeyAlertsShowAllCards];
|
||||
BOOL showAllCards = AppPreferences.shared.alertsShowAllCards;
|
||||
self.expandeCollapseButton.image = showAllCards ? [UIImage imageNamed:@"navbar-icon-arrow-collapse"] : [UIImage imageNamed:@"navbar-icon-arrow-expand"];
|
||||
|
||||
NSDate *date = [[NSUserDefaults standardUserDefaults] objectForKey:NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA];
|
||||
if (date) {
|
||||
if ([EQNUtility getDifferenceMinute:date] < TEMPO_VISUALIZZAZIONE_NOTIFICA)
|
||||
self.isNotificaAttiva = YES;
|
||||
else{
|
||||
self.isNotificaAttiva = NO;
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
// controlliamo se c'è una notifica in tempo reale da mostrare
|
||||
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
||||
if (notification) {
|
||||
self.isNotificaAttiva = YES;
|
||||
|
||||
// mostriamo la schermata solo se il countdown non è a zero
|
||||
if (![notification isCountdownExpired]) {
|
||||
RealtimeAlertViewController *controller = [[RealtimeAlertViewController alloc] initWithNotification:notification];
|
||||
controller.modalInPresentation = YES;
|
||||
[self presentViewController:controller animated:YES completion:nil];
|
||||
}
|
||||
} else {
|
||||
[self resetRealtimeAlert];
|
||||
}
|
||||
|
||||
[self.tableItems removeAllObjects];
|
||||
@@ -152,6 +163,12 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)resetRealtimeAlert
|
||||
{
|
||||
[EQNRealtimePushNotification removeStoredNotification];
|
||||
self.isNotificaAttiva = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)refreshDataTapped:(id)sender
|
||||
@@ -162,20 +179,21 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
- (IBAction)collapseExpandTapped:(id)sender
|
||||
{
|
||||
// toggle saved value
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
BOOL showAll = [defaults boolForKey:EQNUserDefaultKeyAlertsShowAllCards];
|
||||
[defaults setBool:!showAll forKey:EQNUserDefaultKeyAlertsShowAllCards];
|
||||
AppPreferences.shared.alertsShowAllCards = !AppPreferences.shared.alertsShowAllCards;
|
||||
|
||||
[self refreshUI];
|
||||
}
|
||||
|
||||
- (IBAction)backgroundPositionDebugTapped:(id)sender
|
||||
{
|
||||
EQNBackgroundPositionDebugViewController *controller = [[EQNBackgroundPositionDebugViewController alloc] init];
|
||||
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
|
||||
[self presentViewController:navController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)actionCloseNotification
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
self.isNotificaAttiva = NO;
|
||||
|
||||
[self resetRealtimeAlert];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
@@ -252,14 +270,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:CLLocationManager.authorizationStatus];
|
||||
return cell;
|
||||
|
||||
} else if (tableRow == AllerteTableRowSismiRilevati) {
|
||||
if (self.isNotificaAttiva) {
|
||||
AlertsSeismicNotificationExpandedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationExpandedCell" forIndexPath:indexPath];
|
||||
NSDictionary *info = [EQNUtility loadDictionaryFromUserDefaultsForKey:NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA];
|
||||
cell.notification = info;
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
||||
[cell updateWith:notification];
|
||||
|
||||
__weak AllerteViewController *weakSelf = self;
|
||||
cell.onTapClose = ^{
|
||||
@@ -278,7 +298,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 = ^{
|
||||
@@ -298,8 +319,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];
|
||||
};
|
||||
@@ -307,7 +329,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];
|
||||
};
|
||||
@@ -315,16 +338,13 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
|
||||
} else if (tableRow == AllerteTableRowServizioPriorita) {
|
||||
AlertsPriorityServiceTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PriorityCell" forIndexPath:indexPath];
|
||||
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
|
||||
return cell;
|
||||
|
||||
} else if (tableRow == AllerteTableRowVersionePro) {
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ProVersionCell" forIndexPath:indexPath];
|
||||
[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,12 +357,10 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
|
||||
AllerteTableRow tableRow = [self.tableItems[indexPath.row] integerValue];
|
||||
switch (tableRow) {
|
||||
case AllerteTableRowServizioPriorita:
|
||||
[self performSegueWithIdentifier:SegueIdentifierPrioritySubscriptions sender:nil];
|
||||
break;
|
||||
case AllerteTableRowVersionePro:
|
||||
[self performSegueWithIdentifier:SegueIdentifierProVersion 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
-24
@@ -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,25 +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) {
|
||||
if let sunrise = solar.sunrise {
|
||||
sunriseTimeLabel.text = dateFormatter.string(from: sunrise) + " (\(TimeZone.current.identifier))"
|
||||
}
|
||||
if let sunset = solar.sunset {
|
||||
sunsetTimeLabel.text = dateFormatter.string(from: sunset) + " (\(TimeZone.current.identifier))"
|
||||
}
|
||||
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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+66
-24
@@ -8,39 +8,81 @@
|
||||
|
||||
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 }
|
||||
|
||||
let formattedTime = EQNUtility.formattedString(forTimeDifference: smartphoneNetwork.lastSubscriptionDiff)
|
||||
lastSubscriptionLabel.text = String(format: NSLocalizedString("inapp_adv_time", comment: ""), formattedTime)
|
||||
lastSubscriptionLabel.text = subscriptionText(for: smartphoneNetwork.lastSubscriptionDiff)
|
||||
}
|
||||
|
||||
private func subscriptionText(for time: Int) -> String {
|
||||
var format = ""
|
||||
var finalValue = time
|
||||
|
||||
// check for minutes, hours or days
|
||||
if time < 60 {
|
||||
format = NSLocalizedString("inapp_adv_minutes", comment: "")
|
||||
} else if time < 1440 {
|
||||
finalValue = time / 60
|
||||
format = NSLocalizedString("inapp_adv_hours", comment: "")
|
||||
} else {
|
||||
finalValue = time / 1440
|
||||
format = NSLocalizedString("inapp_adv_days", comment: "")
|
||||
}
|
||||
|
||||
return String.localizedStringWithFormat(format, finalValue)
|
||||
}
|
||||
}
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// AlertsProVersionTableViewCell.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 05/04/21.
|
||||
// Copyright © 2021 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class AlertsProVersionTableViewCell: EQNBaseTableViewCell {
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
localizeUI()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
headerLabel.text = NSLocalizedString("network_pro", comment: "")
|
||||
descriptionLabel.text = NSLocalizedString("network_topro", comment: "")
|
||||
}
|
||||
}
|
||||
+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?()
|
||||
}
|
||||
}
|
||||
|
||||
+176
-109
@@ -8,123 +8,198 @@
|
||||
|
||||
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: [String: Any]? {
|
||||
didSet {
|
||||
startCountdown()
|
||||
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 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()
|
||||
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 startCountdown() {
|
||||
guard let notification = notification else { return }
|
||||
|
||||
// calculate the impact timestamp and start a timer for the countdown label
|
||||
if let impactTimestamp = EQNUtility.calculateUserSeismicTimestamp(fromUserInfo: notification) {
|
||||
self.impactTimestamp = impactTimestamp
|
||||
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdownTimerFired(_:)), userInfo: nil, repeats: true)
|
||||
countdownTimer?.fire()
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
let aps = notification["aps"] as? [String: Any],
|
||||
let alert = aps["alert"] as? [String: Any] else { return }
|
||||
guard let notification = notification else { return }
|
||||
|
||||
backgroundColor = backgroundColor(for: notification.relativeIntensity())
|
||||
notificationTitleLabel.text = notification.title
|
||||
notificationIntensityLabel.text = notification.displayBody
|
||||
notificationIntensityLabel.textColor = notification.relativeIntensityColor
|
||||
|
||||
let intensity = notification.eqn_intValue(for: "intensity") ?? 0
|
||||
containerView.backgroundColor = color(for: intensity)
|
||||
|
||||
if let title = alert["loc-key"] as? String, let args = alert["loc-args"] as? [String], let arg = args.first {
|
||||
notificationTitleLabel.text = String(format: NSLocalizedString(title, comment: ""), arg)
|
||||
}
|
||||
|
||||
// get coordinate
|
||||
var coordinate: CLLocation?
|
||||
if let latitude = notification.eqn_doubleValue(for: "latitude"),
|
||||
let longitude = notification.eqn_doubleValue(for: "longitude") {
|
||||
if let date = notification.dateTime {
|
||||
|
||||
coordinate = CLLocation(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
|
||||
if let coordinate = coordinate,
|
||||
let counter = notification["counter"],
|
||||
let dateString = notification["datetime"] as? String,
|
||||
let date = EQNUtility.getDateFrom(dateString) {
|
||||
|
||||
let distance = EQNUser.default().lastPosition?.distance(from: coordinate) ?? 0.0
|
||||
let distance = EQNUser.default().lastPosition?.distance(from: notification.coordinate) ?? 0.0
|
||||
let distanceRound = Int(round(distance / 1_000))
|
||||
let difference = Int(NSDate().timeIntervalSince(date) / 60.0)
|
||||
|
||||
notificationDescriptionLabel.text = ""
|
||||
+ NSLocalizedString("official_card_distance", comment: "") + " \(distanceRound) km"
|
||||
+ " - " + EQNUtility.formattedString(forTimeDifference: difference)
|
||||
+ "\n" + String(format: NSLocalizedString("map_number", comment: ""), "\(counter)")
|
||||
}
|
||||
|
||||
if let coordinate = coordinate {
|
||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||
let region = MKCoordinateRegion(center: coordinate.coordinate, span: span)
|
||||
|
||||
mapView.setCenter(coordinate.coordinate, animated: false)
|
||||
mapView.setRegion(region, animated: true)
|
||||
|
||||
let annotation = EQNMapAnnotationPastquake(title: "", coordinate: coordinate.coordinate, intensity: intensity)
|
||||
mapView.addAnnotation(annotation)
|
||||
}
|
||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||
let region = MKCoordinateRegion(center: notification.coordinate.coordinate, span: span)
|
||||
|
||||
mapView.setCenter(notification.coordinate.coordinate, animated: false)
|
||||
mapView.setRegion(region, animated: true)
|
||||
|
||||
let annotation = EQNMapAnnotationPastquake(title: "", coordinate: notification.coordinate.coordinate, intensity: notification.intensity)
|
||||
mapView.addAnnotation(annotation)
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
@@ -138,44 +213,36 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
||||
return annotationView
|
||||
}
|
||||
|
||||
private func color(for intensity: Int) -> UIColor {
|
||||
switch intensity {
|
||||
case 0: return UIColor(red: 208.0/255.0, green: 234.0/255.0, blue: 201.0/255.0, alpha:1.0)
|
||||
case 1: return UIColor(red: 254.0/255.0, green: 252.0/255.0, blue: 203.0/255.0, alpha:1.0)
|
||||
case 2: return UIColor(red: 254.0/255.0, green: 186.0/255.0, blue: 186.0/255.0, alpha:1.0)
|
||||
default: return UIColor.white
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func countdownTimerFired(_ sender: Timer) {
|
||||
guard let impactTimestamp = impactTimestamp else { return }
|
||||
|
||||
let now = Date()
|
||||
let difference = lround(max(impactTimestamp.timeIntervalSince(now), 0))
|
||||
waveTimeLabel.text = String(format: NSLocalizedString("alert_wave", comment: ""), difference)
|
||||
|
||||
if difference <= 0 {
|
||||
// stop the countdown
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
@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: - Private
|
||||
|
||||
private func backgroundColor(for intensity: Double) -> UIColor {
|
||||
switch intensity {
|
||||
case _ where intensity < 0.004:
|
||||
return AppTheme.Colors.cardBackgroundGray
|
||||
case _ where intensity < 0.30:
|
||||
return AppTheme.Colors.cardBackgroundGreen
|
||||
case _ where intensity < 0.70:
|
||||
return AppTheme.Colors.cardBackgroundYellow
|
||||
default:
|
||||
return AppTheme.Colors.cardBackgroundRed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-29
@@ -8,48 +8,82 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
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)
|
||||
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?()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ class PasquakesMapViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
||||
mapView.deselectAnnotation(annotation, animated: true)
|
||||
|
||||
guard let annotation = annotation as? EQNMapAnnotationPastquake, let pastquake = annotation.pastquake else {
|
||||
return
|
||||
}
|
||||
@@ -96,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()
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,8 @@ class EQNDebugViewController: UIViewController {
|
||||
}
|
||||
|
||||
private func loadDebugData() {
|
||||
let firebaseToken = UserDefaults.standard.string(forKey: EQNUserDefaultUserFirebaseToken) ?? ""
|
||||
let pushToken = UserDefaults.standard.string(forKey: EQNUserDefaultPushToken) ?? ""
|
||||
let firebaseToken = UserDefaults.standard.string(forKey: UserDefaults.UserDataFirebaseToken) ?? ""
|
||||
let pushToken = UserDefaults.standard.string(forKey: UserDefaults.UserDataPushToken) ?? ""
|
||||
|
||||
let text =
|
||||
"""
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
[[EQNManager defaultManager] avviaManager];
|
||||
[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
|
||||
|
||||
self.testo = [NSString stringWithFormat:@" LOG ID UTENTE %@\n\nTOKEN FIREBASE:\n%@\n\n", [EQNUser defaultUser].user_ID, [EQNUser defaultUser].tokenUser];
|
||||
self.testo = [NSString stringWithFormat:@" LOG ID UTENTE %@\n\nTOKEN FIREBASE:\n%@\n\n", [EQNUser defaultUser].user_ID, EQNUserData.sharedData.firebaseToken];
|
||||
|
||||
self.logView.text = self.testo;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,11 @@
|
||||
|
||||
- (BOOL)isBannerVisible
|
||||
{
|
||||
#if ADS_ENABLED
|
||||
return ![EQNPurchaseUtility isProVersionEnabled];
|
||||
#else
|
||||
return NO;
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - View Lifecycle
|
||||
@@ -88,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
|
||||
@@ -63,9 +77,9 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"retry", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
// retry server registration
|
||||
[[EQNUser defaultUser] verificaRegistrazione];
|
||||
[[EQNUser defaultUser] retryUserRegistration];
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"options_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"status_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
@@ -136,7 +150,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
||||
}
|
||||
|
||||
// tap 5 times on "Settings" to open debug view
|
||||
if ([controller isKindOfClass:[SettingsViewController class]]) {
|
||||
if ([controller isKindOfClass:[SettingsViewController class]] && EQNEnableDebugView) {
|
||||
self.debugTapCounter += 1;
|
||||
if (self.debugTapCounter == 5) {
|
||||
self.debugTapCounter = 0;
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
//
|
||||
// PurchaseProVersionViewController.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 PurchaseProVersionViewController: UIViewController {
|
||||
|
||||
@IBOutlet private weak var containerView: UIView!
|
||||
@IBOutlet private weak var titleLabel: UILabel!
|
||||
@IBOutlet private weak var subtitleLabel: UILabel!
|
||||
@IBOutlet private weak var discountTextLabel: UILabel!
|
||||
@IBOutlet private weak var descriptionTextLabel: UILabel!
|
||||
@IBOutlet private weak var openPrivacyButton: UIButton!
|
||||
@IBOutlet private weak var openTermsButton: UIButton!
|
||||
@IBOutlet private weak var payingLabel: UILabel!
|
||||
@IBOutlet private weak var priceLabel: UILabel!
|
||||
@IBOutlet private weak var purchaseButton: UIButton!
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
private var products = [SKProduct]()
|
||||
private var proProduct: SKProduct?
|
||||
|
||||
private var restoreTapped = false
|
||||
/// Time remaining (in hours) for discounted price. If zero, no discount available
|
||||
private var discountTimeRemaining: Int = 0
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseNotification(_:)),
|
||||
name: .EQNInAppPurchaseDidComplete,
|
||||
object: nil)
|
||||
|
||||
configureUI()
|
||||
loadProducts()
|
||||
checkDiscountPrice()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func configureUI() {
|
||||
let restoreButton = UIBarButtonItem(title: NSLocalizedString("purchase_pro_restore", comment: ""),
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(restoreTapped(_:)))
|
||||
navigationItem.rightBarButtonItem = restoreButton
|
||||
|
||||
purchaseButton.isEnabled = false
|
||||
titleLabel.text = NSLocalizedString("network_pro", comment: "")
|
||||
subtitleLabel.text = NSLocalizedString("network_pro_subtitle", comment: "")
|
||||
descriptionTextLabel.text = NSLocalizedString("purchase_pro_description", comment: "")
|
||||
discountTextLabel.text = NSLocalizedString("purchase_pro_discount", comment: "")
|
||||
discountTextLabel.isHidden = true
|
||||
openPrivacyButton.setTitle(NSLocalizedString("network_pro_privacy_disclaimer", comment: ""), for: .normal)
|
||||
openTermsButton.setTitle(NSLocalizedString("network_pro_terms_conditions", comment: ""), for: .normal)
|
||||
payingLabel.text = NSLocalizedString("network_pro_paying", comment: "")
|
||||
purchaseButton.setTitle(NSLocalizedString("network_pro_convert", comment: "").uppercased(), for: .normal)
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
// search for the Pro product
|
||||
let isDiscountEnabled = discountTimeRemaining > 0
|
||||
let identifier = isDiscountEnabled ? VersioneProProducts.Identifier.ProVersionDiscounted : VersioneProProducts.Identifier.ProVersionFullPrice
|
||||
guard let proProduct = products.first(where: { $0.productIdentifier.lowercased() == identifier.lowercased() }) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.proProduct = proProduct
|
||||
priceFormatter.locale = proProduct.priceLocale
|
||||
|
||||
if isDiscountEnabled {
|
||||
discountTextLabel.isHidden = false
|
||||
let string = NSLocalizedString("purchase_pro_discount", comment: "")
|
||||
discountTextLabel.text = String(format: string, discountTimeRemaining)
|
||||
}
|
||||
|
||||
priceLabel.text = priceFormatter.string(from: proProduct.price)
|
||||
purchaseButton.isEnabled = true
|
||||
|
||||
if UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.ProVersionFullPrice) ||
|
||||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.ProVersionDiscounted) ||
|
||||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.Subscription10kYearly) ||
|
||||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.Subscription10kYearlyDiscounted) ||
|
||||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.Subscription100kYearly) ||
|
||||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.Subscription100kYearlyDiscounted) {
|
||||
|
||||
purchaseButton.isEnabled = false
|
||||
priceLabel.text = "-"
|
||||
}
|
||||
}
|
||||
|
||||
private func loadProducts() {
|
||||
VersioneProProducts.store.requestProducts { [weak self] success, products in
|
||||
guard let self = self, let products = products, success == true else { return }
|
||||
|
||||
self.products = products
|
||||
self.updateUI()
|
||||
}
|
||||
}
|
||||
|
||||
private func checkDiscountPrice() {
|
||||
EQNPurchaseUtility.offerTimeRemaining { (timeRemaining) in
|
||||
DispatchQueue.main.async {
|
||||
self.discountTimeRemaining = timeRemaining
|
||||
self.updateUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func restoreTapped(_ sender: Any) {
|
||||
restoreTapped = true
|
||||
VersioneProProducts.store.restorePurchases()
|
||||
}
|
||||
|
||||
@IBAction func purchaseTapped(_ sender: UIButton) {
|
||||
guard let product = proProduct else { return }
|
||||
|
||||
VersioneProProducts.store.buyProduct(product)
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func handlePurchaseNotification(_ notification: Notification) {
|
||||
guard let productId = notification.object as? String,
|
||||
products.contains(where: { $0.productIdentifier == productId }) else {
|
||||
print("[PurchasePro] Unable to find the product")
|
||||
return
|
||||
}
|
||||
|
||||
if restoreTapped {
|
||||
restoreTapped = false
|
||||
let alert = UIAlertController(title: NSLocalizedString("purchase_pro_restore_alert_title", comment: ""),
|
||||
message: NSLocalizedString("purchase_pro_restore_alert_message", comment: ""),
|
||||
preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "ok", style: .default) { [weak self] _ in
|
||||
self?.navigationController?.popViewController(animated: true)
|
||||
})
|
||||
present(alert, animated: true)
|
||||
} else {
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helper
|
||||
|
||||
private var priceFormatter: NumberFormatter = {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.formatterBehavior = .behavior10_4
|
||||
formatter.numberStyle = .currency
|
||||
return formatter
|
||||
}()
|
||||
}
|
||||
@@ -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,182 @@
|
||||
//
|
||||
// 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 == .monthly }) ?? 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.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: "")
|
||||
}
|
||||
}
|
||||
|
||||
+40
-13
@@ -8,26 +8,53 @@
|
||||
|
||||
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
|
||||
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
|
||||
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 {
|
||||
|
||||
@@ -8,39 +8,31 @@
|
||||
|
||||
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
|
||||
@@ -48,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() {
|
||||
@@ -63,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() {
|
||||
@@ -89,75 +80,52 @@ 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.reloadData()
|
||||
tableView.estimatedRowHeight = 600.0
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = .systemGroupedBackground
|
||||
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.isTop100k
|
||||
let rIs10k = rProduct.isTop100k
|
||||
if lIs10k {
|
||||
return lIs10k
|
||||
}
|
||||
return rIs10k
|
||||
}
|
||||
self.productSubscribed = purchased.first
|
||||
self.products = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,18 +138,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)
|
||||
}
|
||||
@@ -189,7 +152,7 @@ class SubscriptionsViewController: UITableViewController {
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func fail(_ notification: Notification){
|
||||
VersioneProProducts.store.loadPurchase()
|
||||
EQNInAppProducts.store.loadPurchase()
|
||||
}
|
||||
|
||||
@objc func handlePurchaseNotification(_ notification: Notification) {
|
||||
@@ -208,7 +171,7 @@ class SubscriptionsViewController: UITableViewController {
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
VersioneProProducts.store.loadPurchase()
|
||||
EQNInAppProducts.store.loadPurchase()
|
||||
loadData()
|
||||
}
|
||||
|
||||
@@ -227,12 +190,9 @@ 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
|
||||
}
|
||||
return nil
|
||||
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
|
||||
view.update(isLoading: isLoading, title: tableSection.sectionTitle)
|
||||
return view
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
@@ -248,96 +208,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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
//
|
||||
// RealtimeAlertView.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 15/06/22.
|
||||
// Copyright © 2022 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
|
||||
|
||||
class RealtimeAlertContainerView: UIView {
|
||||
|
||||
lazy var alertView: RealtimeAlertView = {
|
||||
let view = RealtimeAlertView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.eqn_applyRoundedCorners()
|
||||
view.clipsToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
var animationColor: UIColor = .white
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
convenience init() {
|
||||
self.init(frame: .zero)
|
||||
configureUI()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func configureUI() {
|
||||
backgroundColor = .white
|
||||
|
||||
addSubview(alertView)
|
||||
|
||||
let margin: CGFloat = 24
|
||||
alertView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: margin).isActive = true
|
||||
alertView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -margin).isActive = true
|
||||
alertView.topAnchor.constraint(equalTo: topAnchor, constant: margin).isActive = true
|
||||
alertView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -margin).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func startBackgroundAnimation() {
|
||||
let animation = CABasicAnimation(keyPath: "backgroundColor")
|
||||
animation.fromValue = UIColor.white.cgColor
|
||||
animation.toValue = animationColor.cgColor
|
||||
animation.duration = 2.0
|
||||
animation.beginTime = CACurrentMediaTime() + 1
|
||||
animation.autoreverses = true
|
||||
animation.repeatCount = .infinity
|
||||
|
||||
layer.add(animation, forKey: "backgroundColor")
|
||||
}
|
||||
}
|
||||
|
||||
class RealtimeAlertView: UIView {
|
||||
|
||||
let titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .title2)
|
||||
label.text = NSLocalizedString("app_name", comment: "")
|
||||
return label
|
||||
}()
|
||||
|
||||
let descriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 0
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
let waveTimeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
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
|
||||
}()
|
||||
|
||||
let intensityLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
lazy var mapView: MKMapView = {
|
||||
let map = MKMapView()
|
||||
map.translatesAutoresizingMaskIntoConstraints = false
|
||||
map.delegate = self
|
||||
map.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||
map.showsUserLocation = true
|
||||
return map
|
||||
}()
|
||||
|
||||
let closeButton: EQNBlurredCloseButton = {
|
||||
let button = EQNBlurredCloseButton()
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.setTitleColor(.darkGray, for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
convenience init() {
|
||||
self.init(frame: .zero)
|
||||
configureUI()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func configureUI() {
|
||||
backgroundColor = AppTheme.Colors.lightGray
|
||||
|
||||
addSubview(closeButton)
|
||||
addSubview(titleLabel)
|
||||
addSubview(descriptionLabel)
|
||||
addSubview(mapView)
|
||||
|
||||
closeButton.addDefaultConstraint(to: self)
|
||||
|
||||
titleLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
titleLabel.trailingAnchor.constraint(equalTo: closeButton.leadingAnchor, constant: -10.0).isActive = true
|
||||
titleLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 10.0).isActive = true
|
||||
|
||||
descriptionLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
descriptionLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20.0).isActive = true
|
||||
|
||||
let stackView = UIStackView(arrangedSubviews: [waveTimeLabel, intensityLabel])
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.distribution = .equalSpacing
|
||||
stackView.spacing = 20.0
|
||||
addSubview(stackView)
|
||||
|
||||
stackView.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
|
||||
stackView.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 20.0).isActive = true
|
||||
|
||||
mapView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
|
||||
mapView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
|
||||
mapView.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 20.0).isActive = true
|
||||
mapView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func addMapCircle(
|
||||
center: CLLocationCoordinate2D,
|
||||
radius: CLLocationDistance,
|
||||
overlayId: String
|
||||
) {
|
||||
// remove any other existing overlays
|
||||
let overlays = mapView.overlays.filter { $0.title == overlayId }
|
||||
mapView.removeOverlays(overlays)
|
||||
|
||||
// add new overlay
|
||||
let circle = MKCircle(center: center, radius: radius)
|
||||
circle.title = overlayId
|
||||
mapView.addOverlay(circle)
|
||||
}
|
||||
|
||||
func addMapLine(
|
||||
coordinates: [CLLocationCoordinate2D]
|
||||
) {
|
||||
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
|
||||
mapView.addOverlay(polyline)
|
||||
}
|
||||
|
||||
func addMapAnnotation(
|
||||
title: String = "",
|
||||
center: CLLocationCoordinate2D,
|
||||
intensity: Int
|
||||
) {
|
||||
let annotation = EQNMapAnnotationPastquake(title: title, coordinate: center, intensity: intensity)
|
||||
mapView.addAnnotation(annotation)
|
||||
}
|
||||
}
|
||||
|
||||
extension RealtimeAlertView: MKMapViewDelegate {
|
||||
|
||||
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
switch overlay {
|
||||
case let circle as MKCircle:
|
||||
let circleRenderer = MKCircleRenderer(overlay: circle)
|
||||
circleRenderer.strokeColor = AppTheme.Colors.red
|
||||
circleRenderer.fillColor = AppTheme.Colors.red.withAlphaComponent(0.2)
|
||||
circleRenderer.lineWidth = 3.0
|
||||
return circleRenderer
|
||||
case let polyline as MKPolyline:
|
||||
let polylineRenderer = MKPolylineRenderer(polyline: polyline)
|
||||
polylineRenderer.strokeColor = .blue
|
||||
polylineRenderer.lineWidth = 2.0
|
||||
return polylineRenderer
|
||||
default:
|
||||
return MKOverlayRenderer(overlay: overlay)
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
guard let annotation = annotation as? EQNMapAnnotationPastquake else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
|
||||
annotationView.image = annotation.image
|
||||
annotationView.title = annotation.title
|
||||
return annotationView
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
//
|
||||
// RealtimeAlertViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 15/06/22.
|
||||
// Copyright © 2022 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
|
||||
|
||||
class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
|
||||
@objc var onClose: () -> Void = {}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
private let containerView = RealtimeAlertContainerView()
|
||||
private var notificationView: RealtimeAlertView {
|
||||
containerView.alertView
|
||||
}
|
||||
/// Alert to display
|
||||
private let realtimeAlert: EQNRealtimePushNotification
|
||||
/// Timer to constantly update countdown label
|
||||
private var countdownTimer: Timer?
|
||||
/// Refresh time for wave animation
|
||||
private let waveAnimationRefreshRate = 0.1
|
||||
/// Current radius of the wave animation on the map
|
||||
private var waveAnimationCurrentRadius: CLLocationDistance = 0
|
||||
private var waveAnimationVelocity: Double = 1_000
|
||||
/// Timer to simulate animation for the wave
|
||||
private var waveAnimationTimer: Timer?
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
@objc
|
||||
init(notification: EQNRealtimePushNotification) {
|
||||
self.realtimeAlert = notification
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
self.waveAnimationCurrentRadius = currentWavePosition()
|
||||
self.waveAnimationVelocity = evaluateWaveAnimationVelocity()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
// importante togliere il delegato, altrimenti causa crash
|
||||
notificationView.mapView.delegate = nil
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func loadView() {
|
||||
view = containerView
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
configureUI()
|
||||
updateUI()
|
||||
|
||||
startCountdown()
|
||||
startWaveAnimation()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
containerView.startBackgroundAnimation()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func configureUI() {
|
||||
notificationView.closeButton.addTarget(self, action: #selector(onTapClose(_:)), for: .touchUpInside)
|
||||
|
||||
// configure color for animation
|
||||
containerView.animationColor = realtimeAlert.relativeIntensityColor
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
notificationView.descriptionLabel.text = realtimeAlert.title
|
||||
|
||||
// update title with distance from earthquake
|
||||
let distanceRound = Int(round(realtimeAlert.distanceFromUser() / 1_000))
|
||||
notificationView.descriptionLabel.text = (notificationView.descriptionLabel.text ?? "")
|
||||
+ ".\n"
|
||||
+ String(format: NSLocalizedString("official_distance", comment: ""), distanceRound)
|
||||
|
||||
notificationView.intensityLabel.text = realtimeAlert.displayBody
|
||||
notificationView.intensityLabel.textColor = realtimeAlert.relativeIntensityColor
|
||||
|
||||
// center map on the earthquake coordinate
|
||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||
let region = MKCoordinateRegion(center: realtimeAlert.coordinate.coordinate, span: span)
|
||||
notificationView.mapView.setCenter(realtimeAlert.coordinate.coordinate, animated: false)
|
||||
notificationView.mapView.setRegion(region, animated: true)
|
||||
|
||||
// aggiungiamo annotation con epicentro sisma
|
||||
notificationView.addMapAnnotation(center: realtimeAlert.coordinate.coordinate, intensity: realtimeAlert.intensity)
|
||||
|
||||
// simuliamo animazione dell'onda sismica
|
||||
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
||||
|
||||
// aggiungiamo un segmento tra la posizione del sisma e quella dell'utente
|
||||
if let lastPosition = EQNUser.default().lastPosition {
|
||||
notificationView.addMapLine(coordinates: [realtimeAlert.coordinate.coordinate, lastPosition.coordinate])
|
||||
}
|
||||
}
|
||||
|
||||
private func startCountdown() {
|
||||
// show countdown only if time is less than 300 seconds
|
||||
if realtimeAlert.currentCountdown() < 300 {
|
||||
// start a timer for the countdown label
|
||||
notificationView.waveTimeLabel.isHidden = false
|
||||
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdownTimerFired(_:)), userInfo: nil, repeats: true)
|
||||
countdownTimer?.fire()
|
||||
}
|
||||
}
|
||||
|
||||
private func startWaveAnimation() {
|
||||
waveAnimationTimer = Timer.scheduledTimer(timeInterval: waveAnimationRefreshRate, target: self, selector: #selector(mapWaveAnimationFired(_:)), userInfo: nil, repeats: true)
|
||||
waveAnimationTimer?.fire()
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
@objc private func onTapClose(_ sender: UIButton) {
|
||||
// invalidiamo i timer, altri
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
waveAnimationTimer?.invalidate()
|
||||
waveAnimationTimer = nil
|
||||
|
||||
onClose()
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Timer
|
||||
|
||||
@objc private func countdownTimerFired(_ sender: Timer) {
|
||||
let countdown = realtimeAlert.currentCountdown()
|
||||
notificationView.waveTimeLabel.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), countdown)
|
||||
notificationView.waveTimeLabel.textColor = waveTimeTextColor(for: countdown)
|
||||
|
||||
if countdown <= 0 {
|
||||
// stop the countdown
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func mapWaveAnimationFired(_ sender: Timer) {
|
||||
waveAnimationCurrentRadius += waveAnimationVelocity
|
||||
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Evaluate current position for the wave
|
||||
/// Used to define initial position for the wave circle
|
||||
/// - Returns: Distance of the wave from the original earthquake point
|
||||
private func currentWavePosition() -> Double {
|
||||
// distanza tra utente e terremoto
|
||||
let distance = realtimeAlert.distanceFromUser()
|
||||
|
||||
// calcoliamo la distanza rimanente da mostrare, perchè la schermata potrebbe anche essere aperta in ritardo
|
||||
let remainingDistance = realtimeAlert.waveSpeed * Double(realtimeAlert.currentCountdown())
|
||||
return distance - remainingDistance
|
||||
}
|
||||
|
||||
/// Evaluate wave velocity based on push notification data
|
||||
/// - Returns: Wave velocity, used for animation
|
||||
private func evaluateWaveAnimationVelocity() -> Double {
|
||||
let velocity = realtimeAlert.waveSpeed
|
||||
return velocity * waveAnimationRefreshRate
|
||||
}
|
||||
|
||||
/// Returns the text color based on impact countdown
|
||||
private func waveTimeTextColor(for countdown: Int) -> UIColor {
|
||||
switch countdown {
|
||||
case _ where countdown > 15:
|
||||
return UIColor(red: 255.0/255.0, green: 140.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
case _ where countdown > 5:
|
||||
return UIColor(red: 255.0/255.0, green: 100.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
default:
|
||||
return UIColor(red: 255.0/255.0, green: 0.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,51 +7,123 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
|
||||
@objc
|
||||
class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var mildReportsLabel: UILabel!
|
||||
@IBOutlet private weak var strongReportsLabel: UILabel!
|
||||
@IBOutlet private weak var veryStrongReportsLabel: UILabel!
|
||||
|
||||
@IBOutlet private weak var twitterButton: UIButton! {
|
||||
didSet {
|
||||
twitterButton.imageView?.contentMode = .scaleAspectFit
|
||||
}
|
||||
}
|
||||
@IBOutlet private weak var telegramButton: UIButton! {
|
||||
didSet {
|
||||
telegramButton.imageView?.contentMode = .scaleAspectFit
|
||||
}
|
||||
}
|
||||
@IBOutlet private weak var mapButton: UIButton!
|
||||
@objc var onTapTwitter: (() -> Void)?
|
||||
@objc var onTapMap: (() -> Void)?
|
||||
@objc var onTapTelegram: (() -> Void)?
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
override var headerText: String { NSLocalizedString("tab_manual", comment: "") }
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var reportsLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||
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
|
||||
}()
|
||||
|
||||
private lazy var twitterButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(twitterButtonTapped(_:)))
|
||||
button.imageView?.contentMode = .scaleAspectFit
|
||||
button.setImage(.init(named: "twitter_icon"), for: .normal)
|
||||
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: - Internal
|
||||
|
||||
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("main_map", comment: "")
|
||||
mapButton.setTitle(NSLocalizedString("official_button_map", comment: ""), for: .normal)
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
reportsDescriptionLabel.text = NSLocalizedString("main_map", comment: "")
|
||||
}
|
||||
|
||||
|
||||
// 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 }
|
||||
|
||||
self.mildReportsLabel.text = "\(smartphoneNetwork.manualGreen)"
|
||||
self.strongReportsLabel.text = "\(smartphoneNetwork.manualYellow)"
|
||||
self.veryStrongReportsLabel.text = "\(smartphoneNetwork.manualRed)"
|
||||
let reports = smartphoneNetwork.manual
|
||||
self.reportsLabel.text = "\(reports)"
|
||||
|
||||
if reports < 100 {
|
||||
self.reportsLabel.textColor = UIColor(hex6: 0x01b400)
|
||||
} else if reports < 1000 {
|
||||
self.reportsLabel.textColor = UIColor(hex6: 0xe8da00)
|
||||
} else {
|
||||
self.reportsLabel.textColor = UIColor(hex6: 0xff0000)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func twitterButtonTapped(_ sender: UIButton) {
|
||||
onTapTwitter?()
|
||||
}
|
||||
|
||||
@objc private func mapButtonTapped(_ sender: UIButton) {
|
||||
onTapMap?()
|
||||
}
|
||||
|
||||
@objc private func telegramButtonTapped(_ sender: UIButton) {
|
||||
onTapTelegram?()
|
||||
}
|
||||
}
|
||||
|
||||
+89
-64
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
import MapKit
|
||||
import Shogun
|
||||
|
||||
class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
|
||||
@@ -16,11 +17,46 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
let circle: MKCircle
|
||||
}
|
||||
|
||||
override var isCloseButtonVisible: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
private let appPreferences = AppPreferences.shared
|
||||
/// Contains circles and related colors to draw overlays on the map
|
||||
private var mapCircles = [MapCircle]()
|
||||
/// Reports currently showned on the map
|
||||
private var filteredReports = [EQNSegnalazione]()
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var magnitudeLegendView: UIView = {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
let stackView = UIStackView()
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .horizontal
|
||||
stackView.distribution = .fillEqually
|
||||
[20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120].forEach { magnitude in
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.text = (magnitude / 10).romanNumber()
|
||||
label.backgroundColor = UIColor(named: "Mercalli \(magnitude)")
|
||||
label.textAlignment = .center
|
||||
label.font = .preferredFont(forTextStyle: .callout)
|
||||
label.textColor = magnitude >= 100 ? .white : .black
|
||||
stackView.addArrangedSubview(label)
|
||||
}
|
||||
|
||||
view.addSubview(stackView)
|
||||
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
||||
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
@@ -29,8 +65,26 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func extraUI() {
|
||||
view.addSubview(magnitudeLegendView)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
override func configureUI() {
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(onTapCloseButton(_:)))
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), style: .plain, target: self, action: #selector(onTapScreenshotButton(_:))),
|
||||
UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), style: .plain, target: self, action: #selector(onTapMapDetailStyleButton(_:)))
|
||||
]
|
||||
}
|
||||
|
||||
override func registerMapAnnotationViews() {
|
||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SmallIdentifier)
|
||||
}
|
||||
|
||||
override func loadDataSource() {
|
||||
@@ -66,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
|
||||
@@ -96,27 +150,28 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
}
|
||||
}
|
||||
|
||||
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
||||
guard let annotation = annotation as? EQNMapAnnotationUserReport, let report = annotation.report else {
|
||||
return
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func onTapCloseButton(_ sender: Any) {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
@objc private func onTapMapDetailStyleButton(_ sender: Any) {
|
||||
appPreferences.userReportExpandedView.toggle()
|
||||
reloadMap()
|
||||
}
|
||||
|
||||
@objc private func onTapScreenshotButton(_ sender: Any) {
|
||||
let screenshot = createSnapshot {
|
||||
// nascondiamo la legenda
|
||||
magnitudeLegendView.isHidden = true
|
||||
} restore: {
|
||||
// ri-visualizziamo la legenda
|
||||
magnitudeLegendView.isHidden = false
|
||||
}
|
||||
|
||||
let difference = Int(Date().timeIntervalSince(report.date) / 60.0)
|
||||
let title = EQNUtility.formattedString(forTimeDifference: difference) + " - \(report.intensity.description)"
|
||||
|
||||
var message = ""
|
||||
+ "🏢 " + report.address
|
||||
+ "\n⏱ " + EQNUtility.formattedDate(from: report.date) + " \(NSLocalizedString("share_yourtime", comment: ""))"
|
||||
if !report.message.isEmpty {
|
||||
message += "\n💬 \(report.message)"
|
||||
}
|
||||
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("main_share", comment: ""), style: .default, handler: { [unowned self] _ in
|
||||
self.openShareActivity(for: report)
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("official_close", comment: ""), style: .cancel, handler: nil))
|
||||
present(alert, animated: true, completion: nil)
|
||||
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -125,17 +180,17 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
let vector_latitude = reports.map { $0.coordinate.coordinate.latitude }
|
||||
let vector_longitude = reports.map { $0.coordinate.coordinate.longitude }
|
||||
let vector_date = reports.map { $0.date }
|
||||
let vector_state = reports.map { $0.intensity.rawValue }
|
||||
let vector_state = reports.map { $0.intensity }
|
||||
|
||||
let minutes: TimeInterval = filter.minutes
|
||||
|
||||
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 {
|
||||
@@ -200,21 +255,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
let circles = Array(0..<cluster_code).map { (i) -> MapCircle in
|
||||
var value_distance = max_distance[i] / 20.0
|
||||
if value_distance > 1.0 {
|
||||
value_distance = 1.0
|
||||
}
|
||||
let value_intensity = (cluster_intensity[i]-1.0) / 2.0
|
||||
let value_reference = max(value_distance, value_intensity)
|
||||
|
||||
let color: UIColor
|
||||
if value_reference <= 0.5 {
|
||||
let red = round(value_reference * 510)
|
||||
color = UIColor(red: CGFloat(red / 255.0), green: 230.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
} else {
|
||||
let green = round(230 - (value_reference - 0.5) * 460)
|
||||
color = UIColor(red: 255.0, green: CGFloat(green / 255.0), blue: 0.0, alpha: 1.0)
|
||||
}
|
||||
let color: UIColor = AppTheme.Colors.darkGray
|
||||
|
||||
let centre = CLLocation(latitude: lat_centre[i], longitude: lon_centre[i])
|
||||
let farest = CLLocation(latitude: lat_farest[i], longitude: lon_farest[i])
|
||||
@@ -240,29 +281,6 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
mapView.addOverlays(overlays)
|
||||
}
|
||||
|
||||
private func getDeltaMinute(_ date: Date) -> TimeInterval {
|
||||
Date().timeIntervalSince(date) / 60.0
|
||||
}
|
||||
|
||||
private func openShareActivity(for report: EQNSegnalazione) {
|
||||
// create message to share
|
||||
let intensity = report.intensity.description
|
||||
let difference = Int(Date().timeIntervalSince(report.date) / 60.0)
|
||||
let time = EQNUtility.formattedString(forTimeDifference: difference)
|
||||
|
||||
let message = [
|
||||
NSLocalizedString("share_hashtag", comment: ""),
|
||||
intensity,
|
||||
NSLocalizedString("share_felt", comment: ""),
|
||||
report.address,
|
||||
time + ".",
|
||||
NSLocalizedString("share_notified", comment: "")
|
||||
].joined(separator: " ")
|
||||
|
||||
let controller = UIActivityViewController(activityItems: [message], applicationActivities: [])
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Map
|
||||
|
||||
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
||||
@@ -270,10 +288,17 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||
return nil
|
||||
}
|
||||
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
|
||||
let identifier = appPreferences.userReportExpandedView ? EQNCustomAnnotationView.SingleLineIdentifier : EQNCustomAnnotationView.SmallIdentifier
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as! EQNCustomAnnotationView
|
||||
|
||||
annotationView.image = annotation.image
|
||||
annotationView.title = annotation.title
|
||||
let size = appPreferences.userReportExpandedView ? EQNCustomAnnotationView.SingleLineImageHeight : EQNCustomAnnotationView.SmallViewImageHeight
|
||||
annotationView.image = annotation.image(with: size)
|
||||
annotationView.title = annotation.timeDifference
|
||||
annotationView.canShowCallout = true
|
||||
// Psizioniamo più in alto le segnalazioni con intensità maggiore.
|
||||
// Valori maggiori di anchorPointZ mettono la view più in basso,
|
||||
// quindi invertiamo il valore dell'intensità
|
||||
annotationView.layer.anchorPointZ = (1000 - CGFloat(annotation.report?.intensity ?? 0))
|
||||
|
||||
return annotationView
|
||||
}
|
||||
|
||||
@@ -7,45 +7,111 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
class SegnalazioniSendReportCell: EQNBaseTableViewCell {
|
||||
class SegnalazioniSendReportCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
@IBOutlet private weak var headerLabel: UILabel!
|
||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
||||
@IBOutlet private weak var mildLabel: UILabel!
|
||||
@IBOutlet private weak var mildButton: UIButton!
|
||||
@IBOutlet private weak var strongLabel: UILabel!
|
||||
@IBOutlet private weak var strongButton: UIButton!
|
||||
@IBOutlet private weak var veryStrongLabel: UILabel!
|
||||
@IBOutlet private weak var veryStrongButton: UIButton!
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
private struct Report: Equatable {
|
||||
let magnitude: Int
|
||||
let text: String
|
||||
let color: UIColor
|
||||
|
||||
localizeUI()
|
||||
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
|
||||
|
||||
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")!)
|
||||
]
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
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: "")
|
||||
descriptionLabel.text = NSLocalizedString("manual_usebutton", comment: "")
|
||||
mildLabel.text = NSLocalizedString("manual_mild", comment: "")
|
||||
strongLabel.text = NSLocalizedString("manual_strong", comment: "")
|
||||
veryStrongLabel.text = NSLocalizedString("manual_verystrong", 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
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let labels: [UILabel] = [mildLabel, strongLabel, veryStrongLabel]
|
||||
labels.forEach { (label) in
|
||||
label.layer.borderWidth = AppTheme.shared.buttonBorderWidth
|
||||
label.layer.borderColor = AppTheme.shared.buttonBorderColor.cgColor
|
||||
label.layer.cornerRadius = AppTheme.shared.buttonCornerRadius
|
||||
label.clipsToBounds = true
|
||||
}
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction private func onTapReportButton(_ sender: UIButton) {
|
||||
let magnitude = sender.tag
|
||||
onTapReport(magnitude)
|
||||
}
|
||||
|
||||
@objc private func onTapMagnitudeButton(_ sender: UIButton) {
|
||||
let magnitude = sender.tag
|
||||
onTapReport(magnitude)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,17 +40,19 @@
|
||||
self.title = [NSLocalizedString(@"tab_manual", nil) capitalizedString];
|
||||
self.tableView.estimatedRowHeight = 500.0;
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
[self.tableView registerClass:[SegnalazioniLast24HoursCell class] forCellReuseIdentifier:@"Last24HCell"];
|
||||
[self.tableView registerClass:[SegnalazioniSendReportCell class] forCellReuseIdentifier:@"ReportEarthquakeCell"];
|
||||
}
|
||||
|
||||
- (void)refreshUI
|
||||
{
|
||||
[super refreshUI];
|
||||
|
||||
if ([self.userDefoult objectForKey:DATA_MESSAGE_EQN]){
|
||||
NSDate *dateMessage = [self.userDefoult objectForKey:DATA_MESSAGE_EQN];
|
||||
if ([EQNUtility getDifferenceMinute:dateMessage] >= EQNSendReportDelayBetweenComments){
|
||||
[self.userDefoult removeObjectForKey:DATA_MESSAGE_EQN];
|
||||
[self.userDefoult removeObjectForKey:CODE_MESSAGE_EQN];
|
||||
if ([self.userDefoult objectForKey:NSUserDefaults.UserReportMessage]){
|
||||
NSDate *dateMessage = [self.userDefoult objectForKey:NSUserDefaults.UserReportMessage];
|
||||
if (![dateMessage isBeforeInterval:EQNSendReportDelayBetweenComments]) {
|
||||
[self.userDefoult removeObjectForKey:NSUserDefaults.UserReportMessage];
|
||||
[self.userDefoult removeObjectForKey:NSUserDefaults.UserReportCodeStatus];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,43 +72,48 @@
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
switch (indexPath.row) {
|
||||
case 0: return 140;
|
||||
default: return UITableViewAutomaticDimension;
|
||||
}
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.row == 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ReportEarthquakeCell" forIndexPath:indexPath];
|
||||
SegnalazioniSendReportCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ReportEarthquakeCell" forIndexPath:indexPath];
|
||||
cell.onTapReport = ^(NSInteger magnitude) {
|
||||
[self sendReportTappedWithMagnitude:magnitude];
|
||||
};
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)openMapTapped:(id)sender
|
||||
- (void)openMap
|
||||
{
|
||||
SegnalazioniMapViewController *controller = [[SegnalazioniMapViewController alloc] init];
|
||||
[self presentViewController:controller animated:YES completion:nil];
|
||||
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
|
||||
[self presentViewController:navController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (IBAction)openTwitterTapped:(id)sender
|
||||
- (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];
|
||||
@@ -117,12 +124,12 @@
|
||||
[[EQNManager defaultManager] sincronizza];
|
||||
}
|
||||
|
||||
- (IBAction)sendReportTapped:(UIButton *)sender
|
||||
- (void)sendReportTappedWithMagnitude:(NSInteger)magnitude
|
||||
{
|
||||
// check to avoid multiple consecutive reports
|
||||
if ([self.userDefoult objectForKey:CODE_MESSAGE_EQN]) {
|
||||
NSDate *dateMessage = [self.userDefoult objectForKey:DATA_MESSAGE_EQN];
|
||||
if ([EQNUtility getDifferenceMinute:dateMessage] <= EQNSendReportDelayBetweenMessages){
|
||||
if ([self.userDefoult objectForKey:NSUserDefaults.UserReportCodeStatus]) {
|
||||
NSDate *dateMessage = [self.userDefoult objectForKey:NSUserDefaults.UserReportMessage];
|
||||
if (![dateMessage isBeforeInterval:EQNSendReportDelayBetweenMessages]) {
|
||||
NSString *message = NSLocalizedString(@"manual_wait", @"");
|
||||
[self showErrorAlertWithMessage:message];
|
||||
[self performSelectorOnMainThread:@selector(sincronizzazione) withObject:nil waitUntilDone:YES];
|
||||
@@ -133,9 +140,9 @@
|
||||
// ask for user confirmation
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"attention", @"") message:NSLocalizedString(@"manual_sure", nil) preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"manual_yes", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self executeSendReportWithMagnitude:sender.tag];
|
||||
[self executeSendReportWithMagnitude:magnitude];
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"options_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"status_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
@@ -158,16 +165,14 @@
|
||||
|
||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataSegnalazioneTerremoto success:^(id result) {
|
||||
|
||||
[self.userDefoult setObject:result forKey:CODE_MESSAGE_EQN];
|
||||
[self.userDefoult setObject:[NSDate date] forKey:DATA_MESSAGE_EQN];
|
||||
[self.userDefoult setObject:result forKey:NSUserDefaults.UserReportCodeStatus];
|
||||
[self.userDefoult setObject:[NSDate date] forKey:NSUserDefaults.UserReportMessage];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"report", @"")
|
||||
message:NSLocalizedString(@"manual_ok", @"")
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self sendComment];
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:nil]];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
});
|
||||
} failure:^(NSError * error) {
|
||||
@@ -177,42 +182,6 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)sendComment
|
||||
{
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"manual_sendmessage_button" , @"")
|
||||
message:NSLocalizedString(@"manual_sendmessage", @"")
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
|
||||
}];
|
||||
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok" ,@"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
UITextField * messaggio = alertController.textFields.firstObject;
|
||||
|
||||
NSURL *url = [EQNGeneratoreURLServer urlInvioCommentoTerremoto:messaggio.text codeMessage:[self.userDefoult objectForKey:CODE_MESSAGE_EQN]];
|
||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataCommentoTerremoto success:^(id result) {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"manual_sendmessage_button" , @"")
|
||||
message:NSLocalizedString(@"manual_message_received", @"")
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
});
|
||||
|
||||
} failure:^(NSError * error) {
|
||||
[self showErrorAlertWithMessage:error.localizedDescription];
|
||||
}];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self refreshUI];
|
||||
});
|
||||
}]];
|
||||
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"options_cancel", @"") style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)showErrorAlertWithMessage:(NSString *)errorMessage
|
||||
|
||||
+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
|
||||
}
|
||||
}
|
||||
|
||||
+71
-210
@@ -9,12 +9,12 @@
|
||||
import UIKit
|
||||
import MapKit
|
||||
import CoreLocation
|
||||
import Shogun
|
||||
|
||||
protocol SeismicNetworkTableViewCellDelegate: AnyObject {
|
||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapWeather(_ cell: SeismicNetworkTableViewCell, hasValidWeatherData: Bool)
|
||||
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell)
|
||||
@@ -23,9 +23,7 @@ protocol SeismicNetworkTableViewCellDelegate: AnyObject {
|
||||
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
|
||||
@@ -44,8 +42,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
case normal
|
||||
/// Cell with map visible
|
||||
case mapExpanded
|
||||
/// Cell with weather info visible
|
||||
case weatherExpanded
|
||||
}
|
||||
|
||||
/// Delegate
|
||||
@@ -54,42 +50,38 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
// MARK: - Internal
|
||||
|
||||
private static let DefaultVerticalSpacing: CGFloat = 6.0
|
||||
private static let DefaultBodyFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
private static let DefaultBodyFontLight = UIFont.preferredFont(for: .body, weight: .light)
|
||||
|
||||
/// 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
|
||||
view.backgroundColor = .white
|
||||
view.clipsToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var titleImageView: UIImageView = {
|
||||
let imageView = UIImageView(frame: .zero)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return imageView
|
||||
private lazy var gradientView: UIImageView = {
|
||||
// Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine
|
||||
// creata ad-hoc con il gradiente desiderato.
|
||||
// Le prove fatte utilizzando una view normale sono fallite perchè al momento di
|
||||
// disegnare la view non abbiamo le misure corrette.
|
||||
let view = UIImageView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.contentMode = .scaleToFill
|
||||
return view
|
||||
}()
|
||||
|
||||
|
||||
private lazy var placeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = UIFont.preferredFont(for: .title2, weight: .semibold)
|
||||
label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold)
|
||||
label.numberOfLines = 3
|
||||
return label
|
||||
}()
|
||||
@@ -97,8 +89,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
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
|
||||
}()
|
||||
|
||||
@@ -113,35 +106,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
|
||||
}()
|
||||
|
||||
@@ -149,8 +147,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
|
||||
}()
|
||||
|
||||
@@ -158,8 +157,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
|
||||
}()
|
||||
|
||||
@@ -173,20 +173,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
return mapView
|
||||
}()
|
||||
|
||||
private lazy var weatherImageView: UIImageView = {
|
||||
let imageView = UIImageView(frame: .zero)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var weatherInfoLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
@@ -199,11 +185,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
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
backgroundColor = .clear
|
||||
|
||||
// container view
|
||||
@@ -213,6 +207,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
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)
|
||||
|
||||
// this variable is used to keep track of the previous view, in order to attach proper constraints
|
||||
var previousView: UIView = containerView
|
||||
|
||||
@@ -247,16 +244,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
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
|
||||
@@ -335,6 +325,13 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
previousView = separator3
|
||||
}
|
||||
|
||||
// network
|
||||
containerView.addSubview(networkLabel)
|
||||
networkLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).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(.buttons) {
|
||||
// buttons
|
||||
let stackViewButtons = UIStackView()
|
||||
@@ -343,13 +340,11 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
stackViewButtons.distribution = .fillEqually
|
||||
stackViewButtons.spacing = 4
|
||||
|
||||
let buttonMap = createRoundedButton(title: "🗺", action: #selector(mapTapped(_:)))
|
||||
let buttonMap = EQNRoundedButton.make(title: "🗺", target: self, action: #selector(mapTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonMap)
|
||||
let buttonWeather = createRoundedButton(title: "🌤", action: #selector(weatherTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonWeather)
|
||||
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)
|
||||
@@ -369,33 +364,10 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
mapView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
previousView = mapView
|
||||
} else if displayType == .weatherExpanded {
|
||||
let weatherTitleLabel = UILabel()
|
||||
weatherTitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
weatherTitleLabel.text = NSLocalizedString("weather_weather", comment: "")
|
||||
weatherTitleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
|
||||
|
||||
containerView.addSubview(weatherTitleLabel)
|
||||
weatherTitleLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
weatherTitleLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
weatherTitleLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
containerView.addSubview(weatherInfoLabel)
|
||||
containerView.addSubview(weatherImageView)
|
||||
weatherImageView.heightAnchor.constraint(equalToConstant: 60.0).isActive = true
|
||||
weatherImageView.widthAnchor.constraint(equalTo: weatherImageView.heightAnchor).isActive = true
|
||||
weatherImageView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
weatherImageView.trailingAnchor.constraint(equalTo: weatherInfoLabel.leadingAnchor, constant: -8.0).isActive = true
|
||||
weatherImageView.centerYAnchor.constraint(equalTo: weatherInfoLabel.centerYAnchor).isActive = true
|
||||
|
||||
weatherInfoLabel.topAnchor.constraint(equalTo: weatherTitleLabel.bottomAnchor, constant: 4.0).isActive = true
|
||||
weatherInfoLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
previousView = weatherInfoLabel
|
||||
}
|
||||
|
||||
if (displayType == .mapExpanded || displayType == .weatherExpanded) {
|
||||
let buttonClose = createRoundedButton(title: NSLocalizedString("official_close", comment: "").uppercased(), action: #selector(closeTapped(_:)))
|
||||
if (displayType == .mapExpanded) {
|
||||
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
|
||||
@@ -406,6 +378,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
else {
|
||||
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
gradientView.eqn_applyRoundedCorners()
|
||||
}
|
||||
|
||||
private func recreateUI() {
|
||||
@@ -419,15 +394,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)"
|
||||
@@ -463,14 +436,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(mapDetailTapped(_:)))
|
||||
mapView.addGestureRecognizer(tapRecognizer)
|
||||
} else if displayType == .weatherExpanded {
|
||||
weatherInfoLabel.text = ""
|
||||
+ String(format: NSLocalizedString("weather_temperature", comment: ""), seismic.weatherTemperature.doubleValue - EQNMathKelvin) + "\n"
|
||||
+ String(format: NSLocalizedString("weather_pressure", comment: ""), seismic.weatherPressure) + "\n"
|
||||
+ String(format: NSLocalizedString("weather_windspeed", comment: ""), seismic.weatherWindSpeed) + "\n"
|
||||
+ String(format: NSLocalizedString("weather_humidity", comment: ""), seismic.weatherHumidity) + "\n"
|
||||
+ String(format: NSLocalizedString("weather_clouds", comment: ""), seismic.weatherCloud)
|
||||
weatherImageView.image = UIImage(named: "weather_\(seismic.weatherIcon).png")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,11 +446,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]
|
||||
@@ -505,16 +475,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
/// Creates a snapshot of the current cell
|
||||
/// - Returns: Image with the snapshot of the cell
|
||||
public func createSnapshot() -> UIImage {
|
||||
let renderer = UIGraphicsImageRenderer(size: contentView.bounds.size)
|
||||
let image = renderer.image { ctx in
|
||||
contentView.drawHierarchy(in: contentView.bounds, afterScreenUpdates: true)
|
||||
}
|
||||
return image
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func shareTapped(_ sender: UIButton) {
|
||||
@@ -526,13 +486,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
delegate?.seismicNetworkCellDidTapMap(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func weatherTapped(_ sender: UIButton) {
|
||||
if displayType != .weatherExpanded {
|
||||
let validData = seismic?.weatherCode != nil
|
||||
delegate?.seismicNetworkCellDidTapWeather(self, hasValidWeatherData: validData)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func calendarTapped(_ sender: UIButton) {
|
||||
delegate?.seismicNetworkCellDidTapCalendar(self)
|
||||
@@ -566,48 +519,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
|
||||
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
|
||||
@@ -620,55 +532,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"
|
||||
}
|
||||
}
|
||||
+80
-115
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
protocol SeismicFiltersViewControllerDelegate: AnyObject {
|
||||
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController)
|
||||
@@ -15,13 +16,11 @@ 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
|
||||
}
|
||||
|
||||
weak var delegate: SeismicFiltersViewControllerDelegate?
|
||||
@@ -36,29 +35,20 @@ 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("Distanza massima", 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: "")),
|
||||
]
|
||||
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
|
||||
|
||||
@@ -85,19 +75,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
|
||||
@@ -107,45 +87,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
|
||||
@@ -154,30 +125,31 @@ 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)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return cell
|
||||
@@ -190,41 +162,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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// 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 minma 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
|
||||
let offset: CGFloat = (index > highlightIndex && smallRectangles) ? rectHighlightedMinHeight : 0
|
||||
let yPosition = CGFloat(index) * rectHeight + offset
|
||||
let rectangle = CGRect(x: 0, y: yPosition, width: rectStandardWidth, height: rectHeight)
|
||||
|
||||
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) // Evita che il bordo venga tagliato
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
-13
@@ -11,23 +11,34 @@ import Foundation
|
||||
|
||||
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
|
||||
struct MagnitudeColors {
|
||||
let textColor: UIColor
|
||||
let startColor: UIColor
|
||||
let endColor: UIColor
|
||||
}
|
||||
|
||||
private let seismic: EQNSisma
|
||||
let place: String
|
||||
let network: String
|
||||
let isPreliminary: Bool
|
||||
let magnitude: String
|
||||
let rawMagnitude: Double
|
||||
let depth: String
|
||||
let time: String
|
||||
let distance: String
|
||||
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
|
||||
self.rawMagnitude = seismic.magnitude.doubleValue
|
||||
|
||||
let isPreliminary = seismic.preliminary.intValue > 0
|
||||
self.isPreliminary = isPreliminary
|
||||
@@ -38,7 +49,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
|
||||
@@ -52,7 +63,7 @@ struct SeismicNetworkViewModel {
|
||||
|
||||
// distance
|
||||
let distanceRounded = Int(round(seismic.userDistance))
|
||||
self.distance = String(format: NSLocalizedString("timer_message2_other", comment: ""), distanceRounded)
|
||||
self.distance = String(format: NSLocalizedString("official_distance", comment: ""), distanceRounded)
|
||||
let coordinateText = EQNUtility.coordinateString(coordinate: seismic.coordinate.coordinate)
|
||||
self.coordinate = "\(coordinateText)"
|
||||
|
||||
@@ -69,6 +80,8 @@ struct SeismicNetworkViewModel {
|
||||
} else {
|
||||
self.users = ""
|
||||
}
|
||||
|
||||
self.colors = Self.calculateColors(for: seismic.magnitude.doubleValue)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -88,4 +101,62 @@ struct SeismicNetworkViewModel {
|
||||
}
|
||||
return populationString
|
||||
}
|
||||
|
||||
/// Calculate colors to use for text and background of the cell
|
||||
private static func calculateColors(for magnitude: Double) -> MagnitudeColors {
|
||||
var textColor = UIColor.black
|
||||
|
||||
var r = 0, g = 0, b = 0
|
||||
if (magnitude < 2.0) {
|
||||
let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0)
|
||||
r = Int(round(200.0 + (255.0 - 200.0) * fraction))
|
||||
g = Int(round(226.0 + (255.0 - 226.0) * fraction))
|
||||
b = Int(round(196.0 + (255.0 - 196.0) * fraction))
|
||||
textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 160.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 2.0 && magnitude < 3.5) {
|
||||
let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2)
|
||||
r = Int(round(136.0 + (200.0 - 136.0) * fraction))
|
||||
g = Int(round(175.0 + (226.0 - 175.0) * fraction))
|
||||
b = Int(round(131.0 + (196.0 - 131.0) * fraction))
|
||||
textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 3.5 && magnitude < 4.5) {
|
||||
let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5)
|
||||
r = 252
|
||||
g = Int(round(233.0 + (253.0 - 233.0) * fraction))
|
||||
b = Int(round(179.0 + (209.0 - 179.0) * fraction))
|
||||
textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 4.5 && magnitude < 5.5) {
|
||||
let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5)
|
||||
r = 252
|
||||
g = Int(round(159.0 + (197.0 - 159.0) * fraction))
|
||||
b = Int(round(161.0 + (197.0 - 161.0) * fraction))
|
||||
textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 5.5) {
|
||||
let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5)
|
||||
r = Int(round(190.0 + (254.0 - 190.0) * fraction))
|
||||
g = Int(round(124.0 + (219.0 - 124.0) * fraction))
|
||||
b = 255
|
||||
textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
|
||||
let r2 = min(r + 30, 255)
|
||||
let g2 = min(g + 30, 255)
|
||||
let b2 = min(b + 30, 255)
|
||||
|
||||
let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0)
|
||||
let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0)
|
||||
|
||||
return .init(textColor: textColor, startColor: startColor, endColor: endColor)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SeismicNetworkViewModel: Equatable {
|
||||
static func == (lhs: SeismicNetworkViewModel, rhs: SeismicNetworkViewModel) -> Bool {
|
||||
return lhs.seismic == rhs.seismic
|
||||
}
|
||||
}
|
||||
|
||||
+119
-48
@@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
import Shogun
|
||||
|
||||
protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
|
||||
func seismicNetworksMapDetailControllerWillUpdateData(_ controller: SeismicNetworksMapDetailViewController, needsDataUpdate: Bool)
|
||||
@@ -15,10 +16,27 @@ protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
|
||||
|
||||
class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
|
||||
private enum PinStyle: CaseIterable {
|
||||
case full
|
||||
case light
|
||||
case circle
|
||||
|
||||
func next() -> Self {
|
||||
let all = Self.allCases
|
||||
let idx = all.firstIndex(of: self)!
|
||||
let next = all.index(after: idx)
|
||||
return all[next == all.endIndex ? all.startIndex : next]
|
||||
}
|
||||
}
|
||||
|
||||
private var pinStyle: PinStyle = .full
|
||||
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?
|
||||
@@ -41,10 +59,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
|
||||
}()
|
||||
|
||||
@@ -75,6 +90,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]) {
|
||||
@@ -83,7 +116,9 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
override func registerMapAnnotationViews() {
|
||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.DoubleLineIdentifier)
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierFull)
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierLight)
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierCircle)
|
||||
}
|
||||
|
||||
override func loadDataSource() {
|
||||
@@ -91,14 +126,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()
|
||||
}
|
||||
|
||||
@@ -107,6 +142,8 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
||||
mapView.deselectAnnotation(annotation, animated: true)
|
||||
|
||||
guard let annotation = annotation as? EQNMapAnnotationSeismic else { return }
|
||||
|
||||
let viewModel = SeismicNetworkViewModel(seismic: annotation.seismic)
|
||||
@@ -124,31 +161,60 @@ 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 = 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: "")
|
||||
}
|
||||
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
|
||||
@@ -159,16 +225,13 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
mapView.addOverlays(circles)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc override func filtersTapped(_ sender: UIGestureRecognizer) {
|
||||
let controller = SeismicFiltersViewController.makeController()
|
||||
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? {
|
||||
@@ -176,15 +239,23 @@ 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
|
||||
switch pinStyle {
|
||||
case .full, .light:
|
||||
let identifier = pinStyle == .full ? EQNSeismicAnnotationView.IdentifierFull : EQNSeismicAnnotationView.IdentifierLight
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as! EQNSeismicAnnotationView
|
||||
annotationView.title = annotation.title
|
||||
annotationView.subtitle = annotation.subtitle
|
||||
annotationView.magnitude = String(format: "M%.1f", annotation.seismic.magnitude.doubleValue)
|
||||
annotationView.magnitudeColor = annotation.textColor
|
||||
annotationView.isUserSelection = isUserSelection
|
||||
return annotationView
|
||||
case .circle:
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNSeismicAnnotationView.IdentifierCircle, for: annotation) as! EQNSeismicAnnotationView
|
||||
annotationView.image = annotation.image(height: EQNSeismicAnnotationView.CircleViewHeight,
|
||||
isUserSelection: isUserSelection)
|
||||
return annotationView
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
@@ -193,8 +264,8 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
+505
-102
@@ -9,28 +9,23 @@
|
||||
import UIKit
|
||||
import EventKitUI
|
||||
import DZNEmptyDataSet
|
||||
import Shogun
|
||||
|
||||
class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
private enum CellType {
|
||||
case seismic(EQNSisma)
|
||||
case advertise(GADNativeAd)
|
||||
case advertise(NativeAd)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -39,27 +34,80 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
|
||||
/// Cells to display (must be seismics or ad banners)
|
||||
private var rows = [CellType]()
|
||||
private var seismicViewModels = [SeismicNetworkViewModel]()
|
||||
/// Informations to display on a single cell
|
||||
private var informations = [SeismicNetworkTableViewCell.InformationType]()
|
||||
/// Index path of row with map expanded
|
||||
private var openMapIndexPath: IndexPath?
|
||||
/// Index path of row with weather expanded
|
||||
private var openWeatherIndexPath: 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 expandeCollapseButton: 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)
|
||||
}
|
||||
@@ -68,16 +116,101 @@ 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: SeismicNetworkAdvertiseTableViewCell.self)
|
||||
tableView.emptyDataSetSource = self
|
||||
tableView.separatorStyle = .none
|
||||
|
||||
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?) {
|
||||
@@ -86,14 +219,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
|
||||
@@ -107,7 +232,8 @@ 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
|
||||
}
|
||||
@@ -116,7 +242,6 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
|
||||
@objc func didReceiveDownloadCompleteNotification(_ sender: Notification) {
|
||||
self.openMapIndexPath = nil
|
||||
self.openWeatherIndexPath = nil
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.refreshUI()
|
||||
@@ -138,11 +263,17 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-expand")
|
||||
}
|
||||
|
||||
tableView?.reloadData()
|
||||
tableView.reloadData()
|
||||
updateCenterCellIndexPath()
|
||||
|
||||
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) {
|
||||
@@ -154,7 +285,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() {
|
||||
@@ -166,6 +298,9 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
if let mapController = currentMapController {
|
||||
mapController.updateSeismics(filteredSeismics)
|
||||
}
|
||||
|
||||
scrollIndicatorView.seismics = seismicViewModels
|
||||
currentCenteredIndexPath = nil
|
||||
}
|
||||
|
||||
private func getSeismics() -> [EQNSisma] {
|
||||
@@ -178,6 +313,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) {
|
||||
@@ -187,11 +623,7 @@ 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) {
|
||||
informations.removeAll(where: { $0 == .buttons })
|
||||
@@ -213,20 +645,19 @@ 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
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkTableViewCell.self, for: indexPath)
|
||||
|
||||
var type = SeismicNetworkTableViewCell.DisplayType.normal
|
||||
if openMapIndexPath == indexPath {
|
||||
type = .mapExpanded
|
||||
} else if openWeatherIndexPath == indexPath {
|
||||
type = .weatherExpanded
|
||||
}
|
||||
|
||||
cell.configure(with: seismic, type: type, informations: informations)
|
||||
|
||||
let isPushSelected = isSeismicToHighlight(seismic: seismic)
|
||||
cell.configure(with: seismic, type: type, informations: informations, isPushSelected: isPushSelected)
|
||||
cell.delegate = self
|
||||
return cell
|
||||
case .advertise(let nativeAd):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: SeismicNetworkAdvertiseTableViewCell.Identifier, for: indexPath) as! SeismicNetworkAdvertiseTableViewCell
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkAdvertiseTableViewCell.self, for: indexPath)
|
||||
cell.loadNativeAd(nativeAd)
|
||||
return cell
|
||||
}
|
||||
@@ -241,6 +672,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIScrollViewDelegate
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
updateCenterCellIndexPath()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func openCalendar(for seismic: EQNSisma) {
|
||||
@@ -296,28 +733,28 @@ 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 {
|
||||
|
||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
|
||||
// create a snapshot of the cell and share with default share sheet
|
||||
let snapshot = cell.createSnapshot()
|
||||
let snapshot = cell.contentView.createSnapshot()
|
||||
|
||||
// text to share with the snapshot
|
||||
let shareHashtag = NSLocalizedString("share_hashtag", comment: "")
|
||||
@@ -330,43 +767,23 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapWeather(_ cell: SeismicNetworkTableViewCell, hasValidWeatherData: Bool) {
|
||||
guard let index = tableView?.indexPath(for: cell) else { return }
|
||||
|
||||
if !hasValidWeatherData {
|
||||
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
|
||||
message: NSLocalizedString("weather_nodata", comment: ""),
|
||||
preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .default))
|
||||
present(alert, animated: true)
|
||||
return
|
||||
}
|
||||
|
||||
let indexToReloads = [openMapIndexPath, openWeatherIndexPath, index].compactMap { $0 }
|
||||
|
||||
openWeatherIndexPath = index
|
||||
openMapIndexPath = nil
|
||||
tableView?.reloadRows(at: indexToReloads, with: .automatic)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell) else { return }
|
||||
guard let index = tableView.indexPath(for: cell) else { return }
|
||||
|
||||
let indexToReloads = [openMapIndexPath, openWeatherIndexPath, index].compactMap { $0 }
|
||||
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
||||
|
||||
openMapIndexPath = index
|
||||
openWeatherIndexPath = nil
|
||||
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 }
|
||||
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 }
|
||||
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
|
||||
openCalendar(for: seismic)
|
||||
}
|
||||
@@ -376,13 +793,12 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell) else { return }
|
||||
guard let index = tableView.indexPath(for: cell) else { return }
|
||||
|
||||
let indexToReloads = [openMapIndexPath, openWeatherIndexPath, index].compactMap { $0 }
|
||||
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
||||
|
||||
openMapIndexPath = nil
|
||||
openWeatherIndexPath = nil
|
||||
tableView?.reloadRows(at: indexToReloads, with: .automatic)
|
||||
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,22 +816,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()
|
||||
@@ -423,9 +823,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
|
||||
}
|
||||
}
|
||||
|
||||
-112
@@ -1,112 +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
|
||||
if let savedNetworks = UserDefaults.standard.object(forKey: IMPOSTAZIONE_ENTI_RETI_SISMICHEI) as? [String] {
|
||||
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)
|
||||
}
|
||||
|
||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func cancelTapped(_ sender: Any) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func saveTapped(_ sender: Any) {
|
||||
// save selected networks
|
||||
UserDefaults.standard.set(savedNetworks, forKey: IMPOSTAZIONE_ENTI_RETI_SISMICHEI)
|
||||
|
||||
// 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: "options_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]
|
||||
UserDefaults.standard.set(selectedNetworks, forKey: IMPOSTAZIONE_ENTI_RETI_SISMICHEI)
|
||||
|
||||
// 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("options_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
|
||||
-308
@@ -1,308 +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 (strong, nonatomic) NSArray<EQNGenericValue *> *dataSourceSismi;
|
||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceRaggioSisma;
|
||||
|
||||
@property (nonatomic, strong) EQNGenericValue *currentSeismicToNotify;
|
||||
@property (strong, nonatomic) EQNGenericValue *currentLowSeismicRadius;
|
||||
@property (strong, nonatomic) EQNGenericValue *currentStrongSeismicRadius;
|
||||
@property (strong, nonatomic) NSDate *currentStartTime;
|
||||
@property (nonatomic) BOOL isStartTimeExpanded;
|
||||
@property (strong, nonatomic) NSDate *currentEndTime;
|
||||
@property (nonatomic) BOOL isEndTimeExpanded;
|
||||
|
||||
@property (nonatomic, strong) NSDateFormatter *dateFormatter;
|
||||
|
||||
@property (nonatomic, assign) BOOL notificationEnabled;
|
||||
@property (nonatomic, assign) BOOL criticalAlertsEnabled;
|
||||
@property (nonatomic, assign) BOOL doNotDisturbEnabled;
|
||||
@end
|
||||
|
||||
@implementation SettingsRealTimeAlertsViewController
|
||||
|
||||
typedef NS_ENUM(NSInteger, RowIdentifier) {
|
||||
RowIdentifierAbilitaNotifiche = 0,
|
||||
RowIdentifierAbilitaCriticalAlerts,
|
||||
RowIdentifierSismiDaNotificare,
|
||||
RowIdentifierRaggioSismiLievi,
|
||||
RowIndntifierRaggioSismiForti,
|
||||
RowIdentifierNonDisturbare,
|
||||
RowIdentifierNonDisturbareOraInizio,
|
||||
RowIdentifierNonDisturbareOraFine
|
||||
};
|
||||
|
||||
#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", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeSegmented title:NSLocalizedString(@"options_notification_eqn_intensity", @"") subtitle:NSLocalizedString(@"", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius_mild", @"")],
|
||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius_strong", @"")]
|
||||
];
|
||||
|
||||
self.dataSourceSismi = [EQNData seismicToNotify];
|
||||
self.dataSourceRaggioSisma = [EQNData raggioSismi];
|
||||
|
||||
[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;
|
||||
self.doNotDisturbEnabled = [EQNAllertaSismica sharedInstance].isintervalloAllarme;
|
||||
|
||||
// sismi da notificare
|
||||
EQNGenericValue *sismiDaNotificare = [EQNData seismicToNotifyFor:[EQNAllertaSismica sharedInstance].sismiDaNotificare];
|
||||
self.currentSeismicToNotify = sismiDaNotificare;
|
||||
|
||||
// raggio sismi lievi
|
||||
EQNGenericValue *raggioSismiLievi = [EQNData raggioSismaFor:[EQNAllertaSismica sharedInstance].raggioSismiLievi];
|
||||
self.currentLowSeismicRadius = raggioSismiLievi;
|
||||
|
||||
// raggio sismi forti
|
||||
EQNGenericValue *raggioSismiForti = [EQNData raggioSismaFor:[EQNAllertaSismica sharedInstance].raggioSismiForti];
|
||||
self.currentStrongSeismicRadius = raggioSismiForti;
|
||||
|
||||
// non disturbare, orari
|
||||
NSDate *startTime = [EQNData doNotDisturbEndDateFrom:[EQNAllertaSismica sharedInstance].oraioInizio];
|
||||
self.currentStartTime = startTime;
|
||||
|
||||
NSDate *endTime = [EQNData doNotDisturbEndDateFrom:[EQNAllertaSismica sharedInstance].orarioFine];
|
||||
self.currentEndTime = endTime;
|
||||
|
||||
[[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];
|
||||
};
|
||||
} else if (indexPath.row == RowIdentifierNonDisturbare) {
|
||||
cell.toggleSwitch.on = self.doNotDisturbEnabled;
|
||||
cell.isDisabled = !self.notificationEnabled;
|
||||
cell.valueChanged = ^(BOOL enabled) {
|
||||
self.doNotDisturbEnabled = enabled;
|
||||
[EQNAllertaSismica sharedInstance].isintervalloAllarme = self.doNotDisturbEnabled;
|
||||
[[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;
|
||||
|
||||
if (indexPath.row == RowIdentifierSismiDaNotificare) {
|
||||
cell.isDisabled = !self.notificationEnabled;
|
||||
[cell configureControlWith:self.dataSourceSismi current:self.currentSeismicToNotify];
|
||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
||||
[self updateSismicToNotify:item];
|
||||
};
|
||||
}
|
||||
|
||||
return cell;
|
||||
} else if (setting.type == SettingTypeSlider) {
|
||||
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.titleLabel.text = setting.displayTitle;
|
||||
|
||||
if (indexPath.row == RowIdentifierRaggioSismiLievi) {
|
||||
cell.isDisabled = !self.notificationEnabled;
|
||||
[cell configureSliderWith:self.dataSourceRaggioSisma current:self.currentLowSeismicRadius];
|
||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
||||
[self updateLowSeismicRadius:item];
|
||||
};
|
||||
} else if (indexPath.row == RowIndntifierRaggioSismiForti) {
|
||||
cell.isDisabled = !self.notificationEnabled;
|
||||
[cell configureSliderWith:self.dataSourceRaggioSisma current:self.currentStrongSeismicRadius];
|
||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
||||
[self updateStrongSeismicRadius:item];
|
||||
};
|
||||
}
|
||||
|
||||
return cell;
|
||||
} else if (setting.type == SettingTypeDate) {
|
||||
SettingDateTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingDateTableViewCell.Identifier forIndexPath:indexPath];
|
||||
cell.isDisabled = !self.doNotDisturbEnabled || !self.notificationEnabled;
|
||||
cell.userInteractionEnabled = self.doNotDisturbEnabled && self.notificationEnabled;
|
||||
cell.titleLabel.text = setting.title;
|
||||
|
||||
if (indexPath.row == RowIdentifierNonDisturbareOraInizio) {
|
||||
cell.isPickerVisible = self.isStartTimeExpanded;
|
||||
[cell updateDate:self.currentStartTime];
|
||||
cell.valuesLabel.text = [self.dateFormatter stringFromDate:self.currentStartTime];
|
||||
cell.valueChanged = ^(NSDate *date) {
|
||||
[self updateStartTime:date];
|
||||
};
|
||||
} else if (indexPath.row == RowIdentifierNonDisturbareOraFine) {
|
||||
cell.isPickerVisible = self.isEndTimeExpanded;
|
||||
[cell updateDate:self.currentEndTime];
|
||||
cell.valuesLabel.text = [self.dateFormatter stringFromDate:self.currentEndTime];
|
||||
cell.valueChanged = ^(NSDate *date) {
|
||||
[self updateEndTime:date];
|
||||
};
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.row == RowIdentifierNonDisturbareOraInizio) {
|
||||
self.isStartTimeExpanded = !self.isStartTimeExpanded;
|
||||
} else if (indexPath.row == RowIdentifierNonDisturbareOraFine) {
|
||||
self.isEndTimeExpanded = !self.isEndTimeExpanded;
|
||||
}
|
||||
|
||||
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)updateSismicToNotify:(EQNGenericValue *)seismic
|
||||
{
|
||||
[EQNAllertaSismica sharedInstance].sismiDaNotificare = seismic.value;
|
||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
||||
|
||||
[self loadDataSource];
|
||||
}
|
||||
|
||||
- (void)updateLowSeismicRadius:(EQNGenericValue *)radius
|
||||
{
|
||||
[EQNAllertaSismica sharedInstance].raggioSismiLievi = radius.value;
|
||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
||||
|
||||
[self loadDataSource];
|
||||
}
|
||||
|
||||
- (void)updateStrongSeismicRadius:(EQNGenericValue *)radius
|
||||
{
|
||||
[EQNAllertaSismica sharedInstance].raggioSismiForti = radius.value;
|
||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
||||
|
||||
[self loadDataSource];
|
||||
}
|
||||
|
||||
- (void)updateStartTime:(NSDate *)date
|
||||
{
|
||||
[EQNAllertaSismica sharedInstance].oraioInizio = date;
|
||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
||||
|
||||
[self loadDataSource];
|
||||
}
|
||||
|
||||
- (void)updateEndTime:(NSDate *)date
|
||||
{
|
||||
[EQNAllertaSismica sharedInstance].orarioFine = date;
|
||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
||||
|
||||
[self loadDataSource];
|
||||
}
|
||||
|
||||
- (void)askForCriticalAlertsPermission
|
||||
{
|
||||
UNAuthorizationOptions authOptions = UNAuthorizationOptionCriticalAlert;
|
||||
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError *error) {
|
||||
// nope
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// 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 abilitaCriticalAlerts
|
||||
}
|
||||
|
||||
private var isNotificationEnabled = false
|
||||
private var isCriticalAlertsEnabled = false
|
||||
|
||||
private let settings: [SettingItem] = [
|
||||
.init(type: .enable, title: NSLocalizedString("options_notification_enable_alarm", 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
|
||||
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 .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 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()
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,15 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
!availableFilters.isEmpty
|
||||
}
|
||||
|
||||
/// If `true` the close button will be shown on top of the map view
|
||||
var isCloseButtonVisible: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
/// Annotations displayed on the map
|
||||
private var mapAnnotations = [MKAnnotation]()
|
||||
private(set) var mapAnnotations = [MKAnnotation]()
|
||||
/// If `true`, the initial filter has been already evaluated
|
||||
private var initialFilterEvaluated = false
|
||||
|
||||
@@ -99,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() {
|
||||
@@ -116,13 +149,14 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
private func setupUI() {
|
||||
view.backgroundColor = .white
|
||||
view.addSubview(mapView)
|
||||
view.addSubview(closeButton)
|
||||
view.addSubview(containerView)
|
||||
if isFilterViewVisible {
|
||||
view.addSubview(filtersView)
|
||||
}
|
||||
|
||||
closeButton.addDefaultConstraint(to: view)
|
||||
if isCloseButtonVisible {
|
||||
view.addSubview(closeButton)
|
||||
closeButton.addDefaultConstraint(to: view)
|
||||
}
|
||||
|
||||
containerView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
@@ -145,6 +179,20 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
mapView.bottomAnchor.constraint(equalTo: (isFilterViewVisible ? filtersView : containerView).topAnchor).isActive = true
|
||||
|
||||
extraUI()
|
||||
}
|
||||
|
||||
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
|
||||
@@ -152,6 +200,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
configureUI()
|
||||
registerMapAnnotationViews()
|
||||
loadDataSource()
|
||||
}
|
||||
@@ -167,6 +216,16 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
/// Add extra UI not available in the base class
|
||||
func extraUI() {
|
||||
// nope, subclass will implement some logic
|
||||
}
|
||||
|
||||
/// Configure UI after view initialization
|
||||
func configureUI() {
|
||||
// nope, subclass will implement some logic
|
||||
}
|
||||
|
||||
/// Load data to display on the map
|
||||
func loadDataSource() {
|
||||
// nope, subclass will implement some logic
|
||||
@@ -219,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)
|
||||
@@ -230,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() {
|
||||
@@ -249,7 +339,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
self.applyFilter(filter)
|
||||
}))
|
||||
}
|
||||
sheet.addAction(UIAlertAction(title: NSLocalizedString("options_cancel", comment: ""), style: .cancel, handler: nil))
|
||||
sheet.addAction(UIAlertAction(title: NSLocalizedString("status_cancel", comment: ""), style: .cancel, handler: nil))
|
||||
present(sheet, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@@ -267,6 +357,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
}
|
||||
|
||||
let annotationView = setupAnnotationView(for: annotation, on: mapView)
|
||||
annotationView?.zPriority = zPriority(for: annotation)
|
||||
return annotationView
|
||||
}
|
||||
|
||||
@@ -277,7 +368,5 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
||||
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
|
||||
guard let annotation = view.annotation else { return }
|
||||
didTapAnnotation(annotation)
|
||||
|
||||
mapView.deselectAnnotation(annotation, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
import Shogun
|
||||
|
||||
class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
|
||||
|
||||
@@ -243,7 +244,7 @@ class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
|
||||
}
|
||||
|
||||
private func navigateToSubscriptions() {
|
||||
let controller = SubscriptionsViewController.makeController()
|
||||
let controller = SubscriptionsViewController()
|
||||
let navigationController = UINavigationController(rootViewController: controller)
|
||||
present(navigationController, animated: true)
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
//
|
||||
// Costanti+Extensions.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 06/03/21.
|
||||
// Copyright © 2021 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
extension EQNPastquakeIntensity {
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .mild: return NSLocalizedString("widget_mild", comment: "")
|
||||
case .strong: return NSLocalizedString("widget_strong", comment: "")
|
||||
case .veryStrong: return NSLocalizedString("widget_verystring", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
/// Stampa le risposte delle chiamate al server
|
||||
static BOOL const EQNDebugPrintResponse = NO;
|
||||
/// Visualizza schermata con info di debug (ex. push token)
|
||||
static BOOL const EQNEnableDebugView = NO;
|
||||
|
||||
#pragma mark - Urls
|
||||
|
||||
@@ -31,11 +33,9 @@ static double const EQNMathKelvin = 273.15;
|
||||
#pragma mark - Server APIs
|
||||
|
||||
/// Download reti sismiche
|
||||
static NSString * const EQNServerUrlDownloadRetiSismiche = @"https://srv.earthquakenetwork.it/distquake_download_automatic18%@.php";
|
||||
static NSString * const EQNServerUrlDownloadRetiSismiche = @"https://cache.earthquakenetwork.it/distquake_download_automatic21.php%@";
|
||||
/// Recupera il tempo ancora disponibile per la versione Pro scontata
|
||||
static NSString * const EQNServerUrlOfferTimeRemaining = @"https://srv.earthquakenetwork.it/distquake_download_offer_time_remaining.php";
|
||||
/// Recupera il numero di sottoscrizioni ancora disponibili per ogni prodotto
|
||||
static NSString * const EQNServerUrlAvailableSubscriptionsCounter = @"https://srv.earthquakenetwork.it/distquake_count_top_redis.php";
|
||||
/// Registra l'abbonamento acquistato dall'utente
|
||||
static NSString * const EQNServerUrlRegisterSubscription = @"https://srv.earthquakenetwork.it/distquake_upload_subscription.php";
|
||||
/// Carica le impostazioni delle notifiche definite dall'utente
|
||||
@@ -47,15 +47,15 @@ static NSString * const EQNServerUrlUserLocation = @"https://srv.earthquakenetwo
|
||||
|
||||
static NSString * const EQNServerUrlCalibration = @"https://srv.earthquakenetwork.it/distquake_upload4.php";
|
||||
// download rete smartphone
|
||||
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://srv.earthquakenetwork.it/distquake_count_redis.php";
|
||||
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://cache.earthquakenetwork.it/distquake_count_redis3.php";
|
||||
// download area check
|
||||
static NSString * const EQNServerUrlDownloadAreaCheck = @"https://srv.earthquakenetwork.it/distquake_download_areacheck.php";
|
||||
// download pastquakes
|
||||
static NSString * const EQNServerUrlDownloadPastQuakes = @"https://srv.earthquakenetwork.it/distquake_download_pastquakes.php";
|
||||
// download segnalazioni
|
||||
static NSString * const EQNServerUrlDownloadUserReports = @"https://srv.earthquakenetwork.it/distquake_download_manual.php";
|
||||
static NSString * const EQNServerUrlDownloadUserReports = @"https://cache.earthquakenetwork.it/distquake_download_manual3.php";
|
||||
// Invio segnalazione
|
||||
static NSString * const EQNServerUrlSendUserReport = @"https://srv.earthquakenetwork.it/distquake_upload_manual3.php";
|
||||
static NSString * const EQNServerUrlSendUserReport = @"https://srv.earthquakenetwork.it/distquake_upload_manual4.php";
|
||||
static NSString * const EQNServerUrlSendUserReportMessage = @"https://srv.earthquakenetwork.it/distquake_upload_manual_message.php";
|
||||
/// Effettua un test delle notifiche push
|
||||
static NSString * const EQNServerUrlTestAlarm = @"https://srv.earthquakenetwork.it/distquake_upload_testalarm.php";
|
||||
@@ -64,23 +64,9 @@ static NSString * const EQNServerUrlAlertSimulator = @"https://srv.earthquakenet
|
||||
|
||||
#pragma mark - UserDefaults Keys
|
||||
|
||||
static NSString * const EQNUserDefaultAppGroupSuite = @"group.com.finazzi.distquake";
|
||||
|
||||
static NSString * const EQNUserDefaultKeyAlertsShowAllCards = @"EQNetwork.AlertsShowAllCards";
|
||||
static NSString * const EQNUserDefaultKeySesmicInformations = @"EQNetwork.SeismicInformations";
|
||||
static NSString * const EQNUserDefaultKeyOneShotShowCountry = @"EQNetwork.OneShot.CountrySelection";
|
||||
static NSString * const EQNUserDefaultLastLocation = @"EQNLast_Location";
|
||||
static NSString * const EQNUserDefaultSeismicNetworkCards = @"EQNData.RetiSismiche";
|
||||
/// Numero di aperture dell'app per sbloccare la versione Pro scontata
|
||||
static NSString * const EQNUserDefaultProDiscountOpenCounter = @"CONTEGGIO_APERTURE_PER_SCONTO";
|
||||
/// Prezzo scontato per la versione pro scaduto
|
||||
static NSString * const EQNUserDefaultProDiscountExpired = @"PREZZO_SCONTATO_SCADUTO";
|
||||
/// Token Firebase dell'utente corrente
|
||||
static NSString * const EQNUserDefaultUserFirebaseToken = @"EQNToken_User";
|
||||
/// Server user ID dell'utente corrente
|
||||
static NSString * const EQNUserDefaultUserId = @"EQNUSER_ID";
|
||||
/// Token delle notifiche push
|
||||
static NSString * const EQNUserDefaultPushToken = @"EQNetwork.PushToken";
|
||||
|
||||
#pragma mark - NSNotification
|
||||
|
||||
@@ -102,10 +88,10 @@ static NSNotificationName const EQNDebugLogWillUpdateNotification = @"EQNDebugLo
|
||||
#pragma mark - Other constants
|
||||
|
||||
static NSTimeInterval const EQNSeismicDataRefreshInterval = 120.0;
|
||||
/// Tempo di attesa (minuti) tra l'invio di due segnalazioni
|
||||
static NSTimeInterval const EQNSendReportDelayBetweenMessages = 5.0;
|
||||
/// Tempo di attesa (minuti) per l'invio di due commenti
|
||||
static NSTimeInterval const EQNSendReportDelayBetweenComments = 30.0;
|
||||
/// Tempo di attesa (secondi) tra l'invio di due segnalazioni
|
||||
static NSTimeInterval const EQNSendReportDelayBetweenMessages = 300.0; // 5 minuti
|
||||
/// Tempo di attesa (secondi) per l'invio di due commenti
|
||||
static NSTimeInterval const EQNSendReportDelayBetweenComments = 900.0; // 30 minuti
|
||||
|
||||
#ifdef DEBUG
|
||||
static NSString * const EQNAdMobAppIdAdaptiveBanner = @"ca-app-pub-3940256099942544/2934735716"; // test
|
||||
@@ -182,53 +168,7 @@ typedef enum : NSInteger {
|
||||
nonCalibrato
|
||||
} EQNStatoCal;
|
||||
|
||||
//////////////////////////////////////// SEGNALAZIONE MANUALE TERREMOTI ////////////////////////////////////////
|
||||
#define CODE_MESSAGE_EQN @"CODE_MESSAGE_EQN"
|
||||
#define DATA_MESSAGE_EQN @"DATA_MESSAGE_EQN"
|
||||
|
||||
/////////////////// Segnalazioni Utente ////////////////////////////
|
||||
#define NOTIFICHE_SU_DISTANZA_POSIZIONE @"NOTIFICHE_SU_DISTANZA_POSIZIONE"
|
||||
#define NOTIFICHE_SU_ATTIVA_SEGNALAZIONE_UTENTE @"NOTIFICHE_SU_ATTIVA_SEGNALAZIONE_UTENTE"
|
||||
|
||||
/////////////////// Reti sismiche ////////////////////////////
|
||||
#define NOTIFICHE_DISTANZA_POSIZIONE_RETI_SISMICHE @"NOTIFICHE_DISTANZA_POSIZIONE_RETI_SISMICHE"
|
||||
#define NOTIFICHE_ATTIVA_RETI_SISMICHE @"NOTIFICHE_ATTIVA_RETI_SISMICHE"
|
||||
#define NOTIFICHE_ATTIVA_RETI_SISMICHE_VICINE @"NOTIFICHE_ATTIVA_RETI_SISMICHE_VICINE"
|
||||
#define NOTIFICHE_ATTIVA_RETI_TERREMOTI_FORTI @"NOTIFICHE_ATTIVA_RETI_TERREMOTI_FORTI"
|
||||
#define NOTIFICHE_ATTIVA_RETI_ENERGIA_SISMI @"NOTIFICHE_ATTIVA_RETI_ENERGIA_SISMI"
|
||||
#define NOTIFICHE_ATTIVA_RETI_ENERGIA_FORTI @"NOTIFICHE_ATTIVA_RETI_ENERGIA_FORTI"
|
||||
#define NOTIFICHE_ATTIVA_RETI_LISTA_ENTI @"NOTIFICHE_ATTIVA_RETI_LISTA_ENTI"
|
||||
|
||||
// Sigla della rete sismica selezionata
|
||||
#define IMPOSTAZIONE_NAZIONE_RETI_SISMICHE @"IMPOSTAIONE_NAZIONE_RETI_SISMICHE"
|
||||
|
||||
#define IMPOSTAZIONE_ENTI_RETI_SISMICHEI @"IMPOSTAZIONE_ENTI_RETI_SISMICHEI"
|
||||
|
||||
/////////////////// Allera sismica ////////////////////////////
|
||||
#define NOTIFICHE_ALLERA_SISMICA_ABILITATO @"NOTIFICHE_ALLERA_SISMICA_ABILITATO"
|
||||
#define NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS @"NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS"
|
||||
#define NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE @"NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE"
|
||||
#define NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI @"NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI"
|
||||
#define NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI @"NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI"
|
||||
#define NOTIFICHE_ALLERA_SISMICA_IMPOSTA_VOLUME @"NOTIFICHE_ALLERA_SISMICA_IMPOSTA_VOLUME"
|
||||
#define NOTIFICHE_ALLERA_SISMICA_TESTA_ALLARME @"NOTIFICHE_ALLERA_SISMICA_TESTA_ALLARME"
|
||||
#define NOTIFICHE_ALLERA_SISMICA_ABILITA_INTERVALLO @"NOTIFICHE_ALLERA_SISMICA_ABILITA_INTERVALLO"
|
||||
#define NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO @"NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
|
||||
#define NOTIFICHE_ALLERA_SISMICA_ORA_FINE @"NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
|
||||
|
||||
// NOTIFICHE RETE SMARTPHONE
|
||||
#define NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA @"NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA"
|
||||
#define NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA @"NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA"
|
||||
#define TEMPO_VISUALIZZAZIONE_NOTIFICA 10800
|
||||
|
||||
|
||||
// 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"
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
#import "Earthquake_Network-Swift.h"
|
||||
#import <Earthquake_Network-Swift.h>
|
||||
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user