Compare commits
21 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 |
@@ -1,5 +1,11 @@
|
||||
# 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
|
||||
|
||||
@@ -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
|
||||
@@ -24,6 +24,7 @@
|
||||
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 */; };
|
||||
@@ -41,6 +42,7 @@
|
||||
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 */; };
|
||||
@@ -49,6 +51,11 @@
|
||||
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 */; };
|
||||
@@ -224,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 */; };
|
||||
@@ -329,6 +335,7 @@
|
||||
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>"; };
|
||||
@@ -356,6 +363,8 @@
|
||||
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>"; };
|
||||
@@ -554,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>"; };
|
||||
@@ -678,6 +685,7 @@
|
||||
children = (
|
||||
658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */,
|
||||
658BC02A2859A4D3009EECAA /* RealtimeAlertView.swift */,
|
||||
657CCF0B2E32711D00E91715 /* MapSeismicWaveAnimator.swift */,
|
||||
);
|
||||
path = "Realtime Alert";
|
||||
sourceTree = "<group>";
|
||||
@@ -913,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 */,
|
||||
);
|
||||
@@ -941,6 +948,7 @@
|
||||
6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.swift */,
|
||||
657747FC2D4D12A200213830 /* SeismicNetworkData.swift */,
|
||||
657747FE2D4D2BA100213830 /* SeismicNetworkScrollIndicatorView.swift */,
|
||||
653C5C2B2E295C5A00E25528 /* SeismicNetworkFilterRecapView.swift */,
|
||||
DC2814372519C56100C1AFF7 /* SeismicNetworksViewController.swift */,
|
||||
DCC76BD7251F56050005C4DC /* SeismicCardSettingsViewController.swift */,
|
||||
65DBFB4A25E29DD60041CBA6 /* SeismicNetworksMapDetailViewController.swift */,
|
||||
@@ -1656,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 */,
|
||||
@@ -1673,6 +1682,7 @@
|
||||
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 */,
|
||||
@@ -1704,17 +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 */,
|
||||
6514FF6C2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */,
|
||||
8CF12CD921DE49B600613AC5 /* NotificationViewController.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1829,7 +1843,6 @@
|
||||
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",
|
||||
@@ -1847,7 +1860,6 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1867,7 +1879,6 @@
|
||||
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",
|
||||
@@ -1883,7 +1894,6 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1923,7 +1933,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 154;
|
||||
CURRENT_PROJECT_VERSION = 159;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = WJA4MR4CPC;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -1943,8 +1953,8 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 5.10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 5.11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1987,7 +1997,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 154;
|
||||
CURRENT_PROJECT_VERSION = 159;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = WJA4MR4CPC;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
@@ -2001,8 +2011,8 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 5.10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 5.11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@@ -2025,7 +2035,6 @@
|
||||
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",
|
||||
@@ -2094,7 +2103,6 @@
|
||||
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;
|
||||
};
|
||||
@@ -2112,7 +2120,6 @@
|
||||
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",
|
||||
@@ -2180,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;
|
||||
};
|
||||
@@ -2200,7 +2206,6 @@
|
||||
"NOTIFICATION_CONTENT=1",
|
||||
);
|
||||
INFOPLIST_FILE = EQNNotificationContent/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -2214,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;
|
||||
};
|
||||
@@ -2232,7 +2236,6 @@
|
||||
"NOTIFICATION_CONTENT=1",
|
||||
);
|
||||
INFOPLIST_FILE = EQNNotificationContent/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -2245,7 +2248,6 @@
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "EQNNotificationContent/EQNNotificationContent-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -2296,7 +2298,7 @@
|
||||
repositoryURL = "https://github.com/andreabusi-it/Shogun.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.0;
|
||||
minimumVersion = 2.0.0;
|
||||
};
|
||||
};
|
||||
65B16E1C2BDFA88D0020527E /* XCRemoteSwiftPackageReference "Solar" */ = {
|
||||
|
||||
+18
-9
@@ -42,8 +42,17 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
|
||||
"state" : {
|
||||
"revision" : "fbd463894af94d90eb4d6a4e54080459a8179519",
|
||||
"version" : "11.12.0"
|
||||
"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" : "f7460ea630bddf172115c28493ae8b3798d95ce3",
|
||||
"version" : "11.12.0"
|
||||
"revision" : "45ce435e9406d3c674dd249a042b932bee006f60",
|
||||
"version" : "11.15.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -69,8 +78,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
|
||||
"version" : "8.0.2"
|
||||
"revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
|
||||
"version" : "8.1.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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -207,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.isAdvertiserTrackingEnabled = YES;
|
||||
} else {
|
||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = NO;
|
||||
}
|
||||
}];
|
||||
});
|
||||
} else {
|
||||
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = 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
|
||||
|
||||
@@ -92,10 +92,8 @@ class SubscriptionsViewController: UITableViewController {
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = .systemGroupedBackground
|
||||
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
|
||||
if #available(iOS 15.0, *) {
|
||||
// remove extra padding on top of each section header
|
||||
tableView.sectionHeaderTopPadding = 0.0
|
||||
}
|
||||
// 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)
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// MapSeismicWaveAnimator.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Andrea Busi on 24/07/25.
|
||||
// Copyright © 2025 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
|
||||
|
||||
class MapSeismicWaveAnimator {
|
||||
|
||||
private weak var mapView: MKMapView?
|
||||
private weak var waveTimeLabel: UILabel?
|
||||
|
||||
private let OverlayCircleId = "wave_animation"
|
||||
|
||||
/// Alert to display
|
||||
private let realtimeAlert: EQNRealtimePushNotification
|
||||
/// Timer to constantly update countdown label
|
||||
private var countdownTimer: Timer?
|
||||
/// Timer to simulate animation for the wave
|
||||
private var waveAnimationTimer: Timer?
|
||||
/// Refresh time for wave animation
|
||||
private let waveAnimationRefreshRate = 0.1
|
||||
/// Current radius of the wave animation on the map
|
||||
private var waveAnimationCurrentRadius: CLLocationDistance = 0
|
||||
private var waveAnimationVelocity: Double = 1_000
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(
|
||||
realtimeAlert: EQNRealtimePushNotification,
|
||||
mapView: MKMapView,
|
||||
waveTimeLabel: UILabel
|
||||
) {
|
||||
self.realtimeAlert = realtimeAlert
|
||||
self.mapView = mapView
|
||||
self.waveTimeLabel = waveTimeLabel
|
||||
self.setup()
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
self.waveAnimationCurrentRadius = currentWavePosition()
|
||||
self.waveAnimationVelocity = evaluateWaveAnimationVelocity()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
startCountdown()
|
||||
startWaveAnimation()
|
||||
}
|
||||
|
||||
func stop() {
|
||||
stopCountdown()
|
||||
stopWaveAnimation()
|
||||
}
|
||||
|
||||
// MARK: - Wave
|
||||
|
||||
private func startCountdown() {
|
||||
// show countdown only if time is less than 300 seconds
|
||||
if realtimeAlert.currentCountdown() < 300 {
|
||||
// start a timer for the countdown label
|
||||
waveTimeLabel?.isHidden = false
|
||||
countdownTimer = Timer.scheduledTimer(
|
||||
timeInterval: 1.0,
|
||||
target: self,
|
||||
selector: #selector(countdownTimerFired(_:)),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
countdownTimer?.fire()
|
||||
}
|
||||
}
|
||||
|
||||
private func startWaveAnimation() {
|
||||
waveAnimationTimer = Timer.scheduledTimer(
|
||||
timeInterval: waveAnimationRefreshRate,
|
||||
target: self,
|
||||
selector: #selector(mapWaveAnimationFired(_:)),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
waveAnimationTimer?.fire()
|
||||
}
|
||||
|
||||
private func stopCountdown() {
|
||||
countdownTimer?.invalidate()
|
||||
countdownTimer = nil
|
||||
}
|
||||
|
||||
private func stopWaveAnimation() {
|
||||
waveAnimationTimer?.invalidate()
|
||||
waveAnimationTimer = nil
|
||||
}
|
||||
|
||||
// MARK: - Timer
|
||||
|
||||
@objc private func countdownTimerFired(_ sender: Timer) {
|
||||
let countdown = realtimeAlert.currentCountdown()
|
||||
waveTimeLabel?.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), countdown)
|
||||
waveTimeLabel?.textColor = waveTimeTextColor(for: countdown)
|
||||
|
||||
if countdown <= 0 {
|
||||
// stop the countdown
|
||||
stopCountdown()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func mapWaveAnimationFired(_ sender: Timer) {
|
||||
waveAnimationCurrentRadius += waveAnimationVelocity
|
||||
addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
/// Evaluate current position for the wave
|
||||
/// Used to define initial position for the wave circle
|
||||
/// - Returns: Distance of the wave from the original earthquake point
|
||||
private func currentWavePosition() -> Double {
|
||||
// distanza tra utente e terremoto
|
||||
let distance = realtimeAlert.distanceFromUser()
|
||||
|
||||
// calcoliamo la distanza rimanente da mostrare, perchè la schermata potrebbe anche essere aperta in ritardo
|
||||
let remainingDistance = realtimeAlert.waveSpeed * Double(realtimeAlert.currentCountdown())
|
||||
return distance - remainingDistance
|
||||
}
|
||||
|
||||
/// Evaluate wave velocity based on push notification data
|
||||
/// - Returns: Wave velocity, used for animation
|
||||
private func evaluateWaveAnimationVelocity() -> Double {
|
||||
let velocity = realtimeAlert.waveSpeed
|
||||
return velocity * waveAnimationRefreshRate
|
||||
}
|
||||
|
||||
/// Returns the text color based on impact countdown
|
||||
private func waveTimeTextColor(for countdown: Int) -> UIColor {
|
||||
switch countdown {
|
||||
case _ where countdown > 15:
|
||||
return UIColor(red: 255.0/255.0, green: 140.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
case _ where countdown > 5:
|
||||
return UIColor(red: 255.0/255.0, green: 100.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
default:
|
||||
return UIColor(red: 255.0/255.0, green: 0.0/255.0, blue: 0.0, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Map management
|
||||
|
||||
func addMapCircle(
|
||||
center: CLLocationCoordinate2D,
|
||||
radius: CLLocationDistance
|
||||
) {
|
||||
guard let mapView else { return }
|
||||
|
||||
// remove any other existing overlays
|
||||
let overlays = mapView.overlays.filter { $0.title == OverlayCircleId }
|
||||
mapView.removeOverlays(overlays)
|
||||
|
||||
// add new overlay
|
||||
let circle = MKCircle(center: center, radius: radius)
|
||||
circle.title = OverlayCircleId
|
||||
mapView.addOverlay(circle)
|
||||
}
|
||||
|
||||
func getOverlayRenderer(for overlay: MKOverlay) -> MKOverlayRenderer? {
|
||||
switch overlay {
|
||||
case let circle as MKCircle where overlay.title == OverlayCircleId:
|
||||
let circleRenderer = MKCircleRenderer(overlay: circle)
|
||||
circleRenderer.strokeColor = AppTheme.Colors.red
|
||||
circleRenderer.fillColor = AppTheme.Colors.red.withAlphaComponent(0.2)
|
||||
circleRenderer.lineWidth = 3.0
|
||||
return circleRenderer
|
||||
case let polyline as MKPolyline:
|
||||
let polylineRenderer = MKPolylineRenderer(polyline: polyline)
|
||||
polylineRenderer.strokeColor = .blue
|
||||
polylineRenderer.lineWidth = 2.0
|
||||
return polylineRenderer
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,6 @@ class RealtimeAlertView: UIView {
|
||||
lazy var mapView: MKMapView = {
|
||||
let map = MKMapView()
|
||||
map.translatesAutoresizingMaskIntoConstraints = false
|
||||
map.delegate = self
|
||||
map.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
|
||||
map.showsUserLocation = true
|
||||
return map
|
||||
@@ -160,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 = "",
|
||||
@@ -192,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
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
|
||||
]
|
||||
|
||||
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
|
||||
|
||||
|
||||
+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)
|
||||
}
|
||||
+22
-2
@@ -140,7 +140,7 @@ class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
|
||||
}
|
||||
|
||||
private func nextPinStyle() {
|
||||
pinStyle.next()
|
||||
pinStyle.advance()
|
||||
reloadMap()
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ extension EQNMapAnnotationShakemap {
|
||||
case .full, .light:
|
||||
let identifier = EQNSeismicAnnotationView.identifier(for: style)
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
|
||||
annotationView.magnitude = String(format: "%.1f", shakemap.intensity)
|
||||
annotationView.magnitude = intensityString(from: shakemap.intensity)
|
||||
annotationView.magnitudeTextColor = intensityTextColor ?? .black
|
||||
annotationView.magnitudeBackgroundColor = intensityColor
|
||||
annotationView.canShowCallout = true
|
||||
@@ -242,6 +242,26 @@ extension EQNMapAnnotationShakemap {
|
||||
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 {
|
||||
|
||||
+11
-4
@@ -63,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()
|
||||
@@ -128,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) {
|
||||
@@ -178,7 +185,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
|
||||
// MARK: - Private
|
||||
|
||||
private func nextPinStyle() {
|
||||
pinStyle.next()
|
||||
pinStyle.advance()
|
||||
reloadMap()
|
||||
}
|
||||
|
||||
|
||||
+80
-32
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import EventKitUI
|
||||
import DZNEmptyDataSet
|
||||
import Shogun
|
||||
@@ -68,7 +69,6 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
// MARK: - UI
|
||||
|
||||
@IBOutlet private weak var displayModeButton: UIBarButtonItem!
|
||||
@IBOutlet private weak var sortButton: UIBarButtonItem!
|
||||
private var tableViewTopConstraint: NSLayoutConstraint?
|
||||
|
||||
private lazy var tableView: UITableView = {
|
||||
@@ -111,6 +111,32 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
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
|
||||
|
||||
@@ -147,19 +173,44 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
// 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: view.safeAreaLayoutGuide.bottomAnchor).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: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
|
||||
tableView.bottomAnchor
|
||||
.constraint(
|
||||
equalTo: filterRecapView.topAnchor,
|
||||
constant: -8
|
||||
).isActive = true
|
||||
|
||||
filterRecapView.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
|
||||
filterRecapView.leadingAnchor
|
||||
.constraint(
|
||||
equalTo: view.leadingAnchor,
|
||||
constant: 10.0
|
||||
).isActive = true
|
||||
filterRecapView.trailingAnchor
|
||||
.constraint(
|
||||
equalTo: view.trailingAnchor,
|
||||
constant: -10.0
|
||||
).isActive = true
|
||||
filterRecapView.topAnchor
|
||||
.constraint(equalTo: tableView.bottomAnchor).isActive = true
|
||||
filterRecapView.bottomAnchor
|
||||
.constraint(
|
||||
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
|
||||
constant: -8
|
||||
).isActive = true
|
||||
}
|
||||
|
||||
private func setupFilterView(isVisible: Bool) {
|
||||
@@ -201,31 +252,13 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
tableView.emptyDataSetSource = self
|
||||
tableView.separatorStyle = .none
|
||||
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
|
||||
|
||||
setupSortMenu()
|
||||
}
|
||||
|
||||
private func setupSortMenu() {
|
||||
let currentSort = EQNSeismic.shared.sort
|
||||
sortButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
|
||||
UIAction(title: NSLocalizedString("sort_date", comment: ""), image: UIImage(systemName: "calendar"), state: currentSort == .time ? .on : .off) { [weak self ] _ in
|
||||
self?.changeSort(to: .time)
|
||||
},
|
||||
UIAction(title: NSLocalizedString("sort_position", comment: ""), image: UIImage(systemName: "ruler"), state: currentSort == .position ? .on : .off) { [weak self] _ in
|
||||
self?.changeSort(to: .position)
|
||||
},
|
||||
UIAction(title: NSLocalizedString("sort_magnitude", comment: ""), image: UIImage(systemName: "thermometer"), state: currentSort == .magnitude ? .on : .off) { [weak self] _ in
|
||||
self?.changeSort(to: .magnitude)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
private func checkForLocation() {
|
||||
// check if a valid location is available,
|
||||
// otherwise change the filter settings
|
||||
if !isLocationAvailable() {
|
||||
EQNSeismic.shared.filterOption = .worldWide
|
||||
EQNSeismic.shared.saveFilters()
|
||||
updateFilter(type: .worldWide)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,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
|
||||
@@ -335,10 +368,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
}
|
||||
|
||||
private func changeSort(to sort: EQNSeismic.Sort) {
|
||||
EQNSeismic.shared.sort = sort
|
||||
EQNSeismic.shared.saveFilters()
|
||||
|
||||
setupSortMenu()
|
||||
updateFilter(sort: sort)
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
@@ -568,11 +598,22 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
|
||||
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)
|
||||
@@ -628,9 +669,15 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
if let centerIndexPath = getCenterCellIndexPath(), centerIndexPath != currentCenteredIndexPath {
|
||||
currentCenteredIndexPath = centerIndexPath
|
||||
|
||||
let row = rows[centerIndexPath.row]
|
||||
if case .seismic = row, seismicViewModels.count > centerIndexPath.row {
|
||||
scrollIndicatorView.highlighted = seismicViewModels[centerIndexPath.row]
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -641,12 +688,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
|
||||
loadData(forced: true)
|
||||
}
|
||||
|
||||
@IBAction func openFilterTapped(_ sender: Any) {
|
||||
private func openFilter() {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierFilters, sender: nil)
|
||||
}
|
||||
|
||||
@IBAction func collapseExpandTapped(_ sender: Any) {
|
||||
cardDisplayType.next()
|
||||
cardDisplayType.advance()
|
||||
|
||||
switch cardDisplayType {
|
||||
case .small:
|
||||
@@ -851,6 +898,7 @@ extension SeismicNetworksViewController: EKEventEditViewDelegate {
|
||||
|
||||
extension SeismicNetworksViewController: SeismicFiltersViewControllerDelegate {
|
||||
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
|
||||
model.filter = controller.currentFilterType
|
||||
loadData(forced: controller.needsDataUpdate)
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
@@ -37,13 +37,3 @@ extension CGFloat {
|
||||
self*2.0
|
||||
}
|
||||
}
|
||||
|
||||
extension CaseIterable where Self: Equatable {
|
||||
mutating func next() {
|
||||
let all = Self.allCases
|
||||
let idx = all.firstIndex(of: self)!
|
||||
let next = all.index(after: idx)
|
||||
let newValue = all[next == all.endIndex ? all.startIndex : next]
|
||||
self = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ public class Log {
|
||||
shared.log(level: .debug, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
public func dumpLog() async -> String {
|
||||
return (try? await getLogEntries()) ?? ""
|
||||
}
|
||||
@@ -104,7 +103,6 @@ public class Log {
|
||||
|
||||
/// Retrieve log entries from a specified time.
|
||||
/// - Returns: String of log entries, newlines separated
|
||||
@available(iOS 15.0, *)
|
||||
private func getLogEntries() async throws -> String {
|
||||
let logTask = Task.init(priority: .utility) { () -> String in
|
||||
let logs = try retrieveLogEntries()
|
||||
@@ -116,7 +114,6 @@ public class Log {
|
||||
return try await logTask.value
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
private func retrieveLogEntries() throws -> [OSLogEntryLog] {
|
||||
// Open the log store.
|
||||
let logStore = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
@@ -134,7 +131,6 @@ public class Log {
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
extension OSLogEntryLog.Level: @retroactive CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
|
||||
@@ -134,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];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -18,7 +18,7 @@ import Foundation
|
||||
case userFelt
|
||||
}
|
||||
|
||||
enum Sort: Int {
|
||||
enum Sort: Int, CaseIterable {
|
||||
case time
|
||||
case position
|
||||
case magnitude
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" 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="23506"/>
|
||||
<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"/>
|
||||
@@ -26,12 +26,6 @@
|
||||
<action selector="collapseExpandTapped:" destination="tVM-DH-fmv" id="EnD-92-5ZX"/>
|
||||
</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-refresh" id="ZJh-jF-ILm">
|
||||
<connections>
|
||||
<action selector="refreshDataTapped:" destination="tVM-DH-fmv" id="qs5-jS-0Op"/>
|
||||
@@ -41,7 +35,6 @@
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="displayModeButton" destination="HTN-07-s5p" id="Lhc-Od-MvL"/>
|
||||
<outlet property="sortButton" destination="LyU-KI-3Mb" id="969-Zg-YBB"/>
|
||||
<segue destination="6LP-zk-O1z" kind="presentation" identifier="ShowFilters" modalPresentationStyle="overCurrentContext" modalTransitionStyle="crossDissolve" id="Nzu-iH-UgB"/>
|
||||
<segue destination="Rfp-kt-2Kx" kind="presentation" identifier="ShowCardSettings" modalPresentationStyle="overCurrentContext" modalTransitionStyle="crossDissolve" id="VWw-16-xGw"/>
|
||||
</connections>
|
||||
@@ -59,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"/>
|
||||
@@ -67,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"/>
|
||||
@@ -276,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>
|
||||
@@ -404,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"/>
|
||||
@@ -506,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"/>
|
||||
@@ -525,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"/>
|
||||
@@ -544,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"/>
|
||||
@@ -565,7 +558,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="200" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="a35-sg-TCr">
|
||||
<rect key="frame" x="0.0" y="144" width="414" height="614"/>
|
||||
<rect key="frame" x="0.0" y="192" width="414" height="532"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="syj-UE-OWc" id="Vah-TU-YfT"/>
|
||||
@@ -573,7 +566,7 @@
|
||||
</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"/>
|
||||
@@ -623,9 +616,7 @@
|
||||
<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"/>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -18,7 +18,7 @@ class EQNSeismicAnnotationView: MKAnnotationView {
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -108,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" = "محاكي";
|
||||
@@ -236,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "شهريا";
|
||||
"subscription_plan_yearly" = "سنوي";
|
||||
"subscription_plan_perpetual" = "حياة";
|
||||
"tap_to_open" = "انقر للتكبير";
|
||||
|
||||
@@ -108,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" = "Προσομοιωτής";
|
||||
@@ -236,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Μηνιαίο";
|
||||
"subscription_plan_yearly" = "Ετήσιο";
|
||||
"subscription_plan_perpetual" = "Διάρκεια Ζωής";
|
||||
"tap_to_open" = "Πατήστε για μεγέθυνση";
|
||||
|
||||
@@ -108,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";
|
||||
@@ -236,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Monthly";
|
||||
"subscription_plan_yearly" = "Annual";
|
||||
"subscription_plan_perpetual" = "Lifetime";
|
||||
"tap_to_open" = "Tap to open";
|
||||
|
||||
@@ -108,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";
|
||||
@@ -236,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Mensual";
|
||||
"subscription_plan_yearly" = "Anual";
|
||||
"subscription_plan_perpetual" = "Para siempre";
|
||||
"tap_to_open" = "Toque para ampliar";
|
||||
|
||||
@@ -108,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";
|
||||
@@ -236,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Mensuel";
|
||||
"subscription_plan_yearly" = "Annuel";
|
||||
"subscription_plan_perpetual" = "Pour toujours";
|
||||
"tap_to_open" = "Appuyez pour agrandir";
|
||||
|
||||
@@ -236,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Mjesečno";
|
||||
"subscription_plan_yearly" = "Godišnji";
|
||||
"subscription_plan_perpetual" = "Zauvijek";
|
||||
"tap_to_open" = "Dodirnite za uvećanje";
|
||||
|
||||
@@ -108,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";
|
||||
@@ -236,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Bulanan";
|
||||
"subscription_plan_yearly" = "Tahunan";
|
||||
"subscription_plan_perpetual" = "Selamanya";
|
||||
"tap_to_open" = "Ketuk untuk memperbesar";
|
||||
|
||||
@@ -108,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";
|
||||
@@ -236,3 +236,4 @@
|
||||
"subscription_plan_monthly" = "Mensile";
|
||||
"subscription_plan_yearly" = "Annuale";
|
||||
"subscription_plan_perpetual" = "A vita";
|
||||
"tap_to_open" = "Tocca per aprire";
|
||||
|
||||
@@ -108,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";
|
||||
@@ -236,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