Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f85c60fdda | |||
| 5f2a083789 | |||
| 9e54f74847 | |||
| b6f9232f56 | |||
| dee14dea0f | |||
| db0bde2f59 | |||
| 79d0d27ec5 | |||
| 68012ec406 | |||
| 59feb7699b | |||
| 388f4e8b89 | |||
| ca3c9ebd83 | |||
| f23c19bce7 | |||
| 276fa2032a | |||
| 09f0d4d4d8 | |||
| 25f061ad5a | |||
| b9d9f7579c | |||
| 39f5ff0249 | |||
| af68d70be5 | |||
| dab999a78d | |||
| f5ede5c26d | |||
| 6d4c1eb979 | |||
| 9bf6b75dac | |||
| 69b83e9944 | |||
| 5061e36a45 | |||
| 8919f3c08f | |||
| 9cf9ef8a64 | |||
| 9ee3a478f0 | |||
| 8744595b56 | |||
| fa05d6b5c4 | |||
| 471ccc9e4a | |||
| 55de6f5ba0 | |||
| b12a9cc476 | |||
| a2f740b0a8 | |||
| 9cf93e652b | |||
| 2d23056ba8 | |||
| cb6ecca774 | |||
| 96286a49f6 | |||
| 481e8a28c0 | |||
| 286a4062f5 | |||
| 01a8ad7419 | |||
| 6e97e9bd2c | |||
| af6e94efcb | |||
| 5387758449 | |||
| 054603b42d | |||
| caf0e3b7cc | |||
| 4c35c38cc5 | |||
| 521254c8c1 | |||
| 78a1710584 | |||
| b2a54a544c | |||
| 0f5ad24744 | |||
| 0296cd50cd | |||
| 7551988b4e | |||
| 5edcaaad99 | |||
| b12f83680a | |||
| ee827c41ae | |||
| d0d06394f0 | |||
| b933b900ed | |||
| 0e7de44332 | |||
| 547bb794f0 | |||
| 9b1f1f12d2 | |||
| 7fc324367d | |||
| 3cb712f709 | |||
| 993e2924c7 | |||
| a167c989cc | |||
| 1b50f4fd17 | |||
| 0003b4607c | |||
| 85c9f333ce | |||
| 217cbfd4e3 | |||
| 5d8de1fb36 | |||
| f23bb78ceb | |||
| 0d91954614 | |||
| 49f5fa91fe | |||
| 68e560768b | |||
| 3e9c319b50 | |||
| d35e0e1b4a | |||
| 6ede137ef7 | |||
| c94195d48e | |||
| 28919d7b72 | |||
| a239534b91 | |||
| 226342f36c | |||
| ca6afbec5f | |||
| 465d3e8013 | |||
| a7e88b43f5 | |||
| 57ef877846 | |||
| c44d97b9fb | |||
| fd4ed7f66f | |||
| ef5db97854 | |||
| ce0e17a0c5 | |||
| 2a46f1d2d6 | |||
| 93871f0358 | |||
| 3e8fe0680d | |||
| 6be5f72360 | |||
| ccd1b9de59 | |||
| 5737eb5b02 | |||
| c549bb6ea5 | |||
| ff80905033 | |||
| dad2bc5648 | |||
| 10c74e278e | |||
| 96dbf960d2 | |||
| 81bfdd02a6 | |||
| 2ab3267981 | |||
| 48b6941ed5 | |||
| 669cb3c4f3 | |||
| 638d819d35 | |||
| a9884d8a8d | |||
| 2ef3560011 | |||
| 05093bb7a4 | |||
| 55f84ab46d | |||
| 03b4d0ddd6 | |||
| 3c5f26bc94 | |||
| 8c79d45b19 | |||
| 931d04c5e1 | |||
| 4d62fbbbd3 | |||
| 1c7065ece7 | |||
| 6dfa51e013 | |||
| b8b21d1458 | |||
| 88317f79e8 | |||
| 4e1147e782 | |||
| 579969d507 | |||
| 4d991d9a10 | |||
| 41491b5ee7 | |||
| 197b375c28 | |||
| f41e6b50ec | |||
| 796e4b5895 | |||
| e43a93979d | |||
| ef1aaa7d71 | |||
| 22d78baa8a | |||
| e4588aa731 | |||
| 07764f91ed | |||
| a0a238e384 |
@@ -1,5 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
## 5.11
|
||||
- Numeri romani in etichette shakemaps
|
||||
- Aggiunta barra di recap dei filtri in Lista Sismi
|
||||
- Riscritta NotificationContent in Swift
|
||||
- Aggiunta animazione onda sismica in NotificationContent (+ altre modifiche grafiche)
|
||||
|
||||
## 5.10
|
||||
- Usato endpoint cache per distquake_download_areacheck
|
||||
- Aggiunta impostazione per non riprodurre suono notifiche per simsi deboli
|
||||
|
||||
## Versione 5.9.1
|
||||
- Corretto ordinamento in sottoscrizioni attive (prima Top10k)
|
||||
- Modificato parsing per risolvere crash con valori nulli
|
||||
|
||||
## Versione 5.9
|
||||
- Aggiunta barra laterale in lista sismi
|
||||
- Aggiunto filtro "sismi percepiti"
|
||||
- Aggiunta "Mappa intensità"
|
||||
|
||||
## Versione 5.8.1
|
||||
- Corrette traduzioni errate (causavano crash)
|
||||
- Aggiunto ordinamento in sottoscrizioni utente (prima Top10k)
|
||||
|
||||
## Versione 5.8
|
||||
- Modifica algoritmo filtro lista (issue #70)
|
||||
- Modifica impostazioni "Notifiche da reti sismiche" (issue #66)
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
{
|
||||
"Simulator Target Bundle": "com.finazzi.distquake",
|
||||
"magnitude_range" : "0",
|
||||
"provider" : "SGC",
|
||||
"google.c.a.e" : "1",
|
||||
"google.c.fid" : "d3PS1dEvrUA-tmLLpl5E5f",
|
||||
"provider" : "INGV",
|
||||
"google.c.fid" : "fFjFx_Em8E-op_zHYXZpSr",
|
||||
"preliminary" : "0",
|
||||
"longitude" : "-75.5157",
|
||||
"gcm.message_id" : "1668682445010677",
|
||||
"latitude" : "4.35306",
|
||||
"type" : "official",
|
||||
"longitude" : "15.2917",
|
||||
"gcm.message_id" : "1719991146578422",
|
||||
"latitude" : "40.7738",
|
||||
"google.c.sender.id" : "899482329945",
|
||||
"difference" : "6",
|
||||
"data" : "2022-11-17 11:48:00",
|
||||
"depth" : "26",
|
||||
"type" : "official",
|
||||
"magnitude" : "1.4",
|
||||
"difference" : "13",
|
||||
"depth" : "14.6",
|
||||
"aps" : {
|
||||
"content-available" : 1,
|
||||
"mutable-content" : 1,
|
||||
"alert" : {
|
||||
"loc-key" : "Sisma rilevato a",
|
||||
"body" : "Sisma rilevato a",
|
||||
"title-loc-key" : "Segnalazione da rete sismica",
|
||||
"title" : "Segnalazione da rete sismica",
|
||||
"loc-key" : "Sisma rilevato a",
|
||||
"action-loc-key" : "",
|
||||
"loc-args" : [
|
||||
"Cajamarca - Tolima, Colombia - M2.2"
|
||||
"2 km SW Laviano (SA) - M1.4"
|
||||
]
|
||||
},
|
||||
"mutable-content" : 1,
|
||||
"content-available" : 1,
|
||||
"sound" : "default"
|
||||
},
|
||||
"magnitude" : "2.2",
|
||||
"magnitude_type" : "M",
|
||||
"place" : "Cajamarca - Tolima, Colombia",
|
||||
"pop100" : "6622"
|
||||
"data" : "2024-07-03 09:05:52",
|
||||
"magnitude_type" : "ML",
|
||||
"place" : "2 km SW Laviano (SA)",
|
||||
"pop100" : "6824"
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"loc-args": [
|
||||
"2 km da Foligno"
|
||||
],
|
||||
"loc-key": "Rilevato sisma forte a",
|
||||
"loc-key": "Sisma segnalato da utente a",
|
||||
"title-loc-key": "Allerta sismica in tempo reale"
|
||||
},
|
||||
"category": "notifica_con_mappa",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?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"/>
|
||||
<dependencies>
|
||||
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -11,37 +11,45 @@
|
||||
<!--Notification View Controller-->
|
||||
<scene sceneID="cwh-vc-ff4">
|
||||
<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">
|
||||
<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"/>
|
||||
<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">
|
||||
<rect key="frame" x="8" y="216" width="304" height="20.5"/>
|
||||
<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="350" width="304" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</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">
|
||||
<rect key="frame" x="8" y="285" width="304" height="29"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<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="417" width="304" height="53"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
|
||||
<color key="textColor" red="0.91764705879999997" green="0.46274509800000002" blue="0.0078431372550000003" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="4ID-Zb-OQF">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="200"/>
|
||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" showsUserLocation="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4ID-Zb-OQF">
|
||||
<rect key="frame" x="0.0" y="20" width="320" height="320"/>
|
||||
<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>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="M4Y-Lb-cyx" id="Cs2-OY-eT2"/>
|
||||
</connections>
|
||||
</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">
|
||||
<rect key="frame" x="8" y="244.5" width="304" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<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="380.5" width="304" height="26.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</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>
|
||||
<viewLayoutGuide key="safeArea" id="2BE-c3-nQJ"/>
|
||||
<constraints>
|
||||
@@ -49,23 +57,26 @@
|
||||
<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="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="2BE-c3-nQJ" firstAttribute="bottom" secondItem="bT3-3m-qLh" secondAttribute="bottom" constant="16" id="I71-6U-jK3"/>
|
||||
<constraint firstItem="pCT-Wh-lut" firstAttribute="top" secondItem="4ID-Zb-OQF" secondAttribute="bottom" constant="16" id="It9-RA-906"/>
|
||||
<constraint firstItem="f3d-th-bgU" firstAttribute="top" secondItem="pCT-Wh-lut" secondAttribute="bottom" constant="10" id="FJ8-nn-ydU"/>
|
||||
<constraint firstItem="s7c-ag-zBA" firstAttribute="top" secondItem="2BE-c3-nQJ" secondAttribute="top" constant="6" id="Gsp-ye-V4M"/>
|
||||
<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="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="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="s7c-ag-zBA" firstAttribute="centerX" secondItem="S3S-Oj-5AN" secondAttribute="centerX" id="mKj-2O-QDw"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<extendedEdge key="edgesForExtendedLayout"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="320" height="330"/>
|
||||
<size key="freeformSize" width="320" height="480"/>
|
||||
<connections>
|
||||
<outlet property="descriptionLabel" destination="f3d-th-bgU" id="Aym-KJ-DqY"/>
|
||||
<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="waveLabel" destination="bT3-3m-qLh" id="AkJ-nd-d2R"/>
|
||||
</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)
|
||||
}
|
||||
|
||||
// evaluate intensity and get proper string to display
|
||||
guard let latitude = userInfo.double(forKey: "latitude"),
|
||||
let longitude = userInfo.double(forKey: "longitude"),
|
||||
let peak = userInfo.double(forKey: "peak") else {
|
||||
print("[NotificationService] Unable to get base info for intensity calculation")
|
||||
return
|
||||
}
|
||||
|
||||
let magnitude = userInfo.double(forKey: "mag") ?? 0
|
||||
let location = CLLocation(latitude: latitude, longitude: longitude)
|
||||
guard let distance = EQNUserData.shared.lastLocation?.distance(from: location) else {
|
||||
print("[NotificationService] Unable to calculate distance or get last location")
|
||||
return
|
||||
}
|
||||
|
||||
let distanceKm = distance / 1_000
|
||||
|
||||
// If the shake is mild, user can disale sound and use default notification sound
|
||||
// This logic is done here and not with the rest because distance and other informations are needed
|
||||
let mildQuakeSoundDisabled = UserDefaults.appGroup?.bool(forKey: UserDefaults.AllertaSismicaSuonoDisabilitatoSismaDebole) ?? true
|
||||
let isMildQuake = isMildQuake(magnitude: magnitude, distance: distanceKm)
|
||||
if isMildQuake && mildQuakeSoundDisabled {
|
||||
bestAttemptContent.sound = UNNotificationSound.default
|
||||
}
|
||||
|
||||
let intensita = peak * exp(-distanceKm/peak/250)
|
||||
let stringSuffix = if intensita < 0.004 {
|
||||
"no_shaking"
|
||||
} else if intensita < 0.30 {
|
||||
"mild"
|
||||
} else if intensita < 0.70 {
|
||||
"moderate"
|
||||
} else {
|
||||
"strong"
|
||||
}
|
||||
bestAttemptContent.body = "alert_intensity_\(stringSuffix)".localized
|
||||
|
||||
let intensity = userInfo.integer(forKey: "intensity")
|
||||
switch intensity {
|
||||
case 0:
|
||||
@@ -63,57 +100,13 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
break
|
||||
}
|
||||
|
||||
// evaluate intensity and get proper string to display
|
||||
guard let latitude = userInfo.double(forKey: "latitude"),
|
||||
let longitude = userInfo.double(forKey: "longitude"),
|
||||
let peak = userInfo.double(forKey: "peak") else {
|
||||
print("[NotificationService] Unable to get base info for intensity calculation")
|
||||
return
|
||||
}
|
||||
|
||||
let location = CLLocation(latitude: latitude, longitude: longitude)
|
||||
guard let distance = EQNUserData.shared.lastLocation?.distance(from: location) else {
|
||||
print("[NotificationService] Unable to calculate distance or get last location")
|
||||
return
|
||||
}
|
||||
|
||||
let distanceKm = distance / 1_000
|
||||
let intensita = peak * exp(-distanceKm/peak/250)
|
||||
let stringSuffix: String
|
||||
|
||||
if intensita < 0.004 {
|
||||
stringSuffix = "no_shaking"
|
||||
} else if intensita < 0.30 {
|
||||
stringSuffix = "mild"
|
||||
} else if intensita < 0.70 {
|
||||
stringSuffix = "moderate"
|
||||
} else {
|
||||
stringSuffix = "strong"
|
||||
}
|
||||
bestAttemptContent.body = "alert_intensity_\(stringSuffix)".localized
|
||||
|
||||
case "manual":
|
||||
// there are 12 levels, so a customized icon doesn't make sense
|
||||
// use a generic warning icon instead
|
||||
iconName = "warning_yellow.png"
|
||||
case "official":
|
||||
let provider = userInfo.string(forKey: "provider", orDefault: "")
|
||||
let intensity = userInfo.double(forKey: "magnitude", orDefault: 0)
|
||||
|
||||
let color: String
|
||||
if intensity < 2.0 {
|
||||
color = "_white"
|
||||
} else if intensity < 3.5 {
|
||||
color = "_green"
|
||||
} else if intensity < 4.5 {
|
||||
color = "_yellow"
|
||||
} else if intensity < 5.5 {
|
||||
color = "_red"
|
||||
} else {
|
||||
color = "_purple"
|
||||
}
|
||||
|
||||
iconName = manualIconName(for: provider, color: color)
|
||||
// don't show any images
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -174,7 +167,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
|
||||
|
||||
// !! Note: this is a known issue/bug
|
||||
// we need to add a delay before invoking the completion, otherwise the notification will not be remved
|
||||
// we need to add a delay before invoking the completion, otherwise the notification will not be removed
|
||||
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
completion()
|
||||
@@ -182,6 +175,20 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private func isMildQuake(
|
||||
magnitude: Double,
|
||||
distance: Double
|
||||
) -> Bool {
|
||||
var intensity_at_location: Double = 0
|
||||
if distance > 0 {
|
||||
let R: Double = 6371
|
||||
let eq_depth: Double = 10.0
|
||||
let hyp_distance = sqrt(pow(eq_depth, 2) + 4 * R * (R - eq_depth) * pow(sin(distance / (2 * R)), 2))
|
||||
intensity_at_location = -2.15 * log10(hyp_distance) + 1.0 * magnitude + 2.31
|
||||
}
|
||||
return intensity_at_location < 3
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func manualIconName(for provider: String, color: String) -> String {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 77;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -13,12 +13,18 @@
|
||||
650247122A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650247112A61832F001AC512 /* EQNBackgroundPositionDebugHelper.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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
652A3C6B2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652A3C6A2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift */; };
|
||||
65355FFF25F38D3300BB57D2 /* SegnalazioniMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65355FFE25F38D3300BB57D2 /* SegnalazioniMapViewController.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 */; };
|
||||
653C67E625F3CC8400FE52AC /* EQNCustomAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653C67E125F3CC2E00FE52AC /* EQNCustomAnnotationView.swift */; };
|
||||
653C67FC25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653C67FB25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift */; };
|
||||
@@ -36,17 +42,26 @@
|
||||
65583A05261B83BE00ECA9F9 /* UIKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65583A04261B83BE00ECA9F9 /* UIKit+Extensions.swift */; };
|
||||
6562C80725FFA6B100C85273 /* SeismicNetworkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.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 */; };
|
||||
656D13912C22371F0094F597 /* SubscriptionDetailsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656D13902C22371F0094F597 /* SubscriptionDetailsTableViewCell.swift */; };
|
||||
656E02162C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656E02152C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift */; };
|
||||
656EB9362A15FD16009DADF3 /* 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 */; };
|
||||
658BAB7B25FE67930015C454 /* EQNBaseMapRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658BAB7A25FE67930015C454 /* EQNBaseMapRepresentable.swift */; };
|
||||
658BC0292859A456009EECAA /* RealtimeAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */; };
|
||||
658BC02B2859A4D3009EECAA /* RealtimeAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658BC02A2859A4D3009EECAA /* RealtimeAlertView.swift */; };
|
||||
6590EFF82C3004EA00F41420 /* EQNOfficialPushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6590EFF72C3004EA00F41420 /* EQNOfficialPushNotification.swift */; };
|
||||
65AB4A952C11BED400950DF7 /* SettingsSeismicNetworkNotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AB4A942C11BED400950DF7 /* SettingsSeismicNetworkNotificationsViewController.swift */; };
|
||||
65AB4A972C11DEB300950DF7 /* SettingsSeismicNetworkNotificationsFilterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AB4A962C11DEB300950DF7 /* SettingsSeismicNetworkNotificationsFilterViewController.swift */; };
|
||||
65AB4A992C11DFC200950DF7 /* EQNSettingSeismicNetworkNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AB4A982C11DFC200950DF7 /* EQNSettingSeismicNetworkNotification.swift */; };
|
||||
65AD23CE261B03D400E3B57C /* SubscriptionsDescriptionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AD23CD261B03D400E3B57C /* SubscriptionsDescriptionTableViewCell.swift */; };
|
||||
65B16E1E2BDFA88D0020527E /* Solar in Frameworks */ = {isa = PBXBuildFile; productRef = 65B16E1D2BDFA88D0020527E /* Solar */; };
|
||||
@@ -73,6 +88,10 @@
|
||||
65E6AC702C2DB3B60073F8FE /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 65E6AC6F2C2DB3B60073F8FE /* FirebaseMessaging */; };
|
||||
65EA58802A60269C0038EE9D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8C10B0BD2281FE7F00125C9F /* Localizable.strings */; };
|
||||
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 */; };
|
||||
65F9B49E2A8CA2AC00F13260 /* EQNBackgroundPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49D2A8CA2AC00F13260 /* EQNBackgroundPosition.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 */; };
|
||||
8CF12CD321DE49B600613AC5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CF12CD221DE49B600613AC5 /* UserNotifications.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 */; };
|
||||
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 */; };
|
||||
@@ -246,7 +264,6 @@
|
||||
DC99A50324E66E270071BC9F /* EQNCommandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC99A50224E66E270071BC9F /* EQNCommandProtocol.swift */; };
|
||||
DC99A50524E66E430071BC9F /* EQNAppearanceCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC99A50424E66E430071BC9F /* EQNAppearanceCommand.swift */; };
|
||||
DC99A50724E66E5F0071BC9F /* EQNStartupCommandsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC99A50624E66E5F0071BC9F /* EQNStartupCommandsBuilder.swift */; };
|
||||
DCA5B6E7252E4BD8002AEC96 /* EQNBaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA5B6E6252E4BD8002AEC96 /* EQNBaseTableViewCell.swift */; };
|
||||
DCAA913F24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA913E24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift */; };
|
||||
DCB28CEE24FB8400001F557E /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB28CED24FB8400001F557E /* SettingsViewController.swift */; };
|
||||
DCB528212560161C005288E5 /* AlertSimulatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB528202560161C005288E5 /* AlertSimulatorViewController.swift */; };
|
||||
@@ -308,13 +325,17 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -339,12 +360,18 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
658BC02A2859A4D3009EECAA /* RealtimeAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealtimeAlertView.swift; sourceTree = "<group>"; };
|
||||
658F19692A0D1F8F00BECC05 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
658F196A2A0D1F8F00BECC05 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
6590EFF72C3004EA00F41420 /* EQNOfficialPushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNOfficialPushNotification.swift; sourceTree = "<group>"; };
|
||||
65A4D5AA26280A24003918E0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
65A4D5AB26280A24003918E0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
65A4D5AC26280A56003918E0 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
@@ -356,7 +383,6 @@
|
||||
65A4D5B526281126003918E0 /* id-ID */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "id-ID"; path = "id-ID.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
65A4D5B626281126003918E0 /* id-ID */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "id-ID"; path = "id-ID.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
65AB4A942C11BED400950DF7 /* SettingsSeismicNetworkNotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSeismicNetworkNotificationsViewController.swift; sourceTree = "<group>"; };
|
||||
65AB4A962C11DEB300950DF7 /* SettingsSeismicNetworkNotificationsFilterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSeismicNetworkNotificationsFilterViewController.swift; sourceTree = "<group>"; };
|
||||
65AB4A982C11DFC200950DF7 /* EQNSettingSeismicNetworkNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNSettingSeismicNetworkNotification.swift; sourceTree = "<group>"; };
|
||||
65AD23CD261B03D400E3B57C /* SubscriptionsDescriptionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsDescriptionTableViewCell.swift; sourceTree = "<group>"; };
|
||||
65BBB22B26064BE6005D6CDF /* SegnalazioniLast24HoursCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegnalazioniLast24HoursCell.swift; sourceTree = "<group>"; };
|
||||
@@ -374,6 +400,10 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -533,8 +563,6 @@
|
||||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -574,7 +602,6 @@
|
||||
DC99A50224E66E270071BC9F /* EQNCommandProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNCommandProtocol.swift; sourceTree = "<group>"; };
|
||||
DC99A50424E66E430071BC9F /* EQNAppearanceCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNAppearanceCommand.swift; sourceTree = "<group>"; };
|
||||
DC99A50624E66E5F0071BC9F /* EQNStartupCommandsBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNStartupCommandsBuilder.swift; sourceTree = "<group>"; };
|
||||
DCA5B6E6252E4BD8002AEC96 /* EQNBaseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBaseTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DCAA913E24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingMultivaluesTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DCB28CED24FB8400001F557E /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
DCB528202560161C005288E5 /* AlertSimulatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertSimulatorViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -658,6 +685,7 @@
|
||||
children = (
|
||||
658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */,
|
||||
658BC02A2859A4D3009EECAA /* RealtimeAlertView.swift */,
|
||||
657CCF0B2E32711D00E91715 /* MapSeismicWaveAnimator.swift */,
|
||||
);
|
||||
path = "Realtime Alert";
|
||||
sourceTree = "<group>";
|
||||
@@ -665,8 +693,10 @@
|
||||
65DBFB5225E2A2580041CBA6 /* Map annotation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */,
|
||||
653C67FB25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift */,
|
||||
651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */,
|
||||
657415DE2D70B6F700F54890 /* EQNMapAnnotationShakemap.swift */,
|
||||
654D18C825F93CD700BB6DB0 /* EQNMapAnnotationPastquake.swift */,
|
||||
);
|
||||
path = "Map annotation";
|
||||
@@ -891,8 +921,7 @@
|
||||
children = (
|
||||
6557CBBC26078A1700962757 /* EQNNotificationContent.entitlements */,
|
||||
8C483CB621FDACD100259FD2 /* EQNNotificationContent-Bridging-Header.h */,
|
||||
8CF12CD721DE49B600613AC5 /* NotificationViewController.h */,
|
||||
8CF12CD821DE49B600613AC5 /* NotificationViewController.m */,
|
||||
657CCF072E323AF700E91715 /* NotificationContentViewController.swift */,
|
||||
8CF12CDA21DE49B600613AC5 /* MainInterface.storyboard */,
|
||||
8CF12CDD21DE49B600613AC5 /* Info.plist */,
|
||||
);
|
||||
@@ -914,12 +943,16 @@
|
||||
DC141968250E769B0059E060 /* Seismic Networks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.swift */,
|
||||
DC28142E2519C21400C1AFF7 /* Cells */,
|
||||
65F0857E2609288E0002615C /* Filters */,
|
||||
6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.swift */,
|
||||
657747FC2D4D12A200213830 /* SeismicNetworkData.swift */,
|
||||
657747FE2D4D2BA100213830 /* SeismicNetworkScrollIndicatorView.swift */,
|
||||
653C5C2B2E295C5A00E25528 /* SeismicNetworkFilterRecapView.swift */,
|
||||
DC2814372519C56100C1AFF7 /* SeismicNetworksViewController.swift */,
|
||||
DCC76BD7251F56050005C4DC /* SeismicCardSettingsViewController.swift */,
|
||||
65DBFB4A25E29DD60041CBA6 /* SeismicNetworksMapDetailViewController.swift */,
|
||||
65F9A6072D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift */,
|
||||
);
|
||||
path = "Seismic Networks";
|
||||
sourceTree = "<group>";
|
||||
@@ -928,7 +961,9 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65DBFB7D25E2CB020041CBA6 /* Ad templates */,
|
||||
652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */,
|
||||
DC28142F2519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift */,
|
||||
652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */,
|
||||
6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */,
|
||||
);
|
||||
path = Cells;
|
||||
@@ -989,6 +1024,7 @@
|
||||
DC10563F251E7EC0002579BB /* Extensions */,
|
||||
8CF66054214C566A009F4314 /* Reachability.h */,
|
||||
8CF66056214C566A009F4314 /* Reachability.m */,
|
||||
65F9A60B2D70781A008A12B5 /* Log.swift */,
|
||||
);
|
||||
path = Libs;
|
||||
sourceTree = "<group>";
|
||||
@@ -1044,6 +1080,7 @@
|
||||
8C4E344A2152EE5B008B0D2A /* EQNGeneratoreURLServer.m */,
|
||||
8CF66057214C566B009F4314 /* ServerRequest.h */,
|
||||
8CF66055214C566A009F4314 /* ServerRequest.m */,
|
||||
65F9A6092D706ADC008A12B5 /* APIService.swift */,
|
||||
);
|
||||
path = "Server Requests";
|
||||
sourceTree = "<group>";
|
||||
@@ -1114,6 +1151,8 @@
|
||||
8CF4F4DA216D44930057110B /* EQNPastquakes.m */,
|
||||
65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */,
|
||||
6552C1452926DBA1008E723C /* AppPreferences.swift */,
|
||||
6590EFF72C3004EA00F41420 /* EQNOfficialPushNotification.swift */,
|
||||
65F9A60D2D707BFE008A12B5 /* EQNShakemap.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -1141,13 +1180,12 @@
|
||||
DCC23DED24D28F41003A2404 /* UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DC52B8A424FCCD6900ABEBA6 /* AppTheme.swift */,
|
||||
DCC23DEE24D28F58003A2404 /* EQNEdgeInsetLabel.swift */,
|
||||
DC03BEAA250BC0A60084769B /* EQNRoundedButton.swift */,
|
||||
DC52B8A424FCCD6900ABEBA6 /* AppTheme.swift */,
|
||||
DCA5B6E6252E4BD8002AEC96 /* EQNBaseTableViewCell.swift */,
|
||||
6586971025F44C26009C0182 /* EQNBlurredCloseButton.swift */,
|
||||
656E02152C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift */,
|
||||
653C67E125F3CC2E00FE52AC /* EQNCustomAnnotationView.swift */,
|
||||
6586971025F44C26009C0182 /* EQNBlurredCloseButton.swift */,
|
||||
65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */,
|
||||
);
|
||||
path = UI;
|
||||
@@ -1175,7 +1213,6 @@
|
||||
65DB60EF2C16F5B100164366 /* SettingsBaseTableViewController.swift */,
|
||||
65DB60F92C17158D00164366 /* SettingsUserReportNotificationsViewController.swift */,
|
||||
65AB4A942C11BED400950DF7 /* SettingsSeismicNetworkNotificationsViewController.swift */,
|
||||
65AB4A962C11DEB300950DF7 /* SettingsSeismicNetworkNotificationsFilterViewController.swift */,
|
||||
65DB60F12C16FA3600164366 /* SettingsRealTimeAlertsViewController.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
@@ -1293,8 +1330,9 @@
|
||||
8CBD3DBA2149B9AD0070C963 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 1410;
|
||||
LastUpgradeCheck = 1410;
|
||||
LastUpgradeCheck = 1620;
|
||||
ORGANIZATIONNAME = "Earthquake Network";
|
||||
TargetAttributes = {
|
||||
65FFDC91292F672B00EA821B = {
|
||||
@@ -1324,7 +1362,6 @@
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 8CBD3DBD2149B9AD0070C963 /* Build configuration list for PBXProject "Earthquake Network" */;
|
||||
compatibilityVersion = "Xcode 8.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
@@ -1348,6 +1385,7 @@
|
||||
65B16E282BDFB39B0020527E /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */,
|
||||
65E6AC6C2C2DB3B60073F8FE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
|
||||
);
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = 8CBD3DC32149B9AD0070C963 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
@@ -1534,6 +1572,7 @@
|
||||
files = (
|
||||
65FFDC95292F672B00EA821B /* NotificationService.swift in Sources */,
|
||||
6502470E2A6136F0001AC512 /* Constants.swift in Sources */,
|
||||
6514FF6D2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */,
|
||||
6502470F2A613AD7001AC512 /* EQNUserData.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1562,6 +1601,7 @@
|
||||
DC03BEAB250BC0A60084769B /* EQNRoundedButton.swift in Sources */,
|
||||
65AD23CE261B03D400E3B57C /* SubscriptionsDescriptionTableViewCell.swift in Sources */,
|
||||
DCD4571C24F6CF0D00B58304 /* EQNGenericValue.swift in Sources */,
|
||||
657747FD2D4D12A200213830 /* SeismicNetworkData.swift in Sources */,
|
||||
8C4E343F215012FA008B0D2A /* EQNManager.m in Sources */,
|
||||
DCAA913F24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift in Sources */,
|
||||
DCF9E14F24F6EA07002B6B1D /* EQNSeismicNetwork.swift in Sources */,
|
||||
@@ -1569,13 +1609,16 @@
|
||||
650247122A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift in Sources */,
|
||||
65CB83432915720400EE1E35 /* EQNUserData.swift in Sources */,
|
||||
654D18C425F93C0600BB6DB0 /* PasquakesMapViewController.swift in Sources */,
|
||||
652247242D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift in Sources */,
|
||||
8C483CBC21FDACE500259FD2 /* EQNInAppProducts.swift in Sources */,
|
||||
8C483CB821FDACD300259FD2 /* IAPHelper.swift in Sources */,
|
||||
652247262D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift in Sources */,
|
||||
6562C80725FFA6B100C85273 /* SeismicNetworkViewModel.swift in Sources */,
|
||||
DCDE0BD924E58CCE00209778 /* EQNMainTabBarController.m in Sources */,
|
||||
8C4E344B2152EE5B008B0D2A /* EQNGeneratoreURLServer.m in Sources */,
|
||||
65F9A60A2D706ADC008A12B5 /* APIService.swift in Sources */,
|
||||
6590EFF82C3004EA00F41420 /* EQNOfficialPushNotification.swift in Sources */,
|
||||
DC99A50724E66E5F0071BC9F /* EQNStartupCommandsBuilder.swift in Sources */,
|
||||
DCA5B6E7252E4BD8002AEC96 /* EQNBaseTableViewCell.swift in Sources */,
|
||||
8CF66058214C566B009F4314 /* ServerRequest.m in Sources */,
|
||||
DCF9E14D24F6D1AA002B6B1D /* EQNData.swift in Sources */,
|
||||
DC52B8A524FCCD6900ABEBA6 /* AppTheme.swift in Sources */,
|
||||
@@ -1585,6 +1628,7 @@
|
||||
DC886A5D24E92D5500F7A5D3 /* EQNBaseViewController.m in Sources */,
|
||||
8C593E8A217BA2470008B260 /* EQNSegnalazione.m in Sources */,
|
||||
8CBD3DCA2149B9AD0070C963 /* AllerteViewController.m in Sources */,
|
||||
65F9A60E2D707BFE008A12B5 /* EQNShakemap.swift in Sources */,
|
||||
DC646F32252B698B000AA5FD /* AlertsSeismicNotificationCompactTableViewCell.swift in Sources */,
|
||||
DCF10DCD24D2C935009F34C3 /* EQNPurchaseAvailability.swift in Sources */,
|
||||
6552C1462926DBA1008E723C /* AppPreferences.swift in Sources */,
|
||||
@@ -1599,6 +1643,7 @@
|
||||
8CF6604F214C0E58009F4314 /* EQNCalibrazione.m in Sources */,
|
||||
65355FFF25F38D3300BB57D2 /* SegnalazioniMapViewController.swift in Sources */,
|
||||
DCBB84F0252CFC4600F12633 /* AlertsNoLocationTableViewCell.swift in Sources */,
|
||||
657747FF2D4D2BA200213830 /* SeismicNetworkScrollIndicatorView.swift in Sources */,
|
||||
651901B925F5358700CAFF20 /* EQNMapAnnotationSeismic.swift in Sources */,
|
||||
DC99A50324E66E270071BC9F /* EQNCommandProtocol.swift in Sources */,
|
||||
DCF10DC624D2B8C7009F34C3 /* EQNPurchaseUtility.swift in Sources */,
|
||||
@@ -1607,7 +1652,7 @@
|
||||
65BBB22C26064BE6005D6CDF /* SegnalazioniLast24HoursCell.swift in Sources */,
|
||||
DC47D1BC252A0C2B004119F6 /* AlertsPastEartquakesTableViewCell.swift in Sources */,
|
||||
654D18C925F93CD700BB6DB0 /* EQNMapAnnotationPastquake.swift in Sources */,
|
||||
65AB4A972C11DEB300950DF7 /* SettingsSeismicNetworkNotificationsFilterViewController.swift in Sources */,
|
||||
6514FF6A2D720C3F000A7BD0 /* MapPinStyle.swift in Sources */,
|
||||
DCEFF21724F58569009D3FE1 /* SettingSectionHeaderView.swift in Sources */,
|
||||
65DB60FD2C172C4A00164366 /* EQNSettingRealTimeAlert.swift in Sources */,
|
||||
DCBB267A24D1E7F500F04559 /* SubscriptionsHeaderTableViewCell.swift in Sources */,
|
||||
@@ -1619,6 +1664,7 @@
|
||||
65F9B4A02A8CC58200F13260 /* UpdateUserLocationTask.swift in Sources */,
|
||||
8C4E34422152B5E8008B0D2A /* EQNRilevamento.m in Sources */,
|
||||
8C7A3B66225A5EA40045B266 /* NSDictionary+EQNExtensions.m in Sources */,
|
||||
653C5C2C2E295C5A00E25528 /* SeismicNetworkFilterRecapView.swift in Sources */,
|
||||
65AB4A992C11DFC200950DF7 /* EQNSettingSeismicNetworkNotification.swift in Sources */,
|
||||
8CF66053214C12DC009F4314 /* EQNMath.m in Sources */,
|
||||
DCF4A54524F8DB8300B17326 /* SettingDateTableViewCell.swift in Sources */,
|
||||
@@ -1626,14 +1672,17 @@
|
||||
DC7EEE4A252A11C9004B4A2A /* AlertsSmartphoneNetworkTableViewCell.swift in Sources */,
|
||||
DC7EEE4F252A1634004B4A2A /* AlertsPriorityServiceTableViewCell.swift in Sources */,
|
||||
653604E9262348FA00B2B651 /* EQNBaseMapFilter.swift in Sources */,
|
||||
65F9A60C2D70781A008A12B5 /* Log.swift in Sources */,
|
||||
65583A05261B83BE00ECA9F9 /* UIKit+Extensions.swift in Sources */,
|
||||
65DBFB7625E2BBF20041CBA6 /* GADTTemplateView.m in Sources */,
|
||||
8C5EA23D2177B51C002DC156 /* SegnalazioniViewController.m in Sources */,
|
||||
656E02162C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift in Sources */,
|
||||
656D138F2C2225560094F597 /* SubscriptionDetailsViewController.swift in Sources */,
|
||||
65F9A6082D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift in Sources */,
|
||||
653C67E225F3CC2E00FE52AC /* EQNCustomAnnotationView.swift in Sources */,
|
||||
8CF4F4D8216D3A110057110B /* EQNAreaCheck.m in Sources */,
|
||||
8C4E34452152B707008B0D2A /* EQNAccelerometroManager.m in Sources */,
|
||||
657CCF0C2E32711D00E91715 /* MapSeismicWaveAnimator.swift in Sources */,
|
||||
65D409942619BA34008CF356 /* SegnalazioniSendReportCell.swift in Sources */,
|
||||
65F9B49C2A8CA22800F13260 /* BackgroundTaskManager.swift in Sources */,
|
||||
656D13912C22371F0094F597 /* SubscriptionDetailsTableViewCell.swift in Sources */,
|
||||
@@ -1653,6 +1702,7 @@
|
||||
8C8EBBA721540039002784BA /* EQNUser.m in Sources */,
|
||||
8CADAA9421B2627D0044E256 /* EQNLogViewController.m in Sources */,
|
||||
DC3BA11124D1A9C90062EE7F /* SubscriptionsViewController.swift in Sources */,
|
||||
657415E02D70B6F800F54890 /* EQNMapAnnotationShakemap.swift in Sources */,
|
||||
DC646F27252B694A000AA5FD /* AlertsSeismicNotificationExpandedTableViewCell.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1664,16 +1714,21 @@
|
||||
8C465D9F21F7BE0600F04673 /* Assets.xcassets in Sources */,
|
||||
65D9938C2922647800F2B0EB /* AppTheme.swift in Sources */,
|
||||
DC0AE1B92538204100111307 /* EQNSegnalazione.m in Sources */,
|
||||
657CCF092E323BE500E91715 /* EQNRealtimePushNotification.swift in Sources */,
|
||||
657CCF082E323AF700E91715 /* NotificationContentViewController.swift in Sources */,
|
||||
654D18DE25F943E200BB6DB0 /* EQNMapAnnotationUserReport.swift in Sources */,
|
||||
654D18D625F9420500BB6DB0 /* EQNMapAnnotationPastquake.swift in Sources */,
|
||||
65D507DB2A852274005BDC57 /* EQNUserData.swift in Sources */,
|
||||
657CCF0A2E323DC600E91715 /* Foundation+Extensions.swift in Sources */,
|
||||
DC0AE1B52538202300111307 /* EQNUtility.m in Sources */,
|
||||
65D507DC2A85227F005BDC57 /* Constants.swift in Sources */,
|
||||
657CCF0D2E32711D00E91715 /* MapSeismicWaveAnimator.swift in Sources */,
|
||||
653C67E625F3CC8400FE52AC /* EQNCustomAnnotationView.swift in Sources */,
|
||||
654D18DA25F9424700BB6DB0 /* EQNUtility+Extensions.swift in Sources */,
|
||||
DC0AE1BA2538204100111307 /* EQNPastquakes.m in Sources */,
|
||||
6568C2D42E2A930500402F16 /* EQNBlurredCloseButton.swift in Sources */,
|
||||
65D9938A29219DEC00F2B0EB /* UIKit+Extensions.swift in Sources */,
|
||||
8CF12CD921DE49B600613AC5 /* NotificationViewController.m in Sources */,
|
||||
6514FF6C2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1777,7 +1832,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 129;
|
||||
CURRENT_PROJECT_VERSION = "$(inherited)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -1788,13 +1843,12 @@
|
||||
INFOPLIST_FILE = EQNNotificationService/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = EQNNotificationService;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Earthquake Network. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = "$(inherited)";
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice;
|
||||
@@ -1806,7 +1860,6 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1819,20 +1872,19 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 129;
|
||||
CURRENT_PROJECT_VERSION = "$(inherited)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = EQNNotificationService/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = EQNNotificationService;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Earthquake Network. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = "$(inherited)";
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1842,7 +1894,6 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1882,6 +1933,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 159;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = WJA4MR4CPC;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -1901,7 +1953,8 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = 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;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1944,6 +1997,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 159;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = WJA4MR4CPC;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
@@ -1957,7 +2011,8 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = 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;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@@ -1968,24 +2023,24 @@
|
||||
8CBD3DDC2149B9AD0070C963 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 129;
|
||||
CURRENT_PROJECT_VERSION = "$(inherited)";
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = WJA4MR4CPC;
|
||||
ENABLE_MODULE_VERIFIER = YES;
|
||||
GCC_PREFIX_HEADER = "Earthquake Network/Earthquake Network-Prefix.pch";
|
||||
INFOPLIST_FILE = "Earthquake Network/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = "$(inherited)";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -2048,30 +2103,29 @@
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Earthquake Network/Earthquake Network-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
8CBD3DDD2149B9AD0070C963 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 129;
|
||||
CURRENT_PROJECT_VERSION = "$(inherited)";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = WJA4MR4CPC;
|
||||
ENABLE_MODULE_VERIFIER = YES;
|
||||
GCC_PREFIX_HEADER = "Earthquake Network/Earthquake Network-Prefix.pch";
|
||||
INFOPLIST_FILE = "Earthquake Network/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = "$(inherited)";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -2133,7 +2187,6 @@
|
||||
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork - AppStore";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Earthquake Network/Earthquake Network-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -2144,7 +2197,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 129;
|
||||
CURRENT_PROJECT_VERSION = "$(inherited)";
|
||||
DEVELOPMENT_TEAM = WJA4MR4CPC;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -2153,13 +2206,12 @@
|
||||
"NOTIFICATION_CONTENT=1",
|
||||
);
|
||||
INFOPLIST_FILE = EQNNotificationContent/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - Development";
|
||||
@@ -2167,7 +2219,6 @@
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "EQNNotificationContent/EQNNotificationContent-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -2178,27 +2229,25 @@
|
||||
CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 129;
|
||||
CURRENT_PROJECT_VERSION = "$(inherited)";
|
||||
DEVELOPMENT_TEAM = WJA4MR4CPC;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"ADS_ENABLED=0",
|
||||
"NOTIFICATION_CONTENT=1",
|
||||
);
|
||||
INFOPLIST_FILE = EQNNotificationContent/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.8;
|
||||
MARKETING_VERSION = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - AppStore";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "EQNNotificationContent/EQNNotificationContent-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -2246,10 +2295,10 @@
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
6552C13629262119008E723C /* XCRemoteSwiftPackageReference "Shogun" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/andreabusi-it/Shogun";
|
||||
repositoryURL = "https://github.com/andreabusi-it/Shogun.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.0;
|
||||
minimumVersion = 2.0.0;
|
||||
};
|
||||
};
|
||||
65B16E1C2BDFA88D0020527E /* XCRemoteSwiftPackageReference "Solar" */ = {
|
||||
@@ -2273,7 +2322,7 @@
|
||||
repositoryURL = "https://github.com/googleads/swift-package-manager-google-mobile-ads.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 11.3.0;
|
||||
minimumVersion = 12.0.0;
|
||||
};
|
||||
};
|
||||
65B16E282BDFB39B0020527E /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = {
|
||||
@@ -2281,7 +2330,7 @@
|
||||
repositoryURL = "https://github.com/facebook/facebook-ios-sdk";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 14.1.0;
|
||||
minimumVersion = 18.0.0;
|
||||
};
|
||||
};
|
||||
65E6AC6C2C2DB3B60073F8FE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
|
||||
@@ -2289,7 +2338,7 @@
|
||||
repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 10.28.1;
|
||||
minimumVersion = 11.1.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
+33
-24
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"originHash" : "702893df2780e15a380fe3df120a583fefc365501dca7487eaf0c2b967baf71c",
|
||||
"originHash" : "898a30d298491e1ce821191ebfa2e7d28e7c9ea4c119cbfdfb1b245bc94ac6c3",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "abseil-cpp-binary",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/abseil-cpp-binary.git",
|
||||
"state" : {
|
||||
"revision" : "748c7837511d0e6a507737353af268484e1745e2",
|
||||
"version" : "1.2024011601.1"
|
||||
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
|
||||
"version" : "1.2024072200.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -15,8 +15,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/app-check.git",
|
||||
"state" : {
|
||||
"revision" : "3b62f154d00019ae29a71e9738800bb6f18b236d",
|
||||
"version" : "10.19.2"
|
||||
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
|
||||
"version" : "11.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -33,8 +33,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/facebook/facebook-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "c19607d535864533523d1f437c84035e5fb101cf",
|
||||
"version" : "14.1.0"
|
||||
"revision" : "b28dde427715b45a26ebebf697929f4a81b15e04",
|
||||
"version" : "18.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -42,8 +42,17 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
|
||||
"state" : {
|
||||
"revision" : "e57841b296d04370ea23580f908881b0ccab17b9",
|
||||
"version" : "10.28.1"
|
||||
"revision" : "fdc352fabaf5916e7faa1f96ad02b1957e93e5a5",
|
||||
"version" : "11.15.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "google-ads-on-device-conversion-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "428d8bb138e00f9a3f4f61cc6cd8863607524f65",
|
||||
"version" : "2.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -51,8 +60,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "fe727587518729046fc1465625b9afd80b5ab361",
|
||||
"version" : "10.28.0"
|
||||
"revision" : "45ce435e9406d3c674dd249a042b932bee006f60",
|
||||
"version" : "11.15.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -60,8 +69,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleDataTransport.git",
|
||||
"state" : {
|
||||
"revision" : "a637d318ae7ae246b02d7305121275bc75ed5565",
|
||||
"version" : "9.4.0"
|
||||
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
|
||||
"version" : "10.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -69,8 +78,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "57a1d307f42df690fdef2637f3e5b776da02aad6",
|
||||
"version" : "7.13.3"
|
||||
"revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
|
||||
"version" : "8.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -78,8 +87,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/grpc-binary.git",
|
||||
"state" : {
|
||||
"revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359",
|
||||
"version" : "1.62.2"
|
||||
"revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
|
||||
"version" : "1.69.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -96,8 +105,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
|
||||
"state" : {
|
||||
"revision" : "2d12673670417654f08f5f90fdd62926dc3a2648",
|
||||
"version" : "100.0.0"
|
||||
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
|
||||
"version" : "101.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -130,10 +139,10 @@
|
||||
{
|
||||
"identity" : "shogun",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/andreabusi-it/Shogun",
|
||||
"location" : "https://github.com/andreabusi-it/Shogun.git",
|
||||
"state" : {
|
||||
"revision" : "ad890190d6be90f7712c2e56a38ef0937d9f8c1a",
|
||||
"version" : "1.8.0"
|
||||
"revision" : "809b56a43fadac72db9963a21c74688af7ef51b7",
|
||||
"version" : "2.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -150,8 +159,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git",
|
||||
"state" : {
|
||||
"revision" : "9ab66e38f5f0c2d02f2b024b1babd880130f19bf",
|
||||
"version" : "11.3.0"
|
||||
"revision" : "aa24c7dc03bca62c42747314dc8537f15587b50d",
|
||||
"version" : "12.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1410"
|
||||
LastUpgradeVersion = "1620"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1410"
|
||||
LastUpgradeVersion = "1620"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
@@ -15,7 +15,7 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8C4B0B7921CACE3F00AED489"
|
||||
BlueprintIdentifier = "65FFDC91292F672B00EA821B"
|
||||
BuildableName = "EQNNotificationService.appex"
|
||||
BlueprintName = "EQNNotificationService"
|
||||
ReferencedContainer = "container:Earthquake Network.xcodeproj">
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1410"
|
||||
LastUpgradeVersion = "1620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -170,20 +170,22 @@
|
||||
{
|
||||
NSString *type = content.userInfo[@"type"];
|
||||
|
||||
// Store both original payload and modified title/body
|
||||
// This will be usefull to avoid to re-evaluate logic for title display.
|
||||
NSDictionary *notification = @{
|
||||
@"title": content.title,
|
||||
@"body": content.body,
|
||||
@"userInfo": content.userInfo
|
||||
};
|
||||
|
||||
EQNTabBarSection section = EQNTabBarSectionAllerte;
|
||||
if ([type isEqualToString:@"eqn"]) {
|
||||
// Store both original payload and modified title/body
|
||||
// This will be usefull to avoid to re-evaluate logic for title display.
|
||||
NSDictionary *notification = @{
|
||||
@"title": content.title,
|
||||
@"body": content.body,
|
||||
@"userInfo": content.userInfo
|
||||
};
|
||||
if ([type isEqualToString:@"eqn"]) {
|
||||
[EQNRealtimePushNotification storeNotificationWithPayload:notification];
|
||||
section = EQNTabBarSectionAllerte;
|
||||
} else if([type isEqualToString:@"manual"]) {
|
||||
section = EQNTabBarSectionSegnalazioni;
|
||||
} else if([type isEqualToString:@"official"]) {
|
||||
[EQNOfficialPushNotification storeNotificationWithPayload:notification];
|
||||
section = EQNTabBarSectionRetiSismiche;
|
||||
}
|
||||
|
||||
@@ -205,20 +207,16 @@
|
||||
|
||||
- (void)configureAppTracking
|
||||
{
|
||||
if (@available(iOS 14, *)) {
|
||||
// add a delay otherwise the alert will not be displayed
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
|
||||
if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
|
||||
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:YES];
|
||||
} else {
|
||||
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:NO];
|
||||
}
|
||||
}];
|
||||
});
|
||||
} else {
|
||||
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:YES];
|
||||
}
|
||||
// add a delay otherwise the alert will not be displayed
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
|
||||
if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
|
||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = YES;
|
||||
} else {
|
||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = NO;
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)configureFirebase
|
||||
@@ -230,7 +228,7 @@
|
||||
- (void)configureFacebookSDKWithApplication:(UIApplication *)application andOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[FBSDKApplicationDelegate.sharedInstance application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
[FBSDKSettings.sharedSettings setAdvertiserIDCollectionEnabled:YES];
|
||||
FBSDKSettings.sharedSettings.isAdvertiserIDCollectionEnabled = YES;
|
||||
}
|
||||
|
||||
#pragma mark - FIRMessagingDelegate
|
||||
|
||||
@@ -17,6 +17,7 @@ extension UserDefaults {
|
||||
|
||||
// Impostazioni della sezione `Allerta in tempo reale`
|
||||
static let AllertaSismicaAbilitato = "NOTIFICHE_ALLERA_SISMICA_ABILITATO"
|
||||
static let AllertaSismicaSuonoDisabilitatoSismaDebole = "NOTIFICHE_ALLERA_SISMICA_SUONO_DISABILITATO_SISMA_DEBOLE"
|
||||
static let AllertaSismicaCriticalAlerts = "NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS"
|
||||
static let AllertaSismicaSismiDaNotificare = "NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE"
|
||||
static let AllertaSismicaRaggioSismiLievi = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI"
|
||||
@@ -37,6 +38,7 @@ extension UserDefaults {
|
||||
static let UserReportCodeStatus = "CODE_MESSAGE_EQN"
|
||||
|
||||
// Proprietà e preferenze dell'utente
|
||||
static let FirstAppStartExecuted = "EQNUserDefaultFirstAppStartExecuted"
|
||||
/// Ultima posizione conosciuta dell'utente
|
||||
static let UserDataLastLocation = "EQNLast_Location"
|
||||
/// Token Firebase dell'utente corrente
|
||||
@@ -55,12 +57,21 @@ extension UserDefaults {
|
||||
static let UserReportExpandedView = "EQNData.UserReportExpandedView"
|
||||
/// Se `true` visualizza le opzioni nella singole card in reti sismiche
|
||||
static let AlertsShowCardOptions = "EQNetwork.AlertsShowAllCards"
|
||||
/// Indica lo stile di pin da visualizzare nelle mappe
|
||||
static let MapPinStyle = "EQNetwork.MapPinStyle"
|
||||
/// Indica le informazioni da visualizzare nelle card `small` e `full` nella Lista Sismi
|
||||
static let SeismicNetworksCardInformations = "EQNetwork.SeismicInformations";
|
||||
/// Indica la tipologia di card da visualizzare nella Lista Sismi
|
||||
static let SeismicNetworksCardStyle = "EQNetwork.SeismicNetworksCardStyle"
|
||||
|
||||
// Migrazioni
|
||||
static let AppMigrationV5_3 = "EQNUserDefaultMigrationV5_3"
|
||||
static let AppMigrationV5_4 = "EQNUserDefaultMigrationV5_4"
|
||||
static let AppMigrationV5_8 = "EQNUserDefaultMigrationV5_8"
|
||||
|
||||
static let AppMigrationV5_8_2 = "EQNUserDefaultMigrationV5_8_2"
|
||||
static let AppMigrationV5_9 = "EQNUserDefaultMigrationV5_9"
|
||||
static let AppMigrationV5_10 = "EQNUserDefaultMigrationV5_10"
|
||||
|
||||
static let SettingsSeismicNetworkNotificationMigrationV5_8 = "EQNUserDefaultSettingsSeismicNetworkNotificationMigrationV5_8"
|
||||
static let SettingsUserReportNotificationMigrationV5_8 = "EQNUserDefaultSettingsUserReportNotificationMigrationV5_8"
|
||||
static let SismicFiltersMigrationV5_8 = "EQNUserDefaultSismicFiltersMigrationV5_8"
|
||||
@@ -69,6 +80,8 @@ extension UserDefaults {
|
||||
// Notifica allerta salvata
|
||||
static let RealTimeAlertPayload = "EQNData.RealtimePushNotificationPayload"
|
||||
static let RealTimeAlertDate = "EQNData.RealtimeAlertDate"
|
||||
// Notifica rete sismica aperta
|
||||
static let OfficialAlertPayload = "EQNData.OfficialPushNotificationPayload"
|
||||
|
||||
// Filtri sezioni reti sismiche
|
||||
static let SeismicFilterOption = "EQN_SISMI_TIPOLOGIA_FILTRO"
|
||||
|
||||
@@ -100,13 +100,16 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
- (void)setupUI
|
||||
{
|
||||
self.title = [NSLocalizedString(@"tab_network", nil) capitalizedString];
|
||||
|
||||
self.tableView.estimatedRowHeight = 200.0;
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
self.tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets;
|
||||
[self.tableView registerClass:[AlertsSmartphoneNetworkTableViewCell class] forCellReuseIdentifier:@"SmartphoneNetworkCell"];
|
||||
[self.tableView registerClass:[AlertsPriorityServiceTableViewCell class] forCellReuseIdentifier:@"PriorityCell"];
|
||||
[self.tableView registerClass:[AlertsNoLocationTableViewCell class] forCellReuseIdentifier:@"NoLocationCell"];
|
||||
[self.tableView registerClass:[AlertsPastEartquakesTableViewCell class] forCellReuseIdentifier:@"PastEarthquakesCell"];
|
||||
[self.tableView registerClass:[AlertsSeismicNotificationCompactTableViewCell class] forCellReuseIdentifier:@"SeismicNotificationCell"];
|
||||
[self.tableView registerClass:[AlertsSeismicNotificationCompactTableViewCell class] forCellReuseIdentifier:@"SeismicNotificationCompactCell"];
|
||||
[self.tableView registerClass:[AlertsSeismicNotificationExpandedTableViewCell class] forCellReuseIdentifier:@"SeismicNotificationExpandedCell"];
|
||||
[self.tableView registerClass:[AlertsPositionDataTableViewCell class] forCellReuseIdentifier:@"PositionDataCell"];
|
||||
|
||||
if (EQNBackgroundPositionDebugHelper.shared.isEnabled) {
|
||||
@@ -146,7 +149,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
[self.tableItems addObject:@(AllerteTableRowReteSmartphone)];
|
||||
}
|
||||
// check if locations is enabled
|
||||
if (CLLocationManager.authorizationStatus != kCLAuthorizationStatusAuthorizedAlways) {
|
||||
if (EQNUserData.sharedData.locationAuthorizationStatus != kCLAuthorizationStatusAuthorizedAlways) {
|
||||
[self.tableItems addObject:@(AllerteTableRowLocationPermission)];
|
||||
}
|
||||
|
||||
@@ -232,7 +235,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
|
||||
- (void)actionTestPush
|
||||
{
|
||||
CLAuthorizationStatus status = CLLocationManager.authorizationStatus;
|
||||
CLAuthorizationStatus status = EQNUserData.sharedData.locationAuthorizationStatus;
|
||||
if (status != kCLAuthorizationStatusAuthorizedAlways && status != kCLAuthorizationStatusAuthorizedWhenInUse) {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"attention", nil)
|
||||
message:NSLocalizedString(@"liveview_unknown_location", nil)
|
||||
@@ -270,7 +273,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
if (tableRow == AllerteTableRowLocationPermission) {
|
||||
AlertsNoLocationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NoLocationCell" forIndexPath:indexPath];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
[cell updateWith:CLLocationManager.authorizationStatus];
|
||||
[cell updateWith:EQNUserData.sharedData.locationAuthorizationStatus];
|
||||
return cell;
|
||||
|
||||
} else if (tableRow == AllerteTableRowSismiRilevati) {
|
||||
@@ -278,7 +281,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
AlertsSeismicNotificationExpandedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationExpandedCell" forIndexPath:indexPath];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
|
||||
cell.notification = notification;
|
||||
[cell updateWith:notification];
|
||||
|
||||
__weak AllerteViewController *weakSelf = self;
|
||||
cell.onTapClose = ^{
|
||||
@@ -297,7 +300,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
|
||||
return cell;
|
||||
}
|
||||
|
||||
AlertsSeismicNotificationCompactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationCell" forIndexPath:indexPath];
|
||||
AlertsSeismicNotificationCompactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationCompactCell" forIndexPath:indexPath];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
|
||||
__weak AllerteViewController *weakSelf = self;
|
||||
|
||||
+3
-2
@@ -12,6 +12,7 @@ import UIKit
|
||||
class AlertsPriorityServiceTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
override var headerText: String { NSLocalizedString("inapp_list", comment: "") }
|
||||
override var isRightArrowVisbile: Bool { true }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
@@ -28,7 +29,7 @@ class AlertsPriorityServiceTableViewCell: EQNBaseContainerTableViewCell {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.textColor = AppTheme.Colors.pureRed
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
return label
|
||||
}()
|
||||
@@ -54,7 +55,7 @@ class AlertsPriorityServiceTableViewCell: EQNBaseContainerTableViewCell {
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
containerView.backgroundColor = AppTheme.Colors.cardBackgroundRed
|
||||
backgroundColor = AppTheme.Colors.cardBackgroundOrange
|
||||
descriptionLabel.text = NSLocalizedString("inapp_adv", comment: "")
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ class AlertsSeismicNotificationCompactTableViewCell: EQNBaseContainerTableViewCe
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
containerView.backgroundColor = AppTheme.Colors.cardBackgroundGreen
|
||||
backgroundColor = AppTheme.Colors.cardBackgroundGreen
|
||||
descriptionLabel.text = NSLocalizedString("main_nodetection", comment: "")
|
||||
testAlertButton.setLocalizedTitle(key: "main_alerttest", uppercased: true, emoji: "🚨")
|
||||
simulatorAlertButton.setLocalizedTitle(key: "main_simulator", uppercased: true, emoji: "⏱")
|
||||
|
||||
+152
-58
@@ -10,78 +10,173 @@ import UIKit
|
||||
import MapKit
|
||||
import Shogun
|
||||
|
||||
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMapViewDelegate {
|
||||
|
||||
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseContainerTableViewCell, MKMapViewDelegate {
|
||||
|
||||
override var isHeaderVisible: Bool { false }
|
||||
|
||||
typealias DefaultCompletion = () -> Void
|
||||
|
||||
@objc var notification: EQNRealtimePushNotification? {
|
||||
didSet {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
@objc var onTapOpenTwitter: DefaultCompletion?
|
||||
@objc var onTapRateApp: DefaultCompletion?
|
||||
@objc var onTapClose: DefaultCompletion?
|
||||
@objc var onTapShareApp: DefaultCompletion?
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var notificationTitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .title1)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var notificationIntensityLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .title1)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var notificationDescriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var mapView: MKMapView = {
|
||||
let mapView = MKMapView()
|
||||
mapView.translatesAutoresizingMaskIntoConstraints = false
|
||||
mapView.delegate = self
|
||||
mapView.isScrollEnabled = false
|
||||
mapView.isZoomEnabled = false
|
||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||
return mapView
|
||||
}()
|
||||
|
||||
private lazy var shareButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(shareAppTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var rateAppButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(rateAppTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var viewOnTwitterButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(viewInTwitterTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var closeButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(closeTapped(_:)))
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var descriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
@IBOutlet weak var notificationTitleLabel: UILabel!
|
||||
@IBOutlet weak var notificationDescriptionLabel: UILabel!
|
||||
@IBOutlet weak var notificationIntensityLabel: UILabel!
|
||||
@IBOutlet weak var waveTimeLabel: UILabel!
|
||||
@IBOutlet weak var mapView: MKMapView! {
|
||||
didSet {
|
||||
mapView.delegate = self
|
||||
mapView.isScrollEnabled = false
|
||||
mapView.isZoomEnabled = false
|
||||
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||
}
|
||||
}
|
||||
@IBOutlet private weak var shareButton: UIButton!
|
||||
@IBOutlet private weak var rateAppButton: UIButton!
|
||||
@IBOutlet private weak var viewOnTwitterButton: UIButton!
|
||||
@IBOutlet private weak var closeButton: UIButton!
|
||||
@IBOutlet private weak var descriptionLabel: UILabel!
|
||||
private var impactTimestamp: Date?
|
||||
private var countdownTimer: Timer?
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
localizeUI()
|
||||
setUI()
|
||||
let stackView = UIStackView(arrangedSubviews: [shareButton, rateAppButton])
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .horizontal
|
||||
stackView.distribution = .fillEqually
|
||||
stackView.spacing = .cardVerticalSpacing
|
||||
|
||||
containerView.addSubview(notificationTitleLabel)
|
||||
containerView.addSubview(notificationIntensityLabel)
|
||||
containerView.addSubview(notificationDescriptionLabel)
|
||||
containerView.addSubview(mapView)
|
||||
containerView.addSubview(stackView)
|
||||
containerView.addSubview(viewOnTwitterButton)
|
||||
containerView.addSubview(descriptionLabel)
|
||||
containerView.addSubview(closeButton)
|
||||
|
||||
notificationTitleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: .cardPadding).isActive = true
|
||||
notificationTitleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
notificationTitleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
notificationIntensityLabel.topAnchor.constraint(equalTo: notificationTitleLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
notificationIntensityLabel.leadingAnchor.constraint(equalTo: notificationTitleLabel.leadingAnchor).isActive = true
|
||||
notificationIntensityLabel.trailingAnchor.constraint(equalTo: notificationTitleLabel.trailingAnchor).isActive = true
|
||||
|
||||
notificationDescriptionLabel.topAnchor.constraint(equalTo: notificationIntensityLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
notificationDescriptionLabel.leadingAnchor.constraint(equalTo: notificationTitleLabel.leadingAnchor).isActive = true
|
||||
notificationDescriptionLabel.trailingAnchor.constraint(equalTo: notificationTitleLabel.trailingAnchor).isActive = true
|
||||
|
||||
mapView.topAnchor.constraint(equalTo: notificationDescriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
mapView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
|
||||
mapView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
|
||||
mapView.heightAnchor.constraint(greaterThanOrEqualToConstant: 240.0).isActive = true
|
||||
|
||||
shareButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
|
||||
rateAppButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
|
||||
viewOnTwitterButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
|
||||
closeButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
|
||||
stackView.topAnchor.constraint(equalTo: mapView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
stackView.leadingAnchor.constraint(equalTo: notificationDescriptionLabel.leadingAnchor).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: notificationDescriptionLabel.trailingAnchor).isActive = true
|
||||
|
||||
viewOnTwitterButton.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
viewOnTwitterButton.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
|
||||
viewOnTwitterButton.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
|
||||
|
||||
descriptionLabel.topAnchor.constraint(equalTo: viewOnTwitterButton.bottomAnchor, constant: .cardPadding).isActive = true
|
||||
descriptionLabel.leadingAnchor.constraint(equalTo: viewOnTwitterButton.leadingAnchor).isActive = true
|
||||
descriptionLabel.trailingAnchor.constraint(equalTo: viewOnTwitterButton.trailingAnchor).isActive = true
|
||||
|
||||
closeButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
closeButton.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
|
||||
closeButton.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
|
||||
closeButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func localizeUI() {
|
||||
override func updateUI() {
|
||||
super.updateUI()
|
||||
|
||||
shareButton.setLocalizedTitle(key: "main_share_app")
|
||||
rateAppButton.setLocalizedTitle(key: "main_vote")
|
||||
viewOnTwitterButton.setLocalizedTitle(key: "main_twitter_see")
|
||||
closeButton.setLocalizedTitle(key: "official_close")
|
||||
descriptionLabel.text = NSLocalizedString("map_smartphone_magnitude", comment: "")
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
private func setUI() {
|
||||
shareButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
||||
rateAppButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
||||
viewOnTwitterButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
||||
closeButton.layer.borderColor = AppTheme.Colors.darkGray.cgColor
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
@objc
|
||||
func update(with notification: EQNRealtimePushNotification?) {
|
||||
// clearn any other previous notifications
|
||||
notificationTitleLabel.text = ""
|
||||
notificationDescriptionLabel.text = ""
|
||||
notificationTitleLabel.text = "Sisma rilevato a 150km (TEST)"
|
||||
notificationIntensityLabel.text = "Previsto uno scuotimento forte"
|
||||
notificationDescriptionLabel.text = "Distanza 150 km - 13 minuti fa"
|
||||
mapView.removeAnnotations(mapView.annotations)
|
||||
|
||||
guard let notification = notification else { return }
|
||||
|
||||
containerView.backgroundColor = backgroundColor(for: notification.relativeIntensity())
|
||||
|
||||
backgroundColor = backgroundColor(for: notification.relativeIntensity())
|
||||
notificationTitleLabel.text = notification.title
|
||||
notificationIntensityLabel.text = notification.displayBody
|
||||
notificationIntensityLabel.textColor = notification.relativeIntensityColor
|
||||
@@ -95,7 +190,6 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
||||
notificationDescriptionLabel.text = ""
|
||||
+ NSLocalizedString("official_card_distance", comment: "") + " \(distanceRound) km"
|
||||
+ " - " + EQNUtility.formattedString(forTimeDifference: difference)
|
||||
+ "\n" + String(format: NSLocalizedString("map_number", comment: ""), "\(notification.counter)")
|
||||
}
|
||||
|
||||
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
|
||||
@@ -118,37 +212,37 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
|
||||
annotationView.title = annotation.title
|
||||
return annotationView
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction private func shareAppTapped(_ sender: UIButton) {
|
||||
@objc private func shareAppTapped(_ sender: UIButton) {
|
||||
onTapShareApp?()
|
||||
}
|
||||
|
||||
@IBAction private func rateAppTapped(_ sender: UIButton) {
|
||||
@objc private func rateAppTapped(_ sender: UIButton) {
|
||||
onTapRateApp?()
|
||||
}
|
||||
|
||||
@IBAction private func viewInTwitterTapped(_ sender: UIButton) {
|
||||
@objc private func viewInTwitterTapped(_ sender: UIButton) {
|
||||
onTapOpenTwitter?()
|
||||
}
|
||||
|
||||
@IBAction private func closeTapped(_ sender: UIButton) {
|
||||
@objc private func closeTapped(_ sender: UIButton) {
|
||||
onTapClose?()
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
// MARK: - Private
|
||||
|
||||
private func backgroundColor(for intensity: Double) -> UIColor {
|
||||
switch intensity {
|
||||
case _ where intensity < 0.004:
|
||||
return UIColor(named: "Gray (card background)")!
|
||||
return AppTheme.Colors.cardBackgroundGray
|
||||
case _ where intensity < 0.30:
|
||||
return UIColor(named: "Green (card background)")!
|
||||
return AppTheme.Colors.cardBackgroundGreen
|
||||
case _ where intensity < 0.70:
|
||||
return UIColor(named: "Yellow (card background)")!
|
||||
return AppTheme.Colors.cardBackgroundYellow
|
||||
default:
|
||||
return UIColor(named: "Red (card background)")!
|
||||
return AppTheme.Colors.cardBackgroundRed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import Shogun
|
||||
|
||||
@objc
|
||||
class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell {
|
||||
@@ -22,7 +22,7 @@ class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.Colors.green
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
+2
-1
@@ -39,7 +39,7 @@ class SubscriptionDetailsViewController: UITableViewController {
|
||||
products: [EQNInAppProducts]
|
||||
) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ class SubscriptionDetailsViewController: UITableViewController {
|
||||
tableView.estimatedRowHeight = 2000.0
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = .systemGroupedBackground
|
||||
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
|
||||
tableView.registerCell(for: SubscriptionDetailsTableViewCell.self)
|
||||
}
|
||||
|
||||
|
||||
+25
-2
@@ -14,6 +14,8 @@ class SubscriptionsActiveTableViewCell: EQNBaseContainerTableViewCell {
|
||||
|
||||
override var headerText: String { NSLocalizedString("inapp_active", comment: "") }
|
||||
|
||||
var onTapRestore: () -> Void = { }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var noSubscriptionsLabel: UILabel = {
|
||||
@@ -32,22 +34,36 @@ class SubscriptionsActiveTableViewCell: EQNBaseContainerTableViewCell {
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var restoreButton: UIButton = {
|
||||
let button = UIButton(type: .system)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.addTarget(self, action: #selector(restoreSubscriptionsTapped(_:)), for: .touchUpInside)
|
||||
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
|
||||
button.backgroundColor = .systemGroupedBackground
|
||||
button.eqn_applyShadowAndRoundedCorners()
|
||||
return button
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
let stackView = UIStackView(arrangedSubviews: [ activeSubscriptionImageView, noSubscriptionsLabel ])
|
||||
let stackView = UIStackView(arrangedSubviews: [ activeSubscriptionImageView, noSubscriptionsLabel, restoreButton ])
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.alignment = .center
|
||||
stackView.distribution = .equalSpacing
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = 20.0
|
||||
containerView.addSubview(stackView)
|
||||
|
||||
activeSubscriptionImageView.widthAnchor.constraint(equalToConstant: 150.0).isActive = true
|
||||
activeSubscriptionImageView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
|
||||
|
||||
restoreButton.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
|
||||
restoreButton.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
|
||||
restoreButton.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
|
||||
|
||||
stackView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
|
||||
stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
@@ -58,6 +74,13 @@ class SubscriptionsActiveTableViewCell: EQNBaseContainerTableViewCell {
|
||||
super.updateUI()
|
||||
|
||||
noSubscriptionsLabel.text = NSLocalizedString("inapp_nosub", comment: "")
|
||||
restoreButton.setTitle(NSLocalizedString("purchase_pro_restore", comment: ""), for: .normal)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func restoreSubscriptionsTapped(_ sender: UIButton) {
|
||||
onTapRestore()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@@ -18,6 +18,7 @@ class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = UIFont.preferredFont(forTextStyle: .title2)
|
||||
label.textColor = AppTheme.Colors.darkGray
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
@@ -48,6 +49,7 @@ class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
|
||||
|
||||
headerTitleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
|
||||
headerTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .cardPadding).isActive = true
|
||||
headerTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
loadingActivityIndicator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
loadingActivityIndicator.centerYAnchor.constraint(equalTo: headerTitleLabel.centerYAnchor).isActive = true
|
||||
}
|
||||
|
||||
@@ -80,12 +80,6 @@ class SubscriptionsViewController: UITableViewController {
|
||||
}
|
||||
|
||||
private func configureUI() {
|
||||
let restoreButton = UIBarButtonItem(title: NSLocalizedString("purchase_pro_restore", comment: ""),
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(restoreTapped(_:)))
|
||||
navigationItem.rightBarButtonItem = restoreButton
|
||||
|
||||
// if is presented in Simulator, add done button
|
||||
if navigationController?.viewControllers.first == self {
|
||||
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closeTapped(_:)))
|
||||
@@ -97,6 +91,9 @@ class SubscriptionsViewController: UITableViewController {
|
||||
tableView.estimatedRowHeight = 600.0
|
||||
tableView.separatorStyle = .none
|
||||
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: SubscriptionsDescriptionTableViewCell.self)
|
||||
tableView.registerCell(for: SubscriptionProductTableViewCell.self)
|
||||
@@ -113,11 +110,29 @@ class SubscriptionsViewController: UITableViewController {
|
||||
|
||||
let products = storeProducts.compactMap { EQNInAppProducts.from(product: $0) }
|
||||
|
||||
let purchased = products.filter { (product) -> Bool in
|
||||
let isPurchased = EQNInAppProducts.store.isProductPurchased(product.productIdentifier)
|
||||
let isSubscription = product.isSubscription
|
||||
return isPurchased && isSubscription
|
||||
}
|
||||
let purchased = products
|
||||
.filter { (product) -> Bool in
|
||||
// filter for subscriptions
|
||||
let isPurchased = EQNInAppProducts.store.isProductPurchased(product.productIdentifier)
|
||||
let isSubscription = product.isSubscription
|
||||
return isPurchased && isSubscription
|
||||
}.sorted { lProduct, rProduct in
|
||||
// If user has more than one subscriptions,
|
||||
// show first the Top10k.
|
||||
let lIs10k = lProduct.isTop10k
|
||||
let rIs10k = rProduct.isTop10k
|
||||
|
||||
// If left item is Top10k, order first
|
||||
if lIs10k && !rIs10k {
|
||||
return true
|
||||
} else if !lProduct.isTop10k && rProduct.isTop10k {
|
||||
// right product is Top10k, left no
|
||||
return false
|
||||
} else {
|
||||
// both products Top10k or Top100k, keep existing order
|
||||
return false
|
||||
}
|
||||
}
|
||||
self.productSubscribed = purchased.first
|
||||
self.products = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
|
||||
self.tableView.reloadData()
|
||||
@@ -140,11 +155,6 @@ class SubscriptionsViewController: UITableViewController {
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func restoreTapped(_ sender: AnyObject) {
|
||||
isRestorePurchase = true
|
||||
EQNInAppProducts.store.restorePurchases()
|
||||
}
|
||||
|
||||
@objc func closeTapped(_ sender: AnyObject) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
@@ -190,17 +200,22 @@ class SubscriptionsViewController: UITableViewController {
|
||||
|
||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let tableSection = sections[section]
|
||||
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
|
||||
view.update(isLoading: isLoading, title: tableSection.sectionTitle)
|
||||
return view
|
||||
switch tableSection.sectionTitle {
|
||||
case .some(let title):
|
||||
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
|
||||
view.update(isLoading: isLoading, title: title)
|
||||
return view
|
||||
case .none:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
let tableSection = sections[section]
|
||||
if tableSection.sectionTitle != nil {
|
||||
return 50
|
||||
return switch tableSection.sectionTitle {
|
||||
case .some: 50.0
|
||||
case .none: 0.0
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
@@ -220,6 +235,12 @@ class SubscriptionsViewController: UITableViewController {
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionsActiveTableViewCell.self, for: indexPath)
|
||||
cell.selectionStyle = .none
|
||||
cell.update(with: productSubscribed)
|
||||
cell.onTapRestore = { [weak self] in
|
||||
guard let self else { return }
|
||||
|
||||
self.isRestorePurchase = true
|
||||
EQNInAppProducts.store.restorePurchases()
|
||||
}
|
||||
return cell
|
||||
case .description:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionsDescriptionTableViewCell.self, for: indexPath)
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// MapSeismicWaveAnimator.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 24/07/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
|
||||
|
||||
class MapSeismicWaveAnimator {
|
||||
|
||||
private weak var mapView: MKMapView?
|
||||
private weak var waveTimeLabel: UILabel?
|
||||
|
||||
private let OverlayCircleId = "wave_animation"
|
||||
|
||||
/// Alert to display
|
||||
private let realtimeAlert: EQNRealtimePushNotification
|
||||
/// Timer to constantly update countdown label
|
||||
private var countdownTimer: Timer?
|
||||
/// Timer to simulate animation for the wave
|
||||
private var waveAnimationTimer: Timer?
|
||||
/// Refresh time for wave animation
|
||||
private let waveAnimationRefreshRate = 0.1
|
||||
/// Current radius of the wave animation on the map
|
||||
private var waveAnimationCurrentRadius: CLLocationDistance = 0
|
||||
private var waveAnimationVelocity: Double = 1_000
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(
|
||||
realtimeAlert: EQNRealtimePushNotification,
|
||||
mapView: MKMapView,
|
||||
waveTimeLabel: UILabel
|
||||
) {
|
||||
self.realtimeAlert = realtimeAlert
|
||||
self.mapView = mapView
|
||||
self.waveTimeLabel = waveTimeLabel
|
||||
self.setup()
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
self.waveAnimationCurrentRadius = currentWavePosition()
|
||||
self.waveAnimationVelocity = evaluateWaveAnimationVelocity()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
startCountdown()
|
||||
startWaveAnimation()
|
||||
}
|
||||
|
||||
func stop() {
|
||||
stopCountdown()
|
||||
stopWaveAnimation()
|
||||
}
|
||||
|
||||
// MARK: - Wave
|
||||
|
||||
private func startCountdown() {
|
||||
// show countdown only if time is less than 300 seconds
|
||||
if realtimeAlert.currentCountdown() < 300 {
|
||||
// start a timer for the countdown label
|
||||
waveTimeLabel?.isHidden = false
|
||||
countdownTimer = Timer.scheduledTimer(
|
||||
timeInterval: 1.0,
|
||||
target: self,
|
||||
selector: #selector(countdownTimerFired(_:)),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
countdownTimer?.fire()
|
||||
}
|
||||
}
|
||||
|
||||
private func startWaveAnimation() {
|
||||
waveAnimationTimer = Timer.scheduledTimer(
|
||||
timeInterval: waveAnimationRefreshRate,
|
||||
target: self,
|
||||
selector: #selector(mapWaveAnimationFired(_:)),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
waveAnimationTimer?.fire()
|
||||
}
|
||||
|
||||
private func stopCountdown() {
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
}
|
||||
|
||||
private func stopWaveAnimation() {
|
||||
waveAnimationTimer?.invalidate()
|
||||
waveAnimationTimer = nil
|
||||
}
|
||||
|
||||
// MARK: - Timer
|
||||
|
||||
@objc private func countdownTimerFired(_ sender: Timer) {
|
||||
let countdown = realtimeAlert.currentCountdown()
|
||||
waveTimeLabel?.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), countdown)
|
||||
waveTimeLabel?.textColor = waveTimeTextColor(for: countdown)
|
||||
|
||||
if countdown <= 0 {
|
||||
// stop the countdown
|
||||
stopCountdown()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func mapWaveAnimationFired(_ sender: Timer) {
|
||||
waveAnimationCurrentRadius += waveAnimationVelocity
|
||||
addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Evaluate current position for the wave
|
||||
/// Used to define initial position for the wave circle
|
||||
/// - Returns: Distance of the wave from the original earthquake point
|
||||
private func currentWavePosition() -> Double {
|
||||
// distanza tra utente e terremoto
|
||||
let distance = realtimeAlert.distanceFromUser()
|
||||
|
||||
// calcoliamo la distanza rimanente da mostrare, perchè la schermata potrebbe anche essere aperta in ritardo
|
||||
let remainingDistance = realtimeAlert.waveSpeed * Double(realtimeAlert.currentCountdown())
|
||||
return distance - remainingDistance
|
||||
}
|
||||
|
||||
/// Evaluate wave velocity based on push notification data
|
||||
/// - Returns: Wave velocity, used for animation
|
||||
private func evaluateWaveAnimationVelocity() -> Double {
|
||||
let velocity = realtimeAlert.waveSpeed
|
||||
return velocity * waveAnimationRefreshRate
|
||||
}
|
||||
|
||||
/// Returns the text color based on impact countdown
|
||||
private func waveTimeTextColor(for countdown: Int) -> UIColor {
|
||||
switch countdown {
|
||||
case _ where countdown > 15:
|
||||
return UIColor(red: 255.0/255.0, green: 140.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
case _ where countdown > 5:
|
||||
return UIColor(red: 255.0/255.0, green: 100.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
default:
|
||||
return UIColor(red: 255.0/255.0, green: 0.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Map management
|
||||
|
||||
func addMapCircle(
|
||||
center: CLLocationCoordinate2D,
|
||||
radius: CLLocationDistance
|
||||
) {
|
||||
guard let mapView else { return }
|
||||
|
||||
// remove any other existing overlays
|
||||
let overlays = mapView.overlays.filter { $0.title == OverlayCircleId }
|
||||
mapView.removeOverlays(overlays)
|
||||
|
||||
// add new overlay
|
||||
let circle = MKCircle(center: center, radius: radius)
|
||||
circle.title = OverlayCircleId
|
||||
mapView.addOverlay(circle)
|
||||
}
|
||||
|
||||
func getOverlayRenderer(for overlay: MKOverlay) -> MKOverlayRenderer? {
|
||||
switch overlay {
|
||||
case let circle as MKCircle where overlay.title == OverlayCircleId:
|
||||
let circleRenderer = MKCircleRenderer(overlay: circle)
|
||||
circleRenderer.strokeColor = AppTheme.Colors.red
|
||||
circleRenderer.fillColor = AppTheme.Colors.red.withAlphaComponent(0.2)
|
||||
circleRenderer.lineWidth = 3.0
|
||||
return circleRenderer
|
||||
case let polyline as MKPolyline:
|
||||
let polylineRenderer = MKPolylineRenderer(polyline: polyline)
|
||||
polylineRenderer.strokeColor = .blue
|
||||
polylineRenderer.lineWidth = 2.0
|
||||
return polylineRenderer
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ class RealtimeAlertContainerView: UIView {
|
||||
lazy var alertView: RealtimeAlertView = {
|
||||
let view = RealtimeAlertView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.eqn_applyRoundedCorners()
|
||||
view.clipsToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
@@ -78,10 +80,11 @@ class RealtimeAlertView: UIView {
|
||||
let waveTimeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .title2)
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), 0)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 2
|
||||
label.isHidden = true
|
||||
return label
|
||||
}()
|
||||
@@ -89,7 +92,7 @@ class RealtimeAlertView: UIView {
|
||||
let intensityLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .title3)
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 2
|
||||
@@ -99,7 +102,6 @@ class RealtimeAlertView: UIView {
|
||||
lazy var mapView: MKMapView = {
|
||||
let map = MKMapView()
|
||||
map.translatesAutoresizingMaskIntoConstraints = false
|
||||
map.delegate = self
|
||||
map.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||
map.showsUserLocation = true
|
||||
return map
|
||||
@@ -157,28 +159,13 @@ class RealtimeAlertView: UIView {
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func addMapCircle(
|
||||
center: CLLocationCoordinate2D,
|
||||
radius: CLLocationDistance,
|
||||
overlayId: String
|
||||
|
||||
func addMapLine(
|
||||
coordinates: [CLLocationCoordinate2D]
|
||||
) {
|
||||
// remove any other existing overlays
|
||||
let overlays = mapView.overlays.filter { $0.title == overlayId }
|
||||
mapView.removeOverlays(overlays)
|
||||
|
||||
// add new overlay
|
||||
let circle = MKCircle(center: center, radius: radius)
|
||||
circle.title = overlayId
|
||||
mapView.addOverlay(circle)
|
||||
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
|
||||
mapView.addOverlay(polyline)
|
||||
}
|
||||
|
||||
func addMapLine(
|
||||
coordinates: [CLLocationCoordinate2D]
|
||||
) {
|
||||
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
|
||||
mapView.addOverlay(polyline)
|
||||
}
|
||||
|
||||
func addMapAnnotation(
|
||||
title: String = "",
|
||||
@@ -189,35 +176,3 @@ class RealtimeAlertView: UIView {
|
||||
mapView.addAnnotation(annotation)
|
||||
}
|
||||
}
|
||||
|
||||
extension RealtimeAlertView: MKMapViewDelegate {
|
||||
|
||||
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
switch overlay {
|
||||
case let circle as MKCircle:
|
||||
let circleRenderer = MKCircleRenderer(overlay: circle)
|
||||
circleRenderer.strokeColor = AppTheme.Colors.red
|
||||
circleRenderer.fillColor = AppTheme.Colors.red.withAlphaComponent(0.2)
|
||||
circleRenderer.lineWidth = 3.0
|
||||
return circleRenderer
|
||||
case let polyline as MKPolyline:
|
||||
let polylineRenderer = MKPolylineRenderer(polyline: polyline)
|
||||
polylineRenderer.strokeColor = .blue
|
||||
polylineRenderer.lineWidth = 2.0
|
||||
return polylineRenderer
|
||||
default:
|
||||
return MKOverlayRenderer(overlay: overlay)
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
guard let annotation = annotation as? EQNMapAnnotationPastquake else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
|
||||
annotationView.image = annotation.image
|
||||
annotationView.title = annotation.title
|
||||
return annotationView
|
||||
}
|
||||
}
|
||||
|
||||
+27
-85
@@ -10,7 +10,7 @@ import UIKit
|
||||
import MapKit
|
||||
|
||||
|
||||
class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
class RealtimeAlertViewController: UIViewController {
|
||||
|
||||
@objc var onClose: () -> Void = {}
|
||||
|
||||
@@ -20,17 +20,17 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
private var notificationView: RealtimeAlertView {
|
||||
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
|
||||
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
|
||||
|
||||
@@ -38,9 +38,6 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
init(notification: EQNRealtimePushNotification) {
|
||||
self.realtimeAlert = notification
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
self.waveAnimationCurrentRadius = currentWavePosition()
|
||||
self.waveAnimationVelocity = evaluateWaveAnimationVelocity()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -64,8 +61,7 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
configureUI()
|
||||
updateUI()
|
||||
|
||||
startCountdown()
|
||||
startWaveAnimation()
|
||||
animator.start()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
@@ -77,6 +73,8 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
// MARK: - Private
|
||||
|
||||
private func configureUI() {
|
||||
notificationView.mapView.delegate = self
|
||||
|
||||
notificationView.closeButton.addTarget(self, action: #selector(onTapClose(_:)), for: .touchUpInside)
|
||||
|
||||
// configure color for animation
|
||||
@@ -104,93 +102,37 @@ class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
|
||||
// aggiungiamo annotation con epicentro sisma
|
||||
notificationView.addMapAnnotation(center: realtimeAlert.coordinate.coordinate, intensity: realtimeAlert.intensity)
|
||||
|
||||
// simuliamo animazione dell'onda sismica
|
||||
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
||||
|
||||
// aggiungiamo un segmento tra la posizione del sisma e quella dell'utente
|
||||
if let lastPosition = EQNUser.default().lastPosition {
|
||||
notificationView.addMapLine(coordinates: [realtimeAlert.coordinate.coordinate, lastPosition.coordinate])
|
||||
}
|
||||
}
|
||||
|
||||
private func startCountdown() {
|
||||
// show countdown only if time is less than 300 seconds
|
||||
if realtimeAlert.currentCountdown() < 300 {
|
||||
// start a timer for the countdown label
|
||||
notificationView.waveTimeLabel.isHidden = false
|
||||
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdownTimerFired(_:)), userInfo: nil, repeats: true)
|
||||
countdownTimer?.fire()
|
||||
}
|
||||
}
|
||||
|
||||
private func startWaveAnimation() {
|
||||
waveAnimationTimer = Timer.scheduledTimer(timeInterval: waveAnimationRefreshRate, target: self, selector: #selector(mapWaveAnimationFired(_:)), userInfo: nil, repeats: true)
|
||||
waveAnimationTimer?.fire()
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
@objc private func onTapClose(_ sender: UIButton) {
|
||||
// invalidiamo i timer, altri
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
waveAnimationTimer?.invalidate()
|
||||
waveAnimationTimer = nil
|
||||
// stoppiamo animazione e countdown
|
||||
animator.stop()
|
||||
|
||||
onClose()
|
||||
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) {
|
||||
let countdown = realtimeAlert.currentCountdown()
|
||||
notificationView.waveTimeLabel.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), countdown)
|
||||
notificationView.waveTimeLabel.textColor = waveTimeTextColor(for: countdown)
|
||||
|
||||
if countdown <= 0 {
|
||||
// stop the countdown
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
guard let annotation = annotation as? EQNMapAnnotationPastquake else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func mapWaveAnimationFired(_ sender: Timer) {
|
||||
waveAnimationCurrentRadius += waveAnimationVelocity
|
||||
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Evaluate current position for the wave
|
||||
/// Used to define initial position for the wave circle
|
||||
/// - Returns: Distance of the wave from the original earthquake point
|
||||
private func currentWavePosition() -> Double {
|
||||
// distanza tra utente e terremoto
|
||||
let distance = realtimeAlert.distanceFromUser()
|
||||
|
||||
// calcoliamo la distanza rimanente da mostrare, perchè la schermata potrebbe anche essere aperta in ritardo
|
||||
let remainingDistance = realtimeAlert.waveSpeed * Double(realtimeAlert.currentCountdown())
|
||||
return distance - remainingDistance
|
||||
}
|
||||
|
||||
/// Evaluate wave velocity based on push notification data
|
||||
/// - Returns: Wave velocity, used for animation
|
||||
private func evaluateWaveAnimationVelocity() -> Double {
|
||||
let velocity = realtimeAlert.waveSpeed
|
||||
return velocity * waveAnimationRefreshRate
|
||||
}
|
||||
|
||||
/// Returns the text color based on impact countdown
|
||||
private func waveTimeTextColor(for countdown: Int) -> UIColor {
|
||||
switch countdown {
|
||||
case _ where countdown > 15:
|
||||
return UIColor(red: 255.0/255.0, green: 140.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
case _ where countdown > 5:
|
||||
return UIColor(red: 255.0/255.0, green: 100.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
default:
|
||||
return UIColor(red: 255.0/255.0, green: 0.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
}
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
|
||||
annotationView.image = annotation.image
|
||||
annotationView.title = annotation.title
|
||||
return annotationView
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
|
||||
@objc var onTapMap: (() -> Void)?
|
||||
@objc var onTapTelegram: (() -> Void)?
|
||||
|
||||
override var headerText: String { NSLocalizedString("tab_manual", comment: "") }
|
||||
override var isHeaderVisible: Bool { false }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
@@ -24,7 +24,7 @@ class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = AppTheme.Colors.red
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle)
|
||||
label.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
@@ -43,7 +43,7 @@ class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
|
||||
private lazy var twitterButton: UIButton = {
|
||||
let button = EQNRoundedButton.make(target: self, action: #selector(twitterButtonTapped(_:)))
|
||||
button.imageView?.contentMode = .scaleAspectFit
|
||||
button.setImage(.init(named: "twitter_icon"), for: .normal)
|
||||
button.setImage(.init(named: "xcorp_icon"), for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
|
||||
@@ -38,8 +38,10 @@
|
||||
- (void)setupUI
|
||||
{
|
||||
self.title = [NSLocalizedString(@"tab_manual", nil) capitalizedString];
|
||||
|
||||
self.tableView.estimatedRowHeight = 500.0;
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
self.tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets;
|
||||
[self.tableView registerClass:[SegnalazioniLast24HoursCell class] forCellReuseIdentifier:@"Last24HCell"];
|
||||
[self.tableView registerClass:[SegnalazioniSendReportCell class] forCellReuseIdentifier:@"ReportEarthquakeCell"];
|
||||
}
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ class SeismicNetworkAdvertiseTableViewCell: UITableViewCell {
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var bannerView: GADNativeAdView = {
|
||||
private lazy var bannerView: NativeAdView = {
|
||||
let view = GADTMediumTemplateView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
return view
|
||||
@@ -71,7 +71,7 @@ class SeismicNetworkAdvertiseTableViewCell: UITableViewCell {
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func loadNativeAd(_ nativeAd: GADNativeAd) {
|
||||
func loadNativeAd(_ nativeAd: NativeAd) {
|
||||
bannerView.nativeAd = nativeAd
|
||||
}
|
||||
}
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// SeismicNetworkBaseTableViewCell.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 06/03/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
protocol SeismicNetworkBaseTableViewCellDelegate: AnyObject {
|
||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkBaseTableViewCell)
|
||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell)
|
||||
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell)
|
||||
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkBaseTableViewCell)
|
||||
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkBaseTableViewCell)
|
||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkBaseTableViewCell)
|
||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell)
|
||||
}
|
||||
|
||||
|
||||
class SeismicNetworkBaseTableViewCell: UITableViewCell {
|
||||
|
||||
/// Delegate
|
||||
weak var delegate: SeismicNetworkBaseTableViewCellDelegate?
|
||||
|
||||
/// Available informations to display inside the cell
|
||||
enum InformationType: Int {
|
||||
case preliminary
|
||||
case time
|
||||
case distance
|
||||
case coordinate
|
||||
case population
|
||||
case realtimeSmartphones
|
||||
case reportUsers
|
||||
case intensityMap
|
||||
case buttons
|
||||
}
|
||||
|
||||
// MARL: - Internal
|
||||
|
||||
static let DefaultButtonHeight: CGFloat = 34.0
|
||||
static let VerticalSpacingDefault: CGFloat = 6.0
|
||||
static let VerticalSpacingSmall: CGFloat = 2.0
|
||||
static let HorizontalSpacingDefault: CGFloat = 4.0
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
lazy var containerView: UIView = {
|
||||
let view = UIView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = .white
|
||||
view.clipsToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var gradientView: UIImageView = {
|
||||
// Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine
|
||||
// creata ad-hoc con il gradiente desiderato.
|
||||
// Le prove fatte utilizzando una view normale sono fallite perchè al momento di
|
||||
// disegnare la view non abbiamo le misure corrette.
|
||||
let view = UIImageView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.contentMode = .scaleToFill
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
gradientView.eqn_applyRoundedCorners()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
func setupUI() {
|
||||
selectionStyle = .default
|
||||
backgroundColor = .clear
|
||||
|
||||
// container view
|
||||
contentView.addSubview(containerView)
|
||||
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0).isActive = true
|
||||
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
|
||||
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
|
||||
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true
|
||||
|
||||
containerView.addSubview(gradientView)
|
||||
gradientView.constraint(to: containerView)
|
||||
}
|
||||
|
||||
func recreateUI() {
|
||||
// remove all subviews and recreate the required components
|
||||
containerView.subviews.forEach({ $0.removeFromSuperview() })
|
||||
setupUI()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func addSeparator(constraintTo: NSLayoutYAxisAnchor, constanst: CGFloat = 8.0) -> UIView {
|
||||
let separator = UIView()
|
||||
separator.translatesAutoresizingMaskIntoConstraints = false
|
||||
separator.backgroundColor = .lightGray
|
||||
containerView.addSubview(separator)
|
||||
|
||||
separator.topAnchor.constraint(equalTo: constraintTo, constant: constanst).isActive = true
|
||||
separator.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
separator.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
|
||||
|
||||
return separator
|
||||
}
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
//
|
||||
// SeismicNetworkMinimalTableViewCell.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 06/03/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
|
||||
class SeismicNetworkMinimalTableViewCell: SeismicNetworkBaseTableViewCell {
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
private lazy var magnitudeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = UIFont.preferredFont(forTextStyle: .largeTitle)
|
||||
label.textColor = .red
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var placeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold)
|
||||
label.numberOfLines = 3
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var timeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var distanceLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var smartphonesLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var alertsLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.textAlignment = .center
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
/// Seismic to show
|
||||
private var seismic: EQNSisma?
|
||||
private var isPushSelected = false
|
||||
private var informationTypes: Set<InformationType> = []
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
// this variable is used to keep track of the previous view, in order to attach proper constraints
|
||||
var previousView: UIView = containerView
|
||||
|
||||
// preliminary banner on top of the cell
|
||||
if informationTypes.contains(.preliminary) {
|
||||
let preliminaryLabel = UILabel()
|
||||
preliminaryLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
preliminaryLabel.text = NSLocalizedString("official_prelimiary", comment: "").uppercased()
|
||||
preliminaryLabel.textAlignment = .center
|
||||
preliminaryLabel.backgroundColor = .red
|
||||
preliminaryLabel.textColor = .yellow
|
||||
|
||||
containerView.addSubview(preliminaryLabel)
|
||||
preliminaryLabel.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
||||
preliminaryLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
|
||||
preliminaryLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
|
||||
preliminaryLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
|
||||
|
||||
previousView = preliminaryLabel
|
||||
}
|
||||
|
||||
containerView.addSubview(magnitudeLabel)
|
||||
containerView.addSubview(placeLabel)
|
||||
|
||||
let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor
|
||||
|
||||
|
||||
let stackViewInformations = UIStackView(arrangedSubviews: [timeLabel, distanceLabel])
|
||||
stackViewInformations.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewInformations.axis = .horizontal
|
||||
stackViewInformations.distribution = .fillEqually
|
||||
stackViewInformations.spacing = Self.HorizontalSpacingDefault
|
||||
containerView.addSubview(stackViewInformations)
|
||||
|
||||
let stackViewRight = UIStackView(arrangedSubviews: [placeLabel, stackViewInformations])
|
||||
stackViewRight.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewRight.axis = .vertical
|
||||
stackViewRight.distribution = .equalSpacing
|
||||
stackViewRight.spacing = Self.VerticalSpacingDefault
|
||||
|
||||
let stackViewMain = UIStackView(arrangedSubviews: [magnitudeLabel, stackViewRight])
|
||||
stackViewMain.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewMain.axis = .horizontal
|
||||
stackViewMain.distribution = .fill
|
||||
stackViewMain.spacing = Self.HorizontalSpacingDefault
|
||||
containerView.addSubview(stackViewMain)
|
||||
|
||||
stackViewMain.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
|
||||
stackViewMain.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
stackViewMain.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
magnitudeLabel.widthAnchor.constraint(equalToConstant: 60.0).isActive = true
|
||||
|
||||
previousView = stackViewMain
|
||||
|
||||
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) || informationTypes.contains(.intensityMap) {
|
||||
let separator = addSeparator(constraintTo: previousView.bottomAnchor, constanst: Self.VerticalSpacingDefault)
|
||||
|
||||
let stackViewReports = UIStackView()
|
||||
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewReports.axis = .vertical
|
||||
stackViewReports.distribution = .equalSpacing
|
||||
stackViewReports.alignment = .center
|
||||
stackViewReports.spacing = Self.VerticalSpacingDefault
|
||||
|
||||
if informationTypes.contains(.realtimeSmartphones) {
|
||||
stackViewReports.addArrangedSubview(smartphonesLabel)
|
||||
}
|
||||
if informationTypes.contains(.reportUsers) {
|
||||
stackViewReports.addArrangedSubview(alertsLabel)
|
||||
}
|
||||
if informationTypes.contains(.intensityMap) {
|
||||
let buttonMap = EQNRoundedButton.make(title: "🎯 \(NSLocalizedString("shakemap", comment: ""))", target: self, action: #selector(intensityMapTapped(_:)))
|
||||
stackViewReports.addArrangedSubview(buttonMap)
|
||||
buttonMap.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
|
||||
buttonMap.leadingAnchor.constraint(equalTo: stackViewReports.leadingAnchor).isActive = true
|
||||
buttonMap.trailingAnchor.constraint(equalTo: stackViewReports.trailingAnchor).isActive = true
|
||||
}
|
||||
|
||||
containerView.addSubview(stackViewReports)
|
||||
stackViewReports.topAnchor.constraint(equalTo: separator.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
previousView = stackViewReports
|
||||
}
|
||||
|
||||
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
gradientView.eqn_applyRoundedCorners()
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
guard let seismic = seismic else { return }
|
||||
|
||||
let viewModel = SeismicNetworkMinimalViewModel(seismic: seismic)
|
||||
|
||||
gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
|
||||
|
||||
placeLabel.text = viewModel.place
|
||||
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
|
||||
magnitudeLabel.textColor = viewModel.colors.textColor
|
||||
magnitudeLabel.text = viewModel.magnitude
|
||||
timeLabel.text = "🕗 \(viewModel.time)"
|
||||
distanceLabel.text = "📐 \(viewModel.distance)"
|
||||
|
||||
if !viewModel.smartphones.isEmpty {
|
||||
smartphonesLabel.text = "🚨 \(viewModel.smartphones)"
|
||||
}
|
||||
if !viewModel.users.isEmpty {
|
||||
alertsLabel.text = "⚠️ \(viewModel.users)"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
/// Configure the cell to display a seismic
|
||||
/// - Parameters:
|
||||
/// - seismic: Seismic to display
|
||||
/// - type: Type of cell
|
||||
/// - informations: Informations to show
|
||||
public func configure(
|
||||
with seismic: EQNSisma,
|
||||
isPushSelected: Bool
|
||||
) {
|
||||
self.seismic = seismic
|
||||
self.isPushSelected = isPushSelected
|
||||
self.informationTypes.removeAll()
|
||||
|
||||
if seismic.preliminary.intValue > 0 {
|
||||
informationTypes.insert(.preliminary)
|
||||
}
|
||||
if seismic.smartphoneNumber.intValue > 0 {
|
||||
informationTypes.insert(.realtimeSmartphones)
|
||||
}
|
||||
if seismic.userNumber.intValue > 0 {
|
||||
informationTypes.insert(.reportUsers)
|
||||
}
|
||||
if seismic.isoCode != "0" {
|
||||
informationTypes.insert(.intensityMap)
|
||||
}
|
||||
|
||||
recreateUI()
|
||||
updateUI()
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func intensityMapTapped(_ sender: Any) {
|
||||
delegate?.seismicNetworkCellDidTapIntensityMapDetail(self)
|
||||
}
|
||||
}
|
||||
+95
-212
@@ -11,32 +11,8 @@ import MapKit
|
||||
import CoreLocation
|
||||
import Shogun
|
||||
|
||||
protocol SeismicNetworkTableViewCellDelegate: AnyObject {
|
||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell)
|
||||
}
|
||||
|
||||
class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
|
||||
static let Identifier = "SeismicNetworkTableViewCell"
|
||||
|
||||
typealias MagnitudeColors = (textColor: UIColor, startColor: UIColor, endColor: UIColor)
|
||||
|
||||
/// Available informations to display inside the cell
|
||||
enum InformationType: Int {
|
||||
case preliminary
|
||||
case time
|
||||
case distance
|
||||
case coordinate
|
||||
case population
|
||||
case realtimeSmartphones
|
||||
case reportUsers
|
||||
case buttons
|
||||
}
|
||||
class SeismicNetworkTableViewCell: SeismicNetworkBaseTableViewCell {
|
||||
|
||||
/// Available cell type
|
||||
enum DisplayType {
|
||||
@@ -45,48 +21,15 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
/// Cell with map visible
|
||||
case mapExpanded
|
||||
}
|
||||
|
||||
/// Delegate
|
||||
weak var delegate: SeismicNetworkTableViewCellDelegate?
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
private static let DefaultVerticalSpacing: CGFloat = 6.0
|
||||
|
||||
/// Seismic to show
|
||||
private var seismic: EQNSisma?
|
||||
private(set) var displayType = DisplayType.normal
|
||||
private var informationTypes = [InformationType]()
|
||||
|
||||
private var colors: MagnitudeColors?
|
||||
private var isPushSelected = false
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
private lazy var containerView: UIView = {
|
||||
let view = UIView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
||||
view.layer.masksToBounds = false
|
||||
|
||||
// add shadow
|
||||
view.layer.shadowColor = UIColor.black.cgColor
|
||||
view.layer.shadowOpacity = 0.5
|
||||
view.layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
view.layer.shadowRadius = 2
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var 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 = .scaleAspectFill
|
||||
return view
|
||||
}()
|
||||
|
||||
|
||||
private lazy var placeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
@@ -95,6 +38,14 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var shareButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.setImage(UIImage(named: "share_icon"), for: .normal)
|
||||
button.addTarget(self, action: #selector(shareTapped(_:)), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var networkLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
@@ -194,23 +145,20 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
setupUI()
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
gradientView.eqn_applyRoundedCorners()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
private func setupUI() {
|
||||
selectionStyle = .default
|
||||
backgroundColor = .clear
|
||||
|
||||
// container view
|
||||
contentView.addSubview(containerView)
|
||||
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0).isActive = true
|
||||
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
|
||||
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
|
||||
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true
|
||||
containerView.clipsToBounds = true
|
||||
override func setupUI() {
|
||||
super.setupUI()
|
||||
|
||||
containerView.addSubview(gradientView)
|
||||
gradientView.constraint(to: containerView)
|
||||
|
||||
// this variable is used to keep track of the previous view, in order to attach proper constraints
|
||||
var previousView: UIView = containerView
|
||||
|
||||
@@ -231,41 +179,27 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
|
||||
previousView = preliminaryLabel
|
||||
}
|
||||
|
||||
// title (bell icon, place label, seismic network and share button)
|
||||
let titleComponentsHeight: CGFloat = 30.0
|
||||
let stackViewTitle = UIStackView()
|
||||
stackViewTitle.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewTitle.axis = .horizontal
|
||||
stackViewTitle.distribution = .fill
|
||||
stackViewTitle.alignment = .center
|
||||
stackViewTitle.spacing = 4
|
||||
|
||||
let shareButton = UIButton(type: .custom)
|
||||
shareButton.setImage(UIImage(named: "share_icon"), for: .normal)
|
||||
shareButton.addTarget(self, action: #selector(shareTapped(_:)), for: .touchUpInside)
|
||||
|
||||
stackViewTitle.addArrangedSubview(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
|
||||
|
||||
containerView.addSubview(placeLabel)
|
||||
containerView.addSubview(shareButton)
|
||||
|
||||
let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor
|
||||
containerView.addSubview(stackViewTitle)
|
||||
stackViewTitle.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
|
||||
stackViewTitle.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
stackViewTitle.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
placeLabel.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
|
||||
placeLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
placeLabel.trailingAnchor.constraint(equalTo: shareButton.leadingAnchor, constant: .cardPadding.negative).isActive = true
|
||||
|
||||
shareButton.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
shareButton.centerYAnchor.constraint(equalTo: placeLabel.centerYAnchor).isActive = true
|
||||
shareButton.heightAnchor.constraint(equalToConstant: 24.0).isActive = true
|
||||
shareButton.heightAnchor.constraint(equalTo: shareButton.widthAnchor, multiplier: 1.0).isActive = true
|
||||
|
||||
let separator1 = addSeparator(constraintTo: stackViewTitle.bottomAnchor)
|
||||
let separator1 = addSeparator(constraintTo: placeLabel.bottomAnchor)
|
||||
let informationsLeadingAnchor = separator1.leadingAnchor
|
||||
let informationsTrailingAnchor = separator1.trailingAnchor
|
||||
|
||||
// magnitude information
|
||||
containerView.addSubview(magnitudeLabel)
|
||||
magnitudeLabel.topAnchor.constraint(equalTo: separator1.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
magnitudeLabel.topAnchor.constraint(equalTo: separator1.bottomAnchor, constant: Self.VerticalSpacingSmall).isActive = true
|
||||
magnitudeLabel.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
|
||||
|
||||
if !informationTypes.contains(.preliminary) {
|
||||
@@ -295,20 +229,27 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
}
|
||||
|
||||
containerView.addSubview(stackViewInformations)
|
||||
stackViewInformations.topAnchor.constraint(equalTo: magnitudeLabel.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
stackViewInformations.topAnchor.constraint(equalTo: magnitudeLabel.bottomAnchor, constant: Self.VerticalSpacingSmall).isActive = true
|
||||
stackViewInformations.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
|
||||
stackViewInformations.trailingAnchor.constraint(equalTo: informationsTrailingAnchor, constant: -14).isActive = true
|
||||
|
||||
previousView = stackViewInformations
|
||||
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) {
|
||||
let separator2 = addSeparator(constraintTo: stackViewInformations.bottomAnchor)
|
||||
|
||||
// network
|
||||
containerView.addSubview(networkLabel)
|
||||
networkLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingSmall).isActive = true
|
||||
networkLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
networkLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
previousView = networkLabel
|
||||
|
||||
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) || informationTypes.contains(.intensityMap) {
|
||||
let separator2 = addSeparator(constraintTo: previousView.bottomAnchor, constanst: Self.VerticalSpacingSmall)
|
||||
|
||||
let stackViewReports = UIStackView()
|
||||
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewReports.axis = .vertical
|
||||
stackViewReports.distribution = .equalSpacing
|
||||
stackViewReports.alignment = .center
|
||||
stackViewReports.spacing = Self.DefaultVerticalSpacing
|
||||
stackViewReports.spacing = Self.VerticalSpacingDefault
|
||||
|
||||
if informationTypes.contains(.realtimeSmartphones) {
|
||||
stackViewReports.addArrangedSubview(smartphonesLabel)
|
||||
@@ -316,30 +257,32 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
if informationTypes.contains(.reportUsers) {
|
||||
stackViewReports.addArrangedSubview(alertsLabel)
|
||||
}
|
||||
if informationTypes.contains(.intensityMap) {
|
||||
let buttonMap = EQNRoundedButton.make(title: "🎯 \(NSLocalizedString("shakemap", comment: ""))", target: self, action: #selector(intensityMapTapped(_:)))
|
||||
stackViewReports.addArrangedSubview(buttonMap)
|
||||
buttonMap.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
|
||||
buttonMap.leadingAnchor.constraint(equalTo: stackViewReports.leadingAnchor).isActive = true
|
||||
buttonMap.trailingAnchor.constraint(equalTo: stackViewReports.trailingAnchor).isActive = true
|
||||
}
|
||||
|
||||
containerView.addSubview(stackViewReports)
|
||||
stackViewReports.topAnchor.constraint(equalTo: separator2.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor, constant: 20.0).isActive = true
|
||||
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor, constant: -20.0).isActive = true
|
||||
stackViewReports.topAnchor.constraint(equalTo: separator2.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
let separator3 = addSeparator(constraintTo: stackViewReports.bottomAnchor)
|
||||
previousView = separator3
|
||||
previousView = stackViewReports
|
||||
}
|
||||
|
||||
// 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) {
|
||||
let separator3 = addSeparator(constraintTo: previousView.bottomAnchor)
|
||||
previousView = separator3
|
||||
|
||||
// buttons
|
||||
let stackViewButtons = UIStackView()
|
||||
stackViewButtons.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewButtons.axis = .horizontal
|
||||
stackViewButtons.distribution = .fillEqually
|
||||
stackViewButtons.spacing = 4
|
||||
stackViewButtons.spacing = 8
|
||||
|
||||
let buttonMap = EQNRoundedButton.make(title: "🗺", target: self, action: #selector(mapTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonMap)
|
||||
@@ -349,8 +292,8 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
stackViewButtons.addArrangedSubview(buttonSettings)
|
||||
|
||||
containerView.addSubview(stackViewButtons)
|
||||
stackViewButtons.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
||||
stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
stackViewButtons.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
|
||||
stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||
stackViewButtons.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
stackViewButtons.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
@@ -360,7 +303,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
if displayType == .mapExpanded {
|
||||
containerView.addSubview(mapView)
|
||||
mapView.heightAnchor.constraint(equalToConstant: 140.0).isActive = true
|
||||
mapView.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
mapView.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||
mapView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
mapView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
@@ -371,7 +314,8 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
let buttonClose = EQNRoundedButton.make(title: NSLocalizedString("official_close", comment: "").uppercased(), target: self, action: #selector(closeTapped(_:)))
|
||||
|
||||
containerView.addSubview(buttonClose)
|
||||
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
buttonClose.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
|
||||
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true
|
||||
buttonClose.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
buttonClose.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
buttonClose.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
@@ -379,12 +323,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
else {
|
||||
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
private func recreateUI() {
|
||||
// remove all subviews and recreate the required components
|
||||
containerView.subviews.forEach({ $0.removeFromSuperview() })
|
||||
setupUI()
|
||||
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
gradientView.eqn_applyRoundedCorners()
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
@@ -392,16 +333,13 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
|
||||
let viewModel = SeismicNetworkViewModel(seismic: seismic)
|
||||
|
||||
if let colors = colors {
|
||||
gradientView.image = .gradient(from: colors.startColor, to: colors.endColor, with: .init(origin: .zero, size: .init(width: 400, height: 300)))
|
||||
} else {
|
||||
gradientView.image = nil
|
||||
}
|
||||
|
||||
gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
|
||||
|
||||
// update seismic data
|
||||
placeLabel.text = viewModel.place
|
||||
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
|
||||
networkLabel.text = String(format: NSLocalizedString("official_provider", comment: ""), viewModel.network)
|
||||
magnitudeLabel.textColor = colors?.textColor
|
||||
magnitudeLabel.textColor = viewModel.colors.textColor
|
||||
magnitudeLabel.text = viewModel.magnitude
|
||||
depthLabel.text = viewModel.depth
|
||||
timeLabel.text = "🕗 \(viewModel.time)"
|
||||
@@ -420,7 +358,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
alertsLabel.text = "⚠️ \(viewModel.users)"
|
||||
}
|
||||
|
||||
|
||||
if displayType == .mapExpanded {
|
||||
// zoom based on population involved
|
||||
let longitudeSpan = mapSpanLongitude(population: seismic.population100km)
|
||||
@@ -447,11 +384,16 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
/// - seismic: Seismic to display
|
||||
/// - type: Type of cell
|
||||
/// - informations: Informations to show
|
||||
public func configure(with seismic: EQNSisma, type: DisplayType, informations: [InformationType]) {
|
||||
public func configure(
|
||||
with seismic: EQNSisma,
|
||||
type: DisplayType,
|
||||
informations: [InformationType],
|
||||
isPushSelected: Bool
|
||||
) {
|
||||
self.seismic = seismic
|
||||
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
|
||||
self.displayType = type
|
||||
self.informationTypes = informations
|
||||
self.isPushSelected = isPushSelected
|
||||
|
||||
if !informations.contains(.time) {
|
||||
self.informationTypes += [.time]
|
||||
@@ -466,6 +408,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
if seismic.userNumber.intValue > 0 && !informations.contains(.reportUsers) {
|
||||
self.informationTypes += [.reportUsers]
|
||||
}
|
||||
if seismic.isoCode == "0" && informations.contains(.intensityMap) {
|
||||
self.informationTypes.removeAll { $0 == .intensityMap }
|
||||
}
|
||||
|
||||
recreateUI()
|
||||
updateUI()
|
||||
@@ -473,48 +418,37 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func shareTapped(_ sender: UIButton) {
|
||||
@objc private func shareTapped(_ sender: UIButton) {
|
||||
delegate?.seismicNetworkCellDidTapShare(self)
|
||||
}
|
||||
|
||||
@objc func mapTapped(_ sender: UIButton) {
|
||||
@objc private func mapTapped(_ sender: UIButton) {
|
||||
if displayType != .mapExpanded {
|
||||
delegate?.seismicNetworkCellDidTapMap(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func calendarTapped(_ sender: UIButton) {
|
||||
@objc private func calendarTapped(_ sender: UIButton) {
|
||||
delegate?.seismicNetworkCellDidTapCalendar(self)
|
||||
}
|
||||
|
||||
@objc func settingsTapped(_ sender: UIButton) {
|
||||
@objc private func settingsTapped(_ sender: UIButton) {
|
||||
delegate?.seismicNetworkCellDidTapSettings(self)
|
||||
}
|
||||
|
||||
@objc func closeTapped(_ sender: UIButton) {
|
||||
@objc private func closeTapped(_ sender: UIButton) {
|
||||
delegate?.seismicNetworkCellDidTapClose(self)
|
||||
}
|
||||
|
||||
@objc func mapDetailTapped(_ sender: Any) {
|
||||
@objc private func mapDetailTapped(_ sender: Any) {
|
||||
delegate?.seismicNetworkCellDidTapMapDetail(self)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
@discardableResult
|
||||
private func addSeparator(constraintTo: NSLayoutYAxisAnchor, constanst: CGFloat = 8.0) -> UIView {
|
||||
let separator = UIView()
|
||||
separator.translatesAutoresizingMaskIntoConstraints = false
|
||||
separator.backgroundColor = .lightGray
|
||||
containerView.addSubview(separator)
|
||||
|
||||
separator.topAnchor.constraint(equalTo: constraintTo, constant: constanst).isActive = true
|
||||
separator.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
separator.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
|
||||
|
||||
return separator
|
||||
@objc private func intensityMapTapped(_ sender: Any) {
|
||||
delegate?.seismicNetworkCellDidTapIntensityMapDetail(self)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Determines the zoom for the map, based on the involved population
|
||||
private func mapSpanLongitude(population: Double) -> CLLocationDegrees {
|
||||
@@ -528,55 +462,4 @@ class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
}
|
||||
return zoom
|
||||
}
|
||||
|
||||
/// Calculate colors to use for text and background of the cell
|
||||
private func calculateColors(for magnitude: Double) -> MagnitudeColors {
|
||||
var textColor = UIColor.black
|
||||
|
||||
var r = 0, g = 0, b = 0
|
||||
if (magnitude < 2.0) {
|
||||
let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0)
|
||||
r = Int(round(200.0 + (255.0 - 200.0) * fraction))
|
||||
g = Int(round(226.0 + (255.0 - 226.0) * fraction))
|
||||
b = Int(round(196.0 + (255.0 - 196.0) * fraction))
|
||||
textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 160.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 2.0 && magnitude < 3.5) {
|
||||
let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2)
|
||||
r = Int(round(136.0 + (200.0 - 136.0) * fraction))
|
||||
g = Int(round(175.0 + (226.0 - 175.0) * fraction))
|
||||
b = Int(round(131.0 + (196.0 - 131.0) * fraction))
|
||||
textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 3.5 && magnitude < 4.5) {
|
||||
let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5)
|
||||
r = 252
|
||||
g = Int(round(233.0 + (253.0 - 233.0) * fraction))
|
||||
b = Int(round(179.0 + (209.0 - 179.0) * fraction))
|
||||
textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 4.5 && magnitude < 5.5) {
|
||||
let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5)
|
||||
r = 252
|
||||
g = Int(round(159.0 + (197.0 - 159.0) * fraction))
|
||||
b = Int(round(161.0 + (197.0 - 161.0) * fraction))
|
||||
textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 5.5) {
|
||||
let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5)
|
||||
r = Int(round(190.0 + (254.0 - 190.0) * fraction))
|
||||
g = Int(round(124.0 + (219.0 - 124.0) * fraction))
|
||||
b = 255
|
||||
textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
|
||||
let r2 = min(r + 30, 255)
|
||||
let g2 = min(g + 30, 255)
|
||||
let b2 = min(b + 30, 255)
|
||||
|
||||
let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0)
|
||||
let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0)
|
||||
|
||||
return (textColor: textColor, startColor: startColor, endColor: endColor)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -21,6 +21,7 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
||||
case magnitudoMinima
|
||||
case sismiRilevanti
|
||||
case sismiTutti
|
||||
case sismiPercepiti
|
||||
}
|
||||
|
||||
weak var delegate: SeismicFiltersViewControllerDelegate?
|
||||
@@ -40,10 +41,11 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
||||
SettingItem(type: .slider, title: NSLocalizedString("filter_minimum_magnitude", comment: "")),
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_show_relevant", comment: "")),
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_show_all", comment: "")),
|
||||
SettingItem(type: .enable, title: NSLocalizedString("filter_show_felt", comment: ""))
|
||||
]
|
||||
|
||||
private let initialFilterType = EQNSeismic.shared.filterOption
|
||||
private var currentFilterType = EQNSeismic.FilterType.inRadius
|
||||
private(set) var currentFilterType = EQNSeismic.FilterType.inRadius
|
||||
private var currentMaximumDistance = EQNData.DefaultFilterRadius
|
||||
private var currentMinimumMagnitude = EQNData.DefaultFilterMagnitude
|
||||
|
||||
@@ -148,6 +150,12 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeFilterOption(enabled, filter: .worldWide)
|
||||
}
|
||||
case .sismiPercepiti:
|
||||
let isCurrentFilter = currentFilterType == .userFelt
|
||||
cell.toggleSwitch.isOn = isCurrentFilter
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeFilterOption(enabled, filter: .userFelt)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
+4
-6
@@ -29,17 +29,16 @@ class SeismicCardSettingsViewController: UIViewController {
|
||||
@IBOutlet private weak var informationPopulationSwitch: UISwitch!
|
||||
@IBOutlet private weak var closeButton: UIButton!
|
||||
|
||||
private var informations = [SeismicNetworkTableViewCell.InformationType]()
|
||||
private var informations: [SeismicNetworkTableViewCell.InformationType] {
|
||||
get { AppPreferences.shared.seismicNetworksInformations }
|
||||
set { AppPreferences.shared.seismicNetworksInformations = newValue }
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
if let saved = UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) as? [Int] {
|
||||
informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
|
||||
}
|
||||
|
||||
setupUI()
|
||||
updateUI()
|
||||
}
|
||||
@@ -84,7 +83,6 @@ class SeismicCardSettingsViewController: UIViewController {
|
||||
toggle(information: .population)
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
|
||||
updateUI()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// SeismicNetworkData.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 31/01/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// SeismicNetworkFilterRecapView.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 17/07/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Shogun
|
||||
|
||||
|
||||
struct SeismicNetworkFilterRecapView: View {
|
||||
class Model: ObservableObject {
|
||||
@Published var filter = EQNSeismic.shared.filterOption
|
||||
@Published var sort = EQNSeismic.shared.sort
|
||||
}
|
||||
|
||||
@ObservedObject private var model: Model
|
||||
private let onSort: (_ sort: EQNSeismic.Sort) -> Void
|
||||
private let onMainFilter: () -> Void
|
||||
private let onMap: () -> Void
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(
|
||||
model: Model,
|
||||
onSort: @escaping (_ sort: EQNSeismic.Sort) -> Void,
|
||||
onMainFilter: @escaping () -> Void,
|
||||
onMap: @escaping () -> Void
|
||||
) {
|
||||
self.model = model
|
||||
self.onSort = onSort
|
||||
self.onMainFilter = onMainFilter
|
||||
self.onMap = onMap
|
||||
}
|
||||
|
||||
// MARK: - View
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
RoundedButton(
|
||||
systemName: sortIcon,
|
||||
tintColor: tintColor,
|
||||
action: {
|
||||
model.sort.advance()
|
||||
onSort(model.sort)
|
||||
}
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
onMainFilter()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 12)
|
||||
|
||||
Text(filterTitle)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.overlay {
|
||||
Capsule().stroke(AppTheme.Colors.gray.color, lineWidth: 1)
|
||||
}
|
||||
}
|
||||
.font(.caption)
|
||||
.tint(tintColor)
|
||||
|
||||
Spacer()
|
||||
|
||||
RoundedButton(
|
||||
systemName: "globe",
|
||||
tintColor: tintColor,
|
||||
action: onMap
|
||||
)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.clear)
|
||||
}
|
||||
|
||||
private var tintColor: Color {
|
||||
Color.blue
|
||||
//AppTheme.Colors.lightBlue.color
|
||||
}
|
||||
|
||||
private var filterTitle: String {
|
||||
switch model.filter {
|
||||
case .inRadius: "filter_area".localized
|
||||
case .positionRelevant: "filter_relevant".localized
|
||||
case .worldWide: "filter_all".localized
|
||||
case .userFelt: "filter_felt".localized
|
||||
}
|
||||
}
|
||||
|
||||
private var sortIcon: String {
|
||||
switch model.sort {
|
||||
case .time: "clock"
|
||||
case .position:
|
||||
if #available(iOS 16, *) {
|
||||
"compass.drawing"
|
||||
} else {
|
||||
"ruler"
|
||||
}
|
||||
case .magnitude: "thermometer"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct RoundedButton: View {
|
||||
let systemName: String
|
||||
let tintColor: Color
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
action()
|
||||
} label: {
|
||||
Image(systemName: systemName)
|
||||
.resizable()
|
||||
.tint(tintColor)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(maxHeight: .infinity)
|
||||
.frame(width: 40.0)
|
||||
.padding(8)
|
||||
.overlay {
|
||||
Capsule().stroke(AppTheme.Colors.gray.color, lineWidth: 1)
|
||||
}
|
||||
.animation(nil, value: systemName) // previene animazioni implicite nel bottone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SeismicNetworkFilterRecapView(
|
||||
model: .init(),
|
||||
onSort: { _ in },
|
||||
onMainFilter: {},
|
||||
onMap: {}
|
||||
)
|
||||
.frame(height: 34.0)
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// SeismicNetworkScrollIndicatorView.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 31/01/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreGraphics
|
||||
|
||||
|
||||
class SeismicNetworkScrollIndicatorView: UIView {
|
||||
|
||||
private static let HighlightColor: UIColor = .red
|
||||
|
||||
var seismics: [SeismicNetworkViewModel] = [] {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
var highlighted: SeismicNetworkViewModel? {
|
||||
didSet {
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private var numberOfRectangles: Int {
|
||||
seismics.count
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard numberOfRectangles > 0 else { return }
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
let rectStandardWidth = rect.width
|
||||
let rectStandardHeight = rect.height / CGFloat(numberOfRectangles)
|
||||
let rectHighlightedMinHeight: CGFloat = 4
|
||||
|
||||
let smallRectangles = rectStandardHeight < 10
|
||||
let highlightIndex = seismics.firstIndex(where: { $0 == highlighted }) ?? 100_000
|
||||
|
||||
|
||||
seismics.enumerated().forEach { index, seismic in
|
||||
// Disegniamo un rettangolo per ogni sisma, quello evidenziato deve avere un contorno rosso.
|
||||
// Ci sono situazioni in cui ci sono molti sismi da mostrare, quindi in quel caso facciamo alcune modifiche:
|
||||
// - usiamo un'altezza minima per il sisma evidenziato
|
||||
// - per il sisma evidenziato, anche il contenuto è rosso (e non solo il bordo)
|
||||
// - negli altri sismi, non mostriamo il bordo
|
||||
|
||||
if highlightIndex == index {
|
||||
// Stiamo disegnando il sisma evidenziato.
|
||||
// Valutiamo se utilizzare l'altezza minima.
|
||||
let rectHeight = smallRectangles ? rectHighlightedMinHeight : rectStandardHeight
|
||||
let yPosition = CGFloat(index) * rectStandardHeight
|
||||
let rectangle = CGRect(x: 0, y: yPosition, width: rectStandardWidth, height: rectHeight)
|
||||
|
||||
let fillColor = smallRectangles ? Self.HighlightColor : seismic.colors.textColor.withAlphaComponent(0.3)
|
||||
context?.setFillColor(fillColor.cgColor)
|
||||
context?.fill(rectangle)
|
||||
|
||||
if !smallRectangles {
|
||||
// disegniamo il bordo solo se i rettangoli non sono piccoli
|
||||
let borderWidth: CGFloat = 2.0
|
||||
context?.setStrokeColor(Self.HighlightColor.cgColor)
|
||||
context?.setLineWidth(borderWidth) // Spessore del bordo
|
||||
context?.stroke(rectangle.insetBy(dx: borderWidth / 2, dy: borderWidth / 2)) // Evita che il bordo venga tagliato
|
||||
}
|
||||
} else {
|
||||
// Stiamo disegnando i sismi non evidenziati, utilizziamo sempre l'altezza predefinita
|
||||
// Dobbiamo eventualmente calcolare un offset aggiuntivo,
|
||||
// perchè il sisma evidenziato ha un'altezza maggiore (se i rettangoli sono piccoli)
|
||||
let rectHeight = rectStandardHeight
|
||||
|
||||
var offset: CGFloat = 0
|
||||
if index > highlightIndex && smallRectangles {
|
||||
// calcoliamo l'offset prima del rettangolo evidenziato
|
||||
let preOffset = CGFloat(highlightIndex - 1) * rectStandardHeight
|
||||
// offset diverso dovuto all'altezza diversa del rettangolo evidenziato
|
||||
let highlightOffset = rectHighlightedMinHeight
|
||||
// calcoliamo l'offset tra il rettangolo evidenziato e quello corrente
|
||||
let postOffset = CGFloat(index - highlightIndex) * rectStandardHeight
|
||||
offset = preOffset + highlightOffset + postOffset
|
||||
} else {
|
||||
// siamo prima del rettangolo evidenziato, non abbiamo calcoli da fare
|
||||
offset = CGFloat(index) * rectHeight
|
||||
}
|
||||
|
||||
let rectangle = CGRect(x: 0, y: offset, width: rectStandardWidth, height: rectHeight)
|
||||
|
||||
let fillColor = seismic.colors.textColor.withAlphaComponent(0.3)
|
||||
context?.setFillColor(fillColor.cgColor)
|
||||
context?.fill(rectangle)
|
||||
|
||||
if !smallRectangles {
|
||||
// altrimenti un bordo grigio
|
||||
let borderWidth: CGFloat = 0.5
|
||||
context?.setStrokeColor(AppTheme.Colors.gray.cgColor)
|
||||
context?.setLineWidth(borderWidth) // Spessore del bordo
|
||||
context?.stroke(rectangle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+129
-21
@@ -8,14 +8,62 @@
|
||||
|
||||
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 {
|
||||
|
||||
private let seismic: EQNSisma
|
||||
let place: String
|
||||
let network: String
|
||||
let isPreliminary: Bool
|
||||
let magnitude: String
|
||||
let rawMagnitude: Double
|
||||
let depth: String
|
||||
let time: String
|
||||
let distance: String
|
||||
@@ -23,13 +71,14 @@ struct SeismicNetworkViewModel {
|
||||
let population: String
|
||||
let smartphones: String
|
||||
let users: String
|
||||
let colors: MagnitudeColors
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(seismic: EQNSisma) {
|
||||
self.seismic = seismic
|
||||
self.place = seismic.place
|
||||
self.network = seismic.provider
|
||||
self.rawMagnitude = seismic.magnitude.doubleValue
|
||||
|
||||
let isPreliminary = seismic.preliminary.intValue > 0
|
||||
self.isPreliminary = isPreliminary
|
||||
@@ -58,7 +107,7 @@ struct SeismicNetworkViewModel {
|
||||
let coordinateText = EQNUtility.coordinateString(coordinate: seismic.coordinate.coordinate)
|
||||
self.coordinate = "\(coordinateText)"
|
||||
|
||||
let population = Self.formatPopulation(seismic.population100km)
|
||||
let population = formatPopulation(seismic.population100km)
|
||||
self.population = String(format: NSLocalizedString("share_radius100", comment: ""), population)
|
||||
|
||||
if seismic.smartphoneNumber.intValue > 0 {
|
||||
@@ -71,23 +120,82 @@ struct SeismicNetworkViewModel {
|
||||
} else {
|
||||
self.users = ""
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Format population value (ex. 1.5M, 2.4k)
|
||||
private static func formatPopulation(_ population: Double) -> String {
|
||||
var populationString = ""
|
||||
if population > 999_999 {
|
||||
let roundedPopulation = round(population / 100_000) / 10
|
||||
populationString = "\(roundedPopulation)M"
|
||||
} else if population > 999 {
|
||||
let roundedPopulation = round(population / 100) / 10
|
||||
populationString = "\(roundedPopulation)K"
|
||||
} else {
|
||||
let roundedPopulation = round(population)
|
||||
populationString = "\(roundedPopulation)"
|
||||
}
|
||||
return populationString
|
||||
|
||||
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworkViewModel: Equatable {
|
||||
static func == (lhs: SeismicNetworkViewModel, rhs: SeismicNetworkViewModel) -> Bool {
|
||||
return lhs.seismic == rhs.seismic
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Calculate colors to use for text and background of the cell
|
||||
private func calculateColors(for magnitude: Double) -> MagnitudeColors {
|
||||
var textColor = UIColor.black
|
||||
|
||||
var r = 0, g = 0, b = 0
|
||||
if (magnitude < 2.0) {
|
||||
let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0)
|
||||
r = Int(round(200.0 + (255.0 - 200.0) * fraction))
|
||||
g = Int(round(226.0 + (255.0 - 226.0) * fraction))
|
||||
b = Int(round(196.0 + (255.0 - 196.0) * fraction))
|
||||
textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 160.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 2.0 && magnitude < 3.5) {
|
||||
let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2)
|
||||
r = Int(round(136.0 + (200.0 - 136.0) * fraction))
|
||||
g = Int(round(175.0 + (226.0 - 175.0) * fraction))
|
||||
b = Int(round(131.0 + (196.0 - 131.0) * fraction))
|
||||
textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 3.5 && magnitude < 4.5) {
|
||||
let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5)
|
||||
r = 252
|
||||
g = Int(round(233.0 + (253.0 - 233.0) * fraction))
|
||||
b = Int(round(179.0 + (209.0 - 179.0) * fraction))
|
||||
textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 4.5 && magnitude < 5.5) {
|
||||
let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5)
|
||||
r = 252
|
||||
g = Int(round(159.0 + (197.0 - 159.0) * fraction))
|
||||
b = Int(round(161.0 + (197.0 - 161.0) * fraction))
|
||||
textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 5.5) {
|
||||
let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5)
|
||||
r = Int(round(190.0 + (254.0 - 190.0) * fraction))
|
||||
g = Int(round(124.0 + (219.0 - 124.0) * fraction))
|
||||
b = 255
|
||||
textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
|
||||
let r2 = min(r + 30, 255)
|
||||
let g2 = min(g + 30, 255)
|
||||
let b2 = min(b + 30, 255)
|
||||
|
||||
let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0)
|
||||
let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0)
|
||||
|
||||
return .init(textColor: textColor, startColor: startColor, endColor: endColor)
|
||||
}
|
||||
|
||||
/// Format population value (ex. 1.5M, 2.4k)
|
||||
private func formatPopulation(_ population: Double) -> String {
|
||||
var populationString = ""
|
||||
if population > 999_999 {
|
||||
let roundedPopulation = round(population / 100_000) / 10
|
||||
populationString = "\(roundedPopulation)M"
|
||||
} else if population > 999 {
|
||||
let roundedPopulation = round(population / 100) / 10
|
||||
populationString = "\(roundedPopulation)K"
|
||||
} else {
|
||||
let roundedPopulation = round(population)
|
||||
populationString = "\(roundedPopulation)"
|
||||
}
|
||||
return populationString
|
||||
}
|
||||
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
//
|
||||
// SeismicNetworksIntensityMapViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 27/02/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
|
||||
class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
|
||||
|
||||
private let seismic: EQNSisma
|
||||
private var shakemaps: [EQNShakemap] = []
|
||||
private var pinStyle: MapPinStyle {
|
||||
get { AppPreferences.shared.mapPinStyle }
|
||||
set { AppPreferences.shared.mapPinStyle = newValue }
|
||||
}
|
||||
|
||||
override var isFilterViewVisible: Bool { false }
|
||||
override var isCloseButtonVisible: Bool { false }
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
lazy var descriptionView: UIView = {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = AppTheme.Colors.pureBlue
|
||||
|
||||
let descriptionLabel = UILabel()
|
||||
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
descriptionLabel.numberOfLines = 0
|
||||
descriptionLabel.textColor = .white
|
||||
descriptionLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
descriptionLabel.textAlignment = .center
|
||||
descriptionLabel.text = NSLocalizedString("shakemap_description", comment: "")
|
||||
|
||||
view.addSubview(descriptionLabel)
|
||||
descriptionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 2.0).isActive = true
|
||||
descriptionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -2.0).isActive = true
|
||||
descriptionLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 2.0).isActive = true
|
||||
descriptionLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -2.0).isActive = true
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(
|
||||
seismic: EQNSisma
|
||||
) {
|
||||
self.seismic = seismic
|
||||
super.init()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("Plase use init(seismic:) instead")
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func extraUI() {
|
||||
super.extraUI()
|
||||
|
||||
view.addSubview(descriptionView)
|
||||
descriptionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||
descriptionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
descriptionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
}
|
||||
|
||||
override func configureUI() {
|
||||
super.configureUI()
|
||||
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: .init(handler: { [weak self] _ in
|
||||
self?.dismiss(animated: true)
|
||||
}))
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), primaryAction: .init(handler: { [weak self] _ in
|
||||
self?.shareScreenshot()
|
||||
})),
|
||||
UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), primaryAction: .init(handler: { [weak self] _ in
|
||||
self?.nextPinStyle()
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
override func registerMapAnnotationViews() {
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full))
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light))
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle))
|
||||
}
|
||||
|
||||
override func loadDataSource() {
|
||||
Task {
|
||||
let result = try await APIService.shared.fetchShakemap(isoCode: seismic.isoCode)
|
||||
elaborateShakemaps(result)
|
||||
}
|
||||
}
|
||||
|
||||
override func elaborateMapCenter() {
|
||||
setMapCenter(for: seismic.coordinate, span: MKCoordinateSpan(latitudeDelta: 2, longitudeDelta: 2))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func elaborateShakemaps(_ shakemaps: [EQNShakemap]) {
|
||||
self.shakemaps = shakemaps
|
||||
|
||||
var shakemapPolyline = [MKPolyline]()
|
||||
var shakemapAnnotations: [MKAnnotation] = []
|
||||
for shakemap in shakemaps {
|
||||
// create coordinates for current shakemap
|
||||
let coordinates = zip(shakemap.lat, shakemap.lon).map { lat, lon in
|
||||
CLLocationCoordinate2D(latitude: Double(lat) / 10_000.0, longitude: Double(lon) / 10_000.0)
|
||||
}
|
||||
|
||||
let intensityColors = getColors(for: shakemap.intensity)
|
||||
|
||||
// create line to show on map
|
||||
let polyline = ShakemapPolyline(coordinates: coordinates, count: coordinates.count)
|
||||
polyline.intensity = shakemap.intensity
|
||||
polyline.intensityColor = intensityColors.lineColor
|
||||
shakemapPolyline.append(polyline)
|
||||
|
||||
// create annotation to show on top of the line
|
||||
let middlePoint = coordinates[coordinates.count / 2]
|
||||
let annotation = EQNMapAnnotationShakemap(coordinate: middlePoint, shakemap: shakemap)
|
||||
annotation.intensityColor = intensityColors.lineColor
|
||||
annotation.intensityTextColor = intensityColors.textColor
|
||||
shakemapAnnotations.append(annotation)
|
||||
}
|
||||
|
||||
let seismicAnnotation = EQNMapAnnotationSeismic(seismic: seismic)
|
||||
shakemapAnnotations.append(seismicAnnotation)
|
||||
|
||||
// draw lines
|
||||
mapView.addOverlays(shakemapPolyline)
|
||||
updateMap(with: shakemapAnnotations)
|
||||
}
|
||||
|
||||
private func nextPinStyle() {
|
||||
pinStyle.advance()
|
||||
reloadMap()
|
||||
}
|
||||
|
||||
private func shareScreenshot() {
|
||||
let screenshot = createSnapshot(prepare: {
|
||||
descriptionView.isHidden = true
|
||||
}, restore: {
|
||||
descriptionView.isHidden = false
|
||||
})
|
||||
|
||||
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - MKMapViewDelegate
|
||||
|
||||
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
|
||||
switch annotation {
|
||||
case let shakemapAnnotation as EQNMapAnnotationShakemap:
|
||||
return shakemapAnnotation.toAnnotationView(mapView: mapView, style: .light)
|
||||
case let seismicAnnotation as EQNMapAnnotationSeismic:
|
||||
return seismicAnnotation.toAnnotationView(mapView: mapView, style: pinStyle)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
if let polyline = overlay as? ShakemapPolyline {
|
||||
let renderer = MKPolylineRenderer(polyline: polyline)
|
||||
renderer.strokeColor = polyline.intensityColor
|
||||
renderer.lineWidth = 6.0
|
||||
return renderer
|
||||
}
|
||||
return MKOverlayRenderer()
|
||||
}
|
||||
|
||||
private func getColors(for intensity: Float) -> (textColor: UIColor, lineColor: UIColor) {
|
||||
let shakemapColors: [String] = [
|
||||
"#3E26A8","#3E27AC","#3F28AF","#3F29B2","#402AB4","#402BB7","#412CBA","#412DBD","#422EBF","#422FC2",
|
||||
"#4330C5","#4331C8","#4332CA","#4433CD","#4434D0","#4535D2","#4537D5","#4538D7","#4639D9","#463ADC",
|
||||
"#463BDE","#463DE0","#473EE1","#473FE3","#4741E5","#4742E6","#4744E8","#4745E9","#4746EB","#4848EC",
|
||||
"#4849ED","#484BEE","#484CF0","#484EF1","#484FF2","#4850F3","#4852F4","#4853F5","#4854F6","#4756F7",
|
||||
"#4757F7","#4759F8","#475AF9","#475BFA","#475DFA","#465EFB","#4660FB","#4661FC","#4562FC","#4564FD",
|
||||
"#4465FD","#4367FD","#4368FE","#426AFE","#416BFE","#406DFE","#3F6EFF","#3E70FF","#3C71FF","#3B73FF",
|
||||
"#3974FF","#3876FE","#3677FE","#3579FD","#337AFD","#327CFC","#317DFC","#307FFB","#2F80FA","#2F82FA",
|
||||
"#2E83F9","#2E84F8","#2E86F8","#2E87F7","#2D88F6","#2D8AF5","#2D8BF4","#2D8CF3","#2D8EF2","#2C8FF1",
|
||||
"#2C90F0","#2B91EF","#2A93EE","#2994ED","#2895EC","#2797EB","#2798EA","#2699E9","#269AE8","#259BE8",
|
||||
"#259CE7","#249EE6","#249FE5","#23A0E5","#23A1E4","#22A2E4","#21A3E3","#20A5E3","#1FA6E2","#1EA7E1",
|
||||
"#1DA8E1","#1DA9E0","#1CAADF","#1BABDE","#1AACDD","#19ADDC","#17AEDA","#16AFD9","#14B0D8","#12B1D6",
|
||||
"#10B2D5","#0EB3D4","#0BB3D2","#08B4D1","#06B5CF","#04B6CE","#02B7CC","#01B7CA","#00B8C9","#00B9C7",
|
||||
"#00BAC6","#01BAC4","#02BBC2","#04BBC1","#06BCBF","#09BDBD","#0DBDBC","#10BEBA","#14BEB8","#17BFB6",
|
||||
"#1AC0B5","#1DC0B3","#20C1B1","#23C1AF","#25C2AE","#27C2AC","#29C3AA","#2BC3A8","#2CC4A6","#2EC4A5",
|
||||
"#2FC5A3","#31C5A1","#32C69F","#33C79D","#35C79B","#36C899","#38C896","#39C994","#3BC992","#3DCA90",
|
||||
"#40CA8D","#42CA8B","#45CB89","#48CB86","#4BCB84","#4ECC81","#51CC7F","#54CC7C","#57CC7A","#5ACC77",
|
||||
"#5ECD74","#61CD72","#64CD6F","#67CD6C","#6BCD69","#6ECD66","#72CD64","#76CC61","#79CC5E","#7DCC5B",
|
||||
"#81CC59","#84CC56","#88CB53","#8BCB51","#8FCB4E","#93CA4B","#96CA48","#9AC946","#9DC943","#A1C840",
|
||||
"#A4C83E","#A7C73B","#ABC739","#AEC637","#B2C635","#B5C533","#B8C431","#BBC42F","#BEC32D","#C2C32C",
|
||||
"#C5C22A","#C8C129","#CBC128","#CEC027","#D0BF27","#D3BF27","#D6BE27","#D9BE28","#DBBD28","#DEBC29",
|
||||
"#E1BC2A","#E3BC2B","#E6BB2D","#E8BB2E","#EABA30","#ECBA32","#EFBA35","#F1BA37","#F3BA39","#F5BA3B",
|
||||
"#F7BA3D","#F9BA3E","#FBBB3E","#FCBC3E","#FEBD3D","#FEBE3C","#FEC03B","#FEC13A","#FEC239","#FEC438",
|
||||
"#FEC537","#FEC735","#FEC834","#FECA33","#FDCB32","#FDCD31","#FDCE31","#FCD030","#FBD22F","#FBD32E",
|
||||
"#FAD52E","#F9D62D","#F9D82C","#F8D92B","#F7DB2A","#F7DD2A","#F6DE29","#F6E028","#F5E128","#F5E327",
|
||||
"#F5E526","#F5E626","#F5E825","#F5E924","#F5EB23","#F5EC22","#F5EE21","#F6EF20","#F6F11F","#F6F21E",
|
||||
"#F7F41C","#F7F51B","#F8F71A","#F8F818","#F9F916","#F9FB15"
|
||||
]
|
||||
|
||||
let minIntensity = shakemaps.map { $0.intensity }.min() ?? 0
|
||||
let maxIntensity = shakemaps.map { $0.intensity }.max() ?? 255
|
||||
let indexColor = if minIntensity == maxIntensity {
|
||||
0
|
||||
} else {
|
||||
Int(round((intensity-minIntensity)/(maxIntensity-minIntensity)*255))
|
||||
}
|
||||
|
||||
let lineColor = UIColor(hexString: shakemapColors[indexColor]) ?? .white
|
||||
let textColor: UIColor = indexColor < 65 ? .white : .black
|
||||
|
||||
return (textColor: textColor, lineColor: lineColor)
|
||||
}
|
||||
}
|
||||
|
||||
extension EQNMapAnnotationShakemap {
|
||||
func toAnnotationView(
|
||||
mapView: MKMapView,
|
||||
style: MapPinStyle,
|
||||
isUserSelection: Bool = false
|
||||
) -> MKAnnotationView? {
|
||||
switch style {
|
||||
case .full, .light:
|
||||
let identifier = EQNSeismicAnnotationView.identifier(for: style)
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
|
||||
annotationView.magnitude = intensityString(from: shakemap.intensity)
|
||||
annotationView.magnitudeTextColor = intensityTextColor ?? .black
|
||||
annotationView.magnitudeBackgroundColor = intensityColor
|
||||
annotationView.canShowCallout = true
|
||||
return annotationView
|
||||
case .circle:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func intensityString(from intensity: Float) -> String {
|
||||
let intensityRounded = (intensity * 10).rounded() / 10
|
||||
let intensityFloor = floor(intensityRounded)
|
||||
|
||||
let romanNumerals: [Int: String] = [
|
||||
1: "I", 2: "II", 3: "III", 4: "IV",
|
||||
5: "V", 6: "VI", 7: "VII", 8: "VIII",
|
||||
9: "IX", 10: "X", 11: "XI", 12: "XII"
|
||||
]
|
||||
|
||||
var result = romanNumerals[Int(intensityFloor)] ?? ""
|
||||
|
||||
if intensityRounded != intensityFloor {
|
||||
let reminder = Int(((intensityRounded - intensityFloor) * 10).rounded())
|
||||
result += ".\(reminder)"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class ShakemapPolyline: MKPolyline {
|
||||
var intensity: Float = 0
|
||||
var intensityColor: UIColor = .white
|
||||
}
|
||||
+47
-37
@@ -15,21 +15,11 @@ protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
|
||||
}
|
||||
|
||||
class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
|
||||
private enum PinStyle: CaseIterable {
|
||||
case full
|
||||
case light
|
||||
case circle
|
||||
|
||||
func next() -> Self {
|
||||
let all = Self.allCases
|
||||
let idx = all.firstIndex(of: self)!
|
||||
let next = all.index(after: idx)
|
||||
return all[next == all.endIndex ? all.startIndex : next]
|
||||
}
|
||||
}
|
||||
|
||||
private var pinStyle: PinStyle = .full
|
||||
private var pinStyle: MapPinStyle {
|
||||
get { AppPreferences.shared.mapPinStyle }
|
||||
set { AppPreferences.shared.mapPinStyle = newValue }
|
||||
}
|
||||
private let eqnSeismic = EQNSeismic.shared
|
||||
|
||||
// MARK: - State
|
||||
@@ -73,14 +63,17 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
private let seismic: EQNSisma
|
||||
private let seismic: EQNSisma?
|
||||
private var allSeismics: [EQNSisma]
|
||||
/// Contains circles drawed on the map
|
||||
private var mapCircles = [MKCircle]()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(seismic: EQNSisma, allSeismics: [EQNSisma]) {
|
||||
init(
|
||||
seismic: EQNSisma?,
|
||||
allSeismics: [EQNSisma]
|
||||
) {
|
||||
self.seismic = seismic
|
||||
self.allSeismics = allSeismics
|
||||
super.init()
|
||||
@@ -116,9 +109,9 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
override func registerMapAnnotationViews() {
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierFull)
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierLight)
|
||||
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierCircle)
|
||||
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() {
|
||||
@@ -138,7 +131,11 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -188,7 +185,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
// MARK: - Private
|
||||
|
||||
private func nextPinStyle() {
|
||||
pinStyle = pinStyle.next()
|
||||
pinStyle.advance()
|
||||
reloadMap()
|
||||
}
|
||||
|
||||
@@ -199,6 +196,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
case .inRadius: NSLocalizedString("filter_area", comment: "")
|
||||
case .positionRelevant: NSLocalizedString("filter_relevant", comment: "")
|
||||
case .worldWide: NSLocalizedString("filter_all", comment: "")
|
||||
case .userFelt: NSLocalizedString("filter_felt", comment: "")
|
||||
}
|
||||
seismicsFilterLabel.text = text
|
||||
}
|
||||
@@ -240,22 +238,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
let isUserSelection = annotation.seismic == seismic
|
||||
switch pinStyle {
|
||||
case .full, .light:
|
||||
let identifier = pinStyle == .full ? EQNSeismicAnnotationView.IdentifierFull : EQNSeismicAnnotationView.IdentifierLight
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as! EQNSeismicAnnotationView
|
||||
annotationView.title = annotation.title
|
||||
annotationView.subtitle = annotation.subtitle
|
||||
annotationView.magnitude = String(format: "M%.1f", annotation.seismic.magnitude.doubleValue)
|
||||
annotationView.magnitudeColor = annotation.textColor
|
||||
annotationView.isUserSelection = isUserSelection
|
||||
return annotationView
|
||||
case .circle:
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNSeismicAnnotationView.IdentifierCircle, for: annotation) as! EQNSeismicAnnotationView
|
||||
annotationView.image = annotation.image(height: EQNSeismicAnnotationView.CircleViewHeight,
|
||||
isUserSelection: isUserSelection)
|
||||
return annotationView
|
||||
}
|
||||
return annotation.toAnnotationView(mapView: mapView, style: pinStyle, isUserSelection: isUserSelection)
|
||||
}
|
||||
|
||||
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 {
|
||||
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
|
||||
delegate?.seismicNetworksMapDetailControllerWillUpdateData(self, needsDataUpdate: controller.needsDataUpdate)
|
||||
|
||||
+595
-82
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import EventKitUI
|
||||
import DZNEmptyDataSet
|
||||
import Shogun
|
||||
@@ -15,7 +16,13 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
|
||||
private enum CellType {
|
||||
case seismic(EQNSisma)
|
||||
case advertise(GADNativeAd)
|
||||
case advertise(NativeAd)
|
||||
}
|
||||
|
||||
enum CardDisplayType: Int, CaseIterable {
|
||||
case small
|
||||
case full
|
||||
case minimal
|
||||
}
|
||||
|
||||
private static let SegueIdentifierFilters = "ShowFilters"
|
||||
@@ -23,14 +30,9 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
@IBOutlet private weak var tableView: UITableView?
|
||||
@IBOutlet private weak var expandeCollapseButton: UIBarButtonItem!
|
||||
@IBOutlet private weak var sortButton: UIBarButtonItem!
|
||||
weak var currentMapController: SeismicNetworksMapDetailViewController?
|
||||
|
||||
/// The ad loader
|
||||
private lazy var adLoader: GADAdLoader = {
|
||||
let adLoader = GADAdLoader(
|
||||
private lazy var adLoader: AdLoader = {
|
||||
let adLoader = AdLoader(
|
||||
adUnitID: EQNAdMobAppIdNativeBanner, rootViewController: self,
|
||||
adTypes: [.native], options: nil)
|
||||
adLoader.delegate = self
|
||||
@@ -39,19 +41,113 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
|
||||
/// Cells to display (must be seismics or ad banners)
|
||||
private var rows = [CellType]()
|
||||
/// Type of cards to show
|
||||
private var cardDisplayType: CardDisplayType {
|
||||
get { AppPreferences.shared.seismicNetworksCardStyle }
|
||||
set { AppPreferences.shared.seismicNetworksCardStyle = newValue }
|
||||
}
|
||||
private var seismicViewModels = [SeismicNetworkViewModel]()
|
||||
/// Informations to display on a single cell
|
||||
private var informations = [SeismicNetworkTableViewCell.InformationType]()
|
||||
private var informations: [SeismicNetworkTableViewCell.InformationType] {
|
||||
get { AppPreferences.shared.seismicNetworksInformations }
|
||||
set { AppPreferences.shared.seismicNetworksInformations = newValue }
|
||||
}
|
||||
/// Index path of row with map expanded
|
||||
private var openMapIndexPath: IndexPath?
|
||||
/// Push notification opened by the user
|
||||
private var openedPushNotification: EQNOfficialPushNotification? {
|
||||
didSet {
|
||||
scrollToOpenedSeismic = true
|
||||
}
|
||||
}
|
||||
private var scrollToOpenedSeismic = false
|
||||
/// Current displayed controller with map
|
||||
private weak var currentMapController: SeismicNetworksMapDetailViewController?
|
||||
/// Keep track of the current cell at the center
|
||||
private var currentCenteredIndexPath: IndexPath?
|
||||
|
||||
// MARK: - UI
|
||||
|
||||
@IBOutlet private weak var displayModeButton: UIBarButtonItem!
|
||||
private var tableViewTopConstraint: NSLayoutConstraint?
|
||||
|
||||
private lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .plain)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.showsVerticalScrollIndicator = false
|
||||
return tableView
|
||||
}()
|
||||
|
||||
private lazy var filterChangedView: UIView = {
|
||||
let view = UIView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = AppTheme.Colors.pureBlue
|
||||
|
||||
view.addSubview(filterChangedLabel)
|
||||
filterChangedLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
|
||||
filterChangedLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
|
||||
filterChangedLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
|
||||
filterChangedLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var filterChangedLabel: UILabel = {
|
||||
let label = UILabel(frame: .zero)
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = .white
|
||||
label.font = .preferredFont(forTextStyle: .subheadline)
|
||||
label.numberOfLines = 0
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var scrollIndicatorView: SeismicNetworkScrollIndicatorView = {
|
||||
let view = SeismicNetworkScrollIndicatorView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = .clear
|
||||
view.layer.borderColor = AppTheme.Colors.gray.cgColor
|
||||
return view
|
||||
}()
|
||||
|
||||
// model is needed in order to send data to SwiftUI view
|
||||
private let model = SeismicNetworkFilterRecapView.Model()
|
||||
|
||||
private lazy var filterRecapView: UIView = {
|
||||
let hosting = UIHostingController(
|
||||
rootView: SeismicNetworkFilterRecapView(
|
||||
model: model,
|
||||
onSort: { [weak self] sort in
|
||||
self?.changeSort(to: sort)
|
||||
},
|
||||
onMainFilter: { [weak self] in
|
||||
self?.openFilter()
|
||||
},
|
||||
onMap: { [weak self] in
|
||||
self?.showMapDetail(for: nil)
|
||||
}
|
||||
)
|
||||
)
|
||||
addChild(hosting)
|
||||
let filterRecapView = hosting.view!
|
||||
hosting.view.isOpaque = false
|
||||
hosting.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
hosting.didMove(toParent: self)
|
||||
return hosting.view
|
||||
}()
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
configureUI()
|
||||
checkForLocation()
|
||||
refreshUI()
|
||||
configureFilterView(isVisible: false)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveDownloadCompleteNotification(_:)), name: .EQNDownloadDataDidComplete, object: nil)
|
||||
}
|
||||
@@ -60,41 +156,109 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
loadData(forced: false)
|
||||
|
||||
// check for a push to manage
|
||||
if let notification = EQNOfficialPushNotification.stored() {
|
||||
manageFilter(for: notification)
|
||||
self.openedPushNotification = notification
|
||||
EQNOfficialPushNotification.removeStored()
|
||||
} else {
|
||||
configureFilterView(isVisible: false)
|
||||
self.openedPushNotification = nil
|
||||
}
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupUI() {
|
||||
view.backgroundColor = tableView.backgroundColor
|
||||
view.addSubview(scrollIndicatorView)
|
||||
view.addSubview(tableView)
|
||||
view.addSubview(filterRecapView)
|
||||
|
||||
scrollIndicatorView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true
|
||||
scrollIndicatorView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
scrollIndicatorView.bottomAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true
|
||||
scrollIndicatorView.widthAnchor.constraint(equalToConstant: 10.0).isActive = true
|
||||
|
||||
tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: view.topAnchor)
|
||||
tableViewTopConstraint?.isActive = true
|
||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
tableView.trailingAnchor.constraint(equalTo: scrollIndicatorView.leadingAnchor).isActive = true
|
||||
tableView.bottomAnchor
|
||||
.constraint(
|
||||
equalTo: filterRecapView.topAnchor,
|
||||
constant: -8
|
||||
).isActive = true
|
||||
|
||||
filterRecapView.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
|
||||
filterRecapView.leadingAnchor
|
||||
.constraint(
|
||||
equalTo: view.leadingAnchor,
|
||||
constant: 10.0
|
||||
).isActive = true
|
||||
filterRecapView.trailingAnchor
|
||||
.constraint(
|
||||
equalTo: view.trailingAnchor,
|
||||
constant: -10.0
|
||||
).isActive = true
|
||||
filterRecapView.topAnchor
|
||||
.constraint(equalTo: tableView.bottomAnchor).isActive = true
|
||||
filterRecapView.bottomAnchor
|
||||
.constraint(
|
||||
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
|
||||
constant: -8
|
||||
).isActive = true
|
||||
}
|
||||
|
||||
private func setupFilterView(isVisible: Bool) {
|
||||
if isVisible && filterChangedView.superview == nil {
|
||||
view.addSubview(filterChangedView)
|
||||
tableViewTopConstraint?.isActive = false
|
||||
filterChangedView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
filterChangedView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
filterChangedView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||
tableViewTopConstraint = filterChangedView.bottomAnchor.constraint(equalTo: tableView.topAnchor)
|
||||
tableViewTopConstraint?.isActive = true
|
||||
} else {
|
||||
filterChangedView.removeFromSuperview()
|
||||
tableViewTopConstraint?.isActive = false
|
||||
tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: view.topAnchor)
|
||||
tableViewTopConstraint?.isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
private func configureFilterView(isVisible: Bool) {
|
||||
setupFilterView(isVisible: isVisible)
|
||||
if isVisible {
|
||||
filterChangedLabel.text = NSLocalizedString("official_filter_changed", comment: "")
|
||||
filterChangedView.backgroundColor = AppTheme.Colors.pureBlue
|
||||
} else {
|
||||
filterChangedLabel.text = nil
|
||||
filterChangedView.backgroundColor = .white
|
||||
}
|
||||
}
|
||||
|
||||
private func configureUI() {
|
||||
title = NSLocalizedString("tab_official", comment: "").capitalized
|
||||
|
||||
tableView?.estimatedRowHeight = 300.0
|
||||
tableView?.rowHeight = UITableView.automaticDimension
|
||||
tableView?.registerCell(for: SeismicNetworkTableViewCell.self)
|
||||
tableView?.registerCell(for: SeismicNetworkAdvertiseTableViewCell.self)
|
||||
tableView?.emptyDataSetSource = self
|
||||
|
||||
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)
|
||||
}
|
||||
])
|
||||
tableView.estimatedRowHeight = 300.0
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.registerCell(for: SeismicNetworkTableViewCell.self)
|
||||
tableView.registerCell(for: SeismicNetworkMinimalTableViewCell.self)
|
||||
tableView.registerCell(for: SeismicNetworkAdvertiseTableViewCell.self)
|
||||
tableView.emptyDataSetSource = self
|
||||
tableView.separatorStyle = .none
|
||||
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
|
||||
}
|
||||
|
||||
private func checkForLocation() {
|
||||
// check if a valid location is available,
|
||||
// otherwise change the filter settings
|
||||
if EQNUser.default().lastPosition == nil {
|
||||
EQNSeismic.shared.filterOption = .worldWide
|
||||
EQNSeismic.shared.saveFilters()
|
||||
if !isLocationAvailable() {
|
||||
updateFilter(type: .worldWide)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +277,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
}
|
||||
}
|
||||
|
||||
private func showMapDetail(for seismic: EQNSisma) {
|
||||
private func showMapDetail(for seismic: EQNSisma?) {
|
||||
let seismics = getSeismics()
|
||||
let controller = SeismicNetworksMapDetailViewController(seismic: seismic, allSeismics: seismics)
|
||||
controller.delegate = self
|
||||
@@ -123,6 +287,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
self.currentMapController = controller
|
||||
}
|
||||
|
||||
private func showIntensityMap(for seismic: EQNSisma) {
|
||||
let controller = SeismicNetworksIntensityMapViewController(seismic: seismic)
|
||||
let navController = UINavigationController(rootViewController: controller)
|
||||
present(navController, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func didReceiveDownloadCompleteNotification(_ sender: Notification) {
|
||||
@@ -138,21 +308,26 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
private func refreshUI() {
|
||||
elaborateData()
|
||||
|
||||
if let saved = UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) as? [Int] {
|
||||
informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
|
||||
switch cardDisplayType {
|
||||
case .small:
|
||||
displayModeButton.image = UIImage(systemName: "1.square")
|
||||
case .full:
|
||||
displayModeButton.image = UIImage(systemName: "2.square")
|
||||
case .minimal:
|
||||
displayModeButton.image = UIImage(systemName: "3.square")
|
||||
}
|
||||
|
||||
if informations.contains(.buttons) {
|
||||
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-collapse")
|
||||
} else {
|
||||
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-expand")
|
||||
}
|
||||
tableView.reloadData()
|
||||
updateCenterCellIndexPath()
|
||||
|
||||
tableView?.reloadData()
|
||||
if scrollToOpenedSeismic, let index = getSeismics().firstIndex(where: { isSeismicToHighlight(seismic: $0) }) {
|
||||
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
|
||||
scrollToOpenedSeismic = false
|
||||
}
|
||||
}
|
||||
|
||||
private func loadAd() {
|
||||
adLoader.load(GADRequest())
|
||||
adLoader.load(Request())
|
||||
}
|
||||
|
||||
private func loadData(forced: Bool) {
|
||||
@@ -164,7 +339,8 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
let allSeismics = EQNManager.manager().retiSismiche
|
||||
let filteredSeismics = EQNSeismic.shared.filterSeismicList(allSeismics ?? [])
|
||||
rows = filteredSeismics.map { .seismic($0) }
|
||||
|
||||
seismicViewModels = filteredSeismics.map(SeismicNetworkViewModel.init)
|
||||
|
||||
#if ADS_ENABLED
|
||||
// if is not a pro user, show an advertise
|
||||
if !EQNPurchaseUtility.isProVersionEnabled() {
|
||||
@@ -176,6 +352,9 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
if let mapController = currentMapController {
|
||||
mapController.updateSeismics(filteredSeismics)
|
||||
}
|
||||
|
||||
scrollIndicatorView.seismics = seismicViewModels
|
||||
currentCenteredIndexPath = nil
|
||||
}
|
||||
|
||||
private func getSeismics() -> [EQNSisma] {
|
||||
@@ -189,31 +368,342 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
}
|
||||
|
||||
private func changeSort(to sort: EQNSeismic.Sort) {
|
||||
EQNSeismic.shared.sort = sort
|
||||
EQNSeismic.shared.saveFilters()
|
||||
|
||||
setupSortMenu()
|
||||
updateFilter(sort: sort)
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
private func manageFilter(
|
||||
for notification: EQNOfficialPushNotification
|
||||
) {
|
||||
//gestisco i filtri solo se la posizione dell'utente è nota
|
||||
guard let userPosition = EQNUser.default().lastPosition else {
|
||||
return
|
||||
}
|
||||
|
||||
var filter_type = EQNSeismic.shared.filterOption
|
||||
var filter_radius = Double(EQNSeismic.shared.maximumDistance) ?? 0
|
||||
var filter_min_magnitude = Double(EQNSeismic.shared.minimumMagnitude) ?? 0
|
||||
|
||||
var filter_changed = false
|
||||
|
||||
//recupero i dati del sisma notificato
|
||||
let notification_magnitude = notification.magnitude
|
||||
let notification_latitude = notification.coordinate.coordinate.latitude
|
||||
let notification_longitude = notification.coordinate.coordinate.longitude
|
||||
|
||||
//distanza tra smartphone utente e sisma notificato
|
||||
let locationNotification = CLLocation(latitude: notification_latitude, longitude: notification_longitude)
|
||||
let distance = userPosition.distance(from: locationNotification) / 1_000
|
||||
|
||||
//verifico se il sisma è significativo in base alla definizione di significativo
|
||||
var is_significant = true
|
||||
if notification_magnitude < 7.0 && distance > 2000 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 6.5 && distance > 1600 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 6.0 && distance > 1300 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 5.5 && distance > 1000 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 5.0 && distance > 700 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 4.5 && distance > 500 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 4.0 && distance > 350 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 3.5 && distance > 200 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 3.0 && distance > 125 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 2.5 && distance > 70 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 2.0 && distance > 35 {
|
||||
is_significant = false
|
||||
} else if notification_magnitude < 1.5 && distance > 20 {
|
||||
is_significant = false
|
||||
}
|
||||
|
||||
//verifico se devo modificare il filtro scelto dall'utente
|
||||
if filter_type == .inRadius { //filter_type=0 è il filtro basato su raggio e magnitudo
|
||||
if distance > 2000 && is_significant {
|
||||
filter_type = .positionRelevant //passo al filtro che mostra i sismi significativi (perché il raggio massimo del filtro basato sul raggio è 2000)
|
||||
updateFilter(type: filter_type)
|
||||
filter_changed = true
|
||||
}
|
||||
else if distance > 2000 && notification_magnitude >= 2.0 {
|
||||
filter_type = .worldWide //passo al filtro che mostra tutti i sismi nel mondo
|
||||
updateFilter(type: filter_type)
|
||||
filter_changed = true
|
||||
}
|
||||
else {
|
||||
//verifico se devo cambiare il raggio del filtro
|
||||
if distance > filter_radius {
|
||||
if distance > 1500 {
|
||||
filter_radius = 2000
|
||||
} else if distance > 1000 {
|
||||
filter_radius = 1500
|
||||
} else if distance > 750 {
|
||||
filter_radius = 1000
|
||||
} else if distance > 500 {
|
||||
filter_radius = 750
|
||||
} else if distance > 250 {
|
||||
filter_radius = 500
|
||||
} else if distance > 100 {
|
||||
filter_radius = 250
|
||||
}
|
||||
updateFilter(radius: filter_radius)
|
||||
|
||||
filter_changed = true
|
||||
}
|
||||
//verifico se devo cambiare la mgnitudo del filtro
|
||||
if notification_magnitude < filter_min_magnitude {
|
||||
if notification_magnitude < 1.0 {
|
||||
filter_min_magnitude = 0.0
|
||||
} else if notification_magnitude < 2.0 {
|
||||
filter_min_magnitude = 1.0
|
||||
} else if notification_magnitude < 3.0 {
|
||||
filter_min_magnitude = 2.0
|
||||
} else if notification_magnitude < 4.0 {
|
||||
filter_min_magnitude = 3.0
|
||||
} else if notification_magnitude < 5.0 {
|
||||
filter_min_magnitude = 4.0
|
||||
} else if notification_magnitude < 6.0 {
|
||||
filter_min_magnitude = 5.0
|
||||
}
|
||||
|
||||
filter_changed = true
|
||||
updateFilter(magnitude: filter_min_magnitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filter_type == .positionRelevant && !is_significant && distance <= 2000 {
|
||||
filter_type = .inRadius //passo a filtro basato su raggio e magnitudo
|
||||
updateFilter(type: filter_type)
|
||||
|
||||
if distance > filter_radius {
|
||||
if distance>1500 {
|
||||
filter_radius = 2000
|
||||
}
|
||||
else if distance > 1000 {
|
||||
filter_radius = 1500
|
||||
}
|
||||
else if distance > 750 {
|
||||
filter_radius = 1000
|
||||
}
|
||||
else if distance > 500 {
|
||||
filter_radius = 750
|
||||
}
|
||||
else if distance > 250 {
|
||||
filter_radius = 500
|
||||
}
|
||||
else if distance > 100 {
|
||||
filter_radius = 250
|
||||
}
|
||||
updateFilter(radius: filter_radius)
|
||||
}
|
||||
if notification_magnitude < filter_min_magnitude {
|
||||
if notification_magnitude < 1.0 {
|
||||
filter_min_magnitude = 0.0
|
||||
}
|
||||
else if notification_magnitude < 2.0 {
|
||||
filter_min_magnitude = 1.0
|
||||
}
|
||||
else if notification_magnitude < 3.0 {
|
||||
filter_min_magnitude = 2.0
|
||||
}
|
||||
else if notification_magnitude < 4.0 {
|
||||
filter_min_magnitude = 3.0
|
||||
}
|
||||
else if notification_magnitude < 5.0 {
|
||||
filter_min_magnitude = 4.0
|
||||
}
|
||||
else if notification_magnitude < 6.0 {
|
||||
filter_min_magnitude = 5.0
|
||||
}
|
||||
updateFilter(magnitude: filter_min_magnitude)
|
||||
}
|
||||
|
||||
filter_changed = true
|
||||
}
|
||||
|
||||
if filter_type == .positionRelevant && !is_significant && distance > 2000 && notification_magnitude >= 2.0 {
|
||||
filter_type = .worldWide //passo a filtro che mostra tutti i sismi nel mondo
|
||||
updateFilter(type: filter_type)
|
||||
filter_changed = true
|
||||
}
|
||||
|
||||
if filter_type == .worldWide && notification_magnitude < 2.0 && is_significant {
|
||||
filter_type = .positionRelevant //passo a filtro sismi significativi
|
||||
updateFilter(type: filter_type)
|
||||
filter_changed = true
|
||||
}
|
||||
|
||||
if filter_type == .worldWide && notification_magnitude < 2.0 && distance <= 2000 && !is_significant {
|
||||
filter_type = .inRadius
|
||||
updateFilter(type: filter_type)
|
||||
|
||||
if distance > filter_radius {
|
||||
if distance > 1500 {
|
||||
filter_radius = 2000
|
||||
}
|
||||
else if distance > 1000 {
|
||||
filter_radius = 1500
|
||||
}
|
||||
else if distance > 750 {
|
||||
filter_radius = 1000
|
||||
}
|
||||
else if distance > 500 {
|
||||
filter_radius = 750
|
||||
}
|
||||
else if distance > 250 {
|
||||
filter_radius = 500
|
||||
}
|
||||
else if distance > 100 {
|
||||
filter_radius = 250
|
||||
}
|
||||
updateFilter(radius: filter_radius)
|
||||
}
|
||||
if notification_magnitude < filter_min_magnitude {
|
||||
if notification_magnitude < 1.0 {
|
||||
filter_min_magnitude = 0.0
|
||||
}
|
||||
else if notification_magnitude < 2.0 {
|
||||
filter_min_magnitude = 1.0
|
||||
}
|
||||
else if notification_magnitude < 3.0 {
|
||||
filter_min_magnitude = 2.0
|
||||
}
|
||||
else if notification_magnitude < 4.0 {
|
||||
filter_min_magnitude = 3.0
|
||||
}
|
||||
else if notification_magnitude < 5.0 {
|
||||
filter_min_magnitude = 4.0
|
||||
}
|
||||
else if notification_magnitude < 6.0 {
|
||||
filter_min_magnitude = 5.0
|
||||
}
|
||||
updateFilter(magnitude: filter_min_magnitude)
|
||||
}
|
||||
|
||||
filter_changed = true
|
||||
}
|
||||
|
||||
//mostro all'utente un messaggio per avvisarlo che i filtri sono stati modificati
|
||||
configureFilterView(isVisible: filter_changed)
|
||||
if filter_changed {
|
||||
loadData(forced: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateFilter(
|
||||
type: EQNSeismic.FilterType? = nil,
|
||||
sort: EQNSeismic.Sort? = nil,
|
||||
radius: Double? = nil,
|
||||
magnitude: Double? = nil
|
||||
) {
|
||||
if let type {
|
||||
let previous = EQNSeismic.shared.filterOption
|
||||
if previous == .userFelt && previous != type {
|
||||
loadData(forced: true)
|
||||
}
|
||||
|
||||
EQNSeismic.shared.filterOption = type
|
||||
model.filter = type
|
||||
}
|
||||
if let sort {
|
||||
EQNSeismic.shared.sort = sort
|
||||
model.sort = sort
|
||||
}
|
||||
if let radius {
|
||||
EQNSeismic.shared.maximumDistance = String(format: "%.0f", radius)
|
||||
}
|
||||
if let magnitude {
|
||||
EQNSeismic.shared.minimumMagnitude = String(format: "%.1f", magnitude)
|
||||
}
|
||||
EQNSeismic.shared.saveFilters()
|
||||
}
|
||||
|
||||
private func isLocationAvailable() -> Bool {
|
||||
EQNUser.default().lastPosition != nil
|
||||
}
|
||||
|
||||
private func isSeismicToHighlight(seismic: EQNSisma) -> Bool {
|
||||
guard let notification = openedPushNotification else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let seismicDate = seismic.date, let notificationDate = notification.date else { return false }
|
||||
|
||||
let deltaTime = abs(seismicDate.timeIntervalSince(notificationDate))
|
||||
let magnitudeRatio = seismic.magnitude.doubleValue / notification.magnitude
|
||||
let latitudeDiff = abs(seismic.coordinate.coordinate.latitude - notification.coordinate.coordinate.latitude)
|
||||
let longitudeDiff = abs(seismic.coordinate.coordinate.longitude - notification.coordinate.coordinate.longitude)
|
||||
if deltaTime <= 120 && magnitudeRatio > 0.8 && magnitudeRatio < 1.2 && latitudeDiff < 1 && longitudeDiff < 1 { // secondi?
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func getCenterCellIndexPath() -> IndexPath? {
|
||||
let centerPoint = CGPoint(x: tableView.bounds.midX, y: tableView.bounds.midY)
|
||||
if let indexPath = tableView.indexPathForRow(at: centerPoint) {
|
||||
return indexPath
|
||||
}
|
||||
|
||||
// Se il metodo diretto fallisce, cerchiamo la cella più vicina
|
||||
if let visibleIndexPaths = tableView.indexPathsForVisibleRows {
|
||||
return visibleIndexPaths.min(by: { (indexPath1, indexPath2) -> Bool in
|
||||
let rect1 = tableView.rectForRow(at: indexPath1)
|
||||
let rect2 = tableView.rectForRow(at: indexPath2)
|
||||
let distance1 = abs(rect1.midY - centerPoint.y)
|
||||
let distance2 = abs(rect2.midY - centerPoint.y)
|
||||
return distance1 < distance2
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func updateCenterCellIndexPath() {
|
||||
if let centerIndexPath = getCenterCellIndexPath(), centerIndexPath != currentCenteredIndexPath {
|
||||
currentCenteredIndexPath = centerIndexPath
|
||||
|
||||
if rows.count > centerIndexPath.row {
|
||||
let row = rows[centerIndexPath.row]
|
||||
if case .seismic = row, seismicViewModels.count > centerIndexPath.row {
|
||||
scrollIndicatorView.highlighted = seismicViewModels[centerIndexPath.row]
|
||||
} else {
|
||||
scrollIndicatorView.highlighted = nil
|
||||
}
|
||||
} else {
|
||||
scrollIndicatorView.highlighted = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func refreshDataTapped(_ sender: Any) {
|
||||
loadData(forced: true)
|
||||
}
|
||||
|
||||
@IBAction func openFilterTapped(_ sender: Any) {
|
||||
private func openFilter() {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierFilters, sender: nil)
|
||||
}
|
||||
|
||||
@IBAction func collapseExpandTapped(_ sender: Any) {
|
||||
if informations.contains(.buttons) {
|
||||
cardDisplayType.advance()
|
||||
|
||||
switch cardDisplayType {
|
||||
case .small:
|
||||
informations.removeAll(where: { $0 == .buttons })
|
||||
} else {
|
||||
case .full:
|
||||
informations.append(.buttons)
|
||||
case .minimal:
|
||||
break
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
@@ -227,16 +717,26 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
let row = rows[indexPath.row]
|
||||
switch row {
|
||||
case .seismic(let seismic):
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkTableViewCell.self, for: indexPath)
|
||||
|
||||
var type = SeismicNetworkTableViewCell.DisplayType.normal
|
||||
if openMapIndexPath == indexPath {
|
||||
type = .mapExpanded
|
||||
switch cardDisplayType {
|
||||
case .small, .full:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkTableViewCell.self, for: indexPath)
|
||||
|
||||
var type = SeismicNetworkTableViewCell.DisplayType.normal
|
||||
if openMapIndexPath == indexPath {
|
||||
type = .mapExpanded
|
||||
}
|
||||
|
||||
let isPushSelected = isSeismicToHighlight(seismic: seismic)
|
||||
cell.configure(with: seismic, type: type, informations: informations, isPushSelected: isPushSelected)
|
||||
cell.delegate = self
|
||||
return cell
|
||||
case .minimal:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkMinimalTableViewCell.self, for: indexPath)
|
||||
let isPushSelected = isSeismicToHighlight(seismic: seismic)
|
||||
cell.configure(with: seismic, isPushSelected: isPushSelected)
|
||||
cell.delegate = self
|
||||
return cell
|
||||
}
|
||||
|
||||
cell.configure(with: seismic, type: type, informations: informations)
|
||||
cell.delegate = self
|
||||
return cell
|
||||
case .advertise(let nativeAd):
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkAdvertiseTableViewCell.self, for: indexPath)
|
||||
cell.loadNativeAd(nativeAd)
|
||||
@@ -253,6 +753,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIScrollViewDelegate
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
updateCenterCellIndexPath()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func openCalendar(for seismic: EQNSisma) {
|
||||
@@ -308,25 +814,25 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworksViewController: GADNativeAdLoaderDelegate {
|
||||
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADNativeAd) {
|
||||
print("[GADAdLoader] didReceive")
|
||||
extension SeismicNetworksViewController: NativeAdLoaderDelegate {
|
||||
func adLoader(_ adLoader: AdLoader, didReceive nativeAd: NativeAd) {
|
||||
print("[AdLoader] didReceive")
|
||||
|
||||
let adPosition = min(3, rows.count)
|
||||
rows.insert(.advertise(nativeAd), at: adPosition)
|
||||
tableView?.reloadData()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: Error) {
|
||||
func adLoader(_ adLoader: AdLoader, didFailToReceiveAdWithError error: Error) {
|
||||
// nope
|
||||
print("[GADAdLoader] didFailToReceiveAdWithError: \(error.localizedDescription)")
|
||||
print("[AdLoader] didFailToReceiveAdWithError: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
||||
extension SeismicNetworksViewController: SeismicNetworkBaseTableViewCellDelegate {
|
||||
|
||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
|
||||
// create a snapshot of the cell and share with default share sheet
|
||||
let snapshot = cell.contentView.createSnapshot()
|
||||
@@ -342,38 +848,44 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell) else { return }
|
||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
guard let index = tableView.indexPath(for: cell) else { return }
|
||||
|
||||
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
||||
|
||||
openMapIndexPath = index
|
||||
tableView?.reloadRows(at: indexToReloads, with: .automatic)
|
||||
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
|
||||
showMapDetail(for: seismic)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
|
||||
showIntensityMap(for: seismic)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
||||
|
||||
openCalendar(for: seismic)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell) {
|
||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierCardSettings, sender: nil)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell) else { return }
|
||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell) {
|
||||
guard let index = tableView.indexPath(for: cell) else { return }
|
||||
|
||||
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
||||
|
||||
openMapIndexPath = nil
|
||||
tableView?.reloadRows(at: indexToReloads, with: .automatic)
|
||||
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +898,7 @@ extension SeismicNetworksViewController: EKEventEditViewDelegate {
|
||||
|
||||
extension SeismicNetworksViewController: SeismicFiltersViewControllerDelegate {
|
||||
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
|
||||
model.filter = controller.currentFilterType
|
||||
loadData(forced: controller.needsDataUpdate)
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
+27
@@ -13,14 +13,17 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
|
||||
|
||||
private enum RowIdentifier: Int {
|
||||
case abilitaNotifiche
|
||||
case disabilitaSuonoAllerta
|
||||
case abilitaCriticalAlerts
|
||||
}
|
||||
|
||||
private var isNotificationEnabled = false
|
||||
private var isMildQuakeSoundDisabled = false
|
||||
private var isCriticalAlertsEnabled = false
|
||||
|
||||
private let settings: [SettingItem] = [
|
||||
.init(type: .enable, title: NSLocalizedString("options_notification_enable_alarm", comment: "")),
|
||||
.init(type: .enable, title: NSLocalizedString("options_notification_disable_sound", comment: "")),
|
||||
.init(type: .enable, title: NSLocalizedString("critical_alerts_setting", comment: ""))
|
||||
]
|
||||
|
||||
@@ -56,6 +59,7 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
|
||||
let saved = EQNSettingRealTimeAlert.shared
|
||||
|
||||
isNotificationEnabled = saved.isAbilitato
|
||||
isMildQuakeSoundDisabled = saved.isMildQuakeSoundDisabled
|
||||
isCriticalAlertsEnabled = saved.isCriticalAlertsEnabled
|
||||
}
|
||||
|
||||
@@ -93,6 +97,11 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeNotificationEnabled(enabled)
|
||||
}
|
||||
case .disabilitaSuonoAllerta:
|
||||
cell.toggleSwitch.isOn = isMildQuakeSoundDisabled
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
self?.onChangeDisableSoundEnabled(enabled)
|
||||
}
|
||||
case .abilitaCriticalAlerts:
|
||||
cell.toggleSwitch.isOn = isCriticalAlertsEnabled
|
||||
cell.valueChanged = { [weak self] enabled in
|
||||
@@ -116,11 +125,29 @@ class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func onChangeDisableSoundEnabled(_ enabled: Bool) {
|
||||
isMildQuakeSoundDisabled = enabled
|
||||
EQNSettingRealTimeAlert.shared.isMildQuakeSoundDisabled = isMildQuakeSoundDisabled
|
||||
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func onChangeCriticalAlertsEnabled(_ enabled: Bool) {
|
||||
if enabled {
|
||||
askForCriticalAlertsPermission()
|
||||
}
|
||||
|
||||
isCriticalAlertsEnabled = enabled
|
||||
EQNSettingRealTimeAlert.shared.isCriticalAlertsEnabled = isCriticalAlertsEnabled
|
||||
EQNSettingRealTimeAlert.shared.saveUserInfo()
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func askForCriticalAlertsPermission() {
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [ .criticalAlert ]) { granted, error in
|
||||
// nope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
//
|
||||
// SettingsSeismicNetworkNotificationsFilterViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 06/06/24.
|
||||
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Shogun
|
||||
|
||||
class SettingsSeismicNetworkNotificationsFilterViewController: UITableViewController {
|
||||
|
||||
private let filters: [EQNSettingSeismicNetworkNotification.FilterType] = [.soloRilevanti, .condizionati]
|
||||
private var currentFilter = EQNSettingSeismicNetworkNotification.shared.filtro
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
convenience init() {
|
||||
self.init(style: .insetGrouped)
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupUI() {
|
||||
tableView.estimatedRowHeight = 200.0
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
|
||||
tableView.registerCell(for: SettingDetailTableViewCell.self)
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
|
||||
view.titleLabel.text = NSLocalizedString("options_official_type", comment: "")
|
||||
return view
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
SettingSectionHeaderView.Height
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return filters.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let filter = filters[indexPath.row]
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingDetailTableViewCell.self, for: indexPath)
|
||||
cell.textLabel?.text = filter.displayName
|
||||
cell.textLabel?.numberOfLines = 0
|
||||
cell.accessoryType = currentFilter == filter ? .checkmark : .none
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let filter = filters[indexPath.row]
|
||||
|
||||
EQNSettingSeismicNetworkNotification.shared.filtro = filter
|
||||
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
|
||||
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
+2
-36
@@ -13,15 +13,11 @@ class SettingsSeismicNetworkNotificationsViewController: SettingsBaseTableViewCo
|
||||
|
||||
private enum RowIdentifier: Int {
|
||||
case abilitaNotifiche
|
||||
case filtroNotifiche
|
||||
case magnitudoMinima
|
||||
case distanzaMassima
|
||||
}
|
||||
|
||||
private static let SegueFilters = "ShowFilters"
|
||||
|
||||
|
||||
private var isNotificationEnabled = false
|
||||
private var currentFilter: EQNSettingSeismicNetworkNotification.FilterType = .soloRilevanti
|
||||
private var currentMinimumMagnitude = EQNData.DefaultSettingSeismicNetworkNotificationMagitude
|
||||
private var currentMaximumDistance = EQNData.DefaultSettingSeismicNetworkNotificationRadius
|
||||
private let dataSourceMinimumMagnitude = EQNData.settingSeismicNetworkNotificationMagnitudes
|
||||
@@ -29,7 +25,6 @@ class SettingsSeismicNetworkNotificationsViewController: SettingsBaseTableViewCo
|
||||
|
||||
private let settings: [SettingItem] = [
|
||||
.init(type: .enable, title: NSLocalizedString("options_notification_enable_official", comment: "")),
|
||||
.init(type: .multiValues, title: NSLocalizedString("options_official_type", comment: ""), segue: SegueFilters),
|
||||
.init(type: .slider, title: NSLocalizedString("options_official_minmag", comment: "")),
|
||||
.init(type: .slider, title: NSLocalizedString("options_official_maxdist", comment: ""))
|
||||
]
|
||||
@@ -69,9 +64,6 @@ class SettingsSeismicNetworkNotificationsViewController: SettingsBaseTableViewCo
|
||||
|
||||
isNotificationEnabled = saved.isAbilitato
|
||||
|
||||
// filtro notifiche
|
||||
currentFilter = saved.filtro
|
||||
|
||||
// magnitudo minima
|
||||
let magnitudoMinima = EQNData.getSettingSeismicNetworkNotificationMagnitudes(for: saved.magnitudoMinima)
|
||||
currentMinimumMagnitude = magnitudoMinima
|
||||
@@ -119,28 +111,13 @@ class SettingsSeismicNetworkNotificationsViewController: SettingsBaseTableViewCo
|
||||
break
|
||||
}
|
||||
|
||||
return cell
|
||||
case .multiValues:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingMultivaluesTableViewCell.self, for: indexPath)
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.titleLabel.text = setting.title
|
||||
|
||||
switch identifier {
|
||||
case .filtroNotifiche:
|
||||
cell.isDisabled = !isNotificationEnabled
|
||||
cell.isUserInteractionEnabled = isNotificationEnabled
|
||||
cell.valuesLabel.text = currentFilter.displayName
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return cell
|
||||
|
||||
case .slider:
|
||||
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingSliderTableViewCell.self, for: indexPath)
|
||||
cell.titleLabel.text = setting.displayTitle
|
||||
|
||||
let filtersEnabled = isNotificationEnabled && currentFilter == .condizionati
|
||||
let filtersEnabled = isNotificationEnabled
|
||||
switch identifier {
|
||||
case .magnitudoMinima:
|
||||
cell.isDisabled = !filtersEnabled
|
||||
@@ -165,17 +142,6 @@ class SettingsSeismicNetworkNotificationsViewController: SettingsBaseTableViewCo
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let setting = settings[indexPath.row]
|
||||
switch setting.segue {
|
||||
case Self.SegueFilters:
|
||||
let controller = SettingsSeismicNetworkNotificationsFilterViewController()
|
||||
navigationController?.pushViewController(controller, animated: true)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func onChangeNotificationEnabled(_ enabled: Bool) {
|
||||
isNotificationEnabled = enabled
|
||||
EQNSettingSeismicNetworkNotification.shared.isAbilitato = isNotificationEnabled
|
||||
|
||||
@@ -49,7 +49,7 @@ static NSString * const EQNServerUrlCalibration = @"https://srv.earthquakenetwor
|
||||
// download rete smartphone
|
||||
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://cache.earthquakenetwork.it/distquake_count_redis3.php";
|
||||
// download area check
|
||||
static NSString * const EQNServerUrlDownloadAreaCheck = @"https://srv.earthquakenetwork.it/distquake_download_areacheck.php";
|
||||
static NSString * const EQNServerUrlDownloadAreaCheck = @"https://cache.earthquakenetwork.it/distquake_download_areacheck.php";
|
||||
// download pastquakes
|
||||
static NSString * const EQNServerUrlDownloadPastQuakes = @"https://srv.earthquakenetwork.it/distquake_download_pastquakes.php";
|
||||
// download segnalazioni
|
||||
@@ -64,8 +64,6 @@ static NSString * const EQNServerUrlAlertSimulator = @"https://srv.earthquakenet
|
||||
|
||||
#pragma mark - UserDefaults Keys
|
||||
|
||||
static NSString * const EQNUserDefaultKeySesmicInformations = @"EQNetwork.SeismicInformations";
|
||||
static NSString * const EQNUserDefaultKeyOneShotShowCountry = @"EQNetwork.OneShot.CountrySelection";
|
||||
static NSString * const EQNUserDefaultSeismicNetworkCards = @"EQNData.RetiSismiche";
|
||||
|
||||
#pragma mark - NSNotification
|
||||
|
||||
@@ -55,9 +55,9 @@
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>L'accesso al calendario è richiesto per poter salvare le informazioni dei sismi di interesse</string>
|
||||
<string>L'accesso al calendario è richiesto per poter salvare le informazioni dei sismi di interesse</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>L'accesso ai contatti è richiesto per poter aggiungere persone agli eventi creati</string>
|
||||
<string>L'accesso ai contatti è richiesto per poter aggiungere persone agli eventi creati</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
@@ -65,9 +65,9 @@
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>L'accesso alla libreria è richiesto per poter salvare le immagini generate dall'app</string>
|
||||
<string>L'accesso alla libreria è richiesto per poter salvare le immagini generate dall'app</string>
|
||||
<key>NSUserTrackingUsageDescription</key>
|
||||
<string>Il tracciamento serve a capire se la pubblicità dell'app è efficace</string>
|
||||
<string>Il tracciamento serve a capire se la pubblicità dell'app è efficace</string>
|
||||
<key>SKAdNetworkItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -104,5 +104,7 @@
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -12,9 +12,7 @@ import UIKit
|
||||
extension UIView {
|
||||
|
||||
func eqn_applyShadowAndRoundedCorners() {
|
||||
// rounded corners
|
||||
layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
||||
layer.masksToBounds = false
|
||||
eqn_applyRoundedCorners()
|
||||
|
||||
// apply a shadow to the current view
|
||||
layer.shadowColor = UIColor.black.cgColor
|
||||
@@ -22,4 +20,10 @@ extension UIView {
|
||||
layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
layer.shadowRadius = 2
|
||||
}
|
||||
|
||||
func eqn_applyRoundedCorners() {
|
||||
// rounded corners
|
||||
layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
||||
layer.masksToBounds = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,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) }
|
||||
set { UserDefaults.standard.set(newValue, forKey: UserDefaults.AlertsShowCardOptions) }
|
||||
}
|
||||
|
||||
var mapPinStyle: MapPinStyle {
|
||||
get {
|
||||
let saved = UserDefaults.standard.integer(forKey: UserDefaults.MapPinStyle)
|
||||
return MapPinStyle(rawValue: saved) ?? .circle
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaults.MapPinStyle)
|
||||
}
|
||||
}
|
||||
|
||||
var seismicNetworksInformations: [SeismicNetworkTableViewCell.InformationType] {
|
||||
get {
|
||||
if let saved = UserDefaults.standard.array(forKey: UserDefaults.SeismicNetworksCardInformations) as? [Int] {
|
||||
let informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
|
||||
return informations
|
||||
}
|
||||
return [.buttons, .distance, .coordinate, .population, .intensityMap]
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.map { $0.rawValue }, forKey: UserDefaults.SeismicNetworksCardInformations)
|
||||
}
|
||||
}
|
||||
|
||||
var seismicNetworksCardStyle: SeismicNetworksViewController.CardDisplayType {
|
||||
get {
|
||||
let saved = UserDefaults.standard.integer(forKey: UserDefaults.SeismicNetworksCardStyle)
|
||||
return SeismicNetworksViewController.CardDisplayType(rawValue: saved) ?? .small
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaults.SeismicNetworksCardStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public class EQNAppearanceCommand: EQNCommandProtocol {
|
||||
// MARK: - Public
|
||||
|
||||
func execute() {
|
||||
print("EQNAppearanceCommand: start execute")
|
||||
print("[EQNAppearanceCommand] Start execute")
|
||||
|
||||
applyAppearance()
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public class EQNAppearanceCommand: EQNCommandProtocol {
|
||||
navAppearance.largeTitleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: AppTheme.Colors.darkGray
|
||||
]
|
||||
navAppearance.backgroundColor = AppTheme.Colors.primary
|
||||
navAppearance.backgroundColor = AppTheme.Colors.navBar
|
||||
navAppearance.shadowColor = UIColor.clear
|
||||
|
||||
proxyNavBar.isTranslucent = false
|
||||
|
||||
@@ -15,71 +15,17 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
|
||||
// MARK: - Public
|
||||
|
||||
func execute() {
|
||||
print("EQNUserDefaultsCommand: start execute")
|
||||
print("[EQNUserDefaultsCommand] Start execute")
|
||||
|
||||
applyDefaultSettings()
|
||||
saveMissingValues()
|
||||
|
||||
migrationV5_3()
|
||||
migrationV5_4()
|
||||
migrationV5_8()
|
||||
migrationFirstAppStat()
|
||||
migrationCriticalAlerts()
|
||||
migrationV5_9()
|
||||
migrationV5_10()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func applyDefaultSettings() {
|
||||
|
||||
// seismic card settings
|
||||
if UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) == nil {
|
||||
let informations: [SeismicNetworkTableViewCell.InformationType] = [.buttons, .distance, .coordinate, .population]
|
||||
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
|
||||
}
|
||||
}
|
||||
|
||||
private func saveMissingValues() {
|
||||
// `raggio sismi forti` was not saved before v2.3
|
||||
if UserDefaults.standard.object(forKey: UserDefaults.AllertaSismicaRaggioSismiForti) == nil {
|
||||
UserDefaults.standard.set("600", forKey: UserDefaults.AllertaSismicaRaggioSismiForti)
|
||||
}
|
||||
}
|
||||
|
||||
private func migrationV5_3() {
|
||||
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_3)
|
||||
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() {
|
||||
let migrationPerformed = UserDefaults.standard.bool(forKey: UserDefaults.AppMigrationV5_8)
|
||||
if migrationPerformed {
|
||||
@@ -115,4 +61,78 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,14 @@
|
||||
- (void)scaricaDatiReteSmartphone
|
||||
{
|
||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:EQNServerUrlDownloadSmartphoneNetwork] richiesta:EQNTipoChiamataDownloadDati success:^(id result) {
|
||||
|
||||
self.rete_smartphone = [[EQNReteSmartphone alloc] initWithInfo:result];
|
||||
|
||||
// Parsiamo la risposta (assicurandoci che non contenga valori nulli)
|
||||
EQNReteSmartphone *rete = [EQNReteSmartphone fromResponse:result];
|
||||
if (rete != nil) {
|
||||
self.rete_smartphone = rete;
|
||||
} else {
|
||||
NSLog(@"[EQNManager] Impossibile parsare la risposta di DownloadSmartphoneNetwork");
|
||||
}
|
||||
|
||||
[self performSelectorOnMainThread:@selector(scaricaAreaCheck) withObject:nil waitUntilDone:YES];
|
||||
} failure:^(NSError * error) {
|
||||
@@ -72,7 +78,12 @@
|
||||
|
||||
- (void)scaricaAreaCheck
|
||||
{
|
||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?lat=%f&lon=%f", EQNServerUrlDownloadAreaCheck, [EQNUser defaultUser].lastPosition.coordinate.latitude, [EQNUser defaultUser].lastPosition.coordinate.longitude]] richiesta:EQNTipoChiamataAreaCheck success:^(id result) {
|
||||
// Quantizziamo le coordinate, in modo che venga sfruttata la cache lato server
|
||||
CLLocation *lastPosition = [EQNUser defaultUser].lastPosition;
|
||||
double latitude = round(lastPosition.coordinate.latitude * 5.0) / 5.0;
|
||||
double longitude = round(lastPosition.coordinate.longitude * 5.0) / 5.0;
|
||||
|
||||
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?lat=%f&lon=%f", EQNServerUrlDownloadAreaCheck, latitude, longitude]] richiesta:EQNTipoChiamataAreaCheck success:^(id result) {
|
||||
|
||||
self.area_check = [[EQNAreaCheck alloc] initWithInfo:result];
|
||||
|
||||
@@ -123,11 +134,13 @@
|
||||
// L'endpoint per lo scaricamento dei dati prende due parametri:
|
||||
// - `pro` per il provider selezionato,
|
||||
// - `mag` per la magnitudo minima.
|
||||
// Dalla v5.8 non esiste più la selezione delle reti, quindi passiamo sempre "ALL".
|
||||
// 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,
|
||||
// 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 *queryString = [NSString stringWithFormat:@"?pro=%@&mag=%@", filterProvider, filterMagnitude];
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// EQNOfficialPushNotification.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 29/06/24.
|
||||
// Copyright © 2024 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Shogun
|
||||
|
||||
|
||||
@objc
|
||||
class EQNOfficialPushNotification: NSObject, Codable {
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case latitude
|
||||
case longitude
|
||||
case magnitude
|
||||
case date
|
||||
}
|
||||
|
||||
private let latitude: Double
|
||||
private let longitude: Double
|
||||
let magnitude: Double
|
||||
let date: Date?
|
||||
|
||||
var coordinate: CLLocation {
|
||||
.init(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
magnitude: Double,
|
||||
date: Date?
|
||||
) {
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.magnitude = magnitude
|
||||
self.date = date
|
||||
}
|
||||
|
||||
// MARK: - Class
|
||||
|
||||
/// Remove any saved notification
|
||||
static func removeStored() {
|
||||
UserDefaults.standard.removeObject(forKey: UserDefaults.OfficialAlertPayload)
|
||||
}
|
||||
|
||||
/// Retrieve stored notification (if any)
|
||||
static func stored() -> EQNOfficialPushNotification? {
|
||||
guard let data = UserDefaults.standard.object(forKey: UserDefaults.OfficialAlertPayload) as? Data else {
|
||||
print("[EQNOfficialPushNotification] No notification saved for key '\(UserDefaults.OfficialAlertPayload)'")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let notification = try? JSONDecoder().decode(EQNOfficialPushNotification.self, from: data) else {
|
||||
print("[EQNOfficialPushNotification] Unable to decode given notification")
|
||||
return nil
|
||||
}
|
||||
|
||||
return notification
|
||||
}
|
||||
|
||||
/// Convert and store a push notification payload.
|
||||
/// Expected payload has the following structure:
|
||||
/// ```
|
||||
/// {
|
||||
/// "title": "Segnalazione da rete sismica",
|
||||
/// "body": "Sisma rilevato a 4km S Valfabbrica (PG)",
|
||||
/// "userInfo": {
|
||||
/// "data" : "2024-06-29 11:21:30",
|
||||
/// ...
|
||||
/// "aps": {
|
||||
/// "alert" : {
|
||||
/// "loc-key" : "Sisma rilevato a",
|
||||
/// "title-loc-key" : "Segnalazione da rete sismica",
|
||||
/// "title" : "Segnalazione da rete sismica",
|
||||
/// "action-loc-key" : "",
|
||||
/// "loc-args" : [
|
||||
/// "6 km S Acate (RG) - M1.9"
|
||||
/// ]
|
||||
/// },
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// - Parameter payload: Notification payload
|
||||
/// - Returns: `true` if save succeed, `false` otherwise
|
||||
@objc(storeNotificationWithPayload:)
|
||||
@discardableResult
|
||||
static func store(payload: [String: Any]) -> Bool {
|
||||
guard let notification = from(payload: payload) else {
|
||||
print("[EQNOfficialPushNotification] Unable to convert received notification")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let data = try? JSONEncoder().encode(notification) else {
|
||||
print("[EQNOfficialPushNotification] Unable to encode given notification")
|
||||
return false
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(data, forKey: UserDefaults.OfficialAlertPayload)
|
||||
return true
|
||||
}
|
||||
|
||||
@objc
|
||||
private static func from(payload: [String: Any]) -> EQNOfficialPushNotification? {
|
||||
guard let userInfo = payload["userInfo"] as? [String: Any] else {
|
||||
print("[EQNOfficialPushNotification] Missing required info to parse push notification")
|
||||
return nil
|
||||
}
|
||||
|
||||
let latitude = userInfo.double(forKey: "latitude") ?? 0
|
||||
let longitude = userInfo.double(forKey: "longitude") ?? 0
|
||||
let magnitude = userInfo.double(forKey: "magnitude") ?? 0
|
||||
let dateString = userInfo.string(forKey: "data") ?? ""
|
||||
let date = EQNUtility.getDateFrom(dateString)
|
||||
|
||||
return .init(
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
magnitude: magnitude,
|
||||
date: date
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case intensity
|
||||
case magnitude
|
||||
case latitude
|
||||
case longitude
|
||||
case counter
|
||||
@@ -36,6 +37,7 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
||||
let type: String
|
||||
/// Earthquake intensity
|
||||
let intensity: Int
|
||||
let magnitude: Int
|
||||
/// Earthquake coordinate
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
@@ -63,6 +65,7 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
||||
init(
|
||||
type: String,
|
||||
intensity: Int,
|
||||
magnitude: Int,
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
counter: Int,
|
||||
@@ -76,6 +79,7 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
||||
) {
|
||||
self.type = type
|
||||
self.intensity = intensity
|
||||
self.magnitude = magnitude
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.counter = counter
|
||||
@@ -201,6 +205,27 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
||||
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"),
|
||||
let longitude = userInfo.double(forKey: "longitude") else {
|
||||
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 intensity = userInfo.integer(forKey: "intensity", orDefault: 0)
|
||||
let magnitude = userInfo.integer(forKey: "magnitude", orDefault: 0)
|
||||
|
||||
let counter = userInfo.integer(forKey: "counter", orDefault: 0)
|
||||
var dateTime: Date?
|
||||
@@ -222,16 +248,10 @@ class EQNRealtimePushNotification: NSObject, Codable {
|
||||
}
|
||||
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(
|
||||
type: type,
|
||||
intensity: intensity,
|
||||
magnitude: magnitude,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
counter: counter,
|
||||
|
||||
@@ -21,13 +21,27 @@ class EQNReteSmartphone: NSObject {
|
||||
let top10kAvailable: Int
|
||||
let top100kAvailable: Int
|
||||
|
||||
// MARK: - Class
|
||||
|
||||
// Sometimes the response returns a broken response, with all values to null.
|
||||
// In order to avoid crashes due to ObjC-Swift bridging, we need to take a generic nullable object,
|
||||
// and then cast to the expected type (a dictionary).
|
||||
@objc static func from(response: Any?) -> EQNReteSmartphone? {
|
||||
if let info = response as? [[String: Any]?] {
|
||||
return .init(info: info)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
@objc init(info: [[String: Any]]) {
|
||||
private init(info: [[String: Any]?]) {
|
||||
// merge array in a single dictionary
|
||||
let allValues = info.reduce([:]) { (result, dictionary) -> [String: Any] in
|
||||
return result.merging(dictionary, uniquingKeysWith: { (_, new) in new })
|
||||
}
|
||||
let allValues = info
|
||||
.compactMap { $0 }
|
||||
.reduce([:]) { (result, dictionary) -> [String: Any] in
|
||||
return result.merging(dictionary, uniquingKeysWith: { (_, new) in new })
|
||||
}
|
||||
|
||||
self.counterLastDayAlerts = allValues.integer(forKey: "eq", orDefault: 0)
|
||||
self.counterTotalAlerts = allValues.integer(forKey: "eq_p", orDefault: 0)
|
||||
|
||||
@@ -15,9 +15,10 @@ import Foundation
|
||||
case inRadius
|
||||
case positionRelevant
|
||||
case worldWide
|
||||
case userFelt
|
||||
}
|
||||
|
||||
enum Sort: Int {
|
||||
enum Sort: Int, CaseIterable {
|
||||
case time
|
||||
case position
|
||||
case magnitude
|
||||
@@ -173,11 +174,16 @@ import Foundation
|
||||
} else if magnitude < 1.5 && distance > 20 {
|
||||
keep = false
|
||||
}
|
||||
} else {
|
||||
} else if filterOption == .worldWide {
|
||||
//filtro che mostra tutti i sismi a livello mondiale di magnitudo>=2
|
||||
if magnitude < 2 {
|
||||
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 *smartphoneNumber;
|
||||
@property (nonatomic, strong) NSNumber *userNumber;
|
||||
/// Code to show "intensity map"
|
||||
@property (nonatomic, strong) NSString *isoCode;
|
||||
|
||||
- (instancetype)initWithInfo:(NSDictionary *)info;
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
self.preliminary = info[@"py"];
|
||||
self.smartphoneNumber = info[@"sm"];
|
||||
self.userNumber = info[@"rp"];
|
||||
|
||||
self.isoCode = info[@"iso"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -65,6 +67,7 @@
|
||||
[encoder encodeObject:self.preliminary forKey:@"preliminary"];
|
||||
[encoder encodeObject:self.smartphoneNumber forKey:@"smartphoneNumber"];
|
||||
[encoder encodeObject:self.userNumber forKey:@"userNumber"];
|
||||
[encoder encodeObject:self.isoCode forKey:@"isoCode"];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder
|
||||
@@ -86,6 +89,7 @@
|
||||
self.preliminary = [decoder decodeObjectForKey:@"preliminary"];
|
||||
self.smartphoneNumber = [decoder decodeObjectForKey:@"smartphoneNumber"];
|
||||
self.userNumber = [decoder decodeObjectForKey:@"userNumber"];
|
||||
self.isoCode = [decoder decodeObjectForKey:@"isoCode"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
self.registrationInProgress = NO;
|
||||
self.user_ID = EQNUserData.sharedData.userId;
|
||||
self.lastPosition = EQNUserData.sharedData.lastLocation;
|
||||
self.currentFirebaseToken = EQNUserData.sharedData.firebaseToken;
|
||||
|
||||
[self registerForLocationUpdates];
|
||||
}
|
||||
|
||||
@@ -19,7 +19,15 @@ import CoreLocation
|
||||
|
||||
@objc
|
||||
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
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
{
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Europe/Rome"];
|
||||
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
|
||||
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
|
||||
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()
|
||||
|
||||
@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 raggioSismiForti: String
|
||||
var raggioSismiForti: String
|
||||
|
||||
private static let DefaultSismiDaNotificare = "0"
|
||||
private static let DefaultRaggioSismiLievi = "250"
|
||||
@@ -38,6 +39,7 @@ class EQNSettingRealTimeAlert: NSObject {
|
||||
|
||||
let sharedDefaults = UserDefaults.appGroup
|
||||
isCriticalAlertsEnabled = sharedDefaults?.bool(forKey: UserDefaults.AllertaSismicaCriticalAlerts) ?? false
|
||||
isMildQuakeSoundDisabled = sharedDefaults?.bool(forKey: UserDefaults.AllertaSismicaSuonoDisabilitatoSismaDebole) ?? true
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -50,12 +52,14 @@ class EQNSettingRealTimeAlert: NSObject {
|
||||
defaults.set(raggioSismiForti, forKey: UserDefaults.AllertaSismicaRaggioSismiForti)
|
||||
if let sharedDefaults = UserDefaults.appGroup {
|
||||
sharedDefaults.set(isCriticalAlertsEnabled, forKey: UserDefaults.AllertaSismicaCriticalAlerts)
|
||||
sharedDefaults.set(isMildQuakeSoundDisabled, forKey: UserDefaults.AllertaSismicaSuonoDisabilitatoSismaDebole)
|
||||
}
|
||||
}
|
||||
|
||||
@objc class func saveDefaultValues() {
|
||||
shared.isAbilitato = true
|
||||
shared.isCriticalAlertsEnabled = false
|
||||
shared.isMildQuakeSoundDisabled = true
|
||||
shared.sismiDaNotificare = Self.DefaultSismiDaNotificare
|
||||
shared.raggioSismiLievi = Self.DefaultRaggioSismiLievi
|
||||
shared.raggioSismiForti = Self.DefaultRaggioSismiForti
|
||||
|
||||
@@ -11,29 +11,15 @@ import Foundation
|
||||
@objc
|
||||
class EQNSettingSeismicNetworkNotification: NSObject {
|
||||
|
||||
enum FilterType: Int {
|
||||
case soloRilevanti
|
||||
case condizionati
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .soloRilevanti: NSLocalizedString("options_official_type_relevant", comment: "")
|
||||
case .condizionati: NSLocalizedString("options_official_type_area", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc(sharedInstance)
|
||||
static let shared = EQNSettingSeismicNetworkNotification()
|
||||
|
||||
@objc var isAbilitato: Bool
|
||||
@objc var magnitudoMinima: String
|
||||
@objc var distanzaMassima: String
|
||||
var filtro: FilterType
|
||||
|
||||
private static let DefaultMagnitudoMinima = EQNData.DefaultFilterMagnitude.value
|
||||
private static let DefaultDistanzaMassima = EQNData.DefaultFilterRadius.value
|
||||
private static let DefaultFiltro = FilterType.soloRilevanti
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
@@ -44,7 +30,6 @@ class EQNSettingSeismicNetworkNotification: NSObject {
|
||||
self.isAbilitato = defaults.bool(forKey: UserDefaults.NotificheRetiSismicheAbilitato)
|
||||
self.magnitudoMinima = defaults.object(forKey: UserDefaults.NotificheRetiSismicheMagnitudoMinima, or: Self.DefaultMagnitudoMinima)
|
||||
self.distanzaMassima = defaults.object(forKey: UserDefaults.NotificheRetiSismicheDistanzaMassima, or: Self.DefaultDistanzaMassima)
|
||||
self.filtro = defaults.enumObject(forKey: UserDefaults.NotificheRetiSismicheFiltroNotifiche, or: Self.DefaultFiltro)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -54,14 +39,12 @@ class EQNSettingSeismicNetworkNotification: NSObject {
|
||||
defaults.set(isAbilitato, forKey: UserDefaults.NotificheRetiSismicheAbilitato)
|
||||
defaults.set(magnitudoMinima, forKey: UserDefaults.NotificheRetiSismicheMagnitudoMinima)
|
||||
defaults.set(distanzaMassima, forKey: UserDefaults.NotificheRetiSismicheDistanzaMassima)
|
||||
defaults.set(filtro.rawValue, forKey: UserDefaults.NotificheRetiSismicheFiltroNotifiche)
|
||||
}
|
||||
|
||||
@objc class func saveDefaultValues() {
|
||||
shared.isAbilitato = true
|
||||
shared.magnitudoMinima = DefaultMagnitudoMinima
|
||||
shared.distanzaMassima = DefaultDistanzaMassima
|
||||
shared.filtro = DefaultFiltro
|
||||
shared.saveUserInfo()
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "130",
|
||||
"green" : "212",
|
||||
"red" : "148"
|
||||
"blue" : "196",
|
||||
"green" : "226",
|
||||
"red" : "200"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "167",
|
||||
"green" : "231",
|
||||
"red" : "255"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.318",
|
||||
"green" : "0.565",
|
||||
"red" : "0.000"
|
||||
"blue" : "35",
|
||||
"green" : "160",
|
||||
"red" : "12"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
||||
Vendored
-21
@@ -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
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "xcorp_icon.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -134,6 +134,7 @@
|
||||
case EQNTipoChiamataCalibrazione:
|
||||
case EQNTipoChiamataImpostazioniNotifiche:
|
||||
case EQNTipoChiamataAlertSimulator:
|
||||
case EQNTipoChiamataAlertPushTest:
|
||||
case EQNTipoChiamataRegisterSubscription:
|
||||
onSuccess(newStr);
|
||||
default:
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<?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"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@@ -17,55 +16,32 @@
|
||||
<view key="view" contentMode="scaleToFill" id="yYN-HE-bpD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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="q4o-YO-KLX">
|
||||
<rect key="frame" x="0.0" y="144" width="414" height="669"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="tVM-DH-fmv" id="XVf-fb-5Kl"/>
|
||||
<outlet property="delegate" destination="tVM-DH-fmv" id="dWO-2A-Ukg"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="VUD-fs-xgm"/>
|
||||
<color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="VUD-fs-xgm" firstAttribute="bottom" secondItem="q4o-YO-KLX" secondAttribute="bottom" id="3P1-fP-chi"/>
|
||||
<constraint firstItem="q4o-YO-KLX" firstAttribute="top" secondItem="VUD-fs-xgm" secondAttribute="top" id="IeK-TW-jL5"/>
|
||||
<constraint firstItem="q4o-YO-KLX" firstAttribute="leading" secondItem="VUD-fs-xgm" secondAttribute="leading" id="Yyx-Tc-hnn"/>
|
||||
<constraint firstItem="VUD-fs-xgm" firstAttribute="trailing" secondItem="q4o-YO-KLX" secondAttribute="trailing" id="yd6-Pa-c2s"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="6kD-jk-5Aw">
|
||||
<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">
|
||||
<connections>
|
||||
<action selector="refreshDataTapped:" destination="tVM-DH-fmv" id="qs5-jS-0Op"/>
|
||||
</connections>
|
||||
</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>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="expandeCollapseButton" destination="HTN-07-s5p" id="lxP-Im-NME"/>
|
||||
<outlet property="sortButton" destination="LyU-KI-3Mb" id="969-Zg-YBB"/>
|
||||
<outlet property="tableView" destination="q4o-YO-KLX" id="tee-h5-dZi"/>
|
||||
<outlet property="displayModeButton" destination="HTN-07-s5p" id="Lhc-Od-MvL"/>
|
||||
<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"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="34i-9D-p3O" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-8709" y="-5464"/>
|
||||
<point key="canvasLocation" x="-8710.144927536232" y="-5464.2857142857138"/>
|
||||
</scene>
|
||||
<!--Segnalazioni View Controller-->
|
||||
<scene sceneID="2v7-dY-aHc">
|
||||
@@ -76,7 +52,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="fbo-Ug-IiE" id="meg-jS-D2z"/>
|
||||
@@ -84,7 +60,7 @@
|
||||
</connections>
|
||||
</tableView>
|
||||
<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"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55" id="1Dm-ex-6od"/>
|
||||
@@ -139,28 +115,28 @@
|
||||
<blurEffect style="light"/>
|
||||
</visualEffectView>
|
||||
<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>
|
||||
<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"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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>
|
||||
<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>
|
||||
<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"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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>
|
||||
<action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="bzY-wG-8Qw"/>
|
||||
</connections>
|
||||
@@ -168,16 +144,16 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<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>
|
||||
<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"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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>
|
||||
<action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="QI3-0U-fDp"/>
|
||||
</connections>
|
||||
@@ -185,16 +161,16 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<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>
|
||||
<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"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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>
|
||||
<action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="U53-Iv-BcF"/>
|
||||
</connections>
|
||||
@@ -212,7 +188,7 @@
|
||||
</constraints>
|
||||
</stackView>
|
||||
<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>
|
||||
<constraint firstAttribute="height" constant="30" id="2j0-dH-u0p"/>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="Dpt-7I-vJu"/>
|
||||
@@ -244,7 +220,7 @@
|
||||
<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="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="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"/>
|
||||
@@ -293,17 +269,17 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="EQNLogViewController" id="noK-2F-IZE" customClass="EQNLogViewController" sceneMemberID="viewController">
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<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>
|
||||
<constraint firstAttribute="height" constant="30" id="CFr-4Z-X1A"/>
|
||||
</constraints>
|
||||
@@ -421,7 +397,7 @@
|
||||
<tabBarItem key="tabBarItem" title="Settings" image="tabbar-icon-settings" id="5VO-yI-kw5"/>
|
||||
<toolbarItems/>
|
||||
<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"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -450,16 +426,16 @@
|
||||
<blurEffect style="light"/>
|
||||
</visualEffectView>
|
||||
<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>
|
||||
<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"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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>
|
||||
<constraint firstAttribute="height" constant="30" id="NoD-eT-hcR"/>
|
||||
</constraints>
|
||||
@@ -469,7 +445,7 @@
|
||||
</connections>
|
||||
</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">
|
||||
<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"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="6LP-zk-O1z" id="9ZD-Dr-D9f"/>
|
||||
@@ -498,7 +474,7 @@
|
||||
<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 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="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"/>
|
||||
@@ -523,7 +499,7 @@
|
||||
<tabBarItem key="tabBarItem" title="Reports" image="tabbar-icon-reports" id="oaL-SG-Zpq"/>
|
||||
<toolbarItems/>
|
||||
<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"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -542,7 +518,7 @@
|
||||
<tabBarItem key="tabBarItem" title="Alerts" image="tabbar-icon-alerts" id="aeo-GH-qCD"/>
|
||||
<toolbarItems/>
|
||||
<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"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -561,7 +537,7 @@
|
||||
<tabBarItem key="tabBarItem" title="Seismic Networks" image="tabbar-icon-networks" id="eed-sY-0Ua"/>
|
||||
<toolbarItems/>
|
||||
<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"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -582,161 +558,15 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="92" width="414" height="666"/>
|
||||
<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"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="SeismicNotificationExpandedCell" rowHeight="700" id="C7v-rs-1fb" customClass="AlertsSeismicNotificationExpandedTableViewCell" customModule="Earthquake_Network" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="50" width="414" height="700"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="C7v-rs-1fb" id="aEH-vE-u7m">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="700"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TfA-6E-CQc">
|
||||
<rect key="frame" x="8" y="8" width="398" height="684"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Rilevato un sisma debole a 150 km (Test)" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="M7i-e5-N6v">
|
||||
<rect key="frame" x="8" y="8" width="382" height="67.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
|
||||
<color key="textColor" name="Gray (dark)"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DyB-fs-sPo">
|
||||
<rect key="frame" x="8" y="87.5" width="382" height="42.5"/>
|
||||
<string key="text">Distanza 149 km - 0 minuti fa
|
||||
Sisma rilevato da 10 smartphone</string>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" name="Gray (dark)"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="tqM-QH-LJ3">
|
||||
<rect key="frame" x="0.0" y="172.5" width="398" height="287"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="240" id="w6M-Ao-KHo"/>
|
||||
</constraints>
|
||||
</mapView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Q2V-3p-DUh">
|
||||
<rect key="frame" x="8" y="467.5" width="382" height="40"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="V2G-UH-Ibe" customClass="EQNRoundedButton" customModule="Earthquake_Network" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="187" height="40"/>
|
||||
<color key="backgroundColor" white="1" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<state key="normal" title="SHARE APP">
|
||||
<color key="titleColor" name="Gray (dark)"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="shareAppTapped:" destination="C7v-rs-1fb" eventType="touchUpInside" id="Igp-0D-llw"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="n9y-jx-2xG" customClass="EQNRoundedButton" customModule="Earthquake_Network" customModuleProvider="target">
|
||||
<rect key="frame" x="195" y="0.0" width="187" height="40"/>
|
||||
<color key="backgroundColor" white="1" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<state key="normal" title="RATE THE APP">
|
||||
<color key="titleColor" name="Gray (dark)"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="rateAppTapped:" destination="C7v-rs-1fb" eventType="touchUpInside" id="xgm-dy-Uy3"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="40" id="Qe8-J8-tY6"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Jwx-kf-che" customClass="EQNRoundedButton" customModule="Earthquake_Network" customModuleProvider="target">
|
||||
<rect key="frame" x="8" y="515.5" width="382" height="40"/>
|
||||
<color key="backgroundColor" white="1" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<state key="normal" title="VIEW ON TWITTER">
|
||||
<color key="titleColor" name="Gray (dark)"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="viewInTwitterTapped:" destination="C7v-rs-1fb" eventType="touchUpInside" id="djx-1X-bbD"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="The magnitude will be estimated by the national seismic network and it will appear in the Seismic Networks tab of the app" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GIh-sK-KX1">
|
||||
<rect key="frame" x="8" y="563.5" width="382" height="64.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" name="Gray (dark)"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3Ga-UF-Cr7" customClass="EQNRoundedButton" customModule="Earthquake_Network" customModuleProvider="target">
|
||||
<rect key="frame" x="8" y="636" width="382" height="40"/>
|
||||
<color key="backgroundColor" white="1" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<state key="normal" title="CLOSE">
|
||||
<color key="titleColor" name="Gray (dark)"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="closeTapped:" destination="C7v-rs-1fb" eventType="touchUpInside" id="y39-44-3gS"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Rilevato scuotimento forte" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Xb4-4c-oEd">
|
||||
<rect key="frame" x="8" y="138" width="382" height="26.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="22"/>
|
||||
<color key="textColor" name="Gray (dark)"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="3Ga-UF-Cr7" firstAttribute="trailing" secondItem="GIh-sK-KX1" secondAttribute="trailing" id="1qg-dS-vGZ"/>
|
||||
<constraint firstItem="DyB-fs-sPo" firstAttribute="top" secondItem="M7i-e5-N6v" secondAttribute="bottom" constant="12" id="7gG-Y2-tda"/>
|
||||
<constraint firstAttribute="trailing" secondItem="GIh-sK-KX1" secondAttribute="trailing" constant="8" id="8TC-4t-7fC"/>
|
||||
<constraint firstItem="tqM-QH-LJ3" firstAttribute="top" secondItem="Xb4-4c-oEd" secondAttribute="bottom" constant="8" id="9Nv-Zb-mlE"/>
|
||||
<constraint firstItem="Xb4-4c-oEd" firstAttribute="trailing" secondItem="DyB-fs-sPo" secondAttribute="trailing" id="F4e-yh-mKB"/>
|
||||
<constraint firstAttribute="trailing" secondItem="tqM-QH-LJ3" secondAttribute="trailing" id="Gf0-a2-jaj"/>
|
||||
<constraint firstItem="3Ga-UF-Cr7" firstAttribute="height" secondItem="Q2V-3p-DUh" secondAttribute="height" id="N2x-vd-6D6"/>
|
||||
<constraint firstItem="Xb4-4c-oEd" firstAttribute="leading" secondItem="DyB-fs-sPo" secondAttribute="leading" id="QAk-d3-ZOG"/>
|
||||
<constraint firstItem="Jwx-kf-che" firstAttribute="trailing" secondItem="Q2V-3p-DUh" secondAttribute="trailing" id="Qie-q3-No5"/>
|
||||
<constraint firstItem="Q2V-3p-DUh" firstAttribute="top" secondItem="tqM-QH-LJ3" secondAttribute="bottom" constant="8" id="TM2-ig-acw"/>
|
||||
<constraint firstItem="Jwx-kf-che" firstAttribute="top" secondItem="Q2V-3p-DUh" secondAttribute="bottom" constant="8" id="Wjc-dF-bMj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="M7i-e5-N6v" secondAttribute="trailing" constant="8" id="XHM-kN-PXl"/>
|
||||
<constraint firstItem="3Ga-UF-Cr7" firstAttribute="leading" secondItem="GIh-sK-KX1" secondAttribute="leading" id="YhB-av-ejA"/>
|
||||
<constraint firstItem="DyB-fs-sPo" firstAttribute="leading" secondItem="M7i-e5-N6v" secondAttribute="leading" id="ZHJ-oa-DjC"/>
|
||||
<constraint firstItem="Jwx-kf-che" firstAttribute="height" secondItem="Q2V-3p-DUh" secondAttribute="height" id="Zp0-Q0-p0q"/>
|
||||
<constraint firstItem="Q2V-3p-DUh" firstAttribute="leading" secondItem="TfA-6E-CQc" secondAttribute="leading" constant="8" id="ZsU-Ec-9Bc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Q2V-3p-DUh" secondAttribute="trailing" constant="8" id="bmi-oa-9ss"/>
|
||||
<constraint firstItem="tqM-QH-LJ3" firstAttribute="leading" secondItem="TfA-6E-CQc" secondAttribute="leading" id="gUO-ka-XvB"/>
|
||||
<constraint firstAttribute="bottom" secondItem="3Ga-UF-Cr7" secondAttribute="bottom" constant="8" id="gWA-6h-HmJ"/>
|
||||
<constraint firstItem="DyB-fs-sPo" firstAttribute="trailing" secondItem="M7i-e5-N6v" secondAttribute="trailing" id="jD7-QV-p8s"/>
|
||||
<constraint firstItem="GIh-sK-KX1" firstAttribute="top" secondItem="Jwx-kf-che" secondAttribute="bottom" constant="8" id="nJj-g4-TSs"/>
|
||||
<constraint firstItem="Xb4-4c-oEd" firstAttribute="top" secondItem="DyB-fs-sPo" secondAttribute="bottom" constant="8" symbolic="YES" id="pct-jg-eJW"/>
|
||||
<constraint firstItem="3Ga-UF-Cr7" firstAttribute="top" secondItem="GIh-sK-KX1" secondAttribute="bottom" constant="8" id="qaQ-zK-ael"/>
|
||||
<constraint firstItem="M7i-e5-N6v" firstAttribute="leading" secondItem="TfA-6E-CQc" secondAttribute="leading" constant="8" id="rsV-dp-eqa"/>
|
||||
<constraint firstItem="M7i-e5-N6v" firstAttribute="top" secondItem="TfA-6E-CQc" secondAttribute="top" constant="8" id="seX-2q-JVM"/>
|
||||
<constraint firstItem="Jwx-kf-che" firstAttribute="leading" secondItem="Q2V-3p-DUh" secondAttribute="leading" id="tND-n7-uEb"/>
|
||||
<constraint firstItem="GIh-sK-KX1" firstAttribute="leading" secondItem="TfA-6E-CQc" secondAttribute="leading" constant="8" id="tsI-iu-M9g"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="TfA-6E-CQc" firstAttribute="top" secondItem="aEH-vE-u7m" secondAttribute="top" constant="8" id="9GR-hr-g5x"/>
|
||||
<constraint firstAttribute="trailing" secondItem="TfA-6E-CQc" secondAttribute="trailing" constant="8" id="FWZ-RC-8Ed"/>
|
||||
<constraint firstAttribute="bottom" secondItem="TfA-6E-CQc" secondAttribute="bottom" constant="8" id="fld-qW-XMS"/>
|
||||
<constraint firstItem="TfA-6E-CQc" firstAttribute="leading" secondItem="aEH-vE-u7m" secondAttribute="leading" constant="8" id="r0r-m9-EiO"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<outlet property="closeButton" destination="3Ga-UF-Cr7" id="yNt-rU-bZX"/>
|
||||
<outlet property="containerView" destination="TfA-6E-CQc" id="twS-Jx-IAd"/>
|
||||
<outlet property="descriptionLabel" destination="GIh-sK-KX1" id="L4X-lK-CVb"/>
|
||||
<outlet property="mapView" destination="tqM-QH-LJ3" id="Gp4-gu-q8r"/>
|
||||
<outlet property="notificationDescriptionLabel" destination="DyB-fs-sPo" id="aLr-34-WND"/>
|
||||
<outlet property="notificationIntensityLabel" destination="Xb4-4c-oEd" id="QFh-Jd-pLP"/>
|
||||
<outlet property="notificationTitleLabel" destination="M7i-e5-N6v" id="RqD-wo-sXl"/>
|
||||
<outlet property="rateAppButton" destination="n9y-jx-2xG" id="mvX-2K-I6i"/>
|
||||
<outlet property="shareButton" destination="V2G-UH-Ibe" id="JT4-jR-d45"/>
|
||||
<outlet property="viewOnTwitterButton" destination="Jwx-kf-che" id="xNf-ps-fWL"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="syj-UE-OWc" id="Vah-TU-YfT"/>
|
||||
<outlet property="delegate" destination="syj-UE-OWc" id="Iqp-Mp-8lG"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<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"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55" id="kmt-E8-s2w"/>
|
||||
@@ -784,17 +614,13 @@ Sisma rilevato da 10 smartphone</string>
|
||||
</scene>
|
||||
</scenes>
|
||||
<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-filters" 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-networks" width="25" height="25"/>
|
||||
<image name="tabbar-icon-reports" width="25" height="25"/>
|
||||
<image name="tabbar-icon-settings" width="25" height="25"/>
|
||||
<namedColor name="Gray (dark)">
|
||||
<color red="0.10999999940395355" green="0.13300000131130219" blue="0.14900000393390656" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<systemColor name="groupTableViewBackgroundColor">
|
||||
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
|
||||
@@ -21,7 +21,15 @@ class AppTheme: NSObject {
|
||||
static let lightGray = UIColor(named: "Gray (light)")!
|
||||
|
||||
static let cardBackgroundRed = UIColor(named: "Red (card background)")!
|
||||
static let cardBackgroundOrange = UIColor(named: "Orange (card background)")!
|
||||
static let cardBackgroundGreen = UIColor(named: "Green (card background)")!
|
||||
static let cardBackgroundYellow = UIColor(named: "Yellow (card background)")!
|
||||
static let cardBackgroundGray = UIColor(named: "Gray (card background)")!
|
||||
|
||||
static let pureBlue = UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
|
||||
static let pureRed = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
|
||||
|
||||
static let navBar = UIColor(red: 155.0/255.0, green: 225.0/255.0, blue: 255.0/255.0, alpha: 1.0)
|
||||
}
|
||||
|
||||
static let shared = AppTheme()
|
||||
|
||||
@@ -20,22 +20,18 @@ extension CGFloat {
|
||||
static let cardVerticalSpacing: CGFloat = 10.0
|
||||
}
|
||||
|
||||
@objc
|
||||
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
|
||||
|
||||
private lazy var internalContainerView: UIView = {
|
||||
let view = UIView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = .white
|
||||
view.layer.cornerRadius = AppTheme.shared.cardCornerRadius
|
||||
view.layer.masksToBounds = false
|
||||
|
||||
// add shadow
|
||||
view.layer.shadowColor = UIColor.black.cgColor
|
||||
view.layer.shadowOpacity = 0.5
|
||||
view.layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
view.layer.shadowRadius = 2
|
||||
return view
|
||||
}()
|
||||
|
||||
@@ -54,11 +50,11 @@ class EQNBaseContainerTableViewCell: UITableViewCell {
|
||||
}()
|
||||
|
||||
private lazy var headerLabel: UILabel = {
|
||||
let label = EQNEdgeInsetLabel()
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.backgroundColor = AppTheme.Colors.lightBlue
|
||||
label.font = .systemFont(ofSize: 17.0, weight: .medium)
|
||||
label.textColor = .white
|
||||
label.textAlignment = .center
|
||||
label.font = .preferredFont(forTextStyle: .title2)
|
||||
label.textColor = AppTheme.shared.cardTextColor
|
||||
return label
|
||||
}()
|
||||
|
||||
@@ -80,6 +76,15 @@ class EQNBaseContainerTableViewCell: UITableViewCell {
|
||||
/// Text to display inside the header
|
||||
var headerText: String { "" }
|
||||
|
||||
override var backgroundColor: UIColor? {
|
||||
set {
|
||||
internalContainerView.backgroundColor = newValue
|
||||
}
|
||||
get {
|
||||
internalContainerView.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
@@ -93,12 +98,20 @@ class EQNBaseContainerTableViewCell: UITableViewCell {
|
||||
setupUI()
|
||||
updateUI()
|
||||
}
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
containerView.eqn_applyRoundedCorners()
|
||||
internalContainerView.eqn_applyShadowAndRoundedCorners()
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
func setupUI() {
|
||||
selectionStyle = .default
|
||||
backgroundColor = .clear
|
||||
super.backgroundColor = .clear
|
||||
|
||||
contentView.addSubview(internalContainerView)
|
||||
|
||||
@@ -127,9 +140,9 @@ class EQNBaseContainerTableViewCell: UITableViewCell {
|
||||
|
||||
if isHeaderVisible {
|
||||
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.heightAnchor.constraint(equalToConstant: 26.0).isActive = true
|
||||
headerLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
|
||||
} else {
|
||||
containerView.addSubview(emptyTopView)
|
||||
emptyTopView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// EQNBaseTableViewCell.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 07/10/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
class EQNBaseTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak var containerView: UIView!
|
||||
|
||||
// MARK: - View Lifecycle
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
clipsToBounds = true
|
||||
|
||||
// rounded corners and shadow
|
||||
containerView.eqn_applyShadowAndRoundedCorners()
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
|
||||
class EQNBlurredCloseButton: UIButton {
|
||||
@@ -18,6 +19,12 @@ class EQNBlurredCloseButton: UIButton {
|
||||
setupUI()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
setupUI()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupUI() {
|
||||
|
||||
@@ -12,18 +12,13 @@ import MapKit
|
||||
|
||||
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 MagnitudeHeight: CGFloat = 25.0
|
||||
private static let MagnitudeWidth: CGFloat = 45.0
|
||||
private static let FullViewHeight: CGFloat = MagnitudeHeight + 2*LabelHeight
|
||||
private static let FullViewWidth: CGFloat = 100.0
|
||||
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
|
||||
|
||||
// MARK: - Public
|
||||
@@ -43,11 +38,16 @@ class EQNSeismicAnnotationView: MKAnnotationView {
|
||||
get { magnitudeLabel.text }
|
||||
}
|
||||
|
||||
var magnitudeColor: UIColor {
|
||||
var magnitudeTextColor: UIColor {
|
||||
set { magnitudeLabel.textColor = newValue }
|
||||
get { magnitudeLabel.textColor }
|
||||
}
|
||||
|
||||
var magnitudeBackgroundColor: UIColor? {
|
||||
set { magnitudeView.backgroundColor = newValue }
|
||||
get { magnitudeView.backgroundColor }
|
||||
}
|
||||
|
||||
var isUserSelection: Bool = false {
|
||||
didSet {
|
||||
magnitudeView.layer.borderColor = isUserSelection ? AppTheme.Colors.red.cgColor : AppTheme.Colors.darkGray.cgColor
|
||||
@@ -80,7 +80,7 @@ class EQNSeismicAnnotationView: MKAnnotationView {
|
||||
|
||||
private lazy var magnitudeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = UIFont.systemFont(ofSize: 12, weight: .bold)
|
||||
label.font = UIFont.systemFont(ofSize: 11, weight: .bold)
|
||||
label.textAlignment = .center
|
||||
label.textColor = AppTheme.Colors.lightBlue
|
||||
return label
|
||||
@@ -107,13 +107,13 @@ class EQNSeismicAnnotationView: MKAnnotationView {
|
||||
|
||||
backgroundColor = .clear
|
||||
|
||||
if reuseIdentifier == Self.IdentifierFull {
|
||||
if reuseIdentifier == Self.identifier(for: .full) {
|
||||
frame = CGRect(x: 0, y: 0, width: Self.FullViewWidth, height: Self.FullViewHeight)
|
||||
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)
|
||||
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)
|
||||
setupCircleUI()
|
||||
}
|
||||
@@ -158,3 +158,14 @@ class EQNSeismicAnnotationView: MKAnnotationView {
|
||||
addSubview(imageView)
|
||||
}
|
||||
}
|
||||
|
||||
extension EQNSeismicAnnotationView {
|
||||
|
||||
static func identifier(for style: MapPinStyle) -> String {
|
||||
return switch style {
|
||||
case .circle: "EQNSeismicAnnotationViewCircle"
|
||||
case .light: "EQNSeismicAnnotationViewLight"
|
||||
case .full: "EQNSeismicAnnotationViewFull"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Rilevato sisma debole a" = "رصد زلزال خفيف في ميلانو %@";
|
||||
"Rilevato sisma forte a" = "رصد زلزال قوي في ميلانو %@";
|
||||
"Rilevato sisma a" = "رصد زلزال في ميلانو %@";
|
||||
"Sisma segnalato da utente a" = "أبلغ المستخدم عن الزلزال في %@";
|
||||
"Sisma segnalato da utente a" = "تم الإبلاغ عن الزلزال من قبل مستخدمي التطبيق في %@";
|
||||
"Sisma lieve segnalato da utente a" = "Mild quake reported by user at %@"; // replaced by alert_intensity_mild
|
||||
"Sisma forte segnalato da utente a" = "Strong quake reported by user at %@"; // replaced by alert_intensity_moderate
|
||||
"Sisma molto forte segnalato da utente a" = "Very strong quake reported by user at %@"; // replaced by alert_intensity_strong
|
||||
@@ -26,13 +26,14 @@
|
||||
"drawer_privacy" = "الخصوصيات";
|
||||
"tab_network" = "تنبيهات";
|
||||
"tab_manual" = "تقارير";
|
||||
"tab_official" = "الشبكات الزلزالية";
|
||||
"tab_official" = "قائمة الزلزال";
|
||||
"inapp_available_10k" = "\U200FTop 10K %lu اشتراكات لا تزال متوفرة ليتم تنبيهها في أقل من ثانية واحدة منذ اكتشاف الزلزال";
|
||||
"inapp_available_100k" = "\U200FTop 100K %lu اشتراكات لا تزال متوفرة ليتم تنبيهها في أقل من 5 ثوان منذ اكتشاف الزلزال";
|
||||
"filter_filter" = "اختر الزلازل التي تريد عرضها";
|
||||
"filter_show_area" = "إظهار كافة الزلازل ضمن دائرة نصف قطرها:";
|
||||
"filter_show_relevant" = "إظهار الزلازل ذات الصلة بموقعي فقط";
|
||||
"filter_show_all" = "عرض جميع الزلازل على مستوى العالم (M≥2)";
|
||||
"filter_show_felt" = "إظهار الزلازل التي يشعر بها المستخدمون فقط";
|
||||
"filter_minimum_magnitude" = "والحجم الأدنى:";
|
||||
"main_understood" = "مفهوم";
|
||||
"options_low_magnitude" = "الانتباه إلى أن ليست كل الشبكات الزلزالية توفر بيانات الزلازل التي تقل عن 2.0 درجة. علاوة على ذلك ، فإنك تزيد بشكل كبير من نقل البيانات واستخدام البطارية بسبب الإخطارات. ما لم يتم تصنيع جهازك حديثا ، ستلاحظ أيضا تباطؤا عاما.";
|
||||
@@ -44,6 +45,7 @@
|
||||
"filter_empty_area" = "لا توجد زلازل خلال الـ 24 ساعة الماضية وفقًا للمرشحات";
|
||||
"filter_empty_relevant" = "لم تكن هناك زلازل ذات صلة بموقعك خلال الـ 24 ساعة الماضية";
|
||||
"filter_nolocation" = "هذا الخيار غير متاح لأن موقعك غير معروف";
|
||||
"official_filter_changed" = "تم تغيير مرشحات القائمة لإظهار الزلزال الذي تم الإبلاغ عنه";
|
||||
"official_provider" = "المصدر: %@";
|
||||
"share_hashtag" = "#زلزال أرضي";
|
||||
"share_notified" = "قررت من خلال التطبيق منبه الزلازل. قم بتنزيل التطبيق من https://sismo.app/download/ لاستلام تنبيهات في الوقت الفعلي حول #earthquake @SismoDetector";
|
||||
@@ -52,6 +54,7 @@
|
||||
"filter_area" = "الزلازل المعروضة: في دائرة نصف قطرها";
|
||||
"filter_relevant" = "الزلازل المعروضة: ذات صلة";
|
||||
"filter_all" = "الزلازل المعروضة: الكل";
|
||||
"filter_felt" = "الزلازل المعروضة: شعرت";
|
||||
"liveview_unknown_location" = "موقفك غير معروف. تمكين موقع الهاتف الذكي من تكوين الهاتف الذكي";
|
||||
"map_number" = "تم الكشف عن زلزال بواسطة %@ الهواتف الذكية";
|
||||
"permission_location_no" = "لقد اخترت منع التطبيق من قراءة موقع الجهاز. لن تستلم تنبيهات وإشعارات في الوقت الفعلي";
|
||||
@@ -74,6 +77,7 @@
|
||||
"status_cancel" = "ألغي";
|
||||
"options_alarms" = "تنبيه بوقت فعلي";
|
||||
"options_notification_enable_alarm" = "قم بتفعيل الإنذار عند اكتشاف زلزال في الوقت الفعلي بواسطة شبكة الهواتف الذكية";
|
||||
"options_notification_disable_sound" = "قم بتعطيل صوت المنبه إذا كان الاهتزاز في موقعك خفيفًا";
|
||||
"options_notification_official" = "إخطارات الشبكة الزلزالية";
|
||||
"options_notification_enable_official" = "تلقي إشعارات بشأن الزلازل التي اكتشفتها شبكات الزلازل الوطنية والدولية";
|
||||
"options_official_type" = "مرشح الإشعارات";
|
||||
@@ -84,7 +88,7 @@
|
||||
"options_official_maxdist" = "المسافة القصو";
|
||||
"options_notification_manual" = "إشعارات تقرير المستخدم";
|
||||
"options_notification_enable_manual" = "استلم الإخطارات للزلازل التي أبلغ عنها يدويا من قبل المستخدمين";
|
||||
"main_areacheck_message" = "في الوقت الحالي ، يوجد في منطقتك %s هواتف ذكية تراقب الزلازل في الوقت الفعلي. قدرة شبكة الهاتف الذكي على اكتشاف الزلازل في الوقت الفعلي هي%@. لتحسين اكتشاف الزلازل في منطقتك ، شارك التطبيق مع عائلتك وأصدقائك ، شكرًا!";
|
||||
"main_areacheck_message" = "في الوقت الحالي ، يوجد في منطقتك %@ هواتف ذكية تراقب الزلازل في الوقت الفعلي. لتحسين اكتشاف الزلازل في منطقتك ، شارك التطبيق مع عائلتك وأصدقائك ، شكرًا!";
|
||||
"main_version" = "نموذج";
|
||||
"app_name" = "منبه الزلازل";
|
||||
"official_depth" = "العمق:";
|
||||
@@ -104,7 +108,7 @@
|
||||
"permission_location_no_background_solve" = "حل المشكلة";
|
||||
"main_share_app" = "شارك التطبيق";
|
||||
"main_vote" = "قيّم التطبيق";
|
||||
"main_twitter_see" = "التبيين في تويتر";
|
||||
"main_twitter_see" = "عرض على X";
|
||||
"map_smartphone_magnitude" = "سيتم تقدير قوة الزلزال بواسطة الشبكة الزلزالية الوطنية وسيظهر في علامة تبويب الشبكات الزلزالية في التطبيق";
|
||||
"main_alerttest" = "تنبيه اختياري";
|
||||
"main_simulator" = "محاكي";
|
||||
@@ -141,6 +145,8 @@
|
||||
"mercalli_XI" = "\U200FXI - تدمير مراكز حضرية بأكملها ، العديد من الضحايا ، شقوق في الأرض وانهيارات أرضيةlandslides";
|
||||
"mercalli_XII" = "\U200FXII - اضطراب التربة ، إزاحة قشرة الأرض";
|
||||
"mercalli_intensity" = "شدة %@";
|
||||
"shakemap" = "خريطة اهتزازية";
|
||||
"shakemap_description" = "خريطة الكثافة بناءً على التقارير المحسوسة";
|
||||
|
||||
// values
|
||||
"official_magnitude_value_00" = "درجة >= 0.0";
|
||||
@@ -215,7 +221,7 @@
|
||||
"attention" = "انتباه";
|
||||
"official_no_country_selected" = "لم تقم بتحديد أي بلد";
|
||||
"report" = "التقارير";
|
||||
"purchase_pro_restore" = "يعيد";
|
||||
"purchase_pro_restore" = "استعادة الاشتراكات";
|
||||
"purchase_pro_restore_alert_title" = "اكتملت الاستعادة";
|
||||
"purchase_pro_restore_alert_message" = "لقد استعدت المنتج الذي اشتريته";
|
||||
"purchase_pro_no_subscriptions_alert_message" = "لم يتم العثور على شراء للاستعادة. تأكد من تسجيل الدخول إلى الحساب الذي تم الشراء به.";
|
||||
@@ -230,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "شهريا";
|
||||
"subscription_plan_yearly" = "سنوي";
|
||||
"subscription_plan_perpetual" = "حياة";
|
||||
"tap_to_open" = "انقر للتكبير";
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
"Rilevato sisma debole a" = "Ήπιο σεισμό που ανιχνεύεται στο %@";
|
||||
"Rilevato sisma forte a" = "Ισχυρός σεισμός που ανιχνεύεται στο %@";
|
||||
"Rilevato sisma a" = "Ο σεισμός ανιχνεύθηκε στο %@";
|
||||
"Sisma lieve segnalato da utente a" = "Ήπιο σεισμό που αναφέρθηκε από έναν χρήστη στο %@";
|
||||
"Sisma forte segnalato da utente a" = "Ισχυρός σεισμός που αναφέρθηκε από έναν χρήστη στο %@";
|
||||
"Sisma molto forte segnalato da utente a" = "Πολύ ισχυρός σεισμός που αναφέρθηκε από έναν χρήστη στο %@";
|
||||
"Sisma segnalato da utente a" = "Σεισμός που αναφέρθηκε από χρήστες της εφαρμογής στο %@";
|
||||
"Sisma lieve segnalato da utente a" = "Ήπιο σεισμό που αναφέρθηκε από έναν χρήστη στο %@"; // not used, to be removed
|
||||
"Sisma forte segnalato da utente a" = "Ισχυρός σεισμός που αναφέρθηκε από έναν χρήστη στο %@"; // not used, to be removed
|
||||
"Sisma molto forte segnalato da utente a" = "Πολύ ισχυρός σεισμός που αναφέρθηκε από έναν χρήστη στο %@"; // not used, to be removed
|
||||
"Sisma rilevato a" = "Ο σεισμός ανιχνεύθηκε στο %@";
|
||||
"alert_intensity_no_shaking" = "Δεν γίνεται αισθητό στην τοποθεσία σας";
|
||||
"alert_intensity_mild" = "Να περιμένετε ήπιο ή καθόλου κούνημα";
|
||||
@@ -25,13 +26,14 @@
|
||||
"drawer_privacy" = "Ιδιωτικό απόρρητο";
|
||||
"tab_network" = "ΕΙΔΟΠΟΙΗΣΕΙΣ";
|
||||
"tab_manual" = "ΑΝΑΦΟΡΕΣ";
|
||||
"tab_official" = "ΣΕΙΣΜΙΚΑ ΔΙΚΤΥΑ";
|
||||
"tab_official" = "Λιστα σεισμων";
|
||||
"inapp_available_10k" = "Top 10K: %lu εγγραφές είναι ακόμη διαθέσιμες για ειδοποίηση σε λιγότερο από 1 δευτερόλεπτο από την ανίχνευση του σεισμού";
|
||||
"inapp_available_100k" = "Top 100K: %lu εγγραφή είναι ακόμη διαθέσιμη για ειδοποίηση σε λιγότερο από 5 δευτερόλεπτα από την ανίχνευση του σεισμού";
|
||||
"filter_filter" = "Επιλέξτε ποιους σεισμούς θέλετε να δείτε";
|
||||
"filter_show_area" = "Εμφάνιση όλων των σεισμών σε ακτίνα:";
|
||||
"filter_show_relevant" = "Εμφάνιση μόνο των σχετικών σεισμών σε σχέση με την τοποθεσία μου";
|
||||
"filter_show_all" = "Εμφάνιση όλων των σεισμών παγκοσμίως (M≥2)";
|
||||
"filter_show_felt" = "Εμφάνιση μόνο των σεισμών που αισθάνθηκαν οι χρήστες";
|
||||
"filter_minimum_magnitude" = "και ελάχιστο μέγεθος:";
|
||||
"main_understood" = "Κατάλαβα";
|
||||
"options_low_magnitude" = "Λάβε υπόψη ότι δεν παρέχουν όλα τα σεισμικά δίκτυα δεδομένα για σεισμούς με μέγεθος κάτω από 2.0. Επίσης, αυξάνεις σημαντικά την μεταφορά δεδομένων και την χρήση μπαταρίας λόγω κοινοποιήσεων. Αν η συσκευή σου δεν είναι πρόσφατης κατασκευής, θα παρατηρήσεις επίσης μια γενική επιβράδυνση.";
|
||||
@@ -43,6 +45,7 @@
|
||||
"filter_empty_area" = "Δεν υπάρχουν σεισμοί τις τελευταίες 24 ώρες σύμφωνα με τα φίλτρα";
|
||||
"filter_empty_relevant" = "Δεν υπάρχουν σχετικοί σεισμοί τις τελευταίες 24 ώρες σε σχέση με την τοποθεσία σας";
|
||||
"filter_nolocation" = "Αυτή η επιλογή δεν είναι διαθέσιμη επειδή η τοποθεσία σας είναι άγνωστη";
|
||||
"official_filter_changed" = "Τα φίλτρα της λίστας έχουν αλλάξει για να εμφανιστεί ο ειδοποιημένος σεισμός";
|
||||
"official_provider" = "Πηγή: %@";
|
||||
"share_hashtag" = "#σεισμός";
|
||||
"share_notified" = "Έχει αναφερθεί μέσω της εφαρμογής Ανιχνευτής Σεισμών. Κατέβασε την εφαρμογή από το https://sismo.app/download/ για να λαμβάνεις σε πραγματικό χρόνο ειδοποιήσεις #σεισμού @SismoDetector";
|
||||
@@ -51,6 +54,7 @@
|
||||
"filter_area" = "Εμφανίζονται σεισμοί: στην ακτίνα";
|
||||
"filter_relevant" = "Εμφανίζονται σεισμοί: σχετικοί";
|
||||
"filter_all" = "Εμφανίζονται σεισμοί: όλοι (M≥2)";
|
||||
"filter_felt" = "Εμφανίζονται σεισμοί: αισθητοί";
|
||||
"liveview_unknown_location" = "Η θέση σου είναι άγνωστη. Ενεργοποίησε την τοποθεσία του smartphone από την διαμόρφωση του smartphone";
|
||||
"map_number" = "Ανιχνεύθηκε δόνηση από %@ smartphone";
|
||||
"permission_location_no" = "Έχεις επιλέξει να αποτρέπεις την εφαρμογή από την ανάγνωση της τοποθεσίας της συσκευής. ΔΕΝ θα λαμβάνεις κοινοποιήσεις και ειδοποιήσεις σε πραγματικό χρόνο";
|
||||
@@ -73,6 +77,7 @@
|
||||
"status_cancel" = "Διαγραφή";
|
||||
"options_alarms" = "Ειδοποίηση σε πραγματικό χρόνο";
|
||||
"options_notification_enable_alarm" = "Να ακούγεται συναγερμός όταν ανιχνεύεται σεισμός σε πραγματικό χρόνο από το δίκτυο smartphone";
|
||||
"options_notification_disable_sound" = "Απενεργοποιήστε τον ήχο συναγερμού εάν το κούνημα στην τοποθεσία σας είναι ήπιο";
|
||||
"options_notification_official" = "Κοινοποιήσεις σεισμικών δικτύων";
|
||||
"options_notification_enable_official" = "Λήψη ειδοποιήσεων για τους σεισμούς που εντοπίζονται από τα εθνικά και διεθνή σεισμικά δίκτυα";
|
||||
"options_official_type" = "Φίλτρο ειδοποιήσεων";
|
||||
@@ -103,7 +108,7 @@
|
||||
"permission_location_no_background_solve" = "Διόρθωση";
|
||||
"main_share_app" = "Μοιράσου";
|
||||
"main_vote" = "Ψήφισε την Εφαρμογή";
|
||||
"main_twitter_see" = "Twitter";
|
||||
"main_twitter_see" = "X";
|
||||
"map_smartphone_magnitude" = "Το μέγεθος θα εκτιμηθεί από το εθνικό σεισμικό δίκτυο και θα εμφανιστεί στην οθόνη Σεισμικών Δικτύων της εφαρμογής";
|
||||
"main_alerttest" = "Τεστ ειδοποίησης";
|
||||
"main_simulator" = "Προσομοιωτής";
|
||||
@@ -140,6 +145,8 @@
|
||||
"mercalli_XI" = "XI - Καταστροφές ολόκληρων αστικών κέντρων, πολλά θύματα, ρωγμές στο έδαφος και κατολισθήσεις";
|
||||
"mercalli_XII" = "XII - Διάσπαση του εδάφους, μετατόπιση του φλοιού της γης";
|
||||
"mercalli_intensity" = "Ένταση %@";
|
||||
"shakemap" = "Χάρτης έντασης";
|
||||
"shakemap_description" = "Χάρτης έντασης με βάση τις αναφορές χρηστών";
|
||||
|
||||
// values
|
||||
"official_magnitude_value_00" = "Μέγεθος >= 0.0";
|
||||
@@ -214,7 +221,7 @@
|
||||
"attention" = "Προσοχή";
|
||||
"official_no_country_selected" = "Δεν έχετε επιλέξει καμία χώρα";
|
||||
"report" = "Αναφορά";
|
||||
"purchase_pro_restore" = "Επαναφέρω";
|
||||
"purchase_pro_restore" = "Επαναφορά συνδρομών";
|
||||
"purchase_pro_restore_alert_title" = "Επαναφέρω ολοκληρώθηκε";
|
||||
"purchase_pro_restore_alert_message" = "Έχετε αποκαταστήσει το προϊόν που αγοράσατε";
|
||||
"purchase_pro_no_subscriptions_alert_message" = "Δεν βρέθηκε αγορά αγοράς. Βεβαιωθείτε ότι έχετε συνδεθεί στο λογαριασμό, η αγορά έγινε με.";
|
||||
@@ -229,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Μηνιαίο";
|
||||
"subscription_plan_yearly" = "Ετήσιο";
|
||||
"subscription_plan_perpetual" = "Διάρκεια Ζωής";
|
||||
"tap_to_open" = "Πατήστε για μεγέθυνση";
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
"Rilevato sisma debole a" = "Mild quake detected at %@";
|
||||
"Rilevato sisma forte a" = "Strong quake detected at %@";
|
||||
"Rilevato sisma a" = "Quake detected at %@";
|
||||
"Sisma lieve segnalato da utente a" = "Mild quake reported by user at %@";
|
||||
"Sisma forte segnalato da utente a" = "Strong quake reported by user at %@";
|
||||
"Sisma molto forte segnalato da utente a" = "Very strong quake reported by user at %@";
|
||||
"Sisma segnalato da utente a" = "Earthquake reported by the app users at %@";
|
||||
"Sisma lieve segnalato da utente a" = "Mild quake reported by user at %@"; // not used, to be removed
|
||||
"Sisma forte segnalato da utente a" = "Strong quake reported by user at %@"; // not used, to be removed
|
||||
"Sisma molto forte segnalato da utente a" = "Very strong quake reported by user at %@"; // not used, to be removed
|
||||
"Sisma rilevato a" = "Quake detected at %@";
|
||||
"alert_intensity_no_shaking" = "Not felt at your location";
|
||||
"alert_intensity_mild" = "Expect mild or no shaking";
|
||||
@@ -25,13 +26,14 @@
|
||||
"drawer_privacy" = "Privacy";
|
||||
"tab_network" = "ALERTS";
|
||||
"tab_manual" = "REPORTS";
|
||||
"tab_official" = "SEISMIC NETWORKS";
|
||||
"tab_official" = "Quake list";
|
||||
"inapp_available_10k" = "Top 10K: %lu subscriptions still available to be alerted in less than 1 second since the detection of the quake";
|
||||
"inapp_available_100k" = "Top 100K: %lu subscriptions still available to be alerted in less than 5 seconds since the detection of the quake";
|
||||
"filter_filter" = "Choose which earthquakes you want to view";
|
||||
"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_all" = "Show all earthquakes globally (M≥2)";
|
||||
"filter_show_felt" = "Show only the earthquakes felt by the users";
|
||||
"filter_minimum_magnitude" = "and minimum magnitude:";
|
||||
"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.";
|
||||
@@ -43,14 +45,16 @@
|
||||
"filter_empty_area" = "Nessun sisma nelle ultime 24 ore in base ai filtri impostati";
|
||||
"filter_empty_relevant" = "No relevant earthquakes in the last 24 hours with respect to your location";
|
||||
"filter_nolocation" = "This option is not available because your location is unknown";
|
||||
"official_filter_changed" = "List filters have been changed in order to show the notified earthquake";
|
||||
"official_provider" = "Source: %@";
|
||||
"share_hashtag" = "#earthquake";
|
||||
"share_notified" = "Reported through the app Earthquake Network. Download the app from https://sismo.app/download/ to receive real time alerts of #earthquake @SismoDetector";
|
||||
"manual_sure" = "Do you really want to notify an earthquake?";
|
||||
"manual_sure" = "Do you really want to report an earthquake?";
|
||||
"manual_yes" = "Yes";
|
||||
"filter_area" = "Quakes shown: in the radius";
|
||||
"filter_relevant" = "Quakes shown: relevant";
|
||||
"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";
|
||||
"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";
|
||||
@@ -73,6 +77,7 @@
|
||||
"status_cancel" = "Cancel";
|
||||
"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_disable_sound" = "Disable the alarm sound if the shaking at your location is mild";
|
||||
"options_notification_official" = "Seismic network notifications";
|
||||
"options_notification_enable_official" = "Receive notifications for the earthquakes detected by the national and international seismic networks";
|
||||
"options_official_type" = "Notification filter";
|
||||
@@ -103,7 +108,7 @@
|
||||
"permission_location_no_background_solve" = "Solve";
|
||||
"main_share_app" = "Share 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";
|
||||
"main_alerttest" = "Test alert";
|
||||
"main_simulator" = "Simulator";
|
||||
@@ -140,6 +145,8 @@
|
||||
"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_intensity" = "Intensity %@";
|
||||
"shakemap" = "Shakemap";
|
||||
"shakemap_description" = "Intensity map based on user reports";
|
||||
|
||||
// values
|
||||
"official_magnitude_value_00" = "Magnitude >= 0.0";
|
||||
@@ -214,7 +221,7 @@
|
||||
"attention" = "Attention";
|
||||
"official_no_country_selected" = "You have not selected any country";
|
||||
"report" = "Report";
|
||||
"purchase_pro_restore" = "Restore";
|
||||
"purchase_pro_restore" = "Restore subscriptions";
|
||||
"purchase_pro_restore_alert_title" = "Restore completed";
|
||||
"purchase_pro_restore_alert_message" = "You have restored the product you purchased";
|
||||
"purchase_pro_no_subscriptions_alert_message" = "No purchase was found to restore. Make sure you are logged into the account the purchase was made with.";
|
||||
@@ -229,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Monthly";
|
||||
"subscription_plan_yearly" = "Annual";
|
||||
"subscription_plan_perpetual" = "Lifetime";
|
||||
"tap_to_open" = "Tap to open";
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
"Rilevato sisma debole a" = "Leve sismo detectado en %@";
|
||||
"Rilevato sisma forte a" = "Fuerte sismo detectado en %@";
|
||||
"Rilevato sisma a" = "Sismo detectado e %@";
|
||||
"Sisma lieve segnalato da utente a" = "Sismo leve informado por un usuario en %@";
|
||||
"Sisma forte segnalato da utente a" = "Fuerte sismo reportado por un usuario en %@";
|
||||
"Sisma molto forte segnalato da utente a" = "Sismo muy fuerte reportado por un usuario en %@";
|
||||
"Sisma segnalato da utente a" = "Sismo reportado por los usuarios de la aplicación en %@";
|
||||
"Sisma lieve segnalato da utente a" = "Sismo leve informado por un usuario en %@"; // not used, to be removed
|
||||
"Sisma forte segnalato da utente a" = "Fuerte sismo reportado por un usuario en %@"; // not used, to be removed
|
||||
"Sisma molto forte segnalato da utente a" = "Sismo muy fuerte reportado por un usuario en %@"; // not used, to be removed
|
||||
"Sisma rilevato a" = "Sismo detectado en %@";
|
||||
"alert_intensity_no_shaking" = "No se siente en tu ubicación";
|
||||
"alert_intensity_mild" = "Espera movimiento leve o nulo";
|
||||
@@ -25,13 +26,14 @@
|
||||
"drawer_privacy" = "Privacy";
|
||||
"tab_network" = "ALERTAS";
|
||||
"tab_manual" = "REPORTES";
|
||||
"tab_official" = "REDES SÍSMICAS";
|
||||
"tab_official" = "Lista sismos";
|
||||
"inapp_available_10k" = "Top 10K: %lu suscripciones aún disponibles para recibir la alerta en menos de 1 segundo a partir de la detección del sismo";
|
||||
"inapp_available_100k" = "Top 100K: %lu suscripciones aún disponibles para recibir la alerta en menos de 5 segundos a partir de la detección del sismo";
|
||||
"filter_filter" = "Elige qué sismos quieres ver";
|
||||
"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_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:";
|
||||
"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.";
|
||||
@@ -43,6 +45,7 @@
|
||||
"filter_empty_area" = "No hay sismos en las últimas 24 horas según los filtros";
|
||||
"filter_empty_relevant" = "No hay sismos relevantes en las últimas 24 horas con respecto a tu ubicación";
|
||||
"filter_nolocation" = "Esta opción no está disponible porque tu ubicación es desconocida";
|
||||
"official_filter_changed" = "Los filtros de lista se han cambiado para mostrar el sismo notificado";
|
||||
"official_provider" = "Fuente: %@";
|
||||
"share_hashtag" = "#sismo";
|
||||
"share_notified" = "Reportado a través de la app Sismo Detector. Descarga la app desde https://sismo.app/download/ para recibir alertas de #sismo en tiempo real @SismoDetector";
|
||||
@@ -51,6 +54,7 @@
|
||||
"filter_area" = "Sismos mostrados: en el radio";
|
||||
"filter_relevant" = "Sismos mostrados: relevantes";
|
||||
"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";
|
||||
"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";
|
||||
@@ -66,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_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_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_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";
|
||||
"status_cancel" = "Clara";
|
||||
"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_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_enable_official" = "Recibe las notificaciones de los sismos detectados por las redes sísmicas nacionales e internacionales";
|
||||
"options_official_type" = "Filtro de notificaciones";
|
||||
@@ -103,7 +108,7 @@
|
||||
"permission_location_no_background_solve" = "Corregir";
|
||||
"main_share_app" = "Comparte 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";
|
||||
"main_alerttest" = "Prueba alerta";
|
||||
"main_simulator" = "Simulador";
|
||||
@@ -140,6 +145,8 @@
|
||||
"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_intensity" = "Intensidad %@";
|
||||
"shakemap" = "Mapa intensidad";
|
||||
"shakemap_description" = "Mapa de intensidad basado en informes de usuarios";
|
||||
|
||||
// values
|
||||
"official_magnitude_value_00" = "Magnitud >= 0.0";
|
||||
@@ -214,7 +221,7 @@
|
||||
"attention" = "Atención";
|
||||
"official_no_country_selected" = "No has seleccionado ningún país";
|
||||
"report" = "Informe";
|
||||
"purchase_pro_restore" = "Restaurar";
|
||||
"purchase_pro_restore" = "Restablecer suscripciones";
|
||||
"purchase_pro_restore_alert_title" = "Restauración completada";
|
||||
"purchase_pro_restore_alert_message" = "Has restaurado el producto que compraste.";
|
||||
"purchase_pro_no_subscriptions_alert_message" = "No se encontró ninguna compra para restaurar. Asegúrese de haber iniciado sesión en la cuenta con la que se realizó la compra.";
|
||||
@@ -229,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Mensual";
|
||||
"subscription_plan_yearly" = "Anual";
|
||||
"subscription_plan_perpetual" = "Para siempre";
|
||||
"tap_to_open" = "Toque para ampliar";
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
"Rilevato sisma debole a" = "Séisme léger détecté à %@";
|
||||
"Rilevato sisma forte a" = "Fort séisme détecté à %@";
|
||||
"Rilevato sisma a" = "Séisme détecté à %@";
|
||||
"Sisma lieve segnalato da utente a" = "Séisme léger signalé par un utilisateur à %@";
|
||||
"Sisma forte segnalato da utente a" = "Fort séisme signalé par un utilisateur à %@";
|
||||
"Sisma molto forte segnalato da utente a" = "Très fort séisme signalé par un utilisateur à %@";
|
||||
"Sisma segnalato da utente a" = "Séisme signalé par les utilisateurs de l'application à %@";
|
||||
"Sisma lieve segnalato da utente a" = "Séisme léger signalé par un utilisateur à %@"; // not used, to be removed
|
||||
"Sisma forte segnalato da utente a" = "Fort séisme signalé par un utilisateur à %@"; // not used, to be removed
|
||||
"Sisma molto forte segnalato da utente a" = "Très fort séisme signalé par un utilisateur à %@"; // not used, to be removed
|
||||
"Sisma rilevato a" = "Séisme détecté à %@";
|
||||
"alert_intensity_no_shaking" = "Pas ressenti à votre emplacement";
|
||||
"alert_intensity_mild" = "Attendez-vous à des secousses légères ou nulles";
|
||||
@@ -25,13 +26,14 @@
|
||||
"drawer_privacy" = "Confidentialité";
|
||||
"tab_network" = "ALERTES";
|
||||
"tab_manual" = "SIGNALEMENTS";
|
||||
"tab_official" = "RÉSEAUX SISMIQUES";
|
||||
"tab_official" = "Liste des séisme";
|
||||
"inapp_available_10k" = "Top 10K : %lu abonnements encore disponibles pour être alerté en moins de 1 seconde à partir de la détection du séisme";
|
||||
"inapp_available_100k" = "Top 100K : %lu abonnements encore disponibles pour être alerté en moins de 5 secondes à partir de la détection du séisme";
|
||||
"filter_filter" = "Choisissez les tremblements de terre que vous souhaitez visualiser";
|
||||
"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_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 :";
|
||||
"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.";
|
||||
@@ -43,6 +45,7 @@
|
||||
"filter_empty_area" = "Aucun tremblement de terre au cours des dernières 24 heures selon les filtres";
|
||||
"filter_empty_relevant" = "Aucun tremblement de terre pertinent au cours des dernières 24 heures par rapport à votre emplacement";
|
||||
"filter_nolocation" = "EsCette option n'est pas disponible car votre emplacement est inconnu";
|
||||
"official_filter_changed" = "Les filtres de liste ont été modifiés afin d\'afficher le séisme signalé";
|
||||
"official_provider" = "Source : %@";
|
||||
"share_hashtag" = "#séisme";
|
||||
"share_notified" = "Signalé via l'app Détecteur de Séisme. Téléchargez l'app depuis https://sismo.app/download/ pour recevoir des alertes de #séisme en temps réel @SismoDetector";
|
||||
@@ -51,6 +54,7 @@
|
||||
"filter_area" = "Séismes affichés : dans le rayon";
|
||||
"filter_relevant" = "Séismes affichés : pertinents";
|
||||
"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";
|
||||
"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";
|
||||
@@ -73,6 +77,7 @@
|
||||
"status_cancel" = "Supprimer";
|
||||
"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_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_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";
|
||||
@@ -103,7 +108,7 @@
|
||||
"permission_location_no_background_solve" = "Corriger";
|
||||
"main_share_app" = "Partager 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";
|
||||
"main_alerttest" = "Test alerte";
|
||||
"main_simulator" = "Simulateur";
|
||||
@@ -140,6 +145,8 @@
|
||||
"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_intensity" = "Intensité %@";
|
||||
"shakemap" = "Carte d'intensité";
|
||||
"shakemap_description" = "Carte d'intensité basée sur les rapports ressentis";
|
||||
|
||||
// values
|
||||
"official_magnitude_value_00" = "Magnitude >= 0.0";
|
||||
@@ -214,7 +221,7 @@
|
||||
"attention" = "Attention";
|
||||
"official_no_country_selected" = "Vous n'avez sélectionné aucun pays";
|
||||
"report" = "Signaler";
|
||||
"purchase_pro_restore" = "Restaurer";
|
||||
"purchase_pro_restore" = "Restaurer les abonnements";
|
||||
"purchase_pro_restore_alert_title" = "Restauration terminée";
|
||||
"purchase_pro_restore_alert_message" = "Vous avez restauré le produit que vous avez acheté";
|
||||
"purchase_pro_no_subscriptions_alert_message" = "Aucun achat à restaurer n'a été trouvé. Assurez-vous que vous êtes connecté au compte avec lequel l'achat a été effectué.";
|
||||
@@ -229,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Mensuel";
|
||||
"subscription_plan_yearly" = "Annuel";
|
||||
"subscription_plan_perpetual" = "Pour toujours";
|
||||
"tap_to_open" = "Appuyez pour agrandir";
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
"Rilevato sisma debole a" = "Blagi potres otkriven u %@";
|
||||
"Rilevato sisma forte a" = "Snažan potres otkriven u %@";
|
||||
"Rilevato sisma a" = "Potres otkriven u %@";
|
||||
"Sisma lieve segnalato da utente a" = "Blagi potres prijavio korisnik u %@";
|
||||
"Sisma forte segnalato da utente a" = "Snažan potres prijavio korisnik u %@";
|
||||
"Sisma molto forte segnalato da utente a" = "Vrlo jak potres prijavio korisnik u %@";
|
||||
"Sisma segnalato da utente a" = "Korisnici aplikacije prijavili potres u %@";
|
||||
"Sisma lieve segnalato da utente a" = "Blagi potres prijavio korisnik u %@"; // not used, to be removed
|
||||
"Sisma forte segnalato da utente a" = "Snažan potres prijavio korisnik u %@"; // not used, to be removed
|
||||
"Sisma molto forte segnalato da utente a" = "Vrlo jak potres prijavio korisnik u %@"; // not used, to be removed
|
||||
"Sisma rilevato a" = "Potres otkriven u %@";
|
||||
"alert_intensity_no_shaking" = "Ne osjeća se na vašoj lokaciji";
|
||||
"alert_intensity_mild" = "Očekujte blago podrhtavanje ili ga nema";
|
||||
@@ -25,13 +26,14 @@
|
||||
"drawer_privacy" = "Privatnost";
|
||||
"tab_network" = "UPOZORENJA";
|
||||
"tab_manual" = "PRIJAVE";
|
||||
"tab_official" = "SEIZMOLOŠKE MREŽE";
|
||||
"tab_official" = "Popis potresa";
|
||||
"inapp_available_10k" = "Top 10K: Dostupan je sljedeći broj pretplata za primanje upozorenja za manje od 1 sekunde od otkrivanja potresa: %lu";
|
||||
"inapp_available_100k" = "Top 100K: Dostupan je sljedeći broj pretplata za primanje upozorenja za manje od 1 sekunde od otkrivanja potresa: %lu";
|
||||
"filter_filter" = "Odaberite koje potrese želite vidjeti";
|
||||
"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_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:";
|
||||
"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.";
|
||||
@@ -43,14 +45,16 @@
|
||||
"filter_empty_area" = "Nema potresa u posljednja 24 sata prema filtrima";
|
||||
"filter_empty_relevant" = "Nema relevantnih potresa u posljednja 24 sata u odnosu na vašu lokaciju";
|
||||
"filter_nolocation" = "Ova opcija nije dostupna jer je vaša lokacija nepoznata";
|
||||
"official_filter_changed" = "Filtri popisa su promijenjeni kako bi se prikazao prijavljeni potres";
|
||||
"official_provider" = "Izvor: %@";
|
||||
"share_hashtag" = "#potres";
|
||||
"share_notified" = "Prijavljen putem aplikacije Detektor Potresa. Preuzmite aplikaciju na https://sismo.app/download/ da biste u stvarnom vremenu primali upozorenja kada se dogodi #potres @DetektorPotresa";
|
||||
"manual_sure" = "Želite li zaista poslati obavijest o potresu?";
|
||||
"manual_sure" = "Želite li stvarno poslati izvješće o potresu?";
|
||||
"manual_yes" = "Da";
|
||||
"filter_area" = "Prikazani potresi: u polumjeru";
|
||||
"filter_relevant" = "Prikazani potresi: relevantni";
|
||||
"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";
|
||||
"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";
|
||||
@@ -73,6 +77,7 @@
|
||||
"status_cancel" = "Poništi";
|
||||
"options_alarms" = "Upozorenje u stvarnom vremenu";
|
||||
"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_enable_official" = "Primajte obavijesti o potresima koje su otkrile nacionalne i međunarodne seizmičke mreže";
|
||||
"options_official_type" = "Filter obavijesti";
|
||||
@@ -140,6 +145,8 @@
|
||||
"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_intensity" = "Intenzitet %@";
|
||||
"shakemap" = "Mapa intenziteta";
|
||||
"shakemap_description" = "Karta intenziteta na temelju izvješća o filu";
|
||||
|
||||
// values
|
||||
"official_magnitude_value_00" = "Jačina >= 0.0";
|
||||
@@ -214,7 +221,7 @@
|
||||
"attention" = "Pažnja";
|
||||
"official_no_country_selected" = "Niste odabrali nijednu zemlju";
|
||||
"report" = "Izvješće";
|
||||
"purchase_pro_restore" = "Vratiti";
|
||||
"purchase_pro_restore" = "Obnovi pretplate";
|
||||
"purchase_pro_restore_alert_title" = "Vraćanje dovršeno";
|
||||
"purchase_pro_restore_alert_message" = "Obnovili ste proizvod koji ste kupili";
|
||||
"purchase_pro_no_subscriptions_alert_message" = "Nije pronađena nijedna kupnja za vraćanje. Obavezno se prijavite na račun s kojim je obavljena kupnja.";
|
||||
@@ -229,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Mjesečno";
|
||||
"subscription_plan_yearly" = "Godišnji";
|
||||
"subscription_plan_perpetual" = "Zauvijek";
|
||||
"tap_to_open" = "Dodirnite za uvećanje";
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
"Rilevato sisma debole a" = "Gempa ringan terdeteksi di %@";
|
||||
"Rilevato sisma forte a" = "Gempa kuat terdeteksi di %@";
|
||||
"Rilevato sisma a" = "Gempa terdeteksi di %@";
|
||||
"Sisma lieve segnalato da utente a" = "Gempa ringan dilaporkan oleh pengguna di %@";
|
||||
"Sisma forte segnalato da utente a" = "Gempa kuat dilaporkan oleh pengguna di %@";
|
||||
"Sisma molto forte segnalato da utente a" = "Gempa sangat kuat dilaporkan oleh pengguna di %@";
|
||||
"Sisma segnalato da utente a" = "Gempa bumi yang dilaporkan oleh pengguna aplikasi di %@";
|
||||
"Sisma lieve segnalato da utente a" = "Gempa ringan dilaporkan oleh pengguna di %@"; // not used, to be removed
|
||||
"Sisma forte segnalato da utente a" = "Gempa kuat dilaporkan oleh pengguna di %@"; // not used, to be removed
|
||||
"Sisma molto forte segnalato da utente a" = "Gempa sangat kuat dilaporkan oleh pengguna di %@"; // not used, to be removed
|
||||
"Sisma rilevato a" = "Gempa terdeteksi di %@";
|
||||
"alert_intensity_no_shaking" = "Tidak terasa di lokasi Anda";
|
||||
"alert_intensity_mild" = "Harapkan ringan atau tidak ada guncangan";
|
||||
@@ -25,13 +26,14 @@
|
||||
"drawer_privacy" = "Privasi";
|
||||
"tab_network" = "PERINGATAN";
|
||||
"tab_manual" = "LAPORAN";
|
||||
"tab_official" = "JARINGAN SEISMIK";
|
||||
"tab_official" = "Daftar gempa";
|
||||
"inapp_available_10k" = "Top 10K: %lu langganan masih tersedia untuk mendapat peringatan dalam waktu kurang dari 1 detik sejak deteksi gempa";
|
||||
"inapp_available_100k" = "Top 100K: %lu langganan masih tersedia untuk mendapat peringatan dalam waktu kurang dari 5 detik sejak deteksi gempa";
|
||||
"filter_filter" = "Pilih gempa mana yang ingin Anda lihat";
|
||||
"filter_show_area" = "Tampilkan semua gempa dalam radius:";
|
||||
"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_felt" = "Hanya menampilkan gempa bumi yang dirasakan oleh pengguna";
|
||||
"filter_minimum_magnitude" = "dan besaran minimum:";
|
||||
"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.";
|
||||
@@ -43,6 +45,7 @@
|
||||
"filter_empty_area" = "Tidak ada gempa bumi dalam 24 jam terakhir menurut filter";
|
||||
"filter_empty_relevant" = "Tidak ada gempa bumi yang relevan dalam 24 jam terakhir sehubungan dengan lokasi Anda";
|
||||
"filter_nolocation" = "Opsi ini tidak tersedia karena lokasi Anda tidak diketahui";
|
||||
"official_filter_changed" = "Filter daftar telah diubah untuk menampilkan pemberitahuan gempa";
|
||||
"official_provider" = "Sumber: %@";
|
||||
"share_hashtag" = "#gempa";
|
||||
"share_notified" = "Dilaporkan melalui aplikasi Detektor Gempa. Unduh aplikasi dari https://sismo.app/download/ untuk menerima peringatan real time #gempa @SismoDetector";
|
||||
@@ -51,6 +54,7 @@
|
||||
"filter_area" = "Gempa yang ditampilkan: dalam radius";
|
||||
"filter_relevant" = "Gempa bumi yang ditampilkan: relevan";
|
||||
"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";
|
||||
"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";
|
||||
@@ -73,6 +77,7 @@
|
||||
"status_cancel" = "Batal";
|
||||
"options_alarms" = "Peringatan real time";
|
||||
"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_enable_official" = "Terima pemberitahuan untuk gempa bumi yang terdeteksi oleh jaringan seismik nasional dan internasional";
|
||||
"options_official_type" = "Filter notifikasi";
|
||||
@@ -103,7 +108,7 @@
|
||||
"permission_location_no_background_solve" = "Atasi";
|
||||
"main_share_app" = "Bagikan 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";
|
||||
"main_alerttest" = "Tes peringatan";
|
||||
"main_simulator" = "Simulator";
|
||||
@@ -140,6 +145,8 @@
|
||||
"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_intensity" = "Intensitas %@";
|
||||
"shakemap" = "Peta intensitas";
|
||||
"shakemap_description" = "Peta intensitas berdasarkan laporan pengguna";
|
||||
|
||||
// values
|
||||
"official_magnitude_value_00" = "Magnitudo >= 0.0";
|
||||
@@ -214,7 +221,7 @@
|
||||
"attention" = "Perhatian";
|
||||
"official_no_country_selected" = "Anda belum memilih negara mana pun";
|
||||
"report" = "Melaporkan";
|
||||
"purchase_pro_restore" = "Mengembalikan";
|
||||
"purchase_pro_restore" = "Pulihkan langganan";
|
||||
"purchase_pro_restore_alert_title" = "Kembalikan selesai";
|
||||
"purchase_pro_restore_alert_message" = "Anda telah memulihkan produk yang Anda belid";
|
||||
"purchase_pro_no_subscriptions_alert_message" = "Tidak ada pembelian yang ditemukan untuk dipulihkan. Pastikan Anda masuk ke akun tempat pembelian dilakukan.";
|
||||
@@ -229,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Bulanan";
|
||||
"subscription_plan_yearly" = "Tahunan";
|
||||
"subscription_plan_perpetual" = "Selamanya";
|
||||
"tap_to_open" = "Ketuk untuk memperbesar";
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
"Rilevato sisma debole a" = "Sisma leggero rilevato a %@";
|
||||
"Rilevato sisma forte a" = "Sisma forte rilevato a %@";
|
||||
"Rilevato sisma a" = "Sisma rilevato a %@";
|
||||
"Sisma lieve segnalato da utente a" = "Sisma lieve segnalato da un utente a %@";
|
||||
"Sisma forte segnalato da utente a" = "Sisma forte segnalato da un utente a %@";
|
||||
"Sisma molto forte segnalato da utente a" = "Sisma molto forte segnalato da un utente at %@";
|
||||
"Sisma segnalato da utente a" = "Sisma segnalato dagli utenti dell'app a %@";
|
||||
"Sisma lieve segnalato da utente a" = "Sisma lieve segnalato da un utente a %@"; // not used, to be removed
|
||||
"Sisma forte segnalato da utente a" = "Sisma forte segnalato da un utente a %@"; // not used, to be removed
|
||||
"Sisma molto forte segnalato da utente a" = "Sisma molto forte segnalato da un utente at %@"; // not used, to be removed
|
||||
"Sisma rilevato a" = "Sisma rilevato a %@";
|
||||
"alert_intensity_no_shaking" = "Non percepito dove ti trovi";
|
||||
"alert_intensity_mild" = "Previsto uno scuotimento leggero o nullo";
|
||||
@@ -25,13 +26,14 @@
|
||||
"drawer_privacy" = "Privacy";
|
||||
"tab_network" = "ALLERTE";
|
||||
"tab_manual" = "SEGNALAZIONI";
|
||||
"tab_official" = "RETI SISMICHE";
|
||||
"tab_official" = "Lista sismi";
|
||||
"inapp_available_10k" = "Top 10K: %lu sottoscrizioni ancora disponibili per essere allertato in meno di 1 secondo dal rilevamento del sisma";
|
||||
"inapp_available_100k" = "Top 100K: %lu sottoscrizioni ancora disponibili per essere allertato in meno di 5 secondi dal rilevamento del sisma";
|
||||
"filter_filter" = "Scegli quali sismi vuoi visualizzare";
|
||||
"filter_show_area" = "Mostra tutti i sismi nel raggio di:";
|
||||
"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_felt" = "Mostra solo i sismi percepiti dagli utenti";
|
||||
"filter_minimum_magnitude" = "e magnitudo minima:";
|
||||
"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.";
|
||||
@@ -43,6 +45,7 @@
|
||||
"filter_empty_area" = "Nessun sisma nelle ultime 24 ore in base ai filtri impostati";
|
||||
"filter_empty_relevant" = "Nessun sisma rilevante nelle ultime 24 ore rispetto alla tua posizione";
|
||||
"filter_nolocation" = "Questa opzione non è disponibile perché la tua posizione è sconosciuta";
|
||||
"official_filter_changed" = "I filtri della lista sono stati modificati per mostrare il sisma notificato";
|
||||
"official_provider" = "Fonte: %@";
|
||||
"share_hashtag" = "#terremoto";
|
||||
"share_notified" = "Segnalato tramite la app Rilevatore Terremoto. Scarica la app da https://sismo.app/download/ per ricevere allerte #terremoto in tempo reale @SismoDetector";
|
||||
@@ -51,6 +54,7 @@
|
||||
"filter_area" = "Sismi mostrati: nel raggio";
|
||||
"filter_relevant" = "Sismi mostrati: rilevanti";
|
||||
"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";
|
||||
"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.";
|
||||
@@ -73,6 +77,7 @@
|
||||
"status_cancel" = "Cancella";
|
||||
"options_alarms" = "Allerta in tempo reale";
|
||||
"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_enable_official" = "Ricevi notifiche per i sismi rilevati dalle reti sismiche nazionali e internazionali";
|
||||
"options_official_type" = "Filtro notifiche";
|
||||
@@ -103,7 +108,7 @@
|
||||
"permission_location_no_background_solve" = "Correggi";
|
||||
"main_share_app" = "Condividi 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";
|
||||
"main_alerttest" = "Test Allerta";
|
||||
"main_simulator" = "Simulatore";
|
||||
@@ -140,6 +145,8 @@
|
||||
"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_intensity" = "Intensità %@";
|
||||
"shakemap" = "Mappa intensità";
|
||||
"shakemap_description" = "Mappa di intensità basata sulle segnalazioni degli utenti";
|
||||
|
||||
// values
|
||||
"official_magnitude_value_00" = "Magnitudo >= 0.0";
|
||||
@@ -214,7 +221,7 @@
|
||||
"attention" = "Attenzione";
|
||||
"official_no_country_selected" = "Non hai selezionato alcuna nazione";
|
||||
"report" = "Report";
|
||||
"purchase_pro_restore" = "Ripristina";
|
||||
"purchase_pro_restore" = "Ripristina abbonamenti";
|
||||
"purchase_pro_restore_alert_title" = "Ripristino completato";
|
||||
"purchase_pro_restore_alert_message" = "Hai ripristinato il prodotto che avevi acquistato";
|
||||
"purchase_pro_no_subscriptions_alert_message" = "Non è stato trovato alcun prodotto da ripristinare. Assicurati di essere registrato con l'account dal quale avevi fatto l'acquisto.";
|
||||
@@ -229,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Mensile";
|
||||
"subscription_plan_yearly" = "Annuale";
|
||||
"subscription_plan_perpetual" = "A vita";
|
||||
"tap_to_open" = "Tocca per aprire";
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
"Rilevato sisma debole a" = "%@'da hafif deprem tespit edildi";
|
||||
"Rilevato sisma forte a" = "%@'da şiddetli deprem tespit edildi";
|
||||
"Rilevato sisma a" = "%@'da deprem tespit edildi";
|
||||
"Sisma lieve segnalato da utente a" = "%@da kullanıcı tarafından bildirilen hafif deprem";
|
||||
"Sisma forte segnalato da utente a" = "%@'da kullanıcı tarafından bildirilen şiddetli deprem";
|
||||
"Sisma molto forte segnalato da utente a" = "@'da kullanıcı tarafından bildirilen çok şiddetli deprem";
|
||||
"Sisma segnalato da utente a" = "Uygulama kullanıcıları tarafından %@'de bildirilen deprem";
|
||||
"Sisma lieve segnalato da utente a" = "%@da kullanıcı tarafından bildirilen hafif deprem"; // not used, to be removed
|
||||
"Sisma forte segnalato da utente a" = "%@'da kullanıcı tarafından bildirilen şiddetli deprem"; // not used, to be removed
|
||||
"Sisma molto forte segnalato da utente a" = "@'da kullanıcı tarafından bildirilen çok şiddetli deprem"; // not used, to be removed
|
||||
"Sisma rilevato a" = "%@'da deprem tespit edildi";
|
||||
"alert_intensity_no_shaking" = "Bulunduğunuz yerde hissedilmiyor";
|
||||
"alert_intensity_mild" = "Hafif veya hiç titreme beklemeyin";
|
||||
@@ -25,13 +26,14 @@
|
||||
"drawer_privacy" = "Gizlilik";
|
||||
"tab_network" = "UYARILAR";
|
||||
"tab_manual" = "RAPORLAR";
|
||||
"tab_official" = "SİSMİK AĞLAR";
|
||||
"tab_official" = "Deprem listesi";
|
||||
"inapp_available_10k" = "Top 10K: Hala depremin tespitinden itibaren 1 saniyeden daha kısa süre içinde uyarı alacak %lu abonelik mevcut";
|
||||
"inapp_available_100k" = "Top 100K: Hala depremin tespitinden itibaren 5 saniyeden daha kısa süre içinde uyarı alacak %lu abonelik mevcut";
|
||||
"filter_filter" = "Görüntülemek istediğiniz depremleri seçin";
|
||||
"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_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:";
|
||||
"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.";
|
||||
@@ -43,6 +45,7 @@
|
||||
"filter_empty_area" = "Filtrelere göre son 24 saatte deprem yok";
|
||||
"filter_empty_relevant" = "Son 24 saat içinde konumunuza göre alakalı deprem yok";
|
||||
"filter_nolocation" = "Konumunuz bilinmediğinden bu seçenek kullanılamıyor";
|
||||
"official_filter_changed" = "Bildirilen depremin gösterilmesi için liste filtreleri değiştirildi";
|
||||
"official_provider" = "Kaynak: %@";
|
||||
"share_hashtag" = "#deprem";
|
||||
"share_notified" = "Deprem Ağı uygulaması aracılığıyla bildirildi. @SismoDetector gerçek zamanlı #deprem uyarılarını almak için uygulamayı https://sismo.app/download/ adresinden indirin.";
|
||||
@@ -51,6 +54,7 @@
|
||||
"filter_area" = "Gösterilen depremler: yarıçap içinde";
|
||||
"filter_relevant" = "Gösterilen depremler: alakalı";
|
||||
"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";
|
||||
"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";
|
||||
@@ -73,6 +77,7 @@
|
||||
"status_cancel" = "İptal et";
|
||||
"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_disable_sound" = "Bulunduğunuz yerdeki sarsıntı hafifse alarm sesini devre dışı bırakın";
|
||||
"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_official_type" = "Bildirim filtresi";
|
||||
@@ -103,7 +108,7 @@
|
||||
"permission_location_no_background_solve" = "Çözün";
|
||||
"main_share_app" = "Uygul. Paylaş";
|
||||
"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.";
|
||||
"main_alerttest" = "Test uyarısı";
|
||||
"main_simulator" = "Simülatör";
|
||||
@@ -140,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_XII" = "XII - Toprağın bozulması, yer kabuğunun yer değiştirmesi";
|
||||
"mercalli_intensity" = "Şiddeti %@";
|
||||
"shakemap" = "Yoğunluk haritası";
|
||||
"shakemap_description" = "Kullanıcı raporlarına dayalı yoğunluk haritası";
|
||||
|
||||
// values
|
||||
"official_magnitude_value_00" = "Büyüklük >= 0.0";
|
||||
@@ -214,7 +221,7 @@
|
||||
"attention" = "Dikkat";
|
||||
"official_no_country_selected" = "Herhangi bir ülke seçmediniz";
|
||||
"report" = "Bildiri";
|
||||
"purchase_pro_restore" = "Geri yükle";
|
||||
"purchase_pro_restore" = "Abonelikleri geri yükle";
|
||||
"purchase_pro_restore_alert_title" = "Geri yükleme tamamlandı";
|
||||
"purchase_pro_restore_alert_message" = "Satın aldığınız ürünü geri yüklediniz";
|
||||
"purchase_pro_no_subscriptions_alert_message" = "Geri yüklenecek satın alma bulunamadı. Satın alma işleminin yapıldığı hesapta oturum açtığınızdan emin olun.";
|
||||
@@ -229,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Aylık";
|
||||
"subscription_plan_yearly" = "Yıllık";
|
||||
"subscription_plan_perpetual" = "Sonsuza kadar";
|
||||
"tap_to_open" = "Büyütmek için dokunun";
|
||||
|
||||
Reference in New Issue
Block a user