// // 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 class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private enum CellType { case seismic(EQNSisma) case advertise(GADNativeAd) } private static let SegueIdentifierFilters = "ShowFilters" private static let SegueIdentifierSettings = "ShowSettings" private static let SegueIdentifierSeismicNetworks = "ShowSeismicNetworks" private static let SegueIdentifierCardSettings = "ShowCardSettings" // MARK: - Internal @IBOutlet private weak var tableView: UITableView? @IBOutlet private weak var expandeCollapseButton: UIBarButtonItem! weak var currentMapController: SeismicNetworksMapDetailViewController? /// The ad loader private lazy var adLoader: GADAdLoader = { let adLoader = GADAdLoader( 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]() /// Informations to display on a single cell private var informations = [SeismicNetworkTableViewCell.InformationType]() /// Index path of row with map expanded private var openMapIndexPath: IndexPath? /// Index path of row with weather expanded private var openWeatherIndexPath: IndexPath? // MARK: - View Lifecycle override func viewDidLoad() { super.viewDidLoad() setupUI() refreshUI() // only the first time, show the popup for country selection let alreadyPresented = UserDefaults.standard.bool(forKey: EQNUserDefaultKeyOneShotShowCountry) if !alreadyPresented { performSegue(withIdentifier: Self.SegueIdentifierSettings, sender: nil) UserDefaults.standard.setValue(true, forKey: EQNUserDefaultKeyOneShotShowCountry) } NotificationCenter.default.addObserver(self, selector: #selector(didReceiveDownloadCompleteNotification(_:)), name: .EQNDownloadDataDidComplete, object: nil) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) loadData(forced: false) } private func setupUI() { title = NSLocalizedString("tab_official", comment: "") tableView?.estimatedRowHeight = 300.0 tableView?.rowHeight = UITableView.automaticDimension tableView?.register(SeismicNetworkTableViewCell.self, forCellReuseIdentifier: SeismicNetworkTableViewCell.Identifier) tableView?.register(SeismicNetworkAdvertiseTableViewCell.self, forCellReuseIdentifier: SeismicNetworkAdvertiseTableViewCell.Identifier) tableView?.emptyDataSetSource = self } 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.SegueIdentifierSettings: if let controller = segue.destination as? SeismicSettingsViewController { controller.delegate = self } case Self.SegueIdentifierSeismicNetworks: if let navController = segue.destination as? UINavigationController, let controller = navController.viewControllers.first as? SeismicSettingsNetworksViewController { 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 present(controller, animated: true, completion: nil) self.currentMapController = controller } // MARK: - Notifications @objc func didReceiveDownloadCompleteNotification(_ sender: Notification) { self.openMapIndexPath = nil self.openWeatherIndexPath = 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() } private func loadAd() { adLoader.load(GADRequest()) } 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) } // if is not a pro user, show an advertise if !EQNPurchaseUtility.isProVersionEnabled() { loadAd() } // if a map detail is presented, update its data if let mapController = currentMapController { mapController.updateSeismics(filteredSeismics) } } private func getSeismics() -> [EQNSisma] { let seismics = rows.compactMap { (row) -> EQNSisma? in if case .seismic(let seismic) = row { return seismic } return nil } return seismics } // MARK: - Actions @IBAction func refreshDataTapped(_ sender: Any) { loadData(forced: true) } @IBAction func openFilterTapped(_ sender: Any) { performSegue(withIdentifier: Self.SegueIdentifierFilters, sender: nil) } @IBAction func openSettingsTapped(_ sender: Any) { performSegue(withIdentifier: Self.SegueIdentifierSettings, 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(withIdentifier: SeismicNetworkTableViewCell.Identifier, for: indexPath) as! SeismicNetworkTableViewCell var type = SeismicNetworkTableViewCell.DisplayType.normal if openMapIndexPath == indexPath { type = .mapExpanded } else if openWeatherIndexPath == indexPath { type = .weatherExpanded } cell.configure(with: seismic, type: type, informations: informations) cell.delegate = self return cell case .advertise(let nativeAd): let cell = tableView.dequeueReusableCell(withIdentifier: SeismicNetworkAdvertiseTableViewCell.Identifier, for: indexPath) as! SeismicNetworkAdvertiseTableViewCell 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: - Private private func openCalendar(for seismic: EQNSisma) { checkCalendarPermission { self.createCalendarEvent(for: seismic) } failure: { let alert = UIAlertController(title: NSLocalizedString("Attenzione", 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: GADNativeAdLoaderDelegate { func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADNativeAd) { print("[GADAdLoader] didReceive") let adPosition = min(3, rows.count) rows.insert(.advertise(nativeAd), at: adPosition) tableView?.reloadData() } func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: Error) { // nope print("[GADAdLoader] 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.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 seismicNetworkCellDidTapWeather(_ cell: SeismicNetworkTableViewCell, hasValidWeatherData: Bool) { guard let index = tableView?.indexPath(for: cell) else { return } if !hasValidWeatherData { let alert = UIAlertController(title: NSLocalizedString("Attenzione", comment: ""), message: NSLocalizedString("weather_nodata", comment: ""), preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default)) present(alert, animated: true) return } let indexToReloads = [openMapIndexPath, openWeatherIndexPath, index].compactMap { $0 } openWeatherIndexPath = index openMapIndexPath = nil tableView?.reloadRows(at: indexToReloads, with: .automatic) } func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) { guard let index = tableView?.indexPath(for: cell) else { return } let indexToReloads = [openMapIndexPath, openWeatherIndexPath, index].compactMap { $0 } openMapIndexPath = index openWeatherIndexPath = nil 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, openWeatherIndexPath, index].compactMap { $0 } openMapIndexPath = nil openWeatherIndexPath = 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: SeismicSettingsViewControllerDelegate { func seismicSettingsControllerDidComplete(_ controller: SeismicSettingsViewController) { refreshUI() } func seismicSettingsControllerWillOpenProviders(_ controller: SeismicSettingsViewController) { performSegue(withIdentifier: Self.SegueIdentifierSeismicNetworks, sender: nil) } } extension SeismicNetworksViewController: SeismicSettingsNetworksViewControllerDelegate { func seismicSettingsNetworksControllerDidComplete(_ controller: SeismicSettingsNetworksViewController) { refreshUI() } } extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelegate { func seismicCardSettingsDidComplete(_ controller: SeismicCardSettingsViewController) { refreshUI() } } extension SeismicNetworksViewController: DZNEmptyDataSetSource { func title(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { let attributes = [ NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body) ] let string = NSAttributedString(string: NSLocalizedString("filter_empty", comment: ""), attributes: attributes) return string } } extension SeismicNetworksViewController: SeismicNetworksMapDetailViewControllerDelegate { func seismicNetworksMapDetailControllerWillUpdateData(_ controller: SeismicNetworksMapDetailViewController, needsDataUpdate: Bool) { loadData(forced: needsDataUpdate) refreshUI() } }