feat: New beautifult layout for seismic cards
This commit is contained in:
@@ -92,7 +92,10 @@
|
||||
DC08803F24F5A89000186D97 /* SettingEnableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC08803E24F5A89000186D97 /* SettingEnableTableViewCell.swift */; };
|
||||
DC08804124F5B41400186D97 /* SettingSliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC08804024F5B41400186D97 /* SettingSliderTableViewCell.swift */; };
|
||||
DC0E551324F8063300D54270 /* SettingSegmentedTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0E551224F8063300D54270 /* SettingSegmentedTableViewCell.swift */; };
|
||||
DC105641251E7ECE002579BB /* UIFont+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC105640251E7ECE002579BB /* UIFont+Extensions.swift */; };
|
||||
DC27EB2F24F6EBE000ACBFE0 /* SettingsSeismicNetworksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC27EB2E24F6EBE000ACBFE0 /* SettingsSeismicNetworksViewController.swift */; };
|
||||
DC2814302519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28142F2519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift */; };
|
||||
DC2814382519C56100C1AFF7 /* SeismicNetworksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2814372519C56100C1AFF7 /* SeismicNetworksViewController.swift */; };
|
||||
DC3ADD3924CB2F3D00737919 /* alert_star_trek.wav in Resources */ = {isa = PBXBuildFile; fileRef = 8CF12CC721DE43A400613AC5 /* alert_star_trek.wav */; };
|
||||
DC3BA11124D1A9C90062EE7F /* SubscriptionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3BA11024D1A9C90062EE7F /* SubscriptionsViewController.swift */; };
|
||||
DC3CE50A250EB7A8005A7DD5 /* EQNGenericPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3CE509250EB7A8005A7DD5 /* EQNGenericPickerViewController.swift */; };
|
||||
@@ -296,7 +299,10 @@
|
||||
DC08803E24F5A89000186D97 /* SettingEnableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingEnableTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DC08804024F5B41400186D97 /* SettingSliderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingSliderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DC0E551224F8063300D54270 /* SettingSegmentedTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingSegmentedTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DC105640251E7ECE002579BB /* UIFont+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extensions.swift"; sourceTree = "<group>"; };
|
||||
DC27EB2E24F6EBE000ACBFE0 /* SettingsSeismicNetworksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSeismicNetworksViewController.swift; sourceTree = "<group>"; };
|
||||
DC28142F2519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DC2814372519C56100C1AFF7 /* SeismicNetworksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworksViewController.swift; sourceTree = "<group>"; };
|
||||
DC3BA11024D1A9C90062EE7F /* SubscriptionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsViewController.swift; sourceTree = "<group>"; };
|
||||
DC3CE509250EB7A8005A7DD5 /* EQNGenericPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNGenericPickerViewController.swift; sourceTree = "<group>"; };
|
||||
DC414C0024CDA09A008D9AE4 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = "<group>"; };
|
||||
@@ -577,16 +583,34 @@
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DC10563F251E7EC0002579BB /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DC105640251E7ECE002579BB /* UIFont+Extensions.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DC141968250E769B0059E060 /* Seismic Networks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DC28142E2519C21400C1AFF7 /* Cells */,
|
||||
DC974AFE251748B300A139EC /* SeismicFiltersViewController.swift */,
|
||||
DCB45BC7250E86E100DB2D0C /* SeismicSettingsViewController.swift */,
|
||||
DC65B390250F243E00251693 /* SeismicSettingsNetworksViewController.swift */,
|
||||
DC2814372519C56100C1AFF7 /* SeismicNetworksViewController.swift */,
|
||||
);
|
||||
path = "Seismic Networks";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DC28142E2519C21400C1AFF7 /* Cells */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DC28142F2519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift */,
|
||||
);
|
||||
path = Cells;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DC3ADD2A24CB1E6600737919 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -625,6 +649,7 @@
|
||||
DC3ADD2F24CB1EFB00737919 /* Libs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DC10563F251E7EC0002579BB /* Extensions */,
|
||||
8CF66054214C566A009F4314 /* Reachability.h */,
|
||||
8CF66056214C566A009F4314 /* Reachability.m */,
|
||||
8C7A3B65225A5EA40045B266 /* NSDictionary+BVJSONString.h */,
|
||||
@@ -1045,6 +1070,7 @@
|
||||
DCC23DEF24D28F58003A2404 /* EQNEdgeInsetLabel.swift in Sources */,
|
||||
8CCE165121E7BAEC00173CD9 /* EQNNotificheReteSismiche.m in Sources */,
|
||||
DC52B8A224FC145500ABEBA6 /* SettingsBaseViewController.m in Sources */,
|
||||
DC2814382519C56100C1AFF7 /* SeismicNetworksViewController.swift in Sources */,
|
||||
8CF4F4DB216D44930057110B /* EQNPastquakes.m in Sources */,
|
||||
8CCE165821EB1E0000173CD9 /* SettingsSeismicNetworkAlertsViewController.m in Sources */,
|
||||
8CCE165521EA378800173CD9 /* SettingsUserReportAlertsViewController.m in Sources */,
|
||||
@@ -1058,6 +1084,7 @@
|
||||
8C4E343F215012FA008B0D2A /* EQNManager.m in Sources */,
|
||||
DCAA913F24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift in Sources */,
|
||||
DCF9E14F24F6EA07002B6B1D /* EQNSeismicNetwork.swift in Sources */,
|
||||
DC2814302519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift in Sources */,
|
||||
8C14113721EE502800A59729 /* EQNAllertaSismica.m in Sources */,
|
||||
8C483CBC21FDACE500259FD2 /* VersioneProProducts.swift in Sources */,
|
||||
8C483CB821FDACD300259FD2 /* IAPHelper.swift in Sources */,
|
||||
@@ -1111,6 +1138,7 @@
|
||||
8C4E34452152B707008B0D2A /* EQMAccelerometroManager.m in Sources */,
|
||||
8CBD3DC72149B9AD0070C963 /* AppDelegate.m in Sources */,
|
||||
DC974AFF251748B300A139EC /* SeismicFiltersViewController.swift in Sources */,
|
||||
DC105641251E7ECE002579BB /* UIFont+Extensions.swift in Sources */,
|
||||
8CA46BA12194532E00C63C16 /* SismaAnnotation.m in Sources */,
|
||||
DCB28CEE24FB8400001F557E /* SettingsViewController.swift in Sources */,
|
||||
DC3CE50A250EB7A8005A7DD5 /* EQNGenericPickerViewController.swift in Sources */,
|
||||
|
||||
+710
@@ -0,0 +1,710 @@
|
||||
//
|
||||
// SeismicNetworkTableViewCell.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 22/09/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MapKit
|
||||
import CoreLocation
|
||||
|
||||
protocol SeismicNetworkTableViewCellDelegate: class {
|
||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapWeather(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell)
|
||||
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell)
|
||||
}
|
||||
|
||||
class SeismicNetworkTableViewCell: UITableViewCell {
|
||||
|
||||
static let Identifier = "SeismicNetworkTableViewCell"
|
||||
|
||||
typealias MagnitudeColors = (textColor: UIColor, startColor: UIColor, endColor: UIColor)
|
||||
|
||||
/// Available informations to display inside the cell
|
||||
enum InformationType: Int {
|
||||
case preliminary
|
||||
case time
|
||||
case distance
|
||||
case coordinate
|
||||
case population
|
||||
case realtimeSmartphones
|
||||
case reportUsers
|
||||
}
|
||||
|
||||
/// Available cell type
|
||||
enum DisplayType {
|
||||
/// Compact view
|
||||
case normal
|
||||
/// Cell with map visible
|
||||
case mapExpanded
|
||||
/// Cell with weather info visible
|
||||
case weatherExpanded
|
||||
}
|
||||
|
||||
/// Delegate
|
||||
weak var delegate: SeismicNetworkTableViewCellDelegate?
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
private static let DefaultVerticalSpacing: CGFloat = 6.0
|
||||
private static let DefaultBodyFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
private static let DefaultBodyFontLight = UIFont.preferredFont(for: .body, weight: .light)
|
||||
|
||||
private static var dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "HH:mm:ss d-MMM"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
/// Seismic to show
|
||||
private var seismic: EQNSisma?
|
||||
private(set) var displayType = DisplayType.normal
|
||||
private var informationTypes = [InformationType]()
|
||||
|
||||
private var colors: MagnitudeColors?
|
||||
|
||||
// MARK: - UI Components
|
||||
|
||||
private lazy var containerView: UIView = {
|
||||
let view = UIView(frame: .zero)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.layer.cornerRadius = AppTheme.shared.borderCornerRadius
|
||||
view.layer.masksToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var titleImageView: UIImageView = {
|
||||
let imageView = UIImageView(frame: .zero)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var placeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = UIFont.preferredFont(for: .title2, weight: .semibold)
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var networkLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.backgroundColor = UIColor.white.withAlphaComponent(0.5)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var magnitudeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = UIFont.preferredFont(forTextStyle: .largeTitle)
|
||||
label.textColor = .red
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var depthLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var timeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var distanceLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var coordinateLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var populationLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var smartphonesLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.font = Self.DefaultBodyFont
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var alertsLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.font = Self.DefaultBodyFont
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var mapView: MKMapView = {
|
||||
let mapView = MKMapView(frame: .zero)
|
||||
mapView.translatesAutoresizingMaskIntoConstraints = false
|
||||
mapView.isScrollEnabled = false
|
||||
mapView.isZoomEnabled = false
|
||||
mapView.layer.cornerRadius = AppTheme.shared.borderCornerRadius
|
||||
mapView.layer.masksToBounds = true
|
||||
return mapView
|
||||
}()
|
||||
|
||||
private lazy var weatherImageView: UIImageView = {
|
||||
let imageView = UIImageView(frame: .zero)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var weatherInfoLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.numberOfLines = 0
|
||||
label.font = Self.DefaultBodyFontLight
|
||||
return label
|
||||
}()
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
private func setupUI() {
|
||||
selectionStyle = .default
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
backgroundColor = .clear
|
||||
|
||||
// container view
|
||||
contentView.addSubview(containerView)
|
||||
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5.0).isActive = true
|
||||
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10.0).isActive = true
|
||||
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10.0).isActive = true
|
||||
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5.0).isActive = true
|
||||
|
||||
// this variable is used to keep track of the previous view, in order to attach proper constraints
|
||||
var previousView: UIView = containerView
|
||||
|
||||
// preliminary banner on top of the cell
|
||||
if informationTypes.contains(.preliminary) {
|
||||
let preliminaryLabel = UILabel()
|
||||
preliminaryLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
preliminaryLabel.text = NSLocalizedString("official_prelimiary", comment: "").uppercased()
|
||||
preliminaryLabel.textAlignment = .center
|
||||
preliminaryLabel.backgroundColor = .red
|
||||
preliminaryLabel.textColor = .yellow
|
||||
|
||||
containerView.addSubview(preliminaryLabel)
|
||||
preliminaryLabel.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
||||
preliminaryLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
|
||||
preliminaryLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
|
||||
preliminaryLabel.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
|
||||
|
||||
previousView = preliminaryLabel
|
||||
}
|
||||
|
||||
// title (bell icon, place label, seismic network and share button)
|
||||
let titleComponentsHeight: CGFloat = 30.0
|
||||
let stackViewTitle = UIStackView()
|
||||
stackViewTitle.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewTitle.axis = .horizontal
|
||||
stackViewTitle.distribution = .fill
|
||||
stackViewTitle.alignment = .center
|
||||
stackViewTitle.spacing = 4
|
||||
|
||||
let shareButton = UIButton(type: .custom)
|
||||
shareButton.setImage(UIImage(named: "share_icon"), for: .normal)
|
||||
shareButton.addTarget(self, action: #selector(shareTapped(_:)), for: .touchUpInside)
|
||||
|
||||
stackViewTitle.addArrangedSubview(titleImageView)
|
||||
stackViewTitle.addArrangedSubview(placeLabel)
|
||||
stackViewTitle.addArrangedSubview(networkLabel)
|
||||
stackViewTitle.addArrangedSubview(shareButton)
|
||||
|
||||
titleImageView.heightAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true
|
||||
titleImageView.widthAnchor.constraint(equalTo: titleImageView.heightAnchor).isActive = true
|
||||
networkLabel.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
|
||||
networkLabel.widthAnchor.constraint(equalToConstant: 60.0).isActive = true
|
||||
shareButton.widthAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true
|
||||
shareButton.widthAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
|
||||
|
||||
let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor
|
||||
containerView.addSubview(stackViewTitle)
|
||||
stackViewTitle.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true
|
||||
stackViewTitle.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
stackViewTitle.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
|
||||
let separator1 = addSeparator(constraintTo: stackViewTitle.bottomAnchor)
|
||||
let informationsLeadingAnchor = separator1.leadingAnchor
|
||||
let informationsTrailingAnchor = separator1.trailingAnchor
|
||||
|
||||
// magnitude information
|
||||
containerView.addSubview(magnitudeLabel)
|
||||
magnitudeLabel.topAnchor.constraint(equalTo: separator1.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
magnitudeLabel.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
|
||||
|
||||
if !informationTypes.contains(.preliminary) {
|
||||
containerView.addSubview(depthLabel)
|
||||
depthLabel.lastBaselineAnchor.constraint(equalTo: magnitudeLabel.lastBaselineAnchor).isActive = true
|
||||
depthLabel.leadingAnchor.constraint(equalTo: magnitudeLabel.trailingAnchor, constant: 16).isActive = true
|
||||
}
|
||||
|
||||
// informations
|
||||
let stackViewInformations = UIStackView()
|
||||
stackViewInformations.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewInformations.axis = .vertical
|
||||
stackViewInformations.distribution = .equalSpacing
|
||||
stackViewInformations.spacing = 4
|
||||
|
||||
if informationTypes.contains(.time) {
|
||||
stackViewInformations.addArrangedSubview(timeLabel)
|
||||
}
|
||||
if informationTypes.contains(.distance) {
|
||||
stackViewInformations.addArrangedSubview(distanceLabel)
|
||||
}
|
||||
if informationTypes.contains(.coordinate) {
|
||||
stackViewInformations.addArrangedSubview(coordinateLabel)
|
||||
}
|
||||
if informationTypes.contains(.population) {
|
||||
stackViewInformations.addArrangedSubview(populationLabel)
|
||||
}
|
||||
|
||||
containerView.addSubview(stackViewInformations)
|
||||
stackViewInformations.topAnchor.constraint(equalTo: magnitudeLabel.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
stackViewInformations.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
|
||||
stackViewInformations.trailingAnchor.constraint(equalTo: informationsTrailingAnchor, constant: -14).isActive = true
|
||||
|
||||
previousView = stackViewInformations
|
||||
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) {
|
||||
let separator2 = addSeparator(constraintTo: stackViewInformations.bottomAnchor)
|
||||
|
||||
let stackViewReports = UIStackView()
|
||||
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewReports.axis = .vertical
|
||||
stackViewReports.distribution = .equalSpacing
|
||||
stackViewReports.alignment = .center
|
||||
stackViewReports.spacing = Self.DefaultVerticalSpacing
|
||||
|
||||
if informationTypes.contains(.realtimeSmartphones) {
|
||||
stackViewReports.addArrangedSubview(smartphonesLabel)
|
||||
}
|
||||
if informationTypes.contains(.reportUsers) {
|
||||
stackViewReports.addArrangedSubview(alertsLabel)
|
||||
}
|
||||
|
||||
containerView.addSubview(stackViewReports)
|
||||
stackViewReports.topAnchor.constraint(equalTo: separator2.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor, constant: 20.0).isActive = true
|
||||
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor, constant: -20.0).isActive = true
|
||||
|
||||
let separator3 = addSeparator(constraintTo: stackViewReports.bottomAnchor)
|
||||
previousView = separator3
|
||||
}
|
||||
|
||||
// buttons
|
||||
let stackViewButtons = UIStackView()
|
||||
stackViewButtons.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackViewButtons.axis = .horizontal
|
||||
stackViewButtons.distribution = .fillEqually
|
||||
stackViewButtons.spacing = 4
|
||||
|
||||
let buttonMap = createRoundedButton(title: "🗺", action: #selector(mapTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonMap)
|
||||
let buttonWeather = createRoundedButton(title: "🌤", action: #selector(weatherTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonWeather)
|
||||
let buttonCalendar = createRoundedButton(title: "📆", action: #selector(calendarTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonCalendar)
|
||||
let buttonSettings = createRoundedButton(title: "🔧", action: #selector(settingsTapped(_:)))
|
||||
stackViewButtons.addArrangedSubview(buttonSettings)
|
||||
|
||||
containerView.addSubview(stackViewButtons)
|
||||
stackViewButtons.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
|
||||
stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
stackViewButtons.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
stackViewButtons.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
previousView = stackViewButtons
|
||||
if displayType == .mapExpanded {
|
||||
containerView.addSubview(mapView)
|
||||
mapView.heightAnchor.constraint(equalToConstant: 140.0).isActive = true
|
||||
mapView.topAnchor.constraint(equalTo: stackViewButtons.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
mapView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
mapView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
previousView = mapView
|
||||
} else if displayType == .weatherExpanded {
|
||||
let weatherTitleLabel = UILabel()
|
||||
weatherTitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
weatherTitleLabel.text = NSLocalizedString("weather_weather", comment: "")
|
||||
weatherTitleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
|
||||
|
||||
containerView.addSubview(weatherTitleLabel)
|
||||
weatherTitleLabel.topAnchor.constraint(equalTo: stackViewButtons.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
weatherTitleLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
weatherTitleLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
containerView.addSubview(weatherInfoLabel)
|
||||
containerView.addSubview(weatherImageView)
|
||||
weatherImageView.heightAnchor.constraint(equalToConstant: 60.0).isActive = true
|
||||
weatherImageView.widthAnchor.constraint(equalTo: weatherImageView.heightAnchor).isActive = true
|
||||
weatherImageView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
weatherImageView.trailingAnchor.constraint(equalTo: weatherInfoLabel.leadingAnchor, constant: -8.0).isActive = true
|
||||
weatherImageView.centerYAnchor.constraint(equalTo: weatherInfoLabel.centerYAnchor).isActive = true
|
||||
|
||||
weatherInfoLabel.topAnchor.constraint(equalTo: weatherTitleLabel.bottomAnchor, constant: 4.0).isActive = true
|
||||
weatherInfoLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
|
||||
previousView = weatherInfoLabel
|
||||
}
|
||||
|
||||
if (displayType == .mapExpanded || displayType == .weatherExpanded) {
|
||||
let buttonClose = createRoundedButton(title: NSLocalizedString("CHIUDI", comment: ""), action: #selector(closeTapped(_:)))
|
||||
|
||||
containerView.addSubview(buttonClose)
|
||||
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
|
||||
buttonClose.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
buttonClose.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
buttonClose.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
}
|
||||
else {
|
||||
stackViewButtons.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
private func recreateUI() {
|
||||
// remove all subviews and recreate the required components
|
||||
containerView.subviews.forEach({ $0.removeFromSuperview() })
|
||||
setupUI()
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
guard let seismic = seismic else { return }
|
||||
|
||||
containerView.backgroundColor = colors?.startColor
|
||||
|
||||
let notified = couldBeNotified(for: seismic)
|
||||
titleImageView.image = notified ? UIImage(named: "bell") : UIImage(named: "bell_disabled")
|
||||
|
||||
// update seismic data
|
||||
placeLabel.text = seismic.place
|
||||
networkLabel.text = seismic.provider
|
||||
magnitudeLabel.textColor = colors?.textColor
|
||||
if informationTypes.contains(.preliminary) {
|
||||
let lowerValue = seismic.magnitude.doubleValue - seismic.magnitudeRange.doubleValue/2.0
|
||||
let upperValue = seismic.magnitude.doubleValue + seismic.magnitudeRange.doubleValue/2.0
|
||||
magnitudeLabel.text = String(format: "%.1f-%.1fM", lowerValue, upperValue)
|
||||
depthLabel.text = ""
|
||||
} else {
|
||||
magnitudeLabel.text = "\(seismic.magnitude)ML"
|
||||
depthLabel.text = "\(NSLocalizedString("Profondità", comment: "")): \(seismic.depth) km"
|
||||
}
|
||||
|
||||
let formattedDate = Self.dateFormatter.string(from: seismic.date)
|
||||
let timeSuffix = seismic.timeDifference > 60 ? NSLocalizedString("ore fa", comment: "") : NSLocalizedString("minuti fa", comment: "")
|
||||
let timeRounded = seismic.timeDifference > 60 ? Int(round(seismic.timeDifference / 60)) : Int(seismic.timeDifference)
|
||||
timeLabel.text = "🕗 \(formattedDate) - \(timeRounded) \(timeSuffix)"
|
||||
let distanceRounded = Int(round(seismic.userDistance))
|
||||
distanceLabel.text = "📐 \(distanceRounded) km \(NSLocalizedString("dalla tua posizione", comment: ""))"
|
||||
let coordinateText = coordinateString(coordinate: seismic.coordinate.coordinate)
|
||||
coordinateLabel.text = "🌍 \(coordinateText)"
|
||||
|
||||
// evaluate population string
|
||||
let populationIsRed = seismic.population100km >= 1_000_000 || seismic.userDistance <= 250
|
||||
let population = formatPopulation(seismic.population100km)
|
||||
populationLabel.text = "👨👩👦 \(population) \(NSLocalizedString("share_radius100", comment: ""))"
|
||||
populationLabel.textColor = populationIsRed ? AppTheme.Colors.red : .black
|
||||
|
||||
if seismic.smartphoneNumber.intValue > 0 || true {
|
||||
smartphonesLabel.text = String(format: NSLocalizedString("report_smartphones", comment: ""), seismic.smartphoneNumber)
|
||||
}
|
||||
if seismic.userNumber.intValue > 0 || true {
|
||||
alertsLabel.text = String(format: NSLocalizedString("report_users", comment: ""), seismic.userNumber)
|
||||
}
|
||||
|
||||
if displayType == .mapExpanded {
|
||||
// zoom based on population involved
|
||||
let zoom = mapZoomFor(population: seismic.population100km)
|
||||
let span = MKCoordinateSpan(latitudeDelta: zoom, longitudeDelta: zoom)
|
||||
let region = MKCoordinateRegion(center: seismic.coordinate.coordinate, span: span)
|
||||
|
||||
mapView.setCenter(seismic.coordinate.coordinate, animated: false)
|
||||
mapView.setRegion(region, animated: false)
|
||||
|
||||
// add a pin on the center
|
||||
let annotation = MKPointAnnotation()
|
||||
annotation.coordinate = seismic.coordinate.coordinate
|
||||
annotation.title = ""
|
||||
mapView.addAnnotation(annotation)
|
||||
} else if displayType == .weatherExpanded {
|
||||
weatherInfoLabel.text = ""
|
||||
+ String(format: NSLocalizedString("weather_temperature", comment: ""), seismic.weatherTemperature.doubleValue - EQNMathKelvin) + "\n"
|
||||
+ String(format: NSLocalizedString("weather_pressure", comment: ""), seismic.weatherPressure) + "\n"
|
||||
+ String(format: NSLocalizedString("weather_windspeed", comment: ""), seismic.weatherWindSpeed) + "\n"
|
||||
+ String(format: NSLocalizedString("weather_humidity", comment: ""), seismic.weatherHumidity) + "\n"
|
||||
+ String(format: NSLocalizedString("weather_clouds", comment: ""), seismic.weatherCloud)
|
||||
weatherImageView.image = UIImage(named: "weather_\(seismic.weatherIcon).png")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
/// Configure the cell to display a seismic
|
||||
/// - Parameters:
|
||||
/// - seismic: Seismic to display
|
||||
/// - type: Type of cell
|
||||
/// - informations: Informations to show
|
||||
public func configure(with seismic: EQNSisma, type: DisplayType, informations: [InformationType]) {
|
||||
self.seismic = seismic
|
||||
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
|
||||
self.displayType = type
|
||||
self.informationTypes = informations
|
||||
|
||||
if !informations.contains(.time) {
|
||||
self.informationTypes += [.time]
|
||||
}
|
||||
|
||||
if seismic.preliminary.intValue > 0 && !informations.contains(.preliminary) {
|
||||
self.informationTypes += [.preliminary]
|
||||
}
|
||||
if seismic.smartphoneNumber.intValue > 0 && !informations.contains(.realtimeSmartphones) {
|
||||
self.informationTypes += [.realtimeSmartphones]
|
||||
}
|
||||
if seismic.userNumber.intValue > 0 && !informations.contains(.reportUsers) {
|
||||
self.informationTypes += [.reportUsers]
|
||||
}
|
||||
|
||||
recreateUI()
|
||||
updateUI()
|
||||
setNeedsLayout()
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
/// Creates a snapshot of the current cell
|
||||
/// - Returns: Image with the snapshot of the cell
|
||||
public func createSnapshot() -> UIImage {
|
||||
let renderer = UIGraphicsImageRenderer(size: contentView.bounds.size)
|
||||
let image = renderer.image { ctx in
|
||||
contentView.drawHierarchy(in: contentView.bounds, afterScreenUpdates: true)
|
||||
}
|
||||
return image
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func shareTapped(_ sender: UIButton) {
|
||||
delegate?.seismicNetworkCellDidTapShare(self)
|
||||
}
|
||||
|
||||
@objc func mapTapped(_ sender: UIButton) {
|
||||
if displayType != .mapExpanded {
|
||||
delegate?.seismicNetworkCellDidTapMap(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func weatherTapped(_ sender: UIButton) {
|
||||
if displayType != .weatherExpanded {
|
||||
delegate?.seismicNetworkCellDidTapWeather(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func calendarTapped(_ sender: UIButton) {
|
||||
delegate?.seismicNetworkCellDidTapCalendar(self)
|
||||
}
|
||||
|
||||
@objc func settingsTapped(_ sender: UIButton) {
|
||||
delegate?.seismicNetworkCellDidTapSettings(self)
|
||||
}
|
||||
|
||||
@objc func closeTapped(_ sender: UIButton) {
|
||||
delegate?.seismicNetworkCellDidTapClose(self)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
@discardableResult
|
||||
private func addSeparator(constraintTo: NSLayoutYAxisAnchor, constanst: CGFloat = 8.0) -> UIView {
|
||||
let separator = UIView()
|
||||
separator.translatesAutoresizingMaskIntoConstraints = false
|
||||
separator.backgroundColor = .lightGray
|
||||
containerView.addSubview(separator)
|
||||
|
||||
separator.topAnchor.constraint(equalTo: constraintTo, constant: constanst).isActive = true
|
||||
separator.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
separator.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
|
||||
separator.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
|
||||
|
||||
return separator
|
||||
}
|
||||
|
||||
private func createRoundedButton(title: String, action: Selector) -> EQNRoundedButton {
|
||||
let button = EQNRoundedButton(frame: .zero)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.addTarget(self, action: action, for: .touchUpInside)
|
||||
button.setTitle(title, for: .normal)
|
||||
button.setTitleColor(AppTheme.Colors.darkGray, for: .normal)
|
||||
button.backgroundColor = UIColor.white.withAlphaComponent(0.5)
|
||||
return button
|
||||
}
|
||||
|
||||
/// Check if the user could be received a notification for this seismic
|
||||
private func couldBeNotified(for seismic: EQNSisma) -> Bool {
|
||||
let settings = EQNNotificheReteSismiche.shared()
|
||||
|
||||
if !settings.isAbilitato {
|
||||
return false
|
||||
}
|
||||
|
||||
if !settings.listaEnti.contains(seismic.provider) {
|
||||
return false
|
||||
}
|
||||
|
||||
var notified = true
|
||||
if let radius = Double(settings.distanzaPosizione), seismic.userDistance > radius {
|
||||
notified = false
|
||||
}
|
||||
if let magnitude = Double(settings.energiaSisma), seismic.magnitude.doubleValue < magnitude {
|
||||
notified = false
|
||||
}
|
||||
|
||||
if settings.isAbilitaVicini, seismic.userDistance < 50 {
|
||||
notified = true
|
||||
}
|
||||
|
||||
if settings.isTerremortiForti, let strongMagnitude = Double(settings.energiaTerremotiForti), seismic.magnitude.doubleValue >= strongMagnitude {
|
||||
notified = true
|
||||
}
|
||||
|
||||
return notified
|
||||
}
|
||||
|
||||
/// Convert coordinates in degrees
|
||||
private func coordinateString(coordinate: CLLocationCoordinate2D) -> String {
|
||||
var latSeconds = Int(coordinate.latitude * 3600)
|
||||
let latDegrees = latSeconds / 3600
|
||||
latSeconds = abs(latSeconds % 3600)
|
||||
let latMinutes = latSeconds / 60
|
||||
latSeconds %= 60
|
||||
var longSeconds = Int(coordinate.longitude * 3600)
|
||||
let longDegrees = longSeconds / 3600
|
||||
longSeconds = abs(longSeconds % 3600)
|
||||
let longMinutes = longSeconds / 60
|
||||
longSeconds %= 60
|
||||
return String(format:"%d°%d'%d\"%@ lat %d°%d'%d\"%@ lon",
|
||||
abs(latDegrees),
|
||||
latMinutes,
|
||||
latSeconds, latDegrees >= 0 ? "N" : "S",
|
||||
abs(longDegrees),
|
||||
longMinutes,
|
||||
longSeconds,
|
||||
longDegrees >= 0 ? "E" : "W" )
|
||||
}
|
||||
|
||||
/// Determines the zoom for the map, based on the involved population
|
||||
private func mapZoomFor(population: Double) -> CLLocationDegrees {
|
||||
var zoom: CLLocationDegrees = 1
|
||||
if population > 1_000_000 {
|
||||
zoom = 7
|
||||
} else if population < 500 {
|
||||
zoom = 3
|
||||
} else {
|
||||
zoom = 5
|
||||
}
|
||||
return zoom
|
||||
}
|
||||
|
||||
/// Format population value (ex. 1.5M, 2.4k)
|
||||
private func formatPopulation(_ population: Double) -> String {
|
||||
var populationString = ""
|
||||
if population > 999_999 {
|
||||
let roundedPopulation = round(population / 100_000) / 10
|
||||
populationString = "\(roundedPopulation)M"
|
||||
} else if population > 999 {
|
||||
let roundedPopulation = round(population / 100) / 10
|
||||
populationString = "\(roundedPopulation)K"
|
||||
} else {
|
||||
let roundedPopulation = round(population)
|
||||
populationString = "\(roundedPopulation)"
|
||||
}
|
||||
return populationString
|
||||
}
|
||||
|
||||
/// Calculate colors to use for text and background of the cell
|
||||
private func calculateColors(for magnitude: Double) -> MagnitudeColors {
|
||||
var textColor = UIColor.black
|
||||
|
||||
var r = 0, g = 0, b = 0
|
||||
if (magnitude < 2.0) {
|
||||
let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0)
|
||||
r = Int(round(200.0 + (255.0 - 200.0) * fraction))
|
||||
g = Int(round(226.0 + (255.0 - 226.0) * fraction))
|
||||
b = Int(round(196.0 + (255.0 - 196.0) * fraction))
|
||||
textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 16.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 2.0 && magnitude < 3.5) {
|
||||
let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2)
|
||||
r = Int(round(136.0 + (200.0 - 136.0) * fraction))
|
||||
g = Int(round(175.0 + (226.0 - 175.0) * fraction))
|
||||
b = Int(round(131.0 + (196.0 - 131.0) * fraction))
|
||||
textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 3.5 && magnitude < 4.5) {
|
||||
let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5)
|
||||
r = 252
|
||||
g = Int(round(233.0 + (253.0 - 233.0) * fraction))
|
||||
b = Int(round(179.0 + (209.0 - 179.0) * fraction))
|
||||
textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 4.5 && magnitude < 5.5) {
|
||||
let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5)
|
||||
r = 252
|
||||
g = Int(round(159.0 + (197.0 - 159.0) * fraction))
|
||||
b = Int(round(161.0 + (197.0 - 161.0) * fraction))
|
||||
textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
if (magnitude >= 5.5) {
|
||||
let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5)
|
||||
r = Int(round(190.0 + (254.0 - 190.0) * fraction))
|
||||
g = Int(round(124.0 + (219.0 - 124.0) * fraction))
|
||||
b = 255
|
||||
textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0)
|
||||
}
|
||||
|
||||
let r2 = min(r + 30, 255)
|
||||
let g2 = min(g + 30, 255)
|
||||
let b2 = min(b + 30, 255)
|
||||
|
||||
let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0)
|
||||
let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0)
|
||||
|
||||
return (textColor: textColor, startColor: startColor, endColor: endColor)
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
//
|
||||
// SeismicNetworksViewController.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 22/09/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SeismicNetworksViewController: EQNBaseViewController, UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
private static let SegueIdentifierFilters = "FiltriEntiSismici"
|
||||
private static let SegueIdentifierSettings = "impostazioniEntiSismi"
|
||||
private static let SegueIdentifierSeismicNetworks = "elencoRetiSismiche"
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
@IBOutlet private weak var tableView: UITableView?
|
||||
private var seismics = [EQNSisma]()
|
||||
|
||||
/// 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()
|
||||
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(didReceiveDownloadCompletedNotification(_:)),
|
||||
name: NSNotification.Name(rawValue: NOTIFICA_DOWNLOAD_TERMINATO),
|
||||
object: nil)
|
||||
|
||||
setupUI()
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
title = NSLocalizedString("tab_official", comment: "")
|
||||
|
||||
tableView?.estimatedRowHeight = 300.0;
|
||||
tableView?.rowHeight = UITableView.automaticDimension
|
||||
tableView?.register(SeismicNetworkTableViewCell.self, forCellReuseIdentifier: SeismicNetworkTableViewCell.Identifier)
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
// todo
|
||||
}
|
||||
|
||||
// MARK: - Notification
|
||||
|
||||
@objc func didReceiveDownloadCompletedNotification(_ notification: NSNotification) {
|
||||
DispatchQueue.main.async {
|
||||
self.refreshUI()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
override func refreshUI() {
|
||||
super.refreshUI()
|
||||
|
||||
let allSeismics = EQNManager.manager().retiSismiche
|
||||
seismics = EQNSeismic.shared.filterSeismicList(allSeismics ?? [])
|
||||
|
||||
tableView?.reloadData()
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@IBAction func refreshDataTapped(_ sender: Any) {
|
||||
EQNManager.manager().sincronizza()
|
||||
}
|
||||
|
||||
@IBAction func openFilterTapped(_ sender: Any) {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierFilters, sender: nil)
|
||||
}
|
||||
|
||||
@IBAction func openSettingsTapped(_ sender: Any) {
|
||||
performSegue(withIdentifier: Self.SegueIdentifierSettings, sender: nil)
|
||||
}
|
||||
|
||||
// MARK: - Table view delegate and data source
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
seismics.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: SeismicNetworkTableViewCell.Identifier, for: indexPath) as! SeismicNetworkTableViewCell
|
||||
let seismic = seismics[indexPath.row]
|
||||
|
||||
var type = SeismicNetworkTableViewCell.DisplayType.normal
|
||||
if openMapIndexPath == indexPath {
|
||||
type = .mapExpanded
|
||||
} else if openWeatherIndexPath == indexPath {
|
||||
type = .weatherExpanded
|
||||
}
|
||||
|
||||
cell.configure(with: seismic, type: type, informations: [ .time, .distance, .location, .population ])
|
||||
cell.delegate = self
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
|
||||
|
||||
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) {
|
||||
// create a snapshot of the cell and share with default share sheet
|
||||
let snapshot = cell.createSnapshot()
|
||||
let controller = UIActivityViewController(activityItems: [snapshot], applicationActivities: [])
|
||||
present(controller, animated: true)
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapWeather(_ cell: SeismicNetworkTableViewCell) {
|
||||
guard let index = tableView?.indexPath(for: cell) else { 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 seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) {
|
||||
// todo
|
||||
}
|
||||
|
||||
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell) {
|
||||
// todo
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -8,3 +8,4 @@
|
||||
#import "EQNManager.h"
|
||||
#import "EQNNotificheReteSismiche.h"
|
||||
#import "EQNSisma.h"
|
||||
#import "EQNBaseViewController.h"
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>L'accesso alla libreria è richiesto per poter salvare le immagini generate dall'app</string>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// UIFont+Extensions.swift
|
||||
// Earthquake Network
|
||||
//
|
||||
// Created by Busi Andrea on 25/09/2020.
|
||||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
extension UIFont {
|
||||
static func preferredFont(for style: TextStyle, weight: Weight) -> UIFont {
|
||||
let metrics = UIFontMetrics(forTextStyle: style)
|
||||
let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style)
|
||||
let font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight)
|
||||
return metrics.scaledFont(for: font)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bell.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "bell@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "bell@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bell_disabled.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "bell_disabled@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "bell_disabled@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "share_icon.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "original"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
"NSLocationAlwaysAndWhenInUseUsageDescription" = "Change to 'Always allow' so we can alert you in real time when an earthquake occurs nearby.";
|
||||
"NSLocationAlwaysUsageDescription" = "Change to 'Always allow' so we can alert you in real time when an earthquake occurs nearby.";
|
||||
"NSLocationWhenInUseUsageDescription" = "We need your location to send you real time seismic alerts.";
|
||||
"NSPhotoLibraryAddUsageDescription" = "Access to the library is required in order to save the images generated by the app";
|
||||
"CFBundleDisplayName" = "Earthquake Network";
|
||||
|
||||
@@ -111,9 +111,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Copertura" = "Coverage";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copertura nuvolosa : " = "Cloud cover : ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Costa Rica" = "Costa Rica";
|
||||
|
||||
@@ -327,9 +324,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"METEO" = "WEATHER";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Meteo al momento del sisma" = "Weather at the time of the earthquake";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"minuti fa" = "minutes ago";
|
||||
|
||||
@@ -434,9 +428,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Perù" = "Per";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Pressione:" = "Pressure:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy: http://wp.earthquakenetwork.it/privacy/\n\nTermini e condizioni: http://wp.earthquakenetwork.it/it/terms-conditions/" = "Privacy: http://wp.earthquakenetwork.it/privacy/\n\nTerms and conditions: http://wp.earthquakenetwork.it/it/terms-conditions/";
|
||||
|
||||
@@ -558,9 +549,6 @@
|
||||
/* voce elenco messaggio tsunami */
|
||||
"Supplemento di valutazione" = "Evaluation supplement";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Temperatura: " = "Temperature: ";
|
||||
|
||||
/* voce menu */
|
||||
"Terremoti forti" = "Strong earthquakes";
|
||||
|
||||
@@ -601,9 +589,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Ultimo mese" = "Last month";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Umidità : " = "Humidity : ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Un anno" = "One year";
|
||||
|
||||
@@ -637,9 +622,6 @@
|
||||
/* titolo pulsante notifica rete smartphone */
|
||||
"VEDI IN TWITTER" = "SEE ON TWITTER";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Velocità vento: " = "Wind speed: ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Venezuela" = "Venezuela";
|
||||
|
||||
@@ -769,3 +751,13 @@
|
||||
"Modifica le impostazioni di notifica dei sismi in accordo con i filtri" = "Change the earthquake notification settings according to the filters";
|
||||
"main_understood" = "Understood";
|
||||
"options_low_magnitude" = "Beware that not all seismic networks provide earthquake data below magnitude 2.0. Moreover, you significantly increase the data transfer and the battery usage due to notifications. Unless your device is recently manufactured, you will also notice a general slowdown.";
|
||||
"share_radius100" = "in a radius of 100km";
|
||||
"report_smartphones" = "🚨 Sisma rilevato in tempo reale da %@ smartphones. Allerta inviata."; // missing
|
||||
"report_users" = "⚠️ Segnalato da %@ utenti"; // missing
|
||||
"weather_weather" = "Weather at the time of the earthquake";
|
||||
"weather_temperature" = "Temperature: %.02f°C";
|
||||
"weather_pressure" = "Pressure: %@mb";
|
||||
"weather_windspeed" = "Wind speed: %@m/s";
|
||||
"weather_humidity" = "Humidity: %@%%";
|
||||
"weather_clouds" = "Cloud cover: %@%%";
|
||||
"official_prelimiary" = "Preliminary";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"NSLocationAlwaysAndWhenInUseUsageDescription" = "Cambiar a 'Permitir siempre' para que podamos alertarte en tiempo real cuando se produce un sismo cercano.";
|
||||
"NSLocationAlwaysUsageDescription" = "Cambiar a 'Permitir siempre' para que podamos alertarte en tiempo real cuando se produce un sismo cercano.";
|
||||
"NSLocationWhenInUseUsageDescription" = "Necesitamos tu ubicación para enviarte alertas sísmicas en tiempo real";
|
||||
"NSPhotoLibraryAddUsageDescription" = "Se requiere acceso a la biblioteca para guardar las imágenes generadas por la aplicación";
|
||||
"CFBundleDisplayName" = "Sismos Detector";
|
||||
|
||||
@@ -104,9 +104,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Copertura" = "Cobertura";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copertura nuvolosa : " = "Cubierta de nubes: ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Costa Rica" = "Costa Rica";
|
||||
|
||||
@@ -322,9 +319,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"METEO" = "TIEMPO";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Meteo al momento del sisma" = "Tiempo en el momento del terremoto";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"minuti fa" = "minutos";
|
||||
|
||||
@@ -429,9 +423,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Perù" = "Peru";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Pressione:" = "Presión:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy: http://wp.earthquakenetwork.it/privacy/\n\nTermini e condizioni: http://wp.earthquakenetwork.it/it/terms-conditions/" = "Privacidad: http://wp.earthquakenetwork.it/privacy/\n\nTérminos y condiciones: http://wp.earthquakenetwork.it/it/terms-conditions/";
|
||||
|
||||
@@ -553,9 +544,6 @@
|
||||
/* voce elenco messaggio tsunami */
|
||||
"Supplemento di valutazione" = "Suplemento de evaluación";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Temperatura: " = "Temperaturas: ";
|
||||
|
||||
/* voce menu */
|
||||
"Terremoti forti" = "Sismos fuertes";
|
||||
|
||||
@@ -596,9 +584,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Ultimo mese" = "Ultimo mes";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Umidità : " = "Humedad: ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Un anno" = "Un año";
|
||||
|
||||
@@ -759,3 +744,13 @@
|
||||
"Modifica le impostazioni di notifica dei sismi in accordo con i filtri" = "Cambia las notificaciones de sismo de acuerdo a los filtros";
|
||||
"main_understood" = "Entendido";
|
||||
"options_low_magnitude" = "Tenga en cuenta que no todas las redes sísmicas proporcionan datos para sismos de magnitud inferior a 2.0. Además, aumenta significativamente la transferencia de datos con el servidor y el uso de la batería debido a la mayor cantidad de notificaciones. Si tu dispositivo no se ha fabricado recientemente, también notará una desaceleración general de la aplicación.";
|
||||
"share_radius100" = "en un radio de 100km";
|
||||
"report_smartphones" = "🚨 Sisma rilevato in tempo reale da %@ smartphones. Allerta inviata."; // missing
|
||||
"report_users" = "⚠️ Segnalato da %@ utenti"; // missing
|
||||
"weather_weather" = "Tiempo en el momento del terremoto";
|
||||
"weather_temperature" = "Temperaturas: %.02f°C";
|
||||
"weather_pressure" = "Presión: %@mb";
|
||||
"weather_windspeed" = "Velocidad viento: %@m/s";
|
||||
"weather_humidity" = "Humedad: %@%%";
|
||||
"weather_clouds" = "Cubierta de nubes: %@%%";
|
||||
"official_prelimiary" = "Preliminar";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"NSLocationAlwaysAndWhenInUseUsageDescription" = "Passa a 'Consenti sempre' in modo che possiamo allertarti in tempo reale quando accade un sisma nelle tue vicinanze.";
|
||||
"NSLocationAlwaysUsageDescription" = "Passa a 'Consenti sempre' in modo che possiamo allertarti in tempo reale quando accade un sisma nelle tue vicinanze.";
|
||||
"NSLocationWhenInUseUsageDescription" = "La tua posizione è necessaria per ricevere le allerte sismiche in tempo reale.";
|
||||
"NSPhotoLibraryAddUsageDescription" = "L'accesso alla libreria è richiesto per poter salvare le immagini generate dall'app";
|
||||
"CFBundleDisplayName" = "Rilevatore Terremoto";
|
||||
|
||||
@@ -104,9 +104,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Copertura" = "Copertura";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copertura nuvolosa : " = "Copertura nuvolosa : ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Costa Rica" = "Costa Rica";
|
||||
|
||||
@@ -320,9 +317,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"METEO" = "METEO";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Meteo al momento del sisma" = "Meteo al momento del sisma";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"minuti fa" = "minuti fa";
|
||||
|
||||
@@ -427,9 +421,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Perù" = "Perù";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Pressione:" = "Pressione:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy: http://wp.earthquakenetwork.it/privacy/\n\nTermini e condizioni: http://wp.earthquakenetwork.it/it/terms-conditions/" = "Privacy: http://wp.earthquakenetwork.it/privacy/\n\nTermini e condizioni: http://wp.earthquakenetwork.it/it/terms-conditions/";
|
||||
|
||||
@@ -551,9 +542,6 @@
|
||||
/* voce elenco messaggio tsunami */
|
||||
"Supplemento di valutazione" = "Supplemento di valutazione";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Temperatura: " = "Temperatura: ";
|
||||
|
||||
/* voce menu */
|
||||
"Terremoti forti" = "Sismi forti";
|
||||
|
||||
@@ -595,9 +583,6 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Ultimo mese" = "Ultimo mese";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Umidità : " = "Umidità : ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Un anno" = "Un anno";
|
||||
|
||||
@@ -631,9 +616,6 @@
|
||||
/* titolo pulsante notifica rete smartphone */
|
||||
"VEDI IN TWITTER" = "VEDI IN TWITTER";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Velocità vento: " = "Velocità vento: ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Venezuela" = "Venezuela";
|
||||
|
||||
@@ -755,3 +737,13 @@
|
||||
"Modifica le impostazioni di notifica dei sismi in accordo con i filtri" = "Modifica le impostazioni di notifica dei sismi in accordo con i filtri";
|
||||
"main_understood" = "Ho capito";
|
||||
"options_low_magnitude" = "Considera che non tutte le reti sismiche forniscono dati per sismi sotto magnitudo 2.0. Inoltre, incrementi significativamente il trasferimento dati con il server e l'utilizzo batteria dovuto al maggior numero di notifiche. Se il tuo dispositivo non è di recente fabbricazione, noterai anche un generale rallentamento dell'app.";
|
||||
"report_smartphones" = "🚨 Sisma rilevato in tempo reale da %@ smartphones. Allerta inviata."; // missing
|
||||
"report_users" = "⚠️ Segnalato da %@ utenti"; // missing
|
||||
"share_radius100" = "in un raggio di 100km";
|
||||
"weather_weather" = "Meteo al momento del sisma";
|
||||
"weather_temperature" = "Temperatura: %.02f°C";
|
||||
"weather_pressure" = "Pressione: %@mb";
|
||||
"weather_windspeed" = "Velocità vento: %@m/s";
|
||||
"weather_humidity" = "Umidità: %@%%";
|
||||
"weather_clouds" = "Copertura nuvolosa: %@%%";
|
||||
"official_prelimiary" = "Preliminare";
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#import "EQNReteSmartphone.h"
|
||||
#import "EQNAreaCheck.h"
|
||||
|
||||
@class EQNSisma;
|
||||
|
||||
@interface EQNManager : NSObject
|
||||
|
||||
@property (nonatomic, assign) BOOL isBackground;
|
||||
@@ -18,7 +20,7 @@
|
||||
@property (nonatomic, strong, nullable) NSArray *datiGraficoUtente;
|
||||
@property (nonatomic, strong, nullable) NSArray *datiPastQuakes;
|
||||
@property (nonatomic, strong, nullable) NSArray *elencoSelagnazioniManuali;
|
||||
@property (nonatomic, strong, nullable) NSArray *retiSismiche;
|
||||
@property (nonatomic, strong, nullable) NSArray<EQNSisma *> *retiSismiche;
|
||||
|
||||
|
||||
+ (nonnull instancetype)defaultManager NS_SWIFT_NAME(manager());
|
||||
|
||||
Reference in New Issue
Block a user