Compare commits
303 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bf6b75dac | |||
| 69b83e9944 | |||
| 5061e36a45 | |||
| 8919f3c08f | |||
| 9cf9ef8a64 | |||
| 9ee3a478f0 | |||
| 8744595b56 | |||
| fa05d6b5c4 | |||
| 471ccc9e4a | |||
| 55de6f5ba0 | |||
| b12a9cc476 | |||
| a2f740b0a8 | |||
| 9cf93e652b | |||
| 2d23056ba8 | |||
| cb6ecca774 | |||
| 96286a49f6 | |||
| 481e8a28c0 | |||
| 286a4062f5 | |||
| 01a8ad7419 | |||
| 6e97e9bd2c | |||
| af6e94efcb | |||
| 5387758449 | |||
| 054603b42d | |||
| caf0e3b7cc | |||
| 4c35c38cc5 | |||
| 521254c8c1 | |||
| 78a1710584 | |||
| b2a54a544c | |||
| 0f5ad24744 | |||
| 0296cd50cd | |||
| 7551988b4e | |||
| 5edcaaad99 | |||
| b12f83680a | |||
| ee827c41ae | |||
| d0d06394f0 | |||
| b933b900ed | |||
| 0e7de44332 | |||
| 547bb794f0 | |||
| 9b1f1f12d2 | |||
| 7fc324367d | |||
| 3cb712f709 | |||
| 993e2924c7 | |||
| a167c989cc | |||
| 1b50f4fd17 | |||
| 0003b4607c | |||
| 85c9f333ce | |||
| 217cbfd4e3 | |||
| 5d8de1fb36 | |||
| f23bb78ceb | |||
| 0d91954614 | |||
| 49f5fa91fe | |||
| 68e560768b | |||
| 3e9c319b50 | |||
| d35e0e1b4a | |||
| 6ede137ef7 | |||
| c94195d48e | |||
| 28919d7b72 | |||
| a239534b91 | |||
| 226342f36c | |||
| ca6afbec5f | |||
| 465d3e8013 | |||
| a7e88b43f5 | |||
| 57ef877846 | |||
| c44d97b9fb | |||
| fd4ed7f66f | |||
| ef5db97854 | |||
| ce0e17a0c5 | |||
| 2a46f1d2d6 | |||
| 93871f0358 | |||
| 3e8fe0680d | |||
| 6be5f72360 | |||
| ccd1b9de59 | |||
| 5737eb5b02 | |||
| c549bb6ea5 | |||
| ff80905033 | |||
| dad2bc5648 | |||
| 10c74e278e | |||
| 96dbf960d2 | |||
| 81bfdd02a6 | |||
| 2ab3267981 | |||
| 48b6941ed5 | |||
| 669cb3c4f3 | |||
| 638d819d35 | |||
| a9884d8a8d | |||
| 2ef3560011 | |||
| 05093bb7a4 | |||
| 55f84ab46d | |||
| 03b4d0ddd6 | |||
| 3c5f26bc94 | |||
| 8c79d45b19 | |||
| 931d04c5e1 | |||
| 4d62fbbbd3 | |||
| 1c7065ece7 | |||
| 6dfa51e013 | |||
| b8b21d1458 | |||
| 88317f79e8 | |||
| 4e1147e782 | |||
| 579969d507 | |||
| 4d991d9a10 | |||
| 41491b5ee7 | |||
| 197b375c28 | |||
| f41e6b50ec | |||
| 796e4b5895 | |||
| e43a93979d | |||
| ef1aaa7d71 | |||
| 22d78baa8a | |||
| e4588aa731 | |||
| 07764f91ed | |||
| a0a238e384 | |||
| e61a45f78f | |||
| 0fdc60b938 | |||
| 5f02e2b8bb | |||
| b17a57b98e | |||
| 2379077272 | |||
| 78f0cfb2fa | |||
| f6bfe3fca0 | |||
| d5ab49b807 | |||
| b8bd547d65 | |||
| 547c503726 | |||
| 234622bcfd | |||
| 589466c8c6 | |||
| 2e7742951e | |||
| 3ed77ff1af | |||
| c98530fc54 | |||
| 98cc7e7c4c | |||
| 4db0bb6316 | |||
| 8c3f2dad6d | |||
| e0f346a4dc | |||
| eac0f8249e | |||
| d7c691101c | |||
| 49edbe1a14 | |||
| c5b3750ee7 | |||
| 98fb65a640 | |||
| c20041127b | |||
| 3995c29b22 | |||
| dfa07d0d10 | |||
| ce6fbb24ff | |||
| 382dcfa794 | |||
| d46a2e1559 | |||
| b0d1cde42b | |||
| d8612e33a3 | |||
| 73826d7520 | |||
| 3f57ac9b96 | |||
| 54c78aac0f | |||
| 975f5ed5bc | |||
| 52142486cf | |||
| b4b676ca8d | |||
| dd9ef878e2 | |||
| 5b978e535c | |||
| 242c15ba58 | |||
| a224837dcb | |||
| a21c16a01c | |||
| 1496f25251 | |||
| ad6eb6619c | |||
| f9a8dffad5 | |||
| a708a0f79a | |||
| 49431a760c | |||
| a57e883409 | |||
| b373dc1d60 | |||
| 01f1df9c01 | |||
| 72441d0532 | |||
| a4afb84e6d | |||
| 45a59e30ba | |||
| dac13acb9e | |||
| a9e264d666 | |||
| e64aaf2469 | |||
| 30c7536d4c | |||
| 70e82a67b1 | |||
| f020ac70a1 | |||
| dc4ccd796d | |||
| f66d6558b5 | |||
| 536ed32fb9 | |||
| 2e1a2a8e04 | |||
| 527132b7eb | |||
| 8cf69a9d12 | |||
| 6cba42994d | |||
| fd7821c083 | |||
| 5fab419d0e | |||
| befe46465b | |||
| 5e6ee892ce | |||
| 79d4b3b3bd | |||
| 357bdd47e3 | |||
| 1e4dd507da | |||
| bdfcb7a5c4 | |||
| 40fcb4707f | |||
| 37f9a856b1 | |||
| f42b9f1b53 | |||
| 4fd9966435 | |||
| df2c0a94a4 | |||
| cdd1a8d875 | |||
| 31f1cb5f35 | |||
| d426f15c8e | |||
| 037a74061d | |||
| ea6172226d | |||
| ec94db29b9 | |||
| 91a9bce03c | |||
| f54f4a2312 | |||
| a959df7cd9 | |||
| e95a93ff2c | |||
| b7c1f7379d | |||
| 0f71e0fea9 | |||
| 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 |
@@ -1,3 +1,6 @@
|
|||||||
|
# MacOS files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# Exclude Pods
|
# Exclude Pods
|
||||||
Sources/Pods
|
Sources/Pods
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,73 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 5.10
|
||||||
|
- Usato endpoint cache per distquake_download_areacheck
|
||||||
|
- Aggiunta impostazione per non riprodurre suono notifiche per simsi deboli
|
||||||
|
|
||||||
|
## Versione 5.9.1
|
||||||
|
- Corretto ordinamento in sottoscrizioni attive (prima Top10k)
|
||||||
|
- Modificato parsing per risolvere crash con valori nulli
|
||||||
|
|
||||||
|
## Versione 5.9
|
||||||
|
- Aggiunta barra laterale in lista sismi
|
||||||
|
- Aggiunto filtro "sismi percepiti"
|
||||||
|
- Aggiunta "Mappa intensità"
|
||||||
|
|
||||||
|
## Versione 5.8.1
|
||||||
|
- Corrette traduzioni errate (causavano crash)
|
||||||
|
- Aggiunto ordinamento in sottoscrizioni utente (prima Top10k)
|
||||||
|
|
||||||
|
## Versione 5.8
|
||||||
|
- Modifica algoritmo filtro lista (issue #70)
|
||||||
|
- Modifica impostazioni "Notifiche da reti sismiche" (issue #66)
|
||||||
|
- Modifica invio impostazioni app per notifiche (issue #68)
|
||||||
|
- Modifica impostazioni "Notifiche segnalazioni utente" (issue #67)
|
||||||
|
- Modifica tab Reti Sismiche (issue #69)
|
||||||
|
|
||||||
|
## Versione 5.7
|
||||||
|
- Aumentato target ad iOS 13
|
||||||
|
- Disattivata logica calibrazione/monitoraggio
|
||||||
|
- Aggiunto invio posizione in background
|
||||||
|
|
||||||
|
## Versione 5.6
|
||||||
|
- Aggiunta lingua araba
|
||||||
|
|
||||||
|
## 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
|
## Versione 5.2
|
||||||
|
|
||||||
- Nuova schermata per allerta in tempo reale
|
- Nuova schermata per allerta in tempo reale
|
||||||
|
|||||||
@@ -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": {
|
"aps": {
|
||||||
"alert": {
|
"alert": {
|
||||||
"loc-args": [
|
"loc-args": [
|
||||||
"150 km (Test)"
|
"15 χλμ από το Αίγιο"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Rilevato sisma forte a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"sound": "alert_star_trek.wav"
|
"sound": "alert_star_trek.wav"
|
||||||
},
|
},
|
||||||
"counter": 10,
|
"counter": 10,
|
||||||
"datetime": "2022-06-22 16:15:00",
|
"datetime": "2022-06-23 10:10:00",
|
||||||
|
"delay": 4,
|
||||||
"detection_latitude": "37.983810",
|
"detection_latitude": "37.983810",
|
||||||
"detection_longitude": "23.727539",
|
"detection_longitude": "23.727539",
|
||||||
"gcm.message_id": "1614708857742608",
|
"gcm.message_id": "1614708857742608",
|
||||||
@@ -22,12 +23,12 @@
|
|||||||
"google.c.sender.id": "899482329945",
|
"google.c.sender.id": "899482329945",
|
||||||
"intensity": 2,
|
"intensity": 2,
|
||||||
"latitude": "38.19",
|
"latitude": "38.19",
|
||||||
"location": "150 km (Test)",
|
"location": "15 χλμ από το Αίγιο",
|
||||||
"longitude": "22.26",
|
"longitude": "22.26",
|
||||||
"peak": "-1",
|
"peak": "0.4",
|
||||||
"randcode": 0,
|
"randcode": 100,
|
||||||
"test": 1,
|
"test": 0,
|
||||||
"type": "eqn",
|
"type": "eqn",
|
||||||
"wave_speed": "4.7",
|
"wave_speed": "4.7",
|
||||||
"critical": true
|
"critical": true
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"aps": {
|
"aps": {
|
||||||
"alert": {
|
"alert": {
|
||||||
"loc-args": [
|
"loc-args": [
|
||||||
"150 km (Test)"
|
"14 km from California City"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Rilevato sisma forte a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"sound": "alert_star_trek.wav"
|
"sound": "alert_star_trek.wav"
|
||||||
},
|
},
|
||||||
"counter": 10,
|
"counter": 10,
|
||||||
"datetime": "2022-06-22 16:15:00",
|
"datetime": "2022-06-23 07:55:00",
|
||||||
|
"delay": 4,
|
||||||
"detection_latitude": "37.3229978",
|
"detection_latitude": "37.3229978",
|
||||||
"detection_longitude": "-122.0321823",
|
"detection_longitude": "-122.0321823",
|
||||||
"gcm.message_id": "1614708857742608",
|
"gcm.message_id": "1614708857742608",
|
||||||
@@ -22,11 +23,11 @@
|
|||||||
"google.c.sender.id": "899482329945",
|
"google.c.sender.id": "899482329945",
|
||||||
"intensity": 2,
|
"intensity": 2,
|
||||||
"latitude": "35.15",
|
"latitude": "35.15",
|
||||||
"location": "150 km (Test)",
|
"location": "14 km from California City",
|
||||||
"longitude": "-117.78",
|
"longitude": "-117.78",
|
||||||
"peak": "-1",
|
"peak": "0.4",
|
||||||
"randcode": 0,
|
"randcode": 100,
|
||||||
"test": 1,
|
"test": 0,
|
||||||
"type": "eqn",
|
"type": "eqn",
|
||||||
"wave_speed": "4.7",
|
"wave_speed": "4.7",
|
||||||
"critical": true
|
"critical": true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"aps": {
|
"aps": {
|
||||||
"alert": {
|
"alert": {
|
||||||
"loc-args": [
|
"loc-args": [
|
||||||
"150 km (Test)"
|
"5 km de Las Cujas"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Rilevato sisma forte a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"sound": "alert_star_trek.wav"
|
"sound": "alert_star_trek.wav"
|
||||||
},
|
},
|
||||||
"counter": 10,
|
"counter": 10,
|
||||||
"datetime": "2022-06-22 16:15:00",
|
"datetime": "2022-06-23 10:19:00",
|
||||||
|
"delay": 4,
|
||||||
"detection_latitude": "-34.603722",
|
"detection_latitude": "-34.603722",
|
||||||
"detection_longitude": "-58.381592",
|
"detection_longitude": "-58.381592",
|
||||||
"gcm.message_id": "1614708857742608",
|
"gcm.message_id": "1614708857742608",
|
||||||
@@ -22,11 +23,11 @@
|
|||||||
"google.c.sender.id": "899482329945",
|
"google.c.sender.id": "899482329945",
|
||||||
"intensity": 2,
|
"intensity": 2,
|
||||||
"latitude": "-32.57",
|
"latitude": "-32.57",
|
||||||
"location": "150 km (Test)",
|
"location": "5 km de Las Cujas",
|
||||||
"longitude": "-71.46",
|
"longitude": "-71.46",
|
||||||
"peak": "-1",
|
"peak": "0.4",
|
||||||
"randcode": 0,
|
"randcode": 100,
|
||||||
"test": 1,
|
"test": 0,
|
||||||
"type": "eqn",
|
"type": "eqn",
|
||||||
"wave_speed": "4.7",
|
"wave_speed": "4.7",
|
||||||
"critical": true
|
"critical": true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"aps": {
|
"aps": {
|
||||||
"alert": {
|
"alert": {
|
||||||
"loc-args": [
|
"loc-args": [
|
||||||
"150 km (Test)"
|
"15 km de Dieppe"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Rilevato sisma forte a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"sound": "alert_star_trek.wav"
|
"sound": "alert_star_trek.wav"
|
||||||
},
|
},
|
||||||
"counter": 10,
|
"counter": 10,
|
||||||
"datetime": "2022-06-22 16:15:00",
|
"datetime": "2022-06-23 10:07:00",
|
||||||
|
"delay": 4,
|
||||||
"detection_latitude": "48.856614",
|
"detection_latitude": "48.856614",
|
||||||
"detection_longitude": "2.8522219",
|
"detection_longitude": "2.8522219",
|
||||||
"gcm.message_id": "1614708857742608",
|
"gcm.message_id": "1614708857742608",
|
||||||
@@ -22,11 +23,11 @@
|
|||||||
"google.c.sender.id": "899482329945",
|
"google.c.sender.id": "899482329945",
|
||||||
"intensity": 2,
|
"intensity": 2,
|
||||||
"latitude": "49.77",
|
"latitude": "49.77",
|
||||||
"location": "150 km (Test)",
|
"location": "15 km de Dieppe",
|
||||||
"longitude": "1.05",
|
"longitude": "1.05",
|
||||||
"peak": "-1",
|
"peak": "0.4",
|
||||||
"randcode": 0,
|
"randcode": 100,
|
||||||
"test": 1,
|
"test": 0,
|
||||||
"type": "eqn",
|
"type": "eqn",
|
||||||
"wave_speed": "4.7",
|
"wave_speed": "4.7",
|
||||||
"critical": true
|
"critical": true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"aps": {
|
"aps": {
|
||||||
"alert": {
|
"alert": {
|
||||||
"loc-args": [
|
"loc-args": [
|
||||||
"150 km (Test)"
|
"5 km od Novog"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Rilevato sisma forte a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"sound": "alert_star_trek.wav"
|
"sound": "alert_star_trek.wav"
|
||||||
},
|
},
|
||||||
"counter": 10,
|
"counter": 10,
|
||||||
"datetime": "2022-06-22 16:15:00",
|
"datetime": "2022-06-23 10:02:00",
|
||||||
|
"delay": 4,
|
||||||
"detection_latitude": "45.813177",
|
"detection_latitude": "45.813177",
|
||||||
"detection_longitude": "15.977048",
|
"detection_longitude": "15.977048",
|
||||||
"gcm.message_id": "1614708857742608",
|
"gcm.message_id": "1614708857742608",
|
||||||
@@ -22,11 +23,11 @@
|
|||||||
"google.c.sender.id": "899482329945",
|
"google.c.sender.id": "899482329945",
|
||||||
"intensity": 2,
|
"intensity": 2,
|
||||||
"latitude": "45.09",
|
"latitude": "45.09",
|
||||||
"location": "150 km (Test)",
|
"location": "5 km od Novog",
|
||||||
"longitude": "14.87",
|
"longitude": "14.87",
|
||||||
"peak": "-1",
|
"peak": "0.4",
|
||||||
"randcode": 0,
|
"randcode": 100,
|
||||||
"test": 1,
|
"test": 0,
|
||||||
"type": "eqn",
|
"type": "eqn",
|
||||||
"wave_speed": "4.7",
|
"wave_speed": "4.7",
|
||||||
"critical": true
|
"critical": true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"aps": {
|
"aps": {
|
||||||
"alert": {
|
"alert": {
|
||||||
"loc-args": [
|
"loc-args": [
|
||||||
"150 km (Test)"
|
"35 km dari Sindanbarang"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Rilevato sisma forte a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"sound": "alert_star_trek.wav"
|
"sound": "alert_star_trek.wav"
|
||||||
},
|
},
|
||||||
"counter": 10,
|
"counter": 10,
|
||||||
"datetime": "2022-06-22 16:15:00",
|
"datetime": "2022-06-23 10:13:00",
|
||||||
|
"delay": 4,
|
||||||
"detection_latitude": "-2.548926",
|
"detection_latitude": "-2.548926",
|
||||||
"detection_longitude": "118.0148634",
|
"detection_longitude": "118.0148634",
|
||||||
"gcm.message_id": "1614708857742608",
|
"gcm.message_id": "1614708857742608",
|
||||||
@@ -22,11 +23,11 @@
|
|||||||
"google.c.sender.id": "899482329945",
|
"google.c.sender.id": "899482329945",
|
||||||
"intensity": 2,
|
"intensity": 2,
|
||||||
"latitude": "-7.38",
|
"latitude": "-7.38",
|
||||||
"location": "150 km (Test)",
|
"location": "35 km dari Sindanbarang",
|
||||||
"longitude": "106.83",
|
"longitude": "106.83",
|
||||||
"peak": "-1",
|
"peak": "0.4",
|
||||||
"randcode": 0,
|
"randcode": 100,
|
||||||
"test": 1,
|
"test": 0,
|
||||||
"type": "eqn",
|
"type": "eqn",
|
||||||
"wave_speed": "4.7",
|
"wave_speed": "4.7",
|
||||||
"critical": true
|
"critical": true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"aps": {
|
"aps": {
|
||||||
"alert": {
|
"alert": {
|
||||||
"loc-args": [
|
"loc-args": [
|
||||||
"150 km (Test)"
|
"2 km da Foligno"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Rilevato sisma forte a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"sound": "alert_star_trek.wav"
|
"sound": "alert_star_trek.wav"
|
||||||
},
|
},
|
||||||
"counter": 10,
|
"counter": 10,
|
||||||
"datetime": "2022-06-22 16:15:00",
|
"datetime": "2022-06-22 19:12:00",
|
||||||
|
"delay": 4,
|
||||||
"detection_latitude": "45.64664",
|
"detection_latitude": "45.64664",
|
||||||
"detection_longitude": "9.188540",
|
"detection_longitude": "9.188540",
|
||||||
"gcm.message_id": "1614708857742608",
|
"gcm.message_id": "1614708857742608",
|
||||||
@@ -22,11 +23,11 @@
|
|||||||
"google.c.sender.id": "899482329945",
|
"google.c.sender.id": "899482329945",
|
||||||
"intensity": 2,
|
"intensity": 2,
|
||||||
"latitude": "42.958",
|
"latitude": "42.958",
|
||||||
"location": "150 km (Test)",
|
"location": "2 km da Foligno",
|
||||||
"longitude": "12.702",
|
"longitude": "12.702",
|
||||||
"peak": "-1",
|
"peak": "0.4",
|
||||||
"randcode": 0,
|
"randcode": 100,
|
||||||
"test": 1,
|
"test": 0,
|
||||||
"type": "eqn",
|
"type": "eqn",
|
||||||
"wave_speed": "4.7",
|
"wave_speed": "4.7",
|
||||||
"critical": true
|
"critical": true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"aps": {
|
"aps": {
|
||||||
"alert": {
|
"alert": {
|
||||||
"loc-args": [
|
"loc-args": [
|
||||||
"150 km (Test)"
|
"45 km de Puebla"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Rilevato sisma forte a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"sound": "alert_star_trek.wav"
|
"sound": "alert_star_trek.wav"
|
||||||
},
|
},
|
||||||
"counter": 10,
|
"counter": 10,
|
||||||
"datetime": "2022-06-22 16:15:00",
|
"datetime": "2022-06-23 10:16:00",
|
||||||
|
"delay": 4,
|
||||||
"detection_latitude": "19.432608",
|
"detection_latitude": "19.432608",
|
||||||
"detection_longitude": "-99.133209",
|
"detection_longitude": "-99.133209",
|
||||||
"gcm.message_id": "1614708857742608",
|
"gcm.message_id": "1614708857742608",
|
||||||
@@ -22,11 +23,11 @@
|
|||||||
"google.c.sender.id": "899482329945",
|
"google.c.sender.id": "899482329945",
|
||||||
"intensity": 2,
|
"intensity": 2,
|
||||||
"latitude": "18.72",
|
"latitude": "18.72",
|
||||||
"location": "150 km (Test)",
|
"location": "45 km de Puebla",
|
||||||
"longitude": "-97.90",
|
"longitude": "-97.90",
|
||||||
"peak": "-1",
|
"peak": "0.4",
|
||||||
"randcode": 0,
|
"randcode": 100,
|
||||||
"test": 1,
|
"test": 0,
|
||||||
"type": "eqn",
|
"type": "eqn",
|
||||||
"wave_speed": "4.7",
|
"wave_speed": "4.7",
|
||||||
"critical": true
|
"critical": true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"aps": {
|
"aps": {
|
||||||
"alert": {
|
"alert": {
|
||||||
"loc-args": [
|
"loc-args": [
|
||||||
"150 km (Test)"
|
"Bandırma'ya 25 km"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Rilevato sisma forte a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"sound": "alert_star_trek.wav"
|
"sound": "alert_star_trek.wav"
|
||||||
},
|
},
|
||||||
"counter": 10,
|
"counter": 10,
|
||||||
"datetime": "2022-06-22 16:15:00",
|
"datetime": "2022-06-23 10:22:00",
|
||||||
|
"delay": 4,
|
||||||
"detection_latitude": "41.008238",
|
"detection_latitude": "41.008238",
|
||||||
"detection_longitude": "28.978359",
|
"detection_longitude": "28.978359",
|
||||||
"gcm.message_id": "1614708857742608",
|
"gcm.message_id": "1614708857742608",
|
||||||
@@ -22,12 +23,12 @@
|
|||||||
"google.c.sender.id": "899482329945",
|
"google.c.sender.id": "899482329945",
|
||||||
"intensity": 2,
|
"intensity": 2,
|
||||||
"latitude": "40.33",
|
"latitude": "40.33",
|
||||||
"location": "150 km (Test)",
|
"location": "Bandırma'ya 25 km",
|
||||||
"longitude": "27.66",
|
"longitude": "27.66",
|
||||||
"peak": "-1",
|
"peak": "0.4",
|
||||||
"randcode": 0,
|
"randcode": 100,
|
||||||
"test": 1,
|
"test": 0,
|
||||||
"type": "eqn",
|
"type": "eqn",
|
||||||
"wave_speed": "4.7",
|
"wave_speed": "4.7",
|
||||||
"critical": true
|
"critical": true
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
|
||||||
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
|
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
|
||||||
|
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SmallIdentifier];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReceiveNotification:(UNNotification *)notification
|
- (void)didReceiveNotification:(UNNotification *)notification
|
||||||
@@ -67,9 +68,8 @@
|
|||||||
[self.mapView addAnnotation:annotation];
|
[self.mapView addAnnotation:annotation];
|
||||||
|
|
||||||
} else if ([userInfo[@"type"] isEqualToString:@"manual"]){
|
} else if ([userInfo[@"type"] isEqualToString:@"manual"]){
|
||||||
EQNMapAnnotationUserReport *annotation = [[EQNMapAnnotationUserReport alloc] initWithTitle:@""
|
EQNMapAnnotationUserReport *annotation = [[EQNMapAnnotationUserReport alloc] initWithMagnitude:[userInfo[@"magnitude"] intValue]
|
||||||
coordinate:coordinate.coordinate
|
coordinate:coordinate.coordinate];
|
||||||
magnitude:[userInfo[@"magnitudo"] intValue]];
|
|
||||||
[self.mapView addAnnotation:annotation];
|
[self.mapView addAnnotation:annotation];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
NSTimeInterval difference = MAX([self.userSeismicTimestamp timeIntervalSinceDate:now], 0);
|
NSTimeInterval difference = MAX([self.userSeismicTimestamp timeIntervalSinceDate:now], 0);
|
||||||
NSInteger seconds = (int)lround(difference);
|
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) {
|
if (difference <= 0) {
|
||||||
// stop the countdown
|
// stop the countdown
|
||||||
@@ -118,9 +118,9 @@
|
|||||||
} else if ([annotation isKindOfClass:[EQNMapAnnotationUserReport class]]) {
|
} else if ([annotation isKindOfClass:[EQNMapAnnotationUserReport class]]) {
|
||||||
EQNMapAnnotationUserReport *report = (EQNMapAnnotationUserReport *)annotation;
|
EQNMapAnnotationUserReport *report = (EQNMapAnnotationUserReport *)annotation;
|
||||||
|
|
||||||
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
|
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SmallIdentifier];
|
||||||
annotationView.image = report.image;
|
annotationView.image = [report imageWithHeight:EQNCustomAnnotationView.SmallViewImageHeight];
|
||||||
annotationView.title = report.title;
|
annotationView.title = report.timeDifference;
|
||||||
return annotationView;
|
return annotationView;
|
||||||
}
|
}
|
||||||
return nil;
|
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">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<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>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
<string>com.apple.usernotifications.service</string>
|
<string>com.apple.usernotifications.service</string>
|
||||||
<key>NSExtensionPrincipalClass</key>
|
<key>NSExtensionPrincipalClass</key>
|
||||||
<string>NotificationService</string>
|
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
//
|
|
||||||
// NotificationService+Extension.swift
|
|
||||||
// EQNNotificationService
|
|
||||||
//
|
|
||||||
// Created by Andrea Busi on 28/05/22.
|
|
||||||
// Copyright © 2022 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import UserNotifications
|
|
||||||
|
|
||||||
|
|
||||||
extension NotificationService {
|
|
||||||
|
|
||||||
@objc(removeNotificationsForType:completion:)
|
|
||||||
func removeNotifications(
|
|
||||||
for type: String?,
|
|
||||||
completion: @escaping() -> Void
|
|
||||||
) {
|
|
||||||
guard let type = type else {
|
|
||||||
completion()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let notificationCenter = UNUserNotificationCenter.current()
|
|
||||||
|
|
||||||
notificationCenter.getDeliveredNotifications { notifications in
|
|
||||||
let sameTypeNotifications = notifications.filter { notification in
|
|
||||||
let payload = notification.request.content.userInfo
|
|
||||||
if let notificationType = payload["type"] as? String {
|
|
||||||
return notificationType == type
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let identifiers = sameTypeNotifications.map { $0.request.identifier }
|
|
||||||
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
|
|
||||||
|
|
||||||
// !! Note: this is a known issue/bug
|
|
||||||
// we need to add a delay before invoking the completion, otherwise the notification will not be remved
|
|
||||||
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
//
|
|
||||||
// NotificationService.h
|
|
||||||
// EQNNotificationService
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UserNotifications/UserNotifications.h>
|
|
||||||
|
|
||||||
@interface NotificationService : UNNotificationServiceExtension
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
//
|
|
||||||
// NotificationService.m
|
|
||||||
// EQNNotificationService
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "NotificationService.h"
|
|
||||||
#import "EQNAllertaSismica.h"
|
|
||||||
#import "Costanti.h"
|
|
||||||
#import "EQNNotificationService-Swift.h"
|
|
||||||
|
|
||||||
|
|
||||||
@interface NotificationService ()
|
|
||||||
|
|
||||||
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
|
|
||||||
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation NotificationService
|
|
||||||
|
|
||||||
static NSString * const EQNSoundNotificationEQN = @"alert_sound.wav";
|
|
||||||
|
|
||||||
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler
|
|
||||||
{
|
|
||||||
self.contentHandler = contentHandler;
|
|
||||||
self.bestAttemptContent = [request.content mutableCopy];
|
|
||||||
|
|
||||||
// Configure the notification's payload.
|
|
||||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
|
||||||
content.title = [NSString localizedUserNotificationStringForKey:request.content.title arguments:nil];
|
|
||||||
content.body = [NSString localizedUserNotificationStringForKey:request.content.body arguments:nil];
|
|
||||||
|
|
||||||
self.bestAttemptContent.title = content.title;
|
|
||||||
self.bestAttemptContent.body = content.body;
|
|
||||||
|
|
||||||
// check for required informations
|
|
||||||
NSDictionary *userInfo = request.content.userInfo;
|
|
||||||
NSString *notificationType = [userInfo objectForKey:@"type"];
|
|
||||||
if (userInfo == nil || notificationType == nil) {
|
|
||||||
[self contentComplete];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
NSString *iconName = @"";
|
|
||||||
if ([notificationType isEqualToString:@"eqn"]) {
|
|
||||||
// check if user has enabled critical alerts
|
|
||||||
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:EQNUserDefaultAppGroupSuite];
|
|
||||||
BOOL criticalAlertsEnabled = [sharedDefaults boolForKey:NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS];
|
|
||||||
|
|
||||||
// !!WORKAROUND
|
|
||||||
// this is a workaround to use critical alerts with legacy FCM api
|
|
||||||
// when the server implementation will be migrated to Firebase v1 APIs, this could be removed
|
|
||||||
BOOL isCritical = [[userInfo objectForKey:@"critical"] boolValue];
|
|
||||||
if (isCritical && criticalAlertsEnabled) {
|
|
||||||
self.bestAttemptContent.sound = [UNNotificationSound criticalSoundNamed:EQNSoundNotificationEQN withAudioVolume:1.0];
|
|
||||||
} else {
|
|
||||||
self.bestAttemptContent.sound = [UNNotificationSound soundNamed:EQNSoundNotificationEQN];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *intensity = [userInfo objectForKey:@"intensity"];
|
|
||||||
switch ([intensity intValue]) {
|
|
||||||
case 0:
|
|
||||||
iconName = @"star_realtime_white.png";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
iconName = @"star_realtime_lightblue.png";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
iconName = @"star_realtime_blue.png";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if ([notificationType isEqualToString:@"manual"]) {
|
|
||||||
NSString *intensity = [userInfo objectForKey:@"magnitude"];
|
|
||||||
switch ([intensity intValue]) {
|
|
||||||
case 0:
|
|
||||||
iconName = @"star_green.png";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
iconName = @"star_yellow.png";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
iconName = @"star_red.png";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if ([notificationType isEqualToString:@"official"]) {
|
|
||||||
NSString *provaider = [userInfo objectForKey:@"provider"];
|
|
||||||
double intensity = [[userInfo objectForKey:@"magnitude"] doubleValue];
|
|
||||||
|
|
||||||
NSString *colore = @"";
|
|
||||||
if (intensity < 2.0) {
|
|
||||||
colore = @"_white";
|
|
||||||
} else if (intensity < 3.5) {
|
|
||||||
colore = @"_green";
|
|
||||||
} else if (intensity < 4.5) {
|
|
||||||
colore = @"_yellow";
|
|
||||||
} else if (intensity < 5.5) {
|
|
||||||
colore = @"_red";
|
|
||||||
} else {
|
|
||||||
colore = @"_purple";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([provaider isEqualToString:@"USGS"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"star%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"SGC"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"star3%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"CSN"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"star3f%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"SSN"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"star4%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"INPRES"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"star4r%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"FUNVISIS"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"star6%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"Ineter"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"triangle%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"RSN"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"triangle2%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"PHIVOLCS"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"triround_inner%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"IGEPN"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"triround%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"INGV"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"circle%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"EMSC"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"dyamond%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"IGP"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"dyamond_round%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"JMA"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"esa%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"GEONET"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"oct%@.png", colore];
|
|
||||||
} if ([provaider isEqualToString:@"CSI"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"penta%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"IGN"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"square%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"UASD"] || [provaider isEqualToString:@"BDTIM"] || [provaider isEqualToString:@"NCS"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"thick_star%@.png", colore];
|
|
||||||
} else if ([provaider isEqualToString:@"RSPR"]) {
|
|
||||||
iconName = [NSString stringWithFormat:@"star6f%@.png", colore];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![iconName isEqualToString:@""]) {
|
|
||||||
iconName = [iconName stringByReplacingOccurrencesOfString:@".png" withString:@""];
|
|
||||||
NSURL *imageUrl = [NSBundle.mainBundle URLForResource:iconName withExtension:@"png"];
|
|
||||||
NSError *attachmentError = nil;
|
|
||||||
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:iconName
|
|
||||||
URL:imageUrl
|
|
||||||
options:nil
|
|
||||||
error:&attachmentError];
|
|
||||||
if (!attachmentError) {
|
|
||||||
self.bestAttemptContent.attachments = @[attachment];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove same type posted notification
|
|
||||||
[self removeNotificationsForType:notificationType completion:^{
|
|
||||||
[self contentComplete];
|
|
||||||
}];;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)serviceExtensionTimeWillExpire
|
|
||||||
{
|
|
||||||
// Called just before the extension will be terminated by the system.
|
|
||||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
|
||||||
[self contentComplete];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)contentComplete
|
|
||||||
{
|
|
||||||
self.contentHandler(self.bestAttemptContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
//
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate intensity and get proper string to display
|
||||||
|
guard let latitude = userInfo.double(forKey: "latitude"),
|
||||||
|
let longitude = userInfo.double(forKey: "longitude"),
|
||||||
|
let peak = userInfo.double(forKey: "peak") else {
|
||||||
|
print("[NotificationService] Unable to get base info for intensity calculation")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let magnitude = userInfo.double(forKey: "mag") ?? 0
|
||||||
|
let location = CLLocation(latitude: latitude, longitude: longitude)
|
||||||
|
guard let distance = EQNUserData.shared.lastLocation?.distance(from: location) else {
|
||||||
|
print("[NotificationService] Unable to calculate distance or get last location")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let distanceKm = distance / 1_000
|
||||||
|
|
||||||
|
// If the shake is mild, user can disale sound and use default notification sound
|
||||||
|
// This logic is done here and not with the rest because distance and other informations are needed
|
||||||
|
let mildQuakeSoundDisabled = UserDefaults.appGroup?.bool(forKey: UserDefaults.AllertaSismicaSuonoDisabilitatoSismaDebole) ?? true
|
||||||
|
let isMildQuake = isMildQuake(magnitude: magnitude, distance: distanceKm)
|
||||||
|
if isMildQuake && mildQuakeSoundDisabled {
|
||||||
|
bestAttemptContent.sound = UNNotificationSound.default
|
||||||
|
}
|
||||||
|
|
||||||
|
let intensita = peak * exp(-distanceKm/peak/250)
|
||||||
|
let stringSuffix = if intensita < 0.004 {
|
||||||
|
"no_shaking"
|
||||||
|
} else if intensita < 0.30 {
|
||||||
|
"mild"
|
||||||
|
} else if intensita < 0.70 {
|
||||||
|
"moderate"
|
||||||
|
} else {
|
||||||
|
"strong"
|
||||||
|
}
|
||||||
|
bestAttemptContent.body = "alert_intensity_\(stringSuffix)".localized
|
||||||
|
|
||||||
|
let intensity = userInfo.integer(forKey: "intensity")
|
||||||
|
switch intensity {
|
||||||
|
case 0:
|
||||||
|
iconName = "star_realtime_white.png"
|
||||||
|
case 1:
|
||||||
|
iconName = "star_realtime_lightblue.png"
|
||||||
|
case 2:
|
||||||
|
iconName = "star_realtime_blue.png"
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case "manual":
|
||||||
|
// there are 12 levels, so a customized icon doesn't make sense
|
||||||
|
// use a generic warning icon instead
|
||||||
|
iconName = "warning_yellow.png"
|
||||||
|
case "official":
|
||||||
|
// 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 removed
|
||||||
|
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isMildQuake(
|
||||||
|
magnitude: Double,
|
||||||
|
distance: Double
|
||||||
|
) -> Bool {
|
||||||
|
var intensity_at_location: Double = 0
|
||||||
|
if distance > 0 {
|
||||||
|
let R: Double = 6371
|
||||||
|
let eq_depth: Double = 10.0
|
||||||
|
let hyp_distance = sqrt(pow(eq_depth, 2) + 4 * R * (R - eq_depth) * pow(sin(distance / (2 * R)), 2))
|
||||||
|
intensity_at_location = -2.15 * log10(hyp_distance) + 1.0 * magnitude + 2.31
|
||||||
|
}
|
||||||
|
return intensity_at_location < 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private func manualIconName(for provider: String, color: String) -> String {
|
||||||
|
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
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "self:Earthquake Network.xcodeproj">
|
location = "self:">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "898a30d298491e1ce821191ebfa2e7d28e7c9ea4c119cbfdfb1b245bc94ac6c3",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "abseil-cpp-binary",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/abseil-cpp-binary.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
|
||||||
|
"version" : "1.2024072200.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "app-check",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/app-check.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
|
||||||
|
"version" : "11.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "dznemptydataset",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/dzenbot/DZNEmptyDataSet",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "9bffa69a83a9fa58a14b3cf43cb6dd8a63774179"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "facebook-ios-sdk",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/facebook/facebook-ios-sdk",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b28dde427715b45a26ebebf697929f4a81b15e04",
|
||||||
|
"version" : "18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "firebase-ios-sdk",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "fbd463894af94d90eb4d6a4e54080459a8179519",
|
||||||
|
"version" : "11.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "googleappmeasurement",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "f7460ea630bddf172115c28493ae8b3798d95ce3",
|
||||||
|
"version" : "11.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "googledatatransport",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/GoogleDataTransport.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
|
||||||
|
"version" : "10.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "googleutilities",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
|
||||||
|
"version" : "8.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "grpc-binary",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/grpc-binary.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
|
||||||
|
"version" : "1.69.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "gtm-session-fetcher",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/gtm-session-fetcher.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
|
||||||
|
"version" : "3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "interop-ios-for-google-sdks",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
|
||||||
|
"version" : "101.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "leveldb",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/firebase/leveldb.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
|
||||||
|
"version" : "1.22.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "nanopb",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/firebase/nanopb.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
|
||||||
|
"version" : "2.30910.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "promises",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/promises.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
|
||||||
|
"version" : "2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "shogun",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/andreabusi-it/Shogun",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "ad890190d6be90f7712c2e56a38ef0937d9f8c1a",
|
||||||
|
"version" : "1.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "solar",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/ceeK/Solar.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "c2b96f2d5fb7f835b91cefac5e83101f54643901",
|
||||||
|
"version" : "3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-package-manager-google-mobile-ads",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "aa24c7dc03bca62c42747314dc8537f15587b50d",
|
||||||
|
"version" : "12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-package-manager-google-user-messaging-platform",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "9b68aa69fb508f0274853e226c734151a973c7b7",
|
||||||
|
"version" : "2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-protobuf",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
|
||||||
|
"version" : "1.26.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1200"
|
LastUpgradeVersion = "1620"
|
||||||
wasCreatedForAppExtension = "YES"
|
wasCreatedForAppExtension = "YES"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
launchAutomaticallySubstyle = "2">
|
launchAutomaticallySubstyle = "2">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1200"
|
LastUpgradeVersion = "1620"
|
||||||
wasCreatedForAppExtension = "YES"
|
wasCreatedForAppExtension = "YES"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
buildForAnalyzing = "YES">
|
buildForAnalyzing = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "8C4B0B7921CACE3F00AED489"
|
BlueprintIdentifier = "65FFDC91292F672B00EA821B"
|
||||||
BuildableName = "EQNNotificationService.appex"
|
BuildableName = "EQNNotificationService.appex"
|
||||||
BlueprintName = "EQNNotificationService"
|
BlueprintName = "EQNNotificationService"
|
||||||
ReferencedContainer = "container:Earthquake Network.xcodeproj">
|
ReferencedContainer = "container:Earthquake Network.xcodeproj">
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
launchAutomaticallySubstyle = "2">
|
launchAutomaticallySubstyle = "2">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1200"
|
LastUpgradeVersion = "1620"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
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 "AppDelegate.h"
|
||||||
#import "Costanti.h"
|
#import "Costanti.h"
|
||||||
#import "ServerRequest.h"
|
|
||||||
#import "EQNGeneratoreURLServer.h"
|
|
||||||
#import "EQNUser.h"
|
#import "EQNUser.h"
|
||||||
#import "EQNAccelerometroManager.h"
|
#import "EQNAccelerometroManager.h"
|
||||||
#import "EQNManager.h"
|
#import "EQNManager.h"
|
||||||
#import "EQNUtility.h"
|
|
||||||
#import "EQNAllertaSismica.h"
|
|
||||||
#import "EQNNotificheSegnalazioniUtente.h"
|
|
||||||
#import "EQNNotificheReteSismiche.h"
|
|
||||||
#import "EQNMainTabBarController.h"
|
#import "EQNMainTabBarController.h"
|
||||||
|
#import "NSDictionary+EQNExtensions.h"
|
||||||
|
|
||||||
@import Firebase;
|
|
||||||
@import FirebaseCrashlytics;
|
|
||||||
@import UserNotifications;
|
@import UserNotifications;
|
||||||
|
@import AppTrackingTransparency;
|
||||||
|
@import FirebaseCore;
|
||||||
|
@import FirebaseMessaging;
|
||||||
|
@import FirebaseCrashlytics;
|
||||||
@import GoogleMobileAds;
|
@import GoogleMobileAds;
|
||||||
|
@import FBSDKCoreKit;
|
||||||
|
|
||||||
@interface AppDelegate () <FIRMessagingDelegate, UNUserNotificationCenterDelegate>
|
@interface AppDelegate () <FIRMessagingDelegate, UNUserNotificationCenterDelegate>
|
||||||
@property (strong, nonatomic) NSArray<id<EQNCommandProtocol>> *commands;
|
@property (strong, nonatomic) NSArray<id<EQNCommandProtocol>> *commands;
|
||||||
@@ -43,29 +41,25 @@
|
|||||||
}];
|
}];
|
||||||
#endif
|
#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
|
// execute commands
|
||||||
EQNStartupCommandsBuilder *builder = [[EQNStartupCommandsBuilder alloc] init];
|
EQNStartupCommandsBuilder *builder = [[EQNStartupCommandsBuilder alloc] init];
|
||||||
self.commands = [builder build];
|
self.commands = [builder build];
|
||||||
[builder executeWithCommands:self.commands];
|
[builder executeWithCommands:self.commands];
|
||||||
|
|
||||||
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
|
[EQNUser defaultUser];
|
||||||
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
|
[EQNManager defaultManager];
|
||||||
[[UNUserNotificationCenter currentNotificationCenter]
|
[self configureFirebase];
|
||||||
requestAuthorizationWithOptions:authOptions
|
[self configureFacebookSDKWithApplication:application andOptions:launchOptions];
|
||||||
completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
// schedule background tasks
|
||||||
[self registraNotifica];
|
[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];
|
[application registerForRemoteNotifications];
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
@@ -86,12 +80,14 @@
|
|||||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
- (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.
|
// 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.
|
// 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;
|
counter += 1;
|
||||||
[[NSUserDefaults standardUserDefaults] setInteger:counter forKey:EQNUserDefaultProDiscountOpenCounter];
|
[[NSUserDefaults standardUserDefaults] setInteger:counter forKey:NSUserDefaults.UserDataProDiscountOpenCounter];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -125,8 +121,8 @@
|
|||||||
{
|
{
|
||||||
NSString *token = [self stringWithDeviceToken:deviceToken];
|
NSString *token = [self stringWithDeviceToken:deviceToken];
|
||||||
NSLog(@"[DEBUG] push token: %@", token);
|
NSLog(@"[DEBUG] push token: %@", token);
|
||||||
[[NSUserDefaults standardUserDefaults] setObject:token forKey:EQNUserDefaultPushToken];
|
|
||||||
|
[[NSUserDefaults standardUserDefaults] setObject:token forKey:NSUserDefaults.UserDataPushToken];
|
||||||
FIRMessaging.messaging.APNSToken = deviceToken;
|
FIRMessaging.messaging.APNSToken = deviceToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,47 +145,47 @@
|
|||||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
|
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
|
||||||
{
|
{
|
||||||
// execute common logic and refresh the proper tab
|
// execute common logic and refresh the proper tab
|
||||||
NSDictionary *userInfo = notification.request.content.userInfo;
|
UNNotificationContent *content = notification.request.content;
|
||||||
[self handlePushNotificationWithUserInfo:userInfo];
|
[self handlePushNotificationWithNotificationContent:content];
|
||||||
|
|
||||||
// Change this to your preferred presentation option
|
// 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.
|
// Handle notification messages after display notification is tapped by the user.
|
||||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler
|
- (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
|
// execute common logic and refresh the proper tab
|
||||||
NSDictionary *userInfo = response.notification.request.content.userInfo;
|
UNNotificationContent *content = response.notification.request.content;
|
||||||
[self handlePushNotificationWithUserInfo:userInfo];
|
[self handlePushNotificationWithNotificationContent:content];
|
||||||
|
|
||||||
completionHandler();
|
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
|
#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;
|
EQNTabBarSection section = EQNTabBarSectionAllerte;
|
||||||
if ([userInfo[@"type"] isEqualToString:@"eqn"]) {
|
if ([type isEqualToString:@"eqn"]) {
|
||||||
NSDate *dataRicezione = [NSDate date];
|
[EQNRealtimePushNotification storeNotificationWithPayload:notification];
|
||||||
[[NSUserDefaults standardUserDefaults] setObject:dataRicezione forKey:EQNUserDefaultRealTimeAlertDate];
|
|
||||||
|
|
||||||
[EQNUtility storeDictionary:userInfo toUserDefaultForKey:EQNUserDefaultRealTimeAlertPayload];
|
|
||||||
section = EQNTabBarSectionAllerte;
|
section = EQNTabBarSectionAllerte;
|
||||||
} else if([userInfo[@"type"] isEqualToString:@"manual"]) {
|
} else if([type isEqualToString:@"manual"]) {
|
||||||
section = EQNTabBarSectionSegnalazioni;
|
section = EQNTabBarSectionSegnalazioni;
|
||||||
} else if([userInfo[@"type"] isEqualToString:@"official"]) {
|
} else if([type isEqualToString:@"official"]) {
|
||||||
|
[EQNOfficialPushNotification storeNotificationWithPayload:notification];
|
||||||
section = EQNTabBarSectionRetiSismiche;
|
section = EQNTabBarSectionRetiSismiche;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,19 +193,61 @@
|
|||||||
[self.mainTabBarController selectSection:section];
|
[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
|
#pragma mark - FIRMessagingDelegate
|
||||||
|
|
||||||
- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken
|
- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken
|
||||||
{
|
{
|
||||||
NSLog(@"[Firebase] fcmToken %@", fcmToken);
|
NSLog(@"[Firebase] fcmToken %@", fcmToken);
|
||||||
if (![[NSUserDefaults standardUserDefaults] objectForKey:EQNUserDefaultUserFirebaseToken]) {
|
if (EQNUserData.sharedData.isFirstStart) {
|
||||||
// save default values for notification settings
|
// save default values for notification settings
|
||||||
[EQNAllertaSismica saveDefaultValues];
|
[EQNSettingRealTimeAlert saveDefaultValues];
|
||||||
[EQNNotificheSegnalazioniUtente saveDefaultValues];
|
[EQNSettingUserReportNotification saveDefaultValues];
|
||||||
[EQNNotificheReteSismiche saveDefaultValues];
|
[EQNSettingSeismicNetworkNotification saveDefaultValues];
|
||||||
}
|
}
|
||||||
|
|
||||||
[EQNUser defaultUser].tokenUser = fcmToken;
|
[EQNUser.defaultUser registerUserIfNeededWithFirebaseToken:fcmToken];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// 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 AllertaSismicaSuonoDisabilitatoSismaDebole = "NOTIFICHE_ALLERA_SISMICA_SUONO_DISABILITATO_SISMA_DEBOLE"
|
||||||
|
static let AllertaSismicaCriticalAlerts = "NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS"
|
||||||
|
static let AllertaSismicaSismiDaNotificare = "NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE"
|
||||||
|
static let AllertaSismicaRaggioSismiLievi = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI"
|
||||||
|
static let AllertaSismicaRaggioSismiForti = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI"
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
/// Indica lo stile di pin da visualizzare nelle mappe
|
||||||
|
static let MapPinStyle = "EQNetwork.MapPinStyle"
|
||||||
|
/// Indica le informazioni da visualizzare nelle card `small` e `full` nella Lista Sismi
|
||||||
|
static let SeismicNetworksCardInformations = "EQNetwork.SeismicInformations";
|
||||||
|
/// Indica la tipologia di card da visualizzare nella Lista Sismi
|
||||||
|
static let SeismicNetworksCardStyle = "EQNetwork.SeismicNetworksCardStyle"
|
||||||
|
|
||||||
|
// Migrazioni
|
||||||
|
static let AppMigrationV5_3 = "EQNUserDefaultMigrationV5_3"
|
||||||
|
static let AppMigrationV5_4 = "EQNUserDefaultMigrationV5_4"
|
||||||
|
static let AppMigrationV5_8 = "EQNUserDefaultMigrationV5_8"
|
||||||
|
static let AppMigrationV5_8_2 = "EQNUserDefaultMigrationV5_8_2"
|
||||||
|
static let AppMigrationV5_9 = "EQNUserDefaultMigrationV5_9"
|
||||||
|
static let AppMigrationV5_10 = "EQNUserDefaultMigrationV5_10"
|
||||||
|
|
||||||
|
static let SettingsSeismicNetworkNotificationMigrationV5_8 = "EQNUserDefaultSettingsSeismicNetworkNotificationMigrationV5_8"
|
||||||
|
static let SettingsUserReportNotificationMigrationV5_8 = "EQNUserDefaultSettingsUserReportNotificationMigrationV5_8"
|
||||||
|
static let SismicFiltersMigrationV5_8 = "EQNUserDefaultSismicFiltersMigrationV5_8"
|
||||||
|
static let SaveSettingsNotificationMigrationV5_8 = "EQNUserDefaultSaveSettingsNotificationMigrationV5_8"
|
||||||
|
|
||||||
|
// Notifica allerta salvata
|
||||||
|
static let RealTimeAlertPayload = "EQNData.RealtimePushNotificationPayload"
|
||||||
|
static let RealTimeAlertDate = "EQNData.RealtimeAlertDate"
|
||||||
|
// Notifica rete sismica aperta
|
||||||
|
static let OfficialAlertPayload = "EQNData.OfficialPushNotificationPayload"
|
||||||
|
|
||||||
|
// Filtri sezioni reti sismiche
|
||||||
|
static let SeismicFilterOption = "EQN_SISMI_TIPOLOGIA_FILTRO"
|
||||||
|
static let SeismicSort = "EQN_SISMI_TIPOLOGIA_ORDINAMENTO"
|
||||||
|
static let SeismicMagnitudoMinima = "EQN_MAGNITUDO_MINIMA"
|
||||||
|
static let SeismicDistanzaMassima = "EQN_DISTANZA_MASSIMA"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UserDefaults {
|
||||||
|
|
||||||
|
/// Get a generic stored values
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: A key in the current user‘s defaults database.
|
||||||
|
/// - defaultValue: Default value to return if the key is not found
|
||||||
|
/// - Returns: The object associated with the specified key, or `defaultValue` if the key was not found.
|
||||||
|
func object<T>(forKey key: String, or defaultValue: T) -> T {
|
||||||
|
if let value = UserDefaults.standard.object(forKey: key) as? T {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func enumObject<T: RawRepresentable>(forKey key: String, or defaultValue: T) -> T {
|
||||||
|
if let rawValue = UserDefaults.standard.object(forKey: key) as? T.RawValue,
|
||||||
|
let value = T.init(rawValue: rawValue) {
|
||||||
|
return value
|
||||||
|
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,9 +26,6 @@
|
|||||||
|
|
||||||
@implementation AllerteViewController
|
@implementation AllerteViewController
|
||||||
|
|
||||||
static NSString * const SegueIdentifierProVersion = @"ShowProVersion";
|
|
||||||
static NSString * const SegueIdentifierPrioritySubscriptions = @"ShowPrioritySubscriptions";
|
|
||||||
|
|
||||||
/// Sections inside the app
|
/// Sections inside the app
|
||||||
typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||||
AllerteTableRowLocationPermission = 0,
|
AllerteTableRowLocationPermission = 0,
|
||||||
@@ -36,7 +33,6 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
AllerteTableRowAllertePassate,
|
AllerteTableRowAllertePassate,
|
||||||
AllerteTableRowReteSmartphone,
|
AllerteTableRowReteSmartphone,
|
||||||
AllerteTableRowServizioPriorita,
|
AllerteTableRowServizioPriorita,
|
||||||
AllerteTableRowVersionePro, // non più visualizzata con passaggio dell'app a pagamento
|
|
||||||
AllerteTableRowDatiPosizione
|
AllerteTableRowDatiPosizione
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,8 +100,21 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
- (void)setupUI
|
- (void)setupUI
|
||||||
{
|
{
|
||||||
self.title = [NSLocalizedString(@"tab_network", nil) capitalizedString];
|
self.title = [NSLocalizedString(@"tab_network", nil) capitalizedString];
|
||||||
self.tableView.estimatedRowHeight = 600.0;
|
|
||||||
|
self.tableView.estimatedRowHeight = 200.0;
|
||||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||||
|
self.tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets;
|
||||||
|
[self.tableView registerClass:[AlertsSmartphoneNetworkTableViewCell class] forCellReuseIdentifier:@"SmartphoneNetworkCell"];
|
||||||
|
[self.tableView registerClass:[AlertsPriorityServiceTableViewCell class] forCellReuseIdentifier:@"PriorityCell"];
|
||||||
|
[self.tableView registerClass:[AlertsNoLocationTableViewCell class] forCellReuseIdentifier:@"NoLocationCell"];
|
||||||
|
[self.tableView registerClass:[AlertsPastEartquakesTableViewCell class] forCellReuseIdentifier:@"PastEarthquakesCell"];
|
||||||
|
[self.tableView registerClass:[AlertsSeismicNotificationCompactTableViewCell class] forCellReuseIdentifier:@"SeismicNotificationCompactCell"];
|
||||||
|
[self.tableView registerClass:[AlertsSeismicNotificationExpandedTableViewCell class] forCellReuseIdentifier:@"SeismicNotificationExpandedCell"];
|
||||||
|
[self.tableView registerClass:[AlertsPositionDataTableViewCell class] forCellReuseIdentifier:@"PositionDataCell"];
|
||||||
|
|
||||||
|
if (EQNBackgroundPositionDebugHelper.shared.isEnabled) {
|
||||||
|
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:@selector(backgroundPositionDebugTapped:)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)refreshUI
|
- (void)refreshUI
|
||||||
@@ -113,22 +122,18 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
[super refreshUI];
|
[super refreshUI];
|
||||||
|
|
||||||
// `AllerteTableRowReteSmartphone` and `AllerteTableRowDatiPosizione` are hidden bu default, user can show them
|
// `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"];
|
self.expandeCollapseButton.image = showAllCards ? [UIImage imageNamed:@"navbar-icon-arrow-collapse"] : [UIImage imageNamed:@"navbar-icon-arrow-expand"];
|
||||||
|
|
||||||
// controlliamo se c'è una notifica in tempo reale da mostrare
|
// controlliamo se c'è una notifica in tempo reale da mostrare
|
||||||
NSDate *date = [[NSUserDefaults standardUserDefaults] objectForKey:EQNUserDefaultRealTimeAlertDate];
|
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
||||||
NSDictionary *info = [EQNUtility loadDictionaryFromUserDefaultsForKey:EQNUserDefaultRealTimeAlertPayload];
|
if (notification) {
|
||||||
if (date && info && [EQNUtility getDifferenceMinute:date] < EQNRealtimeAlertExpiration) {
|
|
||||||
self.isNotificaAttiva = YES;
|
self.isNotificaAttiva = YES;
|
||||||
|
|
||||||
// mostriamo la schermata solo se il countdown non è a zero
|
// mostriamo la schermata solo se il countdown non è a zero
|
||||||
EQNRealtimeAlert *alert = [[EQNRealtimeAlert alloc] initWithNotification:info];
|
if (![notification isCountdownExpired]) {
|
||||||
if (alert != nil && ![alert isCountdownExpired]) {
|
RealtimeAlertViewController *controller = [[RealtimeAlertViewController alloc] initWithNotification:notification];
|
||||||
RealtimeAlertViewController *controller = [[RealtimeAlertViewController alloc] initWithAlert:alert];
|
controller.modalInPresentation = YES;
|
||||||
if (@available(iOS 13.0, *)) {
|
|
||||||
controller.modalInPresentation = YES;
|
|
||||||
}
|
|
||||||
[self presentViewController:controller animated:YES completion:nil];
|
[self presentViewController:controller animated:YES completion:nil];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -144,7 +149,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
[self.tableItems addObject:@(AllerteTableRowReteSmartphone)];
|
[self.tableItems addObject:@(AllerteTableRowReteSmartphone)];
|
||||||
}
|
}
|
||||||
// check if locations is enabled
|
// check if locations is enabled
|
||||||
if (CLLocationManager.authorizationStatus != kCLAuthorizationStatusAuthorizedAlways) {
|
if (EQNUserData.sharedData.locationAuthorizationStatus != kCLAuthorizationStatusAuthorizedAlways) {
|
||||||
[self.tableItems addObject:@(AllerteTableRowLocationPermission)];
|
[self.tableItems addObject:@(AllerteTableRowLocationPermission)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,8 +167,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
|
|
||||||
- (void)resetRealtimeAlert
|
- (void)resetRealtimeAlert
|
||||||
{
|
{
|
||||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:EQNUserDefaultRealTimeAlertDate];
|
[EQNRealtimePushNotification removeStoredNotification];
|
||||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:EQNUserDefaultRealTimeAlertPayload];
|
|
||||||
self.isNotificaAttiva = NO;
|
self.isNotificaAttiva = NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,13 +181,18 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
- (IBAction)collapseExpandTapped:(id)sender
|
- (IBAction)collapseExpandTapped:(id)sender
|
||||||
{
|
{
|
||||||
// toggle saved value
|
// toggle saved value
|
||||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
AppPreferences.shared.alertsShowAllCards = !AppPreferences.shared.alertsShowAllCards;
|
||||||
BOOL showAll = [defaults boolForKey:EQNUserDefaultKeyAlertsShowAllCards];
|
|
||||||
[defaults setBool:!showAll forKey:EQNUserDefaultKeyAlertsShowAllCards];
|
|
||||||
|
|
||||||
[self refreshUI];
|
[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
|
- (void)actionCloseNotification
|
||||||
{
|
{
|
||||||
[self resetRealtimeAlert];
|
[self resetRealtimeAlert];
|
||||||
@@ -226,7 +235,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
|
|
||||||
- (void)actionTestPush
|
- (void)actionTestPush
|
||||||
{
|
{
|
||||||
CLAuthorizationStatus status = CLLocationManager.authorizationStatus;
|
CLAuthorizationStatus status = EQNUserData.sharedData.locationAuthorizationStatus;
|
||||||
if (status != kCLAuthorizationStatusAuthorizedAlways && status != kCLAuthorizationStatusAuthorizedWhenInUse) {
|
if (status != kCLAuthorizationStatusAuthorizedAlways && status != kCLAuthorizationStatusAuthorizedWhenInUse) {
|
||||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"attention", nil)
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"attention", nil)
|
||||||
message:NSLocalizedString(@"liveview_unknown_location", nil)
|
message:NSLocalizedString(@"liveview_unknown_location", nil)
|
||||||
@@ -263,14 +272,16 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
|
|
||||||
if (tableRow == AllerteTableRowLocationPermission) {
|
if (tableRow == AllerteTableRowLocationPermission) {
|
||||||
AlertsNoLocationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NoLocationCell" forIndexPath:indexPath];
|
AlertsNoLocationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NoLocationCell" forIndexPath:indexPath];
|
||||||
cell.status = CLLocationManager.authorizationStatus;
|
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||||
|
[cell updateWith:EQNUserData.sharedData.locationAuthorizationStatus];
|
||||||
return cell;
|
return cell;
|
||||||
|
|
||||||
} else if (tableRow == AllerteTableRowSismiRilevati) {
|
} else if (tableRow == AllerteTableRowSismiRilevati) {
|
||||||
if (self.isNotificaAttiva) {
|
if (self.isNotificaAttiva) {
|
||||||
AlertsSeismicNotificationExpandedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationExpandedCell" forIndexPath:indexPath];
|
AlertsSeismicNotificationExpandedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationExpandedCell" forIndexPath:indexPath];
|
||||||
NSDictionary *info = [EQNUtility loadDictionaryFromUserDefaultsForKey:EQNUserDefaultRealTimeAlertPayload];
|
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||||
cell.notification = info;
|
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
||||||
|
[cell updateWith:notification];
|
||||||
|
|
||||||
__weak AllerteViewController *weakSelf = self;
|
__weak AllerteViewController *weakSelf = self;
|
||||||
cell.onTapClose = ^{
|
cell.onTapClose = ^{
|
||||||
@@ -289,7 +300,8 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertsSeismicNotificationCompactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationCell" forIndexPath:indexPath];
|
AlertsSeismicNotificationCompactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationCompactCell" forIndexPath:indexPath];
|
||||||
|
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||||
|
|
||||||
__weak AllerteViewController *weakSelf = self;
|
__weak AllerteViewController *weakSelf = self;
|
||||||
cell.onTapAlertTest = ^{
|
cell.onTapAlertTest = ^{
|
||||||
@@ -309,8 +321,9 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
|
|
||||||
} else if (tableRow == AllerteTableRowAllertePassate) {
|
} else if (tableRow == AllerteTableRowAllertePassate) {
|
||||||
AlertsPastEartquakesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PastEarthquakesCell" forIndexPath:indexPath];
|
AlertsPastEartquakesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PastEarthquakesCell" forIndexPath:indexPath];
|
||||||
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
|
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||||
cell.onTapMapButton = ^{
|
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
|
||||||
|
cell.onTapMap = ^{
|
||||||
PasquakesMapViewController *controller = [[PasquakesMapViewController alloc] init];
|
PasquakesMapViewController *controller = [[PasquakesMapViewController alloc] init];
|
||||||
[self presentViewController:controller animated:YES completion:nil];
|
[self presentViewController:controller animated:YES completion:nil];
|
||||||
};
|
};
|
||||||
@@ -318,7 +331,8 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
|
|
||||||
} else if (tableRow == AllerteTableRowReteSmartphone) {
|
} else if (tableRow == AllerteTableRowReteSmartphone) {
|
||||||
AlertsSmartphoneNetworkTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SmartphoneNetworkCell" forIndexPath:indexPath];
|
AlertsSmartphoneNetworkTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SmartphoneNetworkCell" forIndexPath:indexPath];
|
||||||
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
|
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||||
|
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
|
||||||
cell.onTapButton = ^{
|
cell.onTapButton = ^{
|
||||||
[self visualizzaCopertura];
|
[self visualizzaCopertura];
|
||||||
};
|
};
|
||||||
@@ -326,16 +340,13 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
|
|
||||||
} else if (tableRow == AllerteTableRowServizioPriorita) {
|
} else if (tableRow == AllerteTableRowServizioPriorita) {
|
||||||
AlertsPriorityServiceTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PriorityCell" forIndexPath:indexPath];
|
AlertsPriorityServiceTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PriorityCell" forIndexPath:indexPath];
|
||||||
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
|
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
|
||||||
return cell;
|
|
||||||
|
|
||||||
} else if (tableRow == AllerteTableRowVersionePro) {
|
|
||||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ProVersionCell" forIndexPath:indexPath];
|
|
||||||
return cell;
|
return cell;
|
||||||
|
|
||||||
} else if (tableRow == AllerteTableRowDatiPosizione) {
|
} else if (tableRow == AllerteTableRowDatiPosizione) {
|
||||||
AlertsPositionDataTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PositionDataCell" forIndexPath:indexPath];
|
AlertsPositionDataTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PositionDataCell" forIndexPath:indexPath];
|
||||||
cell.position = [EQNUser defaultUser].lastPosition;
|
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||||
|
[cell updateWith:[EQNUser defaultUser].lastPosition];
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,12 +359,10 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
|||||||
|
|
||||||
AllerteTableRow tableRow = [self.tableItems[indexPath.row] integerValue];
|
AllerteTableRow tableRow = [self.tableItems[indexPath.row] integerValue];
|
||||||
switch (tableRow) {
|
switch (tableRow) {
|
||||||
case AllerteTableRowServizioPriorita:
|
case AllerteTableRowServizioPriorita: {
|
||||||
[self performSegueWithIdentifier:SegueIdentifierPrioritySubscriptions sender:nil];
|
SubscriptionsViewController *controller = [[SubscriptionsViewController alloc] init];
|
||||||
break;
|
[self.navigationController pushViewController:controller animated:YES];
|
||||||
case AllerteTableRowVersionePro:
|
}; break;
|
||||||
[self performSegueWithIdentifier:SegueIdentifierProVersion sender:nil];
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-22
@@ -9,41 +9,70 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
|
|
||||||
class AlertsNoLocationTableViewCell: EQNBaseTableViewCell {
|
@objc
|
||||||
|
class AlertsNoLocationTableViewCell: EQNBaseContainerTableViewCell {
|
||||||
@objc var status: CLAuthorizationStatus = .notDetermined {
|
|
||||||
didSet {
|
override var isHeaderVisible: Bool { false }
|
||||||
updateUI()
|
|
||||||
}
|
// 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!
|
override func updateUI() {
|
||||||
@IBOutlet private weak var actionButton: UIButton!
|
super.updateUI()
|
||||||
|
|
||||||
|
actionButton.backgroundColor = AppTheme.Colors.lightGray
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Public
|
||||||
|
|
||||||
private func updateUI() {
|
@objc
|
||||||
var message = ""
|
func update(with status: CLAuthorizationStatus) {
|
||||||
switch status {
|
messageLabel.text = switch status {
|
||||||
case .authorizedAlways:
|
case .authorizedAlways: ""
|
||||||
message = ""
|
case .authorizedWhenInUse: NSLocalizedString("permission_location_no_background", comment: "")
|
||||||
case .authorizedWhenInUse:
|
default: NSLocalizedString("permission_location_no", comment: "")
|
||||||
message = NSLocalizedString("permission_location_no_background", comment: "")
|
|
||||||
default:
|
|
||||||
message = NSLocalizedString("permission_location_no", comment: "")
|
|
||||||
}
|
}
|
||||||
messageLabel.text = message
|
|
||||||
actionButton.setLocalizedTitle(key: "permission_location_no_background_solve")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
@IBAction private func solveTapped(_ sender: UIButton) {
|
@objc private func solveTapped(_ sender: UIButton) {
|
||||||
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
|
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
UIApplication.shared.open(settingsUrl, options: [:], completionHandler: nil)
|
UIApplication.shared.open(settingsUrl, options: [:], completionHandler: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-26
@@ -8,50 +8,87 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class AlertsPastEartquakesTableViewCell: EQNBaseTableViewCell {
|
|
||||||
|
|
||||||
@objc var smartphoneNetwork: EQNReteSmartphone? {
|
@objc
|
||||||
didSet {
|
class AlertsPastEartquakesTableViewCell: EQNBaseContainerTableViewCell {
|
||||||
updateUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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
|
// MARK: - Internal
|
||||||
|
|
||||||
@IBOutlet private weak var headerLabel: UILabel!
|
override func setupUI() {
|
||||||
@IBOutlet private weak var last24hLabel: UILabel!
|
super.setupUI()
|
||||||
@IBOutlet private weak var from2013Label: UILabel!
|
|
||||||
@IBOutlet private weak var mapButton: UIButton!
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
|
||||||
|
|
||||||
override func awakeFromNib() {
|
|
||||||
super.awakeFromNib()
|
|
||||||
|
|
||||||
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
|
override func updateUI() {
|
||||||
|
super.updateUI()
|
||||||
private func localizeUI() {
|
|
||||||
headerLabel.text = NSLocalizedString("main_past_quakes", comment: "")
|
|
||||||
last24hLabel.text = NSLocalizedString("main_recent_quakes_initial", comment: "")
|
last24hLabel.text = NSLocalizedString("main_recent_quakes_initial", comment: "")
|
||||||
from2013Label.text = NSLocalizedString("main_total_quakes_initial", comment: "")
|
from2013Label.text = NSLocalizedString("main_total_quakes_initial", comment: "")
|
||||||
mapButton.setLocalizedTitle(key: "official_button_map", uppercased: true, emoji: "🗺")
|
mapButton.setLocalizedTitle(key: "official_button_map", uppercased: true, emoji: "🗺")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateUI() {
|
// MARK: - Public
|
||||||
guard let smartphoneNetwork = smartphoneNetwork else { return }
|
|
||||||
|
@objc
|
||||||
|
func update(with smartphoneNetwork: EQNReteSmartphone?) {
|
||||||
|
guard let smartphoneNetwork else { return }
|
||||||
|
|
||||||
last24hLabel.text = String(format: NSLocalizedString("main_recent_quakes", comment: ""), smartphoneNetwork.counterLastDayAlerts)
|
last24hLabel.text = String(format: NSLocalizedString("main_recent_quakes", comment: ""), smartphoneNetwork.counterLastDayAlerts)
|
||||||
from2013Label.text = String(format: NSLocalizedString("main_total_quakes", comment: ""), smartphoneNetwork.counterTotalAlerts)
|
from2013Label.text = String(format: NSLocalizedString("main_total_quakes", comment: ""), smartphoneNetwork.counterTotalAlerts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
@IBAction func mapTapped(_ sender: UIButton) {
|
@objc private func mapTapped(_ sender: UIButton) {
|
||||||
onTapMapButton?()
|
onTapMap?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+107
-24
@@ -9,20 +9,11 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Solar
|
import Solar
|
||||||
|
|
||||||
class AlertsPositionDataTableViewCell: EQNBaseTableViewCell {
|
@objc
|
||||||
|
class AlertsPositionDataTableViewCell: EQNBaseContainerTableViewCell {
|
||||||
@objc var position: CLLocation? {
|
|
||||||
didSet {
|
|
||||||
updateUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = {
|
private lazy var dateFormatter: DateFormatter = {
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateStyle = .none
|
formatter.dateStyle = .none
|
||||||
@@ -30,25 +21,117 @@ class AlertsPositionDataTableViewCell: EQNBaseTableViewCell {
|
|||||||
return formatter
|
return formatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - UI
|
||||||
|
|
||||||
private func updateUI() {
|
private lazy var positionImage: UIImageView = {
|
||||||
headerLabel.text = NSLocalizedString("weather_location", comment: "")
|
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."
|
positionLabel.text = "n.d."
|
||||||
sunriseTimeLabel.text = "n.d."
|
sunriseTimeLabel.text = "n.d."
|
||||||
sunsetTimeLabel.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)
|
positionLabel.text = EQNUtility.coordinateString(coordinate: position.coordinate)
|
||||||
|
|
||||||
if let solar = Solar(coordinate: position.coordinate) {
|
guard let solar = Solar(coordinate: position.coordinate) else { return }
|
||||||
if let sunrise = solar.sunrise {
|
let timeZone = TimeZone.current.localizedName(for: .generic, locale: .current) ?? TimeZone.current.identifier
|
||||||
sunriseTimeLabel.text = dateFormatter.string(from: sunrise) + " (\(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.current.identifier))"
|
if let sunset = solar.sunset {
|
||||||
}
|
sunsetTimeLabel.text = dateFormatter.string(from: sunset) + " \(timeZone)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-24
@@ -8,39 +8,81 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class AlertsPriorityServiceTableViewCell: EQNBaseTableViewCell {
|
@objc
|
||||||
|
class AlertsPriorityServiceTableViewCell: EQNBaseContainerTableViewCell {
|
||||||
@objc var smartphoneNetwork: EQNReteSmartphone? {
|
|
||||||
didSet {
|
|
||||||
updateUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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
|
// MARK: - Internal
|
||||||
|
|
||||||
@IBOutlet private weak var headerLabel: UILabel!
|
override func setupUI() {
|
||||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
super.setupUI()
|
||||||
@IBOutlet private weak var lastSubscriptionLabel: UILabel!
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
|
||||||
|
|
||||||
override func awakeFromNib() {
|
|
||||||
super.awakeFromNib()
|
|
||||||
|
|
||||||
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
|
override func updateUI() {
|
||||||
|
super.updateUI()
|
||||||
private func localizeUI() {
|
|
||||||
headerLabel.text = NSLocalizedString("inapp_list", comment: "")
|
backgroundColor = AppTheme.Colors.cardBackgroundOrange
|
||||||
descriptionLabel.text = NSLocalizedString("inapp_adv", comment: "")
|
descriptionLabel.text = NSLocalizedString("inapp_adv", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateUI() {
|
// MARK: - Public
|
||||||
guard let smartphoneNetwork = smartphoneNetwork else { return }
|
|
||||||
|
@objc
|
||||||
|
func update(with smartphoneNetwork: EQNReteSmartphone?) {
|
||||||
|
guard let smartphoneNetwork else { return }
|
||||||
|
|
||||||
let formattedTime = EQNUtility.formattedString(forTimeDifference: smartphoneNetwork.lastSubscriptionDiff)
|
lastSubscriptionLabel.text = subscriptionText(for: smartphoneNetwork.lastSubscriptionDiff)
|
||||||
lastSubscriptionLabel.text = String(format: NSLocalizedString("inapp_adv_time", comment: ""), formattedTime)
|
}
|
||||||
|
|
||||||
|
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
|
import UIKit
|
||||||
|
|
||||||
|
|
||||||
class AlertsSeismicNotificationCompactTableViewCell: EQNBaseTableViewCell {
|
@objc
|
||||||
|
class AlertsSeismicNotificationCompactTableViewCell: EQNBaseContainerTableViewCell {
|
||||||
|
|
||||||
typealias DefaultCompletion = () -> Void
|
typealias DefaultCompletion = () -> Void
|
||||||
|
|
||||||
@objc var onTapAlertTest: DefaultCompletion?
|
@objc var onTapAlertTest: DefaultCompletion?
|
||||||
@objc var onTapSimulator: DefaultCompletion?
|
@objc var onTapSimulator: DefaultCompletion?
|
||||||
@objc var onTapHowItWorks: DefaultCompletion?
|
@objc var onTapHowItWorks: DefaultCompletion?
|
||||||
@objc var onTapShareApp: 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!
|
private lazy var testAlertButton: UIButton = {
|
||||||
@IBOutlet private weak var testAlertButton: UIButton!
|
let button = EQNRoundedButton.make(target: self, action: #selector(testAlertTapped(_:)))
|
||||||
@IBOutlet private weak var simulatorAlertButton: UIButton!
|
return button
|
||||||
@IBOutlet private weak var howItWorksAlertButton: UIButton!
|
}()
|
||||||
@IBOutlet private weak var shareAppButton: UIButton!
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
private lazy var simulatorAlertButton: UIButton = {
|
||||||
|
let button = EQNRoundedButton.make(target: self, action: #selector(simulatorTapped(_:)))
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
override func awakeFromNib() {
|
private lazy var howItWorksAlertButton: UIButton = {
|
||||||
super.awakeFromNib()
|
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
|
override func updateUI() {
|
||||||
|
super.updateUI()
|
||||||
private func localizeUI() {
|
|
||||||
|
backgroundColor = AppTheme.Colors.cardBackgroundGreen
|
||||||
descriptionLabel.text = NSLocalizedString("main_nodetection", comment: "")
|
descriptionLabel.text = NSLocalizedString("main_nodetection", comment: "")
|
||||||
testAlertButton.setLocalizedTitle(key: "main_alerttest", uppercased: true, emoji: "🚨")
|
testAlertButton.setLocalizedTitle(key: "main_alerttest", uppercased: true, emoji: "🚨")
|
||||||
simulatorAlertButton.setLocalizedTitle(key: "main_simulator", uppercased: true, emoji: "⏱")
|
simulatorAlertButton.setLocalizedTitle(key: "main_simulator", uppercased: true, emoji: "⏱")
|
||||||
howItWorksAlertButton.setLocalizedTitle(key: "main_how_it_work", uppercased: true, emoji: "💡")
|
howItWorksAlertButton.setLocalizedTitle(key: "main_how_it_work", uppercased: true, emoji: "💡")
|
||||||
shareAppButton.setLocalizedTitle(key: "main_share_app", uppercased: true, emoji: "👥")
|
shareAppButton.setLocalizedTitle(key: "main_share_app", uppercased: true, emoji: "👥")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
@IBAction private func testAlertTapped() {
|
@objc private func testAlertTapped(_ sender: UIButton) {
|
||||||
onTapAlertTest?()
|
onTapAlertTest?()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func simulatorTapped() {
|
@objc private func simulatorTapped(_ sender: UIButton) {
|
||||||
onTapSimulator?()
|
onTapSimulator?()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func howItWorksTapped() {
|
@objc private func howItWorksTapped(_ sender: UIButton) {
|
||||||
onTapHowItWorks?()
|
onTapHowItWorks?()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func shareAppTapped() {
|
@objc private func shareAppTapped(_ sender: UIButton) {
|
||||||
onTapShareApp?()
|
onTapShareApp?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+176
-83
@@ -8,111 +8,198 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MapKit
|
import MapKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMapViewDelegate {
|
|
||||||
|
|
||||||
|
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseContainerTableViewCell, MKMapViewDelegate {
|
||||||
|
|
||||||
|
override var isHeaderVisible: Bool { false }
|
||||||
|
|
||||||
typealias DefaultCompletion = () -> Void
|
typealias DefaultCompletion = () -> Void
|
||||||
|
|
||||||
@objc var notification: [String: Any]? {
|
|
||||||
didSet {
|
|
||||||
updateUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@objc var onTapOpenTwitter: DefaultCompletion?
|
@objc var onTapOpenTwitter: DefaultCompletion?
|
||||||
@objc var onTapRateApp: DefaultCompletion?
|
@objc var onTapRateApp: DefaultCompletion?
|
||||||
@objc var onTapClose: DefaultCompletion?
|
@objc var onTapClose: DefaultCompletion?
|
||||||
@objc var onTapShareApp: 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
|
// MARK: - Internal
|
||||||
|
|
||||||
@IBOutlet weak var notificationTitleLabel: UILabel!
|
override func setupUI() {
|
||||||
@IBOutlet weak var notificationDescriptionLabel: UILabel!
|
super.setupUI()
|
||||||
@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()
|
|
||||||
|
|
||||||
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
|
override func updateUI() {
|
||||||
|
super.updateUI()
|
||||||
private func localizeUI() {
|
|
||||||
shareButton.setLocalizedTitle(key: "main_share_app")
|
shareButton.setLocalizedTitle(key: "main_share_app")
|
||||||
rateAppButton.setLocalizedTitle(key: "main_vote")
|
rateAppButton.setLocalizedTitle(key: "main_vote")
|
||||||
viewOnTwitterButton.setLocalizedTitle(key: "main_twitter_see")
|
viewOnTwitterButton.setLocalizedTitle(key: "main_twitter_see")
|
||||||
closeButton.setLocalizedTitle(key: "official_close")
|
closeButton.setLocalizedTitle(key: "official_close")
|
||||||
descriptionLabel.text = NSLocalizedString("map_smartphone_magnitude", comment: "")
|
descriptionLabel.text = NSLocalizedString("map_smartphone_magnitude", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
private func updateUI() {
|
@objc
|
||||||
|
func update(with notification: EQNRealtimePushNotification?) {
|
||||||
// clearn any other previous notifications
|
// clearn any other previous notifications
|
||||||
notificationTitleLabel.text = ""
|
notificationTitleLabel.text = "Sisma rilevato a 150km (TEST)"
|
||||||
notificationDescriptionLabel.text = ""
|
notificationIntensityLabel.text = "Previsto uno scuotimento forte"
|
||||||
|
notificationDescriptionLabel.text = "Distanza 150 km - 13 minuti fa"
|
||||||
mapView.removeAnnotations(mapView.annotations)
|
mapView.removeAnnotations(mapView.annotations)
|
||||||
|
|
||||||
guard let notification = notification,
|
guard let notification = notification else { return }
|
||||||
let aps = notification["aps"] as? [String: Any],
|
|
||||||
let alert = aps["alert"] as? [String: Any] 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
|
if let date = notification.dateTime {
|
||||||
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") {
|
|
||||||
|
|
||||||
coordinate = CLLocation(latitude: latitude, longitude: longitude)
|
let distance = EQNUser.default().lastPosition?.distance(from: notification.coordinate) ?? 0.0
|
||||||
}
|
|
||||||
|
|
||||||
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 distanceRound = Int(round(distance / 1_000))
|
let distanceRound = Int(round(distance / 1_000))
|
||||||
let difference = Int(NSDate().timeIntervalSince(date) / 60.0)
|
let difference = Int(NSDate().timeIntervalSince(date) / 60.0)
|
||||||
|
|
||||||
notificationDescriptionLabel.text = ""
|
notificationDescriptionLabel.text = ""
|
||||||
+ NSLocalizedString("official_card_distance", comment: "") + " \(distanceRound) km"
|
+ NSLocalizedString("official_card_distance", comment: "") + " \(distanceRound) km"
|
||||||
+ " - " + EQNUtility.formattedString(forTimeDifference: difference)
|
+ " - " + 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 span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
let region = MKCoordinateRegion(center: notification.coordinate.coordinate, span: span)
|
||||||
let region = MKCoordinateRegion(center: coordinate.coordinate, span: span)
|
|
||||||
|
mapView.setCenter(notification.coordinate.coordinate, animated: false)
|
||||||
mapView.setCenter(coordinate.coordinate, animated: false)
|
mapView.setRegion(region, animated: true)
|
||||||
mapView.setRegion(region, animated: true)
|
|
||||||
|
let annotation = EQNMapAnnotationPastquake(title: "", coordinate: notification.coordinate.coordinate, intensity: notification.intensity)
|
||||||
let annotation = EQNMapAnnotationPastquake(title: "", coordinate: coordinate.coordinate, intensity: intensity)
|
mapView.addAnnotation(annotation)
|
||||||
mapView.addAnnotation(annotation)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||||
@@ -126,30 +213,36 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
|||||||
return annotationView
|
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
|
// MARK: - Actions
|
||||||
|
|
||||||
@IBAction private func shareAppTapped(_ sender: UIButton) {
|
@objc private func shareAppTapped(_ sender: UIButton) {
|
||||||
onTapShareApp?()
|
onTapShareApp?()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func rateAppTapped(_ sender: UIButton) {
|
@objc private func rateAppTapped(_ sender: UIButton) {
|
||||||
onTapRateApp?()
|
onTapRateApp?()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func viewInTwitterTapped(_ sender: UIButton) {
|
@objc private func viewInTwitterTapped(_ sender: UIButton) {
|
||||||
onTapOpenTwitter?()
|
onTapOpenTwitter?()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func closeTapped(_ sender: UIButton) {
|
@objc private func closeTapped(_ sender: UIButton) {
|
||||||
onTapClose?()
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+64
-30
@@ -7,49 +7,83 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
class AlertsSmartphoneNetworkTableViewCell: EQNBaseTableViewCell {
|
@objc
|
||||||
|
class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell {
|
||||||
@objc var smartphoneNetwork: EQNReteSmartphone? {
|
|
||||||
didSet {
|
|
||||||
updateUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc var onTapButton: (() -> Void)?
|
@objc var onTapButton: (() -> Void)?
|
||||||
|
|
||||||
|
override var headerText: String { NSLocalizedString("main_network", comment: "") }
|
||||||
|
|
||||||
|
// MARK: - UI
|
||||||
|
|
||||||
|
private lazy var counterLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textColor = AppTheme.Colors.green
|
||||||
|
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
|
||||||
|
label.textAlignment = .center
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var descriptionLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.textColor = AppTheme.shared.cardTextColor
|
||||||
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.textAlignment = .center
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var coverageButton: UIButton = {
|
||||||
|
let button = EQNRoundedButton.make(target: self, action: #selector(localCovergeTapped(_:)))
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
// MARK: - Internal
|
// MARK: - Internal
|
||||||
|
|
||||||
@IBOutlet private weak var headerLabel: UILabel!
|
override func setupUI() {
|
||||||
@IBOutlet private weak var smartphoneCounterLabel: UILabel!
|
super.setupUI()
|
||||||
@IBOutlet private weak var coverageDescriptionLabel: UILabel!
|
|
||||||
@IBOutlet private weak var localCoverageButton: UIButton!
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
|
||||||
|
|
||||||
override func awakeFromNib() {
|
|
||||||
super.awakeFromNib()
|
|
||||||
|
|
||||||
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
|
override func updateUI() {
|
||||||
|
super.updateUI()
|
||||||
private func localizeUI() {
|
|
||||||
headerLabel.text = NSLocalizedString("main_network", comment: "")
|
coverageButton.setLocalizedTitle(key: "main_coverage", uppercased: true, emoji: "🗺")
|
||||||
coverageDescriptionLabel.text = NSLocalizedString("main_monitoring_currently2", comment: "")
|
descriptionLabel.text = NSLocalizedString("main_monitoring_currently2", comment: "")
|
||||||
localCoverageButton.setLocalizedTitle(key: "main_coverage", uppercased: true, emoji: "🗺")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateUI() {
|
// MARK: - Public
|
||||||
guard let smartphoneNetwork = smartphoneNetwork else { return }
|
|
||||||
|
@objc
|
||||||
smartphoneCounterLabel.text = "\(smartphoneNetwork.counterSmartphones)"
|
func update(with smartphoneNetwork: EQNReteSmartphone?) {
|
||||||
|
guard let smartphoneNetwork else { return }
|
||||||
|
counterLabel.text = "\(smartphoneNetwork.counterSmartphones)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
@IBAction private func localCovergeTapped(_ sender: UIButton) {
|
@objc private func localCovergeTapped(_ sender: UIButton) {
|
||||||
onTapButton?()
|
onTapButton?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ class PasquakesMapViewController: EQNBaseMapViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
||||||
|
mapView.deselectAnnotation(annotation, animated: true)
|
||||||
|
|
||||||
guard let annotation = annotation as? EQNMapAnnotationPastquake, let pastquake = annotation.pastquake else {
|
guard let annotation = annotation as? EQNMapAnnotationPastquake, let pastquake = annotation.pastquake else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -96,8 +98,8 @@ class PasquakesMapViewController: EQNBaseMapViewController {
|
|||||||
.first
|
.first
|
||||||
|
|
||||||
// controlliamo che sia inferiore al raggio massimo impostato per le notifiche
|
// controlliamo che sia inferiore al raggio massimo impostato per le notifiche
|
||||||
if let radiusLow = Double(EQNAllertaSismica.shared().raggioSismiLievi),
|
if let radiusLow = Double(EQNSettingRealTimeAlert.shared.raggioSismiLievi),
|
||||||
let radiusStrong = Double(EQNAllertaSismica.shared().raggioSismiForti),
|
let radiusStrong = Double(EQNSettingRealTimeAlert.shared.raggioSismiForti),
|
||||||
let nearestPastquake = nearestPastquake {
|
let nearestPastquake = nearestPastquake {
|
||||||
let radius = max(radiusLow, radiusStrong)
|
let radius = max(radiusLow, radiusStrong)
|
||||||
if abs(nearestPastquake.coordinate.distance(from: userPosition)) < radius {
|
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() {
|
private func loadDebugData() {
|
||||||
let firebaseToken = UserDefaults.standard.string(forKey: EQNUserDefaultUserFirebaseToken) ?? ""
|
let firebaseToken = UserDefaults.standard.string(forKey: UserDefaults.UserDataFirebaseToken) ?? ""
|
||||||
let pushToken = UserDefaults.standard.string(forKey: EQNUserDefaultPushToken) ?? ""
|
let pushToken = UserDefaults.standard.string(forKey: UserDefaults.UserDataPushToken) ?? ""
|
||||||
|
|
||||||
let text =
|
let text =
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
[[EQNManager defaultManager] avviaManager];
|
[[EQNManager defaultManager] avviaManager];
|
||||||
[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
|
[[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;
|
self.logView.text = self.testo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,11 @@
|
|||||||
|
|
||||||
- (BOOL)isBannerVisible
|
- (BOOL)isBannerVisible
|
||||||
{
|
{
|
||||||
|
#if ADS_ENABLED
|
||||||
return ![EQNPurchaseUtility isProVersionEnabled];
|
return ![EQNPurchaseUtility isProVersionEnabled];
|
||||||
|
#else
|
||||||
|
return NO;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - View Lifecycle
|
#pragma mark - View Lifecycle
|
||||||
@@ -88,12 +92,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine the view width to use for the ad width.
|
// 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
|
// Here safe area is taken into account, hence the view frame is used after
|
||||||
// the view has been laid out.
|
// the view has been laid out.
|
||||||
if (@available(iOS 11.0, *)) {
|
CGRect frame = UIEdgeInsetsInsetRect(self.view.frame, self.view.safeAreaInsets);
|
||||||
frame = UIEdgeInsetsInsetRect(self.view.frame, self.view.safeAreaInsets);
|
|
||||||
}
|
|
||||||
CGFloat viewWidth = frame.size.width;
|
CGFloat viewWidth = frame.size.width;
|
||||||
|
|
||||||
// Step 3 - Get Adaptive GADAdSize and set the ad view.
|
// Step 3 - Get Adaptive GADAdSize and set the ad view.
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#import "EQNMainTabBarController.h"
|
#import "EQNMainTabBarController.h"
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "EQNBaseViewController.h"
|
#import "EQNBaseViewController.h"
|
||||||
#import "SettingsBaseViewController.h"
|
|
||||||
#import "EQNManager.h"
|
#import "EQNManager.h"
|
||||||
#import "ServerRequest.h"
|
#import "ServerRequest.h"
|
||||||
|
|
||||||
@@ -20,9 +19,6 @@
|
|||||||
|
|
||||||
@implementation EQNMainTabBarController
|
@implementation EQNMainTabBarController
|
||||||
|
|
||||||
static NSString * const SegueIdentifierSettings = @"ShowSettings";
|
|
||||||
static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
|
||||||
|
|
||||||
#pragma mark - View Lifecycle
|
#pragma mark - View Lifecycle
|
||||||
|
|
||||||
- (void)viewDidLoad
|
- (void)viewDidLoad
|
||||||
@@ -41,6 +37,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
|||||||
object:nil];
|
object:nil];
|
||||||
|
|
||||||
[self sincronizza];
|
[self sincronizza];
|
||||||
|
[self migrationV5_8];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Private
|
#pragma mark - Private
|
||||||
@@ -53,6 +50,23 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
|||||||
self.tabBar.items[EQNTabBarSectionImpostazioni].title = [NSLocalizedString(@"drawer_main_settings", comment: "") capitalizedString];
|
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
|
#pragma mark - Notification
|
||||||
|
|
||||||
- (void)serverRegistrationFailedNotification:(NSNotification *)notification
|
- (void)serverRegistrationFailedNotification:(NSNotification *)notification
|
||||||
@@ -63,9 +77,9 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
|||||||
preferredStyle:UIAlertControllerStyleAlert];
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"retry", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"retry", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||||
// retry server registration
|
// 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];
|
[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
|
// if user switch from settings page, we need to force a settings save
|
||||||
UIViewController *controller = [self getTopControllerFromController:tabBarController.selectedViewController];
|
UIViewController *controller = [self getTopControllerFromController:tabBarController.selectedViewController];
|
||||||
if ([controller isKindOfClass:[SettingsViewController class]]) {
|
if ([controller isKindOfClass:[SettingsViewController class]]) {
|
||||||
[SettingsBaseViewController saveSettings];
|
[SettingsBaseTableViewController saveSettings];
|
||||||
}
|
}
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
@@ -136,7 +150,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tap 5 times on "Settings" to open debug view
|
// tap 5 times on "Settings" to open debug view
|
||||||
if ([controller isKindOfClass:[SettingsViewController class]]) {
|
if ([controller isKindOfClass:[SettingsViewController class]] && EQNEnableDebugView) {
|
||||||
self.debugTapCounter += 1;
|
self.debugTapCounter += 1;
|
||||||
if (self.debugTapCounter == 5) {
|
if (self.debugTapCounter == 5) {
|
||||||
self.debugTapCounter = 0;
|
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,183 @@
|
|||||||
|
//
|
||||||
|
// SubscriptionDetailsViewController.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 18/06/24.
|
||||||
|
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import StoreKit
|
||||||
|
import SafariServices
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionDetailsViewController: UITableViewController {
|
||||||
|
|
||||||
|
/// Enable this allows shake to enable the current subscription
|
||||||
|
private static let ShakeToEnableSubscription = false
|
||||||
|
|
||||||
|
// MARK: - Internal
|
||||||
|
|
||||||
|
private let products: [EQNInAppProducts]
|
||||||
|
private var selectedProduct: EQNInAppProducts {
|
||||||
|
didSet {
|
||||||
|
onProductSelected()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var priceFormatter: NumberFormatter = {
|
||||||
|
let formatter = NumberFormatter()
|
||||||
|
formatter.formatterBehavior = .behavior10_4
|
||||||
|
formatter.numberStyle = .currency
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(
|
||||||
|
products: [EQNInAppProducts]
|
||||||
|
) {
|
||||||
|
self.products = products
|
||||||
|
self.selectedProduct = products.first(where: { $0.plan == .yearly }) ?? products.first!
|
||||||
|
super.init(style: .plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("Please use init(products:) instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
configureUI()
|
||||||
|
addObservers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addObservers() {
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseNotification(_:)),
|
||||||
|
name: .EQNInAppPurchaseDidComplete,
|
||||||
|
object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func configureUI() {
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
tableView.estimatedRowHeight = 2000.0
|
||||||
|
tableView.separatorStyle = .none
|
||||||
|
tableView.backgroundColor = .systemGroupedBackground
|
||||||
|
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
|
||||||
|
tableView.registerCell(for: SubscriptionDetailsTableViewCell.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Notifications
|
||||||
|
|
||||||
|
@objc private func handlePurchaseNotification(_ notification: Notification) {
|
||||||
|
navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Table view delegate & data source
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionDetailsTableViewCell.self, for: indexPath)
|
||||||
|
cell.selectionStyle = .none
|
||||||
|
cell.productTitleLabel.text = selectedProduct.product.localizedTitle
|
||||||
|
cell.productImageView.image = selectedProduct.category.image
|
||||||
|
|
||||||
|
var purchaseRecapString = ""
|
||||||
|
var subscriptionDetailsString = ""
|
||||||
|
switch selectedProduct.productIdentifier {
|
||||||
|
case EQNInAppProducts.Identifier.Subscription10kMonthly,
|
||||||
|
EQNInAppProducts.Identifier.Subscription100kMonthly:
|
||||||
|
purchaseRecapString = "inapp_monthly_payment"
|
||||||
|
subscriptionDetailsString = "inapp_detail_description"
|
||||||
|
case EQNInAppProducts.Identifier.Subscription100kYearly,
|
||||||
|
EQNInAppProducts.Identifier.Subscription100kYearlyDiscounted,
|
||||||
|
EQNInAppProducts.Identifier.Subscription10kYearly,
|
||||||
|
EQNInAppProducts.Identifier.Subscription10kYearlyDiscounted:
|
||||||
|
purchaseRecapString = "inapp_yearly_payment"
|
||||||
|
subscriptionDetailsString = "inapp_detail_description"
|
||||||
|
case EQNInAppProducts.Identifier.Subscription10kPerpetual,
|
||||||
|
EQNInAppProducts.Identifier.Subscription100kPerpetual:
|
||||||
|
purchaseRecapString = "inapp_lifetime_payment"
|
||||||
|
subscriptionDetailsString = "inapp_lifetime_detail_description"
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.subscriptionDetailsLabel.text = NSLocalizedString(subscriptionDetailsString, comment: "")
|
||||||
|
cell.onTapPrivacy = { [weak self] in
|
||||||
|
self?.openExternalLink("\(EQNWebsiteAddress)/privacy/")
|
||||||
|
}
|
||||||
|
cell.onTapTerms = { [weak self] in
|
||||||
|
self?.openExternalLink("\(EQNWebsiteAddress)/terms-conditions/")
|
||||||
|
}
|
||||||
|
cell.onTapPurchase = { [weak self] in
|
||||||
|
self?.purchaseSelectedProduct()
|
||||||
|
}
|
||||||
|
cell.onChangePlan = { [weak self] type in
|
||||||
|
if let product = self?.productFromProductType(type) {
|
||||||
|
self?.selectedProduct = product
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cell.planSegmentedControl.selectedSegmentIndex = selectedProduct.plan.index
|
||||||
|
cell.purchaseRecapLabel.text = "\(selectedProduct.product.localizedDescription), \(NSLocalizedString(purchaseRecapString, comment: ""))"
|
||||||
|
cell.productPriceLabel.text = priceFormatter.string(from: selectedProduct.product.price)
|
||||||
|
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func onProductSelected() {
|
||||||
|
priceFormatter.locale = selectedProduct.product.priceLocale
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openExternalLink(_ stringUrl: String) {
|
||||||
|
if let url = URL(string: stringUrl) {
|
||||||
|
let controller = SFSafariViewController(url: url)
|
||||||
|
present(controller, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func purchaseSelectedProduct() {
|
||||||
|
EQNInAppProducts.store.buyProduct(selectedProduct.product)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func productFromProductType(_ type: EQNInAppProducts.Plan) -> EQNInAppProducts? {
|
||||||
|
let product: EQNInAppProducts?
|
||||||
|
switch type {
|
||||||
|
case .monthly:
|
||||||
|
product = products.first { $0.plan == .monthly }
|
||||||
|
case .yearly:
|
||||||
|
product = products.first { $0.plan == .yearly }
|
||||||
|
case .perpetual:
|
||||||
|
product = products.first { $0.plan == .perpetual }
|
||||||
|
}
|
||||||
|
return product
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SubscriptionDetailsViewController {
|
||||||
|
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
||||||
|
guard event?.subtype == .motionShake, Self.ShakeToEnableSubscription else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let alert = UIAlertController(title: "🧑💻", message: "Please select an action", preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: "Reset all purchases", style: .default) { action in
|
||||||
|
EQNPurchaseUtility.resetInAppPurchases()
|
||||||
|
})
|
||||||
|
alert.addAction(UIAlertAction(title: "Activate this subscription", style: .default) { action in
|
||||||
|
EQNPurchaseUtility.simulateProPurchase(identifier: self.selectedProduct.productIdentifier)
|
||||||
|
})
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||||
|
present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
-44
@@ -9,57 +9,81 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import StoreKit
|
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? {
|
private lazy var productTitleLabel: UILabel = {
|
||||||
didSet {
|
let label = UILabel()
|
||||||
updateUI()
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
}
|
label.textColor = AppTheme.shared.cardTextColor
|
||||||
}
|
label.font = .preferredFont(forTextStyle: .headline)
|
||||||
var availability: EQNPurchaseAvailability? {
|
label.numberOfLines = 0
|
||||||
didSet {
|
return label
|
||||||
updateUI()
|
}()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBOutlet private weak var productImageView: UIImageView!
|
private lazy var productInfoLabel: UILabel = {
|
||||||
@IBOutlet private weak var productTitleLabel: UILabel!
|
let label = UILabel()
|
||||||
@IBOutlet private weak var productDescriptionLabel: UILabel?
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
@IBOutlet private weak var productInfoLabel: UILabel!
|
label.textColor = AppTheme.Colors.red
|
||||||
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 0
|
||||||
// MARK: - View Lifecycle
|
return label
|
||||||
|
}()
|
||||||
// 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 }
|
|
||||||
|
|
||||||
productImageView.image = VersioneProProducts.image(for: product.productIdentifier)
|
// MARK: - Internal
|
||||||
productTitleLabel.text = product.localizedTitle
|
|
||||||
productDescriptionLabel?.text = product.localizedDescription
|
override func setupUI() {
|
||||||
|
super.setupUI()
|
||||||
|
|
||||||
let infoKey = VersioneProProducts.is100kSubscription(for: product.productIdentifier) ? "inapp_available_100k" : "inapp_available_10k"
|
containerView.addSubview(productImageView)
|
||||||
let counter = availability(for: product.productIdentifier)
|
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)
|
productInfoLabel.text = String(format: NSLocalizedString(infoKey, comment: ""), counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func availability(for productIdentifier: String) -> Int {
|
private func availabilityCounter(
|
||||||
if VersioneProProducts.is100kSubscription(for: productIdentifier) {
|
for category: EQNInAppProducts.Category,
|
||||||
|
availability: EQNPurchaseAvailability?
|
||||||
|
) -> Int {
|
||||||
|
if category == .top100k {
|
||||||
return availability?.top100kAvailable ?? 0
|
return availability?.top100kAvailable ?? 0
|
||||||
}
|
}
|
||||||
return availability?.top10kAvailable ?? 0
|
return availability?.top10kAvailable ?? 0
|
||||||
|
|||||||
+69
-20
@@ -9,38 +9,87 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import StoreKit
|
import StoreKit
|
||||||
|
|
||||||
class SubscriptionsActiveTableViewCell: EQNBaseTableViewCell {
|
|
||||||
|
|
||||||
var product: SKProduct? {
|
class SubscriptionsActiveTableViewCell: EQNBaseContainerTableViewCell {
|
||||||
didSet {
|
|
||||||
updateUI()
|
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!
|
private lazy var activeSubscriptionImageView: UIImageView = {
|
||||||
@IBOutlet private weak var noSubscriptionsLabel: UILabel!
|
let imageView = UIImageView(frame: .zero)
|
||||||
@IBOutlet private weak var activeSubscriptionImageView: UIImageView!
|
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() {
|
override func setupUI() {
|
||||||
super.awakeFromNib()
|
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() {
|
override func updateUI() {
|
||||||
headerLabel.text = NSLocalizedString("inapp_active", comment: "")
|
super.updateUI()
|
||||||
|
|
||||||
noSubscriptionsLabel.text = NSLocalizedString("inapp_nosub", comment: "")
|
noSubscriptionsLabel.text = NSLocalizedString("inapp_nosub", comment: "")
|
||||||
|
restoreButton.setTitle(NSLocalizedString("purchase_pro_restore", comment: ""), for: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateUI() {
|
// MARK: - Actions
|
||||||
if let productIdentifier = product?.productIdentifier {
|
|
||||||
|
@objc private func restoreSubscriptionsTapped(_ sender: UIButton) {
|
||||||
|
onTapRestore()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
func update(with product: EQNInAppProducts?) {
|
||||||
|
if let product {
|
||||||
noSubscriptionsLabel.isHidden = true
|
noSubscriptionsLabel.isHidden = true
|
||||||
activeSubscriptionImageView.isHidden = false
|
activeSubscriptionImageView.isHidden = false
|
||||||
activeSubscriptionImageView.image = VersioneProProducts.image(for: productIdentifier)
|
activeSubscriptionImageView.image = product.category.image
|
||||||
} else {
|
} else {
|
||||||
noSubscriptionsLabel.isHidden = false
|
noSubscriptionsLabel.isHidden = false
|
||||||
activeSubscriptionImageView.isHidden = true
|
activeSubscriptionImageView.isHidden = true
|
||||||
|
|||||||
+29
-10
@@ -8,21 +8,40 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class SubscriptionsDescriptionTableViewCell: EQNBaseTableViewCell {
|
|
||||||
|
|
||||||
@IBOutlet private weak var headerLabel: UILabel!
|
class SubscriptionsDescriptionTableViewCell: EQNBaseContainerTableViewCell {
|
||||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override var headerText: String { NSLocalizedString("inapp_list", comment: "") }
|
||||||
super.awakeFromNib()
|
|
||||||
|
// 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() {
|
override func updateUI() {
|
||||||
headerLabel.text = NSLocalizedString("inapp_list", comment: "")
|
super.updateUI()
|
||||||
|
|
||||||
descriptionLabel.text = NSLocalizedString("inapp_description", comment: "")
|
descriptionLabel.text = NSLocalizedString("inapp_description", comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-13
@@ -8,26 +8,55 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class SubscriptionsHeaderTableViewCell: UITableViewCell {
|
|
||||||
|
|
||||||
var isLoading = false {
|
class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
|
||||||
didSet {
|
|
||||||
updateUI()
|
// MARK: - UI
|
||||||
}
|
|
||||||
|
private lazy var headerTitleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.font = UIFont.preferredFont(forTextStyle: .title2)
|
||||||
|
label.textColor = AppTheme.Colors.darkGray
|
||||||
|
label.textAlignment = .center
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var loadingActivityIndicator: UIActivityIndicatorView = {
|
||||||
|
let spinner = UIActivityIndicatorView(style: .medium)
|
||||||
|
spinner.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
spinner.hidesWhenStopped = true
|
||||||
|
return spinner
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
override init(reuseIdentifier: String?) {
|
||||||
|
super.init(reuseIdentifier: reuseIdentifier)
|
||||||
|
setupUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
var title: String? = nil {
|
required init?(coder: NSCoder) {
|
||||||
didSet {
|
super.init(coder: coder)
|
||||||
updateUI()
|
setupUI()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBOutlet private weak var headerTitleLabel: UILabel!
|
|
||||||
@IBOutlet private weak var loadingActivityIndicator: UIActivityIndicatorView!
|
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func updateUI() {
|
private func setupUI() {
|
||||||
|
contentView.addSubview(headerTitleLabel)
|
||||||
|
contentView.addSubview(loadingActivityIndicator)
|
||||||
|
|
||||||
|
headerTitleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
|
||||||
|
headerTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||||
|
headerTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||||
|
loadingActivityIndicator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||||
|
loadingActivityIndicator.centerYAnchor.constraint(equalTo: headerTitleLabel.centerYAnchor).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
func update(isLoading: Bool, title: String?) {
|
||||||
headerTitleLabel.text = title
|
headerTitleLabel.text = title
|
||||||
|
|
||||||
if isLoading && title != nil {
|
if isLoading && title != nil {
|
||||||
|
|||||||
@@ -8,39 +8,31 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import StoreKit
|
import StoreKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
@objc
|
||||||
class SubscriptionsViewController: UITableViewController {
|
class SubscriptionsViewController: UITableViewController {
|
||||||
|
|
||||||
private static let SegueIdentifierSubscriptionDetail = "ShowSubscriptionDetail"
|
|
||||||
private static let CellHeightDescription: CGFloat = 320.0
|
|
||||||
|
|
||||||
// sezioni
|
// sezioni
|
||||||
private enum TableSection: CaseIterable {
|
private enum TableSection: CaseIterable {
|
||||||
case active
|
case active
|
||||||
case description
|
case description
|
||||||
case monthly
|
case products
|
||||||
case yearly
|
|
||||||
case perpetual
|
|
||||||
|
|
||||||
var sectionTitle: String? {
|
var sectionTitle: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .monthly: return NSLocalizedString("inapp_monthly_subscriptions", comment: "")
|
case .products: NSLocalizedString("subscriptions_available", comment: "")
|
||||||
case .yearly: return NSLocalizedString("inapp_yearly_subscriptions", comment: "")
|
default: nil
|
||||||
case .perpetual: return NSLocalizedString("inapp_lifetime_subscriptions", comment: "")
|
|
||||||
default: return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let sections = TableSection.allCases
|
private let sections = TableSection.allCases
|
||||||
|
|
||||||
private var allProducts = [SKProduct]()
|
/// All products retrieved from AppStore
|
||||||
private var monthlyProducts = [SKProduct]()
|
private var products = [EQNInAppProducts]()
|
||||||
private var yearlyProducts = [SKProduct]()
|
|
||||||
private var perpetualProducts = [SKProduct]()
|
|
||||||
/// Product already bought by the user
|
/// Product already bought by the user
|
||||||
private var subscribedProduct: SKProduct?
|
private var productSubscribed: EQNInAppProducts?
|
||||||
/// Availability for subscriptions
|
/// Availability for subscriptions
|
||||||
private var availability: EQNPurchaseAvailability?
|
private var availability: EQNPurchaseAvailability?
|
||||||
/// Tells if products are loading
|
/// Tells if products are loading
|
||||||
@@ -48,6 +40,13 @@ class SubscriptionsViewController: UITableViewController {
|
|||||||
/// Tells if a restore is in progress
|
/// Tells if a restore is in progress
|
||||||
private var isRestorePurchase = false
|
private var isRestorePurchase = false
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
@objc
|
||||||
|
convenience init() {
|
||||||
|
self.init(style: .plain)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
@@ -63,15 +62,7 @@ class SubscriptionsViewController: UITableViewController {
|
|||||||
loadData()
|
loadData()
|
||||||
checkAvailabilities()
|
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
|
// MARK: - Private
|
||||||
|
|
||||||
private func addObservers() {
|
private func addObservers() {
|
||||||
@@ -89,75 +80,64 @@ class SubscriptionsViewController: UITableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func configureUI() {
|
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 is presented in Simulator, add done button
|
||||||
if navigationController?.viewControllers.first == self {
|
if navigationController?.viewControllers.first == self {
|
||||||
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closeTapped(_:)))
|
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closeTapped(_:)))
|
||||||
navigationItem.leftBarButtonItem = doneButton
|
navigationItem.leftBarButtonItem = doneButton
|
||||||
}
|
}
|
||||||
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
|
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.estimatedRowHeight = Self.CellHeightDescription;
|
tableView.estimatedRowHeight = 600.0
|
||||||
}
|
tableView.separatorStyle = .none
|
||||||
|
tableView.backgroundColor = .systemGroupedBackground
|
||||||
private func updateUI() {
|
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
|
||||||
monthlyProducts.removeAll()
|
if #available(iOS 15.0, *) {
|
||||||
yearlyProducts.removeAll()
|
// remove extra padding on top of each section header
|
||||||
perpetualProducts.removeAll()
|
tableView.sectionHeaderTopPadding = 0.0
|
||||||
|
|
||||||
// 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.registerCell(for: SubscriptionsActiveTableViewCell.self)
|
||||||
tableView.reloadData()
|
tableView.registerCell(for: SubscriptionsDescriptionTableViewCell.self)
|
||||||
|
tableView.registerCell(for: SubscriptionProductTableViewCell.self)
|
||||||
|
tableView.registerHeaderFooterView(for: SubscriptionsHeaderTableViewCell.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadData() {
|
private func loadData() {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
VersioneProProducts.store.requestProducts{ [weak self] success, products in
|
EQNInAppProducts.store.requestProducts { [weak self] success, storeProducts in
|
||||||
self?.isLoading = false
|
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 products = storeProducts.compactMap { EQNInAppProducts.from(product: $0) }
|
||||||
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 })
|
|
||||||
|
|
||||||
self.updateUI()
|
let purchased = products
|
||||||
|
.filter { (product) -> Bool in
|
||||||
|
// filter for subscriptions
|
||||||
|
let isPurchased = EQNInAppProducts.store.isProductPurchased(product.productIdentifier)
|
||||||
|
let isSubscription = product.isSubscription
|
||||||
|
return isPurchased && isSubscription
|
||||||
|
}.sorted { lProduct, rProduct in
|
||||||
|
// If user has more than one subscriptions,
|
||||||
|
// show first the Top10k.
|
||||||
|
let lIs10k = lProduct.isTop10k
|
||||||
|
let rIs10k = rProduct.isTop10k
|
||||||
|
|
||||||
|
// If left item is Top10k, order first
|
||||||
|
if lIs10k && !rIs10k {
|
||||||
|
return true
|
||||||
|
} else if !lProduct.isTop10k && rProduct.isTop10k {
|
||||||
|
// right product is Top10k, left no
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
// both products Top10k or Top100k, keep existing order
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.productSubscribed = purchased.first
|
||||||
|
self.products = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
|
||||||
|
self.tableView.reloadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,18 +150,13 @@ class SubscriptionsViewController: UITableViewController {
|
|||||||
EQNPurchaseUtility.availableSubscriptions { (availability) in
|
EQNPurchaseUtility.availableSubscriptions { (availability) in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.availability = availability
|
self.availability = availability
|
||||||
self.updateUI()
|
self.tableView.reloadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
@objc func restoreTapped(_ sender: AnyObject) {
|
|
||||||
isRestorePurchase = true
|
|
||||||
VersioneProProducts.store.restorePurchases()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func closeTapped(_ sender: AnyObject) {
|
@objc func closeTapped(_ sender: AnyObject) {
|
||||||
dismiss(animated: true, completion: nil)
|
dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
@@ -189,7 +164,7 @@ class SubscriptionsViewController: UITableViewController {
|
|||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
|
|
||||||
@objc func fail(_ notification: Notification){
|
@objc func fail(_ notification: Notification){
|
||||||
VersioneProProducts.store.loadPurchase()
|
EQNInAppProducts.store.loadPurchase()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handlePurchaseNotification(_ notification: Notification) {
|
@objc func handlePurchaseNotification(_ notification: Notification) {
|
||||||
@@ -208,7 +183,7 @@ class SubscriptionsViewController: UITableViewController {
|
|||||||
present(alert, animated: true, completion: nil)
|
present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
VersioneProProducts.store.loadPurchase()
|
EQNInAppProducts.store.loadPurchase()
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,20 +202,22 @@ class SubscriptionsViewController: UITableViewController {
|
|||||||
|
|
||||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
let tableSection = sections[section]
|
let tableSection = sections[section]
|
||||||
if let cell = tableView.dequeueReusableCell(withIdentifier: "SectionHeaderCell") as? SubscriptionsHeaderTableViewCell {
|
switch tableSection.sectionTitle {
|
||||||
cell.title = tableSection.sectionTitle
|
case .some(let title):
|
||||||
cell.isLoading = isLoading
|
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
|
||||||
return cell
|
view.update(isLoading: isLoading, title: title)
|
||||||
|
return view
|
||||||
|
case .none:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
let tableSection = sections[section]
|
let tableSection = sections[section]
|
||||||
if tableSection.sectionTitle != nil {
|
return switch tableSection.sectionTitle {
|
||||||
return 50
|
case .some: 50.0
|
||||||
|
case .none: 0.0
|
||||||
}
|
}
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
@@ -248,96 +225,59 @@ class SubscriptionsViewController: UITableViewController {
|
|||||||
switch tableSection {
|
switch tableSection {
|
||||||
case .active: return 1
|
case .active: return 1
|
||||||
case .description: return 1
|
case .description: return 1
|
||||||
case .monthly,
|
case .products: return products.isEmpty ? 0 : 2
|
||||||
.yearly,
|
|
||||||
.perpetual:
|
|
||||||
return availableProducts(for: tableSection).count
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let tableSection = sections[indexPath.section]
|
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)
|
switch tableSection {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "SubscriptionCell", for: indexPath) as! SubscriptionProductTableViewCell
|
case .active:
|
||||||
cell.product = products[indexPath.row]
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionsActiveTableViewCell.self, for: indexPath)
|
||||||
cell.availability = availability
|
cell.selectionStyle = .none
|
||||||
return cell
|
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) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
|
||||||
let tableSection = sections[indexPath.section]
|
let products = availableProducts(for: indexPath)
|
||||||
let products = availableProducts(for: tableSection)
|
|
||||||
if !products.isEmpty {
|
if !products.isEmpty {
|
||||||
performSegue(withIdentifier: Self.SegueIdentifierSubscriptionDetail, sender: products[indexPath.row])
|
let controller = SubscriptionDetailsViewController(products: products)
|
||||||
|
navigationController?.pushViewController(controller, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func availableProducts(for section: TableSection) -> [SKProduct] {
|
private func availableProducts(for indexPath: IndexPath) -> [EQNInAppProducts] {
|
||||||
switch section {
|
let section = sections[indexPath.section]
|
||||||
case .monthly: return monthlyProducts
|
switch (section, indexPath.row) {
|
||||||
case .yearly: return yearlyProducts
|
case (.products, 0): return products.filter { $0.isTop10k }
|
||||||
case .perpetual: return perpetualProducts
|
case (.products, 1): return products.filter { $0.isTop100k }
|
||||||
default: return []
|
default: return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SubscriptionsViewController: StoryboardInitializable {
|
|
||||||
static var storyboardName: String {
|
|
||||||
"Main"
|
|
||||||
}
|
|
||||||
|
|
||||||
static var storyboardControllerId: String {
|
|
||||||
"subscriptionsController"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,6 +10,54 @@ import UIKit
|
|||||||
import MapKit
|
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 {
|
class RealtimeAlertView: UIView {
|
||||||
|
|
||||||
let titleLabel: UILabel = {
|
let titleLabel: UILabel = {
|
||||||
@@ -25,20 +73,32 @@ class RealtimeAlertView: UIView {
|
|||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.font = .preferredFont(forTextStyle: .body)
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
|
label.textAlignment = .center
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let waveTimeLabel: UILabel = {
|
let waveTimeLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.font = .preferredFont(forTextStyle: .title3)
|
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||||
label.textColor = AppTheme.Colors.red
|
label.textColor = AppTheme.Colors.red
|
||||||
label.text = String(format: NSLocalizedString("alert_wave", comment: ""), 0)
|
label.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), 0)
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
|
label.numberOfLines = 2
|
||||||
label.isHidden = true
|
label.isHidden = true
|
||||||
return label
|
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 = {
|
lazy var mapView: MKMapView = {
|
||||||
let map = MKMapView()
|
let map = MKMapView()
|
||||||
map.translatesAutoresizingMaskIntoConstraints = false
|
map.translatesAutoresizingMaskIntoConstraints = false
|
||||||
@@ -70,7 +130,6 @@ class RealtimeAlertView: UIView {
|
|||||||
addSubview(closeButton)
|
addSubview(closeButton)
|
||||||
addSubview(titleLabel)
|
addSubview(titleLabel)
|
||||||
addSubview(descriptionLabel)
|
addSubview(descriptionLabel)
|
||||||
addSubview(waveTimeLabel)
|
|
||||||
addSubview(mapView)
|
addSubview(mapView)
|
||||||
|
|
||||||
closeButton.addDefaultConstraint(to: self)
|
closeButton.addDefaultConstraint(to: self)
|
||||||
@@ -83,13 +142,20 @@ class RealtimeAlertView: UIView {
|
|||||||
descriptionLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor).isActive = true
|
descriptionLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20.0).isActive = true
|
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20.0).isActive = true
|
||||||
|
|
||||||
waveTimeLabel.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
|
let stackView = UIStackView(arrangedSubviews: [waveTimeLabel, intensityLabel])
|
||||||
waveTimeLabel.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
waveTimeLabel.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 20.0).isActive = true
|
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.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
|
||||||
mapView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
|
mapView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
|
||||||
mapView.topAnchor.constraint(equalTo: waveTimeLabel.bottomAnchor, constant: 20.0).isActive = true
|
mapView.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 20.0).isActive = true
|
||||||
mapView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
mapView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
-7
@@ -16,9 +16,12 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
|
|
||||||
// MARK: - Internal
|
// MARK: - Internal
|
||||||
|
|
||||||
private let notificationView = RealtimeAlertView()
|
private let containerView = RealtimeAlertContainerView()
|
||||||
|
private var notificationView: RealtimeAlertView {
|
||||||
|
containerView.alertView
|
||||||
|
}
|
||||||
/// Alert to display
|
/// Alert to display
|
||||||
private let realtimeAlert: EQNRealtimeAlert
|
private let realtimeAlert: EQNRealtimePushNotification
|
||||||
/// Timer to constantly update countdown label
|
/// Timer to constantly update countdown label
|
||||||
private var countdownTimer: Timer?
|
private var countdownTimer: Timer?
|
||||||
/// Refresh time for wave animation
|
/// Refresh time for wave animation
|
||||||
@@ -32,8 +35,8 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
init(alert: EQNRealtimeAlert) {
|
init(notification: EQNRealtimePushNotification) {
|
||||||
self.realtimeAlert = alert
|
self.realtimeAlert = notification
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
self.waveAnimationCurrentRadius = currentWavePosition()
|
self.waveAnimationCurrentRadius = currentWavePosition()
|
||||||
@@ -52,7 +55,7 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
// MARK: - View Lifecycle
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
view = notificationView
|
view = containerView
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
@@ -65,10 +68,19 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
startWaveAnimation()
|
startWaveAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
containerView.startBackgroundAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func configureUI() {
|
private func configureUI() {
|
||||||
notificationView.closeButton.addTarget(self, action: #selector(onTapClose(_:)), for: .touchUpInside)
|
notificationView.closeButton.addTarget(self, action: #selector(onTapClose(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
|
// configure color for animation
|
||||||
|
containerView.animationColor = realtimeAlert.relativeIntensityColor
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateUI() {
|
private func updateUI() {
|
||||||
@@ -78,7 +90,10 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
let distanceRound = Int(round(realtimeAlert.distanceFromUser() / 1_000))
|
let distanceRound = Int(round(realtimeAlert.distanceFromUser() / 1_000))
|
||||||
notificationView.descriptionLabel.text = (notificationView.descriptionLabel.text ?? "")
|
notificationView.descriptionLabel.text = (notificationView.descriptionLabel.text ?? "")
|
||||||
+ ".\n"
|
+ ".\n"
|
||||||
+ String(format: NSLocalizedString("timer_message2_other", comment: ""), distanceRound)
|
+ String(format: NSLocalizedString("official_distance", comment: ""), distanceRound)
|
||||||
|
|
||||||
|
notificationView.intensityLabel.text = realtimeAlert.displayBody
|
||||||
|
notificationView.intensityLabel.textColor = realtimeAlert.relativeIntensityColor
|
||||||
|
|
||||||
// center map on the earthquake coordinate
|
// center map on the earthquake coordinate
|
||||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||||
@@ -131,7 +146,8 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
|
|
||||||
@objc private func countdownTimerFired(_ sender: Timer) {
|
@objc private func countdownTimerFired(_ sender: Timer) {
|
||||||
let countdown = realtimeAlert.currentCountdown()
|
let countdown = realtimeAlert.currentCountdown()
|
||||||
notificationView.waveTimeLabel.text = String(format: NSLocalizedString("alert_wave", comment: ""), countdown)
|
notificationView.waveTimeLabel.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), countdown)
|
||||||
|
notificationView.waveTimeLabel.textColor = waveTimeTextColor(for: countdown)
|
||||||
|
|
||||||
if countdown <= 0 {
|
if countdown <= 0 {
|
||||||
// stop the countdown
|
// stop the countdown
|
||||||
@@ -165,4 +181,16 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
let velocity = realtimeAlert.waveSpeed
|
let velocity = realtimeAlert.waveSpeed
|
||||||
return velocity * waveAnimationRefreshRate
|
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 UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
|
@objc
|
||||||
|
class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
|
||||||
|
|
||||||
@IBOutlet private weak var headerLabel: UILabel!
|
@objc var onTapTwitter: (() -> Void)?
|
||||||
@IBOutlet private weak var mildReportsLabel: UILabel!
|
@objc var onTapMap: (() -> Void)?
|
||||||
@IBOutlet private weak var strongReportsLabel: UILabel!
|
@objc var onTapTelegram: (() -> Void)?
|
||||||
@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!
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
override var isHeaderVisible: Bool { false }
|
||||||
|
|
||||||
override func awakeFromNib() {
|
// MARK: - UI
|
||||||
super.awakeFromNib()
|
|
||||||
|
private lazy var reportsLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textColor = AppTheme.Colors.red
|
||||||
|
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.numberOfLines = 0
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var reportsDescriptionLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textColor = AppTheme.shared.cardTextColor
|
||||||
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.textAlignment = .center
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var twitterButton: UIButton = {
|
||||||
|
let button = EQNRoundedButton.make(target: self, action: #selector(twitterButtonTapped(_:)))
|
||||||
|
button.imageView?.contentMode = .scaleAspectFit
|
||||||
|
button.setImage(.init(named: "xcorp_icon"), for: .normal)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var mapButton: UIButton = {
|
||||||
|
let button = EQNRoundedButton.make(title: NSLocalizedString("official_button_map", comment: ""), target: self, action: #selector(mapButtonTapped(_:)))
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var telegramButton: UIButton = {
|
||||||
|
let button = EQNRoundedButton.make(target: self, action: #selector(telegramButtonTapped(_:)))
|
||||||
|
button.imageView?.contentMode = .scaleAspectFit
|
||||||
|
button.setImage(.init(named: "telegram_icon"), for: .normal)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - 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
|
override func updateUI() {
|
||||||
|
super.updateUI()
|
||||||
private func localizeUI() {
|
|
||||||
headerLabel.text = NSLocalizedString("main_map", comment: "")
|
reportsDescriptionLabel.text = NSLocalizedString("main_map", comment: "")
|
||||||
mapButton.setTitle(NSLocalizedString("official_button_map", comment: ""), for: .normal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
@objc func updateUI(for smartphoneNetwork: EQNReteSmartphone?) {
|
@objc
|
||||||
guard let smartphoneNetwork = smartphoneNetwork else {
|
func update(with smartphoneNetwork: EQNReteSmartphone?) {
|
||||||
return
|
guard let smartphoneNetwork = smartphoneNetwork else { return }
|
||||||
}
|
|
||||||
|
|
||||||
self.mildReportsLabel.text = "\(smartphoneNetwork.manualGreen)"
|
let reports = smartphoneNetwork.manual
|
||||||
self.strongReportsLabel.text = "\(smartphoneNetwork.manualYellow)"
|
self.reportsLabel.text = "\(reports)"
|
||||||
self.veryStrongReportsLabel.text = "\(smartphoneNetwork.manualRed)"
|
|
||||||
|
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 Foundation
|
||||||
import MapKit
|
import MapKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
class SegnalazioniMapViewController: EQNBaseMapViewController {
|
class SegnalazioniMapViewController: EQNBaseMapViewController {
|
||||||
|
|
||||||
@@ -16,11 +17,46 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
let circle: MKCircle
|
let circle: MKCircle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var isCloseButtonVisible: Bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
private let appPreferences = AppPreferences.shared
|
||||||
/// Contains circles and related colors to draw overlays on the map
|
/// Contains circles and related colors to draw overlays on the map
|
||||||
private var mapCircles = [MapCircle]()
|
private var mapCircles = [MapCircle]()
|
||||||
/// Reports currently showned on the map
|
/// Reports currently showned on the map
|
||||||
private var filteredReports = [EQNSegnalazione]()
|
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
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
@@ -29,8 +65,26 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
|
|
||||||
// MARK: - Public
|
// 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() {
|
override func registerMapAnnotationViews() {
|
||||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||||
|
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SmallIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func loadDataSource() {
|
override func loadDataSource() {
|
||||||
@@ -66,7 +120,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
.first
|
.first
|
||||||
|
|
||||||
// controlliamo che sia inferiore al raggio impostato per le notifiche
|
// 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,
|
let nearestCluser = nearestCluser,
|
||||||
abs(nearestCluser.distance(from: userPosition)) < radius {
|
abs(nearestCluser.distance(from: userPosition)) < radius {
|
||||||
centerLocation = nearestCluser
|
centerLocation = nearestCluser
|
||||||
@@ -96,27 +150,28 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
// MARK: - Actions
|
||||||
guard let annotation = annotation as? EQNMapAnnotationUserReport, let report = annotation.report else {
|
|
||||||
return
|
@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 controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
||||||
let title = EQNUtility.formattedString(forTimeDifference: difference) + " - \(report.intensity.description)"
|
present(controller, animated: true)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
@@ -125,17 +180,17 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
let vector_latitude = reports.map { $0.coordinate.coordinate.latitude }
|
let vector_latitude = reports.map { $0.coordinate.coordinate.latitude }
|
||||||
let vector_longitude = reports.map { $0.coordinate.coordinate.longitude }
|
let vector_longitude = reports.map { $0.coordinate.coordinate.longitude }
|
||||||
let vector_date = reports.map { $0.date }
|
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
|
let minutes: TimeInterval = filter.minutes
|
||||||
|
|
||||||
var cluster_code = 0
|
var cluster_code = 0
|
||||||
var vector_cluster = [Int](repeating: 0, count: vector_latitude.count)
|
var vector_cluster = [Int](repeating: 0, count: vector_latitude.count)
|
||||||
for i in 0..<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 {
|
if vector_cluster[i] == 0 && deltaMinute_i <= minutes {
|
||||||
for j in 0..<vector_latitude.count {
|
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 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 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 {
|
if vector_cluster[j] > 0 {
|
||||||
@@ -200,21 +255,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let circles = Array(0..<cluster_code).map { (i) -> MapCircle in
|
let circles = Array(0..<cluster_code).map { (i) -> MapCircle in
|
||||||
var value_distance = max_distance[i] / 20.0
|
let color: UIColor = AppTheme.Colors.darkGray
|
||||||
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 centre = CLLocation(latitude: lat_centre[i], longitude: lon_centre[i])
|
let centre = CLLocation(latitude: lat_centre[i], longitude: lon_centre[i])
|
||||||
let farest = CLLocation(latitude: lat_farest[i], longitude: lon_farest[i])
|
let farest = CLLocation(latitude: lat_farest[i], longitude: lon_farest[i])
|
||||||
@@ -240,29 +281,6 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
mapView.addOverlays(overlays)
|
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
|
// MARK: - Map
|
||||||
|
|
||||||
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
||||||
@@ -270,10 +288,17 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
return nil
|
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
|
let size = appPreferences.userReportExpandedView ? EQNCustomAnnotationView.SingleLineImageHeight : EQNCustomAnnotationView.SmallViewImageHeight
|
||||||
annotationView.title = annotation.title
|
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
|
return annotationView
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,45 +7,111 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
class SegnalazioniSendReportCell: EQNBaseTableViewCell {
|
class SegnalazioniSendReportCell: EQNBaseContainerTableViewCell {
|
||||||
|
|
||||||
@IBOutlet private weak var headerLabel: UILabel!
|
private struct Report: Equatable {
|
||||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
let magnitude: Int
|
||||||
@IBOutlet private weak var mildLabel: UILabel!
|
let text: String
|
||||||
@IBOutlet private weak var mildButton: UIButton!
|
let color: UIColor
|
||||||
@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()
|
|
||||||
|
|
||||||
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
|
// MARK: - Private
|
||||||
|
|
||||||
private func localizeUI() {
|
private func createContentView(
|
||||||
headerLabel.text = NSLocalizedString("main_feel", comment: "")
|
magnitude: Int,
|
||||||
descriptionLabel.text = NSLocalizedString("manual_usebutton", comment: "")
|
text: String,
|
||||||
mildLabel.text = NSLocalizedString("manual_mild", comment: "")
|
color: UIColor
|
||||||
strongLabel.text = NSLocalizedString("manual_strong", comment: "")
|
) -> UIView {
|
||||||
veryStrongLabel.text = NSLocalizedString("manual_verystrong", comment: "")
|
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() {
|
// MARK: - Actions
|
||||||
super.layoutSubviews()
|
|
||||||
|
@IBAction private func onTapReportButton(_ sender: UIButton) {
|
||||||
let labels: [UILabel] = [mildLabel, strongLabel, veryStrongLabel]
|
let magnitude = sender.tag
|
||||||
labels.forEach { (label) in
|
onTapReport(magnitude)
|
||||||
label.layer.borderWidth = AppTheme.shared.buttonBorderWidth
|
}
|
||||||
label.layer.borderColor = AppTheme.shared.buttonBorderColor.cgColor
|
|
||||||
label.layer.cornerRadius = AppTheme.shared.buttonCornerRadius
|
@objc private func onTapMagnitudeButton(_ sender: UIButton) {
|
||||||
label.clipsToBounds = true
|
let magnitude = sender.tag
|
||||||
}
|
onTapReport(magnitude)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,19 +38,23 @@
|
|||||||
- (void)setupUI
|
- (void)setupUI
|
||||||
{
|
{
|
||||||
self.title = [NSLocalizedString(@"tab_manual", nil) capitalizedString];
|
self.title = [NSLocalizedString(@"tab_manual", nil) capitalizedString];
|
||||||
|
|
||||||
self.tableView.estimatedRowHeight = 500.0;
|
self.tableView.estimatedRowHeight = 500.0;
|
||||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||||
|
self.tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets;
|
||||||
|
[self.tableView registerClass:[SegnalazioniLast24HoursCell class] forCellReuseIdentifier:@"Last24HCell"];
|
||||||
|
[self.tableView registerClass:[SegnalazioniSendReportCell class] forCellReuseIdentifier:@"ReportEarthquakeCell"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)refreshUI
|
- (void)refreshUI
|
||||||
{
|
{
|
||||||
[super refreshUI];
|
[super refreshUI];
|
||||||
|
|
||||||
if ([self.userDefoult objectForKey:DATA_MESSAGE_EQN]){
|
if ([self.userDefoult objectForKey:NSUserDefaults.UserReportMessage]){
|
||||||
NSDate *dateMessage = [self.userDefoult objectForKey:DATA_MESSAGE_EQN];
|
NSDate *dateMessage = [self.userDefoult objectForKey:NSUserDefaults.UserReportMessage];
|
||||||
if ([EQNUtility getDifferenceMinute:dateMessage] >= EQNSendReportDelayBetweenComments){
|
if (![dateMessage isBeforeInterval:EQNSendReportDelayBetweenComments]) {
|
||||||
[self.userDefoult removeObjectForKey:DATA_MESSAGE_EQN];
|
[self.userDefoult removeObjectForKey:NSUserDefaults.UserReportMessage];
|
||||||
[self.userDefoult removeObjectForKey:CODE_MESSAGE_EQN];
|
[self.userDefoult removeObjectForKey:NSUserDefaults.UserReportCodeStatus];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,43 +74,48 @@
|
|||||||
return 2;
|
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
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||||
{
|
{
|
||||||
if (indexPath.row == 0) {
|
if (indexPath.row == 0) {
|
||||||
SegnalazioniLast24HoursCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Last24HCell" forIndexPath:indexPath];
|
SegnalazioniLast24HoursCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Last24HCell" forIndexPath:indexPath];
|
||||||
EQNReteSmartphone *reteSmartPhone = [EQNManager defaultManager].rete_smartphone;
|
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
|
||||||
[cell updateUIFor:reteSmartPhone];
|
__weak SegnalazioniViewController *weakSelf = self;
|
||||||
|
cell.onTapMap = ^{
|
||||||
|
[weakSelf openMap];
|
||||||
|
};
|
||||||
|
cell.onTapTwitter = ^{
|
||||||
|
[weakSelf openTwitter];
|
||||||
|
};
|
||||||
|
cell.onTapTelegram = ^{
|
||||||
|
[weakSelf openTelegram];
|
||||||
|
};
|
||||||
return cell;
|
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;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Actions
|
#pragma mark - Actions
|
||||||
|
|
||||||
- (IBAction)openMapTapped:(id)sender
|
- (void)openMap
|
||||||
{
|
{
|
||||||
SegnalazioniMapViewController *controller = [[SegnalazioniMapViewController alloc] init];
|
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];
|
NSURL *twitterUrl = [NSURL URLWithString:EQNTwitterProfileUrl];
|
||||||
SFSafariViewController *controller = [[SFSafariViewController alloc] initWithURL:twitterUrl];
|
SFSafariViewController *controller = [[SFSafariViewController alloc] initWithURL:twitterUrl];
|
||||||
[self presentViewController:controller animated:YES completion:nil];
|
[self presentViewController:controller animated:YES completion:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)openTelegramTapped:(id)sender
|
- (void)openTelegram
|
||||||
{
|
{
|
||||||
NSURL *telegramUrl = [NSURL URLWithString:EQNTelegramUrl];
|
NSURL *telegramUrl = [NSURL URLWithString:EQNTelegramUrl];
|
||||||
[[UIApplication sharedApplication] openURL:telegramUrl options:@{} completionHandler:nil];
|
[[UIApplication sharedApplication] openURL:telegramUrl options:@{} completionHandler:nil];
|
||||||
@@ -117,12 +126,12 @@
|
|||||||
[[EQNManager defaultManager] sincronizza];
|
[[EQNManager defaultManager] sincronizza];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)sendReportTapped:(UIButton *)sender
|
- (void)sendReportTappedWithMagnitude:(NSInteger)magnitude
|
||||||
{
|
{
|
||||||
// check to avoid multiple consecutive reports
|
// check to avoid multiple consecutive reports
|
||||||
if ([self.userDefoult objectForKey:CODE_MESSAGE_EQN]) {
|
if ([self.userDefoult objectForKey:NSUserDefaults.UserReportCodeStatus]) {
|
||||||
NSDate *dateMessage = [self.userDefoult objectForKey:DATA_MESSAGE_EQN];
|
NSDate *dateMessage = [self.userDefoult objectForKey:NSUserDefaults.UserReportMessage];
|
||||||
if ([EQNUtility getDifferenceMinute:dateMessage] <= EQNSendReportDelayBetweenMessages){
|
if (![dateMessage isBeforeInterval:EQNSendReportDelayBetweenMessages]) {
|
||||||
NSString *message = NSLocalizedString(@"manual_wait", @"");
|
NSString *message = NSLocalizedString(@"manual_wait", @"");
|
||||||
[self showErrorAlertWithMessage:message];
|
[self showErrorAlertWithMessage:message];
|
||||||
[self performSelectorOnMainThread:@selector(sincronizzazione) withObject:nil waitUntilDone:YES];
|
[self performSelectorOnMainThread:@selector(sincronizzazione) withObject:nil waitUntilDone:YES];
|
||||||
@@ -133,9 +142,9 @@
|
|||||||
// ask for user confirmation
|
// ask for user confirmation
|
||||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"attention", @"") message:NSLocalizedString(@"manual_sure", nil) preferredStyle:UIAlertControllerStyleAlert];
|
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) {
|
[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];
|
[self presentViewController:alert animated:YES completion:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,16 +167,14 @@
|
|||||||
|
|
||||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataSegnalazioneTerremoto success:^(id result) {
|
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataSegnalazioneTerremoto success:^(id result) {
|
||||||
|
|
||||||
[self.userDefoult setObject:result forKey:CODE_MESSAGE_EQN];
|
[self.userDefoult setObject:result forKey:NSUserDefaults.UserReportCodeStatus];
|
||||||
[self.userDefoult setObject:[NSDate date] forKey:DATA_MESSAGE_EQN];
|
[self.userDefoult setObject:[NSDate date] forKey:NSUserDefaults.UserReportMessage];
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"report", @"")
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"report", @"")
|
||||||
message:NSLocalizedString(@"manual_ok", @"")
|
message:NSLocalizedString(@"manual_ok", @"")
|
||||||
preferredStyle:UIAlertControllerStyleAlert];
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:nil]];
|
||||||
[self sendComment];
|
|
||||||
}]];
|
|
||||||
[self presentViewController:alert animated:YES completion:nil];
|
[self presentViewController:alert animated:YES completion:nil];
|
||||||
});
|
});
|
||||||
} failure:^(NSError * error) {
|
} failure:^(NSError * error) {
|
||||||
@@ -177,42 +184,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
|
#pragma mark - Private
|
||||||
|
|
||||||
- (void)showErrorAlertWithMessage:(NSString *)errorMessage
|
- (void)showErrorAlertWithMessage:(NSString *)errorMessage
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ class SeismicNetworkAdvertiseTableViewCell: UITableViewCell {
|
|||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var bannerView: GADNativeAdView = {
|
private lazy var bannerView: NativeAdView = {
|
||||||
let view = GADTMediumTemplateView()
|
let view = GADTMediumTemplateView()
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
return view
|
return view
|
||||||
@@ -71,7 +71,7 @@ class SeismicNetworkAdvertiseTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
func loadNativeAd(_ nativeAd: GADNativeAd) {
|
func loadNativeAd(_ nativeAd: NativeAd) {
|
||||||
bannerView.nativeAd = nativeAd
|
bannerView.nativeAd = nativeAd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
//
|
||||||
|
// SeismicNetworkBaseTableViewCell.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 06/03/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
protocol SeismicNetworkBaseTableViewCellDelegate: AnyObject {
|
||||||
|
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkBaseTableViewCell)
|
||||||
|
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell)
|
||||||
|
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell)
|
||||||
|
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkBaseTableViewCell)
|
||||||
|
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkBaseTableViewCell)
|
||||||
|
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkBaseTableViewCell)
|
||||||
|
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SeismicNetworkBaseTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
|
/// Delegate
|
||||||
|
weak var delegate: SeismicNetworkBaseTableViewCellDelegate?
|
||||||
|
|
||||||
|
/// Available informations to display inside the cell
|
||||||
|
enum InformationType: Int {
|
||||||
|
case preliminary
|
||||||
|
case time
|
||||||
|
case distance
|
||||||
|
case coordinate
|
||||||
|
case population
|
||||||
|
case realtimeSmartphones
|
||||||
|
case reportUsers
|
||||||
|
case intensityMap
|
||||||
|
case buttons
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARL: - Internal
|
||||||
|
|
||||||
|
static let DefaultButtonHeight: CGFloat = 34.0
|
||||||
|
static let VerticalSpacingDefault: CGFloat = 6.0
|
||||||
|
static let VerticalSpacingSmall: CGFloat = 2.0
|
||||||
|
static let HorizontalSpacingDefault: CGFloat = 4.0
|
||||||
|
|
||||||
|
// MARK: - UI Components
|
||||||
|
|
||||||
|
lazy var containerView: UIView = {
|
||||||
|
let view = UIView(frame: .zero)
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.backgroundColor = .white
|
||||||
|
view.clipsToBounds = true
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var gradientView: UIImageView = {
|
||||||
|
// Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine
|
||||||
|
// creata ad-hoc con il gradiente desiderato.
|
||||||
|
// Le prove fatte utilizzando una view normale sono fallite perchè al momento di
|
||||||
|
// disegnare la view non abbiamo le misure corrette.
|
||||||
|
let view = UIImageView(frame: .zero)
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.contentMode = .scaleToFill
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
containerView.eqn_applyShadowAndRoundedCorners()
|
||||||
|
gradientView.eqn_applyRoundedCorners()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Setup
|
||||||
|
|
||||||
|
func setupUI() {
|
||||||
|
selectionStyle = .default
|
||||||
|
backgroundColor = .clear
|
||||||
|
|
||||||
|
// container view
|
||||||
|
contentView.addSubview(containerView)
|
||||||
|
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0).isActive = true
|
||||||
|
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
|
||||||
|
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
|
||||||
|
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true
|
||||||
|
|
||||||
|
containerView.addSubview(gradientView)
|
||||||
|
gradientView.constraint(to: containerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func recreateUI() {
|
||||||
|
// remove all subviews and recreate the required components
|
||||||
|
containerView.subviews.forEach({ $0.removeFromSuperview() })
|
||||||
|
setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func addSeparator(constraintTo: NSLayoutYAxisAnchor, constanst: CGFloat = 8.0) -> UIView {
|
||||||
|
let separator = UIView()
|
||||||
|
separator.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
separator.backgroundColor = .lightGray
|
||||||
|
containerView.addSubview(separator)
|
||||||
|
|
||||||
|
separator.topAnchor.constraint(equalTo: constraintTo, constant: constanst).isActive = true
|
||||||
|
separator.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
|
separator.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
|
separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
|
||||||
|
|
||||||
|
return separator
|
||||||
|
}
|
||||||
|
}
|
||||||
+233
@@ -0,0 +1,233 @@
|
|||||||
|
//
|
||||||
|
// SeismicNetworkMinimalTableViewCell.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 06/03/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
|
||||||
|
class SeismicNetworkMinimalTableViewCell: SeismicNetworkBaseTableViewCell {
|
||||||
|
|
||||||
|
// MARK: - UI
|
||||||
|
|
||||||
|
private lazy var magnitudeLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.font = UIFont.preferredFont(forTextStyle: .largeTitle)
|
||||||
|
label.textColor = .red
|
||||||
|
label.textAlignment = .center
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var placeLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold)
|
||||||
|
label.numberOfLines = 3
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var timeLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 2
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var distanceLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 2
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var smartphonesLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.numberOfLines = 2
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var alertsLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.numberOfLines = 2
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Internal
|
||||||
|
|
||||||
|
/// Seismic to show
|
||||||
|
private var seismic: EQNSisma?
|
||||||
|
private var isPushSelected = false
|
||||||
|
private var informationTypes: Set<InformationType> = []
|
||||||
|
|
||||||
|
// MARK: - Setup
|
||||||
|
|
||||||
|
override func setupUI() {
|
||||||
|
super.setupUI()
|
||||||
|
|
||||||
|
// this variable is used to keep track of the previous view, in order to attach proper constraints
|
||||||
|
var previousView: UIView = containerView
|
||||||
|
|
||||||
|
// preliminary banner on top of the cell
|
||||||
|
if informationTypes.contains(.preliminary) {
|
||||||
|
let preliminaryLabel = UILabel()
|
||||||
|
preliminaryLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
preliminaryLabel.text = NSLocalizedString("official_prelimiary", comment: "").uppercased()
|
||||||
|
preliminaryLabel.textAlignment = .center
|
||||||
|
preliminaryLabel.backgroundColor = .red
|
||||||
|
preliminaryLabel.textColor = .yellow
|
||||||
|
|
||||||
|
containerView.addSubview(preliminaryLabel)
|
||||||
|
preliminaryLabel.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
||||||
|
preliminaryLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
|
||||||
|
preliminaryLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
|
||||||
|
preliminaryLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
|
||||||
|
|
||||||
|
previousView = preliminaryLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
containerView.addSubview(magnitudeLabel)
|
||||||
|
containerView.addSubview(placeLabel)
|
||||||
|
|
||||||
|
let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor
|
||||||
|
|
||||||
|
|
||||||
|
let stackViewInformations = UIStackView(arrangedSubviews: [timeLabel, distanceLabel])
|
||||||
|
stackViewInformations.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
stackViewInformations.axis = .horizontal
|
||||||
|
stackViewInformations.distribution = .fillEqually
|
||||||
|
stackViewInformations.spacing = Self.HorizontalSpacingDefault
|
||||||
|
containerView.addSubview(stackViewInformations)
|
||||||
|
|
||||||
|
let stackViewRight = UIStackView(arrangedSubviews: [placeLabel, stackViewInformations])
|
||||||
|
stackViewRight.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
stackViewRight.axis = .vertical
|
||||||
|
stackViewRight.distribution = .equalSpacing
|
||||||
|
stackViewRight.spacing = Self.VerticalSpacingDefault
|
||||||
|
|
||||||
|
let stackViewMain = UIStackView(arrangedSubviews: [magnitudeLabel, stackViewRight])
|
||||||
|
stackViewMain.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
stackViewMain.axis = .horizontal
|
||||||
|
stackViewMain.distribution = .fill
|
||||||
|
stackViewMain.spacing = Self.HorizontalSpacingDefault
|
||||||
|
containerView.addSubview(stackViewMain)
|
||||||
|
|
||||||
|
stackViewMain.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
|
||||||
|
stackViewMain.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
|
stackViewMain.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
|
magnitudeLabel.widthAnchor.constraint(equalToConstant: 60.0).isActive = true
|
||||||
|
|
||||||
|
previousView = stackViewMain
|
||||||
|
|
||||||
|
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) || informationTypes.contains(.intensityMap) {
|
||||||
|
let separator = addSeparator(constraintTo: previousView.bottomAnchor, constanst: Self.VerticalSpacingDefault)
|
||||||
|
|
||||||
|
let stackViewReports = UIStackView()
|
||||||
|
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
stackViewReports.axis = .vertical
|
||||||
|
stackViewReports.distribution = .equalSpacing
|
||||||
|
stackViewReports.alignment = .center
|
||||||
|
stackViewReports.spacing = Self.VerticalSpacingDefault
|
||||||
|
|
||||||
|
if informationTypes.contains(.realtimeSmartphones) {
|
||||||
|
stackViewReports.addArrangedSubview(smartphonesLabel)
|
||||||
|
}
|
||||||
|
if informationTypes.contains(.reportUsers) {
|
||||||
|
stackViewReports.addArrangedSubview(alertsLabel)
|
||||||
|
}
|
||||||
|
if informationTypes.contains(.intensityMap) {
|
||||||
|
let buttonMap = EQNRoundedButton.make(title: "🎯 \(NSLocalizedString("shakemap", comment: ""))", target: self, action: #selector(intensityMapTapped(_:)))
|
||||||
|
stackViewReports.addArrangedSubview(buttonMap)
|
||||||
|
buttonMap.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
|
||||||
|
buttonMap.leadingAnchor.constraint(equalTo: stackViewReports.leadingAnchor).isActive = true
|
||||||
|
buttonMap.trailingAnchor.constraint(equalTo: stackViewReports.trailingAnchor).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
containerView.addSubview(stackViewReports)
|
||||||
|
stackViewReports.topAnchor.constraint(equalTo: separator.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||||
|
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
|
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
|
|
||||||
|
previousView = stackViewReports
|
||||||
|
}
|
||||||
|
|
||||||
|
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||||
|
|
||||||
|
containerView.eqn_applyShadowAndRoundedCorners()
|
||||||
|
gradientView.eqn_applyRoundedCorners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateUI() {
|
||||||
|
guard let seismic = seismic else { return }
|
||||||
|
|
||||||
|
let viewModel = SeismicNetworkMinimalViewModel(seismic: seismic)
|
||||||
|
|
||||||
|
gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
|
||||||
|
|
||||||
|
placeLabel.text = viewModel.place
|
||||||
|
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
|
||||||
|
magnitudeLabel.textColor = viewModel.colors.textColor
|
||||||
|
magnitudeLabel.text = viewModel.magnitude
|
||||||
|
timeLabel.text = "🕗 \(viewModel.time)"
|
||||||
|
distanceLabel.text = "📐 \(viewModel.distance)"
|
||||||
|
|
||||||
|
if !viewModel.smartphones.isEmpty {
|
||||||
|
smartphonesLabel.text = "🚨 \(viewModel.smartphones)"
|
||||||
|
}
|
||||||
|
if !viewModel.users.isEmpty {
|
||||||
|
alertsLabel.text = "⚠️ \(viewModel.users)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
/// Configure the cell to display a seismic
|
||||||
|
/// - Parameters:
|
||||||
|
/// - seismic: Seismic to display
|
||||||
|
/// - type: Type of cell
|
||||||
|
/// - informations: Informations to show
|
||||||
|
public func configure(
|
||||||
|
with seismic: EQNSisma,
|
||||||
|
isPushSelected: Bool
|
||||||
|
) {
|
||||||
|
self.seismic = seismic
|
||||||
|
self.isPushSelected = isPushSelected
|
||||||
|
self.informationTypes.removeAll()
|
||||||
|
|
||||||
|
if seismic.preliminary.intValue > 0 {
|
||||||
|
informationTypes.insert(.preliminary)
|
||||||
|
}
|
||||||
|
if seismic.smartphoneNumber.intValue > 0 {
|
||||||
|
informationTypes.insert(.realtimeSmartphones)
|
||||||
|
}
|
||||||
|
if seismic.userNumber.intValue > 0 {
|
||||||
|
informationTypes.insert(.reportUsers)
|
||||||
|
}
|
||||||
|
if seismic.isoCode != "0" {
|
||||||
|
informationTypes.insert(.intensityMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
recreateUI()
|
||||||
|
updateUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
|
||||||
|
@objc private func intensityMapTapped(_ sender: Any) {
|
||||||
|
delegate?.seismicNetworkCellDidTapIntensityMapDetail(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
+119
-328
@@ -9,34 +9,10 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import MapKit
|
import MapKit
|
||||||
import CoreLocation
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
class SeismicNetworkTableViewCell: UITableViewCell {
|
class SeismicNetworkTableViewCell: SeismicNetworkBaseTableViewCell {
|
||||||
|
|
||||||
static let Identifier = "SeismicNetworkTableViewCell"
|
|
||||||
|
|
||||||
typealias MagnitudeColors = (textColor: UIColor, startColor: UIColor, endColor: UIColor)
|
|
||||||
|
|
||||||
/// Available informations to display inside the cell
|
|
||||||
enum InformationType: Int {
|
|
||||||
case preliminary
|
|
||||||
case time
|
|
||||||
case distance
|
|
||||||
case coordinate
|
|
||||||
case population
|
|
||||||
case realtimeSmartphones
|
|
||||||
case reportUsers
|
|
||||||
case buttons
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Available cell type
|
/// Available cell type
|
||||||
enum DisplayType {
|
enum DisplayType {
|
||||||
@@ -44,61 +20,38 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
case normal
|
case normal
|
||||||
/// Cell with map visible
|
/// Cell with map visible
|
||||||
case mapExpanded
|
case mapExpanded
|
||||||
/// Cell with weather info visible
|
|
||||||
case weatherExpanded
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delegate
|
|
||||||
weak var delegate: SeismicNetworkTableViewCellDelegate?
|
|
||||||
|
|
||||||
// MARK: - Internal
|
|
||||||
|
|
||||||
private static let DefaultVerticalSpacing: CGFloat = 6.0
|
|
||||||
private static let DefaultBodyFont = UIFont.preferredFont(forTextStyle: .body)
|
|
||||||
private static let DefaultBodyFontLight = UIFont.preferredFont(for: .body, weight: .light)
|
|
||||||
|
|
||||||
/// Seismic to show
|
/// Seismic to show
|
||||||
private var seismic: EQNSisma?
|
private var seismic: EQNSisma?
|
||||||
private(set) var displayType = DisplayType.normal
|
private(set) var displayType = DisplayType.normal
|
||||||
private var informationTypes = [InformationType]()
|
private var informationTypes = [InformationType]()
|
||||||
|
private var isPushSelected = false
|
||||||
private var colors: MagnitudeColors?
|
|
||||||
|
|
||||||
// MARK: - UI Components
|
// MARK: - UI Components
|
||||||
|
|
||||||
private lazy var containerView: UIView = {
|
|
||||||
let view = UIView(frame: .zero)
|
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
view.layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
|
||||||
view.layer.masksToBounds = false
|
|
||||||
|
|
||||||
// add shadow
|
|
||||||
view.layer.shadowColor = UIColor.black.cgColor
|
|
||||||
view.layer.shadowOpacity = 0.5
|
|
||||||
view.layer.shadowOffset = CGSize(width: 0, height: 2)
|
|
||||||
view.layer.shadowRadius = 2
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
private lazy var titleImageView: UIImageView = {
|
|
||||||
let imageView = UIImageView(frame: .zero)
|
|
||||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
return imageView
|
|
||||||
}()
|
|
||||||
|
|
||||||
private lazy var placeLabel: UILabel = {
|
private lazy var placeLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.font = UIFont.preferredFont(for: .title2, weight: .semibold)
|
label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold)
|
||||||
label.numberOfLines = 3
|
label.numberOfLines = 3
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
private lazy var shareButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
button.setImage(UIImage(named: "share_icon"), for: .normal)
|
||||||
|
button.addTarget(self, action: #selector(shareTapped(_:)), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
private lazy var networkLabel: UILabel = {
|
private lazy var networkLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.backgroundColor = UIColor.white.withAlphaComponent(0.5)
|
label.textAlignment = .right
|
||||||
label.textAlignment = .center
|
label.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
|
label.numberOfLines = 2
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -113,35 +66,40 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
private lazy var depthLabel: UILabel = {
|
private lazy var depthLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.font = Self.DefaultBodyFontLight
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 2
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var timeLabel: UILabel = {
|
private lazy var timeLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.font = Self.DefaultBodyFontLight
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 2
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var distanceLabel: UILabel = {
|
private lazy var distanceLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.font = Self.DefaultBodyFontLight
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 2
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var coordinateLabel: UILabel = {
|
private lazy var coordinateLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.font = Self.DefaultBodyFontLight
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 2
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var populationLabel: UILabel = {
|
private lazy var populationLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.font = Self.DefaultBodyFontLight
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 2
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -149,8 +107,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
label.font = Self.DefaultBodyFont
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
|
label.numberOfLines = 2
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -158,8 +117,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
label.font = Self.DefaultBodyFont
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
|
label.numberOfLines = 2
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -173,20 +133,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
return mapView
|
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
|
// MARK: - Init
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
@@ -199,19 +145,19 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
setupUI()
|
setupUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
containerView.eqn_applyShadowAndRoundedCorners()
|
||||||
|
gradientView.eqn_applyRoundedCorners()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
||||||
private func setupUI() {
|
override func setupUI() {
|
||||||
selectionStyle = .default
|
super.setupUI()
|
||||||
translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
backgroundColor = .clear
|
|
||||||
|
|
||||||
// container view
|
|
||||||
contentView.addSubview(containerView)
|
|
||||||
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0).isActive = true
|
|
||||||
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
|
|
||||||
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
|
|
||||||
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true
|
|
||||||
|
|
||||||
// this variable is used to keep track of the previous view, in order to attach proper constraints
|
// this variable is used to keep track of the previous view, in order to attach proper constraints
|
||||||
var previousView: UIView = containerView
|
var previousView: UIView = containerView
|
||||||
@@ -233,48 +179,27 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
previousView = preliminaryLabel
|
previousView = preliminaryLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
// title (bell icon, place label, seismic network and share button)
|
containerView.addSubview(placeLabel)
|
||||||
let titleComponentsHeight: CGFloat = 30.0
|
containerView.addSubview(shareButton)
|
||||||
let stackViewTitle = UIStackView()
|
|
||||||
stackViewTitle.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
stackViewTitle.axis = .horizontal
|
|
||||||
stackViewTitle.distribution = .fill
|
|
||||||
stackViewTitle.alignment = .center
|
|
||||||
stackViewTitle.spacing = 4
|
|
||||||
|
|
||||||
let shareButton = UIButton(type: .custom)
|
|
||||||
shareButton.setImage(UIImage(named: "share_icon"), for: .normal)
|
|
||||||
shareButton.addTarget(self, action: #selector(shareTapped(_:)), for: .touchUpInside)
|
|
||||||
|
|
||||||
stackViewTitle.addArrangedSubview(titleImageView)
|
|
||||||
stackViewTitle.addArrangedSubview(placeLabel)
|
|
||||||
stackViewTitle.addArrangedSubview(networkLabel)
|
|
||||||
stackViewTitle.addArrangedSubview(shareButton)
|
|
||||||
|
|
||||||
titleImageView.heightAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true
|
|
||||||
titleImageView.widthAnchor.constraint(equalTo: titleImageView.heightAnchor).isActive = true
|
|
||||||
networkLabel.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
|
|
||||||
networkLabel.setContentHuggingPriority(.init(800), for: .horizontal)
|
|
||||||
networkLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
||||||
placeLabel.setContentHuggingPriority(.init(200), for: .horizontal)
|
|
||||||
placeLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
||||||
shareButton.widthAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true
|
|
||||||
shareButton.widthAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
|
|
||||||
|
|
||||||
let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor
|
let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor
|
||||||
containerView.addSubview(stackViewTitle)
|
placeLabel.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
|
||||||
stackViewTitle.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
|
placeLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
stackViewTitle.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
placeLabel.trailingAnchor.constraint(equalTo: shareButton.leadingAnchor, constant: .cardPadding.negative).isActive = true
|
||||||
stackViewTitle.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
|
||||||
|
shareButton.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
|
shareButton.centerYAnchor.constraint(equalTo: placeLabel.centerYAnchor).isActive = true
|
||||||
|
shareButton.heightAnchor.constraint(equalToConstant: 24.0).isActive = true
|
||||||
|
shareButton.heightAnchor.constraint(equalTo: shareButton.widthAnchor, multiplier: 1.0).isActive = true
|
||||||
|
|
||||||
let separator1 = addSeparator(constraintTo: stackViewTitle.bottomAnchor)
|
let separator1 = addSeparator(constraintTo: placeLabel.bottomAnchor)
|
||||||
let informationsLeadingAnchor = separator1.leadingAnchor
|
let informationsLeadingAnchor = separator1.leadingAnchor
|
||||||
let informationsTrailingAnchor = separator1.trailingAnchor
|
let informationsTrailingAnchor = separator1.trailingAnchor
|
||||||
|
|
||||||
// magnitude information
|
// magnitude information
|
||||||
containerView.addSubview(magnitudeLabel)
|
containerView.addSubview(magnitudeLabel)
|
||||||
magnitudeLabel.topAnchor.constraint(equalTo: separator1.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
magnitudeLabel.topAnchor.constraint(equalTo: separator1.bottomAnchor, constant: Self.VerticalSpacingSmall).isActive = true
|
||||||
magnitudeLabel.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
|
magnitudeLabel.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
|
||||||
|
|
||||||
if !informationTypes.contains(.preliminary) {
|
if !informationTypes.contains(.preliminary) {
|
||||||
@@ -304,20 +229,27 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
containerView.addSubview(stackViewInformations)
|
containerView.addSubview(stackViewInformations)
|
||||||
stackViewInformations.topAnchor.constraint(equalTo: magnitudeLabel.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
stackViewInformations.topAnchor.constraint(equalTo: magnitudeLabel.bottomAnchor, constant: Self.VerticalSpacingSmall).isActive = true
|
||||||
stackViewInformations.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
|
stackViewInformations.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
|
||||||
stackViewInformations.trailingAnchor.constraint(equalTo: informationsTrailingAnchor, constant: -14).isActive = true
|
stackViewInformations.trailingAnchor.constraint(equalTo: informationsTrailingAnchor, constant: -14).isActive = true
|
||||||
|
|
||||||
previousView = stackViewInformations
|
previousView = stackViewInformations
|
||||||
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) {
|
|
||||||
let separator2 = addSeparator(constraintTo: stackViewInformations.bottomAnchor)
|
// network
|
||||||
|
containerView.addSubview(networkLabel)
|
||||||
|
networkLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingSmall).isActive = true
|
||||||
|
networkLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
|
networkLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
|
previousView = networkLabel
|
||||||
|
|
||||||
|
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) || informationTypes.contains(.intensityMap) {
|
||||||
|
let separator2 = addSeparator(constraintTo: previousView.bottomAnchor, constanst: Self.VerticalSpacingSmall)
|
||||||
|
|
||||||
let stackViewReports = UIStackView()
|
let stackViewReports = UIStackView()
|
||||||
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
|
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackViewReports.axis = .vertical
|
stackViewReports.axis = .vertical
|
||||||
stackViewReports.distribution = .equalSpacing
|
stackViewReports.distribution = .equalSpacing
|
||||||
stackViewReports.alignment = .center
|
stackViewReports.alignment = .center
|
||||||
stackViewReports.spacing = Self.DefaultVerticalSpacing
|
stackViewReports.spacing = Self.VerticalSpacingDefault
|
||||||
|
|
||||||
if informationTypes.contains(.realtimeSmartphones) {
|
if informationTypes.contains(.realtimeSmartphones) {
|
||||||
stackViewReports.addArrangedSubview(smartphonesLabel)
|
stackViewReports.addArrangedSubview(smartphonesLabel)
|
||||||
@@ -325,36 +257,43 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
if informationTypes.contains(.reportUsers) {
|
if informationTypes.contains(.reportUsers) {
|
||||||
stackViewReports.addArrangedSubview(alertsLabel)
|
stackViewReports.addArrangedSubview(alertsLabel)
|
||||||
}
|
}
|
||||||
|
if informationTypes.contains(.intensityMap) {
|
||||||
|
let buttonMap = EQNRoundedButton.make(title: "🎯 \(NSLocalizedString("shakemap", comment: ""))", target: self, action: #selector(intensityMapTapped(_:)))
|
||||||
|
stackViewReports.addArrangedSubview(buttonMap)
|
||||||
|
buttonMap.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
|
||||||
|
buttonMap.leadingAnchor.constraint(equalTo: stackViewReports.leadingAnchor).isActive = true
|
||||||
|
buttonMap.trailingAnchor.constraint(equalTo: stackViewReports.trailingAnchor).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
containerView.addSubview(stackViewReports)
|
containerView.addSubview(stackViewReports)
|
||||||
stackViewReports.topAnchor.constraint(equalTo: separator2.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
stackViewReports.topAnchor.constraint(equalTo: separator2.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||||
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor, constant: 20.0).isActive = true
|
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor, constant: -20.0).isActive = true
|
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
|
|
||||||
let separator3 = addSeparator(constraintTo: stackViewReports.bottomAnchor)
|
previousView = stackViewReports
|
||||||
previousView = separator3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if informationTypes.contains(.buttons) {
|
if informationTypes.contains(.buttons) {
|
||||||
|
let separator3 = addSeparator(constraintTo: previousView.bottomAnchor)
|
||||||
|
previousView = separator3
|
||||||
|
|
||||||
// buttons
|
// buttons
|
||||||
let stackViewButtons = UIStackView()
|
let stackViewButtons = UIStackView()
|
||||||
stackViewButtons.translatesAutoresizingMaskIntoConstraints = false
|
stackViewButtons.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackViewButtons.axis = .horizontal
|
stackViewButtons.axis = .horizontal
|
||||||
stackViewButtons.distribution = .fillEqually
|
stackViewButtons.distribution = .fillEqually
|
||||||
stackViewButtons.spacing = 4
|
stackViewButtons.spacing = 8
|
||||||
|
|
||||||
let buttonMap = createRoundedButton(title: "🗺", action: #selector(mapTapped(_:)))
|
let buttonMap = EQNRoundedButton.make(title: "🗺", target: self, action: #selector(mapTapped(_:)))
|
||||||
stackViewButtons.addArrangedSubview(buttonMap)
|
stackViewButtons.addArrangedSubview(buttonMap)
|
||||||
let buttonWeather = createRoundedButton(title: "🌤", action: #selector(weatherTapped(_:)))
|
let buttonCalendar = EQNRoundedButton.make(title: "📆", target: self, action: #selector(calendarTapped(_:)))
|
||||||
stackViewButtons.addArrangedSubview(buttonWeather)
|
|
||||||
let buttonCalendar = createRoundedButton(title: "📆", action: #selector(calendarTapped(_:)))
|
|
||||||
stackViewButtons.addArrangedSubview(buttonCalendar)
|
stackViewButtons.addArrangedSubview(buttonCalendar)
|
||||||
let buttonSettings = createRoundedButton(title: "🔧", action: #selector(settingsTapped(_:)))
|
let buttonSettings = EQNRoundedButton.make(title: "🔧", target: self, action: #selector(settingsTapped(_:)))
|
||||||
stackViewButtons.addArrangedSubview(buttonSettings)
|
stackViewButtons.addArrangedSubview(buttonSettings)
|
||||||
|
|
||||||
containerView.addSubview(stackViewButtons)
|
containerView.addSubview(stackViewButtons)
|
||||||
stackViewButtons.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
stackViewButtons.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
|
||||||
stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||||
stackViewButtons.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
stackViewButtons.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
stackViewButtons.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
stackViewButtons.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
|
|
||||||
@@ -364,41 +303,19 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
if displayType == .mapExpanded {
|
if displayType == .mapExpanded {
|
||||||
containerView.addSubview(mapView)
|
containerView.addSubview(mapView)
|
||||||
mapView.heightAnchor.constraint(equalToConstant: 140.0).isActive = true
|
mapView.heightAnchor.constraint(equalToConstant: 140.0).isActive = true
|
||||||
mapView.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
mapView.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||||
mapView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
mapView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
mapView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
mapView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
|
|
||||||
previousView = mapView
|
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) {
|
if (displayType == .mapExpanded) {
|
||||||
let buttonClose = createRoundedButton(title: NSLocalizedString("official_close", comment: "").uppercased(), action: #selector(closeTapped(_:)))
|
let buttonClose = EQNRoundedButton.make(title: NSLocalizedString("official_close", comment: "").uppercased(), target: self, action: #selector(closeTapped(_:)))
|
||||||
|
|
||||||
containerView.addSubview(buttonClose)
|
containerView.addSubview(buttonClose)
|
||||||
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
buttonClose.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
|
||||||
|
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||||
buttonClose.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
buttonClose.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||||
buttonClose.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
buttonClose.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||||
buttonClose.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
buttonClose.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||||
@@ -406,12 +323,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
else {
|
else {
|
||||||
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
containerView.eqn_applyShadowAndRoundedCorners()
|
||||||
private func recreateUI() {
|
gradientView.eqn_applyRoundedCorners()
|
||||||
// remove all subviews and recreate the required components
|
|
||||||
containerView.subviews.forEach({ $0.removeFromSuperview() })
|
|
||||||
setupUI()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateUI() {
|
private func updateUI() {
|
||||||
@@ -419,15 +333,13 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
let viewModel = SeismicNetworkViewModel(seismic: seismic)
|
let viewModel = SeismicNetworkViewModel(seismic: seismic)
|
||||||
|
|
||||||
containerView.backgroundColor = colors?.startColor
|
gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
|
||||||
|
|
||||||
let notified = couldBeNotified(for: seismic)
|
|
||||||
titleImageView.image = notified ? UIImage(named: "bell") : UIImage(named: "bell_disabled")
|
|
||||||
|
|
||||||
// update seismic data
|
// update seismic data
|
||||||
placeLabel.text = viewModel.place
|
placeLabel.text = viewModel.place
|
||||||
networkLabel.text = viewModel.network + " " // add some padding
|
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
|
||||||
magnitudeLabel.textColor = colors?.textColor
|
networkLabel.text = String(format: NSLocalizedString("official_provider", comment: ""), viewModel.network)
|
||||||
|
magnitudeLabel.textColor = viewModel.colors.textColor
|
||||||
magnitudeLabel.text = viewModel.magnitude
|
magnitudeLabel.text = viewModel.magnitude
|
||||||
depthLabel.text = viewModel.depth
|
depthLabel.text = viewModel.depth
|
||||||
timeLabel.text = "🕗 \(viewModel.time)"
|
timeLabel.text = "🕗 \(viewModel.time)"
|
||||||
@@ -446,7 +358,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
alertsLabel.text = "⚠️ \(viewModel.users)"
|
alertsLabel.text = "⚠️ \(viewModel.users)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if displayType == .mapExpanded {
|
if displayType == .mapExpanded {
|
||||||
// zoom based on population involved
|
// zoom based on population involved
|
||||||
let longitudeSpan = mapSpanLongitude(population: seismic.population100km)
|
let longitudeSpan = mapSpanLongitude(population: seismic.population100km)
|
||||||
@@ -463,14 +374,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(mapDetailTapped(_:)))
|
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(mapDetailTapped(_:)))
|
||||||
mapView.addGestureRecognizer(tapRecognizer)
|
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 +384,16 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
/// - seismic: Seismic to display
|
/// - seismic: Seismic to display
|
||||||
/// - type: Type of cell
|
/// - type: Type of cell
|
||||||
/// - informations: Informations to show
|
/// - 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.seismic = seismic
|
||||||
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
|
|
||||||
self.displayType = type
|
self.displayType = type
|
||||||
self.informationTypes = informations
|
self.informationTypes = informations
|
||||||
|
self.isPushSelected = isPushSelected
|
||||||
|
|
||||||
if !informations.contains(.time) {
|
if !informations.contains(.time) {
|
||||||
self.informationTypes += [.time]
|
self.informationTypes += [.time]
|
||||||
@@ -500,114 +408,48 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
if seismic.userNumber.intValue > 0 && !informations.contains(.reportUsers) {
|
if seismic.userNumber.intValue > 0 && !informations.contains(.reportUsers) {
|
||||||
self.informationTypes += [.reportUsers]
|
self.informationTypes += [.reportUsers]
|
||||||
}
|
}
|
||||||
|
if seismic.isoCode == "0" && informations.contains(.intensityMap) {
|
||||||
|
self.informationTypes.removeAll { $0 == .intensityMap }
|
||||||
|
}
|
||||||
|
|
||||||
recreateUI()
|
recreateUI()
|
||||||
updateUI()
|
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
|
// MARK: - Actions
|
||||||
|
|
||||||
@objc func shareTapped(_ sender: UIButton) {
|
@objc private func shareTapped(_ sender: UIButton) {
|
||||||
delegate?.seismicNetworkCellDidTapShare(self)
|
delegate?.seismicNetworkCellDidTapShare(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func mapTapped(_ sender: UIButton) {
|
@objc private func mapTapped(_ sender: UIButton) {
|
||||||
if displayType != .mapExpanded {
|
if displayType != .mapExpanded {
|
||||||
delegate?.seismicNetworkCellDidTapMap(self)
|
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) {
|
@objc private func calendarTapped(_ sender: UIButton) {
|
||||||
delegate?.seismicNetworkCellDidTapCalendar(self)
|
delegate?.seismicNetworkCellDidTapCalendar(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func settingsTapped(_ sender: UIButton) {
|
@objc private func settingsTapped(_ sender: UIButton) {
|
||||||
delegate?.seismicNetworkCellDidTapSettings(self)
|
delegate?.seismicNetworkCellDidTapSettings(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func closeTapped(_ sender: UIButton) {
|
@objc private func closeTapped(_ sender: UIButton) {
|
||||||
delegate?.seismicNetworkCellDidTapClose(self)
|
delegate?.seismicNetworkCellDidTapClose(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func mapDetailTapped(_ sender: Any) {
|
@objc private func mapDetailTapped(_ sender: Any) {
|
||||||
delegate?.seismicNetworkCellDidTapMapDetail(self)
|
delegate?.seismicNetworkCellDidTapMapDetail(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func intensityMapTapped(_ sender: Any) {
|
||||||
|
delegate?.seismicNetworkCellDidTapIntensityMapDetail(self)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
private func addSeparator(constraintTo: NSLayoutYAxisAnchor, constanst: CGFloat = 8.0) -> UIView {
|
|
||||||
let separator = UIView()
|
|
||||||
separator.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
separator.backgroundColor = .lightGray
|
|
||||||
containerView.addSubview(separator)
|
|
||||||
|
|
||||||
separator.topAnchor.constraint(equalTo: constraintTo, constant: constanst).isActive = true
|
|
||||||
separator.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
|
||||||
separator.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
|
||||||
separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
|
|
||||||
|
|
||||||
return separator
|
|
||||||
}
|
|
||||||
|
|
||||||
private func createRoundedButton(title: String, action: Selector) -> EQNRoundedButton {
|
|
||||||
let button = EQNRoundedButton(frame: .zero)
|
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
button.addTarget(self, action: action, for: .touchUpInside)
|
|
||||||
button.setTitle(title, for: .normal)
|
|
||||||
button.setTitleColor(AppTheme.Colors.darkGray, for: .normal)
|
|
||||||
button.backgroundColor = UIColor.white.withAlphaComponent(0.5)
|
|
||||||
return button
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the user could be received a notification for this seismic
|
|
||||||
private func couldBeNotified(for seismic: EQNSisma) -> Bool {
|
|
||||||
let settings = EQNNotificheReteSismiche.shared()
|
|
||||||
|
|
||||||
if !settings.isAbilitato {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !settings.listaEnti.contains(seismic.provider) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var notified = true
|
|
||||||
if let radius = Double(settings.distanzaPosizione), seismic.userDistance > radius {
|
|
||||||
notified = false
|
|
||||||
}
|
|
||||||
if let magnitude = Double(settings.energiaSisma), seismic.magnitude.doubleValue < magnitude {
|
|
||||||
notified = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.isAbilitaVicini, seismic.userDistance < 50 {
|
|
||||||
notified = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.isTerremortiForti, let strongMagnitude = Double(settings.energiaTerremotiForti), seismic.magnitude.doubleValue >= strongMagnitude {
|
|
||||||
notified = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return notified
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the zoom for the map, based on the involved population
|
/// Determines the zoom for the map, based on the involved population
|
||||||
private func mapSpanLongitude(population: Double) -> CLLocationDegrees {
|
private func mapSpanLongitude(population: Double) -> CLLocationDegrees {
|
||||||
var zoom: CLLocationDegrees = 1
|
var zoom: CLLocationDegrees = 1
|
||||||
@@ -620,55 +462,4 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
return zoom
|
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+88
-115
@@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
protocol SeismicFiltersViewControllerDelegate: AnyObject {
|
protocol SeismicFiltersViewControllerDelegate: AnyObject {
|
||||||
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController)
|
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController)
|
||||||
@@ -15,13 +16,12 @@ protocol SeismicFiltersViewControllerDelegate: AnyObject {
|
|||||||
class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
||||||
|
|
||||||
private enum RowIdentifier: Int {
|
private enum RowIdentifier: Int {
|
||||||
case magnitudoMinima
|
case sismiNelRaggio
|
||||||
case distanzaMassima
|
case distanzaMassima
|
||||||
case periodoTemporale
|
case magnitudoMinima
|
||||||
case sismiFortiAbilita
|
case sismiRilevanti
|
||||||
case sismiFortiDistanza
|
case sismiTutti
|
||||||
case sismiQualsiasiMagnitudo
|
case sismiPercepiti
|
||||||
case modificaImpostazioni
|
|
||||||
}
|
}
|
||||||
|
|
||||||
weak var delegate: SeismicFiltersViewControllerDelegate?
|
weak var delegate: SeismicFiltersViewControllerDelegate?
|
||||||
@@ -36,29 +36,21 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
|||||||
@IBOutlet private weak var closeButton: UIButton!
|
@IBOutlet private weak var closeButton: UIButton!
|
||||||
|
|
||||||
private var settings = [
|
private var settings = [
|
||||||
SettingItem(type: .slider, title: NSLocalizedString("filter_magnitude", comment: "")),
|
SettingItem(type: .enable, title: NSLocalizedString("filter_show_area", comment: "")),
|
||||||
SettingItem(type: .slider, title: NSLocalizedString("Distanza massima", comment: "")),
|
SettingItem(type: .slider, title: ""),
|
||||||
SettingItem(type: .slider, title: NSLocalizedString("filter_timeframe", comment: "")),
|
SettingItem(type: .slider, title: NSLocalizedString("filter_minimum_magnitude", comment: "")),
|
||||||
SettingItem(type: .enable, title: NSLocalizedString("filter_strong", comment: "")),
|
SettingItem(type: .enable, title: NSLocalizedString("filter_show_relevant", comment: "")),
|
||||||
SettingItem(type: .slider, title: NSLocalizedString("options_strong_magnitude", comment: "")),
|
SettingItem(type: .enable, title: NSLocalizedString("filter_show_all", comment: "")),
|
||||||
SettingItem(type: .enable, title: NSLocalizedString("filter_near", comment: "")),
|
SettingItem(type: .enable, title: NSLocalizedString("filter_show_felt", comment: ""))
|
||||||
SettingItem(type: .enable, title: NSLocalizedString("filter_reflect", 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 let initialFilterType = EQNSeismic.shared.filterOption
|
||||||
private var initialQualsiasiMagnitudo: Bool?
|
private var currentFilterType = EQNSeismic.FilterType.inRadius
|
||||||
|
private var currentMaximumDistance = EQNData.DefaultFilterRadius
|
||||||
|
private var currentMinimumMagnitude = EQNData.DefaultFilterMagnitude
|
||||||
|
|
||||||
private var currentMagnitudoMinima = EQNData.DefaultMagitudoDebole
|
private let dataSourceMaximumDistance = EQNData.filterRadius
|
||||||
private var currentDistanzaMassima = EQNData.DefaultRaggioSisma
|
private let dataSourceMinimumMagnitude = EQNData.filterMagnitude
|
||||||
private var currentPeriodoTemporale = EQNData.DefaultPeriodoTemporale
|
|
||||||
private var currentSismiFortiAbilitati = false
|
|
||||||
private var currentSismiFortiDistanza = EQNData.DefaultMagitudoForte
|
|
||||||
private var currentSismiQualsiasiMagnitudo = false
|
|
||||||
private var currentModificaImpostazioni = false
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
@@ -85,19 +77,9 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func loadDataSource() {
|
private func loadDataSource() {
|
||||||
currentMagnitudoMinima = EQNData.magitudoDebole(for: EQNSeismic.shared.magnitudoMinima)
|
currentFilterType = EQNSeismic.shared.filterOption
|
||||||
if initialMagnitudoMinima == nil {
|
currentMaximumDistance = EQNData.filterRadius(for: EQNSeismic.shared.maximumDistance)
|
||||||
initialMagnitudoMinima = currentMagnitudoMinima
|
currentMinimumMagnitude = EQNData.filterMagnitude(for: EQNSeismic.shared.minimumMagnitude)
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Table view delegate and data source
|
// MARK: - Table view delegate and data source
|
||||||
@@ -107,45 +89,36 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||||
|
return UITableViewCell()
|
||||||
|
}
|
||||||
|
|
||||||
let setting = settings[indexPath.row]
|
let setting = settings[indexPath.row]
|
||||||
|
let isLocationAvailable = EQNUser.default().lastPosition != nil
|
||||||
|
|
||||||
switch setting.type {
|
switch setting.type {
|
||||||
case .slider:
|
case .slider:
|
||||||
let cell = SettingSliderTableViewCell(style: .default, reuseIdentifier: nil)
|
let cell = SettingSliderTableViewCell(style: .default, reuseIdentifier: nil)
|
||||||
cell.titleLabel.text = setting.displayTitle
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
|
||||||
if indexPath.row == RowIdentifier.magnitudoMinima.rawValue {
|
let isFilterInRadiusEnabled = currentFilterType == .inRadius && isLocationAvailable
|
||||||
cell.configureSlider(with: dataSourceMagnitudoMinima, current: currentMagnitudoMinima)
|
switch identifier {
|
||||||
cell.valueChanged = { [unowned self] value in
|
case .distanzaMassima:
|
||||||
currentMagnitudoMinima = value
|
cell.isDisabled = !isFilterInRadiusEnabled
|
||||||
EQNSeismic.shared.magnitudoMinima = value.value
|
cell.isUserInteractionEnabled = isFilterInRadiusEnabled
|
||||||
EQNSeismic.shared.saveFilters()
|
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
|
||||||
|
cell.valueChanged = { [weak self] value in
|
||||||
|
self?.onChangeMaximumDistance(value)
|
||||||
}
|
}
|
||||||
cell.dragEnded = { [unowned self] in
|
case .magnitudoMinima:
|
||||||
showWarningAlertIfNeeded(for: currentMagnitudoMinima)
|
cell.isDisabled = !isFilterInRadiusEnabled
|
||||||
}
|
cell.isUserInteractionEnabled = isFilterInRadiusEnabled
|
||||||
} else if indexPath.row == RowIdentifier.distanzaMassima.rawValue {
|
cell.configureSlider(with: dataSourceMinimumMagnitude, current: currentMinimumMagnitude)
|
||||||
cell.configureSlider(with: dataSourceDistanzaMassima, current: currentDistanzaMassima)
|
cell.valueChanged = { [weak self] value in
|
||||||
cell.valueChanged = { [unowned self] value in
|
self?.onChangeMinimumMagnitude(value)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
@@ -154,30 +127,37 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
|||||||
cell.titleLabel.text = setting.displayTitle
|
cell.titleLabel.text = setting.displayTitle
|
||||||
cell.detailTextLabel?.text = setting.subtitle
|
cell.detailTextLabel?.text = setting.subtitle
|
||||||
|
|
||||||
if indexPath.row == RowIdentifier.sismiFortiAbilita.rawValue {
|
switch identifier {
|
||||||
cell.toggleSwitch.isOn = currentSismiFortiAbilitati
|
case .sismiNelRaggio:
|
||||||
cell.valueChanged = { [unowned self] value in
|
let isCurrentFilter = currentFilterType == .inRadius
|
||||||
currentSismiFortiAbilitati = value
|
cell.isDisabled = !isLocationAvailable
|
||||||
EQNSeismic.shared.sismiFortiAbilitati = value
|
cell.toggleSwitch.isOn = isCurrentFilter
|
||||||
EQNSeismic.shared.saveFilters()
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeFilterOption(enabled, filter: .inRadius)
|
||||||
loadDataSource()
|
|
||||||
tableView.reloadData()
|
|
||||||
}
|
}
|
||||||
} else if indexPath.row == RowIdentifier.sismiQualsiasiMagnitudo.rawValue {
|
cell.errorLabel.text = !isLocationAvailable ? NSLocalizedString("filter_nolocation", comment: "") : nil
|
||||||
cell.toggleSwitch.isOn = currentSismiQualsiasiMagnitudo
|
case .sismiRilevanti:
|
||||||
cell.valueChanged = { [unowned self] value in
|
let isCurrentFilter = currentFilterType == .positionRelevant
|
||||||
currentSismiQualsiasiMagnitudo = value
|
cell.isDisabled = !isLocationAvailable
|
||||||
EQNSeismic.shared.sismiQualsiasiAbilitati = value
|
cell.toggleSwitch.isOn = isCurrentFilter
|
||||||
EQNSeismic.shared.saveFilters()
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeFilterOption(enabled, filter: .positionRelevant)
|
||||||
}
|
}
|
||||||
} else if indexPath.row == RowIdentifier.modificaImpostazioni.rawValue {
|
cell.errorLabel.text = !isLocationAvailable ? NSLocalizedString("filter_nolocation", comment: "") : nil
|
||||||
cell.toggleSwitch.isOn = currentModificaImpostazioni
|
case .sismiTutti:
|
||||||
cell.valueChanged = { [unowned self] value in
|
let isCurrentFilter = currentFilterType == .worldWide
|
||||||
currentModificaImpostazioni = value
|
cell.toggleSwitch.isOn = isCurrentFilter
|
||||||
EQNSeismic.shared.modificaImpostazioniAbilitato = value
|
cell.valueChanged = { [weak self] enabled in
|
||||||
EQNSeismic.shared.saveFilters()
|
self?.onChangeFilterOption(enabled, filter: .worldWide)
|
||||||
}
|
}
|
||||||
|
case .sismiPercepiti:
|
||||||
|
let isCurrentFilter = currentFilterType == .userFelt
|
||||||
|
cell.toggleSwitch.isOn = isCurrentFilter
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeFilterOption(enabled, filter: .userFelt)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
@@ -190,41 +170,34 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
|||||||
|
|
||||||
@IBAction func exitTapped(_ sender: UIButton) {
|
@IBAction func exitTapped(_ sender: UIButton) {
|
||||||
// data needs to be re-downloaded if (or conditions):
|
// 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
|
// a) filter type is changed
|
||||||
// b) show any near earthquake is active and value is changed
|
needsDataUpdate = initialFilterType != currentFilterType
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate?.seismicFiltersControllerDidUpdateFilters(self)
|
delegate?.seismicFiltersControllerDidUpdateFilters(self)
|
||||||
updateNotificationSettingsIfNeeded()
|
|
||||||
|
|
||||||
dismiss(animated: true, completion: nil)
|
dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func showWarningAlertIfNeeded(for value: EQNGenericValue) {
|
private func onChangeFilterOption(_ enabled: Bool, filter: EQNSeismic.FilterType) {
|
||||||
guard let magnitude = Double(value.value), magnitude < 2.0 else { return }
|
currentFilterType = filter
|
||||||
|
EQNSeismic.shared.filterOption = filter
|
||||||
|
EQNSeismic.shared.saveFilters()
|
||||||
|
|
||||||
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""), message: NSLocalizedString("options_low_magnitude", comment: ""), preferredStyle: .alert)
|
loadDataSource()
|
||||||
alert.addAction(UIAlertAction(title: NSLocalizedString("main_understood", comment: ""), style: .default, handler: nil))
|
tableView.reloadData()
|
||||||
present(alert, animated: true, completion: nil)
|
}
|
||||||
|
|
||||||
|
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
|
||||||
|
currentMaximumDistance = item
|
||||||
|
EQNSeismic.shared.maximumDistance = item.value
|
||||||
|
EQNSeismic.shared.saveFilters()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateNotificationSettingsIfNeeded() {
|
private func onChangeMinimumMagnitude(_ item: EQNGenericValue) {
|
||||||
// if the switch is enabled, update also the settings notification
|
currentMinimumMagnitude = item
|
||||||
guard currentModificaImpostazioni == true else { return }
|
EQNSeismic.shared.minimumMagnitude = item.value
|
||||||
|
EQNSeismic.shared.saveFilters()
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-6
@@ -29,17 +29,16 @@ class SeismicCardSettingsViewController: UIViewController {
|
|||||||
@IBOutlet private weak var informationPopulationSwitch: UISwitch!
|
@IBOutlet private weak var informationPopulationSwitch: UISwitch!
|
||||||
@IBOutlet private weak var closeButton: UIButton!
|
@IBOutlet private weak var closeButton: UIButton!
|
||||||
|
|
||||||
private var informations = [SeismicNetworkTableViewCell.InformationType]()
|
private var informations: [SeismicNetworkTableViewCell.InformationType] {
|
||||||
|
get { AppPreferences.shared.seismicNetworksInformations }
|
||||||
|
set { AppPreferences.shared.seismicNetworksInformations = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
if let saved = UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) as? [Int] {
|
|
||||||
informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
setupUI()
|
setupUI()
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
@@ -84,7 +83,6 @@ class SeismicCardSettingsViewController: UIViewController {
|
|||||||
toggle(information: .population)
|
toggle(information: .population)
|
||||||
}
|
}
|
||||||
|
|
||||||
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
|
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//
|
||||||
|
// SeismicNetworkData.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 31/01/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
//
|
||||||
|
// SeismicNetworkScrollIndicatorView.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 31/01/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import CoreGraphics
|
||||||
|
|
||||||
|
|
||||||
|
class SeismicNetworkScrollIndicatorView: UIView {
|
||||||
|
|
||||||
|
private static let HighlightColor: UIColor = .red
|
||||||
|
|
||||||
|
var seismics: [SeismicNetworkViewModel] = [] {
|
||||||
|
didSet {
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var highlighted: SeismicNetworkViewModel? {
|
||||||
|
didSet {
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var numberOfRectangles: Int {
|
||||||
|
seismics.count
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func draw(_ rect: CGRect) {
|
||||||
|
guard numberOfRectangles > 0 else { return }
|
||||||
|
|
||||||
|
let context = UIGraphicsGetCurrentContext()
|
||||||
|
let rectStandardWidth = rect.width
|
||||||
|
let rectStandardHeight = rect.height / CGFloat(numberOfRectangles)
|
||||||
|
let rectHighlightedMinHeight: CGFloat = 4
|
||||||
|
|
||||||
|
let smallRectangles = rectStandardHeight < 10
|
||||||
|
let highlightIndex = seismics.firstIndex(where: { $0 == highlighted }) ?? 100_000
|
||||||
|
|
||||||
|
|
||||||
|
seismics.enumerated().forEach { index, seismic in
|
||||||
|
// Disegniamo un rettangolo per ogni sisma, quello evidenziato deve avere un contorno rosso.
|
||||||
|
// Ci sono situazioni in cui ci sono molti sismi da mostrare, quindi in quel caso facciamo alcune modifiche:
|
||||||
|
// - usiamo un'altezza minima per il sisma evidenziato
|
||||||
|
// - per il sisma evidenziato, anche il contenuto è rosso (e non solo il bordo)
|
||||||
|
// - negli altri sismi, non mostriamo il bordo
|
||||||
|
|
||||||
|
if highlightIndex == index {
|
||||||
|
// Stiamo disegnando il sisma evidenziato.
|
||||||
|
// Valutiamo se utilizzare l'altezza minima.
|
||||||
|
let rectHeight = smallRectangles ? rectHighlightedMinHeight : rectStandardHeight
|
||||||
|
let yPosition = CGFloat(index) * rectStandardHeight
|
||||||
|
let rectangle = CGRect(x: 0, y: yPosition, width: rectStandardWidth, height: rectHeight)
|
||||||
|
|
||||||
|
let fillColor = smallRectangles ? Self.HighlightColor : seismic.colors.textColor.withAlphaComponent(0.3)
|
||||||
|
context?.setFillColor(fillColor.cgColor)
|
||||||
|
context?.fill(rectangle)
|
||||||
|
|
||||||
|
if !smallRectangles {
|
||||||
|
// disegniamo il bordo solo se i rettangoli non sono piccoli
|
||||||
|
let borderWidth: CGFloat = 2.0
|
||||||
|
context?.setStrokeColor(Self.HighlightColor.cgColor)
|
||||||
|
context?.setLineWidth(borderWidth) // Spessore del bordo
|
||||||
|
context?.stroke(rectangle.insetBy(dx: borderWidth / 2, dy: borderWidth / 2)) // Evita che il bordo venga tagliato
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Stiamo disegnando i sismi non evidenziati, utilizziamo sempre l'altezza predefinita
|
||||||
|
// Dobbiamo eventualmente calcolare un offset aggiuntivo,
|
||||||
|
// perchè il sisma evidenziato ha un'altezza maggiore (se i rettangoli sono piccoli)
|
||||||
|
let rectHeight = rectStandardHeight
|
||||||
|
|
||||||
|
var offset: CGFloat = 0
|
||||||
|
if index > highlightIndex && smallRectangles {
|
||||||
|
// calcoliamo l'offset prima del rettangolo evidenziato
|
||||||
|
let preOffset = CGFloat(highlightIndex - 1) * rectStandardHeight
|
||||||
|
// offset diverso dovuto all'altezza diversa del rettangolo evidenziato
|
||||||
|
let highlightOffset = rectHighlightedMinHeight
|
||||||
|
// calcoliamo l'offset tra il rettangolo evidenziato e quello corrente
|
||||||
|
let postOffset = CGFloat(index - highlightIndex) * rectStandardHeight
|
||||||
|
offset = preOffset + highlightOffset + postOffset
|
||||||
|
} else {
|
||||||
|
// siamo prima del rettangolo evidenziato, non abbiamo calcoli da fare
|
||||||
|
offset = CGFloat(index) * rectHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
let rectangle = CGRect(x: 0, y: offset, width: rectStandardWidth, height: rectHeight)
|
||||||
|
|
||||||
|
let fillColor = seismic.colors.textColor.withAlphaComponent(0.3)
|
||||||
|
context?.setFillColor(fillColor.cgColor)
|
||||||
|
context?.fill(rectangle)
|
||||||
|
|
||||||
|
if !smallRectangles {
|
||||||
|
// altrimenti un bordo grigio
|
||||||
|
let borderWidth: CGFloat = 0.5
|
||||||
|
context?.setStrokeColor(AppTheme.Colors.gray.cgColor)
|
||||||
|
context?.setLineWidth(borderWidth) // Spessore del bordo
|
||||||
|
context?.stroke(rectangle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+142
-32
@@ -8,24 +8,75 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
struct MagnitudeColors {
|
||||||
|
let textColor: UIColor
|
||||||
|
let startColor: UIColor
|
||||||
|
let endColor: UIColor
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct SeismicNetworkMinimalViewModel {
|
||||||
|
private let seismic: EQNSisma
|
||||||
|
let place: String
|
||||||
|
let isPreliminary: Bool
|
||||||
|
let magnitude: String
|
||||||
|
let time: String
|
||||||
|
let distance: String
|
||||||
|
let smartphones: String
|
||||||
|
let users: String
|
||||||
|
let colors: MagnitudeColors
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(seismic: EQNSisma) {
|
||||||
|
self.seismic = seismic
|
||||||
|
self.place = seismic.place
|
||||||
|
let isPreliminary = seismic.preliminary.intValue > 0
|
||||||
|
self.isPreliminary = isPreliminary
|
||||||
|
self.magnitude = String(format: "%.1f", seismic.magnitude.doubleValue)
|
||||||
|
|
||||||
|
let time = EQNUtility.formattedString(forTimeDifference: Int(seismic.timeDifference))
|
||||||
|
self.time = time
|
||||||
|
|
||||||
|
let distanceRounded = Int(round(seismic.userDistance))
|
||||||
|
self.distance = "\(distanceRounded) km"
|
||||||
|
|
||||||
|
if seismic.smartphoneNumber.intValue > 0 {
|
||||||
|
self.smartphones = String(format: NSLocalizedString("official_smartphones", comment: ""), seismic.smartphoneNumber)
|
||||||
|
} else {
|
||||||
|
self.smartphones = ""
|
||||||
|
}
|
||||||
|
if seismic.userNumber.intValue > 0 {
|
||||||
|
self.users = String(format: NSLocalizedString("official_reports", comment: ""), seismic.userNumber)
|
||||||
|
} else {
|
||||||
|
self.users = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
struct SeismicNetworkViewModel {
|
struct SeismicNetworkViewModel {
|
||||||
|
|
||||||
var place: String
|
private let seismic: EQNSisma
|
||||||
var network: String
|
let place: String
|
||||||
var isPreliminary: Bool
|
let network: String
|
||||||
var magnitude: String
|
let isPreliminary: Bool
|
||||||
var depth: String
|
let magnitude: String
|
||||||
var time: String
|
let depth: String
|
||||||
var distance: String
|
let time: String
|
||||||
var coordinate: String
|
let distance: String
|
||||||
var population: String
|
let coordinate: String
|
||||||
var smartphones: String
|
let population: String
|
||||||
var users: String
|
let smartphones: String
|
||||||
|
let users: String
|
||||||
|
let colors: MagnitudeColors
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
init(seismic: EQNSisma) {
|
init(seismic: EQNSisma) {
|
||||||
|
self.seismic = seismic
|
||||||
self.place = seismic.place
|
self.place = seismic.place
|
||||||
self.network = seismic.provider
|
self.network = seismic.provider
|
||||||
|
|
||||||
@@ -38,7 +89,7 @@ struct SeismicNetworkViewModel {
|
|||||||
self.depth = ""
|
self.depth = ""
|
||||||
} else {
|
} else {
|
||||||
self.magnitude = String(format: "%.1f%@", seismic.magnitude.doubleValue, seismic.magnitudeType)
|
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
|
// we need to check agains null values, because sometimes WS returns invalid dates
|
||||||
@@ -52,11 +103,11 @@ struct SeismicNetworkViewModel {
|
|||||||
|
|
||||||
// distance
|
// distance
|
||||||
let distanceRounded = Int(round(seismic.userDistance))
|
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)
|
let coordinateText = EQNUtility.coordinateString(coordinate: seismic.coordinate.coordinate)
|
||||||
self.coordinate = "\(coordinateText)"
|
self.coordinate = "\(coordinateText)"
|
||||||
|
|
||||||
let population = Self.formatPopulation(seismic.population100km)
|
let population = formatPopulation(seismic.population100km)
|
||||||
self.population = String(format: NSLocalizedString("share_radius100", comment: ""), population)
|
self.population = String(format: NSLocalizedString("share_radius100", comment: ""), population)
|
||||||
|
|
||||||
if seismic.smartphoneNumber.intValue > 0 {
|
if seismic.smartphoneNumber.intValue > 0 {
|
||||||
@@ -69,23 +120,82 @@ struct SeismicNetworkViewModel {
|
|||||||
} else {
|
} else {
|
||||||
self.users = ""
|
self.users = ""
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
|
||||||
// MARK: - Private
|
|
||||||
|
|
||||||
/// Format population value (ex. 1.5M, 2.4k)
|
|
||||||
private static func formatPopulation(_ population: Double) -> String {
|
|
||||||
var populationString = ""
|
|
||||||
if population > 999_999 {
|
|
||||||
let roundedPopulation = round(population / 100_000) / 10
|
|
||||||
populationString = "\(roundedPopulation)M"
|
|
||||||
} else if population > 999 {
|
|
||||||
let roundedPopulation = round(population / 100) / 10
|
|
||||||
populationString = "\(roundedPopulation)K"
|
|
||||||
} else {
|
|
||||||
let roundedPopulation = round(population)
|
|
||||||
populationString = "\(roundedPopulation)"
|
|
||||||
}
|
|
||||||
return populationString
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SeismicNetworkViewModel: Equatable {
|
||||||
|
static func == (lhs: SeismicNetworkViewModel, rhs: SeismicNetworkViewModel) -> Bool {
|
||||||
|
return lhs.seismic == rhs.seismic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
/// Calculate colors to use for text and background of the cell
|
||||||
|
private func calculateColors(for magnitude: Double) -> MagnitudeColors {
|
||||||
|
var textColor = UIColor.black
|
||||||
|
|
||||||
|
var r = 0, g = 0, b = 0
|
||||||
|
if (magnitude < 2.0) {
|
||||||
|
let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0)
|
||||||
|
r = Int(round(200.0 + (255.0 - 200.0) * fraction))
|
||||||
|
g = Int(round(226.0 + (255.0 - 226.0) * fraction))
|
||||||
|
b = Int(round(196.0 + (255.0 - 196.0) * fraction))
|
||||||
|
textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 160.0 / 255.0, alpha: 1.0)
|
||||||
|
}
|
||||||
|
if (magnitude >= 2.0 && magnitude < 3.5) {
|
||||||
|
let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2)
|
||||||
|
r = Int(round(136.0 + (200.0 - 136.0) * fraction))
|
||||||
|
g = Int(round(175.0 + (226.0 - 175.0) * fraction))
|
||||||
|
b = Int(round(131.0 + (196.0 - 131.0) * fraction))
|
||||||
|
textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0)
|
||||||
|
}
|
||||||
|
if (magnitude >= 3.5 && magnitude < 4.5) {
|
||||||
|
let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5)
|
||||||
|
r = 252
|
||||||
|
g = Int(round(233.0 + (253.0 - 233.0) * fraction))
|
||||||
|
b = Int(round(179.0 + (209.0 - 179.0) * fraction))
|
||||||
|
textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||||
|
}
|
||||||
|
if (magnitude >= 4.5 && magnitude < 5.5) {
|
||||||
|
let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5)
|
||||||
|
r = 252
|
||||||
|
g = Int(round(159.0 + (197.0 - 159.0) * fraction))
|
||||||
|
b = Int(round(161.0 + (197.0 - 161.0) * fraction))
|
||||||
|
textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||||
|
}
|
||||||
|
if (magnitude >= 5.5) {
|
||||||
|
let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5)
|
||||||
|
r = Int(round(190.0 + (254.0 - 190.0) * fraction))
|
||||||
|
g = Int(round(124.0 + (219.0 - 124.0) * fraction))
|
||||||
|
b = 255
|
||||||
|
textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let r2 = min(r + 30, 255)
|
||||||
|
let g2 = min(g + 30, 255)
|
||||||
|
let b2 = min(b + 30, 255)
|
||||||
|
|
||||||
|
let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0)
|
||||||
|
let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0)
|
||||||
|
|
||||||
|
return .init(textColor: textColor, startColor: startColor, endColor: endColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format population value (ex. 1.5M, 2.4k)
|
||||||
|
private func formatPopulation(_ population: Double) -> String {
|
||||||
|
var populationString = ""
|
||||||
|
if population > 999_999 {
|
||||||
|
let roundedPopulation = round(population / 100_000) / 10
|
||||||
|
populationString = "\(roundedPopulation)M"
|
||||||
|
} else if population > 999 {
|
||||||
|
let roundedPopulation = round(population / 100) / 10
|
||||||
|
populationString = "\(roundedPopulation)K"
|
||||||
|
} else {
|
||||||
|
let roundedPopulation = round(population)
|
||||||
|
populationString = "\(roundedPopulation)"
|
||||||
|
}
|
||||||
|
return populationString
|
||||||
|
}
|
||||||
|
|||||||
+250
@@ -0,0 +1,250 @@
|
|||||||
|
//
|
||||||
|
// SeismicNetworksIntensityMapViewController.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 27/02/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MapKit
|
||||||
|
|
||||||
|
class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
|
||||||
|
|
||||||
|
private let seismic: EQNSisma
|
||||||
|
private var shakemaps: [EQNShakemap] = []
|
||||||
|
private var pinStyle: MapPinStyle {
|
||||||
|
get { AppPreferences.shared.mapPinStyle }
|
||||||
|
set { AppPreferences.shared.mapPinStyle = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
override var isFilterViewVisible: Bool { false }
|
||||||
|
override var isCloseButtonVisible: Bool { false }
|
||||||
|
|
||||||
|
// MARK: - UI
|
||||||
|
|
||||||
|
lazy var descriptionView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.backgroundColor = AppTheme.Colors.pureBlue
|
||||||
|
|
||||||
|
let descriptionLabel = UILabel()
|
||||||
|
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
descriptionLabel.numberOfLines = 0
|
||||||
|
descriptionLabel.textColor = .white
|
||||||
|
descriptionLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
|
descriptionLabel.textAlignment = .center
|
||||||
|
descriptionLabel.text = NSLocalizedString("shakemap_description", comment: "")
|
||||||
|
|
||||||
|
view.addSubview(descriptionLabel)
|
||||||
|
descriptionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 2.0).isActive = true
|
||||||
|
descriptionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -2.0).isActive = true
|
||||||
|
descriptionLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 2.0).isActive = true
|
||||||
|
descriptionLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -2.0).isActive = true
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(
|
||||||
|
seismic: EQNSisma
|
||||||
|
) {
|
||||||
|
self.seismic = seismic
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("Plase use init(seismic:) instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func extraUI() {
|
||||||
|
super.extraUI()
|
||||||
|
|
||||||
|
view.addSubview(descriptionView)
|
||||||
|
descriptionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||||
|
descriptionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||||
|
descriptionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override func configureUI() {
|
||||||
|
super.configureUI()
|
||||||
|
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: .init(handler: { [weak self] _ in
|
||||||
|
self?.dismiss(animated: true)
|
||||||
|
}))
|
||||||
|
navigationItem.rightBarButtonItems = [
|
||||||
|
UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), primaryAction: .init(handler: { [weak self] _ in
|
||||||
|
self?.shareScreenshot()
|
||||||
|
})),
|
||||||
|
UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), primaryAction: .init(handler: { [weak self] _ in
|
||||||
|
self?.nextPinStyle()
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
override func registerMapAnnotationViews() {
|
||||||
|
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full))
|
||||||
|
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light))
|
||||||
|
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func loadDataSource() {
|
||||||
|
Task {
|
||||||
|
let result = try await APIService.shared.fetchShakemap(isoCode: seismic.isoCode)
|
||||||
|
elaborateShakemaps(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func elaborateMapCenter() {
|
||||||
|
setMapCenter(for: seismic.coordinate, span: MKCoordinateSpan(latitudeDelta: 2, longitudeDelta: 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func elaborateShakemaps(_ shakemaps: [EQNShakemap]) {
|
||||||
|
self.shakemaps = shakemaps
|
||||||
|
|
||||||
|
var shakemapPolyline = [MKPolyline]()
|
||||||
|
var shakemapAnnotations: [MKAnnotation] = []
|
||||||
|
for shakemap in shakemaps {
|
||||||
|
// create coordinates for current shakemap
|
||||||
|
let coordinates = zip(shakemap.lat, shakemap.lon).map { lat, lon in
|
||||||
|
CLLocationCoordinate2D(latitude: Double(lat) / 10_000.0, longitude: Double(lon) / 10_000.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let intensityColors = getColors(for: shakemap.intensity)
|
||||||
|
|
||||||
|
// create line to show on map
|
||||||
|
let polyline = ShakemapPolyline(coordinates: coordinates, count: coordinates.count)
|
||||||
|
polyline.intensity = shakemap.intensity
|
||||||
|
polyline.intensityColor = intensityColors.lineColor
|
||||||
|
shakemapPolyline.append(polyline)
|
||||||
|
|
||||||
|
// create annotation to show on top of the line
|
||||||
|
let middlePoint = coordinates[coordinates.count / 2]
|
||||||
|
let annotation = EQNMapAnnotationShakemap(coordinate: middlePoint, shakemap: shakemap)
|
||||||
|
annotation.intensityColor = intensityColors.lineColor
|
||||||
|
annotation.intensityTextColor = intensityColors.textColor
|
||||||
|
shakemapAnnotations.append(annotation)
|
||||||
|
}
|
||||||
|
|
||||||
|
let seismicAnnotation = EQNMapAnnotationSeismic(seismic: seismic)
|
||||||
|
shakemapAnnotations.append(seismicAnnotation)
|
||||||
|
|
||||||
|
// draw lines
|
||||||
|
mapView.addOverlays(shakemapPolyline)
|
||||||
|
updateMap(with: shakemapAnnotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func nextPinStyle() {
|
||||||
|
pinStyle.next()
|
||||||
|
reloadMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func shareScreenshot() {
|
||||||
|
let screenshot = createSnapshot(prepare: {
|
||||||
|
descriptionView.isHidden = true
|
||||||
|
}, restore: {
|
||||||
|
descriptionView.isHidden = false
|
||||||
|
})
|
||||||
|
|
||||||
|
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
||||||
|
present(controller, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - MKMapViewDelegate
|
||||||
|
|
||||||
|
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
||||||
|
switch annotation {
|
||||||
|
case let shakemapAnnotation as EQNMapAnnotationShakemap:
|
||||||
|
return shakemapAnnotation.toAnnotationView(mapView: mapView, style: .light)
|
||||||
|
case let seismicAnnotation as EQNMapAnnotationSeismic:
|
||||||
|
return seismicAnnotation.toAnnotationView(mapView: mapView, style: pinStyle)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||||
|
if let polyline = overlay as? ShakemapPolyline {
|
||||||
|
let renderer = MKPolylineRenderer(polyline: polyline)
|
||||||
|
renderer.strokeColor = polyline.intensityColor
|
||||||
|
renderer.lineWidth = 6.0
|
||||||
|
return renderer
|
||||||
|
}
|
||||||
|
return MKOverlayRenderer()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getColors(for intensity: Float) -> (textColor: UIColor, lineColor: UIColor) {
|
||||||
|
let shakemapColors: [String] = [
|
||||||
|
"#3E26A8","#3E27AC","#3F28AF","#3F29B2","#402AB4","#402BB7","#412CBA","#412DBD","#422EBF","#422FC2",
|
||||||
|
"#4330C5","#4331C8","#4332CA","#4433CD","#4434D0","#4535D2","#4537D5","#4538D7","#4639D9","#463ADC",
|
||||||
|
"#463BDE","#463DE0","#473EE1","#473FE3","#4741E5","#4742E6","#4744E8","#4745E9","#4746EB","#4848EC",
|
||||||
|
"#4849ED","#484BEE","#484CF0","#484EF1","#484FF2","#4850F3","#4852F4","#4853F5","#4854F6","#4756F7",
|
||||||
|
"#4757F7","#4759F8","#475AF9","#475BFA","#475DFA","#465EFB","#4660FB","#4661FC","#4562FC","#4564FD",
|
||||||
|
"#4465FD","#4367FD","#4368FE","#426AFE","#416BFE","#406DFE","#3F6EFF","#3E70FF","#3C71FF","#3B73FF",
|
||||||
|
"#3974FF","#3876FE","#3677FE","#3579FD","#337AFD","#327CFC","#317DFC","#307FFB","#2F80FA","#2F82FA",
|
||||||
|
"#2E83F9","#2E84F8","#2E86F8","#2E87F7","#2D88F6","#2D8AF5","#2D8BF4","#2D8CF3","#2D8EF2","#2C8FF1",
|
||||||
|
"#2C90F0","#2B91EF","#2A93EE","#2994ED","#2895EC","#2797EB","#2798EA","#2699E9","#269AE8","#259BE8",
|
||||||
|
"#259CE7","#249EE6","#249FE5","#23A0E5","#23A1E4","#22A2E4","#21A3E3","#20A5E3","#1FA6E2","#1EA7E1",
|
||||||
|
"#1DA8E1","#1DA9E0","#1CAADF","#1BABDE","#1AACDD","#19ADDC","#17AEDA","#16AFD9","#14B0D8","#12B1D6",
|
||||||
|
"#10B2D5","#0EB3D4","#0BB3D2","#08B4D1","#06B5CF","#04B6CE","#02B7CC","#01B7CA","#00B8C9","#00B9C7",
|
||||||
|
"#00BAC6","#01BAC4","#02BBC2","#04BBC1","#06BCBF","#09BDBD","#0DBDBC","#10BEBA","#14BEB8","#17BFB6",
|
||||||
|
"#1AC0B5","#1DC0B3","#20C1B1","#23C1AF","#25C2AE","#27C2AC","#29C3AA","#2BC3A8","#2CC4A6","#2EC4A5",
|
||||||
|
"#2FC5A3","#31C5A1","#32C69F","#33C79D","#35C79B","#36C899","#38C896","#39C994","#3BC992","#3DCA90",
|
||||||
|
"#40CA8D","#42CA8B","#45CB89","#48CB86","#4BCB84","#4ECC81","#51CC7F","#54CC7C","#57CC7A","#5ACC77",
|
||||||
|
"#5ECD74","#61CD72","#64CD6F","#67CD6C","#6BCD69","#6ECD66","#72CD64","#76CC61","#79CC5E","#7DCC5B",
|
||||||
|
"#81CC59","#84CC56","#88CB53","#8BCB51","#8FCB4E","#93CA4B","#96CA48","#9AC946","#9DC943","#A1C840",
|
||||||
|
"#A4C83E","#A7C73B","#ABC739","#AEC637","#B2C635","#B5C533","#B8C431","#BBC42F","#BEC32D","#C2C32C",
|
||||||
|
"#C5C22A","#C8C129","#CBC128","#CEC027","#D0BF27","#D3BF27","#D6BE27","#D9BE28","#DBBD28","#DEBC29",
|
||||||
|
"#E1BC2A","#E3BC2B","#E6BB2D","#E8BB2E","#EABA30","#ECBA32","#EFBA35","#F1BA37","#F3BA39","#F5BA3B",
|
||||||
|
"#F7BA3D","#F9BA3E","#FBBB3E","#FCBC3E","#FEBD3D","#FEBE3C","#FEC03B","#FEC13A","#FEC239","#FEC438",
|
||||||
|
"#FEC537","#FEC735","#FEC834","#FECA33","#FDCB32","#FDCD31","#FDCE31","#FCD030","#FBD22F","#FBD32E",
|
||||||
|
"#FAD52E","#F9D62D","#F9D82C","#F8D92B","#F7DB2A","#F7DD2A","#F6DE29","#F6E028","#F5E128","#F5E327",
|
||||||
|
"#F5E526","#F5E626","#F5E825","#F5E924","#F5EB23","#F5EC22","#F5EE21","#F6EF20","#F6F11F","#F6F21E",
|
||||||
|
"#F7F41C","#F7F51B","#F8F71A","#F8F818","#F9F916","#F9FB15"
|
||||||
|
]
|
||||||
|
|
||||||
|
let minIntensity = shakemaps.map { $0.intensity }.min() ?? 0
|
||||||
|
let maxIntensity = shakemaps.map { $0.intensity }.max() ?? 255
|
||||||
|
let indexColor = if minIntensity == maxIntensity {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
Int(round((intensity-minIntensity)/(maxIntensity-minIntensity)*255))
|
||||||
|
}
|
||||||
|
|
||||||
|
let lineColor = UIColor(hexString: shakemapColors[indexColor]) ?? .white
|
||||||
|
let textColor: UIColor = indexColor < 65 ? .white : .black
|
||||||
|
|
||||||
|
return (textColor: textColor, lineColor: lineColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EQNMapAnnotationShakemap {
|
||||||
|
func toAnnotationView(
|
||||||
|
mapView: MKMapView,
|
||||||
|
style: MapPinStyle,
|
||||||
|
isUserSelection: Bool = false
|
||||||
|
) -> MKAnnotationView? {
|
||||||
|
switch style {
|
||||||
|
case .full, .light:
|
||||||
|
let identifier = EQNSeismicAnnotationView.identifier(for: style)
|
||||||
|
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
|
||||||
|
annotationView.magnitude = String(format: "%.1f", shakemap.intensity)
|
||||||
|
annotationView.magnitudeTextColor = intensityTextColor ?? .black
|
||||||
|
annotationView.magnitudeBackgroundColor = intensityColor
|
||||||
|
annotationView.canShowCallout = true
|
||||||
|
return annotationView
|
||||||
|
case .circle:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class ShakemapPolyline: MKPolyline {
|
||||||
|
var intensity: Float = 0
|
||||||
|
var intensityColor: UIColor = .white
|
||||||
|
}
|
||||||
+123
-49
@@ -8,17 +8,25 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MapKit
|
import MapKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
|
protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
|
||||||
func seismicNetworksMapDetailControllerWillUpdateData(_ controller: SeismicNetworksMapDetailViewController, needsDataUpdate: Bool)
|
func seismicNetworksMapDetailControllerWillUpdateData(_ controller: SeismicNetworksMapDetailViewController, needsDataUpdate: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||||
|
|
||||||
|
private var pinStyle: MapPinStyle {
|
||||||
|
get { AppPreferences.shared.mapPinStyle }
|
||||||
|
set { AppPreferences.shared.mapPinStyle = newValue }
|
||||||
|
}
|
||||||
|
private let eqnSeismic = EQNSeismic.shared
|
||||||
|
|
||||||
// MARK: - State
|
// MARK: - State
|
||||||
|
|
||||||
|
override var isCloseButtonVisible: Bool { false }
|
||||||
override var isFilterViewVisible: Bool {
|
override var isFilterViewVisible: Bool {
|
||||||
// a custom filter view id displayed
|
// a custom filter view is displayed
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
weak var delegate: SeismicNetworksMapDetailViewControllerDelegate?
|
weak var delegate: SeismicNetworksMapDetailViewControllerDelegate?
|
||||||
@@ -41,10 +49,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
seismicsFilterLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
seismicsFilterLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
||||||
seismicsFilterLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
|
seismicsFilterLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
|
||||||
seismicsFilterLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, 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
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -75,6 +80,24 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
fatalError("init(coder:) is not available, please use init(seismic:allSeismics:)")
|
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
|
// MARK: - Public
|
||||||
|
|
||||||
func updateSeismics(_ seismics: [EQNSisma]) {
|
func updateSeismics(_ seismics: [EQNSisma]) {
|
||||||
@@ -83,7 +106,9 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func registerMapAnnotationViews() {
|
override func registerMapAnnotationViews() {
|
||||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.DoubleLineIdentifier)
|
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full))
|
||||||
|
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light))
|
||||||
|
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func loadDataSource() {
|
override func loadDataSource() {
|
||||||
@@ -91,14 +116,14 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
|
|
||||||
updateMap(with: annotations)
|
updateMap(with: annotations)
|
||||||
|
|
||||||
// if the given seismic is still in the data source, show circles
|
// if the filter is "in radius",
|
||||||
// otherwise just remove any other circles already on the map
|
// show a circle with selected radius
|
||||||
if allSeismics.contains(seismic) {
|
if eqnSeismic.filterOption == .inRadius, let distance = Double(eqnSeismic.maximumDistance) {
|
||||||
addCircles(for: seismic.coordinate)
|
addCircle(center: EQNUser.default().lastPosition, radius: distance * 1_000)
|
||||||
} else {
|
} else {
|
||||||
addCircles(for: nil)
|
addCircle(center: nil, radius: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFiltersRecap()
|
loadFiltersRecap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +132,8 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
||||||
|
mapView.deselectAnnotation(annotation, animated: true)
|
||||||
|
|
||||||
guard let annotation = annotation as? EQNMapAnnotationSeismic else { return }
|
guard let annotation = annotation as? EQNMapAnnotationSeismic else { return }
|
||||||
|
|
||||||
let viewModel = SeismicNetworkViewModel(seismic: annotation.seismic)
|
let viewModel = SeismicNetworkViewModel(seismic: annotation.seismic)
|
||||||
@@ -124,31 +151,61 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
present(alert, animated: true)
|
present(alert, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
override func zPriority(for annotation: MKAnnotation) -> MKAnnotationViewZPriority {
|
||||||
|
guard let annotation = annotation as? EQNMapAnnotationSeismic else {
|
||||||
private func loadFiltersRecap() {
|
return .min
|
||||||
let filters = FiltersViewModel()
|
}
|
||||||
|
|
||||||
let recap = "\(NSLocalizedString("filter_filter", comment: "")): "
|
// il sisma cliccato dall'utente sta sopra a tutti
|
||||||
+ "M≥\(filters.magnitude) "
|
if annotation.seismic == seismic {
|
||||||
+ "D≤\(filters.distance) "
|
return .max
|
||||||
+ "T≤\(filters.timeframe)"
|
}
|
||||||
seismicsFilterLabel.text = recap
|
|
||||||
|
// Ordiniamo le annotazioni in base all amagnitudo, quelle con valore maggiore devono stare sopra.
|
||||||
|
// La `zPriority` viene calcolata utilizzando la posizione nella lista
|
||||||
|
let index = mapAnnotations
|
||||||
|
.compactMap { $0 as? EQNMapAnnotationSeismic }
|
||||||
|
.sorted(by: { $0.seismic.magnitude.doubleValue < $1.seismic.magnitude.doubleValue })
|
||||||
|
.firstIndex(where: { $0 == annotation })
|
||||||
|
guard let index else {
|
||||||
|
return .min
|
||||||
|
}
|
||||||
|
|
||||||
|
let priority = Float(index) / Float(mapAnnotations.count)
|
||||||
|
return .init(priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addCircles(for location: CLLocation?) {
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func nextPinStyle() {
|
||||||
|
pinStyle.next()
|
||||||
|
reloadMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadFiltersRecap() {
|
||||||
|
let filter = EQNSeismic.shared.filterOption
|
||||||
|
|
||||||
|
let text = switch filter {
|
||||||
|
case .inRadius: NSLocalizedString("filter_area", comment: "")
|
||||||
|
case .positionRelevant: NSLocalizedString("filter_relevant", comment: "")
|
||||||
|
case .worldWide: NSLocalizedString("filter_all", comment: "")
|
||||||
|
case .userFelt: NSLocalizedString("filter_felt", comment: "")
|
||||||
|
}
|
||||||
|
seismicsFilterLabel.text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addCircle(
|
||||||
|
center location: CLLocation?,
|
||||||
|
radius: Double
|
||||||
|
) {
|
||||||
// remove any previous circles
|
// remove any previous circles
|
||||||
mapView.removeOverlays(mapCircles)
|
mapView.removeOverlays(mapCircles)
|
||||||
mapCircles.removeAll()
|
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 }
|
guard let location = location else { return }
|
||||||
|
|
||||||
let circles = [
|
let circles = [
|
||||||
MKCircle(center: location.coordinate, radius: 25_000),
|
MKCircle(center: location.coordinate, radius: radius),
|
||||||
MKCircle(center: location.coordinate, radius: 100_000),
|
|
||||||
MKCircle(center: location.coordinate, radius: 200_000)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// !!note: is important to assign here the circles
|
// !!note: is important to assign here the circles
|
||||||
@@ -159,16 +216,13 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
mapView.addOverlays(circles)
|
mapView.addOverlays(circles)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
private func shareScreenshot() {
|
||||||
|
let screenshot = createSnapshot()
|
||||||
@objc override func filtersTapped(_ sender: UIGestureRecognizer) {
|
|
||||||
let controller = SeismicFiltersViewController.makeController()
|
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
||||||
controller.delegate = self
|
present(controller, animated: true)
|
||||||
controller.modalPresentationStyle = .overCurrentContext
|
|
||||||
controller.modalTransitionStyle = .crossDissolve
|
|
||||||
present(controller, animated: true, completion: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Map
|
// MARK: - Map
|
||||||
|
|
||||||
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
||||||
@@ -176,15 +230,8 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewModel = SeismicNetworkViewModel(seismic: annotation.seismic)
|
let isUserSelection = annotation.seismic == seismic
|
||||||
|
return annotation.toAnnotationView(mapView: mapView, style: pinStyle, isUserSelection: isUserSelection)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||||
@@ -193,12 +240,39 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let circle = MKCircleRenderer(overlay: circleOverlay)
|
let circle = MKCircleRenderer(overlay: circleOverlay)
|
||||||
circle.strokeColor = AppTheme.Colors.darkGray
|
circle.strokeColor = AppTheme.Colors.red
|
||||||
circle.lineWidth = 1.0
|
circle.lineWidth = 2.0
|
||||||
return circle
|
return circle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension EQNMapAnnotationSeismic {
|
||||||
|
func toAnnotationView(
|
||||||
|
mapView: MKMapView,
|
||||||
|
style: MapPinStyle,
|
||||||
|
isUserSelection: Bool = false
|
||||||
|
) -> MKAnnotationView {
|
||||||
|
switch style {
|
||||||
|
case .full, .light:
|
||||||
|
let identifier = EQNSeismicAnnotationView.identifier(for: style)
|
||||||
|
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
|
||||||
|
annotationView.title = self.title
|
||||||
|
annotationView.subtitle = self.subtitle
|
||||||
|
annotationView.magnitude = String(format: "M%.1f", self.seismic.magnitude.doubleValue)
|
||||||
|
annotationView.magnitudeTextColor = self.textColor
|
||||||
|
annotationView.magnitudeBackgroundColor = .white
|
||||||
|
annotationView.isUserSelection = isUserSelection
|
||||||
|
return annotationView
|
||||||
|
case .circle:
|
||||||
|
let identifier = EQNSeismicAnnotationView.identifier(for: style)
|
||||||
|
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
|
||||||
|
annotationView.image = image(height: EQNSeismicAnnotationView.CircleViewHeight,
|
||||||
|
isUserSelection: isUserSelection)
|
||||||
|
return annotationView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension SeismicNetworksMapDetailViewController: SeismicFiltersViewControllerDelegate {
|
extension SeismicNetworksMapDetailViewController: SeismicFiltersViewControllerDelegate {
|
||||||
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
|
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
|
||||||
delegate?.seismicNetworksMapDetailControllerWillUpdateData(self, needsDataUpdate: controller.needsDataUpdate)
|
delegate?.seismicNetworksMapDetailControllerWillUpdateData(self, needsDataUpdate: controller.needsDataUpdate)
|
||||||
|
|||||||
+569
-126
@@ -9,28 +9,29 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import EventKitUI
|
import EventKitUI
|
||||||
import DZNEmptyDataSet
|
import DZNEmptyDataSet
|
||||||
|
import Shogun
|
||||||
|
|
||||||
class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
||||||
|
|
||||||
private enum CellType {
|
private enum CellType {
|
||||||
case seismic(EQNSisma)
|
case seismic(EQNSisma)
|
||||||
case advertise(GADNativeAd)
|
case advertise(NativeAd)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CardDisplayType: Int, CaseIterable {
|
||||||
|
case small
|
||||||
|
case full
|
||||||
|
case minimal
|
||||||
}
|
}
|
||||||
|
|
||||||
private static let SegueIdentifierFilters = "ShowFilters"
|
private static let SegueIdentifierFilters = "ShowFilters"
|
||||||
private static let SegueIdentifierSettings = "ShowSettings"
|
|
||||||
private static let SegueIdentifierSeismicNetworks = "ShowSeismicNetworks"
|
|
||||||
private static let SegueIdentifierCardSettings = "ShowCardSettings"
|
private static let SegueIdentifierCardSettings = "ShowCardSettings"
|
||||||
|
|
||||||
// MARK: - Internal
|
// MARK: - Internal
|
||||||
|
|
||||||
@IBOutlet private weak var tableView: UITableView?
|
|
||||||
@IBOutlet private weak var expandeCollapseButton: UIBarButtonItem!
|
|
||||||
weak var currentMapController: SeismicNetworksMapDetailViewController?
|
|
||||||
|
|
||||||
/// The ad loader
|
/// The ad loader
|
||||||
private lazy var adLoader: GADAdLoader = {
|
private lazy var adLoader: AdLoader = {
|
||||||
let adLoader = GADAdLoader(
|
let adLoader = AdLoader(
|
||||||
adUnitID: EQNAdMobAppIdNativeBanner, rootViewController: self,
|
adUnitID: EQNAdMobAppIdNativeBanner, rootViewController: self,
|
||||||
adTypes: [.native], options: nil)
|
adTypes: [.native], options: nil)
|
||||||
adLoader.delegate = self
|
adLoader.delegate = self
|
||||||
@@ -39,27 +40,88 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
|
|
||||||
/// Cells to display (must be seismics or ad banners)
|
/// Cells to display (must be seismics or ad banners)
|
||||||
private var rows = [CellType]()
|
private var rows = [CellType]()
|
||||||
|
/// Type of cards to show
|
||||||
|
private var cardDisplayType: CardDisplayType {
|
||||||
|
get { AppPreferences.shared.seismicNetworksCardStyle }
|
||||||
|
set { AppPreferences.shared.seismicNetworksCardStyle = newValue }
|
||||||
|
}
|
||||||
|
private var seismicViewModels = [SeismicNetworkViewModel]()
|
||||||
/// Informations to display on a single cell
|
/// Informations to display on a single cell
|
||||||
private var informations = [SeismicNetworkTableViewCell.InformationType]()
|
private var informations: [SeismicNetworkTableViewCell.InformationType] {
|
||||||
|
get { AppPreferences.shared.seismicNetworksInformations }
|
||||||
|
set { AppPreferences.shared.seismicNetworksInformations = newValue }
|
||||||
|
}
|
||||||
/// Index path of row with map expanded
|
/// Index path of row with map expanded
|
||||||
private var openMapIndexPath: IndexPath?
|
private var openMapIndexPath: IndexPath?
|
||||||
/// Index path of row with weather expanded
|
/// Push notification opened by the user
|
||||||
private var openWeatherIndexPath: IndexPath?
|
private var openedPushNotification: EQNOfficialPushNotification? {
|
||||||
|
didSet {
|
||||||
|
scrollToOpenedSeismic = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var scrollToOpenedSeismic = false
|
||||||
|
/// Current displayed controller with map
|
||||||
|
private weak var currentMapController: SeismicNetworksMapDetailViewController?
|
||||||
|
/// Keep track of the current cell at the center
|
||||||
|
private var currentCenteredIndexPath: IndexPath?
|
||||||
|
|
||||||
|
// MARK: - UI
|
||||||
|
|
||||||
|
@IBOutlet private weak var displayModeButton: UIBarButtonItem!
|
||||||
|
@IBOutlet private weak var sortButton: UIBarButtonItem!
|
||||||
|
private var tableViewTopConstraint: NSLayoutConstraint?
|
||||||
|
|
||||||
|
private lazy var tableView: UITableView = {
|
||||||
|
let tableView = UITableView(frame: .zero, style: .plain)
|
||||||
|
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
tableView.delegate = self
|
||||||
|
tableView.dataSource = self
|
||||||
|
tableView.showsVerticalScrollIndicator = false
|
||||||
|
return tableView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var filterChangedView: UIView = {
|
||||||
|
let view = UIView(frame: .zero)
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.backgroundColor = AppTheme.Colors.pureBlue
|
||||||
|
|
||||||
|
view.addSubview(filterChangedLabel)
|
||||||
|
filterChangedLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
|
||||||
|
filterChangedLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
|
||||||
|
filterChangedLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
|
||||||
|
filterChangedLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var filterChangedLabel: UILabel = {
|
||||||
|
let label = UILabel(frame: .zero)
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textColor = .white
|
||||||
|
label.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.textAlignment = .center
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var scrollIndicatorView: SeismicNetworkScrollIndicatorView = {
|
||||||
|
let view = SeismicNetworkScrollIndicatorView(frame: .zero)
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
view.layer.borderColor = AppTheme.Colors.gray.cgColor
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
setupUI()
|
setupUI()
|
||||||
|
configureUI()
|
||||||
|
checkForLocation()
|
||||||
refreshUI()
|
refreshUI()
|
||||||
|
configureFilterView(isVisible: false)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveDownloadCompleteNotification(_:)), name: .EQNDownloadDataDidComplete, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveDownloadCompleteNotification(_:)), name: .EQNDownloadDataDidComplete, object: nil)
|
||||||
}
|
}
|
||||||
@@ -68,16 +130,103 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
loadData(forced: false)
|
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() {
|
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
|
title = NSLocalizedString("tab_official", comment: "").capitalized
|
||||||
|
|
||||||
tableView?.estimatedRowHeight = 300.0
|
tableView.estimatedRowHeight = 300.0
|
||||||
tableView?.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView?.register(SeismicNetworkTableViewCell.self, forCellReuseIdentifier: SeismicNetworkTableViewCell.Identifier)
|
tableView.registerCell(for: SeismicNetworkTableViewCell.self)
|
||||||
tableView?.register(SeismicNetworkAdvertiseTableViewCell.self, forCellReuseIdentifier: SeismicNetworkAdvertiseTableViewCell.Identifier)
|
tableView.registerCell(for: SeismicNetworkMinimalTableViewCell.self)
|
||||||
tableView?.emptyDataSetSource = self
|
tableView.registerCell(for: SeismicNetworkAdvertiseTableViewCell.self)
|
||||||
|
tableView.emptyDataSetSource = self
|
||||||
|
tableView.separatorStyle = .none
|
||||||
|
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
|
||||||
|
|
||||||
|
setupSortMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupSortMenu() {
|
||||||
|
let currentSort = EQNSeismic.shared.sort
|
||||||
|
sortButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
|
||||||
|
UIAction(title: NSLocalizedString("sort_date", comment: ""), image: UIImage(systemName: "calendar"), state: currentSort == .time ? .on : .off) { [weak self ] _ in
|
||||||
|
self?.changeSort(to: .time)
|
||||||
|
},
|
||||||
|
UIAction(title: NSLocalizedString("sort_position", comment: ""), image: UIImage(systemName: "ruler"), state: currentSort == .position ? .on : .off) { [weak self] _ in
|
||||||
|
self?.changeSort(to: .position)
|
||||||
|
},
|
||||||
|
UIAction(title: NSLocalizedString("sort_magnitude", comment: ""), image: UIImage(systemName: "thermometer"), state: currentSort == .magnitude ? .on : .off) { [weak self] _ in
|
||||||
|
self?.changeSort(to: .magnitude)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
private func checkForLocation() {
|
||||||
|
// check if a valid location is available,
|
||||||
|
// otherwise change the filter settings
|
||||||
|
if !isLocationAvailable() {
|
||||||
|
EQNSeismic.shared.filterOption = .worldWide
|
||||||
|
EQNSeismic.shared.saveFilters()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
@@ -86,14 +235,6 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
if let controller = segue.destination as? SeismicFiltersViewController {
|
if let controller = segue.destination as? SeismicFiltersViewController {
|
||||||
controller.delegate = self
|
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:
|
case Self.SegueIdentifierCardSettings:
|
||||||
if let controller = segue.destination as? SeismicCardSettingsViewController {
|
if let controller = segue.destination as? SeismicCardSettingsViewController {
|
||||||
controller.delegate = self
|
controller.delegate = self
|
||||||
@@ -107,16 +248,22 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
let seismics = getSeismics()
|
let seismics = getSeismics()
|
||||||
let controller = SeismicNetworksMapDetailViewController(seismic: seismic, allSeismics: seismics)
|
let controller = SeismicNetworksMapDetailViewController(seismic: seismic, allSeismics: seismics)
|
||||||
controller.delegate = self
|
controller.delegate = self
|
||||||
present(controller, animated: true, completion: nil)
|
let navController = UINavigationController(rootViewController: controller)
|
||||||
|
present(navController, animated: true, completion: nil)
|
||||||
|
|
||||||
self.currentMapController = controller
|
self.currentMapController = controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func showIntensityMap(for seismic: EQNSisma) {
|
||||||
|
let controller = SeismicNetworksIntensityMapViewController(seismic: seismic)
|
||||||
|
let navController = UINavigationController(rootViewController: controller)
|
||||||
|
present(navController, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
|
|
||||||
@objc func didReceiveDownloadCompleteNotification(_ sender: Notification) {
|
@objc func didReceiveDownloadCompleteNotification(_ sender: Notification) {
|
||||||
self.openMapIndexPath = nil
|
self.openMapIndexPath = nil
|
||||||
self.openWeatherIndexPath = nil
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.refreshUI()
|
self.refreshUI()
|
||||||
@@ -128,21 +275,26 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
private func refreshUI() {
|
private func refreshUI() {
|
||||||
elaborateData()
|
elaborateData()
|
||||||
|
|
||||||
if let saved = UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) as? [Int] {
|
switch cardDisplayType {
|
||||||
informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
|
case .small:
|
||||||
|
displayModeButton.image = UIImage(systemName: "1.square")
|
||||||
|
case .full:
|
||||||
|
displayModeButton.image = UIImage(systemName: "2.square")
|
||||||
|
case .minimal:
|
||||||
|
displayModeButton.image = UIImage(systemName: "3.square")
|
||||||
}
|
}
|
||||||
|
|
||||||
if informations.contains(.buttons) {
|
tableView.reloadData()
|
||||||
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-collapse")
|
updateCenterCellIndexPath()
|
||||||
} else {
|
|
||||||
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-expand")
|
|
||||||
}
|
|
||||||
|
|
||||||
tableView?.reloadData()
|
if scrollToOpenedSeismic, let index = getSeismics().firstIndex(where: { isSeismicToHighlight(seismic: $0) }) {
|
||||||
|
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
|
||||||
|
scrollToOpenedSeismic = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadAd() {
|
private func loadAd() {
|
||||||
adLoader.load(GADRequest())
|
adLoader.load(Request())
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadData(forced: Bool) {
|
private func loadData(forced: Bool) {
|
||||||
@@ -154,7 +306,8 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
let allSeismics = EQNManager.manager().retiSismiche
|
let allSeismics = EQNManager.manager().retiSismiche
|
||||||
let filteredSeismics = EQNSeismic.shared.filterSeismicList(allSeismics ?? [])
|
let filteredSeismics = EQNSeismic.shared.filterSeismicList(allSeismics ?? [])
|
||||||
rows = filteredSeismics.map { .seismic($0) }
|
rows = filteredSeismics.map { .seismic($0) }
|
||||||
|
seismicViewModels = filteredSeismics.map(SeismicNetworkViewModel.init)
|
||||||
|
|
||||||
#if ADS_ENABLED
|
#if ADS_ENABLED
|
||||||
// if is not a pro user, show an advertise
|
// if is not a pro user, show an advertise
|
||||||
if !EQNPurchaseUtility.isProVersionEnabled() {
|
if !EQNPurchaseUtility.isProVersionEnabled() {
|
||||||
@@ -166,6 +319,9 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
if let mapController = currentMapController {
|
if let mapController = currentMapController {
|
||||||
mapController.updateSeismics(filteredSeismics)
|
mapController.updateSeismics(filteredSeismics)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollIndicatorView.seismics = seismicViewModels
|
||||||
|
currentCenteredIndexPath = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getSeismics() -> [EQNSisma] {
|
private func getSeismics() -> [EQNSisma] {
|
||||||
@@ -178,6 +334,307 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
return seismics
|
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
|
// MARK: - Actions
|
||||||
|
|
||||||
@IBAction func refreshDataTapped(_ sender: Any) {
|
@IBAction func refreshDataTapped(_ sender: Any) {
|
||||||
@@ -187,19 +644,19 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
@IBAction func openFilterTapped(_ sender: Any) {
|
@IBAction func openFilterTapped(_ sender: Any) {
|
||||||
performSegue(withIdentifier: Self.SegueIdentifierFilters, sender: nil)
|
performSegue(withIdentifier: Self.SegueIdentifierFilters, sender: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func openSettingsTapped(_ sender: Any) {
|
|
||||||
performSegue(withIdentifier: Self.SegueIdentifierSettings, sender: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func collapseExpandTapped(_ sender: Any) {
|
@IBAction func collapseExpandTapped(_ sender: Any) {
|
||||||
if informations.contains(.buttons) {
|
cardDisplayType.next()
|
||||||
|
|
||||||
|
switch cardDisplayType {
|
||||||
|
case .small:
|
||||||
informations.removeAll(where: { $0 == .buttons })
|
informations.removeAll(where: { $0 == .buttons })
|
||||||
} else {
|
case .full:
|
||||||
informations.append(.buttons)
|
informations.append(.buttons)
|
||||||
|
case .minimal:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
|
|
||||||
refreshUI()
|
refreshUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,20 +670,28 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
let row = rows[indexPath.row]
|
let row = rows[indexPath.row]
|
||||||
switch row {
|
switch row {
|
||||||
case .seismic(let seismic):
|
case .seismic(let seismic):
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: SeismicNetworkTableViewCell.Identifier, for: indexPath) as! SeismicNetworkTableViewCell
|
switch cardDisplayType {
|
||||||
|
case .small, .full:
|
||||||
var type = SeismicNetworkTableViewCell.DisplayType.normal
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkTableViewCell.self, for: indexPath)
|
||||||
if openMapIndexPath == indexPath {
|
|
||||||
type = .mapExpanded
|
var type = SeismicNetworkTableViewCell.DisplayType.normal
|
||||||
} else if openWeatherIndexPath == indexPath {
|
if openMapIndexPath == indexPath {
|
||||||
type = .weatherExpanded
|
type = .mapExpanded
|
||||||
|
}
|
||||||
|
|
||||||
|
let isPushSelected = isSeismicToHighlight(seismic: seismic)
|
||||||
|
cell.configure(with: seismic, type: type, informations: informations, isPushSelected: isPushSelected)
|
||||||
|
cell.delegate = self
|
||||||
|
return cell
|
||||||
|
case .minimal:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkMinimalTableViewCell.self, for: indexPath)
|
||||||
|
let isPushSelected = isSeismicToHighlight(seismic: seismic)
|
||||||
|
cell.configure(with: seismic, isPushSelected: isPushSelected)
|
||||||
|
cell.delegate = self
|
||||||
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.configure(with: seismic, type: type, informations: informations)
|
|
||||||
cell.delegate = self
|
|
||||||
return cell
|
|
||||||
case .advertise(let nativeAd):
|
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)
|
cell.loadNativeAd(nativeAd)
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
@@ -241,6 +706,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UIScrollViewDelegate
|
||||||
|
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
updateCenterCellIndexPath()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func openCalendar(for seismic: EQNSisma) {
|
private func openCalendar(for seismic: EQNSisma) {
|
||||||
@@ -296,28 +767,28 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SeismicNetworksViewController: GADNativeAdLoaderDelegate {
|
extension SeismicNetworksViewController: NativeAdLoaderDelegate {
|
||||||
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADNativeAd) {
|
func adLoader(_ adLoader: AdLoader, didReceive nativeAd: NativeAd) {
|
||||||
print("[GADAdLoader] didReceive")
|
print("[AdLoader] didReceive")
|
||||||
|
|
||||||
let adPosition = min(3, rows.count)
|
let adPosition = min(3, rows.count)
|
||||||
rows.insert(.advertise(nativeAd), at: adPosition)
|
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
|
// nope
|
||||||
print("[GADAdLoader] didFailToReceiveAdWithError: \(error.localizedDescription)")
|
print("[AdLoader] didFailToReceiveAdWithError: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
extension SeismicNetworksViewController: SeismicNetworkBaseTableViewCellDelegate {
|
||||||
|
|
||||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) {
|
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||||
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
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
|
// 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
|
// text to share with the snapshot
|
||||||
let shareHashtag = NSLocalizedString("share_hashtag", comment: "")
|
let shareHashtag = NSLocalizedString("share_hashtag", comment: "")
|
||||||
@@ -330,59 +801,44 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
|||||||
present(controller, animated: true)
|
present(controller, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func seismicNetworkCellDidTapWeather(_ cell: SeismicNetworkTableViewCell, hasValidWeatherData: Bool) {
|
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||||
guard let index = tableView?.indexPath(for: cell) else { return }
|
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 }
|
|
||||||
|
|
||||||
let indexToReloads = [openMapIndexPath, openWeatherIndexPath, index].compactMap { $0 }
|
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
||||||
|
|
||||||
openMapIndexPath = index
|
openMapIndexPath = index
|
||||||
openWeatherIndexPath = nil
|
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
||||||
tableView?.reloadRows(at: indexToReloads, with: .automatic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell) {
|
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||||
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||||
|
|
||||||
showMapDetail(for: seismic)
|
showMapDetail(for: seismic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) {
|
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||||
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 }
|
||||||
|
|
||||||
|
showIntensityMap(for: seismic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||||
|
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||||
|
|
||||||
openCalendar(for: seismic)
|
openCalendar(for: seismic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell) {
|
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||||
performSegue(withIdentifier: Self.SegueIdentifierCardSettings, sender: nil)
|
performSegue(withIdentifier: Self.SegueIdentifierCardSettings, sender: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell) {
|
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||||
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
|
openMapIndexPath = nil
|
||||||
openWeatherIndexPath = nil
|
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
||||||
tableView?.reloadRows(at: indexToReloads, with: .automatic)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,22 +856,6 @@ extension SeismicNetworksViewController: SeismicFiltersViewControllerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SeismicNetworksViewController: SeismicSettingsViewControllerDelegate {
|
|
||||||
func seismicSettingsControllerDidComplete(_ controller: SeismicSettingsViewController) {
|
|
||||||
refreshUI()
|
|
||||||
}
|
|
||||||
|
|
||||||
func seismicSettingsControllerWillOpenProviders(_ controller: SeismicSettingsViewController) {
|
|
||||||
performSegue(withIdentifier: Self.SegueIdentifierSeismicNetworks, sender: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SeismicNetworksViewController: SeismicSettingsNetworksViewControllerDelegate {
|
|
||||||
func seismicSettingsNetworksControllerDidComplete(_ controller: SeismicSettingsNetworksViewController) {
|
|
||||||
refreshUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelegate {
|
extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelegate {
|
||||||
func seismicCardSettingsDidComplete(_ controller: SeismicCardSettingsViewController) {
|
func seismicCardSettingsDidComplete(_ controller: SeismicCardSettingsViewController) {
|
||||||
refreshUI()
|
refreshUI()
|
||||||
@@ -423,9 +863,12 @@ extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelega
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension SeismicNetworksViewController: DZNEmptyDataSetSource {
|
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 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
|
return string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-117
@@ -1,117 +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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload all rows with the given acronym
|
|
||||||
let indexes = networks
|
|
||||||
.enumerated()
|
|
||||||
.filter { $0.element.acronym == network.acronym }
|
|
||||||
.map { IndexPath(row: $0.offset, section: 0) }
|
|
||||||
tableView.reloadRows(at: indexes, with: .automatic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
|
|
||||||
@IBAction func cancelTapped(_ sender: Any) {
|
|
||||||
dismiss(animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func saveTapped(_ sender: Any) {
|
|
||||||
// save selected networks
|
|
||||||
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 {
|
class SettingDateTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
@objc static let Identifier = "DateCell"
|
static let Identifier = "DateCell"
|
||||||
|
|
||||||
@objc var isDisabled: Bool = false {
|
var isDisabled: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc var isPickerVisible: Bool = false {
|
var isPickerVisible: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if oldValue != isPickerVisible {
|
if oldValue != isPickerVisible {
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@objc private(set) var date = Date()
|
private(set) var date = Date()
|
||||||
@objc var valueChanged: ((Date) -> Void)?
|
var valueChanged: ((Date) -> Void)?
|
||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
@objc lazy var titleLabel: UILabel = {
|
lazy var titleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
@@ -40,7 +40,7 @@ class SettingDateTableViewCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@objc lazy var valuesLabel: UILabel = {
|
lazy var valuesLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
@@ -58,7 +58,7 @@ class SettingDateTableViewCell: UITableViewCell {
|
|||||||
return picker
|
return picker
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@objc lazy var stackView: UIStackView = {
|
lazy var stackView: UIStackView = {
|
||||||
let stackView = UIStackView()
|
let stackView = UIStackView()
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackView.axis = .vertical
|
stackView.axis = .vertical
|
||||||
@@ -81,7 +81,7 @@ class SettingDateTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
@objc public func updateDate(_ date: Date) {
|
public func updateDate(_ date: Date) {
|
||||||
self.date = date
|
self.date = date
|
||||||
datePicker.setDate(date, animated: true)
|
datePicker.setDate(date, animated: true)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -10,17 +10,16 @@ import UIKit
|
|||||||
|
|
||||||
class SettingDetailTableViewCell: UITableViewCell {
|
class SettingDetailTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
@objc static let Identifier = "DetailCell"
|
static let Identifier = "DetailCell"
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
// Initialization code
|
// Initialization code
|
||||||
}
|
}
|
||||||
|
|
||||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
super.setSelected(selected, animated: animated)
|
super.setSelected(selected, animated: animated)
|
||||||
|
|
||||||
// Configure the view for the selected state
|
// Configure the view for the selected state
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-7
@@ -10,10 +10,10 @@ import UIKit
|
|||||||
|
|
||||||
class SettingEnableTableViewCell: UITableViewCell {
|
class SettingEnableTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
@objc static let Identifier = "EnableCell"
|
static let Identifier = "EnableCell"
|
||||||
|
|
||||||
@objc var valueChanged: ((Bool) -> Void)?
|
var valueChanged: ((Bool) -> Void)?
|
||||||
@objc var isDisabled: Bool = false {
|
var isDisabled: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ class SettingEnableTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
@objc lazy var titleLabel: UILabel = {
|
lazy var titleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
@@ -29,7 +29,7 @@ class SettingEnableTableViewCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@objc lazy var descriptionLabel: UILabel = {
|
lazy var descriptionLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
@@ -37,7 +37,7 @@ class SettingEnableTableViewCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@objc lazy var toggleSwitch: UISwitch = {
|
lazy var toggleSwitch: UISwitch = {
|
||||||
let toggle = UISwitch()
|
let toggle = UISwitch()
|
||||||
toggle.setContentHuggingPriority(.required, for: .horizontal)
|
toggle.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
toggle.setContentCompressionResistancePriority(.required, for: .horizontal)
|
toggle.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
@@ -45,6 +45,15 @@ class SettingEnableTableViewCell: UITableViewCell {
|
|||||||
return toggle
|
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
|
// MARK: - Init
|
||||||
|
|
||||||
@@ -75,6 +84,7 @@ class SettingEnableTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
contentView.addSubview(stackView)
|
contentView.addSubview(stackView)
|
||||||
contentView.addSubview(descriptionLabel)
|
contentView.addSubview(descriptionLabel)
|
||||||
|
contentView.addSubview(errorLabel)
|
||||||
|
|
||||||
stackView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor).isActive = true
|
stackView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor).isActive = true
|
||||||
stackView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor).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.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 8).isActive = true
|
||||||
descriptionLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
|
descriptionLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
|
||||||
descriptionLabel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).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() {
|
private func updateUI() {
|
||||||
|
|||||||
+4
-4
@@ -11,9 +11,9 @@ import Foundation
|
|||||||
|
|
||||||
class SettingMultivaluesTableViewCell: UITableViewCell {
|
class SettingMultivaluesTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
@objc static let Identifier = "MultivaluesCell"
|
static let Identifier = "MultivaluesCell"
|
||||||
|
|
||||||
@objc var isDisabled: Bool = false {
|
var isDisabled: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ class SettingMultivaluesTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
@objc lazy var titleLabel: UILabel = {
|
lazy var titleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
@@ -30,7 +30,7 @@ class SettingMultivaluesTableViewCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@objc lazy var valuesLabel: UILabel = {
|
lazy var valuesLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
|
|||||||
+3
-6
@@ -10,18 +10,17 @@ import UIKit
|
|||||||
|
|
||||||
class SettingSectionHeaderView: UITableViewHeaderFooterView {
|
class SettingSectionHeaderView: UITableViewHeaderFooterView {
|
||||||
|
|
||||||
@objc static let Identifier = "SectionHeaderView"
|
static let Identifier = "SectionHeaderView"
|
||||||
@objc static let Height = 50.0
|
static let Height = 50.0
|
||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
@objc lazy var titleLabel: UILabel = {
|
lazy var titleLabel: UILabel = {
|
||||||
let titleLabel = UILabel()
|
let titleLabel = UILabel()
|
||||||
titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
|
titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
|
||||||
titleLabel.textColor = AppTheme.Colors.lightBlue
|
titleLabel.textColor = AppTheme.Colors.lightBlue
|
||||||
return titleLabel
|
return titleLabel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
@@ -34,7 +33,6 @@ class SettingSectionHeaderView: UITableViewHeaderFooterView {
|
|||||||
super.init(coder: coder)
|
super.init(coder: coder)
|
||||||
setupUI()
|
setupUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
@@ -45,6 +43,5 @@ class SettingSectionHeaderView: UITableViewHeaderFooterView {
|
|||||||
titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
|
titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
|
||||||
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
|
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
|
||||||
titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
|
titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -11,19 +11,19 @@ import Foundation
|
|||||||
|
|
||||||
class SettingSegmentedTableViewCell: UITableViewCell {
|
class SettingSegmentedTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
@objc static let Identifier = "SegmentedCell"
|
static let Identifier = "SegmentedCell"
|
||||||
|
|
||||||
@objc var isDisabled: Bool = false {
|
var isDisabled: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@objc var valueChanged: ((EQNGenericValue) -> Void)?
|
var valueChanged: ((EQNGenericValue) -> Void)?
|
||||||
private var items = [EQNGenericValue]()
|
private var items = [EQNGenericValue]()
|
||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
@objc lazy var titleLabel: UILabel = {
|
lazy var titleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
@@ -32,7 +32,7 @@ class SettingSegmentedTableViewCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@objc lazy var segmentedControl: UISegmentedControl = {
|
lazy var segmentedControl: UISegmentedControl = {
|
||||||
let segmented = UISegmentedControl()
|
let segmented = UISegmentedControl()
|
||||||
segmented.translatesAutoresizingMaskIntoConstraints = false
|
segmented.translatesAutoresizingMaskIntoConstraints = false
|
||||||
segmented.addTarget(self, action: #selector(segmentedControlChanged(_:)), for: .valueChanged)
|
segmented.addTarget(self, action: #selector(segmentedControlChanged(_:)), for: .valueChanged)
|
||||||
|
|||||||
+7
-7
@@ -10,21 +10,21 @@ import UIKit
|
|||||||
|
|
||||||
class SettingSliderTableViewCell: UITableViewCell {
|
class SettingSliderTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
@objc static let Identifier = "SliderCell"
|
static let Identifier = "SliderCell"
|
||||||
|
|
||||||
@objc var isDisabled: Bool = false {
|
var isDisabled: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@objc var valueChanged: ((EQNGenericValue) -> Void)?
|
var valueChanged: ((EQNGenericValue) -> Void)?
|
||||||
@objc var dragEnded: (() -> Void)?
|
var dragEnded: (() -> Void)?
|
||||||
private var items = [EQNGenericValue]()
|
private var items = [EQNGenericValue]()
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
@objc lazy var titleLabel: UILabel = {
|
lazy var titleLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
@@ -33,7 +33,7 @@ class SettingSliderTableViewCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@objc lazy var valueLabel: UILabel = {
|
lazy var valueLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
@@ -42,7 +42,7 @@ class SettingSliderTableViewCell: UITableViewCell {
|
|||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@objc lazy var slider: UISlider = {
|
lazy var slider: UISlider = {
|
||||||
let slider = UISlider()
|
let slider = UISlider()
|
||||||
slider.isContinuous = true
|
slider.isContinuous = true
|
||||||
slider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
|
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
|
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
//
|
||||||
|
// SettingsRealTimeAlertsViewController.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 10/06/24.
|
||||||
|
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
|
||||||
|
|
||||||
|
private enum RowIdentifier: Int {
|
||||||
|
case abilitaNotifiche
|
||||||
|
case disabilitaSuonoAllerta
|
||||||
|
case abilitaCriticalAlerts
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isNotificationEnabled = false
|
||||||
|
private var isMildQuakeSoundDisabled = false
|
||||||
|
private var isCriticalAlertsEnabled = false
|
||||||
|
|
||||||
|
private let settings: [SettingItem] = [
|
||||||
|
.init(type: .enable, title: NSLocalizedString("options_notification_enable_alarm", comment: "")),
|
||||||
|
.init(type: .enable, title: NSLocalizedString("options_notification_disable_sound", comment: "")),
|
||||||
|
.init(type: .enable, title: NSLocalizedString("critical_alerts_setting", comment: ""))
|
||||||
|
]
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
|
|
||||||
|
tableView.estimatedRowHeight = 200.0
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||||
|
tableView.registerCell(for: SettingEnableTableViewCell.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadDataSource() {
|
||||||
|
let saved = EQNSettingRealTimeAlert.shared
|
||||||
|
|
||||||
|
isNotificationEnabled = saved.isAbilitato
|
||||||
|
isMildQuakeSoundDisabled = saved.isMildQuakeSoundDisabled
|
||||||
|
isCriticalAlertsEnabled = saved.isCriticalAlertsEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Table view delegate and data source
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
|
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||||
|
view.titleLabel.text = NSLocalizedString("options_alarms", comment: "")
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
|
SettingSectionHeaderView.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return settings.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||||
|
return UITableViewCell()
|
||||||
|
}
|
||||||
|
|
||||||
|
let setting = settings[indexPath.row]
|
||||||
|
switch setting.type {
|
||||||
|
case .enable:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
cell.descriptionLabel.text = setting.subtitle
|
||||||
|
|
||||||
|
switch identifier {
|
||||||
|
case .abilitaNotifiche:
|
||||||
|
cell.toggleSwitch.isOn = isNotificationEnabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeNotificationEnabled(enabled)
|
||||||
|
}
|
||||||
|
case .disabilitaSuonoAllerta:
|
||||||
|
cell.toggleSwitch.isOn = isMildQuakeSoundDisabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeDisableSoundEnabled(enabled)
|
||||||
|
}
|
||||||
|
case .abilitaCriticalAlerts:
|
||||||
|
cell.toggleSwitch.isOn = isCriticalAlertsEnabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeCriticalAlertsEnabled(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||||
|
isNotificationEnabled = enabled
|
||||||
|
EQNSettingRealTimeAlert.shared.isAbilitato = isNotificationEnabled
|
||||||
|
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeDisableSoundEnabled(_ enabled: Bool) {
|
||||||
|
isMildQuakeSoundDisabled = enabled
|
||||||
|
EQNSettingRealTimeAlert.shared.isMildQuakeSoundDisabled = isMildQuakeSoundDisabled
|
||||||
|
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeCriticalAlertsEnabled(_ enabled: Bool) {
|
||||||
|
if enabled {
|
||||||
|
askForCriticalAlertsPermission()
|
||||||
|
}
|
||||||
|
|
||||||
|
isCriticalAlertsEnabled = enabled
|
||||||
|
EQNSettingRealTimeAlert.shared.isCriticalAlertsEnabled = isCriticalAlertsEnabled
|
||||||
|
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func askForCriticalAlertsPermission() {
|
||||||
|
UNUserNotificationCenter.current().requestAuthorization(options: [ .criticalAlert ]) { granted, error in
|
||||||
|
// nope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsSeismicNetworkAlertsViewController.h
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi 25/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
#import "SettingsBaseViewController.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface SettingsSeismicNetworkAlertsViewController : SettingsBaseViewController
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
-258
@@ -1,258 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsSeismicNetworkAlertsViewController.m
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi 25/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "SettingsSeismicNetworkAlertsViewController.h"
|
|
||||||
#import "EQNNotificheReteSismiche.h"
|
|
||||||
|
|
||||||
@interface SettingsSeismicNetworkAlertsViewController ()
|
|
||||||
|
|
||||||
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
|
|
||||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceRaggioSisma;
|
|
||||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceMagnitudoDeboli;
|
|
||||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceMagnitudoForti;
|
|
||||||
|
|
||||||
@property (nonatomic, assign) BOOL notificationEnabled;
|
|
||||||
@property (nonatomic, assign) BOOL notificationNearEarthquakeEnabled;
|
|
||||||
@property (nonatomic, assign) BOOL notificationStrongEarthquakeEnabled;
|
|
||||||
|
|
||||||
@property (nonatomic, strong) EQNGenericValue *currentUserPositionRadius;
|
|
||||||
@property (nonatomic, strong) EQNGenericValue *currentSeismicEnergy;
|
|
||||||
@property (nonatomic, strong) EQNGenericValue *currentStrongEarthquakeDistance;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation SettingsSeismicNetworkAlertsViewController
|
|
||||||
|
|
||||||
static NSString * const SegueIdentifierListaEnti = @"ShowListaEnti";
|
|
||||||
|
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, RowIdentifier) {
|
|
||||||
RowIdentifierAbilitaNotifiche = 0,
|
|
||||||
RowIdentifierRetiSismiche,
|
|
||||||
RowIdentifierRaggioPosizione,
|
|
||||||
RowIdentifierEnergiaSisma,
|
|
||||||
RowIdentifierTerremotiVicini,
|
|
||||||
RowIdentifierTerremotiForti,
|
|
||||||
RowIdentifierTerremotiFortiDistanza
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma mark - View Lifecycle
|
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
|
|
||||||
[self setupUI];
|
|
||||||
|
|
||||||
self.settings = @[
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_official", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeMultiValues title:NSLocalizedString(@"options_agencies", @"") segue:SegueIdentifierListaEnti],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_energy", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_near", @"") subtitle:NSLocalizedString(@"options_near_alert", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_strong", @"") subtitle:NSLocalizedString(@"options_strong_alert", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_strong_magnitude", @"")]
|
|
||||||
];
|
|
||||||
|
|
||||||
self.dataSourceMagnitudoDeboli = [EQNData magitudoDeboli];
|
|
||||||
self.dataSourceRaggioSisma = [EQNData raggioSismi];
|
|
||||||
self.dataSourceMagnitudoForti = [EQNData magitudoForti];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated
|
|
||||||
{
|
|
||||||
[super viewWillAppear:animated];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (void)setupUI
|
|
||||||
{
|
|
||||||
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
|
|
||||||
|
|
||||||
self.tableView.estimatedRowHeight = 200.0;
|
|
||||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
|
||||||
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingMultivaluesTableViewCell class] forCellReuseIdentifier:SettingMultivaluesTableViewCell.Identifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)loadDataSource
|
|
||||||
{
|
|
||||||
self.notificationEnabled = [EQNNotificheReteSismiche sharedInstance].isAbilitato;
|
|
||||||
self.notificationNearEarthquakeEnabled = [EQNNotificheReteSismiche sharedInstance].isAbilitaVicini;
|
|
||||||
self.notificationStrongEarthquakeEnabled = [EQNNotificheReteSismiche sharedInstance].isTerremortiForti;
|
|
||||||
|
|
||||||
// raggio dalla tua posizione
|
|
||||||
EQNGenericValue *raggioSisma = [EQNData raggioSismaFor:[EQNNotificheReteSismiche sharedInstance].distanzaPosizione];
|
|
||||||
self.currentUserPositionRadius = raggioSisma;
|
|
||||||
|
|
||||||
// energia sisma
|
|
||||||
EQNGenericValue *energiaSisma = [EQNData magitudoDeboleFor:[EQNNotificheReteSismiche sharedInstance].energiaSisma];
|
|
||||||
self.currentSeismicEnergy = energiaSisma;
|
|
||||||
|
|
||||||
// terremoti forti
|
|
||||||
EQNGenericValue *terremotiForti = [EQNData magitudoForteFor:[EQNNotificheReteSismiche sharedInstance].energiaTerremotiForti];
|
|
||||||
self.currentStrongEarthquakeDistance = terremotiForti;
|
|
||||||
|
|
||||||
// enti
|
|
||||||
if (![EQNNotificheReteSismiche sharedInstance].listaEnti) {
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].listaEnti = [EQNData.seismicNetworkAcronyms copy];
|
|
||||||
}
|
|
||||||
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Table view data source
|
|
||||||
|
|
||||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
headerView.titleLabel.text = NSLocalizedString(@"options_notification_official", @"titolo impostazioni notifiche");
|
|
||||||
return headerView;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return SettingSectionHeaderView.Height;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return self.settings.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
SettingItem *setting = self.settings[indexPath.row];
|
|
||||||
|
|
||||||
if (setting.type == SettingTypeEnable) {
|
|
||||||
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
cell.descriptionLabel.text = setting.subtitle;
|
|
||||||
|
|
||||||
if (indexPath.row == RowIdentifierAbilitaNotifiche) {
|
|
||||||
cell.toggleSwitch.on = self.notificationEnabled;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
self.notificationEnabled = enabled;
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].isAbilitato = self.notificationEnabled;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
} else if (indexPath.row == RowIdentifierTerremotiVicini) {
|
|
||||||
cell.toggleSwitch.on = self.notificationNearEarthquakeEnabled;
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
self.notificationNearEarthquakeEnabled = enabled;
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].isAbilitaVicini = self.notificationNearEarthquakeEnabled;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
} else if (indexPath.row == RowIdentifierTerremotiForti) {
|
|
||||||
cell.toggleSwitch.on = self.notificationStrongEarthquakeEnabled;
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
self.notificationStrongEarthquakeEnabled = enabled;
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].isTerremortiForti = self.notificationStrongEarthquakeEnabled;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
} else if (setting.type == SettingTypeMultiValues) {
|
|
||||||
SettingMultivaluesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingMultivaluesTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
cell.userInteractionEnabled = self.notificationEnabled;
|
|
||||||
cell.titleLabel.text = setting.title;
|
|
||||||
|
|
||||||
if (indexPath.row == RowIdentifierRetiSismiche) {
|
|
||||||
cell.valuesLabel.text = [self stringOfSelectedNetworks];
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
} else if (setting.type == SettingTypeSlider) {
|
|
||||||
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
|
|
||||||
if (indexPath.row == RowIdentifierRaggioPosizione) {
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
[cell configureSliderWith:self.dataSourceRaggioSisma current:self.currentUserPositionRadius];
|
|
||||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
|
||||||
[self updateUserPositionRadius:item];
|
|
||||||
};
|
|
||||||
} else if (indexPath.row == RowIdentifierEnergiaSisma) {
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
[cell configureSliderWith:self.dataSourceMagnitudoDeboli current:self.currentSeismicEnergy];
|
|
||||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
|
||||||
[self updateSeismicEnergy:item];
|
|
||||||
};
|
|
||||||
} else if (indexPath.row == RowIdentifierTerremotiFortiDistanza) {
|
|
||||||
cell.isDisabled = !self.notificationEnabled || !self.notificationStrongEarthquakeEnabled;
|
|
||||||
[cell configureSliderWith:self.dataSourceMagnitudoForti current:self.currentStrongEarthquakeDistance];
|
|
||||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
|
||||||
[self updateStrongEarthquakeEnergy:item];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
SettingItem *setting = self.settings[indexPath.row];
|
|
||||||
if (setting.segue != nil) {
|
|
||||||
[self performSegueWithIdentifier:setting.segue sender:nil];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (void)updateUserPositionRadius:(EQNGenericValue *)radius
|
|
||||||
{
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].distanzaPosizione = radius.value;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateSeismicEnergy:(EQNGenericValue *)energy
|
|
||||||
{
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].energiaSisma = energy.value;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateStrongEarthquakeEnergy:(EQNGenericValue *)energy
|
|
||||||
{
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].energiaTerremotiForti = energy.value;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)stringOfSelectedNetworks
|
|
||||||
{
|
|
||||||
NSArray *networks = [EQNNotificheReteSismiche sharedInstance].listaEnti;
|
|
||||||
networks = [networks sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
|
||||||
return [networks componentsJoinedByString:@", "];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
//
|
||||||
|
// SettingsSeismicNetworkNotificationsViewController.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 06/06/24.
|
||||||
|
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
class SettingsSeismicNetworkNotificationsViewController: SettingsBaseTableViewController {
|
||||||
|
|
||||||
|
private enum RowIdentifier: Int {
|
||||||
|
case abilitaNotifiche
|
||||||
|
case magnitudoMinima
|
||||||
|
case distanzaMassima
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isNotificationEnabled = false
|
||||||
|
private var currentMinimumMagnitude = EQNData.DefaultSettingSeismicNetworkNotificationMagitude
|
||||||
|
private var currentMaximumDistance = EQNData.DefaultSettingSeismicNetworkNotificationRadius
|
||||||
|
private let dataSourceMinimumMagnitude = EQNData.settingSeismicNetworkNotificationMagnitudes
|
||||||
|
private let dataSourceMaximumDistance = EQNData.settingSeismicNetworkNotificationRadius
|
||||||
|
|
||||||
|
private let settings: [SettingItem] = [
|
||||||
|
.init(type: .enable, title: NSLocalizedString("options_notification_enable_official", comment: "")),
|
||||||
|
.init(type: .slider, title: NSLocalizedString("options_official_minmag", comment: "")),
|
||||||
|
.init(type: .slider, title: NSLocalizedString("options_official_maxdist", comment: ""))
|
||||||
|
]
|
||||||
|
|
||||||
|
// MARK: - View Liefcycle
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
|
|
||||||
|
tableView.estimatedRowHeight = 200.0
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||||
|
tableView.registerCell(for: SettingEnableTableViewCell.self)
|
||||||
|
tableView.registerCell(for: SettingSliderTableViewCell.self)
|
||||||
|
tableView.registerCell(for: SettingMultivaluesTableViewCell.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadDataSource() {
|
||||||
|
let saved = EQNSettingSeismicNetworkNotification.shared
|
||||||
|
|
||||||
|
isNotificationEnabled = saved.isAbilitato
|
||||||
|
|
||||||
|
// magnitudo minima
|
||||||
|
let magnitudoMinima = EQNData.getSettingSeismicNetworkNotificationMagnitudes(for: saved.magnitudoMinima)
|
||||||
|
currentMinimumMagnitude = magnitudoMinima
|
||||||
|
|
||||||
|
// raggio dalla tua posizione
|
||||||
|
let distanzaMassima = EQNData.getSettingSeismicNetworkNotificationRadius(for: saved.distanzaMassima)
|
||||||
|
currentMaximumDistance = distanzaMassima
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Table view data source
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
|
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||||
|
view.titleLabel.text = NSLocalizedString("options_notification_official", comment: "")
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
|
SettingSectionHeaderView.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return settings.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||||
|
return UITableViewCell()
|
||||||
|
}
|
||||||
|
|
||||||
|
let setting = settings[indexPath.row]
|
||||||
|
switch setting.type {
|
||||||
|
case .enable:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
cell.descriptionLabel.text = setting.subtitle
|
||||||
|
|
||||||
|
switch identifier {
|
||||||
|
case .abilitaNotifiche:
|
||||||
|
cell.toggleSwitch.isOn = isNotificationEnabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeNotificationEnabled(enabled)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
|
||||||
|
case .slider:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingSliderTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
|
||||||
|
let filtersEnabled = isNotificationEnabled
|
||||||
|
switch identifier {
|
||||||
|
case .magnitudoMinima:
|
||||||
|
cell.isDisabled = !filtersEnabled
|
||||||
|
cell.configureSlider(with: dataSourceMinimumMagnitude, current: currentMinimumMagnitude)
|
||||||
|
cell.valueChanged = { [weak self] item in
|
||||||
|
self?.onChangeMinimumMagnitude(item)
|
||||||
|
}
|
||||||
|
case .distanzaMassima:
|
||||||
|
cell.isDisabled = !filtersEnabled
|
||||||
|
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
|
||||||
|
cell.valueChanged = { [weak self] item in
|
||||||
|
self?.onChangeMaximumDistance(item)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||||
|
isNotificationEnabled = enabled
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.isAbilitato = isNotificationEnabled
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeMinimumMagnitude(_ item: EQNGenericValue) {
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.magnitudoMinima = item.value
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.distanzaMassima = item.value
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
}
|
||||||
|
}
|
||||||
-71
@@ -1,71 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsSeismicNetworksViewController.swift
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Created by Busi Andrea on 26/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
class SettingsSeismicNetworksViewController: UITableViewController {
|
|
||||||
|
|
||||||
var networks = [EQNSeismicNetwork]()
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
tableView.register(SettingDetailTableViewCell.self, forCellReuseIdentifier: SettingDetailTableViewCell.Identifier)
|
|
||||||
tableView.register(SettingSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: SettingSectionHeaderView.Identifier)
|
|
||||||
|
|
||||||
networks = EQNData.seismicNetworks().sorted(by: { $0.acronym < $1.acronym })
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Table view data source
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
||||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SettingSectionHeaderView.Identifier) as! SettingSectionHeaderView
|
|
||||||
headerView.titleLabel.text = NSLocalizedString("options_agencies", comment: "");
|
|
||||||
return headerView
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
|
||||||
CGFloat(SettingSectionHeaderView.Height)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
||||||
networks.count
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
||||||
let network = networks[indexPath.row]
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: SettingDetailTableViewCell.Identifier, for: indexPath) as! SettingDetailTableViewCell
|
|
||||||
cell.textLabel?.text = "\(network.acronym) (\(network.country))"
|
|
||||||
|
|
||||||
if EQNNotificheReteSismiche.shared().listaEnti.contains(network.acronym) {
|
|
||||||
cell.accessoryType = .checkmark
|
|
||||||
} else {
|
|
||||||
cell.accessoryType = .none
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
|
||||||
|
|
||||||
let network = networks[indexPath.row]
|
|
||||||
|
|
||||||
var savedNetworks = EQNNotificheReteSismiche.shared().listaEnti
|
|
||||||
if let index = savedNetworks.firstIndex(where: { $0 == network.acronym }) {
|
|
||||||
savedNetworks.remove(at: index)
|
|
||||||
} else {
|
|
||||||
savedNetworks.append(network.acronym)
|
|
||||||
}
|
|
||||||
|
|
||||||
EQNNotificheReteSismiche.shared().listaEnti = savedNetworks
|
|
||||||
EQNNotificheReteSismiche.shared().saveUserInfo()
|
|
||||||
|
|
||||||
tableView.reloadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsUserReportAlertsViewController.h
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi 25/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
#import "SettingsBaseViewController.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface SettingsUserReportAlertsViewController : SettingsBaseViewController
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
-119
@@ -1,119 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsUserReportAlertsViewController.m
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi 25/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "SettingsUserReportAlertsViewController.h"
|
|
||||||
#import "EQNNotificheSegnalazioniUtente.h"
|
|
||||||
|
|
||||||
@interface SettingsUserReportAlertsViewController ()
|
|
||||||
|
|
||||||
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
|
|
||||||
@property (nonatomic, assign) BOOL notificationsEnabled;
|
|
||||||
@property (nonatomic, strong) EQNGenericValue *currentRadius;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation SettingsUserReportAlertsViewController
|
|
||||||
|
|
||||||
#pragma mark - View Lifecycle
|
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
|
|
||||||
[self setupUI];
|
|
||||||
|
|
||||||
self.settings = @[
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_manual", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius", @"")]
|
|
||||||
];
|
|
||||||
|
|
||||||
[self updateUI];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (void)setupUI
|
|
||||||
{
|
|
||||||
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
|
|
||||||
|
|
||||||
self.tableView.estimatedRowHeight = 100.0;
|
|
||||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
|
||||||
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateUI
|
|
||||||
{
|
|
||||||
self.notificationsEnabled = [EQNNotificheSegnalazioniUtente sharedInstance].isAbilitato;
|
|
||||||
|
|
||||||
EQNGenericValue *distanzaPosizione = [EQNData raggioSismaFor:[EQNNotificheSegnalazioniUtente sharedInstance].distanzaPosizione];
|
|
||||||
self.currentRadius = distanzaPosizione;
|
|
||||||
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self.tableView reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateRadius:(EQNGenericValue *)radius
|
|
||||||
{
|
|
||||||
self.currentRadius = radius;
|
|
||||||
[EQNNotificheSegnalazioniUtente sharedInstance].distanzaPosizione = radius.value;
|
|
||||||
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self.tableView reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Table view data source
|
|
||||||
|
|
||||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
headerView.titleLabel.text = NSLocalizedString(@"options_notification_manual", @"titolo impostazioni notifiche");
|
|
||||||
return headerView;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return SettingSectionHeaderView.Height;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return self.settings.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
SettingItem *setting = self.settings[indexPath.row];
|
|
||||||
|
|
||||||
if (setting.type == SettingTypeEnable) {
|
|
||||||
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.toggleSwitch.on = self.notificationsEnabled;
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
cell.descriptionLabel.text = setting.subtitle;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
self.notificationsEnabled = enabled;
|
|
||||||
[EQNNotificheSegnalazioniUtente sharedInstance].isAbilitato = self.notificationsEnabled;
|
|
||||||
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
return cell;
|
|
||||||
} else if (setting.type == SettingTypeSlider) {
|
|
||||||
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.isDisabled = !self.notificationsEnabled;
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
[cell configureSliderWith:[EQNData raggioSismi] current:self.currentRadius];
|
|
||||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
|
||||||
[self updateRadius:item];
|
|
||||||
};
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
//
|
||||||
|
// SettingsUserReportNotificationsViewController.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 10/06/24.
|
||||||
|
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
class SettingsUserReportNotificationsViewController: SettingsBaseTableViewController {
|
||||||
|
|
||||||
|
private enum RowIdentifier: Int {
|
||||||
|
case abilitaNotifiche
|
||||||
|
case distanzaMassima
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isNotificationEnabled = false
|
||||||
|
private var currentMaximumDistance = EQNData.DefaultSettingUserReportNotificationRadius
|
||||||
|
private let dataSourceMaximumDistance = EQNData.settingUserReportNotificationRadius
|
||||||
|
|
||||||
|
private let settings: [SettingItem] = [
|
||||||
|
.init(type: .enable, title: NSLocalizedString("options_notification_enable_manual", comment: "")),
|
||||||
|
.init(type: .slider, title: NSLocalizedString("options_radius", comment: ""))
|
||||||
|
]
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
|
|
||||||
|
tableView.estimatedRowHeight = 200.0
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||||
|
tableView.registerCell(for: SettingEnableTableViewCell.self)
|
||||||
|
tableView.registerCell(for: SettingSliderTableViewCell.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadDataSource() {
|
||||||
|
let saved = EQNSettingUserReportNotification.shared
|
||||||
|
|
||||||
|
isNotificationEnabled = saved.isAbilitato
|
||||||
|
|
||||||
|
let distanzaMassima = EQNData.getSettingUserReportNotificationRadius(for: saved.distanzaMassima)
|
||||||
|
currentMaximumDistance = distanzaMassima
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Table view delegate and data source
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
|
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||||
|
view.titleLabel.text = NSLocalizedString("options_notification_manual", comment: "")
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
|
SettingSectionHeaderView.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return settings.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||||
|
return UITableViewCell()
|
||||||
|
}
|
||||||
|
|
||||||
|
let setting = settings[indexPath.row]
|
||||||
|
switch setting.type {
|
||||||
|
case .enable:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
cell.descriptionLabel.text = setting.subtitle
|
||||||
|
|
||||||
|
switch identifier {
|
||||||
|
case .abilitaNotifiche:
|
||||||
|
cell.toggleSwitch.isOn = isNotificationEnabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeNotificationEnabled(enabled)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
|
||||||
|
case .slider:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingSliderTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
|
||||||
|
switch identifier {
|
||||||
|
case .distanzaMassima:
|
||||||
|
cell.isDisabled = !isNotificationEnabled
|
||||||
|
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
|
||||||
|
cell.valueChanged = { [weak self] item in
|
||||||
|
self?.onChangeMaximumDistance(item)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||||
|
isNotificationEnabled = enabled
|
||||||
|
EQNSettingUserReportNotification.shared.isAbilitato = isNotificationEnabled
|
||||||
|
EQNSettingUserReportNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
|
||||||
|
EQNSettingUserReportNotification.shared.distanzaMassima = item.value
|
||||||
|
EQNSettingUserReportNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,10 +27,15 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
!availableFilters.isEmpty
|
!availableFilters.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If `true` the close button will be shown on top of the map view
|
||||||
|
var isCloseButtonVisible: Bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Internal
|
// MARK: - Internal
|
||||||
|
|
||||||
/// Annotations displayed on the map
|
/// Annotations displayed on the map
|
||||||
private var mapAnnotations = [MKAnnotation]()
|
private(set) var mapAnnotations = [MKAnnotation]()
|
||||||
/// If `true`, the initial filter has been already evaluated
|
/// If `true`, the initial filter has been already evaluated
|
||||||
private var initialFilterEvaluated = false
|
private var initialFilterEvaluated = false
|
||||||
|
|
||||||
@@ -99,6 +104,34 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
return label
|
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
|
// MARK: - Init
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -116,13 +149,14 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
view.backgroundColor = .white
|
view.backgroundColor = .white
|
||||||
view.addSubview(mapView)
|
view.addSubview(mapView)
|
||||||
view.addSubview(closeButton)
|
|
||||||
view.addSubview(containerView)
|
view.addSubview(containerView)
|
||||||
if isFilterViewVisible {
|
if isFilterViewVisible {
|
||||||
view.addSubview(filtersView)
|
view.addSubview(filtersView)
|
||||||
}
|
}
|
||||||
|
if isCloseButtonVisible {
|
||||||
closeButton.addDefaultConstraint(to: view)
|
view.addSubview(closeButton)
|
||||||
|
closeButton.addDefaultConstraint(to: view)
|
||||||
|
}
|
||||||
|
|
||||||
containerView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
|
containerView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||||
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).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.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||||
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||||
mapView.bottomAnchor.constraint(equalTo: (isFilterViewVisible ? filtersView : containerView).topAnchor).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
|
// MARK: - View Lifecycle
|
||||||
@@ -152,6 +200,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
configureUI()
|
||||||
registerMapAnnotationViews()
|
registerMapAnnotationViews()
|
||||||
loadDataSource()
|
loadDataSource()
|
||||||
}
|
}
|
||||||
@@ -167,6 +216,16 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
|
|
||||||
// MARK: - Public
|
// 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
|
/// Load data to display on the map
|
||||||
func loadDataSource() {
|
func loadDataSource() {
|
||||||
// nope, subclass will implement some logic
|
// nope, subclass will implement some logic
|
||||||
@@ -219,6 +278,12 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
elaborateMapCenter()
|
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
|
/// Changes the center coordinate of the map to a given location
|
||||||
func setMapCenter(for location: CLLocation, span: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 8, longitudeDelta: 8)) {
|
func setMapCenter(for location: CLLocation, span: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 8, longitudeDelta: 8)) {
|
||||||
let region = MKCoordinateRegion(center: location.coordinate, span: span)
|
let region = MKCoordinateRegion(center: location.coordinate, span: span)
|
||||||
@@ -230,6 +295,31 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
// nope, subclass will implement logic
|
// 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
|
// MARK: - Private
|
||||||
|
|
||||||
private func updateUI() {
|
private func updateUI() {
|
||||||
@@ -249,7 +339,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
self.applyFilter(filter)
|
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)
|
present(sheet, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +357,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let annotationView = setupAnnotationView(for: annotation, on: mapView)
|
let annotationView = setupAnnotationView(for: annotation, on: mapView)
|
||||||
|
annotationView?.zPriority = zPriority(for: annotation)
|
||||||
return annotationView
|
return annotationView
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +368,5 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
|
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
|
||||||
guard let annotation = view.annotation else { return }
|
guard let annotation = view.annotation else { return }
|
||||||
didTapAnnotation(annotation)
|
didTapAnnotation(annotation)
|
||||||
|
|
||||||
mapView.deselectAnnotation(annotation, animated: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MapKit
|
import MapKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
|
class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
|
||||||
|
|
||||||
@@ -243,7 +244,7 @@ class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func navigateToSubscriptions() {
|
private func navigateToSubscriptions() {
|
||||||
let controller = SubscriptionsViewController.makeController()
|
let controller = SubscriptionsViewController()
|
||||||
let navigationController = UINavigationController(rootViewController: controller)
|
let navigationController = UINavigationController(rootViewController: controller)
|
||||||
present(navigationController, animated: true)
|
present(navigationController, animated: true)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user