diff --git a/Sources/Earthquake Network.xcodeproj/project.pbxproj b/Sources/Earthquake Network.xcodeproj/project.pbxproj index 4e1c660..7b685fd 100644 --- a/Sources/Earthquake Network.xcodeproj/project.pbxproj +++ b/Sources/Earthquake Network.xcodeproj/project.pbxproj @@ -209,6 +209,7 @@ DCAA913F24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA913E24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift */; }; DCB28CEE24FB8400001F557E /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB28CED24FB8400001F557E /* SettingsViewController.swift */; }; DCB45BC8250E86E100DB2D0C /* SeismicSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB45BC7250E86E100DB2D0C /* SeismicSettingsViewController.swift */; }; + DCB528212560161C005288E5 /* AlertSimulatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCB528202560161C005288E5 /* AlertSimulatorViewController.swift */; }; DCB6FBEC24D0B40600ED23B8 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DCB6FBEB24D0B40600ED23B8 /* Colors.xcassets */; }; DCBB267A24D1E7F500F04559 /* SubscriptionsHeaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBB267924D1E7F500F04559 /* SubscriptionsHeaderTableViewCell.swift */; }; DCBB267C24D1E98300F04559 /* EQNInsetTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBB267B24D1E98300F04559 /* EQNInsetTableViewCell.swift */; }; @@ -517,6 +518,7 @@ DCAA913E24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingMultivaluesTableViewCell.swift; sourceTree = ""; }; DCB28CED24FB8400001F557E /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; DCB45BC7250E86E100DB2D0C /* SeismicSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicSettingsViewController.swift; sourceTree = ""; }; + DCB528202560161C005288E5 /* AlertSimulatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertSimulatorViewController.swift; sourceTree = ""; }; DCB6FBEB24D0B40600ED23B8 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; DCBB267924D1E7F500F04559 /* SubscriptionsHeaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsHeaderTableViewCell.swift; sourceTree = ""; }; DCBB267B24D1E98300F04559 /* EQNInsetTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNInsetTableViewCell.swift; sourceTree = ""; }; @@ -993,6 +995,14 @@ path = Commands; sourceTree = ""; }; + DCB5281F256015EB005288E5 /* Simulator */ = { + isa = PBXGroup; + children = ( + DCB528202560161C005288E5 /* AlertSimulatorViewController.swift */, + ); + path = Simulator; + sourceTree = ""; + }; DCB528252560171A005288E5 /* Reports */ = { isa = PBXGroup; children = ( @@ -1039,6 +1049,7 @@ DCD3E3BE24D1555F007C78D4 /* InApp */, DCEFF21024F57163009D3FE1 /* Settings */, DC141968250E769B0059E060 /* Seismic Networks */, + DCB5281F256015EB005288E5 /* Simulator */, ); path = Controllers; sourceTree = ""; @@ -1560,6 +1571,7 @@ DC105641251E7ECE002579BB /* UIFont+Extensions.swift in Sources */, 8CA46BA12194532E00C63C16 /* SismaAnnotation.m in Sources */, DCB28CEE24FB8400001F557E /* SettingsViewController.swift in Sources */, + DCB528212560161C005288E5 /* AlertSimulatorViewController.swift in Sources */, DCC76BD8251F56050005C4DC /* SeismicCardSettingsViewController.swift in Sources */, DC3CE50A250EB7A8005A7DD5 /* EQNGenericPickerViewController.swift in Sources */, 8C602246218D9DB200C799C2 /* PastquakesDettagliMappa.m in Sources */, diff --git a/Sources/Earthquake Network/Controllers/Alerts/AllerteViewController.m b/Sources/Earthquake Network/Controllers/Alerts/AllerteViewController.m index 9677c46..e8d4d8b 100644 --- a/Sources/Earthquake Network/Controllers/Alerts/AllerteViewController.m +++ b/Sources/Earthquake Network/Controllers/Alerts/AllerteViewController.m @@ -195,11 +195,11 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) { [self presentViewController:controller animated:YES completion:nil]; } -- (void)actionComingSoon +- (void)actionOpenSimulator { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Coming soon..." message:@"🚀" preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:nil]]; - [self presentViewController:alert animated:YES completion:nil]; + AlertSimulatorViewController *controller = [[AlertSimulatorViewController alloc] init]; + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; + [self presentViewController:navController animated:YES completion:nil]; } - (void)actionHowItWorks @@ -281,7 +281,7 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) { [weakSelf actionTestPush]; }; cell.onTapSimulator = ^{ - [weakSelf actionComingSoon]; + [weakSelf actionOpenSimulator]; }; cell.onTapHowItWorks = ^{ [weakSelf actionHowItWorks]; diff --git a/Sources/Earthquake Network/Controllers/Simulator/AlertSimulatorViewController.swift b/Sources/Earthquake Network/Controllers/Simulator/AlertSimulatorViewController.swift new file mode 100644 index 0000000..fc1c9b0 --- /dev/null +++ b/Sources/Earthquake Network/Controllers/Simulator/AlertSimulatorViewController.swift @@ -0,0 +1,244 @@ +// +// AlertSimulatorViewController.swift +// Earthquake Network +// +// Created by Busi Andrea on 14/11/2020. +// Copyright © 2020 Earthquake Network. All rights reserved. +// + +import UIKit +import MapKit + +class AlertSimulatorViewController: UIViewController, MKMapViewDelegate { + + struct MapCircle { + let identifier: String + let distance: CLLocationDistance + let color: UIColor + } + + // MARK: - Internal + + private let mapCircles = [ + MapCircle(identifier: "circle_internal", distance: 50_000, color: AppTheme.Colors.red), + MapCircle(identifier: "circle_external", distance: 750_000, color: AppTheme.Colors.green) + ] + + private lazy var viewfinderView: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = .clear + view.isUserInteractionEnabled = false + + // add central vertiacl and horizontal lines + let viewfinderVertical = UIView() + view.addSubview(viewfinderVertical) + viewfinderVertical.translatesAutoresizingMaskIntoConstraints = false + viewfinderVertical.backgroundColor = AppTheme.Colors.lightBlue + viewfinderVertical.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + viewfinderVertical.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + viewfinderVertical.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true + viewfinderVertical.widthAnchor.constraint(equalToConstant: 2.0).isActive = true + + let viewfinderHorizontal = UIView() + view.addSubview(viewfinderHorizontal) + viewfinderHorizontal.translatesAutoresizingMaskIntoConstraints = false + viewfinderHorizontal.backgroundColor = AppTheme.Colors.lightBlue + viewfinderHorizontal.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true + viewfinderHorizontal.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true + viewfinderHorizontal.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true + viewfinderHorizontal.heightAnchor.constraint(equalToConstant: 2.0).isActive = true + + return view + }() + + private lazy var mapView: MKMapView = { + let map = MKMapView() + map.translatesAutoresizingMaskIntoConstraints = false + map.showsUserLocation = true + map.delegate = self + return map + }() + + private lazy var calculateButton: UIButton = { + let button = EQNRoundedButton(type: .custom) + button.backgroundColor = .lightGray + button.setTitleColor(AppTheme.Colors.darkGray, for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle(NSLocalizedString("globe_simulation_button", comment: "").uppercased(), for: .normal) + button.addTarget(self, action: #selector(calculateTapped(_:)), for: .touchUpInside) + return button + }() + + // MARK: - View Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + let closeButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closeTapped(_:))) + navigationItem.rightBarButtonItem = closeButton + + setupUI() + configureMap() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // show an alert view functionality details + let alert = UIAlertController(title: NSLocalizedString("main_simulator", comment: ""), message: NSLocalizedString("globe_simulation", comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("main_understood", comment: ""), style: .default)) + present(alert, animated: true) + } + + // MARK: - Private + + private func setupUI() { + view.addSubview(mapView) + view.addSubview(viewfinderView) + view.addSubview(calculateButton) + + mapView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + mapView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + mapView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + + viewfinderView.topAnchor.constraint(equalTo: mapView.topAnchor).isActive = true + viewfinderView.bottomAnchor.constraint(equalTo: mapView.bottomAnchor).isActive = true + viewfinderView.leftAnchor.constraint(equalTo: mapView.leftAnchor).isActive = true + viewfinderView.rightAnchor.constraint(equalTo: mapView.rightAnchor).isActive = true + + calculateButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0).isActive = true + calculateButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0).isActive = true + calculateButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10.0).isActive = true + calculateButton.heightAnchor.constraint(equalToConstant: 40.0).isActive = true + } + + private func configureMap() { + guard let lastPosition = EQNUser.default().lastPosition else { return } + + let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0) + let region = MKCoordinateRegion(center: lastPosition.coordinate, span: span) + mapView.setCenter(lastPosition.coordinate, animated: false) + mapView.setRegion(region, animated: true) + + mapCircles.forEach { (mapCircle) in + addCircle(location: lastPosition, radius: mapCircle.distance, id: mapCircle.identifier) + } + } + + private func addCircle(location: CLLocation, radius: CLLocationDistance, id: String) { + let circle = MKCircle(center: location.coordinate, radius: radius) + circle.title = id + mapView.addOverlay(circle) + } + + // MARK: - Actions + + @objc func closeTapped(_ sender: Any) { + dismiss(animated: true) + } + + @objc func calculateTapped(_ sender: Any) { + guard let lastPosition = EQNUser.default().lastPosition, + let circleMin = mapCircles.sorted(by: { $0.distance < $1.distance }).first, + let circleMax = mapCircles.sorted(by: { $0.distance > $1.distance }).first else { return } + + let location = CLLocation(latitude: mapView.centerCoordinate.latitude, longitude: mapView.centerCoordinate.longitude) + let distance = lastPosition.distance(from: location) + + // check for a valid point selected by the user + if distance < circleMin.distance || distance > circleMax.distance { + let message = distance < circleMin.distance ? "globe_simulation_outside" : "globe_simulation_inside" + + let alert = UIAlertController(title: NSLocalizedString("Attenzione", comment: ""), message: NSLocalizedString(message, comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .cancel, handler: nil)) + present(alert, animated: true) + return + } + + + let url = EQNGeneratoreURLServer.urlAlertSimulator() + ServerRequest.default().inviaInformazioniAlServer(with: url, richiesta: .alertSimulator) { (result) in + if let result = result as? String { + DispatchQueue.main.async { + self.elaborateServerResponse(result, distance: distance) + } + } + } failure: { (error) in + DispatchQueue.main.async { + let alert = UIAlertController(title: NSLocalizedString("Attenzione", comment: ""), message: error?.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .cancel, handler: nil)) + self.present(alert, animated: true) + return + } + } + } + + private func elaborateServerResponse(_ response: String, distance: CLLocationDistance) { + let isTop10k = EQNPurchaseUtility.isTop10kEnabled() + let isTop100k = EQNPurchaseUtility.isTop100kEnabled() + + // server response (like '3411#12') + let split = response.split(separator: "#") + + let people: Int + let popleTop10k: Int + if split.count > 1 { + //caso top10k=0 e top100k=0 + people = Int(split[0]) ?? 0 + popleTop10k = Int(split[1]) ?? 0 + } else { + //caso top10k=1 oppure top100k=1 + people = Int(split[0]) ?? 0 + popleTop10k = 0 + } + + let distanceKm = distance / 1000 + let delay = round(Double(people) / 1000.0 * 0.15) //calcolo il ritardo di allerta dovuto alla coda di utenti + let leadTime = round(Double(distanceKm) / 8.0 - 3.0) //calcolo il tempo di allerta teorico nel caso di 0 utenti in coda + let actualTime = leadTime - delay //calcolo il tempo di allerta per l'utente + + //il messaggio mostrato all'utente cambia in base al fatto di possedere un abbonamento ed al tempo di allerta calcolato + //il messaggio include tag html perché cambio il colore di parte delle stringa, non so se si fa così anche in iOS + var message = "" + if (isTop10k) { + if (people == 0) { + message = String(format: NSLocalizedString("globe_simulation_message6", comment: ""), "TOP 10K", leadTime) + } else { + message = String(format: NSLocalizedString("globe_simulation_message4", comment: ""), "TOP 10K", leadTime, Double(people)) + } + } else if (isTop100k) { + message = String(format: NSLocalizedString("globe_simulation_message5", comment: ""), "TOP 100K", leadTime, Double(people), "TOP 10K") + } else { + if (leadTime / actualTime < 1.4) { + message = String(format: NSLocalizedString("globe_simulation_message3", comment: ""), leadTime, Double(people), Double(popleTop10k)) + } else { + if (actualTime > 0) { + message = String(format: NSLocalizedString("globe_simulation_message1", comment: ""), leadTime, Double(people), actualTime, Double(popleTop10k)) + } else { + let updatedActualTime = actualTime * -1; + message = String(format: NSLocalizedString("globe_simulation_message2", comment: ""), leadTime, Double(people), updatedActualTime, Double(popleTop10k)) + } + } + } + + let alert = UIAlertController(title: NSLocalizedString("main_simulator", comment: ""), message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .cancel, handler: nil)) + present(alert, animated: true) + } + + // MARK: MKMapViewDelegate + + func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { + if let overlay = overlay as? MKCircle, let mapCircle = mapCircles.first(where: { $0.identifier == overlay.title }) { + let circle = MKCircleRenderer(overlay: overlay) + circle.strokeColor = mapCircle.color + circle.fillColor = AppTheme.Colors.green.withAlphaComponent(0.1) + circle.lineWidth = 2.0 + return circle + } + + return MKOverlayRenderer(overlay: overlay) + } +} diff --git a/Sources/Earthquake Network/Costanti.h b/Sources/Earthquake Network/Costanti.h index b111c4d..9f912c7 100644 --- a/Sources/Earthquake Network/Costanti.h +++ b/Sources/Earthquake Network/Costanti.h @@ -37,7 +37,7 @@ static NSString * const EQNServerUrlOfferTimeRemaining = @"https://srv.earthquak static NSString * const EQNServerUrlAvailableSubscriptionsCounter = @"https://srv.earthquakenetwork.it/distquake_count_top_redis.php"; /// Carica le impostazioni delle notifiche definite dall'utente static NSString * const EQNServerUrlUploadSettings = @"https://srv.earthquakenetwork.it/distquake_upload_settings_ios.php"; -// URL registrazione server +// URL EQNTipoChiamataRegistrazione server static NSString * const EQNServerUrlRegistration = @"https://srv.earthquakenetwork.it/distquake_upload_gcm_regid2.php"; // URL posizione server static NSString * const EQNServerUrlUserLocation = @"https://srv.earthquakenetwork.it/distquake_upload_gcm_latlon.php"; @@ -56,6 +56,8 @@ static NSString * const EQNServerUrlSendUserReport = @"https://srv.earthquakenet static NSString * const EQNServerUrlSendUserReportMessage = @"https://srv.earthquakenetwork.it/distquake_upload_manual_message.php"; /// Effettua un test delle notifiche push static NSString * const EQNServerUrlTestAlarm = @"https://srv.earthquakenetwork.it/distquake_upload_testalarm.php"; +/// Simulatore per calcolo pre-allerta +static NSString * const EQNServerUrlAlertSimulator = @"https://srv.earthquakenetwork.it/distquake_download_alertposition.php"; #pragma mark - UserDefaults Keys @@ -142,7 +144,8 @@ typedef NS_ENUM(NSInteger, EQNTipoChiamata) { EQNTipoChiamataCommentoTerremoto, EQNTipoChiamataImpostazioniNotifiche, EQNTipoChiamataOfferTimeRemaining, - EQNTipoChiamataAlertPushTest + EQNTipoChiamataAlertPushTest, + EQNTipoChiamataAlertSimulator }; diff --git a/Sources/Earthquake Network/Earthquake Network-Bridging-Header.h b/Sources/Earthquake Network/Earthquake Network-Bridging-Header.h index 7cb39ad..e896805 100644 --- a/Sources/Earthquake Network/Earthquake Network-Bridging-Header.h +++ b/Sources/Earthquake Network/Earthquake Network-Bridging-Header.h @@ -12,3 +12,5 @@ #import "ReteSismiDettagliMappa.h" #import "SettingsBaseViewController.h" #import "PastquakesAnnotation.h" +#import "EQNGeneratoreURLServer.h" +#import "ServerRequest.h" diff --git a/Sources/Earthquake Network/ServerRequest.h b/Sources/Earthquake Network/ServerRequest.h index 52d98d3..1b00989 100644 --- a/Sources/Earthquake Network/ServerRequest.h +++ b/Sources/Earthquake Network/ServerRequest.h @@ -20,7 +20,7 @@ typedef void (^errorCompletionHandler)(NSError * _Nullable error); @property (nonatomic) BOOL isConnect; -+ (instancetype)defaultServerConnectionSingleton; ++ (instancetype)defaultServerConnectionSingleton NS_SWIFT_NAME(default()); - (void)inviaInformazioniAlServerWithURL:(NSURL *)url richiesta:(EQNTipoChiamata)chiamata success:(successCompletionHandler)onSuccess failure:(errorCompletionHandler)onFailure; diff --git a/Sources/Earthquake Network/ServerRequest.m b/Sources/Earthquake Network/ServerRequest.m index 4022fce..71ac1ca 100644 --- a/Sources/Earthquake Network/ServerRequest.m +++ b/Sources/Earthquake Network/ServerRequest.m @@ -112,6 +112,9 @@ case EQNTipoChiamataPosizione: onSuccess(@"success"); break; + case EQNTipoChiamataAlertSimulator: + onSuccess([NSString stringWithFormat:@"%@", JSON]); + break; default: onSuccess(JSON); break; @@ -135,9 +138,8 @@ onSuccess([EQNUtility clearStringMessaggi:newStr]); break; case EQNTipoChiamataCalibrazione: - onSuccess(newStr); - break; case EQNTipoChiamataImpostazioniNotifiche: + case EQNTipoChiamataAlertSimulator: onSuccess(newStr); default: // don't call the callback diff --git a/Sources/Earthquake Network/Storyboards/Base.lproj/Main.storyboard b/Sources/Earthquake Network/Storyboards/Base.lproj/Main.storyboard index 000c66b..715a327 100644 --- a/Sources/Earthquake Network/Storyboards/Base.lproj/Main.storyboard +++ b/Sources/Earthquake Network/Storyboards/Base.lproj/Main.storyboard @@ -3,7 +3,7 @@ - + @@ -2001,7 +2001,7 @@ Sisma rilevato da 10 smartphone -