853 lines
33 KiB
Swift
853 lines
33 KiB
Swift
//
|
|
// SeismicNetworksViewController.swift
|
|
// Earthquake Network
|
|
//
|
|
// Created by Busi Andrea on 22/09/2020.
|
|
// Copyright © 2020 Earthquake Network. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import EventKitUI
|
|
import DZNEmptyDataSet
|
|
import Shogun
|
|
|
|
class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
|
|
|
private enum CellType {
|
|
case seismic(EQNSisma)
|
|
case advertise(NativeAd)
|
|
}
|
|
|
|
private static let SegueIdentifierFilters = "ShowFilters"
|
|
private static let SegueIdentifierCardSettings = "ShowCardSettings"
|
|
|
|
// MARK: - Internal
|
|
|
|
/// The ad loader
|
|
private lazy var adLoader: AdLoader = {
|
|
let adLoader = AdLoader(
|
|
adUnitID: EQNAdMobAppIdNativeBanner, rootViewController: self,
|
|
adTypes: [.native], options: nil)
|
|
adLoader.delegate = self
|
|
return adLoader
|
|
}()
|
|
|
|
/// Cells to display (must be seismics or ad banners)
|
|
private var rows = [CellType]()
|
|
private var seismicViewModels = [SeismicNetworkViewModel]()
|
|
/// Informations to display on a single cell
|
|
private var informations: [SeismicNetworkTableViewCell.InformationType] {
|
|
get { AppPreferences.shared.seismicNetworksInformations }
|
|
set { AppPreferences.shared.seismicNetworksInformations = newValue }
|
|
}
|
|
/// Index path of row with map expanded
|
|
private var openMapIndexPath: IndexPath?
|
|
/// Push notification opened by the user
|
|
private var openedPushNotification: EQNOfficialPushNotification? {
|
|
didSet {
|
|
scrollToOpenedSeismic = true
|
|
}
|
|
}
|
|
private var scrollToOpenedSeismic = false
|
|
/// Current displayed controller with map
|
|
private weak var currentMapController: SeismicNetworksMapDetailViewController?
|
|
/// Keep track of the current cell at the center
|
|
private var currentCenteredIndexPath: IndexPath?
|
|
|
|
// MARK: - UI
|
|
|
|
@IBOutlet private weak var expandeCollapseButton: UIBarButtonItem!
|
|
@IBOutlet private weak var sortButton: UIBarButtonItem!
|
|
private var tableViewTopConstraint: NSLayoutConstraint?
|
|
|
|
private lazy var tableView: UITableView = {
|
|
let tableView = UITableView(frame: .zero, style: .plain)
|
|
tableView.translatesAutoresizingMaskIntoConstraints = false
|
|
tableView.delegate = self
|
|
tableView.dataSource = self
|
|
tableView.showsVerticalScrollIndicator = false
|
|
return tableView
|
|
}()
|
|
|
|
private lazy var filterChangedView: UIView = {
|
|
let view = UIView(frame: .zero)
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.backgroundColor = AppTheme.Colors.pureBlue
|
|
|
|
view.addSubview(filterChangedLabel)
|
|
filterChangedLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
|
|
filterChangedLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
|
|
filterChangedLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
|
|
filterChangedLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
|
|
|
|
return view
|
|
}()
|
|
|
|
private lazy var filterChangedLabel: UILabel = {
|
|
let label = UILabel(frame: .zero)
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
label.textColor = .white
|
|
label.font = .preferredFont(forTextStyle: .subheadline)
|
|
label.numberOfLines = 0
|
|
label.textAlignment = .center
|
|
return label
|
|
}()
|
|
|
|
private lazy var scrollIndicatorView: SeismicNetworkScrollIndicatorView = {
|
|
let view = SeismicNetworkScrollIndicatorView(frame: .zero)
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.backgroundColor = .clear
|
|
view.layer.borderColor = AppTheme.Colors.gray.cgColor
|
|
return view
|
|
}()
|
|
|
|
// MARK: - View Lifecycle
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
setupUI()
|
|
configureUI()
|
|
checkForLocation()
|
|
refreshUI()
|
|
configureFilterView(isVisible: false)
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveDownloadCompleteNotification(_:)), name: .EQNDownloadDataDidComplete, object: nil)
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
loadData(forced: false)
|
|
|
|
// check for a push to manage
|
|
if let notification = EQNOfficialPushNotification.stored() {
|
|
manageFilter(for: notification)
|
|
self.openedPushNotification = notification
|
|
EQNOfficialPushNotification.removeStored()
|
|
} else {
|
|
configureFilterView(isVisible: false)
|
|
self.openedPushNotification = nil
|
|
}
|
|
|
|
tableView.reloadData()
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func setupUI() {
|
|
view.addSubview(scrollIndicatorView)
|
|
view.addSubview(tableView)
|
|
|
|
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.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
|
|
}
|
|
|
|
private func setupFilterView(isVisible: Bool) {
|
|
if isVisible && filterChangedView.superview == nil {
|
|
view.addSubview(filterChangedView)
|
|
tableViewTopConstraint?.isActive = false
|
|
filterChangedView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
|
filterChangedView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
|
filterChangedView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
|
tableViewTopConstraint = filterChangedView.bottomAnchor.constraint(equalTo: tableView.topAnchor)
|
|
tableViewTopConstraint?.isActive = true
|
|
} else {
|
|
filterChangedView.removeFromSuperview()
|
|
tableViewTopConstraint?.isActive = false
|
|
tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: view.topAnchor)
|
|
tableViewTopConstraint?.isActive = true
|
|
}
|
|
}
|
|
|
|
private func configureFilterView(isVisible: Bool) {
|
|
setupFilterView(isVisible: isVisible)
|
|
if isVisible {
|
|
filterChangedLabel.text = NSLocalizedString("official_filter_changed", comment: "")
|
|
filterChangedView.backgroundColor = AppTheme.Colors.pureBlue
|
|
} else {
|
|
filterChangedLabel.text = nil
|
|
filterChangedView.backgroundColor = .white
|
|
}
|
|
}
|
|
|
|
private func configureUI() {
|
|
title = NSLocalizedString("tab_official", comment: "").capitalized
|
|
|
|
tableView.estimatedRowHeight = 300.0
|
|
tableView.rowHeight = UITableView.automaticDimension
|
|
tableView.registerCell(for: SeismicNetworkTableViewCell.self)
|
|
tableView.registerCell(for: SeismicNetworkAdvertiseTableViewCell.self)
|
|
tableView.emptyDataSetSource = self
|
|
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()
|
|
}
|
|
}
|
|
|
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
|
switch segue.identifier {
|
|
case Self.SegueIdentifierFilters:
|
|
if let controller = segue.destination as? SeismicFiltersViewController {
|
|
controller.delegate = self
|
|
}
|
|
case Self.SegueIdentifierCardSettings:
|
|
if let controller = segue.destination as? SeismicCardSettingsViewController {
|
|
controller.delegate = self
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
private func showMapDetail(for seismic: EQNSisma) {
|
|
let seismics = getSeismics()
|
|
let controller = SeismicNetworksMapDetailViewController(seismic: seismic, allSeismics: seismics)
|
|
controller.delegate = self
|
|
let navController = UINavigationController(rootViewController: controller)
|
|
present(navController, animated: true, completion: nil)
|
|
|
|
self.currentMapController = controller
|
|
}
|
|
|
|
private func showIntensityMap(for seismic: EQNSisma) {
|
|
let controller = SeismicNetworksIntensityMapViewController(seismic: seismic)
|
|
let navController = UINavigationController(rootViewController: controller)
|
|
present(navController, animated: true)
|
|
}
|
|
|
|
// MARK: - Notifications
|
|
|
|
@objc func didReceiveDownloadCompleteNotification(_ sender: Notification) {
|
|
self.openMapIndexPath = nil
|
|
|
|
DispatchQueue.main.async {
|
|
self.refreshUI()
|
|
}
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func refreshUI() {
|
|
elaborateData()
|
|
|
|
if informations.contains(.buttons) {
|
|
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-collapse")
|
|
} else {
|
|
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-expand")
|
|
}
|
|
|
|
tableView.reloadData()
|
|
updateCenterCellIndexPath()
|
|
|
|
if scrollToOpenedSeismic, let index = getSeismics().firstIndex(where: { isSeismicToHighlight(seismic: $0) }) {
|
|
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
|
|
scrollToOpenedSeismic = false
|
|
}
|
|
}
|
|
|
|
private func loadAd() {
|
|
adLoader.load(Request())
|
|
}
|
|
|
|
private func loadData(forced: Bool) {
|
|
EQNManager.manager().refreshSeismicData(forced: forced)
|
|
}
|
|
|
|
private func elaborateData() {
|
|
// show filtered seismic based on user settings
|
|
let allSeismics = EQNManager.manager().retiSismiche
|
|
let filteredSeismics = EQNSeismic.shared.filterSeismicList(allSeismics ?? [])
|
|
rows = filteredSeismics.map { .seismic($0) }
|
|
seismicViewModels = filteredSeismics.map(SeismicNetworkViewModel.init)
|
|
|
|
#if ADS_ENABLED
|
|
// if is not a pro user, show an advertise
|
|
if !EQNPurchaseUtility.isProVersionEnabled() {
|
|
loadAd()
|
|
}
|
|
#endif
|
|
|
|
// if a map detail is presented, update its data
|
|
if let mapController = currentMapController {
|
|
mapController.updateSeismics(filteredSeismics)
|
|
}
|
|
|
|
scrollIndicatorView.seismics = seismicViewModels
|
|
currentCenteredIndexPath = nil
|
|
}
|
|
|
|
private func getSeismics() -> [EQNSisma] {
|
|
let seismics = rows.compactMap { (row) -> EQNSisma? in
|
|
if case .seismic(let seismic) = row {
|
|
return seismic
|
|
}
|
|
return nil
|
|
}
|
|
return seismics
|
|
}
|
|
|
|
private func changeSort(to sort: EQNSeismic.Sort) {
|
|
EQNSeismic.shared.sort = sort
|
|
EQNSeismic.shared.saveFilters()
|
|
|
|
setupSortMenu()
|
|
refreshUI()
|
|
}
|
|
|
|
private func manageFilter(
|
|
for notification: EQNOfficialPushNotification
|
|
) {
|
|
//gestisco i filtri solo se la posizione dell'utente è nota
|
|
guard let userPosition = EQNUser.default().lastPosition else {
|
|
return
|
|
}
|
|
|
|
var filter_type = EQNSeismic.shared.filterOption
|
|
var filter_radius = Double(EQNSeismic.shared.maximumDistance) ?? 0
|
|
var filter_min_magnitude = Double(EQNSeismic.shared.minimumMagnitude) ?? 0
|
|
|
|
var filter_changed = false
|
|
|
|
//recupero i dati del sisma notificato
|
|
let notification_magnitude = notification.magnitude
|
|
let notification_latitude = notification.coordinate.coordinate.latitude
|
|
let notification_longitude = notification.coordinate.coordinate.longitude
|
|
|
|
//distanza tra smartphone utente e sisma notificato
|
|
let locationNotification = CLLocation(latitude: notification_latitude, longitude: notification_longitude)
|
|
let distance = userPosition.distance(from: locationNotification) / 1_000
|
|
|
|
//verifico se il sisma è significativo in base alla definizione di significativo
|
|
var is_significant = true
|
|
if notification_magnitude < 7.0 && distance > 2000 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 6.5 && distance > 1600 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 6.0 && distance > 1300 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 5.5 && distance > 1000 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 5.0 && distance > 700 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 4.5 && distance > 500 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 4.0 && distance > 350 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 3.5 && distance > 200 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 3.0 && distance > 125 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 2.5 && distance > 70 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 2.0 && distance > 35 {
|
|
is_significant = false
|
|
} else if notification_magnitude < 1.5 && distance > 20 {
|
|
is_significant = false
|
|
}
|
|
|
|
//verifico se devo modificare il filtro scelto dall'utente
|
|
if filter_type == .inRadius { //filter_type=0 è il filtro basato su raggio e magnitudo
|
|
if distance > 2000 && is_significant {
|
|
filter_type = .positionRelevant //passo al filtro che mostra i sismi significativi (perché il raggio massimo del filtro basato sul raggio è 2000)
|
|
updateFilter(type: filter_type)
|
|
filter_changed = true
|
|
}
|
|
else if distance > 2000 && notification_magnitude >= 2.0 {
|
|
filter_type = .worldWide //passo al filtro che mostra tutti i sismi nel mondo
|
|
updateFilter(type: filter_type)
|
|
filter_changed = true
|
|
}
|
|
else {
|
|
//verifico se devo cambiare il raggio del filtro
|
|
if distance > filter_radius {
|
|
if distance > 1500 {
|
|
filter_radius = 2000
|
|
} else if distance > 1000 {
|
|
filter_radius = 1500
|
|
} else if distance > 750 {
|
|
filter_radius = 1000
|
|
} else if distance > 500 {
|
|
filter_radius = 750
|
|
} else if distance > 250 {
|
|
filter_radius = 500
|
|
} else if distance > 100 {
|
|
filter_radius = 250
|
|
}
|
|
updateFilter(radius: filter_radius)
|
|
|
|
filter_changed = true
|
|
}
|
|
//verifico se devo cambiare la mgnitudo del filtro
|
|
if notification_magnitude < filter_min_magnitude {
|
|
if notification_magnitude < 1.0 {
|
|
filter_min_magnitude = 0.0
|
|
} else if notification_magnitude < 2.0 {
|
|
filter_min_magnitude = 1.0
|
|
} else if notification_magnitude < 3.0 {
|
|
filter_min_magnitude = 2.0
|
|
} else if notification_magnitude < 4.0 {
|
|
filter_min_magnitude = 3.0
|
|
} else if notification_magnitude < 5.0 {
|
|
filter_min_magnitude = 4.0
|
|
} else if notification_magnitude < 6.0 {
|
|
filter_min_magnitude = 5.0
|
|
}
|
|
|
|
filter_changed = true
|
|
updateFilter(magnitude: filter_min_magnitude)
|
|
}
|
|
}
|
|
}
|
|
|
|
if filter_type == .positionRelevant && !is_significant && distance <= 2000 {
|
|
filter_type = .inRadius //passo a filtro basato su raggio e magnitudo
|
|
updateFilter(type: filter_type)
|
|
|
|
if distance > filter_radius {
|
|
if distance>1500 {
|
|
filter_radius = 2000
|
|
}
|
|
else if distance > 1000 {
|
|
filter_radius = 1500
|
|
}
|
|
else if distance > 750 {
|
|
filter_radius = 1000
|
|
}
|
|
else if distance > 500 {
|
|
filter_radius = 750
|
|
}
|
|
else if distance > 250 {
|
|
filter_radius = 500
|
|
}
|
|
else if distance > 100 {
|
|
filter_radius = 250
|
|
}
|
|
updateFilter(radius: filter_radius)
|
|
}
|
|
if notification_magnitude < filter_min_magnitude {
|
|
if notification_magnitude < 1.0 {
|
|
filter_min_magnitude = 0.0
|
|
}
|
|
else if notification_magnitude < 2.0 {
|
|
filter_min_magnitude = 1.0
|
|
}
|
|
else if notification_magnitude < 3.0 {
|
|
filter_min_magnitude = 2.0
|
|
}
|
|
else if notification_magnitude < 4.0 {
|
|
filter_min_magnitude = 3.0
|
|
}
|
|
else if notification_magnitude < 5.0 {
|
|
filter_min_magnitude = 4.0
|
|
}
|
|
else if notification_magnitude < 6.0 {
|
|
filter_min_magnitude = 5.0
|
|
}
|
|
updateFilter(magnitude: filter_min_magnitude)
|
|
}
|
|
|
|
filter_changed = true
|
|
}
|
|
|
|
if filter_type == .positionRelevant && !is_significant && distance > 2000 && notification_magnitude >= 2.0 {
|
|
filter_type = .worldWide //passo a filtro che mostra tutti i sismi nel mondo
|
|
updateFilter(type: filter_type)
|
|
filter_changed = true
|
|
}
|
|
|
|
if filter_type == .worldWide && notification_magnitude < 2.0 && is_significant {
|
|
filter_type = .positionRelevant //passo a filtro sismi significativi
|
|
updateFilter(type: filter_type)
|
|
filter_changed = true
|
|
}
|
|
|
|
if filter_type == .worldWide && notification_magnitude < 2.0 && distance <= 2000 && !is_significant {
|
|
filter_type = .inRadius
|
|
updateFilter(type: filter_type)
|
|
|
|
if distance > filter_radius {
|
|
if distance > 1500 {
|
|
filter_radius = 2000
|
|
}
|
|
else if distance > 1000 {
|
|
filter_radius = 1500
|
|
}
|
|
else if distance > 750 {
|
|
filter_radius = 1000
|
|
}
|
|
else if distance > 500 {
|
|
filter_radius = 750
|
|
}
|
|
else if distance > 250 {
|
|
filter_radius = 500
|
|
}
|
|
else if distance > 100 {
|
|
filter_radius = 250
|
|
}
|
|
updateFilter(radius: filter_radius)
|
|
}
|
|
if notification_magnitude < filter_min_magnitude {
|
|
if notification_magnitude < 1.0 {
|
|
filter_min_magnitude = 0.0
|
|
}
|
|
else if notification_magnitude < 2.0 {
|
|
filter_min_magnitude = 1.0
|
|
}
|
|
else if notification_magnitude < 3.0 {
|
|
filter_min_magnitude = 2.0
|
|
}
|
|
else if notification_magnitude < 4.0 {
|
|
filter_min_magnitude = 3.0
|
|
}
|
|
else if notification_magnitude < 5.0 {
|
|
filter_min_magnitude = 4.0
|
|
}
|
|
else if notification_magnitude < 6.0 {
|
|
filter_min_magnitude = 5.0
|
|
}
|
|
updateFilter(magnitude: filter_min_magnitude)
|
|
}
|
|
|
|
filter_changed = true
|
|
}
|
|
|
|
//mostro all'utente un messaggio per avvisarlo che i filtri sono stati modificati
|
|
configureFilterView(isVisible: filter_changed)
|
|
if filter_changed {
|
|
loadData(forced: true)
|
|
}
|
|
}
|
|
|
|
private func updateFilter(
|
|
type: EQNSeismic.FilterType? = nil,
|
|
radius: Double? = nil,
|
|
magnitude: Double? = nil
|
|
) {
|
|
if let type {
|
|
EQNSeismic.shared.filterOption = type
|
|
}
|
|
if let radius {
|
|
EQNSeismic.shared.maximumDistance = String(format: "%.0f", radius)
|
|
}
|
|
if let magnitude {
|
|
EQNSeismic.shared.minimumMagnitude = String(format: "%.1f", magnitude)
|
|
}
|
|
EQNSeismic.shared.saveFilters()
|
|
}
|
|
|
|
private func isLocationAvailable() -> Bool {
|
|
EQNUser.default().lastPosition != nil
|
|
}
|
|
|
|
private func isSeismicToHighlight(seismic: EQNSisma) -> Bool {
|
|
guard let notification = openedPushNotification else {
|
|
return false
|
|
}
|
|
|
|
guard let seismicDate = seismic.date, let notificationDate = notification.date else { return false }
|
|
|
|
let deltaTime = abs(seismicDate.timeIntervalSince(notificationDate))
|
|
let magnitudeRatio = seismic.magnitude.doubleValue / notification.magnitude
|
|
let latitudeDiff = abs(seismic.coordinate.coordinate.latitude - notification.coordinate.coordinate.latitude)
|
|
let longitudeDiff = abs(seismic.coordinate.coordinate.longitude - notification.coordinate.coordinate.longitude)
|
|
if deltaTime <= 120 && magnitudeRatio > 0.8 && magnitudeRatio < 1.2 && latitudeDiff < 1 && longitudeDiff < 1 { // secondi?
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
private func getCenterCellIndexPath() -> IndexPath? {
|
|
let centerPoint = CGPoint(x: tableView.bounds.midX, y: tableView.bounds.midY)
|
|
if let indexPath = tableView.indexPathForRow(at: centerPoint) {
|
|
return indexPath
|
|
}
|
|
|
|
// Se il metodo diretto fallisce, cerchiamo la cella più vicina
|
|
if let visibleIndexPaths = tableView.indexPathsForVisibleRows {
|
|
return visibleIndexPaths.min(by: { (indexPath1, indexPath2) -> Bool in
|
|
let rect1 = tableView.rectForRow(at: indexPath1)
|
|
let rect2 = tableView.rectForRow(at: indexPath2)
|
|
let distance1 = abs(rect1.midY - centerPoint.y)
|
|
let distance2 = abs(rect2.midY - centerPoint.y)
|
|
return distance1 < distance2
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
private func updateCenterCellIndexPath() {
|
|
if let centerIndexPath = getCenterCellIndexPath(), centerIndexPath != currentCenteredIndexPath {
|
|
currentCenteredIndexPath = centerIndexPath
|
|
|
|
let row = rows[centerIndexPath.row]
|
|
if case .seismic = row, seismicViewModels.count > centerIndexPath.row {
|
|
scrollIndicatorView.highlighted = seismicViewModels[centerIndexPath.row]
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
@IBAction func refreshDataTapped(_ sender: Any) {
|
|
loadData(forced: true)
|
|
}
|
|
|
|
@IBAction func openFilterTapped(_ sender: Any) {
|
|
performSegue(withIdentifier: Self.SegueIdentifierFilters, sender: nil)
|
|
}
|
|
|
|
@IBAction func collapseExpandTapped(_ sender: Any) {
|
|
if informations.contains(.buttons) {
|
|
informations.removeAll(where: { $0 == .buttons })
|
|
} else {
|
|
informations.append(.buttons)
|
|
}
|
|
|
|
refreshUI()
|
|
}
|
|
|
|
// MARK: - Table view delegate and data source
|
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
rows.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let row = rows[indexPath.row]
|
|
switch row {
|
|
case .seismic(let seismic):
|
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkTableViewCell.self, for: indexPath)
|
|
|
|
var type = SeismicNetworkTableViewCell.DisplayType.normal
|
|
if openMapIndexPath == indexPath {
|
|
type = .mapExpanded
|
|
}
|
|
|
|
let isPushSelected = isSeismicToHighlight(seismic: seismic)
|
|
cell.configure(with: seismic, type: type, informations: informations, isPushSelected: isPushSelected)
|
|
cell.delegate = self
|
|
return cell
|
|
case .advertise(let nativeAd):
|
|
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkAdvertiseTableViewCell.self, for: indexPath)
|
|
cell.loadNativeAd(nativeAd)
|
|
return cell
|
|
}
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
let row = rows[indexPath.row]
|
|
|
|
if case .seismic(let seismic) = row {
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
showMapDetail(for: seismic)
|
|
}
|
|
}
|
|
|
|
// MARK: - UIScrollViewDelegate
|
|
|
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
updateCenterCellIndexPath()
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func openCalendar(for seismic: EQNSisma) {
|
|
checkCalendarPermission {
|
|
self.createCalendarEvent(for: seismic)
|
|
} failure: {
|
|
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
|
|
message: NSLocalizedString("calendar_missing_permission", comment: ""),
|
|
preferredStyle: .alert)
|
|
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .default))
|
|
self.present(alert, animated: true)
|
|
}
|
|
}
|
|
|
|
private func checkCalendarPermission(success: @escaping () -> Void, failure: @escaping () -> Void) {
|
|
let authorization = EKEventStore.authorizationStatus(for: .event)
|
|
switch authorization {
|
|
case .notDetermined:
|
|
let eventStore = EKEventStore()
|
|
eventStore.requestAccess(to: .event) { (granted, error) in
|
|
DispatchQueue.main.async {
|
|
if granted {
|
|
success()
|
|
} else {
|
|
failure()
|
|
}
|
|
}
|
|
}
|
|
case .authorized:
|
|
success()
|
|
default:
|
|
failure()
|
|
}
|
|
}
|
|
|
|
private func createCalendarEvent(for seismic: EQNSisma) {
|
|
let eventStore = EKEventStore()
|
|
|
|
// calendar event
|
|
let event = EKEvent(eventStore: eventStore)
|
|
event.title = seismic.place
|
|
event.startDate = seismic.date
|
|
event.endDate = seismic.date
|
|
event.notes = String(format: NSLocalizedString("calendar_description_nogeo_km", comment: ""), seismic.magnitude, seismic.depth)
|
|
|
|
// controller to present
|
|
let eventVC = EKEventEditViewController()
|
|
eventVC.editViewDelegate = self
|
|
eventVC.eventStore = eventStore
|
|
eventVC.event = event
|
|
|
|
present(eventVC, animated: true)
|
|
}
|
|
}
|
|
|
|
extension SeismicNetworksViewController: NativeAdLoaderDelegate {
|
|
func adLoader(_ adLoader: AdLoader, didReceive nativeAd: NativeAd) {
|
|
print("[AdLoader] didReceive")
|
|
|
|
let adPosition = min(3, rows.count)
|
|
rows.insert(.advertise(nativeAd), at: adPosition)
|
|
tableView.reloadData()
|
|
}
|
|
|
|
func adLoader(_ adLoader: AdLoader, didFailToReceiveAdWithError error: Error) {
|
|
// nope
|
|
print("[AdLoader] didFailToReceiveAdWithError: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
|
|
|
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) {
|
|
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
|
|
|
// create a snapshot of the cell and share with default share sheet
|
|
let snapshot = cell.contentView.createSnapshot()
|
|
|
|
// text to share with the snapshot
|
|
let shareHashtag = NSLocalizedString("share_hashtag", comment: "")
|
|
let magnitude = String(format: "%.1f", seismic.magnitude.doubleValue)
|
|
let location = seismic.place
|
|
let notified = NSLocalizedString("share_notified", comment: "")
|
|
let shareMessage = "\(shareHashtag) M\(magnitude), \(location). \(notified)"
|
|
|
|
let controller = UIActivityViewController(activityItems: [snapshot, shareMessage], applicationActivities: [])
|
|
present(controller, animated: true)
|
|
}
|
|
|
|
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) {
|
|
guard let index = tableView.indexPath(for: cell) else { return }
|
|
|
|
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
|
|
|
openMapIndexPath = index
|
|
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
|
}
|
|
|
|
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell) {
|
|
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
|
|
|
showMapDetail(for: seismic)
|
|
}
|
|
|
|
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkTableViewCell) {
|
|
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
|
|
|
showIntensityMap(for: seismic)
|
|
}
|
|
|
|
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) {
|
|
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
|
|
|
|
openCalendar(for: seismic)
|
|
}
|
|
|
|
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell) {
|
|
performSegue(withIdentifier: Self.SegueIdentifierCardSettings, sender: nil)
|
|
}
|
|
|
|
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell) {
|
|
guard let index = tableView.indexPath(for: cell) else { return }
|
|
|
|
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
|
|
|
|
openMapIndexPath = nil
|
|
tableView.reloadRows(at: indexToReloads, with: .automatic)
|
|
}
|
|
}
|
|
|
|
|
|
extension SeismicNetworksViewController: EKEventEditViewDelegate {
|
|
func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
|
|
dismiss(animated: true, completion: nil)
|
|
}
|
|
}
|
|
|
|
extension SeismicNetworksViewController: SeismicFiltersViewControllerDelegate {
|
|
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
|
|
loadData(forced: controller.needsDataUpdate)
|
|
refreshUI()
|
|
}
|
|
}
|
|
|
|
extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelegate {
|
|
func seismicCardSettingsDidComplete(_ controller: SeismicCardSettingsViewController) {
|
|
refreshUI()
|
|
}
|
|
}
|
|
|
|
extension SeismicNetworksViewController: DZNEmptyDataSetSource {
|
|
func title(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
|
|
let text = EQNSeismic.shared.filterOption == .positionRelevant
|
|
? NSLocalizedString("filter_empty_relevant", comment: "")
|
|
: NSLocalizedString("filter_empty_area", comment: "")
|
|
let attributes = [ NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body) ]
|
|
let string = NSAttributedString(string: text, attributes: attributes)
|
|
return string
|
|
}
|
|
}
|
|
|
|
extension SeismicNetworksViewController: SeismicNetworksMapDetailViewControllerDelegate {
|
|
func seismicNetworksMapDetailControllerWillUpdateData(_ controller: SeismicNetworksMapDetailViewController, needsDataUpdate: Bool) {
|
|
loadData(forced: needsDataUpdate)
|
|
refreshUI()
|
|
}
|
|
}
|