Compare commits
204 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f85c60fdda | |||
| 5f2a083789 | |||
| 9e54f74847 | |||
| b6f9232f56 | |||
| dee14dea0f | |||
| db0bde2f59 | |||
| 79d0d27ec5 | |||
| 68012ec406 | |||
| 59feb7699b | |||
| 388f4e8b89 | |||
| ca3c9ebd83 | |||
| f23c19bce7 | |||
| 276fa2032a | |||
| 09f0d4d4d8 | |||
| 25f061ad5a | |||
| b9d9f7579c | |||
| 39f5ff0249 | |||
| af68d70be5 | |||
| dab999a78d | |||
| f5ede5c26d | |||
| 6d4c1eb979 | |||
| 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 |
@@ -1,5 +1,35 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 5.11
|
||||||
|
- Numeri romani in etichette shakemaps
|
||||||
|
- Aggiunta barra di recap dei filtri in Lista Sismi
|
||||||
|
- Riscritta NotificationContent in Swift
|
||||||
|
- Aggiunta animazione onda sismica in NotificationContent (+ altre modifiche grafiche)
|
||||||
|
|
||||||
|
## 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
|
## Versione 5.7
|
||||||
- Aumentato target ad iOS 13
|
- Aumentato target ad iOS 13
|
||||||
- Disattivata logica calibrazione/monitoraggio
|
- Disattivata logica calibrazione/monitoraggio
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"magnitude_range" : "0",
|
||||||
|
"google.c.a.e" : "1",
|
||||||
|
"provider" : "INGV",
|
||||||
|
"google.c.fid" : "fFjFx_Em8E-op_zHYXZpSr",
|
||||||
|
"preliminary" : "0",
|
||||||
|
"longitude" : "15.2917",
|
||||||
|
"gcm.message_id" : "1719991146578422",
|
||||||
|
"latitude" : "40.7738",
|
||||||
|
"google.c.sender.id" : "899482329945",
|
||||||
|
"type" : "official",
|
||||||
|
"magnitude" : "1.4",
|
||||||
|
"difference" : "13",
|
||||||
|
"depth" : "14.6",
|
||||||
|
"aps" : {
|
||||||
|
"mutable-content" : 1,
|
||||||
|
"alert" : {
|
||||||
|
"body" : "Sisma rilevato a",
|
||||||
|
"title-loc-key" : "Segnalazione da rete sismica",
|
||||||
|
"title" : "Segnalazione da rete sismica",
|
||||||
|
"loc-key" : "Sisma rilevato a",
|
||||||
|
"action-loc-key" : "",
|
||||||
|
"loc-args" : [
|
||||||
|
"2 km SW Laviano (SA) - M1.4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content-available" : 1,
|
||||||
|
"sound" : "default"
|
||||||
|
},
|
||||||
|
"data" : "2024-07-03 09:05:52",
|
||||||
|
"magnitude_type" : "ML",
|
||||||
|
"place" : "2 km SW Laviano (SA)",
|
||||||
|
"pop100" : "6824"
|
||||||
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"magnitude_range" : "0",
|
|
||||||
"provider" : "SGC",
|
|
||||||
"google.c.a.e" : "1",
|
|
||||||
"google.c.fid" : "d3PS1dEvrUA-tmLLpl5E5f",
|
|
||||||
"preliminary" : "0",
|
|
||||||
"longitude" : "-75.5157",
|
|
||||||
"gcm.message_id" : "1668682445010677",
|
|
||||||
"latitude" : "4.35306",
|
|
||||||
"type" : "official",
|
|
||||||
"google.c.sender.id" : "899482329945",
|
|
||||||
"difference" : "6",
|
|
||||||
"data" : "2022-11-17 11:48:00",
|
|
||||||
"depth" : "26",
|
|
||||||
"aps" : {
|
|
||||||
"content-available" : 1,
|
|
||||||
"alert" : {
|
|
||||||
"loc-key" : "Sisma rilevato a",
|
|
||||||
"title-loc-key" : "Segnalazione da rete sismica",
|
|
||||||
"loc-args" : [
|
|
||||||
"Cajamarca - Tolima, Colombia - M2.2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"mutable-content" : 1,
|
|
||||||
"sound" : "default"
|
|
||||||
},
|
|
||||||
"magnitude" : "2.2",
|
|
||||||
"magnitude_type" : "M",
|
|
||||||
"place" : "Cajamarca - Tolima, Colombia",
|
|
||||||
"pop100" : "6622"
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
"loc-args": [
|
"loc-args": [
|
||||||
"2 km da Foligno"
|
"2 km da Foligno"
|
||||||
],
|
],
|
||||||
"loc-key": "Rilevato sisma forte a",
|
"loc-key": "Sisma segnalato da utente a",
|
||||||
"title-loc-key": "Allerta sismica in tempo reale"
|
"title-loc-key": "Allerta sismica in tempo reale"
|
||||||
},
|
},
|
||||||
"category": "notifica_con_mappa",
|
"category": "notifica_con_mappa",
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="M4Y-Lb-cyx">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="M4Y-Lb-cyx">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@@ -11,37 +11,45 @@
|
|||||||
<!--Notification View Controller-->
|
<!--Notification View Controller-->
|
||||||
<scene sceneID="cwh-vc-ff4">
|
<scene sceneID="cwh-vc-ff4">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="M4Y-Lb-cyx" userLabel="Notification View Controller" customClass="NotificationViewController" sceneMemberID="viewController">
|
<viewController id="M4Y-Lb-cyx" userLabel="Notification View Controller" customClass="NotificationContentViewController" customModule="EQNNotificationContent" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" simulatedAppContext="notificationCenter" id="S3S-Oj-5AN">
|
<view key="view" contentMode="scaleToFill" simulatedAppContext="notificationCenter" id="S3S-Oj-5AN">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="320" height="330"/>
|
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pCT-Wh-lut">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pCT-Wh-lut">
|
||||||
<rect key="frame" x="8" y="216" width="304" height="20.5"/>
|
<rect key="frame" x="8" y="350" width="304" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bT3-3m-qLh">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bT3-3m-qLh">
|
||||||
<rect key="frame" x="8" y="285" width="304" height="29"/>
|
<rect key="frame" x="8" y="417" width="304" height="53"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
|
||||||
<color key="textColor" red="0.91764705879999997" green="0.46274509800000002" blue="0.0078431372550000003" alpha="1" colorSpace="calibratedRGB"/>
|
<color key="textColor" red="0.91764705879999997" green="0.46274509800000002" blue="0.0078431372550000003" alpha="1" colorSpace="calibratedRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="4ID-Zb-OQF">
|
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" showsUserLocation="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4ID-Zb-OQF">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="320" height="200"/>
|
<rect key="frame" x="0.0" y="20" width="320" height="320"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="200" id="Pgl-8e-ePq"/>
|
<constraint firstAttribute="width" secondItem="4ID-Zb-OQF" secondAttribute="height" multiplier="1:1" id="yXb-UG-FZY"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="delegate" destination="M4Y-Lb-cyx" id="Cs2-OY-eT2"/>
|
<outlet property="delegate" destination="M4Y-Lb-cyx" id="Cs2-OY-eT2"/>
|
||||||
</connections>
|
</connections>
|
||||||
</mapView>
|
</mapView>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f3d-th-bgU">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f3d-th-bgU">
|
||||||
<rect key="frame" x="8" y="244.5" width="304" height="20.5"/>
|
<rect key="frame" x="8" y="380.5" width="304" height="26.5"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="s7c-ag-zBA" customClass="EQNBlurredCloseButton" customModule="EQNNotificationContent" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="103" y="26" width="114" height="35"/>
|
||||||
|
<state key="normal" title="Button"/>
|
||||||
|
<buttonConfiguration key="configuration" style="plain" title="Tap to open"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="openAppTapped:" destination="M4Y-Lb-cyx" eventType="touchUpInside" id="Sw3-xS-cXi"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
</subviews>
|
</subviews>
|
||||||
<viewLayoutGuide key="safeArea" id="2BE-c3-nQJ"/>
|
<viewLayoutGuide key="safeArea" id="2BE-c3-nQJ"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
@@ -49,23 +57,26 @@
|
|||||||
<constraint firstItem="f3d-th-bgU" firstAttribute="leading" secondItem="pCT-Wh-lut" secondAttribute="leading" id="7qA-vV-ocI"/>
|
<constraint firstItem="f3d-th-bgU" firstAttribute="leading" secondItem="pCT-Wh-lut" secondAttribute="leading" id="7qA-vV-ocI"/>
|
||||||
<constraint firstItem="2BE-c3-nQJ" firstAttribute="trailing" secondItem="pCT-Wh-lut" secondAttribute="trailing" constant="8" id="CAC-UM-SaJ"/>
|
<constraint firstItem="2BE-c3-nQJ" firstAttribute="trailing" secondItem="pCT-Wh-lut" secondAttribute="trailing" constant="8" id="CAC-UM-SaJ"/>
|
||||||
<constraint firstItem="f3d-th-bgU" firstAttribute="trailing" secondItem="pCT-Wh-lut" secondAttribute="trailing" id="Dd7-BF-iOG"/>
|
<constraint firstItem="f3d-th-bgU" firstAttribute="trailing" secondItem="pCT-Wh-lut" secondAttribute="trailing" id="Dd7-BF-iOG"/>
|
||||||
<constraint firstItem="f3d-th-bgU" firstAttribute="top" secondItem="pCT-Wh-lut" secondAttribute="bottom" constant="8" id="FJ8-nn-ydU"/>
|
<constraint firstItem="f3d-th-bgU" firstAttribute="top" secondItem="pCT-Wh-lut" secondAttribute="bottom" constant="10" id="FJ8-nn-ydU"/>
|
||||||
<constraint firstItem="2BE-c3-nQJ" firstAttribute="bottom" secondItem="bT3-3m-qLh" secondAttribute="bottom" constant="16" id="I71-6U-jK3"/>
|
<constraint firstItem="s7c-ag-zBA" firstAttribute="top" secondItem="2BE-c3-nQJ" secondAttribute="top" constant="6" id="Gsp-ye-V4M"/>
|
||||||
<constraint firstItem="pCT-Wh-lut" firstAttribute="top" secondItem="4ID-Zb-OQF" secondAttribute="bottom" constant="16" id="It9-RA-906"/>
|
<constraint firstItem="2BE-c3-nQJ" firstAttribute="bottom" secondItem="bT3-3m-qLh" secondAttribute="bottom" constant="10" id="I71-6U-jK3"/>
|
||||||
|
<constraint firstItem="pCT-Wh-lut" firstAttribute="top" secondItem="4ID-Zb-OQF" secondAttribute="bottom" constant="10" id="It9-RA-906"/>
|
||||||
<constraint firstItem="bT3-3m-qLh" firstAttribute="trailing" secondItem="f3d-th-bgU" secondAttribute="trailing" id="KXf-x4-iZs"/>
|
<constraint firstItem="bT3-3m-qLh" firstAttribute="trailing" secondItem="f3d-th-bgU" secondAttribute="trailing" id="KXf-x4-iZs"/>
|
||||||
<constraint firstItem="bT3-3m-qLh" firstAttribute="leading" secondItem="f3d-th-bgU" secondAttribute="leading" id="QlJ-Vh-oi4"/>
|
<constraint firstItem="bT3-3m-qLh" firstAttribute="leading" secondItem="f3d-th-bgU" secondAttribute="leading" id="QlJ-Vh-oi4"/>
|
||||||
<constraint firstItem="bT3-3m-qLh" firstAttribute="top" secondItem="f3d-th-bgU" secondAttribute="bottom" constant="20" id="UUO-2F-eE7"/>
|
<constraint firstItem="bT3-3m-qLh" firstAttribute="top" secondItem="f3d-th-bgU" secondAttribute="bottom" constant="10" id="UUO-2F-eE7"/>
|
||||||
<constraint firstItem="4ID-Zb-OQF" firstAttribute="trailing" secondItem="2BE-c3-nQJ" secondAttribute="trailing" id="buf-BU-I5b"/>
|
<constraint firstItem="4ID-Zb-OQF" firstAttribute="trailing" secondItem="2BE-c3-nQJ" secondAttribute="trailing" id="buf-BU-I5b"/>
|
||||||
<constraint firstItem="4ID-Zb-OQF" firstAttribute="leading" secondItem="2BE-c3-nQJ" secondAttribute="leading" id="e8D-ji-t64"/>
|
<constraint firstItem="4ID-Zb-OQF" firstAttribute="leading" secondItem="2BE-c3-nQJ" secondAttribute="leading" id="e8D-ji-t64"/>
|
||||||
<constraint firstItem="4ID-Zb-OQF" firstAttribute="top" secondItem="2BE-c3-nQJ" secondAttribute="top" id="hL6-gc-S6i"/>
|
<constraint firstItem="4ID-Zb-OQF" firstAttribute="top" secondItem="2BE-c3-nQJ" secondAttribute="top" id="hL6-gc-S6i"/>
|
||||||
|
<constraint firstItem="s7c-ag-zBA" firstAttribute="centerX" secondItem="S3S-Oj-5AN" secondAttribute="centerX" id="mKj-2O-QDw"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<extendedEdge key="edgesForExtendedLayout"/>
|
<extendedEdge key="edgesForExtendedLayout"/>
|
||||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||||
<size key="freeformSize" width="320" height="330"/>
|
<size key="freeformSize" width="320" height="480"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="descriptionLabel" destination="f3d-th-bgU" id="Aym-KJ-DqY"/>
|
<outlet property="descriptionLabel" destination="f3d-th-bgU" id="Aym-KJ-DqY"/>
|
||||||
<outlet property="mapView" destination="4ID-Zb-OQF" id="x8o-nT-bL4"/>
|
<outlet property="mapView" destination="4ID-Zb-OQF" id="x8o-nT-bL4"/>
|
||||||
|
<outlet property="tapToOpenButton" destination="s7c-ag-zBA" id="4aZ-hT-1vc"/>
|
||||||
<outlet property="titleLabel" destination="pCT-Wh-lut" id="uIg-dn-Wms"/>
|
<outlet property="titleLabel" destination="pCT-Wh-lut" id="uIg-dn-Wms"/>
|
||||||
<outlet property="waveLabel" destination="bT3-3m-qLh" id="AkJ-nd-d2R"/>
|
<outlet property="waveLabel" destination="bT3-3m-qLh" id="AkJ-nd-d2R"/>
|
||||||
</connections>
|
</connections>
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
//
|
||||||
|
// NotificationContentViewController.swift
|
||||||
|
// EQNNotificationContent
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 24/07/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MapKit
|
||||||
|
import UserNotifications
|
||||||
|
import UserNotificationsUI
|
||||||
|
|
||||||
|
class NotificationContentViewController: UIViewController {
|
||||||
|
|
||||||
|
// MARK: - UI
|
||||||
|
|
||||||
|
@IBOutlet private weak var titleLabel: UILabel!
|
||||||
|
@IBOutlet private weak var descriptionLabel: UILabel!
|
||||||
|
@IBOutlet private weak var waveLabel: UILabel!
|
||||||
|
@IBOutlet private weak var mapView: MKMapView!
|
||||||
|
@IBOutlet private weak var tapToOpenButton: UIButton!
|
||||||
|
|
||||||
|
private var animator: MapSeismicWaveAnimator?
|
||||||
|
|
||||||
|
// MARK: - View
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
let tapRecognizer = UITapGestureRecognizer(
|
||||||
|
target: self,
|
||||||
|
action: #selector(openAppTapped(_:))
|
||||||
|
)
|
||||||
|
view.addGestureRecognizer(tapRecognizer)
|
||||||
|
|
||||||
|
mapView
|
||||||
|
.register(
|
||||||
|
EQNCustomAnnotationView.self,
|
||||||
|
forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier
|
||||||
|
)
|
||||||
|
mapView
|
||||||
|
.register(
|
||||||
|
EQNCustomAnnotationView.self,
|
||||||
|
forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SmallIdentifier
|
||||||
|
)
|
||||||
|
tapToOpenButton.setTitle("tap_to_open".localized, for: .normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
animator?.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
|
||||||
|
@IBAction private func openAppTapped(_ sender: Any) {
|
||||||
|
extensionContext?.performNotificationDefaultAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationContentViewController: UNNotificationContentExtension {
|
||||||
|
|
||||||
|
func didReceive(_ notification: UNNotification) {
|
||||||
|
let content = notification.request.content
|
||||||
|
|
||||||
|
titleLabel.text = content.title
|
||||||
|
descriptionLabel.text = content.body
|
||||||
|
|
||||||
|
let notification = EQNRealtimePushNotification.from(
|
||||||
|
userInfo: content.userInfo,
|
||||||
|
title: "",
|
||||||
|
displayTitle: content.title,
|
||||||
|
displayBody: content.body
|
||||||
|
)
|
||||||
|
if let notification {
|
||||||
|
let coordinate = notification.coordinate.coordinate
|
||||||
|
let span = MKCoordinateSpan(latitudeDelta: 6, longitudeDelta: 6)
|
||||||
|
let region = MKCoordinateRegion(
|
||||||
|
center: coordinate,
|
||||||
|
span: span
|
||||||
|
)
|
||||||
|
mapView.setCenter(coordinate, animated: false)
|
||||||
|
mapView.setRegion(region, animated: true)
|
||||||
|
|
||||||
|
switch notification.type.lowercased() {
|
||||||
|
case "eqn":
|
||||||
|
let annotation = EQNMapAnnotationPastquake(
|
||||||
|
title: "",
|
||||||
|
coordinate: coordinate,
|
||||||
|
intensity: notification.intensity
|
||||||
|
)
|
||||||
|
mapView.addAnnotation(annotation)
|
||||||
|
case "manual":
|
||||||
|
let annotation = EQNMapAnnotationUserReport(
|
||||||
|
magnitude: notification.magnitude,
|
||||||
|
coordinate: coordinate
|
||||||
|
)
|
||||||
|
mapView.addAnnotation(annotation)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// create animator to manage wave animation and countdown
|
||||||
|
animator = MapSeismicWaveAnimator(
|
||||||
|
realtimeAlert: notification,
|
||||||
|
mapView: mapView,
|
||||||
|
waveTimeLabel: waveLabel
|
||||||
|
)
|
||||||
|
animator?.start()
|
||||||
|
// set color based on intensity
|
||||||
|
descriptionLabel.textColor = notification.relativeIntensityColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationContentViewController: MKMapViewDelegate {
|
||||||
|
|
||||||
|
func mapView(_ mapView: MKMapView, viewFor annotation: any MKAnnotation) -> MKAnnotationView? {
|
||||||
|
switch annotation {
|
||||||
|
case let pastquake as EQNMapAnnotationPastquake:
|
||||||
|
let annotationView = mapView.dequeueReusableAnnotationView(
|
||||||
|
withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier
|
||||||
|
) as! EQNCustomAnnotationView
|
||||||
|
annotationView.image = pastquake.image
|
||||||
|
annotationView.title = pastquake.title
|
||||||
|
return annotationView
|
||||||
|
case let report as EQNMapAnnotationUserReport:
|
||||||
|
let annotationView = mapView.dequeueReusableAnnotationView(
|
||||||
|
withIdentifier: EQNCustomAnnotationView.SmallIdentifier
|
||||||
|
) as! EQNCustomAnnotationView
|
||||||
|
annotationView.image = report.image(with: EQNCustomAnnotationView.SmallViewImageHeight)
|
||||||
|
annotationView.title = report.timeDifference
|
||||||
|
return annotationView
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||||
|
animator?.getOverlayRenderer(for: overlay) ?? MKOverlayRenderer(overlay: overlay)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
//
|
|
||||||
// NotificationViewController.h
|
|
||||||
// EQNNotificationContent
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi on 14/10/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
@interface NotificationViewController : UIViewController
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
//
|
|
||||||
// NotificationViewController.m
|
|
||||||
// EQNNotificationContent
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi on 14/10/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "NotificationViewController.h"
|
|
||||||
#import "EQNUtility.h"
|
|
||||||
#import "EQNNotificationContent-Swift.h"
|
|
||||||
|
|
||||||
@import UserNotifications;
|
|
||||||
@import UserNotificationsUI;
|
|
||||||
@import MapKit;
|
|
||||||
|
|
||||||
@interface NotificationViewController () <UNNotificationContentExtension, MKMapViewDelegate>
|
|
||||||
@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
|
|
||||||
@property (weak, nonatomic) IBOutlet UILabel *descriptionLabel;
|
|
||||||
@property (weak, nonatomic) IBOutlet UILabel *waveLabel;
|
|
||||||
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
|
|
||||||
|
|
||||||
/// This will be calculated as seismic date + warning time
|
|
||||||
@property (strong, nonatomic) NSDate *userSeismicTimestamp;
|
|
||||||
@property (strong, nonatomic) NSTimer *countdownTimer;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation NotificationViewController
|
|
||||||
|
|
||||||
- (void)setMapView:(MKMapView *)mapView
|
|
||||||
{
|
|
||||||
_mapView = mapView;
|
|
||||||
_mapView.scrollEnabled = NO;
|
|
||||||
_mapView.zoomEnabled = NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - View Lifecycle
|
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
|
|
||||||
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
|
|
||||||
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SmallIdentifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)didReceiveNotification:(UNNotification *)notification
|
|
||||||
{
|
|
||||||
UNNotificationContent *content = notification.request.content;
|
|
||||||
NSDictionary *userInfo = content.userInfo;
|
|
||||||
|
|
||||||
// set title and description
|
|
||||||
self.titleLabel.text = content.title;
|
|
||||||
self.descriptionLabel.text = content.body;
|
|
||||||
|
|
||||||
// add annotation onthe map
|
|
||||||
CLLocation *coordinate = [[CLLocation alloc] initWithLatitude:[userInfo[@"latitude"] doubleValue]
|
|
||||||
longitude:[userInfo[@"longitude"] doubleValue]];
|
|
||||||
MKCoordinateSpan span = MKCoordinateSpanMake(10.5, 10.5);
|
|
||||||
MKCoordinateRegion region = MKCoordinateRegionMake(coordinate.coordinate, span);
|
|
||||||
[self.mapView setCenterCoordinate:coordinate.coordinate animated:NO];
|
|
||||||
[self.mapView setRegion:region animated:YES];
|
|
||||||
|
|
||||||
if ([userInfo[@"type"] isEqualToString:@"eqn"]) {
|
|
||||||
EQNMapAnnotationPastquake *annotation = [[EQNMapAnnotationPastquake alloc] initWithTitle:@""
|
|
||||||
coordinate:coordinate.coordinate
|
|
||||||
intensity:[userInfo[@"intensity"] intValue]];
|
|
||||||
[self.mapView addAnnotation:annotation];
|
|
||||||
|
|
||||||
} else if ([userInfo[@"type"] isEqualToString:@"manual"]){
|
|
||||||
EQNMapAnnotationUserReport *annotation = [[EQNMapAnnotationUserReport alloc] initWithMagnitude:[userInfo[@"magnitude"] intValue]
|
|
||||||
coordinate:coordinate.coordinate];
|
|
||||||
[self.mapView addAnnotation:annotation];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
self.userSeismicTimestamp = [EQNUtility calculateUserSeismicTimestampFromUserInfo:userInfo];
|
|
||||||
if (self.userSeismicTimestamp) {
|
|
||||||
// start the countdown
|
|
||||||
self.countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
|
|
||||||
target:self
|
|
||||||
selector:@selector(countdownFired:)
|
|
||||||
userInfo:nil
|
|
||||||
repeats:YES];
|
|
||||||
[self.countdownTimer fire];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (void)countdownFired:(id)sender
|
|
||||||
{
|
|
||||||
NSDate *now = [NSDate date];
|
|
||||||
NSTimeInterval difference = MAX([self.userSeismicTimestamp timeIntervalSinceDate:now], 0);
|
|
||||||
NSInteger seconds = (int)lround(difference);
|
|
||||||
|
|
||||||
self.waveLabel.text = [NSString localizedStringWithFormat:NSLocalizedString(@"alert_wave", @""), seconds];
|
|
||||||
|
|
||||||
if (difference <= 0) {
|
|
||||||
// stop the countdown
|
|
||||||
[self.countdownTimer invalidate];
|
|
||||||
self.countdownTimer = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - MKMapViewDelegate
|
|
||||||
|
|
||||||
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
|
|
||||||
{
|
|
||||||
if ([annotation isKindOfClass:[EQNMapAnnotationPastquake class]]) {
|
|
||||||
EQNMapAnnotationPastquake *pastquake = (EQNMapAnnotationPastquake *)annotation;
|
|
||||||
|
|
||||||
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
|
|
||||||
annotationView.image = pastquake.image;
|
|
||||||
annotationView.title = pastquake.title;
|
|
||||||
return annotationView;
|
|
||||||
|
|
||||||
} else if ([annotation isKindOfClass:[EQNMapAnnotationUserReport class]]) {
|
|
||||||
EQNMapAnnotationUserReport *report = (EQNMapAnnotationUserReport *)annotation;
|
|
||||||
|
|
||||||
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SmallIdentifier];
|
|
||||||
annotationView.image = [report imageWithHeight:EQNCustomAnnotationView.SmallViewImageHeight];
|
|
||||||
annotationView.title = report.timeDifference;
|
|
||||||
return annotationView;
|
|
||||||
}
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -51,6 +51,43 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
bestAttemptContent.sound = UNNotificationSound(named: Self.EQNSoundNotification)
|
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")
|
let intensity = userInfo.integer(forKey: "intensity")
|
||||||
switch intensity {
|
switch intensity {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -63,57 +100,13 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate intensity and get proper string to display
|
|
||||||
guard let latitude = userInfo.double(forKey: "latitude"),
|
|
||||||
let longitude = userInfo.double(forKey: "longitude"),
|
|
||||||
let peak = userInfo.double(forKey: "peak") else {
|
|
||||||
print("[NotificationService] Unable to get base info for intensity calculation")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let location = CLLocation(latitude: latitude, longitude: longitude)
|
|
||||||
guard let distance = EQNUserData.shared.lastLocation?.distance(from: location) else {
|
|
||||||
print("[NotificationService] Unable to calculate distance or get last location")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let distanceKm = distance / 1_000
|
|
||||||
let intensita = peak * exp(-distanceKm/peak/250)
|
|
||||||
let stringSuffix: String
|
|
||||||
|
|
||||||
if intensita < 0.004 {
|
|
||||||
stringSuffix = "no_shaking"
|
|
||||||
} else if intensita < 0.30 {
|
|
||||||
stringSuffix = "mild"
|
|
||||||
} else if intensita < 0.70 {
|
|
||||||
stringSuffix = "moderate"
|
|
||||||
} else {
|
|
||||||
stringSuffix = "strong"
|
|
||||||
}
|
|
||||||
bestAttemptContent.body = "alert_intensity_\(stringSuffix)".localized
|
|
||||||
|
|
||||||
case "manual":
|
case "manual":
|
||||||
// there are 12 levels, so a customized icon doesn't make sense
|
// there are 12 levels, so a customized icon doesn't make sense
|
||||||
// use a generic warning icon instead
|
// use a generic warning icon instead
|
||||||
iconName = "warning_yellow.png"
|
iconName = "warning_yellow.png"
|
||||||
case "official":
|
case "official":
|
||||||
let provider = userInfo.string(forKey: "provider", orDefault: "")
|
// don't show any images
|
||||||
let intensity = userInfo.double(forKey: "magnitude", orDefault: 0)
|
break
|
||||||
|
|
||||||
let color: String
|
|
||||||
if intensity < 2.0 {
|
|
||||||
color = "_white"
|
|
||||||
} else if intensity < 3.5 {
|
|
||||||
color = "_green"
|
|
||||||
} else if intensity < 4.5 {
|
|
||||||
color = "_yellow"
|
|
||||||
} else if intensity < 5.5 {
|
|
||||||
color = "_red"
|
|
||||||
} else {
|
|
||||||
color = "_purple"
|
|
||||||
}
|
|
||||||
|
|
||||||
iconName = manualIconName(for: provider, color: color)
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -174,7 +167,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
|
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
|
||||||
|
|
||||||
// !! Note: this is a known issue/bug
|
// !! Note: this is a known issue/bug
|
||||||
// we need to add a delay before invoking the completion, otherwise the notification will not be remved
|
// we need to add a delay before invoking the completion, otherwise the notification will not be removed
|
||||||
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
|
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
completion()
|
completion()
|
||||||
@@ -182,6 +175,20 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func isMildQuake(
|
||||||
|
magnitude: Double,
|
||||||
|
distance: Double
|
||||||
|
) -> Bool {
|
||||||
|
var intensity_at_location: Double = 0
|
||||||
|
if distance > 0 {
|
||||||
|
let R: Double = 6371
|
||||||
|
let eq_depth: Double = 10.0
|
||||||
|
let hyp_distance = sqrt(pow(eq_depth, 2) + 4 * R * (R - eq_depth) * pow(sin(distance / (2 * R)), 2))
|
||||||
|
intensity_at_location = -2.15 * log10(hyp_distance) + 1.0 * magnitude + 2.31
|
||||||
|
}
|
||||||
|
return intensity_at_location < 3
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func manualIconName(for provider: String, color: String) -> String {
|
private func manualIconName(for provider: String, color: String) -> String {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -2,6 +2,6 @@
|
|||||||
<Workspace
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "self:Earthquake Network.xcodeproj">
|
location = "self:">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|||||||
+186
@@ -0,0 +1,186 @@
|
|||||||
|
{
|
||||||
|
"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" : "fdc352fabaf5916e7faa1f96ad02b1957e93e5a5",
|
||||||
|
"version" : "11.15.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "google-ads-on-device-conversion-ios-sdk",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "428d8bb138e00f9a3f4f61cc6cd8863607524f65",
|
||||||
|
"version" : "2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "googleappmeasurement",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "45ce435e9406d3c674dd249a042b932bee006f60",
|
||||||
|
"version" : "11.15.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" : "60da361632d0de02786f709bdc0c4df340f7613e",
|
||||||
|
"version" : "8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "809b56a43fadac72db9963a21c74688af7ef51b7",
|
||||||
|
"version" : "2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "solar",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/ceeK/Solar.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "c2b96f2d5fb7f835b91cefac5e83101f54643901",
|
||||||
|
"version" : "3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-package-manager-google-mobile-ads",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "aa24c7dc03bca62c42747314dc8537f15587b50d",
|
||||||
|
"version" : "12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-package-manager-google-user-messaging-platform",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "9b68aa69fb508f0274853e226c734151a973c7b7",
|
||||||
|
"version" : "2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-protobuf",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
|
||||||
|
"version" : "1.26.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1410"
|
LastUpgradeVersion = "1620"
|
||||||
wasCreatedForAppExtension = "YES"
|
wasCreatedForAppExtension = "YES"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1410"
|
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">
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1410"
|
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>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"pins" : [
|
|
||||||
{
|
|
||||||
"identity" : "shogun",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/andreabusi-it/Shogun",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "c164595fdd5d0771a6a24cbff85a7582f0f07311",
|
|
||||||
"version" : "1.3.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version" : 2
|
|
||||||
}
|
|
||||||
@@ -11,9 +11,6 @@
|
|||||||
#import "EQNUser.h"
|
#import "EQNUser.h"
|
||||||
#import "EQNAccelerometroManager.h"
|
#import "EQNAccelerometroManager.h"
|
||||||
#import "EQNManager.h"
|
#import "EQNManager.h"
|
||||||
#import "EQNAllertaSismica.h"
|
|
||||||
#import "EQNNotificheSegnalazioniUtente.h"
|
|
||||||
#import "EQNNotificheReteSismiche.h"
|
|
||||||
#import "EQNMainTabBarController.h"
|
#import "EQNMainTabBarController.h"
|
||||||
#import "NSDictionary+EQNExtensions.h"
|
#import "NSDictionary+EQNExtensions.h"
|
||||||
|
|
||||||
@@ -152,7 +149,7 @@
|
|||||||
[self handlePushNotificationWithNotificationContent:content];
|
[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.
|
||||||
@@ -173,20 +170,22 @@
|
|||||||
{
|
{
|
||||||
NSString *type = content.userInfo[@"type"];
|
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 ([type isEqualToString:@"eqn"]) {
|
if ([type isEqualToString:@"eqn"]) {
|
||||||
// Store both original payload and modified title/body
|
|
||||||
// This will be usefull to avoid to re-evaluate logic for title display.
|
|
||||||
NSDictionary *notification = @{
|
|
||||||
@"title": content.title,
|
|
||||||
@"body": content.body,
|
|
||||||
@"userInfo": content.userInfo
|
|
||||||
};
|
|
||||||
[EQNRealtimePushNotification storeNotificationWithPayload:notification];
|
[EQNRealtimePushNotification storeNotificationWithPayload:notification];
|
||||||
section = EQNTabBarSectionAllerte;
|
section = EQNTabBarSectionAllerte;
|
||||||
} else if([type isEqualToString:@"manual"]) {
|
} else if([type isEqualToString:@"manual"]) {
|
||||||
section = EQNTabBarSectionSegnalazioni;
|
section = EQNTabBarSectionSegnalazioni;
|
||||||
} else if([type isEqualToString:@"official"]) {
|
} else if([type isEqualToString:@"official"]) {
|
||||||
|
[EQNOfficialPushNotification storeNotificationWithPayload:notification];
|
||||||
section = EQNTabBarSectionRetiSismiche;
|
section = EQNTabBarSectionRetiSismiche;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,20 +207,16 @@
|
|||||||
|
|
||||||
- (void)configureAppTracking
|
- (void)configureAppTracking
|
||||||
{
|
{
|
||||||
if (@available(iOS 14, *)) {
|
// add a delay otherwise the alert will not be displayed
|
||||||
// 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(), ^{
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
|
||||||
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
|
if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
|
||||||
if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
|
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = YES;
|
||||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = YES;
|
} else {
|
||||||
} else {
|
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = NO;
|
||||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = NO;
|
}
|
||||||
}
|
}];
|
||||||
}];
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = YES;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)configureFirebase
|
- (void)configureFirebase
|
||||||
@@ -233,8 +228,7 @@
|
|||||||
- (void)configureFacebookSDKWithApplication:(UIApplication *)application andOptions:(NSDictionary *)launchOptions
|
- (void)configureFacebookSDKWithApplication:(UIApplication *)application andOptions:(NSDictionary *)launchOptions
|
||||||
{
|
{
|
||||||
[FBSDKApplicationDelegate.sharedInstance application:application didFinishLaunchingWithOptions:launchOptions];
|
[FBSDKApplicationDelegate.sharedInstance application:application didFinishLaunchingWithOptions:launchOptions];
|
||||||
[FBSDKSettings.sharedSettings setIsAdvertiserIDCollectionEnabled:YES];
|
FBSDKSettings.sharedSettings.isAdvertiserIDCollectionEnabled = YES;
|
||||||
[FBSDKSettings.sharedSettings setIsAdvertiserIDCollectionEnabled:YES];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - FIRMessagingDelegate
|
#pragma mark - FIRMessagingDelegate
|
||||||
@@ -244,9 +238,9 @@
|
|||||||
NSLog(@"[Firebase] fcmToken %@", fcmToken);
|
NSLog(@"[Firebase] fcmToken %@", fcmToken);
|
||||||
if (EQNUserData.sharedData.isFirstStart) {
|
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 registerUserIfNeededWithFirebaseToken:fcmToken];
|
[EQNUser.defaultUser registerUserIfNeededWithFirebaseToken:fcmToken];
|
||||||
|
|||||||
@@ -17,24 +17,17 @@ extension UserDefaults {
|
|||||||
|
|
||||||
// Impostazioni della sezione `Allerta in tempo reale`
|
// Impostazioni della sezione `Allerta in tempo reale`
|
||||||
static let AllertaSismicaAbilitato = "NOTIFICHE_ALLERA_SISMICA_ABILITATO"
|
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 AllertaSismicaCriticalAlerts = "NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS"
|
||||||
static let AllertaSismicaSismiDaNotificare = "NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE"
|
static let AllertaSismicaSismiDaNotificare = "NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE"
|
||||||
static let AllertaSismicaRaggioSismiLievi = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI"
|
static let AllertaSismicaRaggioSismiLievi = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI"
|
||||||
static let AllertaSismicaRaggioSismiForti = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI"
|
static let AllertaSismicaRaggioSismiForti = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI"
|
||||||
static let AllertaSismicaImpostaVolume = "NOTIFICHE_ALLERA_SISMICA_IMPOSTA_VOLUME"
|
|
||||||
static let AllertaSismicaTestaAllarma = "NOTIFICHE_ALLERA_SISMICA_TESTA_ALLARME"
|
|
||||||
static let AllertaSismicaAbilitaIntervallo = "NOTIFICHE_ALLERA_SISMICA_ABILITA_INTERVALLO"
|
|
||||||
static let AllertaSismicaOraInizio = "NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
|
|
||||||
static let AllertaSismicaOraFine = "NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
|
|
||||||
|
|
||||||
// Impostazioni della sezione `Notifiche da reti sismiche`
|
// Impostazioni della sezione `Notifiche da reti sismiche`
|
||||||
static let NotificheRetiSismicheAbilitato = "NOTIFICHE_ATTIVA_RETI_SISMICHE"
|
static let NotificheRetiSismicheAbilitato = "NOTIFICHE_ATTIVA_RETI_SISMICHE"
|
||||||
static let NotificheRetiSismicheViciniAbilitato = "NOTIFICHE_ATTIVA_RETI_SISMICHE_VICINE"
|
static let NotificheRetiSismicheMagnitudoMinima = "NOTIFICHE_ATTIVA_RETI_ENERGIA_SISMI"
|
||||||
static let NotificheRetiSismicheTerremotiFortiAbilitato = "NOTIFICHE_ATTIVA_RETI_TERREMOTI_FORTI"
|
static let NotificheRetiSismicheDistanzaMassima = "NOTIFICHE_DISTANZA_POSIZIONE_RETI_SISMICHE"
|
||||||
static let NotificheRetiSismicheDistanzaPosizione = "NOTIFICHE_DISTANZA_POSIZIONE_RETI_SISMICHE"
|
static let NotificheRetiSismicheFiltroNotifiche = "NOTIFICHE_FILTRO_NOTIFICHE_RETI_SISMICHE"
|
||||||
static let NotificheRetiSismicheEnergiaSisma = "NOTIFICHE_ATTIVA_RETI_ENERGIA_SISMI"
|
|
||||||
static let NotificheRetiSismicheEnergiaTerremotiForti = "NOTIFICHE_ATTIVA_RETI_ENERGIA_FORTI"
|
|
||||||
static let NotificheRetiSismicheListaEnti = "NOTIFICHE_ATTIVA_RETI_LISTA_ENTI"
|
|
||||||
|
|
||||||
// Impostazioni della sezione `Notifiche segnalazioni utente`
|
// Impostazioni della sezione `Notifiche segnalazioni utente`
|
||||||
static let NotificheSegnalazioniUtenteAbilitato = "NOTIFICHE_SU_ATTIVA_SEGNALAZIONE_UTENTE"
|
static let NotificheSegnalazioniUtenteAbilitato = "NOTIFICHE_SU_ATTIVA_SEGNALAZIONE_UTENTE"
|
||||||
@@ -45,6 +38,7 @@ extension UserDefaults {
|
|||||||
static let UserReportCodeStatus = "CODE_MESSAGE_EQN"
|
static let UserReportCodeStatus = "CODE_MESSAGE_EQN"
|
||||||
|
|
||||||
// Proprietà e preferenze dell'utente
|
// Proprietà e preferenze dell'utente
|
||||||
|
static let FirstAppStartExecuted = "EQNUserDefaultFirstAppStartExecuted"
|
||||||
/// Ultima posizione conosciuta dell'utente
|
/// Ultima posizione conosciuta dell'utente
|
||||||
static let UserDataLastLocation = "EQNLast_Location"
|
static let UserDataLastLocation = "EQNLast_Location"
|
||||||
/// Token Firebase dell'utente corrente
|
/// Token Firebase dell'utente corrente
|
||||||
@@ -63,23 +57,37 @@ extension UserDefaults {
|
|||||||
static let UserReportExpandedView = "EQNData.UserReportExpandedView"
|
static let UserReportExpandedView = "EQNData.UserReportExpandedView"
|
||||||
/// Se `true` visualizza le opzioni nella singole card in reti sismiche
|
/// Se `true` visualizza le opzioni nella singole card in reti sismiche
|
||||||
static let AlertsShowCardOptions = "EQNetwork.AlertsShowAllCards"
|
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
|
// Migrazioni
|
||||||
static let AppMigrationV5_3 = "EQNUserDefaultMigrationV5_3"
|
static let AppMigrationV5_3 = "EQNUserDefaultMigrationV5_3"
|
||||||
static let AppMigrationV5_4 = "EQNUserDefaultMigrationV5_4"
|
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
|
// Notifica allerta salvata
|
||||||
static let RealTimeAlertPayload = "EQNData.RealtimePushNotificationPayload"
|
static let RealTimeAlertPayload = "EQNData.RealtimePushNotificationPayload"
|
||||||
static let RealTimeAlertDate = "EQNData.RealtimeAlertDate"
|
static let RealTimeAlertDate = "EQNData.RealtimeAlertDate"
|
||||||
|
// Notifica rete sismica aperta
|
||||||
|
static let OfficialAlertPayload = "EQNData.OfficialPushNotificationPayload"
|
||||||
|
|
||||||
// Filtri sezioni reti sismiche
|
// 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 SeismicMagnitudoMinima = "EQN_MAGNITUDO_MINIMA"
|
||||||
static let SeismicDistanzaMassima = "EQN_DISTANZA_MASSIMA"
|
static let SeismicDistanzaMassima = "EQN_DISTANZA_MASSIMA"
|
||||||
static let SeismicEtaMassima = "EQN_ETA_MASSIMA"
|
|
||||||
static let SeismicSismiFortiAbilitati = "EQN_SISMI_FORTI_ABILITATI"
|
|
||||||
static let SeismicSismiForti = "EQN_SISMI_FORTI"
|
|
||||||
static let SeismicSismiQualsiasiMagnitudo = "EQN_SISMI_QUALSIASI_MAGNITUDO"
|
|
||||||
static let SeismicModificaImpostazioni = "EQN_SISMI_MODIFICA_IMPOSTAZIONI"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UserDefaults {
|
extension UserDefaults {
|
||||||
@@ -95,4 +103,13 @@ extension UserDefaults {
|
|||||||
}
|
}
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func enumObject<T: RawRepresentable>(forKey key: String, or defaultValue: T) -> T {
|
||||||
|
if let rawValue = UserDefaults.standard.object(forKey: key) as? T.RawValue,
|
||||||
|
let value = T.init(rawValue: rawValue) {
|
||||||
|
return value
|
||||||
|
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,6 @@
|
|||||||
|
|
||||||
@implementation AllerteViewController
|
@implementation AllerteViewController
|
||||||
|
|
||||||
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,
|
||||||
@@ -102,9 +100,18 @@ 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 = 200.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) {
|
if (EQNBackgroundPositionDebugHelper.shared.isEnabled) {
|
||||||
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:@selector(backgroundPositionDebugTapped:)];
|
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:@selector(backgroundPositionDebugTapped:)];
|
||||||
}
|
}
|
||||||
@@ -142,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)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,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)
|
||||||
@@ -265,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];
|
||||||
|
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||||
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
||||||
cell.notification = notification;
|
[cell updateWith:notification];
|
||||||
|
|
||||||
__weak AllerteViewController *weakSelf = self;
|
__weak AllerteViewController *weakSelf = self;
|
||||||
cell.onTapClose = ^{
|
cell.onTapClose = ^{
|
||||||
@@ -291,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 = ^{
|
||||||
@@ -311,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];
|
||||||
};
|
};
|
||||||
@@ -320,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];
|
||||||
};
|
};
|
||||||
@@ -328,12 +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;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,9 +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];
|
||||||
|
}; 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
-25
@@ -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,26 +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 }
|
||||||
let timeZone = TimeZone.current.localizedName(for: .generic, locale: .current) ?? TimeZone.current.identifier
|
let timeZone = TimeZone.current.localizedName(for: .generic, locale: .current) ?? TimeZone.current.identifier
|
||||||
if let sunrise = solar.sunrise {
|
if let sunrise = solar.sunrise {
|
||||||
sunriseTimeLabel.text = dateFormatter.string(from: sunrise) + " \(timeZone)"
|
sunriseTimeLabel.text = dateFormatter.string(from: sunrise) + " \(timeZone)"
|
||||||
}
|
}
|
||||||
if let sunset = solar.sunset {
|
if let sunset = solar.sunset {
|
||||||
sunsetTimeLabel.text = dateFormatter.string(from: sunset) + " \(timeZone)"
|
sunsetTimeLabel.text = dateFormatter.string(from: sunset) + " \(timeZone)"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-22
@@ -8,37 +8,62 @@
|
|||||||
|
|
||||||
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 }
|
||||||
|
|
||||||
lastSubscriptionLabel.text = subscriptionText(for: smartphoneNetwork.lastSubscriptionDiff)
|
lastSubscriptionLabel.text = subscriptionText(for: smartphoneNetwork.lastSubscriptionDiff)
|
||||||
}
|
}
|
||||||
|
|||||||
+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?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+152
-58
@@ -10,78 +10,173 @@ import UIKit
|
|||||||
import MapKit
|
import MapKit
|
||||||
import Shogun
|
import Shogun
|
||||||
|
|
||||||
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMapViewDelegate {
|
|
||||||
|
|
||||||
|
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseContainerTableViewCell, MKMapViewDelegate {
|
||||||
|
|
||||||
|
override var isHeaderVisible: Bool { false }
|
||||||
|
|
||||||
typealias DefaultCompletion = () -> Void
|
typealias DefaultCompletion = () -> Void
|
||||||
|
|
||||||
@objc var notification: EQNRealtimePushNotification? {
|
|
||||||
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 notificationIntensityLabel: UILabel!
|
|
||||||
@IBOutlet weak var waveTimeLabel: UILabel!
|
|
||||||
@IBOutlet weak var mapView: MKMapView! {
|
|
||||||
didSet {
|
|
||||||
mapView.delegate = self
|
|
||||||
mapView.isScrollEnabled = false
|
|
||||||
mapView.isZoomEnabled = false
|
|
||||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@IBOutlet private weak var shareButton: UIButton!
|
|
||||||
@IBOutlet private weak var rateAppButton: UIButton!
|
|
||||||
@IBOutlet private weak var viewOnTwitterButton: UIButton!
|
|
||||||
@IBOutlet private weak var closeButton: UIButton!
|
|
||||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
|
||||||
private var impactTimestamp: Date?
|
|
||||||
private var countdownTimer: Timer?
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
|
||||||
|
|
||||||
override func awakeFromNib() {
|
|
||||||
super.awakeFromNib()
|
|
||||||
|
|
||||||
localizeUI()
|
let stackView = UIStackView(arrangedSubviews: [shareButton, rateAppButton])
|
||||||
setUI()
|
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 setUI() {
|
@objc
|
||||||
shareButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
func update(with notification: EQNRealtimePushNotification?) {
|
||||||
rateAppButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
|
||||||
viewOnTwitterButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
|
||||||
closeButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateUI() {
|
|
||||||
// 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 else { return }
|
guard let notification = notification else { return }
|
||||||
|
|
||||||
containerView.backgroundColor = backgroundColor(for: notification.relativeIntensity())
|
backgroundColor = backgroundColor(for: notification.relativeIntensity())
|
||||||
notificationTitleLabel.text = notification.title
|
notificationTitleLabel.text = notification.title
|
||||||
notificationIntensityLabel.text = notification.displayBody
|
notificationIntensityLabel.text = notification.displayBody
|
||||||
notificationIntensityLabel.textColor = notification.relativeIntensityColor
|
notificationIntensityLabel.textColor = notification.relativeIntensityColor
|
||||||
@@ -95,7 +190,6 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
|||||||
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: ""), "\(notification.counter)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||||
@@ -118,37 +212,37 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
|||||||
annotationView.title = annotation.title
|
annotationView.title = annotation.title
|
||||||
return annotationView
|
return annotationView
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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: - Helpers
|
// MARK: - Private
|
||||||
|
|
||||||
private func backgroundColor(for intensity: Double) -> UIColor {
|
private func backgroundColor(for intensity: Double) -> UIColor {
|
||||||
switch intensity {
|
switch intensity {
|
||||||
case _ where intensity < 0.004:
|
case _ where intensity < 0.004:
|
||||||
return UIColor(named: "Gray (card background)")!
|
return AppTheme.Colors.cardBackgroundGray
|
||||||
case _ where intensity < 0.30:
|
case _ where intensity < 0.30:
|
||||||
return UIColor(named: "Green (card background)")!
|
return AppTheme.Colors.cardBackgroundGreen
|
||||||
case _ where intensity < 0.70:
|
case _ where intensity < 0.70:
|
||||||
return UIColor(named: "Yellow (card background)")!
|
return AppTheme.Colors.cardBackgroundYellow
|
||||||
default:
|
default:
|
||||||
return UIColor(named: "Red (card background)")!
|
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?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,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 {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -10,38 +10,29 @@ import UIKit
|
|||||||
import StoreKit
|
import StoreKit
|
||||||
import Shogun
|
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
|
||||||
@@ -49,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() {
|
||||||
@@ -64,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() {
|
||||||
@@ -90,75 +80,62 @@ 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()
|
// remove extra padding on top of each section header
|
||||||
yearlyProducts.removeAll()
|
tableView.sectionHeaderTopPadding = 0.0
|
||||||
perpetualProducts.removeAll()
|
tableView.registerCell(for: SubscriptionsActiveTableViewCell.self)
|
||||||
|
tableView.registerCell(for: SubscriptionsDescriptionTableViewCell.self)
|
||||||
// creates list to show
|
tableView.registerCell(for: SubscriptionProductTableViewCell.self)
|
||||||
let isDiscountAvailable = checkDiscountPrice()
|
tableView.registerHeaderFooterView(for: SubscriptionsHeaderTableViewCell.self)
|
||||||
allProducts.forEach { (product) in
|
|
||||||
if isDiscountAvailable {
|
|
||||||
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kMonthly ||
|
|
||||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kMonthly {
|
|
||||||
monthlyProducts.append(product)
|
|
||||||
} else if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kYearlyDiscounted ||
|
|
||||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kYearlyDiscounted {
|
|
||||||
yearlyProducts.append(product)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kMonthly ||
|
|
||||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kMonthly {
|
|
||||||
monthlyProducts.append(product)
|
|
||||||
}
|
|
||||||
else if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kYearly ||
|
|
||||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kYearly {
|
|
||||||
yearlyProducts.append(product)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// perpetual scribuscriptions doesn't have discounted version
|
|
||||||
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kPerpetual ||
|
|
||||||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kPerpetual {
|
|
||||||
perpetualProducts.append(product)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tableView.reloadData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,18 +148,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)
|
||||||
}
|
}
|
||||||
@@ -190,7 +162,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) {
|
||||||
@@ -209,7 +181,7 @@ class SubscriptionsViewController: UITableViewController {
|
|||||||
present(alert, animated: true, completion: nil)
|
present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
VersioneProProducts.store.loadPurchase()
|
EQNInAppProducts.store.loadPurchase()
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,20 +200,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 {
|
||||||
@@ -249,96 +223,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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
//
|
||||||
|
// MapSeismicWaveAnimator.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 24/07/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MapKit
|
||||||
|
|
||||||
|
|
||||||
|
class MapSeismicWaveAnimator {
|
||||||
|
|
||||||
|
private weak var mapView: MKMapView?
|
||||||
|
private weak var waveTimeLabel: UILabel?
|
||||||
|
|
||||||
|
private let OverlayCircleId = "wave_animation"
|
||||||
|
|
||||||
|
/// Alert to display
|
||||||
|
private let realtimeAlert: EQNRealtimePushNotification
|
||||||
|
/// Timer to constantly update countdown label
|
||||||
|
private var countdownTimer: Timer?
|
||||||
|
/// Timer to simulate animation for the wave
|
||||||
|
private var waveAnimationTimer: Timer?
|
||||||
|
/// Refresh time for wave animation
|
||||||
|
private let waveAnimationRefreshRate = 0.1
|
||||||
|
/// Current radius of the wave animation on the map
|
||||||
|
private var waveAnimationCurrentRadius: CLLocationDistance = 0
|
||||||
|
private var waveAnimationVelocity: Double = 1_000
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(
|
||||||
|
realtimeAlert: EQNRealtimePushNotification,
|
||||||
|
mapView: MKMapView,
|
||||||
|
waveTimeLabel: UILabel
|
||||||
|
) {
|
||||||
|
self.realtimeAlert = realtimeAlert
|
||||||
|
self.mapView = mapView
|
||||||
|
self.waveTimeLabel = waveTimeLabel
|
||||||
|
self.setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setup() {
|
||||||
|
self.waveAnimationCurrentRadius = currentWavePosition()
|
||||||
|
self.waveAnimationVelocity = evaluateWaveAnimationVelocity()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
startCountdown()
|
||||||
|
startWaveAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
stopCountdown()
|
||||||
|
stopWaveAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Wave
|
||||||
|
|
||||||
|
private func startCountdown() {
|
||||||
|
// show countdown only if time is less than 300 seconds
|
||||||
|
if realtimeAlert.currentCountdown() < 300 {
|
||||||
|
// start a timer for the countdown label
|
||||||
|
waveTimeLabel?.isHidden = false
|
||||||
|
countdownTimer = Timer.scheduledTimer(
|
||||||
|
timeInterval: 1.0,
|
||||||
|
target: self,
|
||||||
|
selector: #selector(countdownTimerFired(_:)),
|
||||||
|
userInfo: nil,
|
||||||
|
repeats: true
|
||||||
|
)
|
||||||
|
countdownTimer?.fire()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startWaveAnimation() {
|
||||||
|
waveAnimationTimer = Timer.scheduledTimer(
|
||||||
|
timeInterval: waveAnimationRefreshRate,
|
||||||
|
target: self,
|
||||||
|
selector: #selector(mapWaveAnimationFired(_:)),
|
||||||
|
userInfo: nil,
|
||||||
|
repeats: true
|
||||||
|
)
|
||||||
|
waveAnimationTimer?.fire()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopCountdown() {
|
||||||
|
countdownTimer?.invalidate()
|
||||||
|
countdownTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopWaveAnimation() {
|
||||||
|
waveAnimationTimer?.invalidate()
|
||||||
|
waveAnimationTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Timer
|
||||||
|
|
||||||
|
@objc private func countdownTimerFired(_ sender: Timer) {
|
||||||
|
let countdown = realtimeAlert.currentCountdown()
|
||||||
|
waveTimeLabel?.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), countdown)
|
||||||
|
waveTimeLabel?.textColor = waveTimeTextColor(for: countdown)
|
||||||
|
|
||||||
|
if countdown <= 0 {
|
||||||
|
// stop the countdown
|
||||||
|
stopCountdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func mapWaveAnimationFired(_ sender: Timer) {
|
||||||
|
waveAnimationCurrentRadius += waveAnimationVelocity
|
||||||
|
addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
/// Evaluate current position for the wave
|
||||||
|
/// Used to define initial position for the wave circle
|
||||||
|
/// - Returns: Distance of the wave from the original earthquake point
|
||||||
|
private func currentWavePosition() -> Double {
|
||||||
|
// distanza tra utente e terremoto
|
||||||
|
let distance = realtimeAlert.distanceFromUser()
|
||||||
|
|
||||||
|
// calcoliamo la distanza rimanente da mostrare, perchè la schermata potrebbe anche essere aperta in ritardo
|
||||||
|
let remainingDistance = realtimeAlert.waveSpeed * Double(realtimeAlert.currentCountdown())
|
||||||
|
return distance - remainingDistance
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate wave velocity based on push notification data
|
||||||
|
/// - Returns: Wave velocity, used for animation
|
||||||
|
private func evaluateWaveAnimationVelocity() -> Double {
|
||||||
|
let velocity = realtimeAlert.waveSpeed
|
||||||
|
return velocity * waveAnimationRefreshRate
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the text color based on impact countdown
|
||||||
|
private func waveTimeTextColor(for countdown: Int) -> UIColor {
|
||||||
|
switch countdown {
|
||||||
|
case _ where countdown > 15:
|
||||||
|
return UIColor(red: 255.0/255.0, green: 140.0/255.0, blue: 0.0, alpha: 1.0)
|
||||||
|
case _ where countdown > 5:
|
||||||
|
return UIColor(red: 255.0/255.0, green: 100.0/255.0, blue: 0.0, alpha: 1.0)
|
||||||
|
default:
|
||||||
|
return UIColor(red: 255.0/255.0, green: 0.0/255.0, blue: 0.0, alpha: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Map management
|
||||||
|
|
||||||
|
func addMapCircle(
|
||||||
|
center: CLLocationCoordinate2D,
|
||||||
|
radius: CLLocationDistance
|
||||||
|
) {
|
||||||
|
guard let mapView else { return }
|
||||||
|
|
||||||
|
// remove any other existing overlays
|
||||||
|
let overlays = mapView.overlays.filter { $0.title == OverlayCircleId }
|
||||||
|
mapView.removeOverlays(overlays)
|
||||||
|
|
||||||
|
// add new overlay
|
||||||
|
let circle = MKCircle(center: center, radius: radius)
|
||||||
|
circle.title = OverlayCircleId
|
||||||
|
mapView.addOverlay(circle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOverlayRenderer(for overlay: MKOverlay) -> MKOverlayRenderer? {
|
||||||
|
switch overlay {
|
||||||
|
case let circle as MKCircle where overlay.title == OverlayCircleId:
|
||||||
|
let circleRenderer = MKCircleRenderer(overlay: circle)
|
||||||
|
circleRenderer.strokeColor = AppTheme.Colors.red
|
||||||
|
circleRenderer.fillColor = AppTheme.Colors.red.withAlphaComponent(0.2)
|
||||||
|
circleRenderer.lineWidth = 3.0
|
||||||
|
return circleRenderer
|
||||||
|
case let polyline as MKPolyline:
|
||||||
|
let polylineRenderer = MKPolylineRenderer(polyline: polyline)
|
||||||
|
polylineRenderer.strokeColor = .blue
|
||||||
|
polylineRenderer.lineWidth = 2.0
|
||||||
|
return polylineRenderer
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ class RealtimeAlertContainerView: UIView {
|
|||||||
lazy var alertView: RealtimeAlertView = {
|
lazy var alertView: RealtimeAlertView = {
|
||||||
let view = RealtimeAlertView()
|
let view = RealtimeAlertView()
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.eqn_applyRoundedCorners()
|
||||||
|
view.clipsToBounds = true
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -78,10 +80,11 @@ class RealtimeAlertView: UIView {
|
|||||||
let waveTimeLabel: UILabel = {
|
let waveTimeLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.font = .preferredFont(forTextStyle: .title2)
|
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||||
label.textColor = AppTheme.Colors.red
|
label.textColor = AppTheme.Colors.red
|
||||||
label.text = String.localizedStringWithFormat(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
|
||||||
}()
|
}()
|
||||||
@@ -89,7 +92,7 @@ class RealtimeAlertView: UIView {
|
|||||||
let intensityLabel: UILabel = {
|
let intensityLabel: 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.textAlignment = .center
|
label.textAlignment = .center
|
||||||
label.numberOfLines = 2
|
label.numberOfLines = 2
|
||||||
@@ -99,7 +102,6 @@ class RealtimeAlertView: UIView {
|
|||||||
lazy var mapView: MKMapView = {
|
lazy var mapView: MKMapView = {
|
||||||
let map = MKMapView()
|
let map = MKMapView()
|
||||||
map.translatesAutoresizingMaskIntoConstraints = false
|
map.translatesAutoresizingMaskIntoConstraints = false
|
||||||
map.delegate = self
|
|
||||||
map.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
map.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||||
map.showsUserLocation = true
|
map.showsUserLocation = true
|
||||||
return map
|
return map
|
||||||
@@ -157,28 +159,13 @@ class RealtimeAlertView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
func addMapCircle(
|
func addMapLine(
|
||||||
center: CLLocationCoordinate2D,
|
coordinates: [CLLocationCoordinate2D]
|
||||||
radius: CLLocationDistance,
|
|
||||||
overlayId: String
|
|
||||||
) {
|
) {
|
||||||
// remove any other existing overlays
|
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
|
||||||
let overlays = mapView.overlays.filter { $0.title == overlayId }
|
mapView.addOverlay(polyline)
|
||||||
mapView.removeOverlays(overlays)
|
|
||||||
|
|
||||||
// add new overlay
|
|
||||||
let circle = MKCircle(center: center, radius: radius)
|
|
||||||
circle.title = overlayId
|
|
||||||
mapView.addOverlay(circle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addMapLine(
|
|
||||||
coordinates: [CLLocationCoordinate2D]
|
|
||||||
) {
|
|
||||||
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
|
|
||||||
mapView.addOverlay(polyline)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addMapAnnotation(
|
func addMapAnnotation(
|
||||||
title: String = "",
|
title: String = "",
|
||||||
@@ -189,35 +176,3 @@ class RealtimeAlertView: UIView {
|
|||||||
mapView.addAnnotation(annotation)
|
mapView.addAnnotation(annotation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RealtimeAlertView: MKMapViewDelegate {
|
|
||||||
|
|
||||||
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
|
||||||
switch overlay {
|
|
||||||
case let circle as MKCircle:
|
|
||||||
let circleRenderer = MKCircleRenderer(overlay: circle)
|
|
||||||
circleRenderer.strokeColor = AppTheme.Colors.red
|
|
||||||
circleRenderer.fillColor = AppTheme.Colors.red.withAlphaComponent(0.2)
|
|
||||||
circleRenderer.lineWidth = 3.0
|
|
||||||
return circleRenderer
|
|
||||||
case let polyline as MKPolyline:
|
|
||||||
let polylineRenderer = MKPolylineRenderer(polyline: polyline)
|
|
||||||
polylineRenderer.strokeColor = .blue
|
|
||||||
polylineRenderer.lineWidth = 2.0
|
|
||||||
return polylineRenderer
|
|
||||||
default:
|
|
||||||
return MKOverlayRenderer(overlay: overlay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
|
||||||
guard let annotation = annotation as? EQNMapAnnotationPastquake else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
|
|
||||||
annotationView.image = annotation.image
|
|
||||||
annotationView.title = annotation.title
|
|
||||||
return annotationView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+27
-85
@@ -10,7 +10,7 @@ import UIKit
|
|||||||
import MapKit
|
import MapKit
|
||||||
|
|
||||||
|
|
||||||
class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
class RealtimeAlertViewController: UIViewController {
|
||||||
|
|
||||||
@objc var onClose: () -> Void = {}
|
@objc var onClose: () -> Void = {}
|
||||||
|
|
||||||
@@ -20,17 +20,17 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
private var notificationView: RealtimeAlertView {
|
private var notificationView: RealtimeAlertView {
|
||||||
containerView.alertView
|
containerView.alertView
|
||||||
}
|
}
|
||||||
|
/// Manage the wave animation on the map and the countdown label
|
||||||
|
private lazy var animator: MapSeismicWaveAnimator = {
|
||||||
|
let animator = MapSeismicWaveAnimator(
|
||||||
|
realtimeAlert: realtimeAlert,
|
||||||
|
mapView: notificationView.mapView,
|
||||||
|
waveTimeLabel: notificationView.waveTimeLabel
|
||||||
|
)
|
||||||
|
return animator
|
||||||
|
}()
|
||||||
/// Alert to display
|
/// Alert to display
|
||||||
private let realtimeAlert: EQNRealtimePushNotification
|
private let realtimeAlert: EQNRealtimePushNotification
|
||||||
/// Timer to constantly update countdown label
|
|
||||||
private var countdownTimer: Timer?
|
|
||||||
/// Refresh time for wave animation
|
|
||||||
private let waveAnimationRefreshRate = 0.1
|
|
||||||
/// Current radius of the wave animation on the map
|
|
||||||
private var waveAnimationCurrentRadius: CLLocationDistance = 0
|
|
||||||
private var waveAnimationVelocity: Double = 1_000
|
|
||||||
/// Timer to simulate animation for the wave
|
|
||||||
private var waveAnimationTimer: Timer?
|
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
@@ -38,9 +38,6 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
init(notification: EQNRealtimePushNotification) {
|
init(notification: EQNRealtimePushNotification) {
|
||||||
self.realtimeAlert = notification
|
self.realtimeAlert = notification
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
self.waveAnimationCurrentRadius = currentWavePosition()
|
|
||||||
self.waveAnimationVelocity = evaluateWaveAnimationVelocity()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@@ -64,8 +61,7 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
configureUI()
|
configureUI()
|
||||||
updateUI()
|
updateUI()
|
||||||
|
|
||||||
startCountdown()
|
animator.start()
|
||||||
startWaveAnimation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
@@ -77,6 +73,8 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func configureUI() {
|
private func configureUI() {
|
||||||
|
notificationView.mapView.delegate = self
|
||||||
|
|
||||||
notificationView.closeButton.addTarget(self, action: #selector(onTapClose(_:)), for: .touchUpInside)
|
notificationView.closeButton.addTarget(self, action: #selector(onTapClose(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
// configure color for animation
|
// configure color for animation
|
||||||
@@ -104,93 +102,37 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
|||||||
// aggiungiamo annotation con epicentro sisma
|
// aggiungiamo annotation con epicentro sisma
|
||||||
notificationView.addMapAnnotation(center: realtimeAlert.coordinate.coordinate, intensity: realtimeAlert.intensity)
|
notificationView.addMapAnnotation(center: realtimeAlert.coordinate.coordinate, intensity: realtimeAlert.intensity)
|
||||||
|
|
||||||
// simuliamo animazione dell'onda sismica
|
|
||||||
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
|
||||||
|
|
||||||
// aggiungiamo un segmento tra la posizione del sisma e quella dell'utente
|
// aggiungiamo un segmento tra la posizione del sisma e quella dell'utente
|
||||||
if let lastPosition = EQNUser.default().lastPosition {
|
if let lastPosition = EQNUser.default().lastPosition {
|
||||||
notificationView.addMapLine(coordinates: [realtimeAlert.coordinate.coordinate, lastPosition.coordinate])
|
notificationView.addMapLine(coordinates: [realtimeAlert.coordinate.coordinate, lastPosition.coordinate])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startCountdown() {
|
|
||||||
// show countdown only if time is less than 300 seconds
|
|
||||||
if realtimeAlert.currentCountdown() < 300 {
|
|
||||||
// start a timer for the countdown label
|
|
||||||
notificationView.waveTimeLabel.isHidden = false
|
|
||||||
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdownTimerFired(_:)), userInfo: nil, repeats: true)
|
|
||||||
countdownTimer?.fire()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func startWaveAnimation() {
|
|
||||||
waveAnimationTimer = Timer.scheduledTimer(timeInterval: waveAnimationRefreshRate, target: self, selector: #selector(mapWaveAnimationFired(_:)), userInfo: nil, repeats: true)
|
|
||||||
waveAnimationTimer?.fire()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Action
|
// MARK: - Action
|
||||||
|
|
||||||
@objc private func onTapClose(_ sender: UIButton) {
|
@objc private func onTapClose(_ sender: UIButton) {
|
||||||
// invalidiamo i timer, altri
|
// stoppiamo animazione e countdown
|
||||||
countdownTimer?.invalidate()
|
animator.stop()
|
||||||
countdownTimer = nil
|
|
||||||
waveAnimationTimer?.invalidate()
|
|
||||||
waveAnimationTimer = nil
|
|
||||||
|
|
||||||
onClose()
|
onClose()
|
||||||
dismiss(animated: true)
|
dismiss(animated: true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension RealtimeAlertViewController: MKMapViewDelegate {
|
||||||
|
|
||||||
// MARK: - Timer
|
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||||
|
animator.getOverlayRenderer(for: overlay) ?? MKOverlayRenderer(overlay: overlay)
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func countdownTimerFired(_ sender: Timer) {
|
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||||
let countdown = realtimeAlert.currentCountdown()
|
guard let annotation = annotation as? EQNMapAnnotationPastquake else {
|
||||||
notificationView.waveTimeLabel.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), countdown)
|
return nil
|
||||||
notificationView.waveTimeLabel.textColor = waveTimeTextColor(for: countdown)
|
|
||||||
|
|
||||||
if countdown <= 0 {
|
|
||||||
// stop the countdown
|
|
||||||
countdownTimer?.invalidate()
|
|
||||||
countdownTimer = nil
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func mapWaveAnimationFired(_ sender: Timer) {
|
|
||||||
waveAnimationCurrentRadius += waveAnimationVelocity
|
|
||||||
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Helpers
|
|
||||||
|
|
||||||
/// Evaluate current position for the wave
|
|
||||||
/// Used to define initial position for the wave circle
|
|
||||||
/// - Returns: Distance of the wave from the original earthquake point
|
|
||||||
private func currentWavePosition() -> Double {
|
|
||||||
// distanza tra utente e terremoto
|
|
||||||
let distance = realtimeAlert.distanceFromUser()
|
|
||||||
|
|
||||||
// calcoliamo la distanza rimanente da mostrare, perchè la schermata potrebbe anche essere aperta in ritardo
|
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
|
||||||
let remainingDistance = realtimeAlert.waveSpeed * Double(realtimeAlert.currentCountdown())
|
annotationView.image = annotation.image
|
||||||
return distance - remainingDistance
|
annotationView.title = annotation.title
|
||||||
}
|
return annotationView
|
||||||
|
|
||||||
/// Evaluate wave velocity based on push notification data
|
|
||||||
/// - Returns: Wave velocity, used for animation
|
|
||||||
private func evaluateWaveAnimationVelocity() -> Double {
|
|
||||||
let velocity = realtimeAlert.waveSpeed
|
|
||||||
return velocity * waveAnimationRefreshRate
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the text color based on impact countdown
|
|
||||||
private func waveTimeTextColor(for countdown: Int) -> UIColor {
|
|
||||||
switch countdown {
|
|
||||||
case _ where countdown > 15:
|
|
||||||
return UIColor(red: 255.0/255.0, green: 140.0/255.0, blue: 0.0, alpha: 1.0)
|
|
||||||
case _ where countdown > 5:
|
|
||||||
return UIColor(red: 255.0/255.0, green: 100.0/255.0, blue: 0.0, alpha: 1.0)
|
|
||||||
default:
|
|
||||||
return UIColor(red: 255.0/255.0, green: 0.0/255.0, blue: 0.0, alpha: 1.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,46 +9,97 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Shogun
|
import Shogun
|
||||||
|
|
||||||
class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
|
@objc
|
||||||
|
class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
|
||||||
|
|
||||||
@IBOutlet private weak var headerLabel: UILabel!
|
@objc var onTapTwitter: (() -> Void)?
|
||||||
@IBOutlet private weak var reportsLabel: UILabel!
|
@objc var onTapMap: (() -> Void)?
|
||||||
@IBOutlet private weak var reportsDescriptionLabel: UILabel!
|
@objc var onTapTelegram: (() -> Void)?
|
||||||
|
|
||||||
|
override var isHeaderVisible: Bool { false }
|
||||||
|
|
||||||
|
// MARK: - UI
|
||||||
|
|
||||||
|
private lazy var reportsLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textColor = AppTheme.Colors.red
|
||||||
|
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.numberOfLines = 0
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var reportsDescriptionLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
label.textColor = AppTheme.shared.cardTextColor
|
||||||
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.textAlignment = .center
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
@IBOutlet private weak var twitterButton: UIButton! {
|
private lazy var twitterButton: UIButton = {
|
||||||
didSet {
|
let button = EQNRoundedButton.make(target: self, action: #selector(twitterButtonTapped(_:)))
|
||||||
twitterButton.imageView?.contentMode = .scaleAspectFit
|
button.imageView?.contentMode = .scaleAspectFit
|
||||||
}
|
button.setImage(.init(named: "xcorp_icon"), for: .normal)
|
||||||
}
|
return button
|
||||||
@IBOutlet private weak var telegramButton: UIButton! {
|
}()
|
||||||
didSet {
|
|
||||||
telegramButton.imageView?.contentMode = .scaleAspectFit
|
private lazy var mapButton: UIButton = {
|
||||||
}
|
let button = EQNRoundedButton.make(title: NSLocalizedString("official_button_map", comment: ""), target: self, action: #selector(mapButtonTapped(_:)))
|
||||||
}
|
return button
|
||||||
@IBOutlet private weak var mapButton: UIButton!
|
}()
|
||||||
|
|
||||||
|
private lazy var telegramButton: UIButton = {
|
||||||
|
let button = EQNRoundedButton.make(target: self, action: #selector(telegramButtonTapped(_:)))
|
||||||
|
button.imageView?.contentMode = .scaleAspectFit
|
||||||
|
button.setImage(.init(named: "telegram_icon"), for: .normal)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
// MARK: - Internal
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func setupUI() {
|
||||||
super.awakeFromNib()
|
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("tab_manual", comment: "").capitalized
|
|
||||||
reportsDescriptionLabel.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 }
|
||||||
}
|
|
||||||
|
|
||||||
let reports = smartphoneNetwork.manual
|
let reports = smartphoneNetwork.manual
|
||||||
self.reportsLabel.text = "\(reports)"
|
self.reportsLabel.text = "\(reports)"
|
||||||
@@ -61,4 +112,18 @@ class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
|
|||||||
self.reportsLabel.textColor = UIColor(hex6: 0xff0000)
|
self.reportsLabel.textColor = UIColor(hex6: 0xff0000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
|
||||||
|
@objc private func twitterButtonTapped(_ sender: UIButton) {
|
||||||
|
onTapTwitter?()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func mapButtonTapped(_ sender: UIButton) {
|
||||||
|
onTapMap?()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func telegramButtonTapped(_ sender: UIButton) {
|
||||||
|
onTapTelegram?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-62
@@ -28,35 +28,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
private var filteredReports = [EQNSegnalazione]()
|
private var filteredReports = [EQNSegnalazione]()
|
||||||
|
|
||||||
// MARK: - UI
|
// MARK: - UI
|
||||||
|
|
||||||
// app icon and name displayed on the screenshot
|
|
||||||
private lazy var watermarkView: UIView = {
|
|
||||||
let view = UIView()
|
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
view.isHidden = true
|
|
||||||
|
|
||||||
let logo = UIImageView(image: .init(named: "eq_icon_transparent"))
|
|
||||||
logo.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
logo.contentMode = .scaleAspectFit
|
|
||||||
view.addSubview(logo)
|
|
||||||
logo.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
|
||||||
logo.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
|
||||||
logo.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
|
||||||
logo.widthAnchor.constraint(equalTo: logo.heightAnchor).isActive = true
|
|
||||||
|
|
||||||
let title = UILabel()
|
|
||||||
title.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
title.text = NSLocalizedString("app_name", comment: "") + " App"
|
|
||||||
title.textColor = AppTheme.Colors.red
|
|
||||||
title.font = .preferredFont(forTextStyle: .title3, weight: .semibold)
|
|
||||||
view.addSubview(title)
|
|
||||||
title.leadingAnchor.constraint(equalTo: logo.trailingAnchor, constant: 10.0).isActive = true
|
|
||||||
title.centerYAnchor.constraint(equalTo: logo.centerYAnchor).isActive = true
|
|
||||||
title.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
|
||||||
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
private lazy var magnitudeLegendView: UIView = {
|
private lazy var magnitudeLegendView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
@@ -95,16 +67,11 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
|
|
||||||
override func extraUI() {
|
override func extraUI() {
|
||||||
view.addSubview(magnitudeLegendView)
|
view.addSubview(magnitudeLegendView)
|
||||||
view.addSubview(watermarkView)
|
|
||||||
|
|
||||||
magnitudeLegendView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
magnitudeLegendView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||||
magnitudeLegendView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
magnitudeLegendView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||||
magnitudeLegendView.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
|
magnitudeLegendView.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
|
||||||
magnitudeLegendView.topAnchor.constraint(equalTo: mapView.topAnchor).isActive = true
|
magnitudeLegendView.topAnchor.constraint(equalTo: mapView.topAnchor).isActive = true
|
||||||
|
|
||||||
watermarkView.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 10.0).isActive = true
|
|
||||||
watermarkView.leadingAnchor.constraint(equalTo: mapView.leadingAnchor, constant: 10.0).isActive = true
|
|
||||||
watermarkView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configureUI() {
|
override func configureUI() {
|
||||||
@@ -153,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
|
||||||
@@ -191,33 +158,20 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
|
|
||||||
@objc private func onTapMapDetailStyleButton(_ sender: Any) {
|
@objc private func onTapMapDetailStyleButton(_ sender: Any) {
|
||||||
appPreferences.userReportExpandedView.toggle()
|
appPreferences.userReportExpandedView.toggle()
|
||||||
loadDataSource()
|
reloadMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func onTapScreenshotButton(_ sender: Any) {
|
@objc private func onTapScreenshotButton(_ sender: Any) {
|
||||||
let snapshot = createSnapshot()
|
let screenshot = createSnapshot {
|
||||||
|
// nascondiamo la legenda
|
||||||
let controller = UIActivityViewController(activityItems: [snapshot], applicationActivities: [])
|
magnitudeLegendView.isHidden = true
|
||||||
present(controller, animated: true)
|
} restore: {
|
||||||
}
|
// ri-visualizziamo la legenda
|
||||||
|
magnitudeLegendView.isHidden = false
|
||||||
public func createSnapshot() -> UIImage {
|
|
||||||
// mostriamo il watermark e nascondiamo la legenda
|
|
||||||
watermarkView.isHidden = false
|
|
||||||
magnitudeLegendView.isHidden = true
|
|
||||||
|
|
||||||
// riduciamo la porzione da salvare alla sola mappa (eliminiamo i filtri)
|
|
||||||
let size = CGSize(width: view.bounds.width, height: mapView.bounds.maxY)
|
|
||||||
let renderer = UIGraphicsImageRenderer(size: size)
|
|
||||||
let image = renderer.image { ctx in
|
|
||||||
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// torniamo allo stato originale
|
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
||||||
watermarkView.isHidden = true
|
present(controller, animated: true)
|
||||||
magnitudeLegendView.isHidden = false
|
|
||||||
|
|
||||||
return image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
@@ -233,10 +187,10 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
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 {
|
||||||
@@ -327,10 +281,6 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
|
|||||||
mapView.addOverlays(overlays)
|
mapView.addOverlays(overlays)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getDeltaMinute(_ date: Date) -> TimeInterval {
|
|
||||||
Date().timeIntervalSince(date) / 60.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Map
|
// MARK: - Map
|
||||||
|
|
||||||
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
||||||
|
|||||||
@@ -7,49 +7,100 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
class SegnalazioniSendReportCell: EQNBaseTableViewCell {
|
class SegnalazioniSendReportCell: EQNBaseContainerTableViewCell {
|
||||||
|
|
||||||
|
private struct Report: Equatable {
|
||||||
|
let magnitude: Int
|
||||||
|
let text: String
|
||||||
|
let color: UIColor
|
||||||
|
|
||||||
|
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||||
|
lhs.magnitude == rhs.magnitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc var onTapReport: (_ magnitude: Int) -> Void = { _ in }
|
@objc var onTapReport: (_ magnitude: Int) -> Void = { _ in }
|
||||||
|
|
||||||
|
override var headerText: String { NSLocalizedString("main_feel", comment: "") }
|
||||||
|
|
||||||
// MARK: - UI
|
// MARK: - UI
|
||||||
|
|
||||||
@IBOutlet private weak var headerLabel: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli2: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli3: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli4: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli5: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli6: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli7: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli8: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli9: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli10: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli11: UILabel!
|
|
||||||
@IBOutlet private weak var reportMercalli12: UILabel!
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
private let reports: [Report] = [
|
||||||
|
.init(magnitude: 20, text: NSLocalizedString("mercalli_II", comment: ""), color: .init(named: "Mercalli 20")!),
|
||||||
|
.init(magnitude: 30, text: NSLocalizedString("mercalli_III", comment: ""), color: .init(named: "Mercalli 30")!),
|
||||||
|
.init(magnitude: 40, text: NSLocalizedString("mercalli_IV", comment: ""), color: .init(named: "Mercalli 40")!),
|
||||||
|
.init(magnitude: 50, text: NSLocalizedString("mercalli_V", comment: ""), color: .init(named: "Mercalli 50")!),
|
||||||
|
.init(magnitude: 60, text: NSLocalizedString("mercalli_VI", comment: ""), color: .init(named: "Mercalli 60")!),
|
||||||
|
.init(magnitude: 70, text: NSLocalizedString("mercalli_VII", comment: ""), color: .init(named: "Mercalli 70")!),
|
||||||
|
.init(magnitude: 80, text: NSLocalizedString("mercalli_VIII", comment: ""), color: .init(named: "Mercalli 80")!),
|
||||||
|
.init(magnitude: 90, text: NSLocalizedString("mercalli_IX", comment: ""), color: .init(named: "Mercalli 90")!),
|
||||||
|
.init(magnitude: 100, text: NSLocalizedString("mercalli_X", comment: ""), color: .init(named: "Mercalli 100")!),
|
||||||
|
.init(magnitude: 110, text: NSLocalizedString("mercalli_XI", comment: ""), color: .init(named: "Mercalli 110")!),
|
||||||
|
.init(magnitude: 120, text: NSLocalizedString("mercalli_XII", comment: ""), color: .init(named: "Mercalli 120")!)
|
||||||
|
]
|
||||||
|
|
||||||
override func awakeFromNib() {
|
// MARK: - Internal
|
||||||
super.awakeFromNib()
|
|
||||||
|
override func setupUI() {
|
||||||
|
super.setupUI()
|
||||||
|
|
||||||
localizeUI()
|
var previousView = topView
|
||||||
|
reports.enumerated().forEach { index, report in
|
||||||
|
let view = createContentView(magnitude: report.magnitude, text: report.text, color: report.color)
|
||||||
|
containerView.addSubview(view)
|
||||||
|
|
||||||
|
view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
|
||||||
|
view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
|
||||||
|
|
||||||
|
let padding: CGFloat = report == reports.first ? .cardPadding : 0
|
||||||
|
view.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: padding).isActive = true
|
||||||
|
|
||||||
|
if report == reports.last {
|
||||||
|
view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
previousView = view
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func localizeUI() {
|
private func createContentView(
|
||||||
headerLabel.text = NSLocalizedString("main_feel", comment: "")
|
magnitude: Int,
|
||||||
reportMercalli2.text = NSLocalizedString("mercalli_II", comment: "")
|
text: String,
|
||||||
reportMercalli3.text = NSLocalizedString("mercalli_III", comment: "")
|
color: UIColor
|
||||||
reportMercalli4.text = NSLocalizedString("mercalli_IV", comment: "")
|
) -> UIView {
|
||||||
reportMercalli5.text = NSLocalizedString("mercalli_V", comment: "")
|
let view = UIView(frame: .zero)
|
||||||
reportMercalli6.text = NSLocalizedString("mercalli_VI", comment: "")
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
reportMercalli7.text = NSLocalizedString("mercalli_VII", comment: "")
|
view.backgroundColor = color
|
||||||
reportMercalli8.text = NSLocalizedString("mercalli_VIII", comment: "")
|
|
||||||
reportMercalli9.text = NSLocalizedString("mercalli_IX", comment: "")
|
let label = UILabel(frame: .zero)
|
||||||
reportMercalli10.text = NSLocalizedString("mercalli_X", comment: "")
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
reportMercalli11.text = NSLocalizedString("mercalli_XI", comment: "")
|
label.font = .preferredFont(forTextStyle: .body)
|
||||||
reportMercalli12.text = NSLocalizedString("mercalli_XII", comment: "")
|
label.textColor = AppTheme.shared.cardTextColor
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.text = text
|
||||||
|
|
||||||
|
let button = UIButton(type: .system)
|
||||||
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
button.backgroundColor = .clear
|
||||||
|
button.tag = magnitude
|
||||||
|
button.addTarget(self, action: #selector(onTapMagnitudeButton(_:)), for: .touchUpInside)
|
||||||
|
|
||||||
|
view.addSubview(label)
|
||||||
|
view.addSubview(button)
|
||||||
|
|
||||||
|
// use a custom vertical spacing to make single lines bigger
|
||||||
|
let verticalSpacing: CGFloat = 15.0
|
||||||
|
label.topAnchor.constraint(equalTo: view.topAnchor, constant: verticalSpacing).isActive = true
|
||||||
|
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: .cardPadding).isActive = true
|
||||||
|
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||||
|
label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -verticalSpacing).isActive = true
|
||||||
|
button.constraint(to: view)
|
||||||
|
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
@@ -58,4 +109,9 @@ class SegnalazioniSendReportCell: EQNBaseTableViewCell {
|
|||||||
let magnitude = sender.tag
|
let magnitude = sender.tag
|
||||||
onTapReport(magnitude)
|
onTapReport(magnitude)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func onTapMagnitudeButton(_ sender: UIButton) {
|
||||||
|
let magnitude = sender.tag
|
||||||
|
onTapReport(magnitude)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,12 @@
|
|||||||
- (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
|
||||||
@@ -74,8 +78,17 @@
|
|||||||
{
|
{
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,21 +101,21 @@
|
|||||||
|
|
||||||
#pragma mark - Actions
|
#pragma mark - Actions
|
||||||
|
|
||||||
- (IBAction)openMapTapped:(id)sender
|
- (void)openMap
|
||||||
{
|
{
|
||||||
SegnalazioniMapViewController *controller = [[SegnalazioniMapViewController alloc] init];
|
SegnalazioniMapViewController *controller = [[SegnalazioniMapViewController alloc] init];
|
||||||
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
|
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
|
||||||
[self presentViewController:navController animated:YES completion:nil];
|
[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];
|
||||||
|
|||||||
+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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+116
-258
@@ -11,32 +11,8 @@ import MapKit
|
|||||||
import CoreLocation
|
import CoreLocation
|
||||||
import Shogun
|
import Shogun
|
||||||
|
|
||||||
protocol SeismicNetworkTableViewCellDelegate: AnyObject {
|
|
||||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell)
|
|
||||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell)
|
|
||||||
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell)
|
|
||||||
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell)
|
|
||||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell)
|
|
||||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell)
|
|
||||||
}
|
|
||||||
|
|
||||||
class SeismicNetworkTableViewCell: UITableViewCell {
|
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 {
|
||||||
@@ -45,45 +21,15 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
/// Cell with map visible
|
/// Cell with map visible
|
||||||
case mapExpanded
|
case mapExpanded
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delegate
|
|
||||||
weak var delegate: SeismicNetworkTableViewCellDelegate?
|
|
||||||
|
|
||||||
// MARK: - Internal
|
|
||||||
|
|
||||||
private static let DefaultVerticalSpacing: CGFloat = 6.0
|
|
||||||
private static let DefaultBodyFont = UIFont.preferredFont(forTextStyle: .body)
|
|
||||||
private static let DefaultBodyFontLight = UIFont.preferredFont(forTextStyle: .body, weight: .light)
|
|
||||||
|
|
||||||
/// Seismic to show
|
/// 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
|
||||||
@@ -92,11 +38,20 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
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
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -111,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
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -147,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
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -156,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
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -183,18 +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()
|
||||||
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
|
||||||
@@ -216,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) {
|
||||||
@@ -287,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)
|
||||||
@@ -308,34 +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 buttonCalendar = createRoundedButton(title: "📆", action: #selector(calendarTapped(_:)))
|
let buttonCalendar = EQNRoundedButton.make(title: "📆", target: self, 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
|
||||||
|
|
||||||
@@ -345,7 +303,7 @@ 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
|
||||||
|
|
||||||
@@ -353,10 +311,11 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (displayType == .mapExpanded) {
|
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
|
||||||
@@ -364,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() {
|
||||||
@@ -377,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)"
|
||||||
@@ -404,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)
|
||||||
@@ -431,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]
|
||||||
@@ -450,6 +408,9 @@ 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()
|
||||||
@@ -457,90 +418,38 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
|||||||
|
|
||||||
// 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 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
|
||||||
@@ -553,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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+87
-115
@@ -16,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?
|
||||||
@@ -37,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("filter_distance", 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(set) 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
|
||||||
|
|
||||||
@@ -86,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
|
||||||
@@ -108,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
|
||||||
@@ -155,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
|
||||||
@@ -191,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
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
//
|
||||||
|
// SeismicNetworkFilterRecapView.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 17/07/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
|
||||||
|
struct SeismicNetworkFilterRecapView: View {
|
||||||
|
class Model: ObservableObject {
|
||||||
|
@Published var filter = EQNSeismic.shared.filterOption
|
||||||
|
@Published var sort = EQNSeismic.shared.sort
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObservedObject private var model: Model
|
||||||
|
private let onSort: (_ sort: EQNSeismic.Sort) -> Void
|
||||||
|
private let onMainFilter: () -> Void
|
||||||
|
private let onMap: () -> Void
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(
|
||||||
|
model: Model,
|
||||||
|
onSort: @escaping (_ sort: EQNSeismic.Sort) -> Void,
|
||||||
|
onMainFilter: @escaping () -> Void,
|
||||||
|
onMap: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
self.model = model
|
||||||
|
self.onSort = onSort
|
||||||
|
self.onMainFilter = onMainFilter
|
||||||
|
self.onMap = onMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
RoundedButton(
|
||||||
|
systemName: sortIcon,
|
||||||
|
tintColor: tintColor,
|
||||||
|
action: {
|
||||||
|
model.sort.advance()
|
||||||
|
onSort(model.sort)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button {
|
||||||
|
onMainFilter()
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "magnifyingglass")
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(height: 12)
|
||||||
|
|
||||||
|
Text(filterTitle)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.overlay {
|
||||||
|
Capsule().stroke(AppTheme.Colors.gray.color, lineWidth: 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.caption)
|
||||||
|
.tint(tintColor)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
RoundedButton(
|
||||||
|
systemName: "globe",
|
||||||
|
tintColor: tintColor,
|
||||||
|
action: onMap
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color.clear)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tintColor: Color {
|
||||||
|
Color.blue
|
||||||
|
//AppTheme.Colors.lightBlue.color
|
||||||
|
}
|
||||||
|
|
||||||
|
private var filterTitle: String {
|
||||||
|
switch model.filter {
|
||||||
|
case .inRadius: "filter_area".localized
|
||||||
|
case .positionRelevant: "filter_relevant".localized
|
||||||
|
case .worldWide: "filter_all".localized
|
||||||
|
case .userFelt: "filter_felt".localized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var sortIcon: String {
|
||||||
|
switch model.sort {
|
||||||
|
case .time: "clock"
|
||||||
|
case .position:
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
"compass.drawing"
|
||||||
|
} else {
|
||||||
|
"ruler"
|
||||||
|
}
|
||||||
|
case .magnitude: "thermometer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct RoundedButton: View {
|
||||||
|
let systemName: String
|
||||||
|
let tintColor: Color
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button {
|
||||||
|
action()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: systemName)
|
||||||
|
.resizable()
|
||||||
|
.tint(tintColor)
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(maxHeight: .infinity)
|
||||||
|
.frame(width: 40.0)
|
||||||
|
.padding(8)
|
||||||
|
.overlay {
|
||||||
|
Capsule().stroke(AppTheme.Colors.gray.color, lineWidth: 1)
|
||||||
|
}
|
||||||
|
.animation(nil, value: systemName) // previene animazioni implicite nel bottone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SeismicNetworkFilterRecapView(
|
||||||
|
model: .init(),
|
||||||
|
onSort: { _ in },
|
||||||
|
onMainFilter: {},
|
||||||
|
onMap: {}
|
||||||
|
)
|
||||||
|
.frame(height: 34.0)
|
||||||
|
}
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
//
|
||||||
|
// SeismicNetworkScrollIndicatorView.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 31/01/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import CoreGraphics
|
||||||
|
|
||||||
|
|
||||||
|
class SeismicNetworkScrollIndicatorView: UIView {
|
||||||
|
|
||||||
|
private static let HighlightColor: UIColor = .red
|
||||||
|
|
||||||
|
var seismics: [SeismicNetworkViewModel] = [] {
|
||||||
|
didSet {
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var highlighted: SeismicNetworkViewModel? {
|
||||||
|
didSet {
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var numberOfRectangles: Int {
|
||||||
|
seismics.count
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func draw(_ rect: CGRect) {
|
||||||
|
guard numberOfRectangles > 0 else { return }
|
||||||
|
|
||||||
|
let context = UIGraphicsGetCurrentContext()
|
||||||
|
let rectStandardWidth = rect.width
|
||||||
|
let rectStandardHeight = rect.height / CGFloat(numberOfRectangles)
|
||||||
|
let rectHighlightedMinHeight: CGFloat = 4
|
||||||
|
|
||||||
|
let smallRectangles = rectStandardHeight < 10
|
||||||
|
let highlightIndex = seismics.firstIndex(where: { $0 == highlighted }) ?? 100_000
|
||||||
|
|
||||||
|
|
||||||
|
seismics.enumerated().forEach { index, seismic in
|
||||||
|
// Disegniamo un rettangolo per ogni sisma, quello evidenziato deve avere un contorno rosso.
|
||||||
|
// Ci sono situazioni in cui ci sono molti sismi da mostrare, quindi in quel caso facciamo alcune modifiche:
|
||||||
|
// - usiamo un'altezza minima per il sisma evidenziato
|
||||||
|
// - per il sisma evidenziato, anche il contenuto è rosso (e non solo il bordo)
|
||||||
|
// - negli altri sismi, non mostriamo il bordo
|
||||||
|
|
||||||
|
if highlightIndex == index {
|
||||||
|
// Stiamo disegnando il sisma evidenziato.
|
||||||
|
// Valutiamo se utilizzare l'altezza minima.
|
||||||
|
let rectHeight = smallRectangles ? rectHighlightedMinHeight : rectStandardHeight
|
||||||
|
let yPosition = CGFloat(index) * rectStandardHeight
|
||||||
|
let rectangle = CGRect(x: 0, y: yPosition, width: rectStandardWidth, height: rectHeight)
|
||||||
|
|
||||||
|
let fillColor = smallRectangles ? Self.HighlightColor : seismic.colors.textColor.withAlphaComponent(0.3)
|
||||||
|
context?.setFillColor(fillColor.cgColor)
|
||||||
|
context?.fill(rectangle)
|
||||||
|
|
||||||
|
if !smallRectangles {
|
||||||
|
// disegniamo il bordo solo se i rettangoli non sono piccoli
|
||||||
|
let borderWidth: CGFloat = 2.0
|
||||||
|
context?.setStrokeColor(Self.HighlightColor.cgColor)
|
||||||
|
context?.setLineWidth(borderWidth) // Spessore del bordo
|
||||||
|
context?.stroke(rectangle.insetBy(dx: borderWidth / 2, dy: borderWidth / 2)) // Evita che il bordo venga tagliato
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Stiamo disegnando i sismi non evidenziati, utilizziamo sempre l'altezza predefinita
|
||||||
|
// Dobbiamo eventualmente calcolare un offset aggiuntivo,
|
||||||
|
// perchè il sisma evidenziato ha un'altezza maggiore (se i rettangoli sono piccoli)
|
||||||
|
let rectHeight = rectStandardHeight
|
||||||
|
|
||||||
|
var offset: CGFloat = 0
|
||||||
|
if index > highlightIndex && smallRectangles {
|
||||||
|
// calcoliamo l'offset prima del rettangolo evidenziato
|
||||||
|
let preOffset = CGFloat(highlightIndex - 1) * rectStandardHeight
|
||||||
|
// offset diverso dovuto all'altezza diversa del rettangolo evidenziato
|
||||||
|
let highlightOffset = rectHighlightedMinHeight
|
||||||
|
// calcoliamo l'offset tra il rettangolo evidenziato e quello corrente
|
||||||
|
let postOffset = CGFloat(index - highlightIndex) * rectStandardHeight
|
||||||
|
offset = preOffset + highlightOffset + postOffset
|
||||||
|
} else {
|
||||||
|
// siamo prima del rettangolo evidenziato, non abbiamo calcoli da fare
|
||||||
|
offset = CGFloat(index) * rectHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
let rectangle = CGRect(x: 0, y: offset, width: rectStandardWidth, height: rectHeight)
|
||||||
|
|
||||||
|
let fillColor = seismic.colors.textColor.withAlphaComponent(0.3)
|
||||||
|
context?.setFillColor(fillColor.cgColor)
|
||||||
|
context?.fill(rectangle)
|
||||||
|
|
||||||
|
if !smallRectangles {
|
||||||
|
// altrimenti un bordo grigio
|
||||||
|
let borderWidth: CGFloat = 0.5
|
||||||
|
context?.setStrokeColor(AppTheme.Colors.gray.cgColor)
|
||||||
|
context?.setLineWidth(borderWidth) // Spessore del bordo
|
||||||
|
context?.stroke(rectangle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+141
-31
@@ -8,24 +8,75 @@
|
|||||||
|
|
||||||
import Foundation
|
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
|
||||||
@@ -56,7 +107,7 @@ struct SeismicNetworkViewModel {
|
|||||||
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
|
||||||
|
}
|
||||||
|
|||||||
+270
@@ -0,0 +1,270 @@
|
|||||||
|
//
|
||||||
|
// 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.advance()
|
||||||
|
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 = intensityString(from: shakemap.intensity)
|
||||||
|
annotationView.magnitudeTextColor = intensityTextColor ?? .black
|
||||||
|
annotationView.magnitudeBackgroundColor = intensityColor
|
||||||
|
annotationView.canShowCallout = true
|
||||||
|
return annotationView
|
||||||
|
case .circle:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func intensityString(from intensity: Float) -> String {
|
||||||
|
let intensityRounded = (intensity * 10).rounded() / 10
|
||||||
|
let intensityFloor = floor(intensityRounded)
|
||||||
|
|
||||||
|
let romanNumerals: [Int: String] = [
|
||||||
|
1: "I", 2: "II", 3: "III", 4: "IV",
|
||||||
|
5: "V", 6: "VI", 7: "VII", 8: "VIII",
|
||||||
|
9: "IX", 10: "X", 11: "XI", 12: "XII"
|
||||||
|
]
|
||||||
|
|
||||||
|
var result = romanNumerals[Int(intensityFloor)] ?? ""
|
||||||
|
|
||||||
|
if intensityRounded != intensityFloor {
|
||||||
|
let reminder = Int(((intensityRounded - intensityFloor) * 10).rounded())
|
||||||
|
result += ".\(reminder)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class ShakemapPolyline: MKPolyline {
|
||||||
|
var intensity: Float = 0
|
||||||
|
var intensityColor: UIColor = .white
|
||||||
|
}
|
||||||
+130
-52
@@ -15,11 +15,18 @@ protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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?
|
||||||
@@ -42,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
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -59,14 +63,17 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
|||||||
|
|
||||||
// MARK: - Internal
|
// MARK: - Internal
|
||||||
|
|
||||||
private let seismic: EQNSisma
|
private let seismic: EQNSisma?
|
||||||
private var allSeismics: [EQNSisma]
|
private var allSeismics: [EQNSisma]
|
||||||
/// Contains circles drawed on the map
|
/// Contains circles drawed on the map
|
||||||
private var mapCircles = [MKCircle]()
|
private var mapCircles = [MKCircle]()
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
init(seismic: EQNSisma, allSeismics: [EQNSisma]) {
|
init(
|
||||||
|
seismic: EQNSisma?,
|
||||||
|
allSeismics: [EQNSisma]
|
||||||
|
) {
|
||||||
self.seismic = seismic
|
self.seismic = seismic
|
||||||
self.allSeismics = allSeismics
|
self.allSeismics = allSeismics
|
||||||
super.init()
|
super.init()
|
||||||
@@ -76,6 +83,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]) {
|
||||||
@@ -84,7 +109,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() {
|
||||||
@@ -92,19 +119,23 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func elaborateMapCenter() {
|
override func elaborateMapCenter() {
|
||||||
setMapCenter(for: seismic.coordinate)
|
if let seismic {
|
||||||
|
setMapCenter(for: seismic.coordinate)
|
||||||
|
} else if let location = CLLocationManager().location {
|
||||||
|
setMapCenter(for: location)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
override func didTapAnnotation(_ annotation: MKAnnotation) {
|
||||||
@@ -127,31 +158,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.advance()
|
||||||
|
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
|
||||||
@@ -162,16 +223,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.makeViewController()
|
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? {
|
||||||
@@ -179,15 +237,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 {
|
||||||
@@ -196,12 +247,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)
|
||||||
|
|||||||
+615
-101
@@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
import EventKitUI
|
import EventKitUI
|
||||||
import DZNEmptyDataSet
|
import DZNEmptyDataSet
|
||||||
import Shogun
|
import Shogun
|
||||||
@@ -15,23 +16,23 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
|
|
||||||
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
|
||||||
@@ -40,25 +41,113 @@ 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?
|
||||||
|
/// Push notification opened by the user
|
||||||
|
private var openedPushNotification: EQNOfficialPushNotification? {
|
||||||
|
didSet {
|
||||||
|
scrollToOpenedSeismic = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var scrollToOpenedSeismic = false
|
||||||
|
/// Current displayed controller with map
|
||||||
|
private weak var currentMapController: SeismicNetworksMapDetailViewController?
|
||||||
|
/// Keep track of the current cell at the center
|
||||||
|
private var currentCenteredIndexPath: IndexPath?
|
||||||
|
|
||||||
|
// MARK: - UI
|
||||||
|
|
||||||
|
@IBOutlet private weak var displayModeButton: UIBarButtonItem!
|
||||||
|
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
|
||||||
|
}()
|
||||||
|
|
||||||
|
// model is needed in order to send data to SwiftUI view
|
||||||
|
private let model = SeismicNetworkFilterRecapView.Model()
|
||||||
|
|
||||||
|
private lazy var filterRecapView: UIView = {
|
||||||
|
let hosting = UIHostingController(
|
||||||
|
rootView: SeismicNetworkFilterRecapView(
|
||||||
|
model: model,
|
||||||
|
onSort: { [weak self] sort in
|
||||||
|
self?.changeSort(to: sort)
|
||||||
|
},
|
||||||
|
onMainFilter: { [weak self] in
|
||||||
|
self?.openFilter()
|
||||||
|
},
|
||||||
|
onMap: { [weak self] in
|
||||||
|
self?.showMapDetail(for: nil)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
addChild(hosting)
|
||||||
|
let filterRecapView = hosting.view!
|
||||||
|
hosting.view.isOpaque = false
|
||||||
|
hosting.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
hosting.didMove(toParent: self)
|
||||||
|
return hosting.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)
|
||||||
}
|
}
|
||||||
@@ -67,16 +156,110 @@ 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.backgroundColor = tableView.backgroundColor
|
||||||
|
view.addSubview(scrollIndicatorView)
|
||||||
|
view.addSubview(tableView)
|
||||||
|
view.addSubview(filterRecapView)
|
||||||
|
|
||||||
|
scrollIndicatorView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true
|
||||||
|
scrollIndicatorView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||||
|
scrollIndicatorView.bottomAnchor.constraint(equalTo: tableView.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: filterRecapView.topAnchor,
|
||||||
|
constant: -8
|
||||||
|
).isActive = true
|
||||||
|
|
||||||
|
filterRecapView.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
|
||||||
|
filterRecapView.leadingAnchor
|
||||||
|
.constraint(
|
||||||
|
equalTo: view.leadingAnchor,
|
||||||
|
constant: 10.0
|
||||||
|
).isActive = true
|
||||||
|
filterRecapView.trailingAnchor
|
||||||
|
.constraint(
|
||||||
|
equalTo: view.trailingAnchor,
|
||||||
|
constant: -10.0
|
||||||
|
).isActive = true
|
||||||
|
filterRecapView.topAnchor
|
||||||
|
.constraint(equalTo: tableView.bottomAnchor).isActive = true
|
||||||
|
filterRecapView.bottomAnchor
|
||||||
|
.constraint(
|
||||||
|
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
|
||||||
|
constant: -8
|
||||||
|
).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
|
||||||
|
}
|
||||||
|
|
||||||
|
private func checkForLocation() {
|
||||||
|
// check if a valid location is available,
|
||||||
|
// otherwise change the filter settings
|
||||||
|
if !isLocationAvailable() {
|
||||||
|
updateFilter(type: .worldWide)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
@@ -85,14 +268,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
|
||||||
@@ -102,15 +277,22 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showMapDetail(for seismic: EQNSisma) {
|
private func showMapDetail(for seismic: EQNSisma?) {
|
||||||
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) {
|
||||||
@@ -126,21 +308,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) {
|
||||||
@@ -152,7 +339,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() {
|
||||||
@@ -164,6 +352,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] {
|
||||||
@@ -176,28 +367,343 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
|||||||
return seismics
|
return seismics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func changeSort(to sort: EQNSeismic.Sort) {
|
||||||
|
updateFilter(sort: sort)
|
||||||
|
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,
|
||||||
|
sort: EQNSeismic.Sort? = nil,
|
||||||
|
radius: Double? = nil,
|
||||||
|
magnitude: Double? = nil
|
||||||
|
) {
|
||||||
|
if let type {
|
||||||
|
let previous = EQNSeismic.shared.filterOption
|
||||||
|
if previous == .userFelt && previous != type {
|
||||||
|
loadData(forced: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
EQNSeismic.shared.filterOption = type
|
||||||
|
model.filter = type
|
||||||
|
}
|
||||||
|
if let sort {
|
||||||
|
EQNSeismic.shared.sort = sort
|
||||||
|
model.sort = sort
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
if rows.count > centerIndexPath.row {
|
||||||
|
let row = rows[centerIndexPath.row]
|
||||||
|
if case .seismic = row, seismicViewModels.count > centerIndexPath.row {
|
||||||
|
scrollIndicatorView.highlighted = seismicViewModels[centerIndexPath.row]
|
||||||
|
} else {
|
||||||
|
scrollIndicatorView.highlighted = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scrollIndicatorView.highlighted = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
@IBAction func refreshDataTapped(_ sender: Any) {
|
@IBAction func refreshDataTapped(_ sender: Any) {
|
||||||
loadData(forced: true)
|
loadData(forced: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func openFilterTapped(_ sender: Any) {
|
private func openFilter() {
|
||||||
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.advance()
|
||||||
|
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,18 +717,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
|
||||||
|
if openMapIndexPath == indexPath {
|
||||||
|
type = .mapExpanded
|
||||||
|
}
|
||||||
|
|
||||||
|
let isPushSelected = isSeismicToHighlight(seismic: seismic)
|
||||||
|
cell.configure(with: seismic, type: type, informations: informations, isPushSelected: isPushSelected)
|
||||||
|
cell.delegate = self
|
||||||
|
return cell
|
||||||
|
case .minimal:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkMinimalTableViewCell.self, for: indexPath)
|
||||||
|
let isPushSelected = isSeismicToHighlight(seismic: seismic)
|
||||||
|
cell.configure(with: seismic, isPushSelected: isPushSelected)
|
||||||
|
cell.delegate = self
|
||||||
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.configure(with: seismic, type: type, informations: informations)
|
|
||||||
cell.delegate = self
|
|
||||||
return cell
|
|
||||||
case .advertise(let nativeAd):
|
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
|
||||||
}
|
}
|
||||||
@@ -237,6 +753,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) {
|
||||||
@@ -292,25 +814,25 @@ 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.contentView.createSnapshot()
|
let snapshot = cell.contentView.createSnapshot()
|
||||||
@@ -326,38 +848,44 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
|||||||
present(controller, animated: true)
|
present(controller, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) {
|
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||||
guard let index = tableView?.indexPath(for: cell) else { return }
|
guard let index = tableView.indexPath(for: cell) else { return }
|
||||||
|
|
||||||
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
||||||
|
|
||||||
openMapIndexPath = index
|
openMapIndexPath = index
|
||||||
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, index].compactMap { $0 }
|
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
||||||
|
|
||||||
openMapIndexPath = nil
|
openMapIndexPath = nil
|
||||||
tableView?.reloadRows(at: indexToReloads, with: .automatic)
|
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,29 +898,12 @@ extension SeismicNetworksViewController: EKEventEditViewDelegate {
|
|||||||
|
|
||||||
extension SeismicNetworksViewController: SeismicFiltersViewControllerDelegate {
|
extension SeismicNetworksViewController: SeismicFiltersViewControllerDelegate {
|
||||||
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
|
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
|
||||||
|
model.filter = controller.currentFilterType
|
||||||
loadData(forced: controller.needsDataUpdate)
|
loadData(forced: controller.needsDataUpdate)
|
||||||
refreshUI()
|
refreshUI()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SeismicNetworksViewController: SeismicSettingsViewControllerDelegate {
|
|
||||||
func seismicSettingsControllerDidComplete(_ controller: SeismicSettingsViewController) {
|
|
||||||
// riscarichiamo i dati, le reti selezionate potrebbero essere cambiate
|
|
||||||
loadData(forced: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func seismicSettingsControllerWillOpenProviders(_ controller: SeismicSettingsViewController) {
|
|
||||||
performSegue(withIdentifier: Self.SegueIdentifierSeismicNetworks, sender: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SeismicNetworksViewController: SeismicSettingsNetworksViewControllerDelegate {
|
|
||||||
func seismicSettingsNetworksControllerDidComplete(_ controller: SeismicSettingsNetworksViewController) {
|
|
||||||
// riscarichiamo i dati, le reti selezionate potrebbero essere cambiate
|
|
||||||
loadData(forced: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelegate {
|
extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelegate {
|
||||||
func seismicCardSettingsDidComplete(_ controller: SeismicCardSettingsViewController) {
|
func seismicCardSettingsDidComplete(_ controller: SeismicCardSettingsViewController) {
|
||||||
refreshUI()
|
refreshUI()
|
||||||
@@ -400,9 +911,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-118
@@ -1,118 +0,0 @@
|
|||||||
//
|
|
||||||
// SeismicSettingsNetworksViewController.swift
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Created by Busi Andrea on 14/09/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
|
|
||||||
protocol SeismicSettingsNetworksViewControllerDelegate: AnyObject {
|
|
||||||
func seismicSettingsNetworksControllerDidComplete(_ controller: SeismicSettingsNetworksViewController)
|
|
||||||
}
|
|
||||||
|
|
||||||
class SeismicSettingsNetworksViewController: UITableViewController {
|
|
||||||
|
|
||||||
weak var delegate: SeismicSettingsNetworksViewControllerDelegate?
|
|
||||||
|
|
||||||
// MARK: - Private
|
|
||||||
|
|
||||||
private var networks = [EQNSeismicNetwork]()
|
|
||||||
private var savedNetworks = [String]()
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
tableView.register(SettingDetailTableViewCell.self, forCellReuseIdentifier: SettingDetailTableViewCell.Identifier)
|
|
||||||
tableView.register(SettingSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: SettingSectionHeaderView.Identifier)
|
|
||||||
|
|
||||||
loadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private
|
|
||||||
|
|
||||||
private func loadData() {
|
|
||||||
networks = EQNData.seismicNetworks().sorted(by: { $0.acronym < $1.acronym })
|
|
||||||
|
|
||||||
// load saved selected networks or fill with all available networks
|
|
||||||
let savedNetworks = EQNUserData.shared.seismicNetworksSelected()
|
|
||||||
if !savedNetworks.isEmpty {
|
|
||||||
self.savedNetworks = savedNetworks
|
|
||||||
} else {
|
|
||||||
self.savedNetworks = EQNData.seismicNetworkAcronyms()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Table view data source
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
||||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SettingSectionHeaderView.Identifier) as! SettingSectionHeaderView
|
|
||||||
headerView.titleLabel.text = NSLocalizedString("options_agencies", comment: "");
|
|
||||||
return headerView
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
|
||||||
CGFloat(SettingSectionHeaderView.Height)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
||||||
networks.count
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
||||||
let network = networks[indexPath.row]
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: SettingDetailTableViewCell.Identifier, for: indexPath) as! SettingDetailTableViewCell
|
|
||||||
cell.textLabel?.text = "\(network.acronym) (\(network.country))"
|
|
||||||
|
|
||||||
if savedNetworks.contains(network.acronym) {
|
|
||||||
cell.accessoryType = .checkmark
|
|
||||||
} else {
|
|
||||||
cell.accessoryType = .none
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
|
||||||
|
|
||||||
let network = networks[indexPath.row]
|
|
||||||
if let index = savedNetworks.firstIndex(of: network.acronym) {
|
|
||||||
savedNetworks.remove(at: index)
|
|
||||||
} else {
|
|
||||||
savedNetworks.append(network.acronym)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload all rows with the given acronym
|
|
||||||
let indexes = networks
|
|
||||||
.enumerated()
|
|
||||||
.filter { $0.element.acronym == network.acronym }
|
|
||||||
.map { IndexPath(row: $0.offset, section: 0) }
|
|
||||||
tableView.reloadRows(at: indexes, with: .automatic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
|
|
||||||
@IBAction func cancelTapped(_ sender: Any) {
|
|
||||||
dismiss(animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func saveTapped(_ sender: Any) {
|
|
||||||
// save selected networks
|
|
||||||
EQNUserData.shared.saveSelectedSeismicNetworks(savedNetworks)
|
|
||||||
|
|
||||||
// se solo un'ente è selezionato, salviamolo anche come nazione
|
|
||||||
if savedNetworks.count == 1 {
|
|
||||||
UserDefaults.standard.set(savedNetworks.first!, forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE)
|
|
||||||
} else {
|
|
||||||
UserDefaults.standard.removeObject(forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE)
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate?.seismicSettingsNetworksControllerDidComplete(self)
|
|
||||||
dismiss(animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-130
@@ -1,130 +0,0 @@
|
|||||||
//
|
|
||||||
// SeismicSettingsViewController.swift
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Created by Busi Andrea on 13/09/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
|
|
||||||
protocol SeismicSettingsViewControllerDelegate: AnyObject {
|
|
||||||
func seismicSettingsControllerDidComplete(_ controller: SeismicSettingsViewController)
|
|
||||||
func seismicSettingsControllerWillOpenProviders(_ controller: SeismicSettingsViewController)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SeismicSettingsViewController: UIViewController {
|
|
||||||
|
|
||||||
weak var delegate: SeismicSettingsViewControllerDelegate?
|
|
||||||
|
|
||||||
// MARK: - Private
|
|
||||||
|
|
||||||
@IBOutlet private weak var containerView: UIView!
|
|
||||||
@IBOutlet private weak var titleLabel: UILabel!
|
|
||||||
@IBOutlet private weak var countryTextField: UITextField!
|
|
||||||
@IBOutlet private weak var confirmButton: UIButton!
|
|
||||||
@IBOutlet private weak var otherwiseLabel: UILabel!
|
|
||||||
@IBOutlet private weak var manageNetworksButton: UIButton!
|
|
||||||
@IBOutlet private weak var cancelButton: UIButton!
|
|
||||||
|
|
||||||
|
|
||||||
private let networks = EQNData.seismicNetworks().sorted(by: { $0.country < $1.country })
|
|
||||||
private let picker = EQNGenericPickerViewController()
|
|
||||||
private var selectedNetwork: EQNSeismicNetwork?
|
|
||||||
|
|
||||||
// MARK: - View Lifecycle
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
setupUI()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private
|
|
||||||
|
|
||||||
private func setupUI() {
|
|
||||||
containerView.layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
|
||||||
containerView.layer.masksToBounds = true
|
|
||||||
|
|
||||||
// localize
|
|
||||||
titleLabel.text = NSLocalizedString("official_select_country", comment: "")
|
|
||||||
countryTextField.placeholder = NSLocalizedString("official_select_country_placeholder", comment: "")
|
|
||||||
confirmButton.setLocalizedTitle(key: "official_select_confirm", uppercased: false)
|
|
||||||
otherwiseLabel.text = NSLocalizedString("official_select_or", comment: "")
|
|
||||||
manageNetworksButton.setLocalizedTitle(key: "official_select_networks", uppercased: false)
|
|
||||||
cancelButton.setLocalizedTitle(key: "status_cancel", uppercased: false)
|
|
||||||
|
|
||||||
// load saved country (if exists)
|
|
||||||
let savedCountry = UserDefaults.standard.object(forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE) as? String
|
|
||||||
selectedNetwork = EQNData.seismic(for: savedCountry)
|
|
||||||
|
|
||||||
countryTextField.text = selectedNetwork?.country
|
|
||||||
countryTextField.inputView = picker.view
|
|
||||||
|
|
||||||
let selectedIndex: Int? = selectedNetwork != nil ? networks.firstIndex(of: selectedNetwork!) : nil
|
|
||||||
picker.configure(with: networks, selectedIndex: selectedIndex) { [unowned self] (network) in
|
|
||||||
guard let network = network as? EQNSeismicNetwork else { return }
|
|
||||||
|
|
||||||
self.view.endEditing(true)
|
|
||||||
self.selectedNetwork = network
|
|
||||||
self.countryTextField.text = self.selectedNetwork?.country
|
|
||||||
}
|
|
||||||
picker.onCancel = { [unowned self] in
|
|
||||||
self.view.endEditing(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func performSave(for network: EQNSeismicNetwork) {
|
|
||||||
// salviamo la sigla dell'ente selezionato
|
|
||||||
UserDefaults.standard.set(network.acronym, forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE)
|
|
||||||
|
|
||||||
// gli enti selezionati conterranno solo l'ente della nazione selezionata
|
|
||||||
let selectedNetworks = [network.acronym]
|
|
||||||
EQNUserData.shared.saveSelectedSeismicNetworks(selectedNetworks)
|
|
||||||
|
|
||||||
// aggiorniamo le impostazioni di notifica
|
|
||||||
EQNNotificheReteSismiche.shared().listaEnti = selectedNetworks
|
|
||||||
EQNNotificheReteSismiche.shared().saveUserInfo()
|
|
||||||
SettingsBaseViewController.saveSettings()
|
|
||||||
|
|
||||||
// informiamo il delegato
|
|
||||||
delegate?.seismicSettingsControllerDidComplete(self)
|
|
||||||
dismiss(animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
|
|
||||||
@IBAction func confirmCountryTapped(_ sender: UIButton) {
|
|
||||||
guard let network = selectedNetwork else {
|
|
||||||
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
|
|
||||||
message: NSLocalizedString("official_no_country_selected", comment: ""),
|
|
||||||
preferredStyle: .alert)
|
|
||||||
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .cancel, handler: { [unowned self] (action) in
|
|
||||||
self.countryTextField.becomeFirstResponder()
|
|
||||||
}))
|
|
||||||
present(alert, animated: true, completion: nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ask confirm to change settings for notifications
|
|
||||||
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
|
|
||||||
message: NSLocalizedString("official_select_message", comment: ""),
|
|
||||||
preferredStyle: .alert)
|
|
||||||
alert.addAction(UIAlertAction(title: NSLocalizedString("status_cancel", comment: ""), style: .cancel))
|
|
||||||
alert.addAction(UIAlertAction(title: NSLocalizedString("official_select_confirm", comment: ""), style: .default, handler: { [unowned self] (action) in
|
|
||||||
self.performSave(for: network)
|
|
||||||
}))
|
|
||||||
present(alert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func selectNetworksTapped(_ sender: UIButton) {
|
|
||||||
delegate?.seismicSettingsControllerWillOpenProviders(self)
|
|
||||||
dismiss(animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func cancelTapped(_ sender: UIButton) {
|
|
||||||
dismiss(animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+9
-9
@@ -11,27 +11,27 @@ import Foundation
|
|||||||
|
|
||||||
class SettingDateTableViewCell: UITableViewCell {
|
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
|
|
||||||
-165
@@ -1,165 +0,0 @@
|
|||||||
//
|
|
||||||
// AletaSismiTableViewController.m
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi 25/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "SettingsRealTimeAlertsViewController.h"
|
|
||||||
#import "EQNAllertaSismica.h"
|
|
||||||
@import UserNotifications;
|
|
||||||
|
|
||||||
@interface SettingsRealTimeAlertsViewController () <UITextFieldDelegate>
|
|
||||||
|
|
||||||
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
|
|
||||||
|
|
||||||
@property (nonatomic, strong) NSDateFormatter *dateFormatter;
|
|
||||||
|
|
||||||
@property (nonatomic, assign) BOOL notificationEnabled;
|
|
||||||
@property (nonatomic, assign) BOOL criticalAlertsEnabled;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation SettingsRealTimeAlertsViewController
|
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, RowIdentifier) {
|
|
||||||
RowIdentifierAbilitaNotifiche = 0,
|
|
||||||
RowIdentifierAbilitaCriticalAlerts
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma mark - Accessories
|
|
||||||
|
|
||||||
- (NSDateFormatter *)dateFormatter
|
|
||||||
{
|
|
||||||
if (!_dateFormatter) {
|
|
||||||
_dateFormatter = [[NSDateFormatter alloc] init];
|
|
||||||
[_dateFormatter setDateFormat:@"HH:mm"];
|
|
||||||
}
|
|
||||||
return _dateFormatter;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - View Lifecycle
|
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
|
|
||||||
[self setupUI];
|
|
||||||
|
|
||||||
self.settings = @[
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_alarm", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"critical_alerts_setting", @"")]
|
|
||||||
];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (void)setupUI
|
|
||||||
{
|
|
||||||
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
|
|
||||||
|
|
||||||
self.tableView.estimatedRowHeight = 200.0;
|
|
||||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
|
||||||
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingMultivaluesTableViewCell class] forCellReuseIdentifier:SettingMultivaluesTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingSegmentedTableViewCell class] forCellReuseIdentifier:SettingSegmentedTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingDateTableViewCell class] forCellReuseIdentifier:SettingDateTableViewCell.Identifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)loadDataSource
|
|
||||||
{
|
|
||||||
self.notificationEnabled = [EQNAllertaSismica sharedInstance].isAbilitato;
|
|
||||||
self.criticalAlertsEnabled = [EQNAllertaSismica sharedInstance].isCriticalAlertsEnabled;
|
|
||||||
|
|
||||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Table view data source
|
|
||||||
|
|
||||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return self.settings.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
headerView.titleLabel.text = NSLocalizedString(@"options_alarms", @"");
|
|
||||||
return headerView;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return SettingSectionHeaderView.Height;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
SettingItem *setting = self.settings[indexPath.row];
|
|
||||||
|
|
||||||
if (setting.type == SettingTypeEnable) {
|
|
||||||
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
cell.descriptionLabel.text = setting.subtitle;
|
|
||||||
|
|
||||||
if (indexPath.row == RowIdentifierAbilitaNotifiche) {
|
|
||||||
cell.toggleSwitch.on = self.notificationEnabled;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
self.notificationEnabled = enabled;
|
|
||||||
[EQNAllertaSismica sharedInstance].isAbilitato = self.notificationEnabled;
|
|
||||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
} else if (indexPath.row == RowIdentifierAbilitaCriticalAlerts) {
|
|
||||||
cell.toggleSwitch.on = self.criticalAlertsEnabled;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
if (enabled) {
|
|
||||||
[self askForCriticalAlertsPermission];
|
|
||||||
}
|
|
||||||
|
|
||||||
self.criticalAlertsEnabled = enabled;
|
|
||||||
[EQNAllertaSismica sharedInstance].isCriticalAlertsEnabled = self.criticalAlertsEnabled;
|
|
||||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
} else if (setting.type == SettingTypeSegmented) {
|
|
||||||
SettingSegmentedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSegmentedTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
return cell;
|
|
||||||
} else if (setting.type == SettingTypeSlider) {
|
|
||||||
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (void)updateSismicToNotify:(EQNGenericValue *)seismic
|
|
||||||
{
|
|
||||||
[EQNAllertaSismica sharedInstance].sismiDaNotificare = seismic.value;
|
|
||||||
[[EQNAllertaSismica sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)askForCriticalAlertsPermission
|
|
||||||
{
|
|
||||||
UNAuthorizationOptions authOptions = UNAuthorizationOptionCriticalAlert;
|
|
||||||
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError *error) {
|
|
||||||
// nope
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
//
|
||||||
|
// SettingsRealTimeAlertsViewController.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 10/06/24.
|
||||||
|
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
|
||||||
|
|
||||||
|
private enum RowIdentifier: Int {
|
||||||
|
case abilitaNotifiche
|
||||||
|
case disabilitaSuonoAllerta
|
||||||
|
case abilitaCriticalAlerts
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isNotificationEnabled = false
|
||||||
|
private var isMildQuakeSoundDisabled = false
|
||||||
|
private var isCriticalAlertsEnabled = false
|
||||||
|
|
||||||
|
private let settings: [SettingItem] = [
|
||||||
|
.init(type: .enable, title: NSLocalizedString("options_notification_enable_alarm", comment: "")),
|
||||||
|
.init(type: .enable, title: NSLocalizedString("options_notification_disable_sound", comment: "")),
|
||||||
|
.init(type: .enable, title: NSLocalizedString("critical_alerts_setting", comment: ""))
|
||||||
|
]
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
|
|
||||||
|
tableView.estimatedRowHeight = 200.0
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||||
|
tableView.registerCell(for: SettingEnableTableViewCell.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadDataSource() {
|
||||||
|
let saved = EQNSettingRealTimeAlert.shared
|
||||||
|
|
||||||
|
isNotificationEnabled = saved.isAbilitato
|
||||||
|
isMildQuakeSoundDisabled = saved.isMildQuakeSoundDisabled
|
||||||
|
isCriticalAlertsEnabled = saved.isCriticalAlertsEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Table view delegate and data source
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
|
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||||
|
view.titleLabel.text = NSLocalizedString("options_alarms", comment: "")
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
|
SettingSectionHeaderView.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return settings.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||||
|
return UITableViewCell()
|
||||||
|
}
|
||||||
|
|
||||||
|
let setting = settings[indexPath.row]
|
||||||
|
switch setting.type {
|
||||||
|
case .enable:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
cell.descriptionLabel.text = setting.subtitle
|
||||||
|
|
||||||
|
switch identifier {
|
||||||
|
case .abilitaNotifiche:
|
||||||
|
cell.toggleSwitch.isOn = isNotificationEnabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeNotificationEnabled(enabled)
|
||||||
|
}
|
||||||
|
case .disabilitaSuonoAllerta:
|
||||||
|
cell.toggleSwitch.isOn = isMildQuakeSoundDisabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeDisableSoundEnabled(enabled)
|
||||||
|
}
|
||||||
|
case .abilitaCriticalAlerts:
|
||||||
|
cell.toggleSwitch.isOn = isCriticalAlertsEnabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeCriticalAlertsEnabled(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||||
|
isNotificationEnabled = enabled
|
||||||
|
EQNSettingRealTimeAlert.shared.isAbilitato = isNotificationEnabled
|
||||||
|
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeDisableSoundEnabled(_ enabled: Bool) {
|
||||||
|
isMildQuakeSoundDisabled = enabled
|
||||||
|
EQNSettingRealTimeAlert.shared.isMildQuakeSoundDisabled = isMildQuakeSoundDisabled
|
||||||
|
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeCriticalAlertsEnabled(_ enabled: Bool) {
|
||||||
|
if enabled {
|
||||||
|
askForCriticalAlertsPermission()
|
||||||
|
}
|
||||||
|
|
||||||
|
isCriticalAlertsEnabled = enabled
|
||||||
|
EQNSettingRealTimeAlert.shared.isCriticalAlertsEnabled = isCriticalAlertsEnabled
|
||||||
|
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func askForCriticalAlertsPermission() {
|
||||||
|
UNUserNotificationCenter.current().requestAuthorization(options: [ .criticalAlert ]) { granted, error in
|
||||||
|
// nope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsSeismicNetworkAlertsViewController.h
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi 25/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
#import "SettingsBaseViewController.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface SettingsSeismicNetworkAlertsViewController : SettingsBaseViewController
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
-258
@@ -1,258 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsSeismicNetworkAlertsViewController.m
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi 25/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "SettingsSeismicNetworkAlertsViewController.h"
|
|
||||||
#import "EQNNotificheReteSismiche.h"
|
|
||||||
|
|
||||||
@interface SettingsSeismicNetworkAlertsViewController ()
|
|
||||||
|
|
||||||
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
|
|
||||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceRaggioSisma;
|
|
||||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceMagnitudoDeboli;
|
|
||||||
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceMagnitudoForti;
|
|
||||||
|
|
||||||
@property (nonatomic, assign) BOOL notificationEnabled;
|
|
||||||
@property (nonatomic, assign) BOOL notificationNearEarthquakeEnabled;
|
|
||||||
@property (nonatomic, assign) BOOL notificationStrongEarthquakeEnabled;
|
|
||||||
|
|
||||||
@property (nonatomic, strong) EQNGenericValue *currentUserPositionRadius;
|
|
||||||
@property (nonatomic, strong) EQNGenericValue *currentSeismicEnergy;
|
|
||||||
@property (nonatomic, strong) EQNGenericValue *currentStrongEarthquakeDistance;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation SettingsSeismicNetworkAlertsViewController
|
|
||||||
|
|
||||||
static NSString * const SegueIdentifierListaEnti = @"ShowListaEnti";
|
|
||||||
|
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, RowIdentifier) {
|
|
||||||
RowIdentifierAbilitaNotifiche = 0,
|
|
||||||
RowIdentifierRetiSismiche,
|
|
||||||
RowIdentifierRaggioPosizione,
|
|
||||||
RowIdentifierEnergiaSisma,
|
|
||||||
RowIdentifierTerremotiVicini,
|
|
||||||
RowIdentifierTerremotiForti,
|
|
||||||
RowIdentifierTerremotiFortiDistanza
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma mark - View Lifecycle
|
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
|
|
||||||
[self setupUI];
|
|
||||||
|
|
||||||
self.settings = @[
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_official", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeMultiValues title:NSLocalizedString(@"options_agencies", @"") segue:SegueIdentifierListaEnti],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_energy", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_near", @"") subtitle:NSLocalizedString(@"options_near_alert", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_strong", @"") subtitle:NSLocalizedString(@"options_strong_alert", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_strong_magnitude", @"")]
|
|
||||||
];
|
|
||||||
|
|
||||||
self.dataSourceMagnitudoDeboli = [EQNData magitudoDeboli];
|
|
||||||
self.dataSourceRaggioSisma = [EQNData raggioSismi];
|
|
||||||
self.dataSourceMagnitudoForti = [EQNData magitudoForti];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated
|
|
||||||
{
|
|
||||||
[super viewWillAppear:animated];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (void)setupUI
|
|
||||||
{
|
|
||||||
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
|
|
||||||
|
|
||||||
self.tableView.estimatedRowHeight = 200.0;
|
|
||||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
|
||||||
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingMultivaluesTableViewCell class] forCellReuseIdentifier:SettingMultivaluesTableViewCell.Identifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)loadDataSource
|
|
||||||
{
|
|
||||||
self.notificationEnabled = [EQNNotificheReteSismiche sharedInstance].isAbilitato;
|
|
||||||
self.notificationNearEarthquakeEnabled = [EQNNotificheReteSismiche sharedInstance].isAbilitaVicini;
|
|
||||||
self.notificationStrongEarthquakeEnabled = [EQNNotificheReteSismiche sharedInstance].isTerremortiForti;
|
|
||||||
|
|
||||||
// raggio dalla tua posizione
|
|
||||||
EQNGenericValue *raggioSisma = [EQNData raggioSismaFor:[EQNNotificheReteSismiche sharedInstance].distanzaPosizione];
|
|
||||||
self.currentUserPositionRadius = raggioSisma;
|
|
||||||
|
|
||||||
// energia sisma
|
|
||||||
EQNGenericValue *energiaSisma = [EQNData magitudoDeboleFor:[EQNNotificheReteSismiche sharedInstance].energiaSisma];
|
|
||||||
self.currentSeismicEnergy = energiaSisma;
|
|
||||||
|
|
||||||
// terremoti forti
|
|
||||||
EQNGenericValue *terremotiForti = [EQNData magitudoForteFor:[EQNNotificheReteSismiche sharedInstance].energiaTerremotiForti];
|
|
||||||
self.currentStrongEarthquakeDistance = terremotiForti;
|
|
||||||
|
|
||||||
// enti
|
|
||||||
if (![EQNNotificheReteSismiche sharedInstance].listaEnti) {
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].listaEnti = [EQNData.seismicNetworkAcronyms copy];
|
|
||||||
}
|
|
||||||
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Table view data source
|
|
||||||
|
|
||||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
headerView.titleLabel.text = NSLocalizedString(@"options_notification_official", @"titolo impostazioni notifiche");
|
|
||||||
return headerView;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return SettingSectionHeaderView.Height;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return self.settings.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
SettingItem *setting = self.settings[indexPath.row];
|
|
||||||
|
|
||||||
if (setting.type == SettingTypeEnable) {
|
|
||||||
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
cell.descriptionLabel.text = setting.subtitle;
|
|
||||||
|
|
||||||
if (indexPath.row == RowIdentifierAbilitaNotifiche) {
|
|
||||||
cell.toggleSwitch.on = self.notificationEnabled;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
self.notificationEnabled = enabled;
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].isAbilitato = self.notificationEnabled;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
} else if (indexPath.row == RowIdentifierTerremotiVicini) {
|
|
||||||
cell.toggleSwitch.on = self.notificationNearEarthquakeEnabled;
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
self.notificationNearEarthquakeEnabled = enabled;
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].isAbilitaVicini = self.notificationNearEarthquakeEnabled;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
} else if (indexPath.row == RowIdentifierTerremotiForti) {
|
|
||||||
cell.toggleSwitch.on = self.notificationStrongEarthquakeEnabled;
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
self.notificationStrongEarthquakeEnabled = enabled;
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].isTerremortiForti = self.notificationStrongEarthquakeEnabled;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
} else if (setting.type == SettingTypeMultiValues) {
|
|
||||||
SettingMultivaluesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingMultivaluesTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
cell.userInteractionEnabled = self.notificationEnabled;
|
|
||||||
cell.titleLabel.text = setting.title;
|
|
||||||
|
|
||||||
if (indexPath.row == RowIdentifierRetiSismiche) {
|
|
||||||
cell.valuesLabel.text = [self stringOfSelectedNetworks];
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
} else if (setting.type == SettingTypeSlider) {
|
|
||||||
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
|
|
||||||
if (indexPath.row == RowIdentifierRaggioPosizione) {
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
[cell configureSliderWith:self.dataSourceRaggioSisma current:self.currentUserPositionRadius];
|
|
||||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
|
||||||
[self updateUserPositionRadius:item];
|
|
||||||
};
|
|
||||||
} else if (indexPath.row == RowIdentifierEnergiaSisma) {
|
|
||||||
cell.isDisabled = !self.notificationEnabled;
|
|
||||||
[cell configureSliderWith:self.dataSourceMagnitudoDeboli current:self.currentSeismicEnergy];
|
|
||||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
|
||||||
[self updateSeismicEnergy:item];
|
|
||||||
};
|
|
||||||
} else if (indexPath.row == RowIdentifierTerremotiFortiDistanza) {
|
|
||||||
cell.isDisabled = !self.notificationEnabled || !self.notificationStrongEarthquakeEnabled;
|
|
||||||
[cell configureSliderWith:self.dataSourceMagnitudoForti current:self.currentStrongEarthquakeDistance];
|
|
||||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
|
||||||
[self updateStrongEarthquakeEnergy:item];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
SettingItem *setting = self.settings[indexPath.row];
|
|
||||||
if (setting.segue != nil) {
|
|
||||||
[self performSegueWithIdentifier:setting.segue sender:nil];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (void)updateUserPositionRadius:(EQNGenericValue *)radius
|
|
||||||
{
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].distanzaPosizione = radius.value;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateSeismicEnergy:(EQNGenericValue *)energy
|
|
||||||
{
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].energiaSisma = energy.value;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateStrongEarthquakeEnergy:(EQNGenericValue *)energy
|
|
||||||
{
|
|
||||||
[EQNNotificheReteSismiche sharedInstance].energiaTerremotiForti = energy.value;
|
|
||||||
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self loadDataSource];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)stringOfSelectedNetworks
|
|
||||||
{
|
|
||||||
NSArray *networks = [EQNNotificheReteSismiche sharedInstance].listaEnti;
|
|
||||||
networks = [networks sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
|
||||||
return [networks componentsJoinedByString:@", "];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
//
|
||||||
|
// SettingsSeismicNetworkNotificationsViewController.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 06/06/24.
|
||||||
|
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
class SettingsSeismicNetworkNotificationsViewController: SettingsBaseTableViewController {
|
||||||
|
|
||||||
|
private enum RowIdentifier: Int {
|
||||||
|
case abilitaNotifiche
|
||||||
|
case magnitudoMinima
|
||||||
|
case distanzaMassima
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isNotificationEnabled = false
|
||||||
|
private var currentMinimumMagnitude = EQNData.DefaultSettingSeismicNetworkNotificationMagitude
|
||||||
|
private var currentMaximumDistance = EQNData.DefaultSettingSeismicNetworkNotificationRadius
|
||||||
|
private let dataSourceMinimumMagnitude = EQNData.settingSeismicNetworkNotificationMagnitudes
|
||||||
|
private let dataSourceMaximumDistance = EQNData.settingSeismicNetworkNotificationRadius
|
||||||
|
|
||||||
|
private let settings: [SettingItem] = [
|
||||||
|
.init(type: .enable, title: NSLocalizedString("options_notification_enable_official", comment: "")),
|
||||||
|
.init(type: .slider, title: NSLocalizedString("options_official_minmag", comment: "")),
|
||||||
|
.init(type: .slider, title: NSLocalizedString("options_official_maxdist", comment: ""))
|
||||||
|
]
|
||||||
|
|
||||||
|
// MARK: - View Liefcycle
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
|
|
||||||
|
tableView.estimatedRowHeight = 200.0
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||||
|
tableView.registerCell(for: SettingEnableTableViewCell.self)
|
||||||
|
tableView.registerCell(for: SettingSliderTableViewCell.self)
|
||||||
|
tableView.registerCell(for: SettingMultivaluesTableViewCell.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadDataSource() {
|
||||||
|
let saved = EQNSettingSeismicNetworkNotification.shared
|
||||||
|
|
||||||
|
isNotificationEnabled = saved.isAbilitato
|
||||||
|
|
||||||
|
// magnitudo minima
|
||||||
|
let magnitudoMinima = EQNData.getSettingSeismicNetworkNotificationMagnitudes(for: saved.magnitudoMinima)
|
||||||
|
currentMinimumMagnitude = magnitudoMinima
|
||||||
|
|
||||||
|
// raggio dalla tua posizione
|
||||||
|
let distanzaMassima = EQNData.getSettingSeismicNetworkNotificationRadius(for: saved.distanzaMassima)
|
||||||
|
currentMaximumDistance = distanzaMassima
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Table view data source
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
|
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||||
|
view.titleLabel.text = NSLocalizedString("options_notification_official", comment: "")
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
|
SettingSectionHeaderView.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return settings.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||||
|
return UITableViewCell()
|
||||||
|
}
|
||||||
|
|
||||||
|
let setting = settings[indexPath.row]
|
||||||
|
switch setting.type {
|
||||||
|
case .enable:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
cell.descriptionLabel.text = setting.subtitle
|
||||||
|
|
||||||
|
switch identifier {
|
||||||
|
case .abilitaNotifiche:
|
||||||
|
cell.toggleSwitch.isOn = isNotificationEnabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeNotificationEnabled(enabled)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
|
||||||
|
case .slider:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingSliderTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
|
||||||
|
let filtersEnabled = isNotificationEnabled
|
||||||
|
switch identifier {
|
||||||
|
case .magnitudoMinima:
|
||||||
|
cell.isDisabled = !filtersEnabled
|
||||||
|
cell.configureSlider(with: dataSourceMinimumMagnitude, current: currentMinimumMagnitude)
|
||||||
|
cell.valueChanged = { [weak self] item in
|
||||||
|
self?.onChangeMinimumMagnitude(item)
|
||||||
|
}
|
||||||
|
case .distanzaMassima:
|
||||||
|
cell.isDisabled = !filtersEnabled
|
||||||
|
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
|
||||||
|
cell.valueChanged = { [weak self] item in
|
||||||
|
self?.onChangeMaximumDistance(item)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||||
|
isNotificationEnabled = enabled
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.isAbilitato = isNotificationEnabled
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeMinimumMagnitude(_ item: EQNGenericValue) {
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.magnitudoMinima = item.value
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.distanzaMassima = item.value
|
||||||
|
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
}
|
||||||
|
}
|
||||||
-71
@@ -1,71 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsSeismicNetworksViewController.swift
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Created by Busi Andrea on 26/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
class SettingsSeismicNetworksViewController: UITableViewController {
|
|
||||||
|
|
||||||
var networks = [EQNSeismicNetwork]()
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
tableView.register(SettingDetailTableViewCell.self, forCellReuseIdentifier: SettingDetailTableViewCell.Identifier)
|
|
||||||
tableView.register(SettingSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: SettingSectionHeaderView.Identifier)
|
|
||||||
|
|
||||||
networks = EQNData.seismicNetworks().sorted(by: { $0.acronym < $1.acronym })
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Table view data source
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
||||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SettingSectionHeaderView.Identifier) as! SettingSectionHeaderView
|
|
||||||
headerView.titleLabel.text = NSLocalizedString("options_agencies", comment: "");
|
|
||||||
return headerView
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
|
||||||
CGFloat(SettingSectionHeaderView.Height)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
||||||
networks.count
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
||||||
let network = networks[indexPath.row]
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: SettingDetailTableViewCell.Identifier, for: indexPath) as! SettingDetailTableViewCell
|
|
||||||
cell.textLabel?.text = "\(network.acronym) (\(network.country))"
|
|
||||||
|
|
||||||
if EQNNotificheReteSismiche.shared().listaEnti.contains(network.acronym) {
|
|
||||||
cell.accessoryType = .checkmark
|
|
||||||
} else {
|
|
||||||
cell.accessoryType = .none
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
|
||||||
|
|
||||||
let network = networks[indexPath.row]
|
|
||||||
|
|
||||||
var savedNetworks = EQNNotificheReteSismiche.shared().listaEnti
|
|
||||||
if let index = savedNetworks.firstIndex(where: { $0 == network.acronym }) {
|
|
||||||
savedNetworks.remove(at: index)
|
|
||||||
} else {
|
|
||||||
savedNetworks.append(network.acronym)
|
|
||||||
}
|
|
||||||
|
|
||||||
EQNNotificheReteSismiche.shared().listaEnti = savedNetworks
|
|
||||||
EQNNotificheReteSismiche.shared().saveUserInfo()
|
|
||||||
|
|
||||||
tableView.reloadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsUserReportAlertsViewController.h
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi 25/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
#import "SettingsBaseViewController.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface SettingsUserReportAlertsViewController : SettingsBaseViewController
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
-119
@@ -1,119 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsUserReportAlertsViewController.m
|
|
||||||
// Earthquake Network
|
|
||||||
//
|
|
||||||
// Refactored by Andrea Busi 25/08/2020.
|
|
||||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "SettingsUserReportAlertsViewController.h"
|
|
||||||
#import "EQNNotificheSegnalazioniUtente.h"
|
|
||||||
|
|
||||||
@interface SettingsUserReportAlertsViewController ()
|
|
||||||
|
|
||||||
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
|
|
||||||
@property (nonatomic, assign) BOOL notificationsEnabled;
|
|
||||||
@property (nonatomic, strong) EQNGenericValue *currentRadius;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation SettingsUserReportAlertsViewController
|
|
||||||
|
|
||||||
#pragma mark - View Lifecycle
|
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
|
|
||||||
[self setupUI];
|
|
||||||
|
|
||||||
self.settings = @[
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_manual", @"")],
|
|
||||||
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius", @"")]
|
|
||||||
];
|
|
||||||
|
|
||||||
[self updateUI];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (void)setupUI
|
|
||||||
{
|
|
||||||
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
|
|
||||||
|
|
||||||
self.tableView.estimatedRowHeight = 100.0;
|
|
||||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
|
||||||
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
|
|
||||||
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateUI
|
|
||||||
{
|
|
||||||
self.notificationsEnabled = [EQNNotificheSegnalazioniUtente sharedInstance].isAbilitato;
|
|
||||||
|
|
||||||
EQNGenericValue *distanzaPosizione = [EQNData raggioSismaFor:[EQNNotificheSegnalazioniUtente sharedInstance].distanzaPosizione];
|
|
||||||
self.currentRadius = distanzaPosizione;
|
|
||||||
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self.tableView reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateRadius:(EQNGenericValue *)radius
|
|
||||||
{
|
|
||||||
self.currentRadius = radius;
|
|
||||||
[EQNNotificheSegnalazioniUtente sharedInstance].distanzaPosizione = radius.value;
|
|
||||||
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
|
|
||||||
|
|
||||||
[self.tableView reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Table view data source
|
|
||||||
|
|
||||||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
|
|
||||||
headerView.titleLabel.text = NSLocalizedString(@"options_notification_manual", @"titolo impostazioni notifiche");
|
|
||||||
return headerView;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return SettingSectionHeaderView.Height;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
return self.settings.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
SettingItem *setting = self.settings[indexPath.row];
|
|
||||||
|
|
||||||
if (setting.type == SettingTypeEnable) {
|
|
||||||
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.toggleSwitch.on = self.notificationsEnabled;
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
cell.descriptionLabel.text = setting.subtitle;
|
|
||||||
cell.valueChanged = ^(BOOL enabled) {
|
|
||||||
self.notificationsEnabled = enabled;
|
|
||||||
[EQNNotificheSegnalazioniUtente sharedInstance].isAbilitato = self.notificationsEnabled;
|
|
||||||
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
};
|
|
||||||
return cell;
|
|
||||||
} else if (setting.type == SettingTypeSlider) {
|
|
||||||
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
|
|
||||||
cell.isDisabled = !self.notificationsEnabled;
|
|
||||||
cell.titleLabel.text = setting.displayTitle;
|
|
||||||
[cell configureSliderWith:[EQNData raggioSismi] current:self.currentRadius];
|
|
||||||
cell.valueChanged = ^(EQNGenericValue *item) {
|
|
||||||
[self updateRadius:item];
|
|
||||||
};
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
//
|
||||||
|
// SettingsUserReportNotificationsViewController.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 10/06/24.
|
||||||
|
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
class SettingsUserReportNotificationsViewController: SettingsBaseTableViewController {
|
||||||
|
|
||||||
|
private enum RowIdentifier: Int {
|
||||||
|
case abilitaNotifiche
|
||||||
|
case distanzaMassima
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isNotificationEnabled = false
|
||||||
|
private var currentMaximumDistance = EQNData.DefaultSettingUserReportNotificationRadius
|
||||||
|
private let dataSourceMaximumDistance = EQNData.settingUserReportNotificationRadius
|
||||||
|
|
||||||
|
private let settings: [SettingItem] = [
|
||||||
|
.init(type: .enable, title: NSLocalizedString("options_notification_enable_manual", comment: "")),
|
||||||
|
.init(type: .slider, title: NSLocalizedString("options_radius", comment: ""))
|
||||||
|
]
|
||||||
|
|
||||||
|
// MARK: - View Lifecycle
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
|
|
||||||
|
tableView.estimatedRowHeight = 200.0
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||||
|
tableView.registerCell(for: SettingEnableTableViewCell.self)
|
||||||
|
tableView.registerCell(for: SettingSliderTableViewCell.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadDataSource() {
|
||||||
|
let saved = EQNSettingUserReportNotification.shared
|
||||||
|
|
||||||
|
isNotificationEnabled = saved.isAbilitato
|
||||||
|
|
||||||
|
let distanzaMassima = EQNData.getSettingUserReportNotificationRadius(for: saved.distanzaMassima)
|
||||||
|
currentMaximumDistance = distanzaMassima
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Table view delegate and data source
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||||
|
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||||
|
view.titleLabel.text = NSLocalizedString("options_notification_manual", comment: "")
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
|
SettingSectionHeaderView.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return settings.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
|
||||||
|
return UITableViewCell()
|
||||||
|
}
|
||||||
|
|
||||||
|
let setting = settings[indexPath.row]
|
||||||
|
switch setting.type {
|
||||||
|
case .enable:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
cell.descriptionLabel.text = setting.subtitle
|
||||||
|
|
||||||
|
switch identifier {
|
||||||
|
case .abilitaNotifiche:
|
||||||
|
cell.toggleSwitch.isOn = isNotificationEnabled
|
||||||
|
cell.valueChanged = { [weak self] enabled in
|
||||||
|
self?.onChangeNotificationEnabled(enabled)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
|
||||||
|
case .slider:
|
||||||
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingSliderTableViewCell.self, for: indexPath)
|
||||||
|
cell.titleLabel.text = setting.displayTitle
|
||||||
|
|
||||||
|
switch identifier {
|
||||||
|
case .distanzaMassima:
|
||||||
|
cell.isDisabled = !isNotificationEnabled
|
||||||
|
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
|
||||||
|
cell.valueChanged = { [weak self] item in
|
||||||
|
self?.onChangeMaximumDistance(item)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||||
|
isNotificationEnabled = enabled
|
||||||
|
EQNSettingUserReportNotification.shared.isAbilitato = isNotificationEnabled
|
||||||
|
EQNSettingUserReportNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
tableView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
|
||||||
|
EQNSettingUserReportNotification.shared.distanzaMassima = item.value
|
||||||
|
EQNSettingUserReportNotification.shared.saveUserInfo()
|
||||||
|
|
||||||
|
loadDataSource()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
// MARK: - Internal
|
// 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
|
||||||
|
|
||||||
@@ -104,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() {
|
||||||
@@ -155,6 +183,18 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
|
|||||||
extraUI()
|
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
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
@@ -238,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)
|
||||||
@@ -249,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() {
|
||||||
@@ -286,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func navigateToSubscriptions() {
|
private func navigateToSubscriptions() {
|
||||||
let controller = SubscriptionsViewController.makeViewController()
|
let controller = SubscriptionsViewController()
|
||||||
let navigationController = UINavigationController(rootViewController: controller)
|
let navigationController = UINavigationController(rootViewController: controller)
|
||||||
present(navigationController, animated: true)
|
present(navigationController, animated: true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ static NSString * const EQNServerUrlCalibration = @"https://srv.earthquakenetwor
|
|||||||
// download rete smartphone
|
// download rete smartphone
|
||||||
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://cache.earthquakenetwork.it/distquake_count_redis3.php";
|
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://cache.earthquakenetwork.it/distquake_count_redis3.php";
|
||||||
// download area check
|
// download area check
|
||||||
static NSString * const EQNServerUrlDownloadAreaCheck = @"https://srv.earthquakenetwork.it/distquake_download_areacheck.php";
|
static NSString * const EQNServerUrlDownloadAreaCheck = @"https://cache.earthquakenetwork.it/distquake_download_areacheck.php";
|
||||||
// download pastquakes
|
// download pastquakes
|
||||||
static NSString * const EQNServerUrlDownloadPastQuakes = @"https://srv.earthquakenetwork.it/distquake_download_pastquakes.php";
|
static NSString * const EQNServerUrlDownloadPastQuakes = @"https://srv.earthquakenetwork.it/distquake_download_pastquakes.php";
|
||||||
// download segnalazioni
|
// download segnalazioni
|
||||||
@@ -64,8 +64,6 @@ static NSString * const EQNServerUrlAlertSimulator = @"https://srv.earthquakenet
|
|||||||
|
|
||||||
#pragma mark - UserDefaults Keys
|
#pragma mark - UserDefaults Keys
|
||||||
|
|
||||||
static NSString * const EQNUserDefaultKeySesmicInformations = @"EQNetwork.SeismicInformations";
|
|
||||||
static NSString * const EQNUserDefaultKeyOneShotShowCountry = @"EQNetwork.OneShot.CountrySelection";
|
|
||||||
static NSString * const EQNUserDefaultSeismicNetworkCards = @"EQNData.RetiSismiche";
|
static NSString * const EQNUserDefaultSeismicNetworkCards = @"EQNData.RetiSismiche";
|
||||||
|
|
||||||
#pragma mark - NSNotification
|
#pragma mark - NSNotification
|
||||||
|
|||||||
@@ -6,16 +6,12 @@
|
|||||||
#import "Costanti.h"
|
#import "Costanti.h"
|
||||||
#import "EQNUser.h"
|
#import "EQNUser.h"
|
||||||
#import "EQNManager.h"
|
#import "EQNManager.h"
|
||||||
#import "EQNNotificheReteSismiche.h"
|
|
||||||
#import "EQNNotificheSegnalazioniUtente.h"
|
|
||||||
#import "EQNSisma.h"
|
#import "EQNSisma.h"
|
||||||
#import "EQNBaseViewController.h"
|
#import "EQNBaseViewController.h"
|
||||||
#import "SettingsBaseViewController.h"
|
|
||||||
#import "EQNGeneratoreURLServer.h"
|
#import "EQNGeneratoreURLServer.h"
|
||||||
#import "ServerRequest.h"
|
#import "ServerRequest.h"
|
||||||
#import "EQNSegnalazione.h"
|
#import "EQNSegnalazione.h"
|
||||||
#import "EQNPastquakes.h"
|
#import "EQNPastquakes.h"
|
||||||
#import "EQNAllertaSismica.h"
|
|
||||||
|
|
||||||
#import "GADTTemplateView.h"
|
#import "GADTTemplateView.h"
|
||||||
#import "GADTMediumTemplateView.h"
|
#import "GADTMediumTemplateView.h"
|
||||||
|
|||||||
@@ -55,9 +55,9 @@
|
|||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCalendarsUsageDescription</key>
|
<key>NSCalendarsUsageDescription</key>
|
||||||
<string>L'accesso al calendario è richiesto per poter salvare le informazioni dei sismi di interesse</string>
|
<string>L'accesso al calendario è richiesto per poter salvare le informazioni dei sismi di interesse</string>
|
||||||
<key>NSContactsUsageDescription</key>
|
<key>NSContactsUsageDescription</key>
|
||||||
<string>L'accesso ai contatti è richiesto per poter aggiungere persone agli eventi creati</string>
|
<string>L'accesso ai contatti è richiesto per poter aggiungere persone agli eventi creati</string>
|
||||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||||
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
||||||
<key>NSLocationAlwaysUsageDescription</key>
|
<key>NSLocationAlwaysUsageDescription</key>
|
||||||
@@ -65,9 +65,9 @@
|
|||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
<string>L'accesso alla libreria è richiesto per poter salvare le immagini generate dall'app</string>
|
<string>L'accesso alla libreria è richiesto per poter salvare le immagini generate dall'app</string>
|
||||||
<key>NSUserTrackingUsageDescription</key>
|
<key>NSUserTrackingUsageDescription</key>
|
||||||
<string>Il tracciamento serve a capire se la pubblicità dell'app è efficace</string>
|
<string>Il tracciamento serve a capire se la pubblicità dell'app è efficace</string>
|
||||||
<key>SKAdNetworkItems</key>
|
<key>SKAdNetworkItems</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
@@ -104,5 +104,7 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIUserInterfaceStyle</key>
|
<key>UIUserInterfaceStyle</key>
|
||||||
<string>Light</string>
|
<string>Light</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>12.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -26,3 +26,14 @@ extension NSDate {
|
|||||||
return (self as Date).isBeforeInterval(interval)
|
return (self as Date).isBeforeInterval(interval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension CGFloat {
|
||||||
|
var negative: CGFloat {
|
||||||
|
-self
|
||||||
|
}
|
||||||
|
|
||||||
|
var x2: CGFloat {
|
||||||
|
self*2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ import UIKit
|
|||||||
extension UIView {
|
extension UIView {
|
||||||
|
|
||||||
func eqn_applyShadowAndRoundedCorners() {
|
func eqn_applyShadowAndRoundedCorners() {
|
||||||
// rounded corners
|
eqn_applyRoundedCorners()
|
||||||
layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
|
||||||
layer.masksToBounds = false
|
|
||||||
|
|
||||||
// apply a shadow to the current view
|
// apply a shadow to the current view
|
||||||
layer.shadowColor = UIColor.black.cgColor
|
layer.shadowColor = UIColor.black.cgColor
|
||||||
@@ -22,4 +20,10 @@ extension UIView {
|
|||||||
layer.shadowOffset = CGSize(width: 0, height: 2)
|
layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||||
layer.shadowRadius = 2
|
layer.shadowRadius = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func eqn_applyRoundedCorners() {
|
||||||
|
// rounded corners
|
||||||
|
layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
||||||
|
layer.masksToBounds = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
//
|
||||||
|
// Log.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 27/02/25.
|
||||||
|
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import OSLog
|
||||||
|
|
||||||
|
|
||||||
|
/// Use this protocol to have a base TAG in a Swift class
|
||||||
|
public protocol Loggable {
|
||||||
|
static var TAG: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Loggable {
|
||||||
|
static var TAG: String {
|
||||||
|
String(describing: Self.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIViewController: Loggable { }
|
||||||
|
|
||||||
|
|
||||||
|
public class Log {
|
||||||
|
|
||||||
|
private static let dumpDateFormatter: DateFormatter = {
|
||||||
|
// create the default date formatter using ISO8601 date format
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
|
||||||
|
private static let shared = Log()
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private let maxNumberOfLogsInDump: Int
|
||||||
|
private let logsLifespanMillis: Int
|
||||||
|
/// Subsystem for OSLog
|
||||||
|
private let subsystem: String
|
||||||
|
/// Logging in everything in a single "APP" category
|
||||||
|
private let appCategory: String = "APP"
|
||||||
|
|
||||||
|
private lazy var logger: os.Logger = {
|
||||||
|
os.Logger(subsystem: subsystem, category: appCategory)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public init(
|
||||||
|
subsystem: String = Bundle.main.bundleIdentifier!,
|
||||||
|
maxNumberOfLogsInDump: Int = 5000,
|
||||||
|
logsLifespanMillis: Int = 3 * 24 * 3600 * 1000
|
||||||
|
) {
|
||||||
|
self.subsystem = subsystem
|
||||||
|
self.maxNumberOfLogsInDump = maxNumberOfLogsInDump
|
||||||
|
self.logsLifespanMillis = logsLifespanMillis
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Internal
|
||||||
|
|
||||||
|
public static func error(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||||
|
shared.log(level: .fault, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func warning(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||||
|
shared.log(level: .error, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func info(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||||
|
shared.log(level: .info, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func debug(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||||
|
shared.log(level: .debug, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func verbose(tag: String?, _ message: String?, _ functionName: String = #function) {
|
||||||
|
shared.log(level: .debug, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dumpLog() async -> String {
|
||||||
|
return (try? await getLogEntries()) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func log(level: OSLogType, tag: String, message: String, functionName: String) {
|
||||||
|
let formattedMessage = "[\(tag)] \(functionName): \(message)"
|
||||||
|
switch level {
|
||||||
|
case .fault: logger.fault("\(formattedMessage, privacy: .public)")
|
||||||
|
case .error: logger.error("\(formattedMessage, privacy: .public)")
|
||||||
|
case .default: logger.notice("\(formattedMessage, privacy: .public)")
|
||||||
|
case .info: logger.info("\(formattedMessage, privacy: .public)")
|
||||||
|
default: logger.debug("\(formattedMessage, privacy: .public)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve log entries from a specified time.
|
||||||
|
/// - Returns: String of log entries, newlines separated
|
||||||
|
private func getLogEntries() async throws -> String {
|
||||||
|
let logTask = Task.init(priority: .utility) { () -> String in
|
||||||
|
let logs = try retrieveLogEntries()
|
||||||
|
let text = logs
|
||||||
|
.compactMap { "\(Self.dumpDateFormatter.string(from: $0.date)) [\($0.level)] \($0.composedMessage)" }
|
||||||
|
.joined(separator: "\n")
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return try await logTask.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private func retrieveLogEntries() throws -> [OSLogEntryLog] {
|
||||||
|
// Open the log store.
|
||||||
|
let logStore = try OSLogStore(scope: .currentProcessIdentifier)
|
||||||
|
|
||||||
|
// Fetch log objects from the given time interval
|
||||||
|
let intervalPosition = logStore.position(date: Date().addingTimeInterval(TimeInterval(-logsLifespanMillis / 1000)))
|
||||||
|
let allEntries = try logStore.getEntries(at: intervalPosition)
|
||||||
|
|
||||||
|
// Filter the log to be relevant for our specific subsystem
|
||||||
|
// and remove other elements (signposts, etc).
|
||||||
|
return allEntries
|
||||||
|
.compactMap { $0 as? OSLogEntryLog }
|
||||||
|
.filter { $0.subsystem == subsystem }
|
||||||
|
.suffix(maxNumberOfLogsInDump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension OSLogEntryLog.Level: @retroactive CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
switch self {
|
||||||
|
case .fault: return "FAULT"
|
||||||
|
case .error: return "ERROR"
|
||||||
|
case .notice: return "WARNING"
|
||||||
|
case .info: return "INFO"
|
||||||
|
case .debug: return "DEBUG"
|
||||||
|
case .undefined: return "UNDEFINED"
|
||||||
|
@unknown default:
|
||||||
|
return "UNKNOWN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,4 +29,37 @@ class AppPreferences: NSObject {
|
|||||||
get { UserDefaults.standard.bool(forKey: UserDefaults.AlertsShowCardOptions) }
|
get { UserDefaults.standard.bool(forKey: UserDefaults.AlertsShowCardOptions) }
|
||||||
set { UserDefaults.standard.set(newValue, forKey: UserDefaults.AlertsShowCardOptions) }
|
set { UserDefaults.standard.set(newValue, forKey: UserDefaults.AlertsShowCardOptions) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mapPinStyle: MapPinStyle {
|
||||||
|
get {
|
||||||
|
let saved = UserDefaults.standard.integer(forKey: UserDefaults.MapPinStyle)
|
||||||
|
return MapPinStyle(rawValue: saved) ?? .circle
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaults.MapPinStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var seismicNetworksInformations: [SeismicNetworkTableViewCell.InformationType] {
|
||||||
|
get {
|
||||||
|
if let saved = UserDefaults.standard.array(forKey: UserDefaults.SeismicNetworksCardInformations) as? [Int] {
|
||||||
|
let informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
|
||||||
|
return informations
|
||||||
|
}
|
||||||
|
return [.buttons, .distance, .coordinate, .population, .intensityMap]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
UserDefaults.standard.set(newValue.map { $0.rawValue }, forKey: UserDefaults.SeismicNetworksCardInformations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var seismicNetworksCardStyle: SeismicNetworksViewController.CardDisplayType {
|
||||||
|
get {
|
||||||
|
let saved = UserDefaults.standard.integer(forKey: UserDefaults.SeismicNetworksCardStyle)
|
||||||
|
return SeismicNetworksViewController.CardDisplayType(rawValue: saved) ?? .small
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaults.SeismicNetworksCardStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class EQNAppearanceCommand: EQNCommandProtocol {
|
|||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
func execute() {
|
func execute() {
|
||||||
print("EQNAppearanceCommand: start execute")
|
print("[EQNAppearanceCommand] Start execute")
|
||||||
|
|
||||||
applyAppearance()
|
applyAppearance()
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ public class EQNAppearanceCommand: EQNCommandProtocol {
|
|||||||
navAppearance.largeTitleTextAttributes = [
|
navAppearance.largeTitleTextAttributes = [
|
||||||
NSAttributedString.Key.foregroundColor: AppTheme.Colors.darkGray
|
NSAttributedString.Key.foregroundColor: AppTheme.Colors.darkGray
|
||||||
]
|
]
|
||||||
navAppearance.backgroundColor = AppTheme.Colors.primary
|
navAppearance.backgroundColor = AppTheme.Colors.navBar
|
||||||
navAppearance.shadowColor = UIColor.clear
|
navAppearance.shadowColor = UIColor.clear
|
||||||
|
|
||||||
proxyNavBar.isTranslucent = false
|
proxyNavBar.isTranslucent = false
|
||||||
|
|||||||
@@ -15,67 +15,124 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
|
|||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
func execute() {
|
func execute() {
|
||||||
print("EQNUserDefaultsCommand: start execute")
|
print("[EQNUserDefaultsCommand] Start execute")
|
||||||
|
|
||||||
applyDefaultSettings()
|
migrationV5_8()
|
||||||
saveMissingValues()
|
migrationFirstAppStat()
|
||||||
|
migrationCriticalAlerts()
|
||||||
migrationV5_3()
|
migrationV5_9()
|
||||||
migrationV5_4()
|
migrationV5_10()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func applyDefaultSettings() {
|
private func migrationV5_8() {
|
||||||
|
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_8)
|
||||||
// seismic card settings
|
|
||||||
if UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) == nil {
|
|
||||||
let informations: [SeismicNetworkTableViewCell.InformationType] = [.buttons, .distance, .coordinate, .population]
|
|
||||||
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func saveMissingValues() {
|
|
||||||
// `raggio sismi forti` was not saved before v2.3
|
|
||||||
if UserDefaults.standard.object(forKey: UserDefaults.AllertaSismicaRaggioSismiForti) == nil {
|
|
||||||
UserDefaults.standard.set("600", forKey: UserDefaults.AllertaSismicaRaggioSismiForti)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func migrationV5_3() {
|
|
||||||
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_3)
|
|
||||||
if migrationPerformed {
|
if migrationPerformed {
|
||||||
print("[EQNUserDefaultsCommand] Migration v5.3 already performed")
|
print("[EQNUserDefaultsCommand] Migration v5.8 already performed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// l'ultima posizione era salvata come array, la trasformiamo in valore singolo
|
// delete old notification settings
|
||||||
let lastLocations = EQNUtility.loadArray(of: CLLocation.self, fromUserDefaultsForKey: UserDefaults.UserDataLastLocation) as? [CLLocation]
|
|
||||||
if let lastLocation = lastLocations?.last {
|
|
||||||
UserDefaults.standard.removeObject(forKey: UserDefaults.UserDataLastLocation)
|
|
||||||
EQNUserData.shared.saveLastLocation(lastLocation)
|
|
||||||
}
|
|
||||||
|
|
||||||
// resettiamo il Firebase token in modo da ri-eseguire la procedura di registrazione corretta
|
|
||||||
EQNUserData.shared.saveFirebaseToken(nil)
|
|
||||||
|
|
||||||
UserDefaults.standard.set(true, forKey: UserDefaults.AppMigrationV5_3)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func migrationV5_4() {
|
|
||||||
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_4)
|
|
||||||
if migrationPerformed {
|
|
||||||
print("[EQNUserDefaultsCommand] Migration v5.4 already performed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// migriamo l'ultima posizione negli user defaults condivisi
|
|
||||||
let userDefaults = UserDefaults.standard
|
let userDefaults = UserDefaults.standard
|
||||||
let groupUserDefaults = UserDefaults.appGroup
|
[
|
||||||
if let encodedLocation = userDefaults.object(forKey: UserDefaults.UserDataLastLocation) as? Data {
|
"NOTIFICHE_ATTIVA_RETI_SISMICHE_VICINE", "NOTIFICHE_ATTIVA_RETI_TERREMOTI_FORTI",
|
||||||
groupUserDefaults?.set(encodedLocation, forKey: UserDefaults.UserDataLastLocation)
|
"NOTIFICHE_ATTIVA_RETI_ENERGIA_FORTI", "NOTIFICHE_ATTIVA_RETI_LISTA_ENTI"
|
||||||
|
].forEach { key in
|
||||||
|
userDefaults.removeObject(forKey: key)
|
||||||
}
|
}
|
||||||
|
|
||||||
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_4)
|
// delete old filter values
|
||||||
|
[
|
||||||
|
"EQN_ETA_MASSIMA", "EQN_SISMI_FORTI_ABILITATI", "EQN_SISMI_FORTI",
|
||||||
|
"EQN_SISMI_QUALSIASI_MAGNITUDO", "EQN_SISMI_MODIFICA_IMPOSTAZIONI"
|
||||||
|
].forEach { key in
|
||||||
|
userDefaults.removeObject(forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete old "real time alert" settings
|
||||||
|
[
|
||||||
|
"NOTIFICHE_ALLERA_SISMICA_IMPOSTA_VOLUME", "NOTIFICHE_ALLERA_SISMICA_TESTA_ALLARME",
|
||||||
|
"NOTIFICHE_ALLERA_SISMICA_ABILITA_INTERVALLO", "NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO",
|
||||||
|
"NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
|
||||||
|
].forEach { key in
|
||||||
|
userDefaults.removeObject(forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func migrationFirstAppStat() {
|
||||||
|
// before v5.8.2, first app start was defined using Firebase Token
|
||||||
|
let userDefaults = UserDefaults.standard
|
||||||
|
let firstAppStartExecuted = userDefaults.bool(forKey: UserDefaults.FirstAppStartExecuted)
|
||||||
|
if firstAppStartExecuted {
|
||||||
|
print("[EQNUserDefaultsCommand] First app start already executed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let firebaseToken = userDefaults.object(forKey: UserDefaults.UserDataFirebaseToken) as? String
|
||||||
|
if firebaseToken != nil {
|
||||||
|
print("[EQNUserDefaultsCommand] First app start migrated")
|
||||||
|
userDefaults.set(true, forKey: UserDefaults.FirstAppStartExecuted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func migrationCriticalAlerts() {
|
||||||
|
let userDefaults = UserDefaults.standard
|
||||||
|
let migrationPerformed = userDefaults.bool(forKey: UserDefaults.AppMigrationV5_8_2)
|
||||||
|
if migrationPerformed {
|
||||||
|
print("[EQNUserDefaultsCommand] Migration v5.8.2 already performed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||||
|
if settings.criticalAlertSetting != .enabled {
|
||||||
|
print("[EQNUserDefaultsCommand] Critical alerts not enabled, disable settings")
|
||||||
|
EQNSettingRealTimeAlert.shared.isCriticalAlertsEnabled = false
|
||||||
|
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||||
|
} else {
|
||||||
|
print("[EQNUserDefaultsCommand] Critical alerts enabled, do nothing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_8_2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func migrationV5_9() {
|
||||||
|
let userDefaults = UserDefaults.standard
|
||||||
|
let migrationPerformed = userDefaults.bool(forKey: UserDefaults.AppMigrationV5_9)
|
||||||
|
if migrationPerformed {
|
||||||
|
print("[EQNUserDefaultsCommand] Migration v5.9 already performed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new intensity map
|
||||||
|
var informations = AppPreferences.shared.seismicNetworksInformations
|
||||||
|
if !informations.contains(.intensityMap) {
|
||||||
|
informations.append(.intensityMap)
|
||||||
|
print("[EQNUserDefaultsCommand] Add intensityMap to seismic informations")
|
||||||
|
}
|
||||||
|
AppPreferences.shared.seismicNetworksInformations = informations
|
||||||
|
|
||||||
|
let cardDisplayType: SeismicNetworksViewController.CardDisplayType = informations.contains(.buttons) ? .full : .small
|
||||||
|
AppPreferences.shared.seismicNetworksCardStyle = cardDisplayType
|
||||||
|
|
||||||
|
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_9)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func migrationV5_10() {
|
||||||
|
let userDefaults = UserDefaults.standard
|
||||||
|
let migrationPerformed = userDefaults.bool(forKey: UserDefaults.AppMigrationV5_10)
|
||||||
|
if migrationPerformed {
|
||||||
|
print("[EQNUserDefaultsCommand] Migration v5.10 already performed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
print("[EQNUserDefaultsCommand] Save default value for mildQuakeSoundDisabled")
|
||||||
|
EQNSettingRealTimeAlert.shared.isMildQuakeSoundDisabled = true
|
||||||
|
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||||
|
|
||||||
|
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,89 +10,121 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
@objc class EQNData: NSObject {
|
@objc class EQNData: NSObject {
|
||||||
@objc public static let MaxRaggioSisma = "100000"
|
static let MaxRaggioSisma = "100000"
|
||||||
@objc public static let DefaultRaggioSisma = EQNGenericValue(value:MaxRaggioSisma, display:"radius_any_distance")
|
static let DefaultSettingSeismicNetworkNotificationRadius = EQNGenericValue(value:"500", display:"500 km")
|
||||||
@objc public static let DefaultMagitudoDebole = EQNGenericValue(value:"2.0", display:"official_magnitude_value_20")
|
static let DefaultSettingSeismicNetworkNotificationMagitude = EQNGenericValue(value:"2.0", display:"official_magnitude_value_20")
|
||||||
@objc public static let DefaultMagitudoForte = EQNGenericValue(value:"5.5", display:"official_magnitude_value_55")
|
static let DefaultSettingUserReportNotificationRadius = EQNGenericValue(value:"1000", display:"1000 km")
|
||||||
@objc public static let DefaultPeriodoTemporale = EQNGenericValue(value: "1440", display: "report_timeframe_one_day")
|
static let DefaultFilterRadius = EQNGenericValue(value:"250", display:"250 km")
|
||||||
|
static let DefaultFilterMagnitude = EQNGenericValue(value:"0.0", display:"official_magnitude_value_00")
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
@objc class func raggioSismi() -> [EQNGenericValue] {
|
// Distances for "seismic network notifications"
|
||||||
[
|
static let settingSeismicNetworkNotificationRadius: [EQNGenericValue] = [
|
||||||
EQNGenericValue(value:"50", display:"50 km"),
|
EQNGenericValue(value:"100", display:"100 km"),
|
||||||
EQNGenericValue(value:"100", display:"100 km"),
|
EQNGenericValue(value:"250", display:"250 km"),
|
||||||
EQNGenericValue(value:"200", display:"200 km"),
|
EQNGenericValue(value:"500", display:"500 km"),
|
||||||
EQNGenericValue(value:"300", display:"300 km"),
|
EQNGenericValue(value:"1000", display:"1000 km")
|
||||||
EQNGenericValue(value:"400", display:"400 km"),
|
]
|
||||||
EQNGenericValue(value:"500", display:"500 km"),
|
|
||||||
EQNGenericValue(value:"600", display:"600 km"),
|
// Magnitudes for "seismic network notifications"
|
||||||
EQNGenericValue(value:"800", display:"800 km"),
|
static let settingSeismicNetworkNotificationMagnitudes: [EQNGenericValue] = [
|
||||||
EQNGenericValue(value:"1000", display:"1000 km"),
|
EQNGenericValue(value:"0.0", display:"0.0"),
|
||||||
EQNGenericValue(value:"2000", display:"2000 km"),
|
EQNGenericValue(value:"0.5", display:"0.5"),
|
||||||
EQNGenericValue(value:"4000", display:"4000 km"),
|
EQNGenericValue(value:"1.0", display:"1.0"),
|
||||||
EQNGenericValue(value:Self.MaxRaggioSisma, display:"radius_any_distance"),
|
EQNGenericValue(value:"1.5", display:"1.5"),
|
||||||
]
|
EQNGenericValue(value:"2.0", display:"2.0"),
|
||||||
}
|
EQNGenericValue(value:"2.5", display:"2.5"),
|
||||||
|
EQNGenericValue(value:"3.0", display:"3.0"),
|
||||||
|
EQNGenericValue(value:"3.5", display:"3.5"),
|
||||||
|
EQNGenericValue(value:"4.0", display:"4.0"),
|
||||||
|
EQNGenericValue(value:"4.5", display:"4.5"),
|
||||||
|
EQNGenericValue(value:"5.0", display:"5.0"),
|
||||||
|
EQNGenericValue(value:"5.5", display:"5.5")
|
||||||
|
]
|
||||||
|
|
||||||
|
// Distances for "user report notifications"
|
||||||
|
static let settingUserReportNotificationRadius: [EQNGenericValue] = [
|
||||||
|
EQNGenericValue(value:"100", display:"100 km"),
|
||||||
|
EQNGenericValue(value:"250", display:"250 km"),
|
||||||
|
EQNGenericValue(value:"500", display:"500 km"),
|
||||||
|
EQNGenericValue(value:"1000", display:"1000 km")
|
||||||
|
]
|
||||||
|
|
||||||
|
// Misure raggio utilizzate nei filtri
|
||||||
|
static let filterRadius: [EQNGenericValue] = [
|
||||||
|
EQNGenericValue(value:"100", display:"100 km"),
|
||||||
|
EQNGenericValue(value:"250", display:"250 km"),
|
||||||
|
EQNGenericValue(value:"500", display:"500 km"),
|
||||||
|
EQNGenericValue(value:"750", display:"750 km"),
|
||||||
|
EQNGenericValue(value:"1000", display:"1000 km"),
|
||||||
|
EQNGenericValue(value:"1500", display:"1500 km"),
|
||||||
|
EQNGenericValue(value:"2000", display:"2000 km")
|
||||||
|
]
|
||||||
|
|
||||||
|
// Magnitudo utilizzate nei filtri
|
||||||
|
static let filterMagnitude: [EQNGenericValue] = [
|
||||||
|
EQNGenericValue(value:"0.0", display:"0.0"),
|
||||||
|
EQNGenericValue(value:"1.0", display:"1.0"),
|
||||||
|
EQNGenericValue(value:"2.0", display:"2.0"),
|
||||||
|
EQNGenericValue(value:"3.0", display:"3.0"),
|
||||||
|
EQNGenericValue(value:"4.0", display:"4.0"),
|
||||||
|
EQNGenericValue(value:"5.0", display:"5.0"),
|
||||||
|
EQNGenericValue(value:"6.0", display:"6.0")
|
||||||
|
]
|
||||||
|
|
||||||
/// Returns the EQNGenericValue for the given value, or the default one
|
/// Returns the EQNGenericValue for the given value, or the default one
|
||||||
/// - Parameter value: Sisma value to search
|
/// - Parameter value: Sisma radius to search
|
||||||
/// - Returns: Found value or default
|
/// - Returns: Found value or default
|
||||||
@objc class func raggioSisma(for value: String?) -> EQNGenericValue {
|
@objc(getSettingSeismicNetworkAlertRadiusForValue:)
|
||||||
if let value = value, let genericValue = Self.raggioSismi().first(where: { $0.value == value }) {
|
class func getSettingSeismicNetworkNotificationRadius(for value: String?) -> EQNGenericValue {
|
||||||
|
if let value = value, let genericValue = settingSeismicNetworkNotificationRadius.first(where: { $0.value == value }) {
|
||||||
return genericValue
|
return genericValue
|
||||||
}
|
}
|
||||||
return Self.DefaultRaggioSisma
|
return DefaultSettingSeismicNetworkNotificationRadius
|
||||||
}
|
|
||||||
|
|
||||||
@objc class func magitudoDeboli() -> [EQNGenericValue] {
|
|
||||||
[
|
|
||||||
EQNGenericValue(value:"0.0", display:"official_magnitude_value_00"),
|
|
||||||
EQNGenericValue(value:"0.5", display:"official_magnitude_value_05"),
|
|
||||||
EQNGenericValue(value:"1.0", display:"official_magnitude_value_10"),
|
|
||||||
EQNGenericValue(value:"1.5", display:"official_magnitude_value_15"),
|
|
||||||
EQNGenericValue(value:"2.0", display:"official_magnitude_value_20"),
|
|
||||||
EQNGenericValue(value:"2.5", display:"official_magnitude_value_25"),
|
|
||||||
EQNGenericValue(value:"3.0", display:"official_magnitude_value_30"),
|
|
||||||
EQNGenericValue(value:"3.5", display:"official_magnitude_value_35"),
|
|
||||||
EQNGenericValue(value:"4.0", display:"official_magnitude_value_40"),
|
|
||||||
EQNGenericValue(value:"4.5", display:"official_magnitude_value_45"),
|
|
||||||
EQNGenericValue(value:"5.0", display:"official_magnitude_value_50"),
|
|
||||||
EQNGenericValue(value:"5.5", display:"official_magnitude_value_55")
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the EQNGenericValue for the given value, or the default one
|
/// Returns the EQNGenericValue for the given value, or the default one
|
||||||
/// - Parameter value: Magnitudo value to search
|
/// - Parameter value: Magnitudo value to search
|
||||||
/// - Returns: Found value or default
|
/// - Returns: Found value or default
|
||||||
@objc class func magitudoDebole(for value: String?) -> EQNGenericValue {
|
class func getSettingSeismicNetworkNotificationMagnitudes(for value: String?) -> EQNGenericValue {
|
||||||
if let value = value, let genericValue = Self.magitudoDeboli().first(where: { $0.value == value }) {
|
if let value = value, let genericValue = settingSeismicNetworkNotificationMagnitudes.first(where: { $0.value == value }) {
|
||||||
return genericValue
|
return genericValue
|
||||||
}
|
}
|
||||||
return Self.DefaultMagitudoDebole
|
return DefaultSettingSeismicNetworkNotificationMagitude
|
||||||
}
|
|
||||||
|
|
||||||
@objc class func magitudoForti() -> [EQNGenericValue] {
|
|
||||||
[
|
|
||||||
EQNGenericValue(value:"5.5", display:"official_magnitude_value_55"),
|
|
||||||
EQNGenericValue(value:"6.0", display:"official_magnitude_value_60"),
|
|
||||||
EQNGenericValue(value:"6.5", display:"official_magnitude_value_65"),
|
|
||||||
EQNGenericValue(value:"7.0", display:"official_magnitude_value_70"),
|
|
||||||
EQNGenericValue(value:"7.5", display:"official_magnitude_value_75")
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the EQNGenericValue for the given value, or the default one
|
/// Returns the EQNGenericValue for the given value, or the default one
|
||||||
/// - Parameter value: Magnitudo value to search
|
/// - Parameter value: Sisma radius to search
|
||||||
/// - Returns: Found value or default
|
/// - Returns: Found value or default
|
||||||
@objc class func magitudoForte(for value: String?) -> EQNGenericValue {
|
class func getSettingUserReportNotificationRadius(for value: String?) -> EQNGenericValue {
|
||||||
if let value = value, let genericValue = Self.magitudoForti().first(where: { $0.value == value }) {
|
if let value = value, let genericValue = settingUserReportNotificationRadius.first(where: { $0.value == value }) {
|
||||||
return genericValue
|
return genericValue
|
||||||
}
|
}
|
||||||
return Self.DefaultMagitudoForte
|
return DefaultSettingUserReportNotificationRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc class func seismicNetworks() -> [EQNSeismicNetwork] {
|
/// Returns the EQNGenericValue for the given value, or the default one
|
||||||
|
/// - Parameter value: Sisma radius to search
|
||||||
|
/// - Returns: Found value or default
|
||||||
|
class func filterRadius(for value: String?) -> EQNGenericValue {
|
||||||
|
if let value = value, let genericValue = filterRadius.first(where: { $0.value == value }) {
|
||||||
|
return genericValue
|
||||||
|
}
|
||||||
|
return DefaultFilterRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the EQNGenericValue for the given value, or the default one
|
||||||
|
/// - Parameter value: Magnitudo value to search
|
||||||
|
/// - Returns: Found value or default
|
||||||
|
class func filterMagnitude(for value: String?) -> EQNGenericValue {
|
||||||
|
if let value = value, let genericValue = filterMagnitude.first(where: { $0.value == value }) {
|
||||||
|
return genericValue
|
||||||
|
}
|
||||||
|
return DefaultFilterMagnitude
|
||||||
|
}
|
||||||
|
|
||||||
|
class func seismicNetworks() -> [EQNSeismicNetwork] {
|
||||||
[
|
[
|
||||||
EQNSeismicNetwork(acronym: "USGS", country: NSLocalizedString("configuration_countries_united_states", comment: ""), extended: ""),
|
EQNSeismicNetwork(acronym: "USGS", country: NSLocalizedString("configuration_countries_united_states", comment: ""), extended: ""),
|
||||||
EQNSeismicNetwork(acronym: "INGV", country: NSLocalizedString("configuration_countries_italy", comment: ""), extended: ""),
|
EQNSeismicNetwork(acronym: "INGV", country: NSLocalizedString("configuration_countries_italy", comment: ""), extended: ""),
|
||||||
@@ -120,37 +152,16 @@ import Foundation
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc class func seismicNetworkAcronyms() -> [String] {
|
class func seismicNetworkAcronyms() -> [String] {
|
||||||
Self.seismicNetworks().map { $0.acronym }
|
Self.seismicNetworks().map { $0.acronym }
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc class func seismicNetworkCountries() -> [String] {
|
class func seismicNetworkCountries() -> [String] {
|
||||||
Self.seismicNetworks().map { $0.country }
|
Self.seismicNetworks().map { $0.country }
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc class func seismic(for acronym: String?) -> EQNSeismicNetwork? {
|
class func seismic(for acronym: String?) -> EQNSeismicNetwork? {
|
||||||
guard let acronym = acronym else { return nil }
|
guard let acronym = acronym else { return nil }
|
||||||
return Self.seismicNetworks().first(where: { $0.acronym == acronym })
|
return Self.seismicNetworks().first(where: { $0.acronym == acronym })
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc class func periodiTemporali() -> [EQNGenericValue] {
|
|
||||||
[
|
|
||||||
EQNGenericValue(value: "10", display: "10 minuti"),
|
|
||||||
EQNGenericValue(value: "60", display: "report_timeframe_one_hour"),
|
|
||||||
EQNGenericValue(value: "120", display: "report_timeframe_two_hours"),
|
|
||||||
EQNGenericValue(value: "360", display: "report_timeframe_six_hours"),
|
|
||||||
EQNGenericValue(value: "720", display: "report_timeframe_twelve_hours"),
|
|
||||||
EQNGenericValue(value: "1440", display: "report_timeframe_one_day")
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the EQNGenericValue for the given value, or the default one
|
|
||||||
/// - Parameter value: Temporal unit value to search
|
|
||||||
/// - Returns: Found value or default
|
|
||||||
@objc class func periodoTemporale(for value: String?) -> EQNGenericValue {
|
|
||||||
if let value = value, let genericValue = Self.periodiTemporali().first(where: { $0.value == value }) {
|
|
||||||
return genericValue
|
|
||||||
}
|
|
||||||
return Self.DefaultPeriodoTemporale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-34
@@ -27,10 +27,110 @@
|
|||||||
/// THE SOFTWARE.
|
/// THE SOFTWARE.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import StoreKit
|
||||||
|
|
||||||
public struct VersioneProProducts {
|
public struct EQNInAppProducts {
|
||||||
|
|
||||||
public enum Identifier {
|
enum Plan: CaseIterable {
|
||||||
|
case monthly
|
||||||
|
case yearly
|
||||||
|
case perpetual
|
||||||
|
|
||||||
|
var localizedTitle: String {
|
||||||
|
switch self {
|
||||||
|
case .monthly: NSLocalizedString("subscription_plan_monthly", comment: "")
|
||||||
|
case .yearly: NSLocalizedString("subscription_plan_yearly", comment: "")
|
||||||
|
case .perpetual: NSLocalizedString("subscription_plan_perpetual", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Category {
|
||||||
|
case pro
|
||||||
|
case top10k
|
||||||
|
case top100k
|
||||||
|
|
||||||
|
var image: UIImage? {
|
||||||
|
switch self {
|
||||||
|
case .pro: nil
|
||||||
|
case .top10k: UIImage(named: "top_10k")
|
||||||
|
case .top100k: UIImage(named: "top_100k")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var localizedTitle: String {
|
||||||
|
switch self {
|
||||||
|
case .pro: return NSLocalizedString("network_pro", comment: "")
|
||||||
|
case .top10k: return "Top 10k"
|
||||||
|
case .top100k: return "Top 100k"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let plan: Plan
|
||||||
|
let category: Category
|
||||||
|
let isDiscounted: Bool
|
||||||
|
let product: SKProduct
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
private init(plan: Plan, category: Category, isDiscounted: Bool, product: SKProduct) {
|
||||||
|
self.plan = plan
|
||||||
|
self.category = category
|
||||||
|
self.isDiscounted = isDiscounted
|
||||||
|
self.product = product
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Accessories
|
||||||
|
|
||||||
|
var isTop10k: Bool {
|
||||||
|
category == .top10k
|
||||||
|
}
|
||||||
|
|
||||||
|
var isTop100k: Bool {
|
||||||
|
category == .top100k
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSubscription: Bool {
|
||||||
|
category != .pro
|
||||||
|
}
|
||||||
|
|
||||||
|
var productIdentifier: String {
|
||||||
|
product.productIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Static
|
||||||
|
|
||||||
|
static func from(product: SKProduct) -> EQNInAppProducts? {
|
||||||
|
switch product.productIdentifier {
|
||||||
|
case Identifier.ProVersionFullPrice:
|
||||||
|
.init(plan: .perpetual, category: .pro, isDiscounted: false, product: product)
|
||||||
|
case Identifier.ProVersionDiscounted:
|
||||||
|
.init(plan: .perpetual, category: .pro, isDiscounted: true, product: product)
|
||||||
|
|
||||||
|
case Identifier.Subscription10kMonthly:
|
||||||
|
.init(plan: .monthly, category: .top10k, isDiscounted: false, product: product)
|
||||||
|
case Identifier.Subscription10kYearly:
|
||||||
|
.init(plan: .yearly, category: .top10k, isDiscounted: false, product: product)
|
||||||
|
case Identifier.Subscription10kYearlyDiscounted:
|
||||||
|
.init(plan: .yearly, category: .top10k, isDiscounted: true, product: product)
|
||||||
|
case Identifier.Subscription10kPerpetual:
|
||||||
|
.init(plan: .perpetual, category: .top10k, isDiscounted: false, product: product)
|
||||||
|
|
||||||
|
case Identifier.Subscription100kMonthly:
|
||||||
|
.init(plan: .monthly, category: .top100k, isDiscounted: false, product: product)
|
||||||
|
case Identifier.Subscription100kYearly:
|
||||||
|
.init(plan: .yearly, category: .top100k, isDiscounted: false, product: product)
|
||||||
|
case Identifier.Subscription100kYearlyDiscounted:
|
||||||
|
.init(plan: .yearly, category: .top100k, isDiscounted: true, product: product)
|
||||||
|
case Identifier.Subscription100kPerpetual:
|
||||||
|
.init(plan: .perpetual, category: .top100k, isDiscounted: false, product: product)
|
||||||
|
default:
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Identifier {
|
||||||
static let ProVersionFullPrice = "com.finazzi.distquake.ProPrezzoPieno"
|
static let ProVersionFullPrice = "com.finazzi.distquake.ProPrezzoPieno"
|
||||||
static let ProVersionDiscounted = "com.finazzi.distquake.VersioneProScontata"
|
static let ProVersionDiscounted = "com.finazzi.distquake.VersioneProScontata"
|
||||||
|
|
||||||
@@ -66,40 +166,9 @@ public struct VersioneProProducts {
|
|||||||
static let identifiersForTop100k: Set<ProductIdentifier> = [
|
static let identifiersForTop100k: Set<ProductIdentifier> = [
|
||||||
Subscription100kMonthly, Subscription100kYearly, Subscription100kYearlyDiscounted, Subscription100kPerpetual
|
Subscription100kMonthly, Subscription100kYearly, Subscription100kYearlyDiscounted, Subscription100kPerpetual
|
||||||
]
|
]
|
||||||
|
|
||||||
static let identifierForSubscriptions: Set<ProductIdentifier> = [
|
|
||||||
Subscription10kMonthly, Subscription100kMonthly,
|
|
||||||
Subscription10kYearly, Subscription10kYearlyDiscounted,
|
|
||||||
Subscription100kYearly, Subscription100kYearlyDiscounted,
|
|
||||||
Subscription10kPerpetual, Subscription100kPerpetual
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func isSubscription(for identifier: String) -> Bool {
|
public static let store = IAPHelper(productIds: EQNInAppProducts.Identifier.identifiers)
|
||||||
Identifier.identifierForSubscriptions.contains(identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func is10kSubscription(for identifier: String) -> Bool {
|
|
||||||
Identifier.identifiersForTop10k.contains(identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func is100kSubscription(for identifier: String) -> Bool {
|
|
||||||
Identifier.identifiersForTop100k.contains(identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func image(for productIdentifier: String) -> UIImage? {
|
|
||||||
if is100kSubscription(for: productIdentifier){
|
|
||||||
return UIImage(named: "top_100k")
|
|
||||||
}
|
|
||||||
|
|
||||||
if is10kSubscription(for: productIdentifier) {
|
|
||||||
return UIImage(named: "top_10k")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
public static let store = IAPHelper(productIds: VersioneProProducts.Identifier.identifiers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceNameForProductIdentifier(_ productIdentifier: String) -> String? {
|
func resourceNameForProductIdentifier(_ productIdentifier: String) -> String? {
|
||||||
@@ -61,8 +61,14 @@
|
|||||||
- (void)scaricaDatiReteSmartphone
|
- (void)scaricaDatiReteSmartphone
|
||||||
{
|
{
|
||||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:EQNServerUrlDownloadSmartphoneNetwork] richiesta:EQNTipoChiamataDownloadDati success:^(id result) {
|
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:EQNServerUrlDownloadSmartphoneNetwork] richiesta:EQNTipoChiamataDownloadDati success:^(id result) {
|
||||||
|
|
||||||
self.rete_smartphone = [[EQNReteSmartphone alloc] initWithInfo:result];
|
// Parsiamo la risposta (assicurandoci che non contenga valori nulli)
|
||||||
|
EQNReteSmartphone *rete = [EQNReteSmartphone fromResponse:result];
|
||||||
|
if (rete != nil) {
|
||||||
|
self.rete_smartphone = rete;
|
||||||
|
} else {
|
||||||
|
NSLog(@"[EQNManager] Impossibile parsare la risposta di DownloadSmartphoneNetwork");
|
||||||
|
}
|
||||||
|
|
||||||
[self performSelectorOnMainThread:@selector(scaricaAreaCheck) withObject:nil waitUntilDone:YES];
|
[self performSelectorOnMainThread:@selector(scaricaAreaCheck) withObject:nil waitUntilDone:YES];
|
||||||
} failure:^(NSError * error) {
|
} failure:^(NSError * error) {
|
||||||
@@ -72,7 +78,12 @@
|
|||||||
|
|
||||||
- (void)scaricaAreaCheck
|
- (void)scaricaAreaCheck
|
||||||
{
|
{
|
||||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?lat=%f&lon=%f", EQNServerUrlDownloadAreaCheck, [EQNUser defaultUser].lastPosition.coordinate.latitude, [EQNUser defaultUser].lastPosition.coordinate.longitude]] richiesta:EQNTipoChiamataAreaCheck success:^(id result) {
|
// Quantizziamo le coordinate, in modo che venga sfruttata la cache lato server
|
||||||
|
CLLocation *lastPosition = [EQNUser defaultUser].lastPosition;
|
||||||
|
double latitude = round(lastPosition.coordinate.latitude * 5.0) / 5.0;
|
||||||
|
double longitude = round(lastPosition.coordinate.longitude * 5.0) / 5.0;
|
||||||
|
|
||||||
|
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?lat=%f&lon=%f", EQNServerUrlDownloadAreaCheck, latitude, longitude]] richiesta:EQNTipoChiamataAreaCheck success:^(id result) {
|
||||||
|
|
||||||
self.area_check = [[EQNAreaCheck alloc] initWithInfo:result];
|
self.area_check = [[EQNAreaCheck alloc] initWithInfo:result];
|
||||||
|
|
||||||
@@ -120,19 +131,17 @@
|
|||||||
|
|
||||||
- (void)scaricaReteSismica
|
- (void)scaricaReteSismica
|
||||||
{
|
{
|
||||||
// L'endpoint per lo scaricamento dei dati prende due parametri: pro per il provider selezionato, mag per la
|
// L'endpoint per lo scaricamento dei dati prende due parametri:
|
||||||
// magnitudo minima. Se l'utente ha selezionato più di un provider, inviamo ALL. Mentre la magnitudo
|
// - `pro` per il provider selezionato,
|
||||||
// deve avere sempre una cifra decimale (es 2.0).
|
// - `mag` per la magnitudo minima.
|
||||||
|
// Dalla v5.8 non esiste più la selezione delle reti, quindi il provider da passare diventa:
|
||||||
NSString *filterProvider = @"";
|
// - `FELT` se filtro selezionato è sismi percepiti
|
||||||
NSArray<NSString *> *networks = [EQNUserData.sharedData seismicNetworksSelected];
|
// - `ALL` in tutti gli altri casi
|
||||||
if (networks.count == 1) {
|
// Per la magnitudo minima, invece, passiamo 0 per i filtri "raggio" e "rilevanti,
|
||||||
filterProvider = [networks firstObject];
|
// altrimenti passiamo 2 per il filtro "mondo"
|
||||||
} else {
|
|
||||||
filterProvider = @"ALL";
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *filterMagnitude = [EQNSeismic shared].magnitudoMinima;
|
NSString *filterProvider = [EQNSeismic shared].filterOption == FilterTypeUserFelt ? @"FELT" : @"ALL";
|
||||||
|
NSString *filterMagnitude = [EQNSeismic shared].filterOption == FilterTypeWorldWide ? @"2.0" : @"0.0";
|
||||||
|
|
||||||
NSString *queryString = [NSString stringWithFormat:@"?pro=%@&mag=%@", filterProvider, filterMagnitude];
|
NSString *queryString = [NSString stringWithFormat:@"?pro=%@&mag=%@", filterProvider, filterMagnitude];
|
||||||
NSString *urlString = [NSString stringWithFormat:EQNServerUrlDownloadRetiSismiche, queryString];
|
NSString *urlString = [NSString stringWithFormat:EQNServerUrlDownloadRetiSismiche, queryString];
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
//
|
||||||
|
// EQNOfficialPushNotification.swift
|
||||||
|
// Earthquake Network
|
||||||
|
//
|
||||||
|
// Created by Andrea Busi on 29/06/24.
|
||||||
|
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Shogun
|
||||||
|
|
||||||
|
|
||||||
|
@objc
|
||||||
|
class EQNOfficialPushNotification: NSObject, Codable {
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case latitude
|
||||||
|
case longitude
|
||||||
|
case magnitude
|
||||||
|
case date
|
||||||
|
}
|
||||||
|
|
||||||
|
private let latitude: Double
|
||||||
|
private let longitude: Double
|
||||||
|
let magnitude: Double
|
||||||
|
let date: Date?
|
||||||
|
|
||||||
|
var coordinate: CLLocation {
|
||||||
|
.init(latitude: latitude, longitude: longitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(
|
||||||
|
latitude: Double,
|
||||||
|
longitude: Double,
|
||||||
|
magnitude: Double,
|
||||||
|
date: Date?
|
||||||
|
) {
|
||||||
|
self.latitude = latitude
|
||||||
|
self.longitude = longitude
|
||||||
|
self.magnitude = magnitude
|
||||||
|
self.date = date
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Class
|
||||||
|
|
||||||
|
/// Remove any saved notification
|
||||||
|
static func removeStored() {
|
||||||
|
UserDefaults.standard.removeObject(forKey: UserDefaults.OfficialAlertPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve stored notification (if any)
|
||||||
|
static func stored() -> EQNOfficialPushNotification? {
|
||||||
|
guard let data = UserDefaults.standard.object(forKey: UserDefaults.OfficialAlertPayload) as? Data else {
|
||||||
|
print("[EQNOfficialPushNotification] No notification saved for key '\(UserDefaults.OfficialAlertPayload)'")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let notification = try? JSONDecoder().decode(EQNOfficialPushNotification.self, from: data) else {
|
||||||
|
print("[EQNOfficialPushNotification] Unable to decode given notification")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert and store a push notification payload.
|
||||||
|
/// Expected payload has the following structure:
|
||||||
|
/// ```
|
||||||
|
/// {
|
||||||
|
/// "title": "Segnalazione da rete sismica",
|
||||||
|
/// "body": "Sisma rilevato a 4km S Valfabbrica (PG)",
|
||||||
|
/// "userInfo": {
|
||||||
|
/// "data" : "2024-06-29 11:21:30",
|
||||||
|
/// ...
|
||||||
|
/// "aps": {
|
||||||
|
/// "alert" : {
|
||||||
|
/// "loc-key" : "Sisma rilevato a",
|
||||||
|
/// "title-loc-key" : "Segnalazione da rete sismica",
|
||||||
|
/// "title" : "Segnalazione da rete sismica",
|
||||||
|
/// "action-loc-key" : "",
|
||||||
|
/// "loc-args" : [
|
||||||
|
/// "6 km S Acate (RG) - M1.9"
|
||||||
|
/// ]
|
||||||
|
/// },
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// - Parameter payload: Notification payload
|
||||||
|
/// - Returns: `true` if save succeed, `false` otherwise
|
||||||
|
@objc(storeNotificationWithPayload:)
|
||||||
|
@discardableResult
|
||||||
|
static func store(payload: [String: Any]) -> Bool {
|
||||||
|
guard let notification = from(payload: payload) else {
|
||||||
|
print("[EQNOfficialPushNotification] Unable to convert received notification")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = try? JSONEncoder().encode(notification) else {
|
||||||
|
print("[EQNOfficialPushNotification] Unable to encode given notification")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDefaults.standard.set(data, forKey: UserDefaults.OfficialAlertPayload)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
private static func from(payload: [String: Any]) -> EQNOfficialPushNotification? {
|
||||||
|
guard let userInfo = payload["userInfo"] as? [String: Any] else {
|
||||||
|
print("[EQNOfficialPushNotification] Missing required info to parse push notification")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let latitude = userInfo.double(forKey: "latitude") ?? 0
|
||||||
|
let longitude = userInfo.double(forKey: "longitude") ?? 0
|
||||||
|
let magnitude = userInfo.double(forKey: "magnitude") ?? 0
|
||||||
|
let dateString = userInfo.string(forKey: "data") ?? ""
|
||||||
|
let date = EQNUtility.getDateFrom(dateString)
|
||||||
|
|
||||||
|
return .init(
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
magnitude: magnitude,
|
||||||
|
date: date
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,21 +57,21 @@ public class EQNPurchaseUtility: NSObject {
|
|||||||
/// Check if user has bought pro app version
|
/// Check if user has bought pro app version
|
||||||
/// Pro version is enabled also if a yearly subscription is enabled
|
/// Pro version is enabled also if a yearly subscription is enabled
|
||||||
@objc public static func isProVersionEnabled() -> Bool {
|
@objc public static func isProVersionEnabled() -> Bool {
|
||||||
VersioneProProducts.Identifier.identifierForProVersion.reduce(false) { (result, identifier) -> Bool in
|
EQNInAppProducts.Identifier.identifierForProVersion.reduce(false) { (result, identifier) -> Bool in
|
||||||
return result || UserDefaults.standard.bool(forKey: identifier)
|
return result || UserDefaults.standard.bool(forKey: identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if user has bought Top 10k subscription
|
/// Check if user has bought Top 10k subscription
|
||||||
@objc public static func isTop10kEnabled() -> Bool {
|
@objc public static func isTop10kEnabled() -> Bool {
|
||||||
VersioneProProducts.Identifier.identifiersForTop10k.reduce(false) { (result, identifier) -> Bool in
|
EQNInAppProducts.Identifier.identifiersForTop10k.reduce(false) { (result, identifier) -> Bool in
|
||||||
return result || UserDefaults.standard.bool(forKey: identifier)
|
return result || UserDefaults.standard.bool(forKey: identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if user has bought Top 100k subscription
|
/// Check if user has bought Top 100k subscription
|
||||||
@objc public static func isTop100kEnabled() -> Bool {
|
@objc public static func isTop100kEnabled() -> Bool {
|
||||||
VersioneProProducts.Identifier.identifiersForTop100k.reduce(false) { (result, identifier) -> Bool in
|
EQNInAppProducts.Identifier.identifiersForTop100k.reduce(false) { (result, identifier) -> Bool in
|
||||||
return result || UserDefaults.standard.bool(forKey: identifier)
|
return result || UserDefaults.standard.bool(forKey: identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ public class EQNPurchaseUtility: NSObject {
|
|||||||
/// Remove saved in-app purchases flags.
|
/// Remove saved in-app purchases flags.
|
||||||
/// Used only during development
|
/// Used only during development
|
||||||
@objc public static func resetInAppPurchases() {
|
@objc public static func resetInAppPurchases() {
|
||||||
VersioneProProducts.Identifier.identifiers.forEach { (identifier) in
|
EQNInAppProducts.Identifier.identifiers.forEach { (identifier) in
|
||||||
UserDefaults.standard.removeObject(forKey: identifier)
|
UserDefaults.standard.removeObject(forKey: identifier)
|
||||||
}
|
}
|
||||||
NotificationCenter.default.post(name: .EQNInAppPurchaseDidComplete, object: nil)
|
NotificationCenter.default.post(name: .EQNInAppPurchaseDidComplete, object: nil)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
|||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case type
|
case type
|
||||||
case intensity
|
case intensity
|
||||||
|
case magnitude
|
||||||
case latitude
|
case latitude
|
||||||
case longitude
|
case longitude
|
||||||
case counter
|
case counter
|
||||||
@@ -36,6 +37,7 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
|||||||
let type: String
|
let type: String
|
||||||
/// Earthquake intensity
|
/// Earthquake intensity
|
||||||
let intensity: Int
|
let intensity: Int
|
||||||
|
let magnitude: Int
|
||||||
/// Earthquake coordinate
|
/// Earthquake coordinate
|
||||||
let latitude: Double
|
let latitude: Double
|
||||||
let longitude: Double
|
let longitude: Double
|
||||||
@@ -63,6 +65,7 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
|||||||
init(
|
init(
|
||||||
type: String,
|
type: String,
|
||||||
intensity: Int,
|
intensity: Int,
|
||||||
|
magnitude: Int,
|
||||||
latitude: Double,
|
latitude: Double,
|
||||||
longitude: Double,
|
longitude: Double,
|
||||||
counter: Int,
|
counter: Int,
|
||||||
@@ -76,6 +79,7 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
|||||||
) {
|
) {
|
||||||
self.type = type
|
self.type = type
|
||||||
self.intensity = intensity
|
self.intensity = intensity
|
||||||
|
self.magnitude = magnitude
|
||||||
self.latitude = latitude
|
self.latitude = latitude
|
||||||
self.longitude = longitude
|
self.longitude = longitude
|
||||||
self.counter = counter
|
self.counter = counter
|
||||||
@@ -201,6 +205,27 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var title: String = ""
|
||||||
|
if let titleKey = alert["loc-key"] as? String, let args = alert["loc-args"] as? [String], let arg = args.first {
|
||||||
|
title = String(format: NSLocalizedString(titleKey, comment: ""), arg)
|
||||||
|
}
|
||||||
|
let displayTitle = payload.string(forKey: "title", orDefault: "")
|
||||||
|
let displayBody = payload.string(forKey: "body", orDefault: "")
|
||||||
|
|
||||||
|
return from(
|
||||||
|
userInfo: userInfo,
|
||||||
|
title: title,
|
||||||
|
displayTitle: displayTitle,
|
||||||
|
displayBody: displayBody
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func from(
|
||||||
|
userInfo: [AnyHashable: Any],
|
||||||
|
title: String,
|
||||||
|
displayTitle: String,
|
||||||
|
displayBody: String
|
||||||
|
) -> EQNRealtimePushNotification? {
|
||||||
guard let latitude = userInfo.double(forKey: "latitude"),
|
guard let latitude = userInfo.double(forKey: "latitude"),
|
||||||
let longitude = userInfo.double(forKey: "longitude") else {
|
let longitude = userInfo.double(forKey: "longitude") else {
|
||||||
print("[EQNRealtimePushNotification] Unable to get coordinate from push notification")
|
print("[EQNRealtimePushNotification] Unable to get coordinate from push notification")
|
||||||
@@ -209,6 +234,7 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
|||||||
|
|
||||||
let type = userInfo.string(forKey: "type", orDefault: "")
|
let type = userInfo.string(forKey: "type", orDefault: "")
|
||||||
let intensity = userInfo.integer(forKey: "intensity", orDefault: 0)
|
let intensity = userInfo.integer(forKey: "intensity", orDefault: 0)
|
||||||
|
let magnitude = userInfo.integer(forKey: "magnitude", orDefault: 0)
|
||||||
|
|
||||||
let counter = userInfo.integer(forKey: "counter", orDefault: 0)
|
let counter = userInfo.integer(forKey: "counter", orDefault: 0)
|
||||||
var dateTime: Date?
|
var dateTime: Date?
|
||||||
@@ -222,16 +248,10 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
|||||||
}
|
}
|
||||||
let peak = userInfo.double(forKey: "peak")
|
let peak = userInfo.double(forKey: "peak")
|
||||||
|
|
||||||
var title: String = ""
|
|
||||||
if let titleKey = alert["loc-key"] as? String, let args = alert["loc-args"] as? [String], let arg = args.first {
|
|
||||||
title = String(format: NSLocalizedString(titleKey, comment: ""), arg)
|
|
||||||
}
|
|
||||||
let displayTitle = payload.string(forKey: "title", orDefault: "")
|
|
||||||
let displayBody = payload.string(forKey: "body", orDefault: "")
|
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
type: type,
|
type: type,
|
||||||
intensity: intensity,
|
intensity: intensity,
|
||||||
|
magnitude: magnitude,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
counter: counter,
|
counter: counter,
|
||||||
|
|||||||
@@ -21,13 +21,27 @@ class EQNReteSmartphone: NSObject {
|
|||||||
let top10kAvailable: Int
|
let top10kAvailable: Int
|
||||||
let top100kAvailable: Int
|
let top100kAvailable: Int
|
||||||
|
|
||||||
|
// MARK: - Class
|
||||||
|
|
||||||
|
// Sometimes the response returns a broken response, with all values to null.
|
||||||
|
// In order to avoid crashes due to ObjC-Swift bridging, we need to take a generic nullable object,
|
||||||
|
// and then cast to the expected type (a dictionary).
|
||||||
|
@objc static func from(response: Any?) -> EQNReteSmartphone? {
|
||||||
|
if let info = response as? [[String: Any]?] {
|
||||||
|
return .init(info: info)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
@objc init(info: [[String: Any]]) {
|
private init(info: [[String: Any]?]) {
|
||||||
// merge array in a single dictionary
|
// merge array in a single dictionary
|
||||||
let allValues = info.reduce([:]) { (result, dictionary) -> [String: Any] in
|
let allValues = info
|
||||||
return result.merging(dictionary, uniquingKeysWith: { (_, new) in new })
|
.compactMap { $0 }
|
||||||
}
|
.reduce([:]) { (result, dictionary) -> [String: Any] in
|
||||||
|
return result.merging(dictionary, uniquingKeysWith: { (_, new) in new })
|
||||||
|
}
|
||||||
|
|
||||||
self.counterLastDayAlerts = allValues.integer(forKey: "eq", orDefault: 0)
|
self.counterLastDayAlerts = allValues.integer(forKey: "eq", orDefault: 0)
|
||||||
self.counterTotalAlerts = allValues.integer(forKey: "eq_p", orDefault: 0)
|
self.counterTotalAlerts = allValues.integer(forKey: "eq_p", orDefault: 0)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user