Files
eqn.ios/Sources/Earthquake Network/Controllers/Seismic Networks/SeismicNetworksViewController.swift
T
2025-02-12 09:06:25 +01:00

842 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]()
/// 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
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
}
// MARK: - Notifications
@objc func didReceiveDownloadCompleteNotification(_ sender: Notification) {
self.openMapIndexPath = nil
DispatchQueue.main.async {
self.refreshUI()
}
}
// MARK: - Private
private func refreshUI() {
elaborateData()
if let saved = UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) as? [Int] {
informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
}
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)
}
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
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 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()
}
}