Compare commits

...

96 Commits

Author SHA1 Message Date
Andrea Busi f85c60fdda release: Increase version for release 2025-07-24 22:07:38 +02:00
Andrea Busi 5f2a083789 fix: Update Twitter to X 2025-07-24 22:07:19 +02:00
Andrea Busi 9e54f74847 release: Increase version for release 2025-07-24 20:44:44 +02:00
Andrea Busi b6f9232f56 fix: Set color based on shake intensity 2025-07-24 20:44:11 +02:00
Andrea Busi dee14dea0f release: Increase version for release 2025-07-24 20:43:57 +02:00
Andrea Busi db0bde2f59 refactor: Remove no longer needed checks for iOS < 15 2025-07-24 16:46:29 +02:00
Andrea Busi 79d0d27ec5 feat: Rewrite NotificationContent in Swift and add wave animation
Resolves: https://github.com/futurainnovation/eqn.ios/issues/4
2025-07-24 16:44:55 +02:00
Andrea Busi 68012ec406 refactor: Improve notification content 2025-07-24 11:56:49 +02:00
Andrea Busi 59feb7699b release: Increase version for release 2025-07-18 17:08:19 +02:00
Andrea Busi 388f4e8b89 feat: Add new felt value as provider parameter
Resolves: https://github.com/futurainnovation/eqn.ios/issues/1
2025-07-18 17:04:29 +02:00
Andrea Busi ca3c9ebd83 refactor: Always use updateFilter method when updating filter values 2025-07-18 17:03:34 +02:00
Andrea Busi f23c19bce7 feat: Improve UI in notification content
Resolves: https://github.com/futurainnovation/eqn.ios/issues/4
2025-07-18 17:02:52 +02:00
Andrea Busi 276fa2032a release: Increase version for release 2025-07-18 12:44:30 +02:00
Andrea Busi 09f0d4d4d8 dependency: Bump Firebase 2025-07-18 12:44:22 +02:00
Andrea Busi 25f061ad5a release: Update changelog 2025-07-18 12:40:59 +02:00
Andrea Busi b9d9f7579c feat: Add filters recap view in seismic networks
Resolves: https://github.com/futurainnovation/eqn.ios/issues/2
2025-07-18 12:40:59 +02:00
Andrea Busi 39f5ff0249 fix: Use extensions from Shogun 2025-07-18 12:28:22 +02:00
Andrea Busi af68d70be5 feat: Increase minimum target to iOS 15 2025-07-18 12:28:22 +02:00
Andrea Busi dab999a78d dependency: Bump Shogun to v2.0 2025-07-18 12:28:22 +02:00
Andrea Busi f5ede5c26d refactor: Use Roman numerals in shakemap labels
Resolves: https://github.com/futurainnovation/eqn.ios/issues/3
2025-07-17 15:52:09 +02:00
Andrea Busi 6d4c1eb979 fix: Check array bounds to avoid crash 2025-05-15 10:31:41 +02:00
Andrea Busi 9bf6b75dac release: Increase version for release 2025-05-02 10:31:57 +02:00
Andrea Busi 69b83e9944 feat: Add settings to disable alert sound for mild quakes
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/89
2025-05-02 10:31:57 +02:00
Andrea Busi 5061e36a45 refactor: Reorganize some code 2025-05-02 10:31:57 +02:00
Andrea Busi 8919f3c08f refactor: Remove some unused @objc 2025-05-02 10:31:57 +02:00
Andrea Busi 9cf9ef8a64 feat: Use cache endpoint and round values to use server cache
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/90
2025-05-02 10:31:57 +02:00
Andrea Busi 9ee3a478f0 dependency: Update Firebase 2025-05-02 10:31:57 +02:00
Andrea Busi 8744595b56 release: Increase version for release 2025-04-25 22:32:54 +02:00
Andrea Busi fa05d6b5c4 fix: Rework init to avoid crash when server returns null values 2025-04-25 22:31:44 +02:00
Andrea Busi 471ccc9e4a release: Increase version for release 2025-04-24 12:49:25 +02:00
Andrea Busi 55de6f5ba0 refactor: Use project to set version and build number 2025-04-24 12:49:08 +02:00
Andrea Busi b12a9cc476 fix: Solve wrong sort in subscriptions 2025-04-24 12:46:53 +02:00
Andrea Busi a2f740b0a8 release: Increase version for release 2025-03-07 17:57:54 +01:00
Andrea Busi 9cf93e652b fix: Reset cell informations 2025-03-07 15:38:51 +01:00
Andrea Busi 2d23056ba8 release: Increase version for release 2025-03-07 14:10:47 +01:00
Andrea Busi cb6ecca774 refactor: Hide description label when taking screenshot in intensity map 2025-03-07 14:10:47 +01:00
Andrea Busi 96286a49f6 feat: Upgrade Xcode project version 2025-03-07 14:00:28 +01:00
Andrea Busi 481e8a28c0 fix: Solve crash with a single shakemape curve 2025-03-07 14:00:16 +01:00
Andrea Busi 286a4062f5 release: Increase version for release 2025-03-07 11:16:35 +01:00
Andrea Busi 01a8ad7419 fix: Solve wrong calculation in scroll indicator 2025-03-07 11:16:07 +01:00
Andrea Busi 6e97e9bd2c release: Increase version for release 2025-03-07 09:56:03 +01:00
Andrea Busi af6e94efcb fix: Solve non working intensity map in minimal cell 2025-03-07 09:45:45 +01:00
Andrea Busi 5387758449 fix: Select yearly as default subscription 2025-03-07 09:42:26 +01:00
Andrea Busi 054603b42d release: Increase version for release 2025-03-06 17:57:41 +01:00
Andrea Busi caf0e3b7cc feat: Add new minimal card in seismics list
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/87
2025-03-06 17:57:07 +01:00
Andrea Busi 4c35c38cc5 refactor: Move card informations to AppPreferences 2025-03-06 15:49:26 +01:00
Andrea Busi 521254c8c1 refactor: Remove unused constant 2025-03-06 13:01:20 +01:00
Andrea Busi 78a1710584 refactor: Replace deprecated methods 2025-03-06 10:59:36 +01:00
Andrea Busi b2a54a544c feat: Update layout in Seismic List section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/86
2025-03-06 10:45:02 +01:00
Andrea Busi 0f5ad24744 feat: Update layout in Subscriptions section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/85
2025-03-06 10:19:14 +01:00
Andrea Busi 0296cd50cd feat: Update layout in Reports section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/84
2025-03-06 10:18:44 +01:00
Andrea Busi 7551988b4e feat: Update layout in Alerts section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/83
2025-03-06 10:17:42 +01:00
Andrea Busi 5edcaaad99 feat: Change layout for base container card (center title) 2025-03-06 10:17:08 +01:00
Andrea Busi b12f83680a release: Increase version for release 2025-03-06 09:02:38 +01:00
Andrea Busi ee827c41ae feat: Show callout for shakemap annotations 2025-02-28 16:49:51 +01:00
Andrea Busi d0d06394f0 feat: Save selected map pin style 2025-02-28 16:49:36 +01:00
Andrea Busi b933b900ed release: Increase version for release 2025-02-28 12:54:40 +01:00
Andrea Busi 0e7de44332 refactor: Move source position in seismic card 2025-02-28 12:54:40 +01:00
Andrea Busi 547bb794f0 feat: Add intensity map 2025-02-28 12:54:40 +01:00
Andrea Busi 9b1f1f12d2 feat: Add new APIService to handle network requests 2025-02-27 16:15:37 +01:00
Andrea Busi 7fc324367d feat: Add Log class 2025-02-27 16:15:37 +01:00
Andrea Busi 3cb712f709 fix: Set 2 as min reported user for felt filter 2025-02-25 16:40:41 +01:00
Andrea Busi 993e2924c7 release: Increase version for release 2025-02-21 16:28:48 +01:00
Andrea Busi a167c989cc feat: Add filter for "user felt"
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/81
2025-02-21 15:13:00 +01:00
Andrea Busi 1b50f4fd17 fix: Set locale in date formatters, to solve parsing issues with 0-12 hours 2025-02-20 15:16:59 +01:00
Andrea Busi 0003b4607c refactor: Make filters bigger 2025-02-20 15:16:42 +01:00
Andrea Busi 85c9f333ce fix: Hide scroll bar in seismics table 2025-02-12 09:06:25 +01:00
Andrea Busi 217cbfd4e3 release: Increase version for release 2025-02-11 15:51:15 +01:00
Andrea Busi 5d8de1fb36 feat: Improve scroll indicator with tons of rectangles 2025-02-11 15:50:20 +01:00
Andrea Busi f23bb78ceb fix: Reset center position when data changes 2025-02-11 15:50:20 +01:00
Andrea Busi 0d91954614 feat: Upgrade to Xcode recommended settings 2025-02-11 14:58:35 +01:00
Andrea Busi 49f5fa91fe release: Increase version for release 2025-02-06 14:10:27 +01:00
Andrea Busi 68e560768b refactor: Replace renamed APIs for GoogleAds SDK 2025-02-06 12:00:26 +01:00
Andrea Busi 3e9c319b50 dependency: Bump GoogleAds SDK to v12 2025-02-06 12:00:26 +01:00
Andrea Busi d35e0e1b4a refactor: Replace deprecated API for Facebook SDK 2025-02-06 12:00:26 +01:00
Andrea Busi 6ede137ef7 dependency: Bump Facebook SDK to v18 2025-02-06 12:00:19 +01:00
Andrea Busi c94195d48e dependency: Update repo url for Shogun 2025-02-06 11:53:53 +01:00
Andrea Busi 28919d7b72 release: Increase version for release 2025-02-06 11:53:44 +01:00
Andrea Busi a239534b91 feat: Add scroll indicator view in seismic list 2025-02-06 11:33:22 +01:00
Andrea Busi 226342f36c dependency: Bump Firebase 2025-02-06 11:33:22 +01:00
Andrea Busi ca6afbec5f refactor: Delete workspace file, no longer used 2025-01-31 14:38:26 +01:00
Andrea Busi 465d3e8013 release: Increase version for release 2024-10-17 18:48:19 +02:00
Andrea Busi a7e88b43f5 fix: Add LSMinimumSystemVersion for macOS compatibility 2024-10-17 18:47:37 +02:00
Andrea Busi 57ef877846 release: Increase version for release 2024-10-17 17:35:57 +02:00
Andrea Busi c44d97b9fb feat: Disable critical alerts setting if permission is not granted 2024-10-17 17:22:41 +02:00
Andrea Busi fd4ed7f66f fix: Load existing Firebase Token 2024-10-17 16:05:17 +02:00
Andrea Busi ef5db97854 refactor: Store first app start using a user default 2024-10-17 16:04:56 +02:00
Andrea Busi ce0e17a0c5 refactor: Remove old migrations 2024-10-17 15:21:18 +02:00
Andrea Busi 2a46f1d2d6 release: Increase version for release 2024-10-17 09:21:44 +02:00
Andrea Busi 93871f0358 chore: Add IDE file 2024-10-17 09:19:10 +02:00
Andrea Busi 3e8fe0680d fix: Solve missing critical alert permission request 2024-10-17 09:18:52 +02:00
Andrea Busi 6be5f72360 release: Increase version for release 2024-07-16 11:45:52 +02:00
Andrea Busi ccd1b9de59 dependency: Update Firebase 2024-07-16 11:45:43 +02:00
Andrea Busi 5737eb5b02 feat: Sort user subscriptions (top10k first) 2024-07-16 11:43:58 +02:00
Andrea Busi c549bb6ea5 fix: Solve wrong localized AR string 2024-07-16 09:23:39 +02:00
Andrea Busi ff80905033 fix: Solve crash due to wrong string format 2024-07-16 09:09:57 +02:00
73 changed files with 2796 additions and 959 deletions
+23
View File
@@ -1,5 +1,28 @@
# 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 ## Versione 5.8
- Modifica algoritmo filtro lista (issue #70) - Modifica algoritmo filtro lista (issue #70)
- Modifica impostazioni "Notifiche da reti sismiche" (issue #66) - Modifica impostazioni "Notifiche da reti sismiche" (issue #66)
@@ -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,35 +100,6 @@ 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
@@ -159,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()
@@ -167,6 +175,20 @@ class NotificationService: UNNotificationServiceExtension {
} }
} }
private func isMildQuake(
magnitude: Double,
distance: Double
) -> Bool {
var intensity_at_location: Double = 0
if distance > 0 {
let R: Double = 6371
let eq_depth: Double = 10.0
let hyp_distance = sqrt(pow(eq_depth, 2) + 4 * R * (R - eq_depth) * pow(sin(distance / (2 * R)), 2))
intensity_at_location = -2.15 * log10(hyp_distance) + 1.0 * magnitude + 2.31
}
return intensity_at_location < 3
}
// MARK: - Helpers // MARK: - Helpers
private func manualIconName(for provider: String, color: String) -> String { private func manualIconName(for provider: String, color: String) -> String {
@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 54; objectVersion = 77;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@@ -13,12 +13,18 @@
650247122A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650247112A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift */; }; 650247122A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650247112A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift */; };
650247152A618D7F001AC512 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650247142A618D7F001AC512 /* Foundation+Extensions.swift */; }; 650247152A618D7F001AC512 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650247142A618D7F001AC512 /* Foundation+Extensions.swift */; };
650B23AB2632CCD3007AE752 /* UIView+EQNExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650B23AA2632CCD3007AE752 /* UIView+EQNExtensions.swift */; }; 650B23AB2632CCD3007AE752 /* UIView+EQNExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650B23AA2632CCD3007AE752 /* UIView+EQNExtensions.swift */; };
6514FF6A2D720C3F000A7BD0 /* MapPinStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */; };
6514FF6C2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */; };
6514FF6D2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */; };
65172F532C25C496006D2A5C /* EQNSeismicAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */; }; 65172F532C25C496006D2A5C /* EQNSeismicAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */; };
651901B925F5358700CAFF20 /* EQNMapAnnotationSeismic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */; }; 651901B925F5358700CAFF20 /* EQNMapAnnotationSeismic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */; };
652247242D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */; };
652247262D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */; };
6525A82625E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */; }; 6525A82625E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */; };
652A3C6B2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652A3C6A2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift */; }; 652A3C6B2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652A3C6A2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift */; };
65355FFF25F38D3300BB57D2 /* SegnalazioniMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65355FFE25F38D3300BB57D2 /* SegnalazioniMapViewController.swift */; }; 65355FFF25F38D3300BB57D2 /* SegnalazioniMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65355FFE25F38D3300BB57D2 /* SegnalazioniMapViewController.swift */; };
653604E9262348FA00B2B651 /* EQNBaseMapFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653604E8262348FA00B2B651 /* EQNBaseMapFilter.swift */; }; 653604E9262348FA00B2B651 /* EQNBaseMapFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653604E8262348FA00B2B651 /* EQNBaseMapFilter.swift */; };
653C5C2C2E295C5A00E25528 /* SeismicNetworkFilterRecapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653C5C2B2E295C5A00E25528 /* SeismicNetworkFilterRecapView.swift */; };
653C67E225F3CC2E00FE52AC /* EQNCustomAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653C67E125F3CC2E00FE52AC /* EQNCustomAnnotationView.swift */; }; 653C67E225F3CC2E00FE52AC /* EQNCustomAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653C67E125F3CC2E00FE52AC /* EQNCustomAnnotationView.swift */; };
653C67E625F3CC8400FE52AC /* EQNCustomAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653C67E125F3CC2E00FE52AC /* EQNCustomAnnotationView.swift */; }; 653C67E625F3CC8400FE52AC /* EQNCustomAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653C67E125F3CC2E00FE52AC /* EQNCustomAnnotationView.swift */; };
653C67FC25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653C67FB25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift */; }; 653C67FC25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653C67FB25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift */; };
@@ -36,11 +42,20 @@
65583A05261B83BE00ECA9F9 /* UIKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65583A04261B83BE00ECA9F9 /* UIKit+Extensions.swift */; }; 65583A05261B83BE00ECA9F9 /* UIKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65583A04261B83BE00ECA9F9 /* UIKit+Extensions.swift */; };
6562C80725FFA6B100C85273 /* SeismicNetworkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.swift */; }; 6562C80725FFA6B100C85273 /* SeismicNetworkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.swift */; };
6563DAA42AAF515F0072D309 /* BackgroundTaskIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6563DAA32AAF515F0072D309 /* BackgroundTaskIdentifiable.swift */; }; 6563DAA42AAF515F0072D309 /* BackgroundTaskIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6563DAA32AAF515F0072D309 /* BackgroundTaskIdentifiable.swift */; };
6568C2D42E2A930500402F16 /* EQNBlurredCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6586971025F44C26009C0182 /* EQNBlurredCloseButton.swift */; };
656D138F2C2225560094F597 /* SubscriptionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656D138E2C2225560094F597 /* SubscriptionDetailsViewController.swift */; }; 656D138F2C2225560094F597 /* SubscriptionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656D138E2C2225560094F597 /* SubscriptionDetailsViewController.swift */; };
656D13912C22371F0094F597 /* SubscriptionDetailsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656D13902C22371F0094F597 /* SubscriptionDetailsTableViewCell.swift */; }; 656D13912C22371F0094F597 /* SubscriptionDetailsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656D13902C22371F0094F597 /* SubscriptionDetailsTableViewCell.swift */; };
656E02162C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656E02152C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift */; }; 656E02162C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656E02152C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift */; };
656EB9362A15FD16009DADF3 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 656EB9382A15FD16009DADF3 /* Localizable.stringsdict */; }; 656EB9362A15FD16009DADF3 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 656EB9382A15FD16009DADF3 /* Localizable.stringsdict */; };
656EB9412A16288A009DADF3 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 656EB9382A15FD16009DADF3 /* Localizable.stringsdict */; }; 656EB9412A16288A009DADF3 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 656EB9382A15FD16009DADF3 /* Localizable.stringsdict */; };
657415E02D70B6F800F54890 /* EQNMapAnnotationShakemap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657415DE2D70B6F700F54890 /* EQNMapAnnotationShakemap.swift */; };
657747FD2D4D12A200213830 /* SeismicNetworkData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657747FC2D4D12A200213830 /* SeismicNetworkData.swift */; };
657747FF2D4D2BA200213830 /* SeismicNetworkScrollIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657747FE2D4D2BA100213830 /* SeismicNetworkScrollIndicatorView.swift */; };
657CCF082E323AF700E91715 /* NotificationContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657CCF072E323AF700E91715 /* NotificationContentViewController.swift */; };
657CCF092E323BE500E91715 /* EQNRealtimePushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */; };
657CCF0A2E323DC600E91715 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650247142A618D7F001AC512 /* Foundation+Extensions.swift */; };
657CCF0C2E32711D00E91715 /* MapSeismicWaveAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657CCF0B2E32711D00E91715 /* MapSeismicWaveAnimator.swift */; };
657CCF0D2E32711D00E91715 /* MapSeismicWaveAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657CCF0B2E32711D00E91715 /* MapSeismicWaveAnimator.swift */; };
6586971125F44C26009C0182 /* EQNBlurredCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6586971025F44C26009C0182 /* EQNBlurredCloseButton.swift */; }; 6586971125F44C26009C0182 /* EQNBlurredCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6586971025F44C26009C0182 /* EQNBlurredCloseButton.swift */; };
658BAB7B25FE67930015C454 /* EQNBaseMapRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658BAB7A25FE67930015C454 /* EQNBaseMapRepresentable.swift */; }; 658BAB7B25FE67930015C454 /* EQNBaseMapRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658BAB7A25FE67930015C454 /* EQNBaseMapRepresentable.swift */; };
658BC0292859A456009EECAA /* RealtimeAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */; }; 658BC0292859A456009EECAA /* RealtimeAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */; };
@@ -73,6 +88,10 @@
65E6AC702C2DB3B60073F8FE /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 65E6AC6F2C2DB3B60073F8FE /* FirebaseMessaging */; }; 65E6AC702C2DB3B60073F8FE /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 65E6AC6F2C2DB3B60073F8FE /* FirebaseMessaging */; };
65EA58802A60269C0038EE9D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8C10B0BD2281FE7F00125C9F /* Localizable.strings */; }; 65EA58802A60269C0038EE9D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8C10B0BD2281FE7F00125C9F /* Localizable.strings */; };
65EA58822A60360D0038EE9D /* EQNRealtimePushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */; }; 65EA58822A60360D0038EE9D /* EQNRealtimePushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */; };
65F9A6082D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9A6072D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift */; };
65F9A60A2D706ADC008A12B5 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9A6092D706ADC008A12B5 /* APIService.swift */; };
65F9A60C2D70781A008A12B5 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9A60B2D70781A008A12B5 /* Log.swift */; };
65F9A60E2D707BFE008A12B5 /* EQNShakemap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9A60D2D707BFE008A12B5 /* EQNShakemap.swift */; };
65F9B49C2A8CA22800F13260 /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49B2A8CA22800F13260 /* BackgroundTaskManager.swift */; }; 65F9B49C2A8CA22800F13260 /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49B2A8CA22800F13260 /* BackgroundTaskManager.swift */; };
65F9B49E2A8CA2AC00F13260 /* EQNBackgroundPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49D2A8CA2AC00F13260 /* EQNBackgroundPosition.swift */; }; 65F9B49E2A8CA2AC00F13260 /* EQNBackgroundPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49D2A8CA2AC00F13260 /* EQNBackgroundPosition.swift */; };
65F9B4A02A8CC58200F13260 /* UpdateUserLocationTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49F2A8CC58200F13260 /* UpdateUserLocationTask.swift */; }; 65F9B4A02A8CC58200F13260 /* UpdateUserLocationTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49F2A8CC58200F13260 /* UpdateUserLocationTask.swift */; };
@@ -212,7 +231,6 @@
8CF05B57218C93BA0055012B /* EQNUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CF05B56218C93BA0055012B /* EQNUtility.m */; }; 8CF05B57218C93BA0055012B /* EQNUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CF05B56218C93BA0055012B /* EQNUtility.m */; };
8CF12CD321DE49B600613AC5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CF12CD221DE49B600613AC5 /* UserNotifications.framework */; }; 8CF12CD321DE49B600613AC5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CF12CD221DE49B600613AC5 /* UserNotifications.framework */; };
8CF12CD521DE49B600613AC5 /* UserNotificationsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CF12CD421DE49B600613AC5 /* UserNotificationsUI.framework */; }; 8CF12CD521DE49B600613AC5 /* UserNotificationsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CF12CD421DE49B600613AC5 /* UserNotificationsUI.framework */; };
8CF12CD921DE49B600613AC5 /* NotificationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CF12CD821DE49B600613AC5 /* NotificationViewController.m */; };
8CF12CDC21DE49B600613AC5 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8CF12CDA21DE49B600613AC5 /* MainInterface.storyboard */; }; 8CF12CDC21DE49B600613AC5 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8CF12CDA21DE49B600613AC5 /* MainInterface.storyboard */; };
8CF12CE021DE49B600613AC5 /* EQNNotificationContent.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 8CF12CD121DE49B600613AC5 /* EQNNotificationContent.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 8CF12CE021DE49B600613AC5 /* EQNNotificationContent.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 8CF12CD121DE49B600613AC5 /* EQNNotificationContent.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
8CF4F4D8216D3A110057110B /* EQNAreaCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CF4F4D7216D3A110057110B /* EQNAreaCheck.m */; }; 8CF4F4D8216D3A110057110B /* EQNAreaCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CF4F4D7216D3A110057110B /* EQNAreaCheck.m */; };
@@ -307,13 +325,17 @@
650247112A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgroundPositionDebugHelper.swift; sourceTree = "<group>"; }; 650247112A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgroundPositionDebugHelper.swift; sourceTree = "<group>"; };
650247142A618D7F001AC512 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = "<group>"; }; 650247142A618D7F001AC512 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = "<group>"; };
650B23AA2632CCD3007AE752 /* UIView+EQNExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+EQNExtensions.swift"; sourceTree = "<group>"; }; 650B23AA2632CCD3007AE752 /* UIView+EQNExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+EQNExtensions.swift"; sourceTree = "<group>"; };
6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapPinStyle.swift; sourceTree = "<group>"; };
65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNSeismicAnnotationView.swift; sourceTree = "<group>"; }; 65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNSeismicAnnotationView.swift; sourceTree = "<group>"; };
651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNMapAnnotationSeismic.swift; sourceTree = "<group>"; }; 651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNMapAnnotationSeismic.swift; sourceTree = "<group>"; };
652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkMinimalTableViewCell.swift; sourceTree = "<group>"; };
652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkBaseTableViewCell.swift; sourceTree = "<group>"; };
6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkAdvertiseTableViewCell.swift; sourceTree = "<group>"; }; 6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkAdvertiseTableViewCell.swift; sourceTree = "<group>"; };
652A3C6A2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgrounPositionDebugViewController.swift; sourceTree = "<group>"; }; 652A3C6A2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgrounPositionDebugViewController.swift; sourceTree = "<group>"; };
65355FFE25F38D3300BB57D2 /* SegnalazioniMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegnalazioniMapViewController.swift; sourceTree = "<group>"; }; 65355FFE25F38D3300BB57D2 /* SegnalazioniMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegnalazioniMapViewController.swift; sourceTree = "<group>"; };
653604E8262348FA00B2B651 /* EQNBaseMapFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBaseMapFilter.swift; sourceTree = "<group>"; }; 653604E8262348FA00B2B651 /* EQNBaseMapFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBaseMapFilter.swift; sourceTree = "<group>"; };
653B791F2934B6DA00E8FFFB /* EQNNotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = EQNNotificationService.entitlements; sourceTree = "<group>"; }; 653B791F2934B6DA00E8FFFB /* EQNNotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = EQNNotificationService.entitlements; sourceTree = "<group>"; };
653C5C2B2E295C5A00E25528 /* SeismicNetworkFilterRecapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkFilterRecapView.swift; sourceTree = "<group>"; };
653C67E125F3CC2E00FE52AC /* EQNCustomAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNCustomAnnotationView.swift; sourceTree = "<group>"; }; 653C67E125F3CC2E00FE52AC /* EQNCustomAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNCustomAnnotationView.swift; sourceTree = "<group>"; };
653C67FB25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNMapAnnotationUserReport.swift; sourceTree = "<group>"; }; 653C67FB25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNMapAnnotationUserReport.swift; sourceTree = "<group>"; };
653C680325F3DF8A00FE52AC /* EQNBaseMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBaseMapViewController.swift; sourceTree = "<group>"; }; 653C680325F3DF8A00FE52AC /* EQNBaseMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBaseMapViewController.swift; sourceTree = "<group>"; };
@@ -338,6 +360,11 @@
656EB93E2A15FD1E009DADF3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 656EB93E2A15FD1E009DADF3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
656EB93F2A15FD1F009DADF3 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 656EB93F2A15FD1F009DADF3 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
656EB9402A15FD1F009DADF3 /* tr-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "tr-TR"; path = "tr-TR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; }; 656EB9402A15FD1F009DADF3 /* tr-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "tr-TR"; path = "tr-TR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
657415DE2D70B6F700F54890 /* EQNMapAnnotationShakemap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNMapAnnotationShakemap.swift; sourceTree = "<group>"; };
657747FC2D4D12A200213830 /* SeismicNetworkData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkData.swift; sourceTree = "<group>"; };
657747FE2D4D2BA100213830 /* SeismicNetworkScrollIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkScrollIndicatorView.swift; sourceTree = "<group>"; };
657CCF072E323AF700E91715 /* NotificationContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentViewController.swift; sourceTree = "<group>"; };
657CCF0B2E32711D00E91715 /* MapSeismicWaveAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSeismicWaveAnimator.swift; sourceTree = "<group>"; };
6586971025F44C26009C0182 /* EQNBlurredCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBlurredCloseButton.swift; sourceTree = "<group>"; }; 6586971025F44C26009C0182 /* EQNBlurredCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBlurredCloseButton.swift; sourceTree = "<group>"; };
658BAB7A25FE67930015C454 /* EQNBaseMapRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBaseMapRepresentable.swift; sourceTree = "<group>"; }; 658BAB7A25FE67930015C454 /* EQNBaseMapRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBaseMapRepresentable.swift; sourceTree = "<group>"; };
658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealtimeAlertViewController.swift; sourceTree = "<group>"; }; 658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealtimeAlertViewController.swift; sourceTree = "<group>"; };
@@ -373,6 +400,10 @@
65DBFB7225E2BBF20041CBA6 /* GADTTemplateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADTTemplateView.m; sourceTree = "<group>"; }; 65DBFB7225E2BBF20041CBA6 /* GADTTemplateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADTTemplateView.m; sourceTree = "<group>"; };
65DBFB7325E2BBF20041CBA6 /* GADTMediumTemplateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADTMediumTemplateView.h; sourceTree = "<group>"; }; 65DBFB7325E2BBF20041CBA6 /* GADTMediumTemplateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADTMediumTemplateView.h; sourceTree = "<group>"; };
65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNRealtimePushNotification.swift; sourceTree = "<group>"; }; 65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNRealtimePushNotification.swift; sourceTree = "<group>"; };
65F9A6072D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworksIntensityMapViewController.swift; sourceTree = "<group>"; };
65F9A6092D706ADC008A12B5 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = "<group>"; };
65F9A60B2D70781A008A12B5 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
65F9A60D2D707BFE008A12B5 /* EQNShakemap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNShakemap.swift; sourceTree = "<group>"; };
65F9B49B2A8CA22800F13260 /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = "<group>"; }; 65F9B49B2A8CA22800F13260 /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = "<group>"; };
65F9B49D2A8CA2AC00F13260 /* EQNBackgroundPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgroundPosition.swift; sourceTree = "<group>"; }; 65F9B49D2A8CA2AC00F13260 /* EQNBackgroundPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgroundPosition.swift; sourceTree = "<group>"; };
65F9B49F2A8CC58200F13260 /* UpdateUserLocationTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateUserLocationTask.swift; sourceTree = "<group>"; }; 65F9B49F2A8CC58200F13260 /* UpdateUserLocationTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateUserLocationTask.swift; sourceTree = "<group>"; };
@@ -532,8 +563,6 @@
8CF12CD121DE49B600613AC5 /* EQNNotificationContent.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = EQNNotificationContent.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 8CF12CD121DE49B600613AC5 /* EQNNotificationContent.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = EQNNotificationContent.appex; sourceTree = BUILT_PRODUCTS_DIR; };
8CF12CD221DE49B600613AC5 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; 8CF12CD221DE49B600613AC5 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
8CF12CD421DE49B600613AC5 /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; }; 8CF12CD421DE49B600613AC5 /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; };
8CF12CD721DE49B600613AC5 /* NotificationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationViewController.h; sourceTree = "<group>"; };
8CF12CD821DE49B600613AC5 /* NotificationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationViewController.m; sourceTree = "<group>"; };
8CF12CDB21DE49B600613AC5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; }; 8CF12CDB21DE49B600613AC5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
8CF12CDD21DE49B600613AC5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 8CF12CDD21DE49B600613AC5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8CF4F4D6216D3A110057110B /* EQNAreaCheck.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EQNAreaCheck.h; sourceTree = "<group>"; }; 8CF4F4D6216D3A110057110B /* EQNAreaCheck.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EQNAreaCheck.h; sourceTree = "<group>"; };
@@ -656,6 +685,7 @@
children = ( children = (
658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */, 658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */,
658BC02A2859A4D3009EECAA /* RealtimeAlertView.swift */, 658BC02A2859A4D3009EECAA /* RealtimeAlertView.swift */,
657CCF0B2E32711D00E91715 /* MapSeismicWaveAnimator.swift */,
); );
path = "Realtime Alert"; path = "Realtime Alert";
sourceTree = "<group>"; sourceTree = "<group>";
@@ -663,8 +693,10 @@
65DBFB5225E2A2580041CBA6 /* Map annotation */ = { 65DBFB5225E2A2580041CBA6 /* Map annotation */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */,
653C67FB25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift */, 653C67FB25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift */,
651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */, 651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */,
657415DE2D70B6F700F54890 /* EQNMapAnnotationShakemap.swift */,
654D18C825F93CD700BB6DB0 /* EQNMapAnnotationPastquake.swift */, 654D18C825F93CD700BB6DB0 /* EQNMapAnnotationPastquake.swift */,
); );
path = "Map annotation"; path = "Map annotation";
@@ -889,8 +921,7 @@
children = ( children = (
6557CBBC26078A1700962757 /* EQNNotificationContent.entitlements */, 6557CBBC26078A1700962757 /* EQNNotificationContent.entitlements */,
8C483CB621FDACD100259FD2 /* EQNNotificationContent-Bridging-Header.h */, 8C483CB621FDACD100259FD2 /* EQNNotificationContent-Bridging-Header.h */,
8CF12CD721DE49B600613AC5 /* NotificationViewController.h */, 657CCF072E323AF700E91715 /* NotificationContentViewController.swift */,
8CF12CD821DE49B600613AC5 /* NotificationViewController.m */,
8CF12CDA21DE49B600613AC5 /* MainInterface.storyboard */, 8CF12CDA21DE49B600613AC5 /* MainInterface.storyboard */,
8CF12CDD21DE49B600613AC5 /* Info.plist */, 8CF12CDD21DE49B600613AC5 /* Info.plist */,
); );
@@ -912,12 +943,16 @@
DC141968250E769B0059E060 /* Seismic Networks */ = { DC141968250E769B0059E060 /* Seismic Networks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.swift */,
DC28142E2519C21400C1AFF7 /* Cells */, DC28142E2519C21400C1AFF7 /* Cells */,
65F0857E2609288E0002615C /* Filters */, 65F0857E2609288E0002615C /* Filters */,
6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.swift */,
657747FC2D4D12A200213830 /* SeismicNetworkData.swift */,
657747FE2D4D2BA100213830 /* SeismicNetworkScrollIndicatorView.swift */,
653C5C2B2E295C5A00E25528 /* SeismicNetworkFilterRecapView.swift */,
DC2814372519C56100C1AFF7 /* SeismicNetworksViewController.swift */, DC2814372519C56100C1AFF7 /* SeismicNetworksViewController.swift */,
DCC76BD7251F56050005C4DC /* SeismicCardSettingsViewController.swift */, DCC76BD7251F56050005C4DC /* SeismicCardSettingsViewController.swift */,
65DBFB4A25E29DD60041CBA6 /* SeismicNetworksMapDetailViewController.swift */, 65DBFB4A25E29DD60041CBA6 /* SeismicNetworksMapDetailViewController.swift */,
65F9A6072D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift */,
); );
path = "Seismic Networks"; path = "Seismic Networks";
sourceTree = "<group>"; sourceTree = "<group>";
@@ -926,7 +961,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
65DBFB7D25E2CB020041CBA6 /* Ad templates */, 65DBFB7D25E2CB020041CBA6 /* Ad templates */,
652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */,
DC28142F2519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift */, DC28142F2519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift */,
652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */,
6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */, 6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */,
); );
path = Cells; path = Cells;
@@ -987,6 +1024,7 @@
DC10563F251E7EC0002579BB /* Extensions */, DC10563F251E7EC0002579BB /* Extensions */,
8CF66054214C566A009F4314 /* Reachability.h */, 8CF66054214C566A009F4314 /* Reachability.h */,
8CF66056214C566A009F4314 /* Reachability.m */, 8CF66056214C566A009F4314 /* Reachability.m */,
65F9A60B2D70781A008A12B5 /* Log.swift */,
); );
path = Libs; path = Libs;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1042,6 +1080,7 @@
8C4E344A2152EE5B008B0D2A /* EQNGeneratoreURLServer.m */, 8C4E344A2152EE5B008B0D2A /* EQNGeneratoreURLServer.m */,
8CF66057214C566B009F4314 /* ServerRequest.h */, 8CF66057214C566B009F4314 /* ServerRequest.h */,
8CF66055214C566A009F4314 /* ServerRequest.m */, 8CF66055214C566A009F4314 /* ServerRequest.m */,
65F9A6092D706ADC008A12B5 /* APIService.swift */,
); );
path = "Server Requests"; path = "Server Requests";
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1113,6 +1152,7 @@
65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */, 65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */,
6552C1452926DBA1008E723C /* AppPreferences.swift */, 6552C1452926DBA1008E723C /* AppPreferences.swift */,
6590EFF72C3004EA00F41420 /* EQNOfficialPushNotification.swift */, 6590EFF72C3004EA00F41420 /* EQNOfficialPushNotification.swift */,
65F9A60D2D707BFE008A12B5 /* EQNShakemap.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1290,8 +1330,9 @@
8CBD3DBA2149B9AD0070C963 /* Project object */ = { 8CBD3DBA2149B9AD0070C963 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1410; LastSwiftUpdateCheck = 1410;
LastUpgradeCheck = 1410; LastUpgradeCheck = 1620;
ORGANIZATIONNAME = "Earthquake Network"; ORGANIZATIONNAME = "Earthquake Network";
TargetAttributes = { TargetAttributes = {
65FFDC91292F672B00EA821B = { 65FFDC91292F672B00EA821B = {
@@ -1321,7 +1362,6 @@
}; };
}; };
buildConfigurationList = 8CBD3DBD2149B9AD0070C963 /* Build configuration list for PBXProject "Earthquake Network" */; buildConfigurationList = 8CBD3DBD2149B9AD0070C963 /* Build configuration list for PBXProject "Earthquake Network" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
@@ -1345,6 +1385,7 @@
65B16E282BDFB39B0020527E /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */, 65B16E282BDFB39B0020527E /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */,
65E6AC6C2C2DB3B60073F8FE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, 65E6AC6C2C2DB3B60073F8FE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
); );
preferredProjectObjectVersion = 77;
productRefGroup = 8CBD3DC32149B9AD0070C963 /* Products */; productRefGroup = 8CBD3DC32149B9AD0070C963 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectReferences = ( projectReferences = (
@@ -1531,6 +1572,7 @@
files = ( files = (
65FFDC95292F672B00EA821B /* NotificationService.swift in Sources */, 65FFDC95292F672B00EA821B /* NotificationService.swift in Sources */,
6502470E2A6136F0001AC512 /* Constants.swift in Sources */, 6502470E2A6136F0001AC512 /* Constants.swift in Sources */,
6514FF6D2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */,
6502470F2A613AD7001AC512 /* EQNUserData.swift in Sources */, 6502470F2A613AD7001AC512 /* EQNUserData.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -1559,6 +1601,7 @@
DC03BEAB250BC0A60084769B /* EQNRoundedButton.swift in Sources */, DC03BEAB250BC0A60084769B /* EQNRoundedButton.swift in Sources */,
65AD23CE261B03D400E3B57C /* SubscriptionsDescriptionTableViewCell.swift in Sources */, 65AD23CE261B03D400E3B57C /* SubscriptionsDescriptionTableViewCell.swift in Sources */,
DCD4571C24F6CF0D00B58304 /* EQNGenericValue.swift in Sources */, DCD4571C24F6CF0D00B58304 /* EQNGenericValue.swift in Sources */,
657747FD2D4D12A200213830 /* SeismicNetworkData.swift in Sources */,
8C4E343F215012FA008B0D2A /* EQNManager.m in Sources */, 8C4E343F215012FA008B0D2A /* EQNManager.m in Sources */,
DCAA913F24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift in Sources */, DCAA913F24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift in Sources */,
DCF9E14F24F6EA07002B6B1D /* EQNSeismicNetwork.swift in Sources */, DCF9E14F24F6EA07002B6B1D /* EQNSeismicNetwork.swift in Sources */,
@@ -1566,11 +1609,14 @@
650247122A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift in Sources */, 650247122A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift in Sources */,
65CB83432915720400EE1E35 /* EQNUserData.swift in Sources */, 65CB83432915720400EE1E35 /* EQNUserData.swift in Sources */,
654D18C425F93C0600BB6DB0 /* PasquakesMapViewController.swift in Sources */, 654D18C425F93C0600BB6DB0 /* PasquakesMapViewController.swift in Sources */,
652247242D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift in Sources */,
8C483CBC21FDACE500259FD2 /* EQNInAppProducts.swift in Sources */, 8C483CBC21FDACE500259FD2 /* EQNInAppProducts.swift in Sources */,
8C483CB821FDACD300259FD2 /* IAPHelper.swift in Sources */, 8C483CB821FDACD300259FD2 /* IAPHelper.swift in Sources */,
652247262D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift in Sources */,
6562C80725FFA6B100C85273 /* SeismicNetworkViewModel.swift in Sources */, 6562C80725FFA6B100C85273 /* SeismicNetworkViewModel.swift in Sources */,
DCDE0BD924E58CCE00209778 /* EQNMainTabBarController.m in Sources */, DCDE0BD924E58CCE00209778 /* EQNMainTabBarController.m in Sources */,
8C4E344B2152EE5B008B0D2A /* EQNGeneratoreURLServer.m in Sources */, 8C4E344B2152EE5B008B0D2A /* EQNGeneratoreURLServer.m in Sources */,
65F9A60A2D706ADC008A12B5 /* APIService.swift in Sources */,
6590EFF82C3004EA00F41420 /* EQNOfficialPushNotification.swift in Sources */, 6590EFF82C3004EA00F41420 /* EQNOfficialPushNotification.swift in Sources */,
DC99A50724E66E5F0071BC9F /* EQNStartupCommandsBuilder.swift in Sources */, DC99A50724E66E5F0071BC9F /* EQNStartupCommandsBuilder.swift in Sources */,
8CF66058214C566B009F4314 /* ServerRequest.m in Sources */, 8CF66058214C566B009F4314 /* ServerRequest.m in Sources */,
@@ -1582,6 +1628,7 @@
DC886A5D24E92D5500F7A5D3 /* EQNBaseViewController.m in Sources */, DC886A5D24E92D5500F7A5D3 /* EQNBaseViewController.m in Sources */,
8C593E8A217BA2470008B260 /* EQNSegnalazione.m in Sources */, 8C593E8A217BA2470008B260 /* EQNSegnalazione.m in Sources */,
8CBD3DCA2149B9AD0070C963 /* AllerteViewController.m in Sources */, 8CBD3DCA2149B9AD0070C963 /* AllerteViewController.m in Sources */,
65F9A60E2D707BFE008A12B5 /* EQNShakemap.swift in Sources */,
DC646F32252B698B000AA5FD /* AlertsSeismicNotificationCompactTableViewCell.swift in Sources */, DC646F32252B698B000AA5FD /* AlertsSeismicNotificationCompactTableViewCell.swift in Sources */,
DCF10DCD24D2C935009F34C3 /* EQNPurchaseAvailability.swift in Sources */, DCF10DCD24D2C935009F34C3 /* EQNPurchaseAvailability.swift in Sources */,
6552C1462926DBA1008E723C /* AppPreferences.swift in Sources */, 6552C1462926DBA1008E723C /* AppPreferences.swift in Sources */,
@@ -1596,6 +1643,7 @@
8CF6604F214C0E58009F4314 /* EQNCalibrazione.m in Sources */, 8CF6604F214C0E58009F4314 /* EQNCalibrazione.m in Sources */,
65355FFF25F38D3300BB57D2 /* SegnalazioniMapViewController.swift in Sources */, 65355FFF25F38D3300BB57D2 /* SegnalazioniMapViewController.swift in Sources */,
DCBB84F0252CFC4600F12633 /* AlertsNoLocationTableViewCell.swift in Sources */, DCBB84F0252CFC4600F12633 /* AlertsNoLocationTableViewCell.swift in Sources */,
657747FF2D4D2BA200213830 /* SeismicNetworkScrollIndicatorView.swift in Sources */,
651901B925F5358700CAFF20 /* EQNMapAnnotationSeismic.swift in Sources */, 651901B925F5358700CAFF20 /* EQNMapAnnotationSeismic.swift in Sources */,
DC99A50324E66E270071BC9F /* EQNCommandProtocol.swift in Sources */, DC99A50324E66E270071BC9F /* EQNCommandProtocol.swift in Sources */,
DCF10DC624D2B8C7009F34C3 /* EQNPurchaseUtility.swift in Sources */, DCF10DC624D2B8C7009F34C3 /* EQNPurchaseUtility.swift in Sources */,
@@ -1604,6 +1652,7 @@
65BBB22C26064BE6005D6CDF /* SegnalazioniLast24HoursCell.swift in Sources */, 65BBB22C26064BE6005D6CDF /* SegnalazioniLast24HoursCell.swift in Sources */,
DC47D1BC252A0C2B004119F6 /* AlertsPastEartquakesTableViewCell.swift in Sources */, DC47D1BC252A0C2B004119F6 /* AlertsPastEartquakesTableViewCell.swift in Sources */,
654D18C925F93CD700BB6DB0 /* EQNMapAnnotationPastquake.swift in Sources */, 654D18C925F93CD700BB6DB0 /* EQNMapAnnotationPastquake.swift in Sources */,
6514FF6A2D720C3F000A7BD0 /* MapPinStyle.swift in Sources */,
DCEFF21724F58569009D3FE1 /* SettingSectionHeaderView.swift in Sources */, DCEFF21724F58569009D3FE1 /* SettingSectionHeaderView.swift in Sources */,
65DB60FD2C172C4A00164366 /* EQNSettingRealTimeAlert.swift in Sources */, 65DB60FD2C172C4A00164366 /* EQNSettingRealTimeAlert.swift in Sources */,
DCBB267A24D1E7F500F04559 /* SubscriptionsHeaderTableViewCell.swift in Sources */, DCBB267A24D1E7F500F04559 /* SubscriptionsHeaderTableViewCell.swift in Sources */,
@@ -1615,6 +1664,7 @@
65F9B4A02A8CC58200F13260 /* UpdateUserLocationTask.swift in Sources */, 65F9B4A02A8CC58200F13260 /* UpdateUserLocationTask.swift in Sources */,
8C4E34422152B5E8008B0D2A /* EQNRilevamento.m in Sources */, 8C4E34422152B5E8008B0D2A /* EQNRilevamento.m in Sources */,
8C7A3B66225A5EA40045B266 /* NSDictionary+EQNExtensions.m in Sources */, 8C7A3B66225A5EA40045B266 /* NSDictionary+EQNExtensions.m in Sources */,
653C5C2C2E295C5A00E25528 /* SeismicNetworkFilterRecapView.swift in Sources */,
65AB4A992C11DFC200950DF7 /* EQNSettingSeismicNetworkNotification.swift in Sources */, 65AB4A992C11DFC200950DF7 /* EQNSettingSeismicNetworkNotification.swift in Sources */,
8CF66053214C12DC009F4314 /* EQNMath.m in Sources */, 8CF66053214C12DC009F4314 /* EQNMath.m in Sources */,
DCF4A54524F8DB8300B17326 /* SettingDateTableViewCell.swift in Sources */, DCF4A54524F8DB8300B17326 /* SettingDateTableViewCell.swift in Sources */,
@@ -1622,14 +1672,17 @@
DC7EEE4A252A11C9004B4A2A /* AlertsSmartphoneNetworkTableViewCell.swift in Sources */, DC7EEE4A252A11C9004B4A2A /* AlertsSmartphoneNetworkTableViewCell.swift in Sources */,
DC7EEE4F252A1634004B4A2A /* AlertsPriorityServiceTableViewCell.swift in Sources */, DC7EEE4F252A1634004B4A2A /* AlertsPriorityServiceTableViewCell.swift in Sources */,
653604E9262348FA00B2B651 /* EQNBaseMapFilter.swift in Sources */, 653604E9262348FA00B2B651 /* EQNBaseMapFilter.swift in Sources */,
65F9A60C2D70781A008A12B5 /* Log.swift in Sources */,
65583A05261B83BE00ECA9F9 /* UIKit+Extensions.swift in Sources */, 65583A05261B83BE00ECA9F9 /* UIKit+Extensions.swift in Sources */,
65DBFB7625E2BBF20041CBA6 /* GADTTemplateView.m in Sources */, 65DBFB7625E2BBF20041CBA6 /* GADTTemplateView.m in Sources */,
8C5EA23D2177B51C002DC156 /* SegnalazioniViewController.m in Sources */, 8C5EA23D2177B51C002DC156 /* SegnalazioniViewController.m in Sources */,
656E02162C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift in Sources */, 656E02162C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift in Sources */,
656D138F2C2225560094F597 /* SubscriptionDetailsViewController.swift in Sources */, 656D138F2C2225560094F597 /* SubscriptionDetailsViewController.swift in Sources */,
65F9A6082D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift in Sources */,
653C67E225F3CC2E00FE52AC /* EQNCustomAnnotationView.swift in Sources */, 653C67E225F3CC2E00FE52AC /* EQNCustomAnnotationView.swift in Sources */,
8CF4F4D8216D3A110057110B /* EQNAreaCheck.m in Sources */, 8CF4F4D8216D3A110057110B /* EQNAreaCheck.m in Sources */,
8C4E34452152B707008B0D2A /* EQNAccelerometroManager.m in Sources */, 8C4E34452152B707008B0D2A /* EQNAccelerometroManager.m in Sources */,
657CCF0C2E32711D00E91715 /* MapSeismicWaveAnimator.swift in Sources */,
65D409942619BA34008CF356 /* SegnalazioniSendReportCell.swift in Sources */, 65D409942619BA34008CF356 /* SegnalazioniSendReportCell.swift in Sources */,
65F9B49C2A8CA22800F13260 /* BackgroundTaskManager.swift in Sources */, 65F9B49C2A8CA22800F13260 /* BackgroundTaskManager.swift in Sources */,
656D13912C22371F0094F597 /* SubscriptionDetailsTableViewCell.swift in Sources */, 656D13912C22371F0094F597 /* SubscriptionDetailsTableViewCell.swift in Sources */,
@@ -1649,6 +1702,7 @@
8C8EBBA721540039002784BA /* EQNUser.m in Sources */, 8C8EBBA721540039002784BA /* EQNUser.m in Sources */,
8CADAA9421B2627D0044E256 /* EQNLogViewController.m in Sources */, 8CADAA9421B2627D0044E256 /* EQNLogViewController.m in Sources */,
DC3BA11124D1A9C90062EE7F /* SubscriptionsViewController.swift in Sources */, DC3BA11124D1A9C90062EE7F /* SubscriptionsViewController.swift in Sources */,
657415E02D70B6F800F54890 /* EQNMapAnnotationShakemap.swift in Sources */,
DC646F27252B694A000AA5FD /* AlertsSeismicNotificationExpandedTableViewCell.swift in Sources */, DC646F27252B694A000AA5FD /* AlertsSeismicNotificationExpandedTableViewCell.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -1660,16 +1714,21 @@
8C465D9F21F7BE0600F04673 /* Assets.xcassets in Sources */, 8C465D9F21F7BE0600F04673 /* Assets.xcassets in Sources */,
65D9938C2922647800F2B0EB /* AppTheme.swift in Sources */, 65D9938C2922647800F2B0EB /* AppTheme.swift in Sources */,
DC0AE1B92538204100111307 /* EQNSegnalazione.m in Sources */, DC0AE1B92538204100111307 /* EQNSegnalazione.m in Sources */,
657CCF092E323BE500E91715 /* EQNRealtimePushNotification.swift in Sources */,
657CCF082E323AF700E91715 /* NotificationContentViewController.swift in Sources */,
654D18DE25F943E200BB6DB0 /* EQNMapAnnotationUserReport.swift in Sources */, 654D18DE25F943E200BB6DB0 /* EQNMapAnnotationUserReport.swift in Sources */,
654D18D625F9420500BB6DB0 /* EQNMapAnnotationPastquake.swift in Sources */, 654D18D625F9420500BB6DB0 /* EQNMapAnnotationPastquake.swift in Sources */,
65D507DB2A852274005BDC57 /* EQNUserData.swift in Sources */, 65D507DB2A852274005BDC57 /* EQNUserData.swift in Sources */,
657CCF0A2E323DC600E91715 /* Foundation+Extensions.swift in Sources */,
DC0AE1B52538202300111307 /* EQNUtility.m in Sources */, DC0AE1B52538202300111307 /* EQNUtility.m in Sources */,
65D507DC2A85227F005BDC57 /* Constants.swift in Sources */, 65D507DC2A85227F005BDC57 /* Constants.swift in Sources */,
657CCF0D2E32711D00E91715 /* MapSeismicWaveAnimator.swift in Sources */,
653C67E625F3CC8400FE52AC /* EQNCustomAnnotationView.swift in Sources */, 653C67E625F3CC8400FE52AC /* EQNCustomAnnotationView.swift in Sources */,
654D18DA25F9424700BB6DB0 /* EQNUtility+Extensions.swift in Sources */, 654D18DA25F9424700BB6DB0 /* EQNUtility+Extensions.swift in Sources */,
DC0AE1BA2538204100111307 /* EQNPastquakes.m in Sources */, DC0AE1BA2538204100111307 /* EQNPastquakes.m in Sources */,
6568C2D42E2A930500402F16 /* EQNBlurredCloseButton.swift in Sources */,
65D9938A29219DEC00F2B0EB /* UIKit+Extensions.swift in Sources */, 65D9938A29219DEC00F2B0EB /* UIKit+Extensions.swift in Sources */,
8CF12CD921DE49B600613AC5 /* NotificationViewController.m in Sources */, 6514FF6C2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -1773,7 +1832,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 135; CURRENT_PROJECT_VERSION = "$(inherited)";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
@@ -1784,13 +1843,12 @@
INFOPLIST_FILE = EQNNotificationService/Info.plist; INFOPLIST_FILE = EQNNotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = EQNNotificationService; INFOPLIST_KEY_CFBundleDisplayName = EQNNotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Earthquake Network. All rights reserved."; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Earthquake Network. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 5.8; MARKETING_VERSION = "$(inherited)";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice; PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice;
@@ -1802,7 +1860,6 @@
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Debug; name = Debug;
}; };
@@ -1815,20 +1872,19 @@
CODE_SIGN_IDENTITY = "Apple Distribution"; CODE_SIGN_IDENTITY = "Apple Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 135; CURRENT_PROJECT_VERSION = "$(inherited)";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = EQNNotificationService/Info.plist; INFOPLIST_FILE = EQNNotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = EQNNotificationService; INFOPLIST_KEY_CFBundleDisplayName = EQNNotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Earthquake Network. All rights reserved."; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Earthquake Network. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 5.8; MARKETING_VERSION = "$(inherited)";
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice; PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1838,7 +1894,6 @@
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Release; name = Release;
}; };
@@ -1878,6 +1933,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 159;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = WJA4MR4CPC; DEVELOPMENT_TEAM = WJA4MR4CPC;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -1897,7 +1953,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 5.11.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1940,6 +1997,7 @@
CODE_SIGN_IDENTITY = "Apple Distribution"; CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 159;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = WJA4MR4CPC; DEVELOPMENT_TEAM = WJA4MR4CPC;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
@@ -1953,7 +2011,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 5.11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
@@ -1964,24 +2023,24 @@
8CBD3DDC2149B9AD0070C963 /* Debug */ = { 8CBD3DDC2149B9AD0070C963 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements"; CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 135; CURRENT_PROJECT_VERSION = "$(inherited)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = WJA4MR4CPC; DEVELOPMENT_TEAM = WJA4MR4CPC;
ENABLE_MODULE_VERIFIER = YES;
GCC_PREFIX_HEADER = "Earthquake Network/Earthquake Network-Prefix.pch"; GCC_PREFIX_HEADER = "Earthquake Network/Earthquake Network-Prefix.pch";
INFOPLIST_FILE = "Earthquake Network/Info.plist"; INFOPLIST_FILE = "Earthquake Network/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 5.8; MARKETING_VERSION = "$(inherited)";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@@ -2044,30 +2103,29 @@
SWIFT_OBJC_BRIDGING_HEADER = "Earthquake Network/Earthquake Network-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Earthquake Network/Earthquake Network-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
}; };
name = Debug; name = Debug;
}; };
8CBD3DDD2149B9AD0070C963 /* Release */ = { 8CBD3DDD2149B9AD0070C963 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements"; CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements";
CODE_SIGN_IDENTITY = "Apple Distribution"; CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 135; CURRENT_PROJECT_VERSION = "$(inherited)";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = WJA4MR4CPC; DEVELOPMENT_TEAM = WJA4MR4CPC;
ENABLE_MODULE_VERIFIER = YES;
GCC_PREFIX_HEADER = "Earthquake Network/Earthquake Network-Prefix.pch"; GCC_PREFIX_HEADER = "Earthquake Network/Earthquake Network-Prefix.pch";
INFOPLIST_FILE = "Earthquake Network/Info.plist"; INFOPLIST_FILE = "Earthquake Network/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 5.8; MARKETING_VERSION = "$(inherited)";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@@ -2129,7 +2187,6 @@
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork - AppStore"; PROVISIONING_PROFILE_SPECIFIER = "EQNetwork - AppStore";
SWIFT_OBJC_BRIDGING_HEADER = "Earthquake Network/Earthquake Network-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Earthquake Network/Earthquake Network-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
}; };
name = Release; name = Release;
}; };
@@ -2140,7 +2197,7 @@
CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements; CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 135; CURRENT_PROJECT_VERSION = "$(inherited)";
DEVELOPMENT_TEAM = WJA4MR4CPC; DEVELOPMENT_TEAM = WJA4MR4CPC;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1", "DEBUG=1",
@@ -2149,13 +2206,12 @@
"NOTIFICATION_CONTENT=1", "NOTIFICATION_CONTENT=1",
); );
INFOPLIST_FILE = EQNNotificationContent/Info.plist; INFOPLIST_FILE = EQNNotificationContent/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 5.8; MARKETING_VERSION = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent; PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - Development"; PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - Development";
@@ -2163,7 +2219,6 @@
SWIFT_OBJC_BRIDGING_HEADER = "EQNNotificationContent/EQNNotificationContent-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "EQNNotificationContent/EQNNotificationContent-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
}; };
name = Debug; name = Debug;
}; };
@@ -2174,27 +2229,25 @@
CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements; CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution"; CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 135; CURRENT_PROJECT_VERSION = "$(inherited)";
DEVELOPMENT_TEAM = WJA4MR4CPC; DEVELOPMENT_TEAM = WJA4MR4CPC;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"ADS_ENABLED=0", "ADS_ENABLED=0",
"NOTIFICATION_CONTENT=1", "NOTIFICATION_CONTENT=1",
); );
INFOPLIST_FILE = EQNNotificationContent/Info.plist; INFOPLIST_FILE = EQNNotificationContent/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 5.8; MARKETING_VERSION = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent; PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - AppStore"; PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - AppStore";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "EQNNotificationContent/EQNNotificationContent-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "EQNNotificationContent/EQNNotificationContent-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
}; };
name = Release; name = Release;
}; };
@@ -2242,10 +2295,10 @@
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */
6552C13629262119008E723C /* XCRemoteSwiftPackageReference "Shogun" */ = { 6552C13629262119008E723C /* XCRemoteSwiftPackageReference "Shogun" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/andreabusi-it/Shogun"; repositoryURL = "https://github.com/andreabusi-it/Shogun.git";
requirement = { requirement = {
kind = upToNextMajorVersion; kind = upToNextMajorVersion;
minimumVersion = 1.0.0; minimumVersion = 2.0.0;
}; };
}; };
65B16E1C2BDFA88D0020527E /* XCRemoteSwiftPackageReference "Solar" */ = { 65B16E1C2BDFA88D0020527E /* XCRemoteSwiftPackageReference "Solar" */ = {
@@ -2269,7 +2322,7 @@
repositoryURL = "https://github.com/googleads/swift-package-manager-google-mobile-ads.git"; repositoryURL = "https://github.com/googleads/swift-package-manager-google-mobile-ads.git";
requirement = { requirement = {
kind = upToNextMajorVersion; kind = upToNextMajorVersion;
minimumVersion = 11.3.0; minimumVersion = 12.0.0;
}; };
}; };
65B16E282BDFB39B0020527E /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = { 65B16E282BDFB39B0020527E /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = {
@@ -2277,7 +2330,7 @@
repositoryURL = "https://github.com/facebook/facebook-ios-sdk"; repositoryURL = "https://github.com/facebook/facebook-ios-sdk";
requirement = { requirement = {
kind = upToNextMajorVersion; kind = upToNextMajorVersion;
minimumVersion = 14.1.0; minimumVersion = 18.0.0;
}; };
}; };
65E6AC6C2C2DB3B60073F8FE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { 65E6AC6C2C2DB3B60073F8FE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
@@ -2285,7 +2338,7 @@
repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git";
requirement = { requirement = {
kind = upToNextMajorVersion; kind = upToNextMajorVersion;
minimumVersion = 10.28.1; minimumVersion = 11.1.0;
}; };
}; };
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
@@ -1,13 +1,13 @@
{ {
"originHash" : "c29b9b16ee6b4d1a6fec2debc59749097860256c8cbb2addc2abc08d3adba59d", "originHash" : "898a30d298491e1ce821191ebfa2e7d28e7c9ea4c119cbfdfb1b245bc94ac6c3",
"pins" : [ "pins" : [
{ {
"identity" : "abseil-cpp-binary", "identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git", "location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : { "state" : {
"revision" : "748c7837511d0e6a507737353af268484e1745e2", "revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024011601.1" "version" : "1.2024072200.0"
} }
}, },
{ {
@@ -15,8 +15,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git", "location" : "https://github.com/google/app-check.git",
"state" : { "state" : {
"revision" : "3b62f154d00019ae29a71e9738800bb6f18b236d", "revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "10.19.2" "version" : "11.2.0"
} }
}, },
{ {
@@ -33,8 +33,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/facebook/facebook-ios-sdk", "location" : "https://github.com/facebook/facebook-ios-sdk",
"state" : { "state" : {
"revision" : "c19607d535864533523d1f437c84035e5fb101cf", "revision" : "b28dde427715b45a26ebebf697929f4a81b15e04",
"version" : "14.1.0" "version" : "18.0.0"
} }
}, },
{ {
@@ -42,8 +42,17 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk.git", "location" : "https://github.com/firebase/firebase-ios-sdk.git",
"state" : { "state" : {
"revision" : "e57841b296d04370ea23580f908881b0ccab17b9", "revision" : "fdc352fabaf5916e7faa1f96ad02b1957e93e5a5",
"version" : "10.28.1" "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"
} }
}, },
{ {
@@ -51,8 +60,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git", "location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : { "state" : {
"revision" : "fe727587518729046fc1465625b9afd80b5ab361", "revision" : "45ce435e9406d3c674dd249a042b932bee006f60",
"version" : "10.28.0" "version" : "11.15.0"
} }
}, },
{ {
@@ -60,8 +69,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git", "location" : "https://github.com/google/GoogleDataTransport.git",
"state" : { "state" : {
"revision" : "a637d318ae7ae246b02d7305121275bc75ed5565", "revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "9.4.0" "version" : "10.1.0"
} }
}, },
{ {
@@ -69,8 +78,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git", "location" : "https://github.com/google/GoogleUtilities.git",
"state" : { "state" : {
"revision" : "57a1d307f42df690fdef2637f3e5b776da02aad6", "revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
"version" : "7.13.3" "version" : "8.1.0"
} }
}, },
{ {
@@ -78,8 +87,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git", "location" : "https://github.com/google/grpc-binary.git",
"state" : { "state" : {
"revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359", "revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
"version" : "1.62.2" "version" : "1.69.0"
} }
}, },
{ {
@@ -96,8 +105,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git", "location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : { "state" : {
"revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", "revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "100.0.0" "version" : "101.0.0"
} }
}, },
{ {
@@ -130,10 +139,10 @@
{ {
"identity" : "shogun", "identity" : "shogun",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/andreabusi-it/Shogun", "location" : "https://github.com/andreabusi-it/Shogun.git",
"state" : { "state" : {
"revision" : "ad890190d6be90f7712c2e56a38ef0937d9f8c1a", "revision" : "809b56a43fadac72db9963a21c74688af7ef51b7",
"version" : "1.8.0" "version" : "2.1.0"
} }
}, },
{ {
@@ -150,8 +159,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git", "location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git",
"state" : { "state" : {
"revision" : "9ab66e38f5f0c2d02f2b024b1babd880130f19bf", "revision" : "aa24c7dc03bca62c42747314dc8537f15587b50d",
"version" : "11.3.0" "version" : "12.0.0"
} }
}, },
{ {
@@ -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
@@ -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,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"
+11 -15
View File
@@ -207,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 setAdvertiserTrackingEnabled:YES]; } else {
} else { FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = NO;
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:NO]; }
} }];
}]; });
});
} else {
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:YES];
}
} }
- (void)configureFirebase - (void)configureFirebase
@@ -232,7 +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 setAdvertiserIDCollectionEnabled:YES]; FBSDKSettings.sharedSettings.isAdvertiserIDCollectionEnabled = YES;
} }
#pragma mark - FIRMessagingDelegate #pragma mark - FIRMessagingDelegate
+12 -1
View File
@@ -17,6 +17,7 @@ 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"
@@ -37,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
@@ -55,12 +57,21 @@ 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 = "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 SettingsSeismicNetworkNotificationMigrationV5_8 = "EQNUserDefaultSettingsSeismicNetworkNotificationMigrationV5_8"
static let SettingsUserReportNotificationMigrationV5_8 = "EQNUserDefaultSettingsUserReportNotificationMigrationV5_8" static let SettingsUserReportNotificationMigrationV5_8 = "EQNUserDefaultSettingsUserReportNotificationMigrationV5_8"
static let SismicFiltersMigrationV5_8 = "EQNUserDefaultSismicFiltersMigrationV5_8" static let SismicFiltersMigrationV5_8 = "EQNUserDefaultSismicFiltersMigrationV5_8"
@@ -100,8 +100,10 @@ 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:[AlertsSmartphoneNetworkTableViewCell class] forCellReuseIdentifier:@"SmartphoneNetworkCell"];
[self.tableView registerClass:[AlertsPriorityServiceTableViewCell class] forCellReuseIdentifier:@"PriorityCell"]; [self.tableView registerClass:[AlertsPriorityServiceTableViewCell class] forCellReuseIdentifier:@"PriorityCell"];
[self.tableView registerClass:[AlertsNoLocationTableViewCell class] forCellReuseIdentifier:@"NoLocationCell"]; [self.tableView registerClass:[AlertsNoLocationTableViewCell class] forCellReuseIdentifier:@"NoLocationCell"];
@@ -147,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)];
} }
@@ -233,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)
@@ -271,7 +273,7 @@ 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.selectionStyle = UITableViewCellSelectionStyleNone; cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell updateWith:CLLocationManager.authorizationStatus]; [cell updateWith:EQNUserData.sharedData.locationAuthorizationStatus];
return cell; return cell;
} else if (tableRow == AllerteTableRowSismiRilevati) { } else if (tableRow == AllerteTableRowSismiRilevati) {
@@ -7,7 +7,7 @@
// //
import UIKit import UIKit
import Shogun
@objc @objc
class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell { class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell {
@@ -22,7 +22,7 @@ class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell {
let label = UILabel() let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.Colors.green label.textColor = AppTheme.Colors.green
label.font = .preferredFont(forTextStyle: .largeTitle) label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
label.textAlignment = .center label.textAlignment = .center
return label return label
}() }()
@@ -39,7 +39,7 @@ class SubscriptionDetailsViewController: UITableViewController {
products: [EQNInAppProducts] products: [EQNInAppProducts]
) { ) {
self.products = products self.products = products
self.selectedProduct = products.first(where: { $0.plan == .monthly }) ?? products.first! self.selectedProduct = products.first(where: { $0.plan == .yearly }) ?? products.first!
super.init(style: .plain) super.init(style: .plain)
} }
@@ -67,6 +67,7 @@ class SubscriptionDetailsViewController: UITableViewController {
tableView.estimatedRowHeight = 2000.0 tableView.estimatedRowHeight = 2000.0
tableView.separatorStyle = .none tableView.separatorStyle = .none
tableView.backgroundColor = .systemGroupedBackground tableView.backgroundColor = .systemGroupedBackground
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
tableView.registerCell(for: SubscriptionDetailsTableViewCell.self) tableView.registerCell(for: SubscriptionDetailsTableViewCell.self)
} }
@@ -18,6 +18,7 @@ class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
label.translatesAutoresizingMaskIntoConstraints = false label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .title2) label.font = UIFont.preferredFont(forTextStyle: .title2)
label.textColor = AppTheme.Colors.darkGray label.textColor = AppTheme.Colors.darkGray
label.textAlignment = .center
return label return label
}() }()
@@ -48,6 +49,7 @@ class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
headerTitleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true headerTitleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
headerTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .cardPadding).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.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .cardPadding.negative).isActive = true
loadingActivityIndicator.centerYAnchor.constraint(equalTo: headerTitleLabel.centerYAnchor).isActive = true loadingActivityIndicator.centerYAnchor.constraint(equalTo: headerTitleLabel.centerYAnchor).isActive = true
} }
@@ -91,6 +91,9 @@ class SubscriptionsViewController: UITableViewController {
tableView.estimatedRowHeight = 600.0 tableView.estimatedRowHeight = 600.0
tableView.separatorStyle = .none tableView.separatorStyle = .none
tableView.backgroundColor = .systemGroupedBackground tableView.backgroundColor = .systemGroupedBackground
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
// remove extra padding on top of each section header
tableView.sectionHeaderTopPadding = 0.0
tableView.registerCell(for: SubscriptionsActiveTableViewCell.self) tableView.registerCell(for: SubscriptionsActiveTableViewCell.self)
tableView.registerCell(for: SubscriptionsDescriptionTableViewCell.self) tableView.registerCell(for: SubscriptionsDescriptionTableViewCell.self)
tableView.registerCell(for: SubscriptionProductTableViewCell.self) tableView.registerCell(for: SubscriptionProductTableViewCell.self)
@@ -107,11 +110,29 @@ class SubscriptionsViewController: UITableViewController {
let products = storeProducts.compactMap { EQNInAppProducts.from(product: $0) } let products = storeProducts.compactMap { EQNInAppProducts.from(product: $0) }
let purchased = products.filter { (product) -> Bool in let purchased = products
let isPurchased = EQNInAppProducts.store.isProductPurchased(product.productIdentifier) .filter { (product) -> Bool in
let isSubscription = product.isSubscription // filter for subscriptions
return isPurchased && isSubscription 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.productSubscribed = purchased.first
self.products = products.sorted(by: { $0.productIdentifier > $1.productIdentifier }) self.products = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
self.tableView.reloadData() self.tableView.reloadData()
@@ -179,17 +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]
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self) switch tableSection.sectionTitle {
view.update(isLoading: isLoading, title: tableSection.sectionTitle) case .some(let title):
return view let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
view.update(isLoading: isLoading, title: title)
return view
case .none:
return nil
}
} }
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 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 {
@@ -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
}
}
}
@@ -102,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
@@ -160,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 = "",
@@ -192,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
}
}
@@ -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)
}
} }
} }
@@ -16,7 +16,7 @@ class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
@objc var onTapMap: (() -> Void)? @objc var onTapMap: (() -> Void)?
@objc var onTapTelegram: (() -> Void)? @objc var onTapTelegram: (() -> Void)?
override var headerText: String { NSLocalizedString("tab_manual", comment: "") } override var isHeaderVisible: Bool { false }
// MARK: - UI // MARK: - UI
@@ -24,7 +24,7 @@ class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
let label = UILabel() let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.Colors.red label.textColor = AppTheme.Colors.red
label.font = .preferredFont(forTextStyle: .largeTitle) label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
label.textAlignment = .center label.textAlignment = .center
label.numberOfLines = 0 label.numberOfLines = 0
return label return label
@@ -43,7 +43,7 @@ class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
private lazy var twitterButton: UIButton = { private lazy var twitterButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(twitterButtonTapped(_:))) let button = EQNRoundedButton.make(target: self, action: #selector(twitterButtonTapped(_:)))
button.imageView?.contentMode = .scaleAspectFit button.imageView?.contentMode = .scaleAspectFit
button.setImage(.init(named: "twitter_icon"), for: .normal) button.setImage(.init(named: "xcorp_icon"), for: .normal)
return button return button
}() }()
@@ -38,8 +38,10 @@
- (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:[SegnalazioniLast24HoursCell class] forCellReuseIdentifier:@"Last24HCell"];
[self.tableView registerClass:[SegnalazioniSendReportCell class] forCellReuseIdentifier:@"ReportEarthquakeCell"]; [self.tableView registerClass:[SegnalazioniSendReportCell class] forCellReuseIdentifier:@"ReportEarthquakeCell"];
} }
@@ -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
} }
} }
@@ -0,0 +1,127 @@
//
// SeismicNetworkBaseTableViewCell.swift
// Earthquake Network
//
// Created by Andrea Busi on 06/03/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import UIKit
import Shogun
protocol SeismicNetworkBaseTableViewCellDelegate: AnyObject {
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkBaseTableViewCell)
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell)
}
class SeismicNetworkBaseTableViewCell: UITableViewCell {
/// Delegate
weak var delegate: SeismicNetworkBaseTableViewCellDelegate?
/// Available informations to display inside the cell
enum InformationType: Int {
case preliminary
case time
case distance
case coordinate
case population
case realtimeSmartphones
case reportUsers
case intensityMap
case buttons
}
// MARL: - Internal
static let DefaultButtonHeight: CGFloat = 34.0
static let VerticalSpacingDefault: CGFloat = 6.0
static let VerticalSpacingSmall: CGFloat = 2.0
static let HorizontalSpacingDefault: CGFloat = 4.0
// MARK: - UI Components
lazy var containerView: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.clipsToBounds = true
return view
}()
lazy var gradientView: UIImageView = {
// Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine
// creata ad-hoc con il gradiente desiderato.
// Le prove fatte utilizzando una view normale sono fallite perchè al momento di
// disegnare la view non abbiamo le misure corrette.
let view = UIImageView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleToFill
return view
}()
// MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
// MARK: - View Lifecycle
override func layoutSubviews() {
super.layoutSubviews()
containerView.eqn_applyShadowAndRoundedCorners()
gradientView.eqn_applyRoundedCorners()
}
// MARK: - Setup
func setupUI() {
selectionStyle = .default
backgroundColor = .clear
// container view
contentView.addSubview(containerView)
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0).isActive = true
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true
containerView.addSubview(gradientView)
gradientView.constraint(to: containerView)
}
func recreateUI() {
// remove all subviews and recreate the required components
containerView.subviews.forEach({ $0.removeFromSuperview() })
setupUI()
}
@discardableResult
func addSeparator(constraintTo: NSLayoutYAxisAnchor, constanst: CGFloat = 8.0) -> UIView {
let separator = UIView()
separator.translatesAutoresizingMaskIntoConstraints = false
separator.backgroundColor = .lightGray
containerView.addSubview(separator)
separator.topAnchor.constraint(equalTo: constraintTo, constant: constanst).isActive = true
separator.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
separator.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
return separator
}
}
@@ -0,0 +1,233 @@
//
// SeismicNetworkMinimalTableViewCell.swift
// Earthquake Network
//
// Created by Andrea Busi on 06/03/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import UIKit
import Shogun
class SeismicNetworkMinimalTableViewCell: SeismicNetworkBaseTableViewCell {
// MARK: - UI
private lazy var magnitudeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .largeTitle)
label.textColor = .red
label.textAlignment = .center
return label
}()
private lazy var placeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold)
label.numberOfLines = 3
return label
}()
private lazy var timeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 2
return label
}()
private lazy var distanceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 2
return label
}()
private lazy var smartphonesLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
label.numberOfLines = 2
return label
}()
private lazy var alertsLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
label.numberOfLines = 2
return label
}()
// MARK: - Internal
/// Seismic to show
private var seismic: EQNSisma?
private var isPushSelected = false
private var informationTypes: Set<InformationType> = []
// MARK: - Setup
override func setupUI() {
super.setupUI()
// this variable is used to keep track of the previous view, in order to attach proper constraints
var previousView: UIView = containerView
// preliminary banner on top of the cell
if informationTypes.contains(.preliminary) {
let preliminaryLabel = UILabel()
preliminaryLabel.translatesAutoresizingMaskIntoConstraints = false
preliminaryLabel.text = NSLocalizedString("official_prelimiary", comment: "").uppercased()
preliminaryLabel.textAlignment = .center
preliminaryLabel.backgroundColor = .red
preliminaryLabel.textColor = .yellow
containerView.addSubview(preliminaryLabel)
preliminaryLabel.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
preliminaryLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
preliminaryLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
preliminaryLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
previousView = preliminaryLabel
}
containerView.addSubview(magnitudeLabel)
containerView.addSubview(placeLabel)
let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor
let stackViewInformations = UIStackView(arrangedSubviews: [timeLabel, distanceLabel])
stackViewInformations.translatesAutoresizingMaskIntoConstraints = false
stackViewInformations.axis = .horizontal
stackViewInformations.distribution = .fillEqually
stackViewInformations.spacing = Self.HorizontalSpacingDefault
containerView.addSubview(stackViewInformations)
let stackViewRight = UIStackView(arrangedSubviews: [placeLabel, stackViewInformations])
stackViewRight.translatesAutoresizingMaskIntoConstraints = false
stackViewRight.axis = .vertical
stackViewRight.distribution = .equalSpacing
stackViewRight.spacing = Self.VerticalSpacingDefault
let stackViewMain = UIStackView(arrangedSubviews: [magnitudeLabel, stackViewRight])
stackViewMain.translatesAutoresizingMaskIntoConstraints = false
stackViewMain.axis = .horizontal
stackViewMain.distribution = .fill
stackViewMain.spacing = Self.HorizontalSpacingDefault
containerView.addSubview(stackViewMain)
stackViewMain.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
stackViewMain.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
stackViewMain.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
magnitudeLabel.widthAnchor.constraint(equalToConstant: 60.0).isActive = true
previousView = stackViewMain
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) || informationTypes.contains(.intensityMap) {
let separator = addSeparator(constraintTo: previousView.bottomAnchor, constanst: Self.VerticalSpacingDefault)
let stackViewReports = UIStackView()
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
stackViewReports.axis = .vertical
stackViewReports.distribution = .equalSpacing
stackViewReports.alignment = .center
stackViewReports.spacing = Self.VerticalSpacingDefault
if informationTypes.contains(.realtimeSmartphones) {
stackViewReports.addArrangedSubview(smartphonesLabel)
}
if informationTypes.contains(.reportUsers) {
stackViewReports.addArrangedSubview(alertsLabel)
}
if informationTypes.contains(.intensityMap) {
let buttonMap = EQNRoundedButton.make(title: "🎯 \(NSLocalizedString("shakemap", comment: ""))", target: self, action: #selector(intensityMapTapped(_:)))
stackViewReports.addArrangedSubview(buttonMap)
buttonMap.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
buttonMap.leadingAnchor.constraint(equalTo: stackViewReports.leadingAnchor).isActive = true
buttonMap.trailingAnchor.constraint(equalTo: stackViewReports.trailingAnchor).isActive = true
}
containerView.addSubview(stackViewReports)
stackViewReports.topAnchor.constraint(equalTo: separator.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
previousView = stackViewReports
}
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
containerView.eqn_applyShadowAndRoundedCorners()
gradientView.eqn_applyRoundedCorners()
}
private func updateUI() {
guard let seismic = seismic else { return }
let viewModel = SeismicNetworkMinimalViewModel(seismic: seismic)
gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
placeLabel.text = viewModel.place
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
magnitudeLabel.textColor = viewModel.colors.textColor
magnitudeLabel.text = viewModel.magnitude
timeLabel.text = "🕗 \(viewModel.time)"
distanceLabel.text = "📐 \(viewModel.distance)"
if !viewModel.smartphones.isEmpty {
smartphonesLabel.text = "🚨 \(viewModel.smartphones)"
}
if !viewModel.users.isEmpty {
alertsLabel.text = "⚠️ \(viewModel.users)"
}
}
// MARK: - Public
/// Configure the cell to display a seismic
/// - Parameters:
/// - seismic: Seismic to display
/// - type: Type of cell
/// - informations: Informations to show
public func configure(
with seismic: EQNSisma,
isPushSelected: Bool
) {
self.seismic = seismic
self.isPushSelected = isPushSelected
self.informationTypes.removeAll()
if seismic.preliminary.intValue > 0 {
informationTypes.insert(.preliminary)
}
if seismic.smartphoneNumber.intValue > 0 {
informationTypes.insert(.realtimeSmartphones)
}
if seismic.userNumber.intValue > 0 {
informationTypes.insert(.reportUsers)
}
if seismic.isoCode != "0" {
informationTypes.insert(.intensityMap)
}
recreateUI()
updateUI()
}
// MARK: - Actions
@objc private func intensityMapTapped(_ sender: Any) {
delegate?.seismicNetworkCellDidTapIntensityMapDetail(self)
}
}
@@ -11,32 +11,8 @@ import MapKit
import CoreLocation import 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,43 +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
/// 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 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.backgroundColor = .white
view.clipsToBounds = true
return view
}()
private lazy var gradientView: UIImageView = {
// Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine
// creata ad-hoc con il gradiente desiderato.
// Le prove fatte utilizzando una view normale sono fallite perchè al momento di
// disegnare la view non abbiamo le misure corrette.
let view = UIImageView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleToFill
return view
}()
private lazy var placeLabel: UILabel = { private lazy var placeLabel: UILabel = {
let label = UILabel() let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false label.translatesAutoresizingMaskIntoConstraints = false
@@ -90,6 +38,14 @@ 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
@@ -200,20 +156,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
// 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
containerView.addSubview(gradientView)
gradientView.constraint(to: containerView)
// 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
@@ -234,41 +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(placeLabel)
stackViewTitle.addArrangedSubview(shareButton)
placeLabel.setContentHuggingPriority(.init(200), for: .horizontal)
placeLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
shareButton.widthAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true
shareButton.widthAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
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) {
@@ -298,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)
@@ -319,30 +257,32 @@ 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
} }
// network
containerView.addSubview(networkLabel)
networkLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
networkLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
networkLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
previousView = networkLabel
if informationTypes.contains(.buttons) { 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 = EQNRoundedButton.make(title: "🗺", target: self, action: #selector(mapTapped(_:))) let buttonMap = EQNRoundedButton.make(title: "🗺", target: self, action: #selector(mapTapped(_:)))
stackViewButtons.addArrangedSubview(buttonMap) stackViewButtons.addArrangedSubview(buttonMap)
@@ -352,8 +292,8 @@ class SeismicNetworkTableViewCell: UITableViewCell {
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
@@ -363,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
@@ -374,7 +314,8 @@ class SeismicNetworkTableViewCell: UITableViewCell {
let buttonClose = EQNRoundedButton.make(title: NSLocalizedString("official_close", comment: "").uppercased(), target: self, 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
@@ -387,28 +328,18 @@ class SeismicNetworkTableViewCell: UITableViewCell {
gradientView.eqn_applyRoundedCorners() gradientView.eqn_applyRoundedCorners()
} }
private func recreateUI() {
// remove all subviews and recreate the required components
containerView.subviews.forEach({ $0.removeFromSuperview() })
setupUI()
}
private func updateUI() { private func updateUI() {
guard let seismic = seismic else { return } guard let seismic = seismic else { return }
let viewModel = SeismicNetworkViewModel(seismic: seismic) let viewModel = SeismicNetworkViewModel(seismic: seismic)
if let colors = colors { gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
gradientView.image = .gradient(from: colors.startColor, to: colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
} else {
gradientView.image = nil
}
// update seismic data // update seismic data
placeLabel.text = viewModel.place placeLabel.text = viewModel.place
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
networkLabel.text = String(format: NSLocalizedString("official_provider", comment: ""), viewModel.network) networkLabel.text = String(format: NSLocalizedString("official_provider", comment: ""), viewModel.network)
magnitudeLabel.textColor = colors?.textColor 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)"
@@ -427,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)
@@ -461,7 +391,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
isPushSelected: Bool 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 self.isPushSelected = isPushSelected
@@ -479,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()
@@ -486,48 +418,37 @@ 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)
} }
// MARK: - Helpers @objc private func intensityMapTapped(_ sender: Any) {
delegate?.seismicNetworkCellDidTapIntensityMapDetail(self)
@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
} }
// MARK: - Helpers
/// 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 {
@@ -541,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)
}
} }
@@ -21,6 +21,7 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
case magnitudoMinima case magnitudoMinima
case sismiRilevanti case sismiRilevanti
case sismiTutti case sismiTutti
case sismiPercepiti
} }
weak var delegate: SeismicFiltersViewControllerDelegate? weak var delegate: SeismicFiltersViewControllerDelegate?
@@ -40,10 +41,11 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
SettingItem(type: .slider, title: NSLocalizedString("filter_minimum_magnitude", comment: "")), SettingItem(type: .slider, title: NSLocalizedString("filter_minimum_magnitude", comment: "")),
SettingItem(type: .enable, title: NSLocalizedString("filter_show_relevant", comment: "")), SettingItem(type: .enable, title: NSLocalizedString("filter_show_relevant", comment: "")),
SettingItem(type: .enable, title: NSLocalizedString("filter_show_all", comment: "")), SettingItem(type: .enable, title: NSLocalizedString("filter_show_all", comment: "")),
SettingItem(type: .enable, title: NSLocalizedString("filter_show_felt", comment: ""))
] ]
private let initialFilterType = EQNSeismic.shared.filterOption private let initialFilterType = EQNSeismic.shared.filterOption
private var currentFilterType = EQNSeismic.FilterType.inRadius private(set) var currentFilterType = EQNSeismic.FilterType.inRadius
private var currentMaximumDistance = EQNData.DefaultFilterRadius private var currentMaximumDistance = EQNData.DefaultFilterRadius
private var currentMinimumMagnitude = EQNData.DefaultFilterMagnitude private var currentMinimumMagnitude = EQNData.DefaultFilterMagnitude
@@ -148,6 +150,12 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
cell.valueChanged = { [weak self] enabled in cell.valueChanged = { [weak self] enabled in
self?.onChangeFilterOption(enabled, filter: .worldWide) 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: default:
break break
} }
@@ -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
@@ -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)
}
@@ -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)
}
}
}
}
}
@@ -8,14 +8,62 @@
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 {
private let seismic: EQNSisma
let place: String let place: String
let network: String let network: String
let isPreliminary: Bool let isPreliminary: Bool
let magnitude: String let magnitude: String
let rawMagnitude: Double
let depth: String let depth: String
let time: String let time: String
let distance: String let distance: String
@@ -23,13 +71,14 @@ struct SeismicNetworkViewModel {
let population: String let population: String
let smartphones: String let smartphones: String
let users: 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
self.rawMagnitude = seismic.magnitude.doubleValue
let isPreliminary = seismic.preliminary.intValue > 0 let isPreliminary = seismic.preliminary.intValue > 0
self.isPreliminary = isPreliminary self.isPreliminary = isPreliminary
@@ -58,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 {
@@ -71,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
}
@@ -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
}
@@ -15,21 +15,11 @@ protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
} }
class SeismicNetworksMapDetailViewController: EQNBaseMapViewController { class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
private enum PinStyle: CaseIterable {
case full
case light
case circle
func next() -> Self {
let all = Self.allCases
let idx = all.firstIndex(of: self)!
let next = all.index(after: idx)
return all[next == all.endIndex ? all.startIndex : next]
}
}
private var pinStyle: PinStyle = .full private var pinStyle: MapPinStyle {
get { AppPreferences.shared.mapPinStyle }
set { AppPreferences.shared.mapPinStyle = newValue }
}
private let eqnSeismic = EQNSeismic.shared private let eqnSeismic = EQNSeismic.shared
// MARK: - State // MARK: - State
@@ -73,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()
@@ -116,9 +109,9 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
} }
override func registerMapAnnotationViews() { override func registerMapAnnotationViews() {
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierFull) mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full))
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierLight) mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light))
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierCircle) mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle))
} }
override func loadDataSource() { override func loadDataSource() {
@@ -138,7 +131,11 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
} }
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) {
@@ -188,7 +185,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
// MARK: - Private // MARK: - Private
private func nextPinStyle() { private func nextPinStyle() {
pinStyle = pinStyle.next() pinStyle.advance()
reloadMap() reloadMap()
} }
@@ -199,6 +196,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
case .inRadius: NSLocalizedString("filter_area", comment: "") case .inRadius: NSLocalizedString("filter_area", comment: "")
case .positionRelevant: NSLocalizedString("filter_relevant", comment: "") case .positionRelevant: NSLocalizedString("filter_relevant", comment: "")
case .worldWide: NSLocalizedString("filter_all", comment: "") case .worldWide: NSLocalizedString("filter_all", comment: "")
case .userFelt: NSLocalizedString("filter_felt", comment: "")
} }
seismicsFilterLabel.text = text seismicsFilterLabel.text = text
} }
@@ -240,22 +238,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
} }
let isUserSelection = annotation.seismic == seismic let isUserSelection = annotation.seismic == seismic
switch pinStyle { return annotation.toAnnotationView(mapView: mapView, style: pinStyle, isUserSelection: isUserSelection)
case .full, .light:
let identifier = pinStyle == .full ? EQNSeismicAnnotationView.IdentifierFull : EQNSeismicAnnotationView.IdentifierLight
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as! EQNSeismicAnnotationView
annotationView.title = annotation.title
annotationView.subtitle = annotation.subtitle
annotationView.magnitude = String(format: "M%.1f", annotation.seismic.magnitude.doubleValue)
annotationView.magnitudeColor = annotation.textColor
annotationView.isUserSelection = isUserSelection
return annotationView
case .circle:
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNSeismicAnnotationView.IdentifierCircle, for: annotation) as! EQNSeismicAnnotationView
annotationView.image = annotation.image(height: EQNSeismicAnnotationView.CircleViewHeight,
isUserSelection: isUserSelection)
return annotationView
}
} }
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
@@ -270,6 +253,33 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
} }
} }
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)
@@ -7,6 +7,7 @@
// //
import UIKit import UIKit
import SwiftUI
import EventKitUI import EventKitUI
import DZNEmptyDataSet import DZNEmptyDataSet
import Shogun import Shogun
@@ -15,7 +16,13 @@ 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"
@@ -24,8 +31,8 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
// MARK: - Internal // MARK: - Internal
/// 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
@@ -34,8 +41,17 @@ 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 /// Push notification opened by the user
@@ -47,11 +63,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
private var scrollToOpenedSeismic = false private var scrollToOpenedSeismic = false
/// Current displayed controller with map /// Current displayed controller with map
private weak var currentMapController: SeismicNetworksMapDetailViewController? private weak var currentMapController: SeismicNetworksMapDetailViewController?
/// Keep track of the current cell at the center
private var currentCenteredIndexPath: IndexPath?
// MARK: - UI // MARK: - UI
@IBOutlet private weak var expandeCollapseButton: UIBarButtonItem! @IBOutlet private weak var displayModeButton: UIBarButtonItem!
@IBOutlet private weak var sortButton: UIBarButtonItem!
private var tableViewTopConstraint: NSLayoutConstraint? private var tableViewTopConstraint: NSLayoutConstraint?
private lazy var tableView: UITableView = { private lazy var tableView: UITableView = {
@@ -59,6 +76,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
tableView.translatesAutoresizingMaskIntoConstraints = false tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self tableView.delegate = self
tableView.dataSource = self tableView.dataSource = self
tableView.showsVerticalScrollIndicator = false
return tableView return tableView
}() }()
@@ -85,6 +103,40 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
label.textAlignment = .center label.textAlignment = .center
return label 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
@@ -121,13 +173,44 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
// MARK: - Private // MARK: - Private
private func setupUI() { private func setupUI() {
view.backgroundColor = tableView.backgroundColor
view.addSubview(scrollIndicatorView)
view.addSubview(tableView) 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 = tableView.topAnchor.constraint(equalTo: view.topAnchor)
tableViewTopConstraint?.isActive = true tableViewTopConstraint?.isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true tableView.trailingAnchor.constraint(equalTo: scrollIndicatorView.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).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) { private func setupFilterView(isVisible: Bool) {
@@ -164,34 +247,18 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
tableView.estimatedRowHeight = 300.0 tableView.estimatedRowHeight = 300.0
tableView.rowHeight = UITableView.automaticDimension tableView.rowHeight = UITableView.automaticDimension
tableView.registerCell(for: SeismicNetworkTableViewCell.self) tableView.registerCell(for: SeismicNetworkTableViewCell.self)
tableView.registerCell(for: SeismicNetworkMinimalTableViewCell.self)
tableView.registerCell(for: SeismicNetworkAdvertiseTableViewCell.self) tableView.registerCell(for: SeismicNetworkAdvertiseTableViewCell.self)
tableView.emptyDataSetSource = self tableView.emptyDataSetSource = self
tableView.separatorStyle = .none tableView.separatorStyle = .none
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
setupSortMenu()
}
private func setupSortMenu() {
let currentSort = EQNSeismic.shared.sort
sortButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
UIAction(title: NSLocalizedString("sort_date", comment: ""), image: UIImage(systemName: "calendar"), state: currentSort == .time ? .on : .off) { [weak self ] _ in
self?.changeSort(to: .time)
},
UIAction(title: NSLocalizedString("sort_position", comment: ""), image: UIImage(systemName: "ruler"), state: currentSort == .position ? .on : .off) { [weak self] _ in
self?.changeSort(to: .position)
},
UIAction(title: NSLocalizedString("sort_magnitude", comment: ""), image: UIImage(systemName: "thermometer"), state: currentSort == .magnitude ? .on : .off) { [weak self] _ in
self?.changeSort(to: .magnitude)
}
])
} }
private func checkForLocation() { private func checkForLocation() {
// check if a valid location is available, // check if a valid location is available,
// otherwise change the filter settings // otherwise change the filter settings
if !isLocationAvailable() { if !isLocationAvailable() {
EQNSeismic.shared.filterOption = .worldWide updateFilter(type: .worldWide)
EQNSeismic.shared.saveFilters()
} }
} }
@@ -210,7 +277,7 @@ 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
@@ -220,6 +287,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
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) {
@@ -235,17 +308,17 @@ 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:
if informations.contains(.buttons) { displayModeButton.image = UIImage(systemName: "2.square")
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-collapse") case .minimal:
} else { displayModeButton.image = UIImage(systemName: "3.square")
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-expand")
} }
tableView.reloadData() tableView.reloadData()
updateCenterCellIndexPath()
if scrollToOpenedSeismic, let index = getSeismics().firstIndex(where: { isSeismicToHighlight(seismic: $0) }) { if scrollToOpenedSeismic, let index = getSeismics().firstIndex(where: { isSeismicToHighlight(seismic: $0) }) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true) tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
@@ -254,7 +327,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
} }
private func loadAd() { private func loadAd() {
adLoader.load(GADRequest()) adLoader.load(Request())
} }
private func loadData(forced: Bool) { private func loadData(forced: Bool) {
@@ -266,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() {
@@ -278,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] {
@@ -291,10 +368,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
} }
private func changeSort(to sort: EQNSeismic.Sort) { private func changeSort(to sort: EQNSeismic.Sort) {
EQNSeismic.shared.sort = sort updateFilter(sort: sort)
EQNSeismic.shared.saveFilters()
setupSortMenu()
refreshUI() refreshUI()
} }
@@ -524,11 +598,22 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
private func updateFilter( private func updateFilter(
type: EQNSeismic.FilterType? = nil, type: EQNSeismic.FilterType? = nil,
sort: EQNSeismic.Sort? = nil,
radius: Double? = nil, radius: Double? = nil,
magnitude: Double? = nil magnitude: Double? = nil
) { ) {
if let type { if let type {
let previous = EQNSeismic.shared.filterOption
if previous == .userFelt && previous != type {
loadData(forced: true)
}
EQNSeismic.shared.filterOption = type EQNSeismic.shared.filterOption = type
model.filter = type
}
if let sort {
EQNSeismic.shared.sort = sort
model.sort = sort
} }
if let radius { if let radius {
EQNSeismic.shared.maximumDistance = String(format: "%.0f", radius) EQNSeismic.shared.maximumDistance = String(format: "%.0f", radius)
@@ -560,24 +645,65 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
return false 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 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()
} }
@@ -591,17 +717,26 @@ 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(cellIdentifiable: SeismicNetworkTableViewCell.self, for: indexPath) 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
} }
let isPushSelected = isSeismicToHighlight(seismic: seismic)
cell.configure(with: seismic, type: type, informations: informations, isPushSelected: isPushSelected)
cell.delegate = self
return cell
case .advertise(let nativeAd): case .advertise(let nativeAd):
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkAdvertiseTableViewCell.self, for: indexPath) let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkAdvertiseTableViewCell.self, for: indexPath)
cell.loadNativeAd(nativeAd) cell.loadNativeAd(nativeAd)
@@ -618,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) {
@@ -673,24 +814,24 @@ 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
@@ -707,7 +848,7 @@ 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 }
@@ -716,23 +857,29 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
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 }
showIntensityMap(for: seismic)
}
func seismicNetworkCellDidTapCalendar(_ 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 }
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 }
@@ -751,6 +898,7 @@ 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()
} }
@@ -13,14 +13,17 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
private enum RowIdentifier: Int { private enum RowIdentifier: Int {
case abilitaNotifiche case abilitaNotifiche
case disabilitaSuonoAllerta
case abilitaCriticalAlerts case abilitaCriticalAlerts
} }
private var isNotificationEnabled = false private var isNotificationEnabled = false
private var isMildQuakeSoundDisabled = false
private var isCriticalAlertsEnabled = false private var isCriticalAlertsEnabled = false
private let settings: [SettingItem] = [ private let settings: [SettingItem] = [
.init(type: .enable, title: NSLocalizedString("options_notification_enable_alarm", comment: "")), .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: "")) .init(type: .enable, title: NSLocalizedString("critical_alerts_setting", comment: ""))
] ]
@@ -56,6 +59,7 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
let saved = EQNSettingRealTimeAlert.shared let saved = EQNSettingRealTimeAlert.shared
isNotificationEnabled = saved.isAbilitato isNotificationEnabled = saved.isAbilitato
isMildQuakeSoundDisabled = saved.isMildQuakeSoundDisabled
isCriticalAlertsEnabled = saved.isCriticalAlertsEnabled isCriticalAlertsEnabled = saved.isCriticalAlertsEnabled
} }
@@ -93,6 +97,11 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
cell.valueChanged = { [weak self] enabled in cell.valueChanged = { [weak self] enabled in
self?.onChangeNotificationEnabled(enabled) self?.onChangeNotificationEnabled(enabled)
} }
case .disabilitaSuonoAllerta:
cell.toggleSwitch.isOn = isMildQuakeSoundDisabled
cell.valueChanged = { [weak self] enabled in
self?.onChangeDisableSoundEnabled(enabled)
}
case .abilitaCriticalAlerts: case .abilitaCriticalAlerts:
cell.toggleSwitch.isOn = isCriticalAlertsEnabled cell.toggleSwitch.isOn = isCriticalAlertsEnabled
cell.valueChanged = { [weak self] enabled in cell.valueChanged = { [weak self] enabled in
@@ -116,11 +125,29 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
tableView.reloadData() tableView.reloadData()
} }
private func onChangeDisableSoundEnabled(_ enabled: Bool) {
isMildQuakeSoundDisabled = enabled
EQNSettingRealTimeAlert.shared.isMildQuakeSoundDisabled = isMildQuakeSoundDisabled
EQNSettingRealTimeAlert.shared.saveUserInfo()
tableView.reloadData()
}
private func onChangeCriticalAlertsEnabled(_ enabled: Bool) { private func onChangeCriticalAlertsEnabled(_ enabled: Bool) {
if enabled {
askForCriticalAlertsPermission()
}
isCriticalAlertsEnabled = enabled isCriticalAlertsEnabled = enabled
EQNSettingRealTimeAlert.shared.isCriticalAlertsEnabled = isCriticalAlertsEnabled EQNSettingRealTimeAlert.shared.isCriticalAlertsEnabled = isCriticalAlertsEnabled
EQNSettingRealTimeAlert.shared.saveUserInfo() EQNSettingRealTimeAlert.shared.saveUserInfo()
tableView.reloadData() tableView.reloadData()
} }
private func askForCriticalAlertsPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [ .criticalAlert ]) { granted, error in
// nope
}
}
} }
+1 -3
View File
@@ -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 -4
View File
@@ -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&apos;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&apos;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&apos;accesso alla libreria è richiesto per poter salvare le immagini generate dall&apos;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&apos;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>
+147
View File
@@ -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)
}
}
} }
@@ -17,69 +17,15 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
func execute() { func execute() {
print("[EQNUserDefaultsCommand] Start execute") print("[EQNUserDefaultsCommand] Start execute")
applyDefaultSettings()
saveMissingValues()
migrationV5_3()
migrationV5_4()
migrationV5_8() migrationV5_8()
migrationFirstAppStat()
migrationCriticalAlerts()
migrationV5_9()
migrationV5_10()
} }
// MARK: - Private // MARK: - Private
private func applyDefaultSettings() {
// seismic card settings
if UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) == nil {
let informations: [SeismicNetworkTableViewCell.InformationType] = [.buttons, .distance, .coordinate, .population]
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
}
}
private func saveMissingValues() {
// `raggio sismi forti` was not saved before v2.3
if UserDefaults.standard.object(forKey: UserDefaults.AllertaSismicaRaggioSismiForti) == nil {
UserDefaults.standard.set("600", forKey: UserDefaults.AllertaSismicaRaggioSismiForti)
}
}
private func migrationV5_3() {
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_3)
if migrationPerformed {
print("[EQNUserDefaultsCommand] Migration v5.3 already performed")
return
}
// l'ultima posizione era salvata come array, la trasformiamo in valore singolo
let lastLocations = EQNUtility.loadArray(of: CLLocation.self, fromUserDefaultsForKey: UserDefaults.UserDataLastLocation) as? [CLLocation]
if let lastLocation = lastLocations?.last {
UserDefaults.standard.removeObject(forKey: UserDefaults.UserDataLastLocation)
EQNUserData.shared.saveLastLocation(lastLocation)
}
// resettiamo il Firebase token in modo da ri-eseguire la procedura di registrazione corretta
EQNUserData.shared.saveFirebaseToken(nil)
UserDefaults.standard.set(true, forKey: UserDefaults.AppMigrationV5_3)
}
private func migrationV5_4() {
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_4)
if migrationPerformed {
print("[EQNUserDefaultsCommand] Migration v5.4 already performed")
return
}
// migriamo l'ultima posizione negli user defaults condivisi
let userDefaults = UserDefaults.standard
let groupUserDefaults = UserDefaults.appGroup
if let encodedLocation = userDefaults.object(forKey: UserDefaults.UserDataLastLocation) as? Data {
groupUserDefaults?.set(encodedLocation, forKey: UserDefaults.UserDataLastLocation)
}
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_4)
}
private func migrationV5_8() { private func migrationV5_8() {
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_8) let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_8)
if migrationPerformed { if migrationPerformed {
@@ -115,4 +61,78 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_8) 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)
}
} }
+18 -5
View File
@@ -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];
@@ -123,11 +134,13 @@
// L'endpoint per lo scaricamento dei dati prende due parametri: // L'endpoint per lo scaricamento dei dati prende due parametri:
// - `pro` per il provider selezionato, // - `pro` per il provider selezionato,
// - `mag` per la magnitudo minima. // - `mag` per la magnitudo minima.
// Dalla v5.8 non esiste più la selezione delle reti, quindi passiamo sempre "ALL". // Dalla v5.8 non esiste più la selezione delle reti, quindi il provider da passare diventa:
// - `FELT` se filtro selezionato è sismi percepiti
// - `ALL` in tutti gli altri casi
// Per la magnitudo minima, invece, passiamo 0 per i filtri "raggio" e "rilevanti, // Per la magnitudo minima, invece, passiamo 0 per i filtri "raggio" e "rilevanti,
// altrimenti passiamo 2 per il filtro "mondo" // altrimenti passiamo 2 per il filtro "mondo"
NSString *filterProvider = @"ALL"; NSString *filterProvider = [EQNSeismic shared].filterOption == FilterTypeUserFelt ? @"FELT" : @"ALL";
NSString *filterMagnitude = [EQNSeismic shared].filterOption == FilterTypeWorldWide ? @"2.0" : @"0.0"; 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];
@@ -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)
@@ -15,9 +15,10 @@ import Foundation
case inRadius case inRadius
case positionRelevant case positionRelevant
case worldWide case worldWide
case userFelt
} }
enum Sort: Int { enum Sort: Int, CaseIterable {
case time case time
case position case position
case magnitude case magnitude
@@ -173,11 +174,16 @@ import Foundation
} else if magnitude < 1.5 && distance > 20 { } else if magnitude < 1.5 && distance > 20 {
keep = false keep = false
} }
} else { } else if filterOption == .worldWide {
//filtro che mostra tutti i sismi a livello mondiale di magnitudo>=2 //filtro che mostra tutti i sismi a livello mondiale di magnitudo>=2
if magnitude < 2 { if magnitude < 2 {
keep = false keep = false
} }
} else if filterOption == .userFelt {
//filtro che mostra i sismi segnalati da più di 1 utente
if seismic.userNumber.intValue < 2 {
keep = false
}
} }
} }
@@ -0,0 +1,15 @@
//
// EQNShakemap.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import Foundation
struct EQNShakemap: Decodable {
let lat: [Int]
let lon: [Int]
let intensity: Float
}
@@ -33,6 +33,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong) NSNumber *preliminary; @property (nonatomic, strong) NSNumber *preliminary;
@property (nonatomic, strong) NSNumber *smartphoneNumber; @property (nonatomic, strong) NSNumber *smartphoneNumber;
@property (nonatomic, strong) NSNumber *userNumber; @property (nonatomic, strong) NSNumber *userNumber;
/// Code to show "intensity map"
@property (nonatomic, strong) NSString *isoCode;
- (instancetype)initWithInfo:(NSDictionary *)info; - (instancetype)initWithInfo:(NSDictionary *)info;
@@ -42,6 +42,8 @@
self.preliminary = info[@"py"]; self.preliminary = info[@"py"];
self.smartphoneNumber = info[@"sm"]; self.smartphoneNumber = info[@"sm"];
self.userNumber = info[@"rp"]; self.userNumber = info[@"rp"];
self.isoCode = info[@"iso"];
} }
return self; return self;
} }
@@ -65,6 +67,7 @@
[encoder encodeObject:self.preliminary forKey:@"preliminary"]; [encoder encodeObject:self.preliminary forKey:@"preliminary"];
[encoder encodeObject:self.smartphoneNumber forKey:@"smartphoneNumber"]; [encoder encodeObject:self.smartphoneNumber forKey:@"smartphoneNumber"];
[encoder encodeObject:self.userNumber forKey:@"userNumber"]; [encoder encodeObject:self.userNumber forKey:@"userNumber"];
[encoder encodeObject:self.isoCode forKey:@"isoCode"];
} }
- (instancetype)initWithCoder:(NSCoder *)decoder - (instancetype)initWithCoder:(NSCoder *)decoder
@@ -86,6 +89,7 @@
self.preliminary = [decoder decodeObjectForKey:@"preliminary"]; self.preliminary = [decoder decodeObjectForKey:@"preliminary"];
self.smartphoneNumber = [decoder decodeObjectForKey:@"smartphoneNumber"]; self.smartphoneNumber = [decoder decodeObjectForKey:@"smartphoneNumber"];
self.userNumber = [decoder decodeObjectForKey:@"userNumber"]; self.userNumber = [decoder decodeObjectForKey:@"userNumber"];
self.isoCode = [decoder decodeObjectForKey:@"isoCode"];
} }
return self; return self;
} }
@@ -42,6 +42,7 @@
self.registrationInProgress = NO; self.registrationInProgress = NO;
self.user_ID = EQNUserData.sharedData.userId; self.user_ID = EQNUserData.sharedData.userId;
self.lastPosition = EQNUserData.sharedData.lastLocation; self.lastPosition = EQNUserData.sharedData.lastLocation;
self.currentFirebaseToken = EQNUserData.sharedData.firebaseToken;
[self registerForLocationUpdates]; [self registerForLocationUpdates];
} }
@@ -19,7 +19,15 @@ import CoreLocation
@objc @objc
var isFirstStart: Bool { var isFirstStart: Bool {
firebaseToken == nil let firstAppStartExecuted = UserDefaults.standard.bool(forKey: UserDefaults.FirstAppStartExecuted)
return !firstAppStartExecuted
}
// MARK: - Permissions
@objc
var locationAuthorizationStatus: CLAuthorizationStatus {
CLLocationManager().authorizationStatus
} }
// MARK: - Firebase Token // MARK: - Firebase Token
@@ -20,6 +20,7 @@
{ {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Europe/Rome"]; formatter.timeZone = [NSTimeZone timeZoneWithName:@"Europe/Rome"];
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
return [formatter dateFromString:dateString]; return [formatter dateFromString:dateString];
} }
@@ -0,0 +1,48 @@
//
// EQNMapAnnotationShakemap.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import UIKit
import MapKit
class EQNMapAnnotationShakemap: NSObject, MKAnnotation {
let coordinate: CLLocationCoordinate2D
let shakemap: EQNShakemap
var intensityColor: UIColor?
var intensityTextColor: UIColor?
let title: String?
let subtitle: String? = nil
init(
coordinate: CLLocationCoordinate2D,
shakemap: EQNShakemap
) {
self.coordinate = coordinate
self.shakemap = shakemap
self.title = Self.title(for: shakemap.intensity)
}
// MARK: - Private
private static func title(for intensity: Float) -> String {
return switch intensity {
case 0..<2.5: NSLocalizedString("mercalli_II", comment: "")
case 2.5..<3.5: NSLocalizedString("mercalli_III", comment: "")
case 3.5..<4.5: NSLocalizedString("mercalli_IV", comment: "")
case 4.5..<5.5: NSLocalizedString("mercalli_V", comment: "")
case 5.5..<6.5: NSLocalizedString("mercalli_VI", comment: "")
case 6.5..<7.5: NSLocalizedString("mercalli_VII", comment: "")
case 7.5..<8.5: NSLocalizedString("mercalli_VIII", comment: "")
case 8.5..<9.5: NSLocalizedString("mercalli_IX", comment: "")
case 9.5..<10.5: NSLocalizedString("mercalli_X", comment: "")
case 10.5..<11.5: NSLocalizedString("mercalli_XI", comment: "")
default: NSLocalizedString("mercalli_XII", comment: "")
}
}
}
@@ -0,0 +1,15 @@
//
// MapPinStyle.swift
// Earthquake Network
//
// Created by Andrea Busi on 28/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import Foundation
enum MapPinStyle: Int, CaseIterable {
case circle
case light
case full
}
@@ -16,11 +16,12 @@ class EQNSettingRealTimeAlert: NSObject {
static let shared = EQNSettingRealTimeAlert() static let shared = EQNSettingRealTimeAlert()
@objc var isAbilitato: Bool @objc var isAbilitato: Bool
@objc var isCriticalAlertsEnabled: Bool var isMildQuakeSoundDisabled: Bool
var isCriticalAlertsEnabled: Bool
@objc var sismiDaNotificare: String var sismiDaNotificare: String
@objc var raggioSismiLievi: String @objc var raggioSismiLievi: String
@objc var raggioSismiForti: String var raggioSismiForti: String
private static let DefaultSismiDaNotificare = "0" private static let DefaultSismiDaNotificare = "0"
private static let DefaultRaggioSismiLievi = "250" private static let DefaultRaggioSismiLievi = "250"
@@ -38,6 +39,7 @@ class EQNSettingRealTimeAlert: NSObject {
let sharedDefaults = UserDefaults.appGroup let sharedDefaults = UserDefaults.appGroup
isCriticalAlertsEnabled = sharedDefaults?.bool(forKey: UserDefaults.AllertaSismicaCriticalAlerts) ?? false isCriticalAlertsEnabled = sharedDefaults?.bool(forKey: UserDefaults.AllertaSismicaCriticalAlerts) ?? false
isMildQuakeSoundDisabled = sharedDefaults?.bool(forKey: UserDefaults.AllertaSismicaSuonoDisabilitatoSismaDebole) ?? true
} }
// MARK: - Public // MARK: - Public
@@ -50,12 +52,14 @@ class EQNSettingRealTimeAlert: NSObject {
defaults.set(raggioSismiForti, forKey: UserDefaults.AllertaSismicaRaggioSismiForti) defaults.set(raggioSismiForti, forKey: UserDefaults.AllertaSismicaRaggioSismiForti)
if let sharedDefaults = UserDefaults.appGroup { if let sharedDefaults = UserDefaults.appGroup {
sharedDefaults.set(isCriticalAlertsEnabled, forKey: UserDefaults.AllertaSismicaCriticalAlerts) sharedDefaults.set(isCriticalAlertsEnabled, forKey: UserDefaults.AllertaSismicaCriticalAlerts)
sharedDefaults.set(isMildQuakeSoundDisabled, forKey: UserDefaults.AllertaSismicaSuonoDisabilitatoSismaDebole)
} }
} }
@objc class func saveDefaultValues() { @objc class func saveDefaultValues() {
shared.isAbilitato = true shared.isAbilitato = true
shared.isCriticalAlertsEnabled = false shared.isCriticalAlertsEnabled = false
shared.isMildQuakeSoundDisabled = true
shared.sismiDaNotificare = Self.DefaultSismiDaNotificare shared.sismiDaNotificare = Self.DefaultSismiDaNotificare
shared.raggioSismiLievi = Self.DefaultRaggioSismiLievi shared.raggioSismiLievi = Self.DefaultRaggioSismiLievi
shared.raggioSismiForti = Self.DefaultRaggioSismiForti shared.raggioSismiForti = Self.DefaultRaggioSismiForti
@@ -1,21 +0,0 @@
{
"images" : [
{
"filename" : "twitter_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

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

After

Width:  |  Height:  |  Size: 281 B

@@ -0,0 +1,122 @@
//
// APIService.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import Foundation
import Shogun
fileprivate enum EQNetworkAPI {
static let scheme = "https"
static let host = "cache.earthquakenetwork.it"
static let timeout: TimeInterval = 20.0
}
class APIService {
static let shared = APIService()
private enum Endpoint: String {
case shakemap = "distquake_download_shakemap.php"
var method: HttpMethod {
.get
}
}
private enum HttpMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
case patch = "PATCH"
}
// MARK: - Public
func fetchShakemap(
isoCode: String
) async throws -> [EQNShakemap] {
try await performRequest(
endpoint: .shakemap,
paramaters: ["iso_id": isoCode]
)
}
// MARK: - Private
private func performRequest<T: Decodable>(
endpoint: Endpoint,
paramaters: [String: String] = [:],
body: Encodable? = nil
) async throws -> T {
var components = makeComponents(for: endpoint)
// add query string parameters to the existing ones
let queryParameters = paramaters.map { key, value in URLQueryItem(name: key, value: value) }
let queryItems = components.queryItems ?? []
components.queryItems = queryItems + queryParameters
guard let url = components.url else {
Log.error(tag: Self.TAG, "Unable to create URL for the request")
throw "Unable to create url for the request"
}
var request = URLRequest(
url: url,
cachePolicy: .reloadIgnoringLocalCacheData,
timeoutInterval: EQNetworkAPI.timeout
)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = endpoint.method.rawValue
Log.debug(tag: Self.TAG, "Request url: \(url)")
if let body {
do {
let bodyData = try JSONEncoder().encode(body)
request.httpBody = bodyData
} catch {
Log.error(tag: Self.TAG, "Unable to encode body data: \(error)")
throw error
}
}
do {
let response = try await URLSession.shared.data(for: request).0
let responseString = String(data: response, encoding: .utf8) ?? "No data"
Log.info(tag: Self.TAG, responseString)
return try JSONDecoder().decode(T.self, from: response)
} catch {
Log.error(tag: Self.TAG, "Decoding error: \(error)")
throw error
}
}
// MARK: - Internal
private func makeComponents(for endpoint: Endpoint) -> URLComponents {
var components = URLComponents()
components.scheme = EQNetworkAPI.scheme
components.host = EQNetworkAPI.host
components.path = "/" + endpoint.rawValue
return components
}
}
extension APIService: Loggable { }
/// Easily throw generic errors with a text description.
extension String: @retroactive Error {}
extension String: @retroactive LocalizedError {
public var errorDescription: String? {
return self
}
}
@@ -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="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="CWo-PE-Dqp"> <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="CWo-PE-Dqp">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/> <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="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.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"/>
@@ -21,27 +21,20 @@
</view> </view>
<navigationItem key="navigationItem" id="6kD-jk-5Aw"> <navigationItem key="navigationItem" id="6kD-jk-5Aw">
<rightBarButtonItems> <rightBarButtonItems>
<barButtonItem image="1.square" catalog="system" id="HTN-07-s5p">
<connections>
<action selector="collapseExpandTapped:" destination="tVM-DH-fmv" id="EnD-92-5ZX"/>
</connections>
</barButtonItem>
<barButtonItem image="navbar-icon-refresh" id="ZJh-jF-ILm"> <barButtonItem image="navbar-icon-refresh" id="ZJh-jF-ILm">
<connections> <connections>
<action selector="refreshDataTapped:" destination="tVM-DH-fmv" id="qs5-jS-0Op"/> <action selector="refreshDataTapped:" destination="tVM-DH-fmv" id="qs5-jS-0Op"/>
</connections> </connections>
</barButtonItem> </barButtonItem>
<barButtonItem image="navbar-icon-filters" id="vOM-Np-CIk">
<connections>
<action selector="openFilterTapped:" destination="tVM-DH-fmv" id="76a-Bl-bCj"/>
</connections>
</barButtonItem>
<barButtonItem image="navbar-icon-sort" id="LyU-KI-3Mb"/>
<barButtonItem image="navbar-icon-arrow-collapse" id="HTN-07-s5p">
<connections>
<action selector="collapseExpandTapped:" destination="tVM-DH-fmv" id="EnD-92-5ZX"/>
</connections>
</barButtonItem>
</rightBarButtonItems> </rightBarButtonItems>
</navigationItem> </navigationItem>
<connections> <connections>
<outlet property="expandeCollapseButton" destination="HTN-07-s5p" id="lxP-Im-NME"/> <outlet property="displayModeButton" destination="HTN-07-s5p" id="Lhc-Od-MvL"/>
<outlet property="sortButton" destination="LyU-KI-3Mb" id="969-Zg-YBB"/>
<segue destination="6LP-zk-O1z" kind="presentation" identifier="ShowFilters" modalPresentationStyle="overCurrentContext" modalTransitionStyle="crossDissolve" id="Nzu-iH-UgB"/> <segue destination="6LP-zk-O1z" kind="presentation" identifier="ShowFilters" modalPresentationStyle="overCurrentContext" modalTransitionStyle="crossDissolve" id="Nzu-iH-UgB"/>
<segue destination="Rfp-kt-2Kx" kind="presentation" identifier="ShowCardSettings" modalPresentationStyle="overCurrentContext" modalTransitionStyle="crossDissolve" id="VWw-16-xGw"/> <segue destination="Rfp-kt-2Kx" kind="presentation" identifier="ShowCardSettings" modalPresentationStyle="overCurrentContext" modalTransitionStyle="crossDissolve" id="VWw-16-xGw"/>
</connections> </connections>
@@ -59,7 +52,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="jl5-sK-UaA"> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="jl5-sK-UaA">
<rect key="frame" x="0.0" y="144" width="414" height="614"/> <rect key="frame" x="0.0" y="192" width="414" height="532"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections> <connections>
<outlet property="dataSource" destination="fbo-Ug-IiE" id="meg-jS-D2z"/> <outlet property="dataSource" destination="fbo-Ug-IiE" id="meg-jS-D2z"/>
@@ -67,7 +60,7 @@
</connections> </connections>
</tableView> </tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4mK-no-RDk"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4mK-no-RDk">
<rect key="frame" x="0.0" y="758" width="414" height="55"/> <rect key="frame" x="0.0" y="724" width="414" height="55"/>
<color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/> <color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="55" id="1Dm-ex-6od"/> <constraint firstAttribute="height" constant="55" id="1Dm-ex-6od"/>
@@ -122,28 +115,28 @@
<blurEffect style="light"/> <blurEffect style="light"/>
</visualEffectView> </visualEffectView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rX7-cf-eHr"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rX7-cf-eHr">
<rect key="frame" x="41.5" y="313" width="331" height="270.5"/> <rect key="frame" x="31" y="313" width="352" height="270.5"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Card settings" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pYc-TM-AIW"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Card settings" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pYc-TM-AIW">
<rect key="frame" x="12" y="12" width="307" height="33.5"/> <rect key="frame" x="12" y="12" width="328" height="33.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/> <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Dzd-L1-MsF"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Dzd-L1-MsF">
<rect key="frame" x="12" y="75.5" width="307" height="133"/> <rect key="frame" x="12" y="75.5" width="328" height="133"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="dKu-4z-j0d"> <stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="dKu-4z-j0d">
<rect key="frame" x="0.0" y="0.0" width="307" height="31"/> <rect key="frame" x="0.0" y="0.0" width="328" height="31"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Distance" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dRl-jP-icD"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Distance" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dRl-jP-icD">
<rect key="frame" x="0.0" y="0.0" width="238" height="31"/> <rect key="frame" x="0.0" y="0.0" width="259" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g8g-7l-bCj"> <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g8g-7l-bCj">
<rect key="frame" x="258" y="0.0" width="51" height="31"/> <rect key="frame" x="279" y="0.0" width="51" height="31"/>
<connections> <connections>
<action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="bzY-wG-8Qw"/> <action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="bzY-wG-8Qw"/>
</connections> </connections>
@@ -151,16 +144,16 @@
</subviews> </subviews>
</stackView> </stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="toG-0g-rVB"> <stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="toG-0g-rVB">
<rect key="frame" x="0.0" y="51" width="307" height="31"/> <rect key="frame" x="0.0" y="51" width="328" height="31"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Coordinates" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vyf-82-r1X"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Coordinates" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vyf-82-r1X">
<rect key="frame" x="0.0" y="0.0" width="238" height="31"/> <rect key="frame" x="0.0" y="0.0" width="259" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jjd-6X-t3e"> <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jjd-6X-t3e">
<rect key="frame" x="258" y="0.0" width="51" height="31"/> <rect key="frame" x="279" y="0.0" width="51" height="31"/>
<connections> <connections>
<action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="QI3-0U-fDp"/> <action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="QI3-0U-fDp"/>
</connections> </connections>
@@ -168,16 +161,16 @@
</subviews> </subviews>
</stackView> </stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="CU8-Db-J5u"> <stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="CU8-Db-J5u">
<rect key="frame" x="0.0" y="102" width="307" height="31"/> <rect key="frame" x="0.0" y="102" width="328" height="31"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Population" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hpe-Qx-6yX"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Population" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hpe-Qx-6yX">
<rect key="frame" x="0.0" y="0.0" width="238" height="31"/> <rect key="frame" x="0.0" y="0.0" width="259" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="N7g-C0-b1h"> <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="N7g-C0-b1h">
<rect key="frame" x="258" y="0.0" width="51" height="31"/> <rect key="frame" x="279" y="0.0" width="51" height="31"/>
<connections> <connections>
<action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="U53-Iv-BcF"/> <action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="U53-Iv-BcF"/>
</connections> </connections>
@@ -195,7 +188,7 @@
</constraints> </constraints>
</stackView> </stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="l7i-w5-sp0"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="l7i-w5-sp0">
<rect key="frame" x="219" y="228.5" width="100" height="30"/> <rect key="frame" x="219" y="228.5" width="121" height="30"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="30" id="2j0-dH-u0p"/> <constraint firstAttribute="height" constant="30" id="2j0-dH-u0p"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="Dpt-7I-vJu"/> <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="Dpt-7I-vJu"/>
@@ -227,7 +220,7 @@
<constraint firstItem="8QE-80-TMN" firstAttribute="leading" secondItem="1tH-EZ-OWj" secondAttribute="leading" id="FLK-lL-lW7"/> <constraint firstItem="8QE-80-TMN" firstAttribute="leading" secondItem="1tH-EZ-OWj" secondAttribute="leading" id="FLK-lL-lW7"/>
<constraint firstItem="rX7-cf-eHr" firstAttribute="centerX" secondItem="Q06-4a-yjE" secondAttribute="centerX" id="LDx-5Z-nve"/> <constraint firstItem="rX7-cf-eHr" firstAttribute="centerX" secondItem="Q06-4a-yjE" secondAttribute="centerX" id="LDx-5Z-nve"/>
<constraint firstItem="1tH-EZ-OWj" firstAttribute="trailing" secondItem="8QE-80-TMN" secondAttribute="trailing" id="NEe-0Z-Ut5"/> <constraint firstItem="1tH-EZ-OWj" firstAttribute="trailing" secondItem="8QE-80-TMN" secondAttribute="trailing" id="NEe-0Z-Ut5"/>
<constraint firstItem="rX7-cf-eHr" firstAttribute="width" secondItem="Q06-4a-yjE" secondAttribute="width" multiplier="0.8" id="P27-yy-HrC"/> <constraint firstItem="rX7-cf-eHr" firstAttribute="width" secondItem="Q06-4a-yjE" secondAttribute="width" multiplier="0.85" id="P27-yy-HrC"/>
<constraint firstItem="8QE-80-TMN" firstAttribute="top" secondItem="Q06-4a-yjE" secondAttribute="top" id="gh4-42-aNP"/> <constraint firstItem="8QE-80-TMN" firstAttribute="top" secondItem="Q06-4a-yjE" secondAttribute="top" id="gh4-42-aNP"/>
<constraint firstItem="rX7-cf-eHr" firstAttribute="centerY" secondItem="Q06-4a-yjE" secondAttribute="centerY" id="kEj-37-bWM"/> <constraint firstItem="rX7-cf-eHr" firstAttribute="centerY" secondItem="Q06-4a-yjE" secondAttribute="centerY" id="kEj-37-bWM"/>
<constraint firstAttribute="bottom" secondItem="8QE-80-TMN" secondAttribute="bottom" id="vey-5u-Ovd"/> <constraint firstAttribute="bottom" secondItem="8QE-80-TMN" secondAttribute="bottom" id="vey-5u-Ovd"/>
@@ -276,17 +269,17 @@
<objects> <objects>
<viewController storyboardIdentifier="EQNLogViewController" id="noK-2F-IZE" customClass="EQNLogViewController" sceneMemberID="viewController"> <viewController storyboardIdentifier="EQNLogViewController" id="noK-2F-IZE" customClass="EQNLogViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="6P7-SV-yrd"> <view key="view" contentMode="scaleToFill" id="6P7-SV-yrd">
<rect key="frame" x="0.0" y="0.0" width="414" height="886"/> <rect key="frame" x="0.0" y="0.0" width="414" height="838"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="ija-iK-2hE"> <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="ija-iK-2hE">
<rect key="frame" x="0.0" y="20" width="414" height="808"/> <rect key="frame" x="0.0" y="20" width="414" height="760"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/> <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView> </textView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="g5C-Wg-DEu"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="g5C-Wg-DEu">
<rect key="frame" x="16" y="836" width="382" height="30"/> <rect key="frame" x="16" y="788" width="382" height="30"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="30" id="CFr-4Z-X1A"/> <constraint firstAttribute="height" constant="30" id="CFr-4Z-X1A"/>
</constraints> </constraints>
@@ -404,7 +397,7 @@
<tabBarItem key="tabBarItem" title="Settings" image="tabbar-icon-settings" id="5VO-yI-kw5"/> <tabBarItem key="tabBarItem" title="Settings" image="tabbar-icon-settings" id="5VO-yI-kw5"/>
<toolbarItems/> <toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="B9g-HM-VPb"> <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="B9g-HM-VPb">
<rect key="frame" x="0.0" y="48" width="414" height="96"/> <rect key="frame" x="0.0" y="96" width="414" height="96"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
@@ -433,16 +426,16 @@
<blurEffect style="light"/> <blurEffect style="light"/>
</visualEffectView> </visualEffectView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dnj-0w-OVz"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dnj-0w-OVz">
<rect key="frame" x="41.5" y="112" width="331" height="672"/> <rect key="frame" x="31" y="112" width="352" height="672"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Filters" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3tl-cp-QLk"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Filters" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3tl-cp-QLk">
<rect key="frame" x="12" y="16" width="307" height="20.5"/> <rect key="frame" x="12" y="16" width="328" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/> <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<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="MVi-r1-SFm"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MVi-r1-SFm">
<rect key="frame" x="276" y="630" width="39" height="30"/> <rect key="frame" x="297" y="630" width="39" height="30"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="30" id="NoD-eT-hcR"/> <constraint firstAttribute="height" constant="30" id="NoD-eT-hcR"/>
</constraints> </constraints>
@@ -452,7 +445,7 @@
</connections> </connections>
</button> </button>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UD6-Ee-0GM"> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UD6-Ee-0GM">
<rect key="frame" x="0.0" y="46.5" width="331" height="573.5"/> <rect key="frame" x="0.0" y="46.5" width="352" height="573.5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<connections> <connections>
<outlet property="dataSource" destination="6LP-zk-O1z" id="9ZD-Dr-D9f"/> <outlet property="dataSource" destination="6LP-zk-O1z" id="9ZD-Dr-D9f"/>
@@ -481,7 +474,7 @@
<constraint firstItem="cc3-jb-kKt" firstAttribute="leading" secondItem="Lp8-R3-fW9" secondAttribute="leading" id="A0P-4p-dRN"/> <constraint firstItem="cc3-jb-kKt" firstAttribute="leading" secondItem="Lp8-R3-fW9" secondAttribute="leading" id="A0P-4p-dRN"/>
<constraint firstAttribute="bottom" secondItem="cc3-jb-kKt" secondAttribute="bottom" id="EO3-iK-i5i"/> <constraint firstAttribute="bottom" secondItem="cc3-jb-kKt" secondAttribute="bottom" id="EO3-iK-i5i"/>
<constraint firstItem="dnj-0w-OVz" firstAttribute="height" secondItem="9GQ-SZ-2Mt" secondAttribute="height" multiplier="0.75" id="EWy-XB-uoC"/> <constraint firstItem="dnj-0w-OVz" firstAttribute="height" secondItem="9GQ-SZ-2Mt" secondAttribute="height" multiplier="0.75" id="EWy-XB-uoC"/>
<constraint firstItem="dnj-0w-OVz" firstAttribute="width" secondItem="9GQ-SZ-2Mt" secondAttribute="width" multiplier="0.8" id="ZHH-KA-oym"/> <constraint firstItem="dnj-0w-OVz" firstAttribute="width" secondItem="9GQ-SZ-2Mt" secondAttribute="width" multiplier="0.85" id="ZHH-KA-oym"/>
<constraint firstItem="dnj-0w-OVz" firstAttribute="centerX" secondItem="9GQ-SZ-2Mt" secondAttribute="centerX" id="fUr-JO-gPl"/> <constraint firstItem="dnj-0w-OVz" firstAttribute="centerX" secondItem="9GQ-SZ-2Mt" secondAttribute="centerX" id="fUr-JO-gPl"/>
<constraint firstItem="Lp8-R3-fW9" firstAttribute="trailing" secondItem="cc3-jb-kKt" secondAttribute="trailing" id="gfa-oV-YnD"/> <constraint firstItem="Lp8-R3-fW9" firstAttribute="trailing" secondItem="cc3-jb-kKt" secondAttribute="trailing" id="gfa-oV-YnD"/>
<constraint firstItem="dnj-0w-OVz" firstAttribute="centerY" secondItem="9GQ-SZ-2Mt" secondAttribute="centerY" id="i3x-eo-Lvg"/> <constraint firstItem="dnj-0w-OVz" firstAttribute="centerY" secondItem="9GQ-SZ-2Mt" secondAttribute="centerY" id="i3x-eo-Lvg"/>
@@ -506,7 +499,7 @@
<tabBarItem key="tabBarItem" title="Reports" image="tabbar-icon-reports" id="oaL-SG-Zpq"/> <tabBarItem key="tabBarItem" title="Reports" image="tabbar-icon-reports" id="oaL-SG-Zpq"/>
<toolbarItems/> <toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="xbw-SF-Eq7"> <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="xbw-SF-Eq7">
<rect key="frame" x="0.0" y="48" width="414" height="96"/> <rect key="frame" x="0.0" y="96" width="414" height="96"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
@@ -525,7 +518,7 @@
<tabBarItem key="tabBarItem" title="Alerts" image="tabbar-icon-alerts" id="aeo-GH-qCD"/> <tabBarItem key="tabBarItem" title="Alerts" image="tabbar-icon-alerts" id="aeo-GH-qCD"/>
<toolbarItems/> <toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="bk1-MS-moG"> <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="bk1-MS-moG">
<rect key="frame" x="0.0" y="48" width="414" height="96"/> <rect key="frame" x="0.0" y="96" width="414" height="96"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
@@ -544,7 +537,7 @@
<tabBarItem key="tabBarItem" title="Seismic Networks" image="tabbar-icon-networks" id="eed-sY-0Ua"/> <tabBarItem key="tabBarItem" title="Seismic Networks" image="tabbar-icon-networks" id="eed-sY-0Ua"/>
<toolbarItems/> <toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="LRX-7t-Fk4"> <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="LRX-7t-Fk4">
<rect key="frame" x="0.0" y="48" width="414" height="96"/> <rect key="frame" x="0.0" y="96" width="414" height="96"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
@@ -565,7 +558,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="200" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="a35-sg-TCr"> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="200" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="a35-sg-TCr">
<rect key="frame" x="0.0" y="144" width="414" height="614"/> <rect key="frame" x="0.0" y="192" width="414" height="532"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections> <connections>
<outlet property="dataSource" destination="syj-UE-OWc" id="Vah-TU-YfT"/> <outlet property="dataSource" destination="syj-UE-OWc" id="Vah-TU-YfT"/>
@@ -573,7 +566,7 @@
</connections> </connections>
</tableView> </tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PyX-yA-DXv"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PyX-yA-DXv">
<rect key="frame" x="0.0" y="758" width="414" height="55"/> <rect key="frame" x="0.0" y="724" width="414" height="55"/>
<color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/> <color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="55" id="kmt-E8-s2w"/> <constraint firstAttribute="height" constant="55" id="kmt-E8-s2w"/>
@@ -621,10 +614,9 @@
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="1.square" catalog="system" width="128" height="114"/>
<image name="navbar-icon-arrow-collapse" width="24" height="24"/> <image name="navbar-icon-arrow-collapse" width="24" height="24"/>
<image name="navbar-icon-filters" width="24" height="24"/>
<image name="navbar-icon-refresh" width="24" height="24"/> <image name="navbar-icon-refresh" width="24" height="24"/>
<image name="navbar-icon-sort" width="24" height="24"/>
<image name="tabbar-icon-alerts" width="25" height="25"/> <image name="tabbar-icon-alerts" width="25" height="25"/>
<image name="tabbar-icon-networks" width="25" height="25"/> <image name="tabbar-icon-networks" width="25" height="25"/>
<image name="tabbar-icon-reports" width="25" height="25"/> <image name="tabbar-icon-reports" width="25" height="25"/>
@@ -20,8 +20,12 @@ extension CGFloat {
static let cardVerticalSpacing: CGFloat = 10.0 static let cardVerticalSpacing: CGFloat = 10.0
} }
@objc
class EQNBaseContainerTableViewCell: UITableViewCell { class EQNBaseContainerTableViewCell: UITableViewCell {
/// Inset to apply to enclosing table view. Used to adjust spacing around the cells (for instance, top space is smaller that the space between the cards)
@objc static let EdgeInsets: UIEdgeInsets = .init(top: CGFloat.cardHorizontalMargin - CGFloat.cardVerticalMargin, left: 0, bottom: 0, right: 0)
// MARK: - UI // MARK: - UI
private lazy var internalContainerView: UIView = { private lazy var internalContainerView: UIView = {
@@ -46,11 +50,11 @@ class EQNBaseContainerTableViewCell: UITableViewCell {
}() }()
private lazy var headerLabel: UILabel = { private lazy var headerLabel: UILabel = {
let label = EQNEdgeInsetLabel() let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = AppTheme.Colors.lightBlue label.textAlignment = .center
label.font = .systemFont(ofSize: 17.0, weight: .medium) label.font = .preferredFont(forTextStyle: .title2)
label.textColor = .white label.textColor = AppTheme.shared.cardTextColor
return label return label
}() }()
@@ -136,9 +140,9 @@ class EQNBaseContainerTableViewCell: UITableViewCell {
if isHeaderVisible { if isHeaderVisible {
containerView.addSubview(headerLabel) containerView.addSubview(headerLabel)
headerLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true headerLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: .cardPadding).isActive = true
headerLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true headerLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
headerLabel.heightAnchor.constraint(equalToConstant: 26.0).isActive = true headerLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
} else { } else {
containerView.addSubview(emptyTopView) containerView.addSubview(emptyTopView)
emptyTopView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true emptyTopView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
@@ -7,6 +7,7 @@
// //
import Foundation import Foundation
import UIKit
class EQNBlurredCloseButton: UIButton { class EQNBlurredCloseButton: UIButton {
@@ -18,6 +19,12 @@ class EQNBlurredCloseButton: UIButton {
setupUI() setupUI()
} }
override func awakeFromNib() {
super.awakeFromNib()
setupUI()
}
// MARK: - Private // MARK: - Private
private func setupUI() { private func setupUI() {
@@ -12,18 +12,13 @@ import MapKit
class EQNSeismicAnnotationView: MKAnnotationView { class EQNSeismicAnnotationView: MKAnnotationView {
static let IdentifierFull = "EQNSeismicAnnotationViewFull"
static let IdentifierLight = "EQNSeismicAnnotationViewLight"
static let IdentifierCircle = "EQNSeismicAnnotationViewCircle"
private static let LabelHeight: CGFloat = 15.0 private static let LabelHeight: CGFloat = 15.0
private static let MagnitudeHeight: CGFloat = 25.0 private static let MagnitudeHeight: CGFloat = 25.0
private static let MagnitudeWidth: CGFloat = 45.0 private static let MagnitudeWidth: CGFloat = 45.0
private static let FullViewHeight: CGFloat = MagnitudeHeight + 2*LabelHeight private static let FullViewHeight: CGFloat = MagnitudeHeight + 2*LabelHeight
private static let FullViewWidth: CGFloat = 100.0 private static let FullViewWidth: CGFloat = 100.0
private static let SmallViewHeight: CGFloat = MagnitudeHeight private static let SmallViewHeight: CGFloat = MagnitudeHeight
private static let SmallViewWidth: CGFloat = 100.0 private static let SmallViewWidth: CGFloat = 80.0
static let CircleViewHeight: CGFloat = 20.0 static let CircleViewHeight: CGFloat = 20.0
// MARK: - Public // MARK: - Public
@@ -43,11 +38,16 @@ class EQNSeismicAnnotationView: MKAnnotationView {
get { magnitudeLabel.text } get { magnitudeLabel.text }
} }
var magnitudeColor: UIColor { var magnitudeTextColor: UIColor {
set { magnitudeLabel.textColor = newValue } set { magnitudeLabel.textColor = newValue }
get { magnitudeLabel.textColor } get { magnitudeLabel.textColor }
} }
var magnitudeBackgroundColor: UIColor? {
set { magnitudeView.backgroundColor = newValue }
get { magnitudeView.backgroundColor }
}
var isUserSelection: Bool = false { var isUserSelection: Bool = false {
didSet { didSet {
magnitudeView.layer.borderColor = isUserSelection ? AppTheme.Colors.red.cgColor : AppTheme.Colors.darkGray.cgColor magnitudeView.layer.borderColor = isUserSelection ? AppTheme.Colors.red.cgColor : AppTheme.Colors.darkGray.cgColor
@@ -80,7 +80,7 @@ class EQNSeismicAnnotationView: MKAnnotationView {
private lazy var magnitudeLabel: UILabel = { private lazy var magnitudeLabel: UILabel = {
let label = UILabel() let label = UILabel()
label.font = UIFont.systemFont(ofSize: 12, weight: .bold) label.font = UIFont.systemFont(ofSize: 11, weight: .bold)
label.textAlignment = .center label.textAlignment = .center
label.textColor = AppTheme.Colors.lightBlue label.textColor = AppTheme.Colors.lightBlue
return label return label
@@ -107,13 +107,13 @@ class EQNSeismicAnnotationView: MKAnnotationView {
backgroundColor = .clear backgroundColor = .clear
if reuseIdentifier == Self.IdentifierFull { if reuseIdentifier == Self.identifier(for: .full) {
frame = CGRect(x: 0, y: 0, width: Self.FullViewWidth, height: Self.FullViewHeight) frame = CGRect(x: 0, y: 0, width: Self.FullViewWidth, height: Self.FullViewHeight)
setupFullUI() setupFullUI()
} else if reuseIdentifier == Self.IdentifierLight { } else if reuseIdentifier == Self.identifier(for: .light) {
frame = CGRect(x: 0, y: 0, width: Self.SmallViewWidth, height: Self.SmallViewHeight) frame = CGRect(x: 0, y: 0, width: Self.SmallViewWidth, height: Self.SmallViewHeight)
setupLightUI() setupLightUI()
} else if reuseIdentifier == Self.IdentifierCircle { } else if reuseIdentifier == Self.identifier(for: .circle) {
frame = CGRect(x: 0, y: 0, width: Self.CircleViewHeight, height: Self.CircleViewHeight) frame = CGRect(x: 0, y: 0, width: Self.CircleViewHeight, height: Self.CircleViewHeight)
setupCircleUI() setupCircleUI()
} }
@@ -158,3 +158,14 @@ class EQNSeismicAnnotationView: MKAnnotationView {
addSubview(imageView) addSubview(imageView)
} }
} }
extension EQNSeismicAnnotationView {
static func identifier(for style: MapPinStyle) -> String {
return switch style {
case .circle: "EQNSeismicAnnotationViewCircle"
case .light: "EQNSeismicAnnotationViewLight"
case .full: "EQNSeismicAnnotationViewFull"
}
}
}
@@ -33,6 +33,7 @@
"filter_show_area" = "إظهار كافة الزلازل ضمن دائرة نصف قطرها:"; "filter_show_area" = "إظهار كافة الزلازل ضمن دائرة نصف قطرها:";
"filter_show_relevant" = "إظهار الزلازل ذات الصلة بموقعي فقط"; "filter_show_relevant" = "إظهار الزلازل ذات الصلة بموقعي فقط";
"filter_show_all" = "عرض جميع الزلازل على مستوى العالم (M≥2)"; "filter_show_all" = "عرض جميع الزلازل على مستوى العالم (M≥2)";
"filter_show_felt" = "إظهار الزلازل التي يشعر بها المستخدمون فقط";
"filter_minimum_magnitude" = "والحجم الأدنى:"; "filter_minimum_magnitude" = "والحجم الأدنى:";
"main_understood" = "مفهوم"; "main_understood" = "مفهوم";
"options_low_magnitude" = "الانتباه إلى أن ليست كل الشبكات الزلزالية توفر بيانات الزلازل التي تقل عن 2.0 درجة. علاوة على ذلك ، فإنك تزيد بشكل كبير من نقل البيانات واستخدام البطارية بسبب الإخطارات. ما لم يتم تصنيع جهازك حديثا ، ستلاحظ أيضا تباطؤا عاما."; "options_low_magnitude" = "الانتباه إلى أن ليست كل الشبكات الزلزالية توفر بيانات الزلازل التي تقل عن 2.0 درجة. علاوة على ذلك ، فإنك تزيد بشكل كبير من نقل البيانات واستخدام البطارية بسبب الإخطارات. ما لم يتم تصنيع جهازك حديثا ، ستلاحظ أيضا تباطؤا عاما.";
@@ -53,6 +54,7 @@
"filter_area" = "الزلازل المعروضة: في دائرة نصف قطرها"; "filter_area" = "الزلازل المعروضة: في دائرة نصف قطرها";
"filter_relevant" = "الزلازل المعروضة: ذات صلة"; "filter_relevant" = "الزلازل المعروضة: ذات صلة";
"filter_all" = "الزلازل المعروضة: الكل"; "filter_all" = "الزلازل المعروضة: الكل";
"filter_felt" = "الزلازل المعروضة: شعرت";
"liveview_unknown_location" = "موقفك غير معروف. تمكين موقع الهاتف الذكي من تكوين الهاتف الذكي"; "liveview_unknown_location" = "موقفك غير معروف. تمكين موقع الهاتف الذكي من تكوين الهاتف الذكي";
"map_number" = "تم الكشف عن زلزال بواسطة %@ الهواتف الذكية"; "map_number" = "تم الكشف عن زلزال بواسطة %@ الهواتف الذكية";
"permission_location_no" = "لقد اخترت منع التطبيق من قراءة موقع الجهاز. لن تستلم تنبيهات وإشعارات في الوقت الفعلي"; "permission_location_no" = "لقد اخترت منع التطبيق من قراءة موقع الجهاز. لن تستلم تنبيهات وإشعارات في الوقت الفعلي";
@@ -75,6 +77,7 @@
"status_cancel" = "ألغي"; "status_cancel" = "ألغي";
"options_alarms" = "تنبيه بوقت فعلي"; "options_alarms" = "تنبيه بوقت فعلي";
"options_notification_enable_alarm" = "قم بتفعيل الإنذار عند اكتشاف زلزال في الوقت الفعلي بواسطة شبكة الهواتف الذكية"; "options_notification_enable_alarm" = "قم بتفعيل الإنذار عند اكتشاف زلزال في الوقت الفعلي بواسطة شبكة الهواتف الذكية";
"options_notification_disable_sound" = "قم بتعطيل صوت المنبه إذا كان الاهتزاز في موقعك خفيفًا";
"options_notification_official" = "إخطارات الشبكة الزلزالية"; "options_notification_official" = "إخطارات الشبكة الزلزالية";
"options_notification_enable_official" = "تلقي إشعارات بشأن الزلازل التي اكتشفتها شبكات الزلازل الوطنية والدولية"; "options_notification_enable_official" = "تلقي إشعارات بشأن الزلازل التي اكتشفتها شبكات الزلازل الوطنية والدولية";
"options_official_type" = "مرشح الإشعارات"; "options_official_type" = "مرشح الإشعارات";
@@ -85,7 +88,7 @@
"options_official_maxdist" = "المسافة القصو"; "options_official_maxdist" = "المسافة القصو";
"options_notification_manual" = "إشعارات تقرير المستخدم"; "options_notification_manual" = "إشعارات تقرير المستخدم";
"options_notification_enable_manual" = "استلم الإخطارات للزلازل التي أبلغ عنها يدويا من قبل المستخدمين"; "options_notification_enable_manual" = "استلم الإخطارات للزلازل التي أبلغ عنها يدويا من قبل المستخدمين";
"main_areacheck_message" = "في الوقت الحالي ، يوجد في منطقتك %s هواتف ذكية تراقب الزلازل في الوقت الفعلي. قدرة شبكة الهاتف الذكي على اكتشاف الزلازل في الوقت الفعلي هي%@. لتحسين اكتشاف الزلازل في منطقتك ، شارك التطبيق مع عائلتك وأصدقائك ، شكرًا!"; "main_areacheck_message" = "في الوقت الحالي ، يوجد في منطقتك %@ هواتف ذكية تراقب الزلازل في الوقت الفعلي. لتحسين اكتشاف الزلازل في منطقتك ، شارك التطبيق مع عائلتك وأصدقائك ، شكرًا!";
"main_version" = "نموذج"; "main_version" = "نموذج";
"app_name" = "منبه الزلازل"; "app_name" = "منبه الزلازل";
"official_depth" = "العمق:"; "official_depth" = "العمق:";
@@ -105,7 +108,7 @@
"permission_location_no_background_solve" = "حل المشكلة"; "permission_location_no_background_solve" = "حل المشكلة";
"main_share_app" = "شارك التطبيق"; "main_share_app" = "شارك التطبيق";
"main_vote" = "قيّم التطبيق"; "main_vote" = "قيّم التطبيق";
"main_twitter_see" = "التبيين في تويتر"; "main_twitter_see" = "عرض على X";
"map_smartphone_magnitude" = "سيتم تقدير قوة الزلزال بواسطة الشبكة الزلزالية الوطنية وسيظهر في علامة تبويب الشبكات الزلزالية في التطبيق"; "map_smartphone_magnitude" = "سيتم تقدير قوة الزلزال بواسطة الشبكة الزلزالية الوطنية وسيظهر في علامة تبويب الشبكات الزلزالية في التطبيق";
"main_alerttest" = "تنبيه اختياري"; "main_alerttest" = "تنبيه اختياري";
"main_simulator" = "محاكي"; "main_simulator" = "محاكي";
@@ -142,6 +145,8 @@
"mercalli_XI" = "\U200FXI - تدمير مراكز حضرية بأكملها ، العديد من الضحايا ، شقوق في الأرض وانهيارات أرضيةlandslides"; "mercalli_XI" = "\U200FXI - تدمير مراكز حضرية بأكملها ، العديد من الضحايا ، شقوق في الأرض وانهيارات أرضيةlandslides";
"mercalli_XII" = "\U200FXII - اضطراب التربة ، إزاحة قشرة الأرض"; "mercalli_XII" = "\U200FXII - اضطراب التربة ، إزاحة قشرة الأرض";
"mercalli_intensity" = "شدة %@"; "mercalli_intensity" = "شدة %@";
"shakemap" = "خريطة اهتزازية";
"shakemap_description" = "خريطة الكثافة بناءً على التقارير المحسوسة";
// values // values
"official_magnitude_value_00" = "درجة >= 0.0"; "official_magnitude_value_00" = "درجة >= 0.0";
@@ -231,3 +236,4 @@
"subscription_plan_monthly" = "شهريا"; "subscription_plan_monthly" = "شهريا";
"subscription_plan_yearly" = "سنوي"; "subscription_plan_yearly" = "سنوي";
"subscription_plan_perpetual" = "حياة"; "subscription_plan_perpetual" = "حياة";
"tap_to_open" = "انقر للتكبير";
@@ -33,6 +33,7 @@
"filter_show_area" = "Εμφάνιση όλων των σεισμών σε ακτίνα:"; "filter_show_area" = "Εμφάνιση όλων των σεισμών σε ακτίνα:";
"filter_show_relevant" = "Εμφάνιση μόνο των σχετικών σεισμών σε σχέση με την τοποθεσία μου"; "filter_show_relevant" = "Εμφάνιση μόνο των σχετικών σεισμών σε σχέση με την τοποθεσία μου";
"filter_show_all" = "Εμφάνιση όλων των σεισμών παγκοσμίως (M≥2)"; "filter_show_all" = "Εμφάνιση όλων των σεισμών παγκοσμίως (M≥2)";
"filter_show_felt" = "Εμφάνιση μόνο των σεισμών που αισθάνθηκαν οι χρήστες";
"filter_minimum_magnitude" = "και ελάχιστο μέγεθος:"; "filter_minimum_magnitude" = "και ελάχιστο μέγεθος:";
"main_understood" = "Κατάλαβα"; "main_understood" = "Κατάλαβα";
"options_low_magnitude" = "Λάβε υπόψη ότι δεν παρέχουν όλα τα σεισμικά δίκτυα δεδομένα για σεισμούς με μέγεθος κάτω από 2.0. Επίσης, αυξάνεις σημαντικά την μεταφορά δεδομένων και την χρήση μπαταρίας λόγω κοινοποιήσεων. Αν η συσκευή σου δεν είναι πρόσφατης κατασκευής, θα παρατηρήσεις επίσης μια γενική επιβράδυνση."; "options_low_magnitude" = "Λάβε υπόψη ότι δεν παρέχουν όλα τα σεισμικά δίκτυα δεδομένα για σεισμούς με μέγεθος κάτω από 2.0. Επίσης, αυξάνεις σημαντικά την μεταφορά δεδομένων και την χρήση μπαταρίας λόγω κοινοποιήσεων. Αν η συσκευή σου δεν είναι πρόσφατης κατασκευής, θα παρατηρήσεις επίσης μια γενική επιβράδυνση.";
@@ -53,6 +54,7 @@
"filter_area" = "Εμφανίζονται σεισμοί: στην ακτίνα"; "filter_area" = "Εμφανίζονται σεισμοί: στην ακτίνα";
"filter_relevant" = "Εμφανίζονται σεισμοί: σχετικοί"; "filter_relevant" = "Εμφανίζονται σεισμοί: σχετικοί";
"filter_all" = "Εμφανίζονται σεισμοί: όλοι (M≥2)"; "filter_all" = "Εμφανίζονται σεισμοί: όλοι (M≥2)";
"filter_felt" = "Εμφανίζονται σεισμοί: αισθητοί";
"liveview_unknown_location" = "Η θέση σου είναι άγνωστη. Ενεργοποίησε την τοποθεσία του smartphone από την διαμόρφωση του smartphone"; "liveview_unknown_location" = "Η θέση σου είναι άγνωστη. Ενεργοποίησε την τοποθεσία του smartphone από την διαμόρφωση του smartphone";
"map_number" = "Ανιχνεύθηκε δόνηση από %@ smartphone"; "map_number" = "Ανιχνεύθηκε δόνηση από %@ smartphone";
"permission_location_no" = "Έχεις επιλέξει να αποτρέπεις την εφαρμογή από την ανάγνωση της τοποθεσίας της συσκευής. ΔΕΝ θα λαμβάνεις κοινοποιήσεις και ειδοποιήσεις σε πραγματικό χρόνο"; "permission_location_no" = "Έχεις επιλέξει να αποτρέπεις την εφαρμογή από την ανάγνωση της τοποθεσίας της συσκευής. ΔΕΝ θα λαμβάνεις κοινοποιήσεις και ειδοποιήσεις σε πραγματικό χρόνο";
@@ -75,6 +77,7 @@
"status_cancel" = "Διαγραφή"; "status_cancel" = "Διαγραφή";
"options_alarms" = "Ειδοποίηση σε πραγματικό χρόνο"; "options_alarms" = "Ειδοποίηση σε πραγματικό χρόνο";
"options_notification_enable_alarm" = "Να ακούγεται συναγερμός όταν ανιχνεύεται σεισμός σε πραγματικό χρόνο από το δίκτυο smartphone"; "options_notification_enable_alarm" = "Να ακούγεται συναγερμός όταν ανιχνεύεται σεισμός σε πραγματικό χρόνο από το δίκτυο smartphone";
"options_notification_disable_sound" = "Απενεργοποιήστε τον ήχο συναγερμού εάν το κούνημα στην τοποθεσία σας είναι ήπιο";
"options_notification_official" = "Κοινοποιήσεις σεισμικών δικτύων"; "options_notification_official" = "Κοινοποιήσεις σεισμικών δικτύων";
"options_notification_enable_official" = "Λήψη ειδοποιήσεων για τους σεισμούς που εντοπίζονται από τα εθνικά και διεθνή σεισμικά δίκτυα"; "options_notification_enable_official" = "Λήψη ειδοποιήσεων για τους σεισμούς που εντοπίζονται από τα εθνικά και διεθνή σεισμικά δίκτυα";
"options_official_type" = "Φίλτρο ειδοποιήσεων"; "options_official_type" = "Φίλτρο ειδοποιήσεων";
@@ -105,7 +108,7 @@
"permission_location_no_background_solve" = "Διόρθωση"; "permission_location_no_background_solve" = "Διόρθωση";
"main_share_app" = "Μοιράσου"; "main_share_app" = "Μοιράσου";
"main_vote" = "Ψήφισε την Εφαρμογή"; "main_vote" = "Ψήφισε την Εφαρμογή";
"main_twitter_see" = "Twitter"; "main_twitter_see" = "X";
"map_smartphone_magnitude" = "Το μέγεθος θα εκτιμηθεί από το εθνικό σεισμικό δίκτυο και θα εμφανιστεί στην οθόνη Σεισμικών Δικτύων της εφαρμογής"; "map_smartphone_magnitude" = "Το μέγεθος θα εκτιμηθεί από το εθνικό σεισμικό δίκτυο και θα εμφανιστεί στην οθόνη Σεισμικών Δικτύων της εφαρμογής";
"main_alerttest" = "Τεστ ειδοποίησης"; "main_alerttest" = "Τεστ ειδοποίησης";
"main_simulator" = "Προσομοιωτής"; "main_simulator" = "Προσομοιωτής";
@@ -142,6 +145,8 @@
"mercalli_XI" = "XI - Καταστροφές ολόκληρων αστικών κέντρων, πολλά θύματα, ρωγμές στο έδαφος και κατολισθήσεις"; "mercalli_XI" = "XI - Καταστροφές ολόκληρων αστικών κέντρων, πολλά θύματα, ρωγμές στο έδαφος και κατολισθήσεις";
"mercalli_XII" = "XII - Διάσπαση του εδάφους, μετατόπιση του φλοιού της γης"; "mercalli_XII" = "XII - Διάσπαση του εδάφους, μετατόπιση του φλοιού της γης";
"mercalli_intensity" = "Ένταση %@"; "mercalli_intensity" = "Ένταση %@";
"shakemap" = "Χάρτης έντασης";
"shakemap_description" = "Χάρτης έντασης με βάση τις αναφορές χρηστών";
// values // values
"official_magnitude_value_00" = "Μέγεθος >= 0.0"; "official_magnitude_value_00" = "Μέγεθος >= 0.0";
@@ -231,3 +236,4 @@
"subscription_plan_monthly" = "Μηνιαίο"; "subscription_plan_monthly" = "Μηνιαίο";
"subscription_plan_yearly" = "Ετήσιο"; "subscription_plan_yearly" = "Ετήσιο";
"subscription_plan_perpetual" = "Διάρκεια Ζωής"; "subscription_plan_perpetual" = "Διάρκεια Ζωής";
"tap_to_open" = "Πατήστε για μεγέθυνση";
@@ -33,6 +33,7 @@
"filter_show_area" = "Show all earthquakes within a radius of:"; "filter_show_area" = "Show all earthquakes within a radius of:";
"filter_show_relevant" = "Show only the relevant earthquakes with respect to my location"; "filter_show_relevant" = "Show only the relevant earthquakes with respect to my location";
"filter_show_all" = "Show all earthquakes globally (M≥2)"; "filter_show_all" = "Show all earthquakes globally (M≥2)";
"filter_show_felt" = "Show only the earthquakes felt by the users";
"filter_minimum_magnitude" = "and minimum magnitude:"; "filter_minimum_magnitude" = "and minimum magnitude:";
"main_understood" = "Understood"; "main_understood" = "Understood";
"options_low_magnitude" = "Beware that not all seismic networks provide earthquake data below magnitude 2.0. Moreover, you significantly increase the data transfer and the battery usage due to notifications. Unless your device is recently manufactured, you will also notice a general slowdown."; "options_low_magnitude" = "Beware that not all seismic networks provide earthquake data below magnitude 2.0. Moreover, you significantly increase the data transfer and the battery usage due to notifications. Unless your device is recently manufactured, you will also notice a general slowdown.";
@@ -53,6 +54,7 @@
"filter_area" = "Quakes shown: in the radius"; "filter_area" = "Quakes shown: in the radius";
"filter_relevant" = "Quakes shown: relevant"; "filter_relevant" = "Quakes shown: relevant";
"filter_all" = "Quakes shown: all (M≥2)"; "filter_all" = "Quakes shown: all (M≥2)";
"filter_felt" = "Quakes shown: felt";
"liveview_unknown_location" = "Your position is unknown. Enable smartphone location from smartphone configuration"; "liveview_unknown_location" = "Your position is unknown. Enable smartphone location from smartphone configuration";
"map_number" = "Quake detected by %@ smartphones"; "map_number" = "Quake detected by %@ smartphones";
"permission_location_no" = "You have chosen to prevent the app from reading the location of the device. You will NOT receive real-time notifications and alerts"; "permission_location_no" = "You have chosen to prevent the app from reading the location of the device. You will NOT receive real-time notifications and alerts";
@@ -75,6 +77,7 @@
"status_cancel" = "Cancel"; "status_cancel" = "Cancel";
"options_alarms" = "Real time alert"; "options_alarms" = "Real time alert";
"options_notification_enable_alarm" = "Activate an alarm when a quake is detected in real time by the network of smartphones"; "options_notification_enable_alarm" = "Activate an alarm when a quake is detected in real time by the network of smartphones";
"options_notification_disable_sound" = "Disable the alarm sound if the shaking at your location is mild";
"options_notification_official" = "Seismic network notifications"; "options_notification_official" = "Seismic network notifications";
"options_notification_enable_official" = "Receive notifications for the earthquakes detected by the national and international seismic networks"; "options_notification_enable_official" = "Receive notifications for the earthquakes detected by the national and international seismic networks";
"options_official_type" = "Notification filter"; "options_official_type" = "Notification filter";
@@ -105,7 +108,7 @@
"permission_location_no_background_solve" = "Solve"; "permission_location_no_background_solve" = "Solve";
"main_share_app" = "Share App"; "main_share_app" = "Share App";
"main_vote" = "Rate the App"; "main_vote" = "Rate the App";
"main_twitter_see" = "View on Twitter"; "main_twitter_see" = "View on X";
"map_smartphone_magnitude" = "The magnitude will be estimated by the national seismic network and it will appear in the Seismic Networks tab of the app"; "map_smartphone_magnitude" = "The magnitude will be estimated by the national seismic network and it will appear in the Seismic Networks tab of the app";
"main_alerttest" = "Test alert"; "main_alerttest" = "Test alert";
"main_simulator" = "Simulator"; "main_simulator" = "Simulator";
@@ -142,6 +145,8 @@
"mercalli_XI" = "XI - Destruction of entire urban centers, many victims, crevices in the ground and landslides"; "mercalli_XI" = "XI - Destruction of entire urban centers, many victims, crevices in the ground and landslides";
"mercalli_XII" = "XII - Disruption of the soil, displacement of the earth's crust"; "mercalli_XII" = "XII - Disruption of the soil, displacement of the earth's crust";
"mercalli_intensity" = "Intensity %@"; "mercalli_intensity" = "Intensity %@";
"shakemap" = "Shakemap";
"shakemap_description" = "Intensity map based on user reports";
// values // values
"official_magnitude_value_00" = "Magnitude >= 0.0"; "official_magnitude_value_00" = "Magnitude >= 0.0";
@@ -231,3 +236,4 @@
"subscription_plan_monthly" = "Monthly"; "subscription_plan_monthly" = "Monthly";
"subscription_plan_yearly" = "Annual"; "subscription_plan_yearly" = "Annual";
"subscription_plan_perpetual" = "Lifetime"; "subscription_plan_perpetual" = "Lifetime";
"tap_to_open" = "Tap to open";
@@ -33,6 +33,7 @@
"filter_show_area" = "Mostrar todos los sismos en un radio de:"; "filter_show_area" = "Mostrar todos los sismos en un radio de:";
"filter_show_relevant" = "Mostrar solo los sismos relevantes con respecto a mi ubicación"; "filter_show_relevant" = "Mostrar solo los sismos relevantes con respecto a mi ubicación";
"filter_show_all" = "Mostrar todos los sismos globalmente (M≥2)"; "filter_show_all" = "Mostrar todos los sismos globalmente (M≥2)";
"filter_show_felt" = "Mostrar solo los sismos sentidos por los usuarios";
"filter_minimum_magnitude" = "y magnitud mínima:"; "filter_minimum_magnitude" = "y magnitud mínima:";
"main_understood" = "Entendido"; "main_understood" = "Entendido";
"options_low_magnitude" = "Tenga en cuenta que no todas las redes sísmicas proporcionan datos para sismos de magnitud inferior a 2.0. Además, aumenta significativamente la transferencia de datos con el servidor y el uso de la batería debido a la mayor cantidad de notificaciones. Si tu dispositivo no se ha fabricado recientemente, también notará una desaceleración general de la aplicación."; "options_low_magnitude" = "Tenga en cuenta que no todas las redes sísmicas proporcionan datos para sismos de magnitud inferior a 2.0. Además, aumenta significativamente la transferencia de datos con el servidor y el uso de la batería debido a la mayor cantidad de notificaciones. Si tu dispositivo no se ha fabricado recientemente, también notará una desaceleración general de la aplicación.";
@@ -53,6 +54,7 @@
"filter_area" = "Sismos mostrados: en el radio"; "filter_area" = "Sismos mostrados: en el radio";
"filter_relevant" = "Sismos mostrados: relevantes"; "filter_relevant" = "Sismos mostrados: relevantes";
"filter_all" = "Sismos mostrados: todos (M≥2)"; "filter_all" = "Sismos mostrados: todos (M≥2)";
"filter_felt" = "Sismos mostrados: sentidos";
"liveview_unknown_location" = "Tu posición es desconocida. Habilitar la ubicación del smartphone desde la página de configuración del smartphone"; "liveview_unknown_location" = "Tu posición es desconocida. Habilitar la ubicación del smartphone desde la página de configuración del smartphone";
"map_number" = "Sismo detectado por %@ smartphones"; "map_number" = "Sismo detectado por %@ smartphones";
"permission_location_no" = "Ha elegido evitar que la aplicación lea la ubicación de tu dispositivo. NO recibirá notificaciones y alertas en tiempo real"; "permission_location_no" = "Ha elegido evitar que la aplicación lea la ubicación de tu dispositivo. NO recibirá notificaciones y alertas en tiempo real";
@@ -68,13 +70,14 @@
"globe_simulation_message1" = "Con este epicentro, deberías recibir la alerta %.0f segundos antes. Sin embargo, %.0f personas serán alertadas antes que tú. El tiempo de aviso será de %.0f segundos. Al suscribirse hoy al servicio de prioridad TOP 10K, solo %.0f personas recibirán la alerta antes que usted (en orden de distancia del epicentro)"; "globe_simulation_message1" = "Con este epicentro, deberías recibir la alerta %.0f segundos antes. Sin embargo, %.0f personas serán alertadas antes que tú. El tiempo de aviso será de %.0f segundos. Al suscribirse hoy al servicio de prioridad TOP 10K, solo %.0f personas recibirán la alerta antes que usted (en orden de distancia del epicentro)";
"globe_simulation_message2" = "Con este epicentro, deberías recibir la alerta %.0f segundos antes. Sin embargo, %.0f personas serán alertadas antes que tú. Recibirá la alerta %.0f segundos después de las ondas sísmicas. Al suscribirse hoy al servicio de prioridad TOP 10K, solo %.0f personas recibirán la alerta antes que usted (en orden de distancia del epicentro)"; "globe_simulation_message2" = "Con este epicentro, deberías recibir la alerta %.0f segundos antes. Sin embargo, %.0f personas serán alertadas antes que tú. Recibirá la alerta %.0f segundos después de las ondas sísmicas. Al suscribirse hoy al servicio de prioridad TOP 10K, solo %.0f personas recibirán la alerta antes que usted (en orden de distancia del epicentro)";
"globe_simulation_message3" = "Con este epicentro, deberías recibir la alerta %.0f segundos antes. Sin embargo, %.0f personas serán alertadas antes que tú. Al suscribirse hoy al servicio de prioridad TOP 10K, solo %.0f personas recibirán la alerta antes que usted (en orden de distancia del epicentro)"; "globe_simulation_message3" = "Con este epicentro, deberías recibir la alerta %.0f segundos antes. Sin embargo, %.0f personas serán alertadas antes que tú. Al suscribirse hoy al servicio de prioridad TOP 10K, solo %.0f personas recibirán la alerta antes que usted (en orden de distancia del epicentro)";
"globe_simulation_message4" = "Con este epicentro, gracias al servicio de prioridad %@, deberías recibir la alerta %.0f segundos antes. Solo %@ personas serán alertadas antes que tú"; "globe_simulation_message4" = "Con este epicentro, gracias al servicio de prioridad %@, deberías recibir la alerta %.0f segundos antes. Solo %.0f personas serán alertadas antes que tú";
"globe_simulation_message5" = "Con este epicentro, gracias al servicio de prioridad %@, deberías recibir la alerta %.0f segundos antes. %.0f personas serán alertadas antes que tú. Para aumentar el tiempo de advertencia, puede suscribirse al servicio de prioridad %@"; "globe_simulation_message5" = "Con este epicentro, gracias al servicio de prioridad %@, deberías recibir la alerta %.0f segundos antes. %.0f personas serán alertadas antes que tú. Para aumentar el tiempo de advertencia, puede suscribirse al servicio de prioridad %@";
"globe_simulation_message6" = "Con este epicentro, gracias al servicio de prioridad %@, deberías recibir la alerta %.0f segundos antes. ¡Serás la primera persona en ser alertada!"; "globe_simulation_message6" = "Con este epicentro, gracias al servicio de prioridad %@, deberías recibir la alerta %.0f segundos antes. ¡Serás la primera persona en ser alertada!";
"globe_simulation_priority" = "Servicio prioritario"; "globe_simulation_priority" = "Servicio prioritario";
"status_cancel" = "Clara"; "status_cancel" = "Clara";
"options_alarms" = "Alerta en tiempo real"; "options_alarms" = "Alerta en tiempo real";
"options_notification_enable_alarm" = "Suena una alarma cuando se detecta un sismo en tiempo real por la red de los teléfonos inteligentes"; "options_notification_enable_alarm" = "Suena una alarma cuando se detecta un sismo en tiempo real por la red de los teléfonos inteligentes";
"options_notification_disable_sound" = "Desactive el sonido de la alarma si el sismo en tu ubicación es leve";
"options_notification_official" = "Notificaciones redes sísmicas"; "options_notification_official" = "Notificaciones redes sísmicas";
"options_notification_enable_official" = "Recibe las notificaciones de los sismos detectados por las redes sísmicas nacionales e internacionales"; "options_notification_enable_official" = "Recibe las notificaciones de los sismos detectados por las redes sísmicas nacionales e internacionales";
"options_official_type" = "Filtro de notificaciones"; "options_official_type" = "Filtro de notificaciones";
@@ -105,7 +108,7 @@
"permission_location_no_background_solve" = "Corregir"; "permission_location_no_background_solve" = "Corregir";
"main_share_app" = "Comparte App"; "main_share_app" = "Comparte App";
"main_vote" = "Vota la app"; "main_vote" = "Vota la app";
"main_twitter_see" = "Ver en Twitter"; "main_twitter_see" = "Ver en X";
"map_smartphone_magnitude" = "La magnitud será comunicada por la red sísmica nacional y aparecerá en la sección de Redes Sísmicas de la app"; "map_smartphone_magnitude" = "La magnitud será comunicada por la red sísmica nacional y aparecerá en la sección de Redes Sísmicas de la app";
"main_alerttest" = "Prueba alerta"; "main_alerttest" = "Prueba alerta";
"main_simulator" = "Simulador"; "main_simulator" = "Simulador";
@@ -142,6 +145,8 @@
"mercalli_XI" = "XI - Destrucción de centros urbanos enteros, muchas víctimas, grietas en el suelo y deslizamientos de tierra"; "mercalli_XI" = "XI - Destrucción de centros urbanos enteros, muchas víctimas, grietas en el suelo y deslizamientos de tierra";
"mercalli_XII" = "XII - Alteración del suelo, desplazamiento de la corteza terrestre."; "mercalli_XII" = "XII - Alteración del suelo, desplazamiento de la corteza terrestre.";
"mercalli_intensity" = "Intensidad %@"; "mercalli_intensity" = "Intensidad %@";
"shakemap" = "Mapa intensidad";
"shakemap_description" = "Mapa de intensidad basado en informes de usuarios";
// values // values
"official_magnitude_value_00" = "Magnitud >= 0.0"; "official_magnitude_value_00" = "Magnitud >= 0.0";
@@ -231,3 +236,4 @@
"subscription_plan_monthly" = "Mensual"; "subscription_plan_monthly" = "Mensual";
"subscription_plan_yearly" = "Anual"; "subscription_plan_yearly" = "Anual";
"subscription_plan_perpetual" = "Para siempre"; "subscription_plan_perpetual" = "Para siempre";
"tap_to_open" = "Toque para ampliar";
@@ -33,6 +33,7 @@
"filter_show_area" = "Afficher tous les tremblements de terre dans un rayon de :"; "filter_show_area" = "Afficher tous les tremblements de terre dans un rayon de :";
"filter_show_relevant" = "Afficher uniquement les tremblements de terre pertinents par rapport à ma position"; "filter_show_relevant" = "Afficher uniquement les tremblements de terre pertinents par rapport à ma position";
"filter_show_all" = "Afficher tous les tremblements de terre globalement (M≥2)"; "filter_show_all" = "Afficher tous les tremblements de terre globalement (M≥2)";
"filter_show_felt" = "Afficher uniquement les tremblements de terre ressentis par les utilisateurs";
"filter_minimum_magnitude" = "et magnitude minimale :"; "filter_minimum_magnitude" = "et magnitude minimale :";
"main_understood" = "J'ai compris"; "main_understood" = "J'ai compris";
"options_low_magnitude" = "Attention, les réseaux sismiques ne fournissent pas tous des données pour des séismes de magnitude inférieure à 2,0. De plus, vous augmentez significativement le transfert de données et l'utilisation de la batterie en raison du plus grand nombre de notifications. Sauf si votre appareil est de fabrication récente, vous remarquerez également un ralentissement général."; "options_low_magnitude" = "Attention, les réseaux sismiques ne fournissent pas tous des données pour des séismes de magnitude inférieure à 2,0. De plus, vous augmentez significativement le transfert de données et l'utilisation de la batterie en raison du plus grand nombre de notifications. Sauf si votre appareil est de fabrication récente, vous remarquerez également un ralentissement général.";
@@ -53,6 +54,7 @@
"filter_area" = "Séismes affichés : dans le rayon"; "filter_area" = "Séismes affichés : dans le rayon";
"filter_relevant" = "Séismes affichés : pertinents"; "filter_relevant" = "Séismes affichés : pertinents";
"filter_all" = "Séismes affichés : tous (M≥2)"; "filter_all" = "Séismes affichés : tous (M≥2)";
"filter_felt" = "Séismes affichés : ressentis";
"liveview_unknown_location" = "Votre position est inconnue. Activez la localisation à partir de la page de configuration de votre appareil"; "liveview_unknown_location" = "Votre position est inconnue. Activez la localisation à partir de la page de configuration de votre appareil";
"map_number" = "Séisme détecté par %@ smartphones"; "map_number" = "Séisme détecté par %@ smartphones";
"permission_location_no" = "Vous avez choisi d'empêcher l'app de lire la position de votre appareil. Vous ne recevrez PAS de notifications et d'alertes en temps réel"; "permission_location_no" = "Vous avez choisi d'empêcher l'app de lire la position de votre appareil. Vous ne recevrez PAS de notifications et d'alertes en temps réel";
@@ -75,6 +77,7 @@
"status_cancel" = "Supprimer"; "status_cancel" = "Supprimer";
"options_alarms" = "Alerte en temps réel"; "options_alarms" = "Alerte en temps réel";
"options_notification_enable_alarm" = "Une alarme retentit lorsqu'un séisme est détecté par le réseau des smartphones"; "options_notification_enable_alarm" = "Une alarme retentit lorsqu'un séisme est détecté par le réseau des smartphones";
"options_notification_disable_sound" = "Désactivez le son de l'alarme si les secousses à votre emplacement sont légères";
"options_notification_official" = "Notifications de réseaux sismiquex"; "options_notification_official" = "Notifications de réseaux sismiquex";
"options_notification_enable_official" = "Recevez les notifications des séismes détectés par les réseaux sismiques nationaux et internationaux"; "options_notification_enable_official" = "Recevez les notifications des séismes détectés par les réseaux sismiques nationaux et internationaux";
"options_official_type" = "Filtre de notificatio"; "options_official_type" = "Filtre de notificatio";
@@ -105,7 +108,7 @@
"permission_location_no_background_solve" = "Corriger"; "permission_location_no_background_solve" = "Corriger";
"main_share_app" = "Partager l'App"; "main_share_app" = "Partager l'App";
"main_vote" = "Voter l'App"; "main_vote" = "Voter l'App";
"main_twitter_see" = "Voir sur Twitter"; "main_twitter_see" = "Voir sur X";
"map_smartphone_magnitude" = "La magnitude sera estimée par le réseau sismique national et s'affichera dans la page d'écran Réseaux Sismiques de l'app"; "map_smartphone_magnitude" = "La magnitude sera estimée par le réseau sismique national et s'affichera dans la page d'écran Réseaux Sismiques de l'app";
"main_alerttest" = "Test alerte"; "main_alerttest" = "Test alerte";
"main_simulator" = "Simulateur"; "main_simulator" = "Simulateur";
@@ -142,6 +145,8 @@
"mercalli_XI" = "XI - Destruction de centres urbains entiers, nombreuses victimes, crevasses dans le sol et glissements de terrain"; "mercalli_XI" = "XI - Destruction de centres urbains entiers, nombreuses victimes, crevasses dans le sol et glissements de terrain";
"mercalli_XII" = "XII - Perturbation du sol, déplacement de la croûte terrestre"; "mercalli_XII" = "XII - Perturbation du sol, déplacement de la croûte terrestre";
"mercalli_intensity" = "Intensité %@"; "mercalli_intensity" = "Intensité %@";
"shakemap" = "Carte d'intensité";
"shakemap_description" = "Carte d'intensité basée sur les rapports ressentis";
// values // values
"official_magnitude_value_00" = "Magnitude >= 0.0"; "official_magnitude_value_00" = "Magnitude >= 0.0";
@@ -231,3 +236,4 @@
"subscription_plan_monthly" = "Mensuel"; "subscription_plan_monthly" = "Mensuel";
"subscription_plan_yearly" = "Annuel"; "subscription_plan_yearly" = "Annuel";
"subscription_plan_perpetual" = "Pour toujours"; "subscription_plan_perpetual" = "Pour toujours";
"tap_to_open" = "Appuyez pour agrandir";
@@ -33,6 +33,7 @@
"filter_show_area" = "Prikaži sve potrese u radijusu od:"; "filter_show_area" = "Prikaži sve potrese u radijusu od:";
"filter_show_relevant" = "Prikaži samo relevantne potrese s obzirom na moju lokaciju"; "filter_show_relevant" = "Prikaži samo relevantne potrese s obzirom na moju lokaciju";
"filter_show_all" = "Prikaži sve potrese globalno (M≥2)"; "filter_show_all" = "Prikaži sve potrese globalno (M≥2)";
"filter_show_felt" = "Prikaži samo potrese koje su korisnici osjetili";
"filter_minimum_magnitude" = "i minimalna veličina:"; "filter_minimum_magnitude" = "i minimalna veličina:";
"main_understood" = "Razumijem"; "main_understood" = "Razumijem";
"options_low_magnitude" = "Imajte na umu da ne pružaju sve seizmološke mreže podatke o potresima jačine ispod 2,0. Štoviše, znatno povećavate prijenos podataka i upotrebu baterije zbog obavijesti. Ako vaš uređaj nije nedavno proizveden, primijetit ćete i opće usporavanje."; "options_low_magnitude" = "Imajte na umu da ne pružaju sve seizmološke mreže podatke o potresima jačine ispod 2,0. Štoviše, znatno povećavate prijenos podataka i upotrebu baterije zbog obavijesti. Ako vaš uređaj nije nedavno proizveden, primijetit ćete i opće usporavanje.";
@@ -53,6 +54,7 @@
"filter_area" = "Prikazani potresi: u polumjeru"; "filter_area" = "Prikazani potresi: u polumjeru";
"filter_relevant" = "Prikazani potresi: relevantni"; "filter_relevant" = "Prikazani potresi: relevantni";
"filter_all" = "Prikazani potresi: svi (M≥2)"; "filter_all" = "Prikazani potresi: svi (M≥2)";
"filter_felt" = "Prikazani potresi: osjetili";
"liveview_unknown_location" = "Vaš položaj nije poznat. Omogući lokaciju u konfiguraciji pametnog telefona"; "liveview_unknown_location" = "Vaš položaj nije poznat. Omogući lokaciju u konfiguraciji pametnog telefona";
"map_number" = "Potres je otkrio sljedeći broj pametnih telefona: %@"; "map_number" = "Potres je otkrio sljedeći broj pametnih telefona: %@";
"permission_location_no" = "Odlučili ste onemogućiti aplikaciji očitavanje lokacije uređaja. NEĆETE primati obavijesti i upozorenja u stvarnom vremenu"; "permission_location_no" = "Odlučili ste onemogućiti aplikaciji očitavanje lokacije uređaja. NEĆETE primati obavijesti i upozorenja u stvarnom vremenu";
@@ -75,6 +77,7 @@
"status_cancel" = "Poništi"; "status_cancel" = "Poništi";
"options_alarms" = "Upozorenje u stvarnom vremenu"; "options_alarms" = "Upozorenje u stvarnom vremenu";
"options_notification_enable_alarm" = "Aktiviraj alarm kada mreža pametnih telefona u stvarnom vremenu otkrije potres"; "options_notification_enable_alarm" = "Aktiviraj alarm kada mreža pametnih telefona u stvarnom vremenu otkrije potres";
"options_notification_disable_sound" = "Onemogućite zvuk alarma ako je podrhtavanje na vašoj lokaciji blago";
"options_notification_official" = "Obavijesti o seizmološkim mrežama"; "options_notification_official" = "Obavijesti o seizmološkim mrežama";
"options_notification_enable_official" = "Primajte obavijesti o potresima koje su otkrile nacionalne i međunarodne seizmičke mreže"; "options_notification_enable_official" = "Primajte obavijesti o potresima koje su otkrile nacionalne i međunarodne seizmičke mreže";
"options_official_type" = "Filter obavijesti"; "options_official_type" = "Filter obavijesti";
@@ -142,6 +145,8 @@
"mercalli_XI" = "XI - Uništavanje čitavih urbanih središta, mnogo žrtava, pukotine u zemlji i klizišta"; "mercalli_XI" = "XI - Uništavanje čitavih urbanih središta, mnogo žrtava, pukotine u zemlji i klizišta";
"mercalli_XII" = "XII - Poremećaj tla, pomicanje zemljine kore"; "mercalli_XII" = "XII - Poremećaj tla, pomicanje zemljine kore";
"mercalli_intensity" = "Intenzitet %@"; "mercalli_intensity" = "Intenzitet %@";
"shakemap" = "Mapa intenziteta";
"shakemap_description" = "Karta intenziteta na temelju izvješća o filu";
// values // values
"official_magnitude_value_00" = "Jačina >= 0.0"; "official_magnitude_value_00" = "Jačina >= 0.0";
@@ -231,3 +236,4 @@
"subscription_plan_monthly" = "Mjesečno"; "subscription_plan_monthly" = "Mjesečno";
"subscription_plan_yearly" = "Godišnji"; "subscription_plan_yearly" = "Godišnji";
"subscription_plan_perpetual" = "Zauvijek"; "subscription_plan_perpetual" = "Zauvijek";
"tap_to_open" = "Dodirnite za uvećanje";
@@ -33,6 +33,7 @@
"filter_show_area" = "Tampilkan semua gempa dalam radius:"; "filter_show_area" = "Tampilkan semua gempa dalam radius:";
"filter_show_relevant" = "Tampilkan hanya gempa bumi yang relevan dengan lokasi saya"; "filter_show_relevant" = "Tampilkan hanya gempa bumi yang relevan dengan lokasi saya";
"filter_show_all" = "Tampilkan semua gempa bumi secara global (M≥2)"; "filter_show_all" = "Tampilkan semua gempa bumi secara global (M≥2)";
"filter_show_felt" = "Hanya menampilkan gempa bumi yang dirasakan oleh pengguna";
"filter_minimum_magnitude" = "dan besaran minimum:"; "filter_minimum_magnitude" = "dan besaran minimum:";
"main_understood" = "Paham"; "main_understood" = "Paham";
"options_low_magnitude" = "Harap diperhatikan bahwa tidak semua jaringan seismik menyediakan data gempa di bawah magnitudo 2,0 SR. Selain itu, pemberitahuan yang diaktifkan akan menambah transfer data dan penggunaan baterai secara signifikan. Perangkat juga mungkin berjalan lebih lambat, kecuali jika perangkat Anda tergolong baru diproduksi."; "options_low_magnitude" = "Harap diperhatikan bahwa tidak semua jaringan seismik menyediakan data gempa di bawah magnitudo 2,0 SR. Selain itu, pemberitahuan yang diaktifkan akan menambah transfer data dan penggunaan baterai secara signifikan. Perangkat juga mungkin berjalan lebih lambat, kecuali jika perangkat Anda tergolong baru diproduksi.";
@@ -53,6 +54,7 @@
"filter_area" = "Gempa yang ditampilkan: dalam radius"; "filter_area" = "Gempa yang ditampilkan: dalam radius";
"filter_relevant" = "Gempa bumi yang ditampilkan: relevan"; "filter_relevant" = "Gempa bumi yang ditampilkan: relevan";
"filter_all" = "Gempa bumi ditampilkan: semua (M≥2)"; "filter_all" = "Gempa bumi ditampilkan: semua (M≥2)";
"filter_felt" = "Gempa yang ditampilkan: terasa";
"liveview_unknown_location" = "Posisi Anda tidak diketahui. Aktifkan lokasi smartphone dari konfigurasi smartphone"; "liveview_unknown_location" = "Posisi Anda tidak diketahui. Aktifkan lokasi smartphone dari konfigurasi smartphone";
"map_number" = "Gempa terdeteksi oleh %@ smartphone"; "map_number" = "Gempa terdeteksi oleh %@ smartphone";
"permission_location_no" = "Anda telah memilih untuk mencegah aplikasi membaca lokasi perangkat. Anda TIDAK akan menerima pemberitahuan dan peringatan secara real time"; "permission_location_no" = "Anda telah memilih untuk mencegah aplikasi membaca lokasi perangkat. Anda TIDAK akan menerima pemberitahuan dan peringatan secara real time";
@@ -75,6 +77,7 @@
"status_cancel" = "Batal"; "status_cancel" = "Batal";
"options_alarms" = "Peringatan real time"; "options_alarms" = "Peringatan real time";
"options_notification_enable_alarm" = "Nyalakan alarm saat gempa terdeteksi secara real time oleh jaringan smartphone"; "options_notification_enable_alarm" = "Nyalakan alarm saat gempa terdeteksi secara real time oleh jaringan smartphone";
"options_notification_disable_sound" = "Nonaktifkan suara alarm jika guncangan di lokasi Anda ringan";
"options_notification_official" = "Pemberitahuan jaringan seismik"; "options_notification_official" = "Pemberitahuan jaringan seismik";
"options_notification_enable_official" = "Terima pemberitahuan untuk gempa bumi yang terdeteksi oleh jaringan seismik nasional dan internasional"; "options_notification_enable_official" = "Terima pemberitahuan untuk gempa bumi yang terdeteksi oleh jaringan seismik nasional dan internasional";
"options_official_type" = "Filter notifikasi"; "options_official_type" = "Filter notifikasi";
@@ -105,7 +108,7 @@
"permission_location_no_background_solve" = "Atasi"; "permission_location_no_background_solve" = "Atasi";
"main_share_app" = "Bagikan Aplikasi"; "main_share_app" = "Bagikan Aplikasi";
"main_vote" = "Beri Rating untuk Aplikasi"; "main_vote" = "Beri Rating untuk Aplikasi";
"main_twitter_see" = "Lihat di Twitter"; "main_twitter_see" = "Lihat di X";
"map_smartphone_magnitude" = "Magnitudo akan diperkirakan oleh jaringan seismik nasional dan akan muncul di tab Jaringan Seismik pada aplikasi"; "map_smartphone_magnitude" = "Magnitudo akan diperkirakan oleh jaringan seismik nasional dan akan muncul di tab Jaringan Seismik pada aplikasi";
"main_alerttest" = "Tes peringatan"; "main_alerttest" = "Tes peringatan";
"main_simulator" = "Simulator"; "main_simulator" = "Simulator";
@@ -142,6 +145,8 @@
"mercalli_XI" = "XI - Penghancuran seluruh pusat kota, banyak korban, celah-celah di tanah dan tanah longsor"; "mercalli_XI" = "XI - Penghancuran seluruh pusat kota, banyak korban, celah-celah di tanah dan tanah longsor";
"mercalli_XII" = "XII - Gangguan tanah, perpindahan kerak bumi"; "mercalli_XII" = "XII - Gangguan tanah, perpindahan kerak bumi";
"mercalli_intensity" = "Intensitas %@"; "mercalli_intensity" = "Intensitas %@";
"shakemap" = "Peta intensitas";
"shakemap_description" = "Peta intensitas berdasarkan laporan pengguna";
// values // values
"official_magnitude_value_00" = "Magnitudo >= 0.0"; "official_magnitude_value_00" = "Magnitudo >= 0.0";
@@ -231,3 +236,4 @@
"subscription_plan_monthly" = "Bulanan"; "subscription_plan_monthly" = "Bulanan";
"subscription_plan_yearly" = "Tahunan"; "subscription_plan_yearly" = "Tahunan";
"subscription_plan_perpetual" = "Selamanya"; "subscription_plan_perpetual" = "Selamanya";
"tap_to_open" = "Ketuk untuk memperbesar";
@@ -33,6 +33,7 @@
"filter_show_area" = "Mostra tutti i sismi nel raggio di:"; "filter_show_area" = "Mostra tutti i sismi nel raggio di:";
"filter_show_relevant" = "Mostra solo i sismi rilevanti rispetto alla mia posizione"; "filter_show_relevant" = "Mostra solo i sismi rilevanti rispetto alla mia posizione";
"filter_show_all" = "Mostra tutti i sismi a livello globale (M≥2)"; "filter_show_all" = "Mostra tutti i sismi a livello globale (M≥2)";
"filter_show_felt" = "Mostra solo i sismi percepiti dagli utenti";
"filter_minimum_magnitude" = "e magnitudo minima:"; "filter_minimum_magnitude" = "e magnitudo minima:";
"main_understood" = "Ho capito"; "main_understood" = "Ho capito";
"options_low_magnitude" = "Considera che non tutte le reti sismiche forniscono dati per sismi sotto magnitudo 2.0. Inoltre, incrementi significativamente il trasferimento dati con il server e l'utilizzo batteria dovuto al maggior numero di notifiche. Se il tuo dispositivo non è di recente fabbricazione, noterai anche un generale rallentamento dell'app."; "options_low_magnitude" = "Considera che non tutte le reti sismiche forniscono dati per sismi sotto magnitudo 2.0. Inoltre, incrementi significativamente il trasferimento dati con il server e l'utilizzo batteria dovuto al maggior numero di notifiche. Se il tuo dispositivo non è di recente fabbricazione, noterai anche un generale rallentamento dell'app.";
@@ -53,6 +54,7 @@
"filter_area" = "Sismi mostrati: nel raggio"; "filter_area" = "Sismi mostrati: nel raggio";
"filter_relevant" = "Sismi mostrati: rilevanti"; "filter_relevant" = "Sismi mostrati: rilevanti";
"filter_all" = "Sismi mostrati: tutti (M≥2)"; "filter_all" = "Sismi mostrati: tutti (M≥2)";
"filter_felt" = "Sismi mostrati: percepiti";
"liveview_unknown_location" = "La tua posizione è sconosciuta. Abilita la localizzazione dalla pagina di configurazione del tuo dispositivo"; "liveview_unknown_location" = "La tua posizione è sconosciuta. Abilita la localizzazione dalla pagina di configurazione del tuo dispositivo";
"map_number" = "Sisma rilevato da %@ smartphone"; "map_number" = "Sisma rilevato da %@ smartphone";
"permission_location_no" = "Hai scelto di impedire alla app di leggere la posizione del tuo dispositivo. NON riceverai notifiche ed allerte in tempo reale."; "permission_location_no" = "Hai scelto di impedire alla app di leggere la posizione del tuo dispositivo. NON riceverai notifiche ed allerte in tempo reale.";
@@ -75,6 +77,7 @@
"status_cancel" = "Cancella"; "status_cancel" = "Cancella";
"options_alarms" = "Allerta in tempo reale"; "options_alarms" = "Allerta in tempo reale";
"options_notification_enable_alarm" = "Attiva un allarme quando un sisma è rilevato dalla rete smartphone"; "options_notification_enable_alarm" = "Attiva un allarme quando un sisma è rilevato dalla rete smartphone";
"options_notification_disable_sound" = "Disabilita il suono di allerta se l'intensità del sisma nella tua zona è debole";
"options_notification_official" = "Notifiche da reti sismiche"; "options_notification_official" = "Notifiche da reti sismiche";
"options_notification_enable_official" = "Ricevi notifiche per i sismi rilevati dalle reti sismiche nazionali e internazionali"; "options_notification_enable_official" = "Ricevi notifiche per i sismi rilevati dalle reti sismiche nazionali e internazionali";
"options_official_type" = "Filtro notifiche"; "options_official_type" = "Filtro notifiche";
@@ -105,7 +108,7 @@
"permission_location_no_background_solve" = "Correggi"; "permission_location_no_background_solve" = "Correggi";
"main_share_app" = "Condividi App"; "main_share_app" = "Condividi App";
"main_vote" = "Vota l'App"; "main_vote" = "Vota l'App";
"main_twitter_see" = "Vedi in Twitter"; "main_twitter_see" = "Vedi in X";
"map_smartphone_magnitude" = "La magnitudo sarà comunicata dalla rete sismica nazionale e comparirà nella sezione Reti Sismiche dell'app"; "map_smartphone_magnitude" = "La magnitudo sarà comunicata dalla rete sismica nazionale e comparirà nella sezione Reti Sismiche dell'app";
"main_alerttest" = "Test Allerta"; "main_alerttest" = "Test Allerta";
"main_simulator" = "Simulatore"; "main_simulator" = "Simulatore";
@@ -142,6 +145,8 @@
"mercalli_XI" = "XI - Distruzione di interi centri urbani, moltissime vittime, crepacci nel suolo e frane "; "mercalli_XI" = "XI - Distruzione di interi centri urbani, moltissime vittime, crepacci nel suolo e frane ";
"mercalli_XII" = "XII - Sconvolgimento del suolo, dislocamento della crosta terrestre"; "mercalli_XII" = "XII - Sconvolgimento del suolo, dislocamento della crosta terrestre";
"mercalli_intensity" = "Intensità %@"; "mercalli_intensity" = "Intensità %@";
"shakemap" = "Mappa intensità";
"shakemap_description" = "Mappa di intensità basata sulle segnalazioni degli utenti";
// values // values
"official_magnitude_value_00" = "Magnitudo >= 0.0"; "official_magnitude_value_00" = "Magnitudo >= 0.0";
@@ -231,3 +236,4 @@
"subscription_plan_monthly" = "Mensile"; "subscription_plan_monthly" = "Mensile";
"subscription_plan_yearly" = "Annuale"; "subscription_plan_yearly" = "Annuale";
"subscription_plan_perpetual" = "A vita"; "subscription_plan_perpetual" = "A vita";
"tap_to_open" = "Tocca per aprire";
@@ -33,6 +33,7 @@
"filter_show_area" = "Aşağıdaki yarıçap içindeki tüm depremleri göster:"; "filter_show_area" = "Aşağıdaki yarıçap içindeki tüm depremleri göster:";
"filter_show_relevant" = "Konumuma göre yalnızca ilgili depremleri göster"; "filter_show_relevant" = "Konumuma göre yalnızca ilgili depremleri göster";
"filter_show_all" = "Dünyadaki tüm depremleri göster (M≥2)"; "filter_show_all" = "Dünyadaki tüm depremleri göster (M≥2)";
"filter_show_felt" = "Sadece kullanıcıların hissettiği depremleri göster";
"filter_minimum_magnitude" = "ve minimum büyüklük:"; "filter_minimum_magnitude" = "ve minimum büyüklük:";
"main_understood" = "Anladım"; "main_understood" = "Anladım";
"options_low_magnitude" = "Tüm sismik ağların 2.0'nin altında büyüklüğe sahip depremlerin verilerini sağlamadığını unutmayın. Ayrıca, bildirimler nedeniyle veri aktarımınız ve pil kullanımınız önemli ölçüde artar. Cihazınız yakın zamanda üretilmediği sürece, genel bir yavaşlama da fark edersiniz."; "options_low_magnitude" = "Tüm sismik ağların 2.0'nin altında büyüklüğe sahip depremlerin verilerini sağlamadığını unutmayın. Ayrıca, bildirimler nedeniyle veri aktarımınız ve pil kullanımınız önemli ölçüde artar. Cihazınız yakın zamanda üretilmediği sürece, genel bir yavaşlama da fark edersiniz.";
@@ -53,6 +54,7 @@
"filter_area" = "Gösterilen depremler: yarıçap içinde"; "filter_area" = "Gösterilen depremler: yarıçap içinde";
"filter_relevant" = "Gösterilen depremler: alakalı"; "filter_relevant" = "Gösterilen depremler: alakalı";
"filter_all" = "Gösterilen depremler: tümü (M≥2)"; "filter_all" = "Gösterilen depremler: tümü (M≥2)";
"filter_felt" = "Gösterilen depremler: hissedildi";
"liveview_unknown_location" = "Konumunuz bilinmiyor. Akıllı telefon yapılandırmasından akıllı telefon konumunu etkinleştirin"; "liveview_unknown_location" = "Konumunuz bilinmiyor. Akıllı telefon yapılandırmasından akıllı telefon konumunu etkinleştirin";
"map_number" = "%@ akıllı telefon tarafından tespit edilen deprem"; "map_number" = "%@ akıllı telefon tarafından tespit edilen deprem";
"permission_location_no" = "Uygulamanın cihazın konumunu okumasını engellemeyi seçtiniz. Gerçek zamanlı bildirim ve uyarılar ALMAYACAKSINIZ"; "permission_location_no" = "Uygulamanın cihazın konumunu okumasını engellemeyi seçtiniz. Gerçek zamanlı bildirim ve uyarılar ALMAYACAKSINIZ";
@@ -75,6 +77,7 @@
"status_cancel" = "İptal et"; "status_cancel" = "İptal et";
"options_alarms" = "Gerçek zamanlı uyarı"; "options_alarms" = "Gerçek zamanlı uyarı";
"options_notification_enable_alarm" = "Bir deprem akıllı telefon ağı tarafından gerçek zamanlı olarak tespit edildiğinde bir alarm çal"; "options_notification_enable_alarm" = "Bir deprem akıllı telefon ağı tarafından gerçek zamanlı olarak tespit edildiğinde bir alarm çal";
"options_notification_disable_sound" = "Bulunduğunuz yerdeki sarsıntı hafifse alarm sesini devre dışı bırakın";
"options_notification_official" = "Sismik ağ bildirimleri"; "options_notification_official" = "Sismik ağ bildirimleri";
"options_notification_enable_official" = "Ulusal ve uluslararası sismik ağlar tarafından tespit edilen depremler için bildirim alın"; "options_notification_enable_official" = "Ulusal ve uluslararası sismik ağlar tarafından tespit edilen depremler için bildirim alın";
"options_official_type" = "Bildirim filtresi"; "options_official_type" = "Bildirim filtresi";
@@ -105,7 +108,7 @@
"permission_location_no_background_solve" = "Çözün"; "permission_location_no_background_solve" = "Çözün";
"main_share_app" = "Uygul. Paylaş"; "main_share_app" = "Uygul. Paylaş";
"main_vote" = "Uygulamayı oyla"; "main_vote" = "Uygulamayı oyla";
"main_twitter_see" = "Twitter"; "main_twitter_see" = "X";
"map_smartphone_magnitude" = "Büyüklük ulusal sismik ağ tarafından tahmin edilecek ve uygulamanın Sismik Ağlar sekmesinde görünecektir."; "map_smartphone_magnitude" = "Büyüklük ulusal sismik ağ tarafından tahmin edilecek ve uygulamanın Sismik Ağlar sekmesinde görünecektir.";
"main_alerttest" = "Test uyarısı"; "main_alerttest" = "Test uyarısı";
"main_simulator" = "Simülatör"; "main_simulator" = "Simülatör";
@@ -142,6 +145,8 @@
"mercalli_XI" = "XI - Tüm şehir merkezlerinin, birçok kurbanın, zemindeki yarıkların ve toprak kaymalarının yok edilmesi"; "mercalli_XI" = "XI - Tüm şehir merkezlerinin, birçok kurbanın, zemindeki yarıkların ve toprak kaymalarının yok edilmesi";
"mercalli_XII" = "XII - Toprağın bozulması, yer kabuğunun yer değiştirmesi"; "mercalli_XII" = "XII - Toprağın bozulması, yer kabuğunun yer değiştirmesi";
"mercalli_intensity" = "Şiddeti %@"; "mercalli_intensity" = "Şiddeti %@";
"shakemap" = "Yoğunluk haritası";
"shakemap_description" = "Kullanıcı raporlarına dayalı yoğunluk haritası";
// values // values
"official_magnitude_value_00" = "Büyüklük >= 0.0"; "official_magnitude_value_00" = "Büyüklük >= 0.0";
@@ -231,3 +236,4 @@
"subscription_plan_monthly" = "Aylık"; "subscription_plan_monthly" = "Aylık";
"subscription_plan_yearly" = "Yıllık"; "subscription_plan_yearly" = "Yıllık";
"subscription_plan_perpetual" = "Sonsuza kadar"; "subscription_plan_perpetual" = "Sonsuza kadar";
"tap_to_open" = "Büyütmek için dokunun";