diff --git a/Sources/Earthquake Network.xcodeproj/project.pbxproj b/Sources/Earthquake Network.xcodeproj/project.pbxproj index 7e48c54..1364493 100644 --- a/Sources/Earthquake Network.xcodeproj/project.pbxproj +++ b/Sources/Earthquake Network.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 6514FF6D2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */; }; 65172F532C25C496006D2A5C /* EQNSeismicAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */; }; 651901B925F5358700CAFF20 /* EQNMapAnnotationSeismic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */; }; + 652247242D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */; }; + 652247262D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */; }; 6525A82625E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */; }; 652A3C6B2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652A3C6A2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift */; }; 65355FFF25F38D3300BB57D2 /* SegnalazioniMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65355FFE25F38D3300BB57D2 /* SegnalazioniMapViewController.swift */; }; @@ -320,6 +322,8 @@ 6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapPinStyle.swift; sourceTree = ""; }; 65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNSeismicAnnotationView.swift; sourceTree = ""; }; 651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNMapAnnotationSeismic.swift; sourceTree = ""; }; + 652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkMinimalTableViewCell.swift; sourceTree = ""; }; + 652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkBaseTableViewCell.swift; sourceTree = ""; }; 6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkAdvertiseTableViewCell.swift; sourceTree = ""; }; 652A3C6A2A8A757800719796 /* EQNBackgrounPositionDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgrounPositionDebugViewController.swift; sourceTree = ""; }; 65355FFE25F38D3300BB57D2 /* SegnalazioniMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegnalazioniMapViewController.swift; sourceTree = ""; }; @@ -949,7 +953,9 @@ isa = PBXGroup; children = ( 65DBFB7D25E2CB020041CBA6 /* Ad templates */, + 652247252D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift */, DC28142F2519C24400C1AFF7 /* SeismicNetworkTableViewCell.swift */, + 652247232D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift */, 6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */, ); path = Cells; @@ -1595,8 +1601,10 @@ 650247122A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift in Sources */, 65CB83432915720400EE1E35 /* EQNUserData.swift in Sources */, 654D18C425F93C0600BB6DB0 /* PasquakesMapViewController.swift in Sources */, + 652247242D79DAA000D2B8DF /* SeismicNetworkMinimalTableViewCell.swift in Sources */, 8C483CBC21FDACE500259FD2 /* EQNInAppProducts.swift in Sources */, 8C483CB821FDACD300259FD2 /* IAPHelper.swift in Sources */, + 652247262D79EC5E00D2B8DF /* SeismicNetworkBaseTableViewCell.swift in Sources */, 6562C80725FFA6B100C85273 /* SeismicNetworkViewModel.swift in Sources */, DCDE0BD924E58CCE00209778 /* EQNMainTabBarController.m in Sources */, 8C4E344B2152EE5B008B0D2A /* EQNGeneratoreURLServer.m in Sources */, diff --git a/Sources/Earthquake Network/Constants.swift b/Sources/Earthquake Network/Constants.swift index ab39940..1d022bc 100644 --- a/Sources/Earthquake Network/Constants.swift +++ b/Sources/Earthquake Network/Constants.swift @@ -60,6 +60,8 @@ extension UserDefaults { static let MapPinStyle = "EQNetwork.MapPinStyle" /// Indica le informazioni da visualizzare nelle card `small` e `full` nella Lista Sismi static let SeismicNetworksCardInformations = "EQNetwork.SeismicInformations"; + /// Indica la tipologia di card da visualizzare nella Lista Sismi + static let SeismicNetworksCardStyle = "EQNetwork.SeismicNetworksCardStyle" // Migrazioni static let AppMigrationV5_3 = "EQNUserDefaultMigrationV5_3" diff --git a/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkBaseTableViewCell.swift b/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkBaseTableViewCell.swift new file mode 100644 index 0000000..16250b2 --- /dev/null +++ b/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkBaseTableViewCell.swift @@ -0,0 +1,127 @@ +// +// SeismicNetworkBaseTableViewCell.swift +// Earthquake Network +// +// Created by Andrea Busi on 06/03/25. +// Copyright © 2025 Earthquake Network. All rights reserved. +// + +import UIKit +import Shogun + +protocol SeismicNetworkBaseTableViewCellDelegate: AnyObject { + func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkBaseTableViewCell) + func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell) + func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell) + func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkBaseTableViewCell) + func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkBaseTableViewCell) + func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkBaseTableViewCell) + func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell) +} + + +class SeismicNetworkBaseTableViewCell: UITableViewCell { + + /// Delegate + weak var delegate: SeismicNetworkBaseTableViewCellDelegate? + + /// Available informations to display inside the cell + enum InformationType: Int { + case preliminary + case time + case distance + case coordinate + case population + case realtimeSmartphones + case reportUsers + case intensityMap + case buttons + } + + // MARL: - Internal + + static let DefaultButtonHeight: CGFloat = 34.0 + static let VerticalSpacingDefault: CGFloat = 6.0 + static let VerticalSpacingSmall: CGFloat = 2.0 + static let HorizontalSpacingDefault: CGFloat = 4.0 + + // MARK: - UI Components + + lazy var containerView: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = .white + view.clipsToBounds = true + return view + }() + + lazy var gradientView: UIImageView = { + // Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine + // creata ad-hoc con il gradiente desiderato. + // Le prove fatte utilizzando una view normale sono fallite perchè al momento di + // disegnare la view non abbiamo le misure corrette. + let view = UIImageView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + view.contentMode = .scaleToFill + return view + }() + + // 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: - View Lifecycle + + override func layoutSubviews() { + super.layoutSubviews() + + containerView.eqn_applyShadowAndRoundedCorners() + gradientView.eqn_applyRoundedCorners() + } + + // MARK: - Setup + + func setupUI() { + selectionStyle = .default + backgroundColor = .clear + + // container view + contentView.addSubview(containerView) + containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0).isActive = true + containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true + containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true + containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true + + containerView.addSubview(gradientView) + gradientView.constraint(to: containerView) + } + + func recreateUI() { + // remove all subviews and recreate the required components + containerView.subviews.forEach({ $0.removeFromSuperview() }) + setupUI() + } + + @discardableResult + 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 + } +} diff --git a/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkMinimalTableViewCell.swift b/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkMinimalTableViewCell.swift new file mode 100644 index 0000000..a6395d0 --- /dev/null +++ b/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkMinimalTableViewCell.swift @@ -0,0 +1,232 @@ +// +// SeismicNetworkMinimalTableViewCell.swift +// Earthquake Network +// +// Created by Andrea Busi on 06/03/25. +// Copyright © 2025 Earthquake Network. All rights reserved. +// + +import UIKit +import Shogun + + +class SeismicNetworkMinimalTableViewCell: SeismicNetworkBaseTableViewCell { + + // MARK: - UI + + private lazy var magnitudeLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.preferredFont(forTextStyle: .largeTitle) + label.textColor = .red + label.textAlignment = .center + return label + }() + + private lazy var placeLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold) + label.numberOfLines = 3 + return label + }() + + private lazy var timeLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = .preferredFont(forTextStyle: .body) + label.numberOfLines = 2 + return label + }() + + private lazy var distanceLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = .preferredFont(forTextStyle: .body) + label.numberOfLines = 2 + return label + }() + + private lazy var smartphonesLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 0 + label.font = .preferredFont(forTextStyle: .body) + label.textAlignment = .center + label.numberOfLines = 2 + return label + }() + + private lazy var alertsLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 0 + label.font = .preferredFont(forTextStyle: .body) + label.textAlignment = .center + label.numberOfLines = 2 + return label + }() + + // MARK: - Internal + + /// Seismic to show + private var seismic: EQNSisma? + private var isPushSelected = false + private var informationTypes: Set = [] + + // MARK: - Setup + + override func setupUI() { + super.setupUI() + + // 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 + } + + containerView.addSubview(magnitudeLabel) + containerView.addSubview(placeLabel) + + let titleTopAnchor = previousView == containerView ? containerView.layoutMarginsGuide.topAnchor : previousView.bottomAnchor + + + let stackViewInformations = UIStackView(arrangedSubviews: [timeLabel, distanceLabel]) + stackViewInformations.translatesAutoresizingMaskIntoConstraints = false + stackViewInformations.axis = .horizontal + stackViewInformations.distribution = .fillEqually + stackViewInformations.spacing = Self.HorizontalSpacingDefault + containerView.addSubview(stackViewInformations) + + let stackViewRight = UIStackView(arrangedSubviews: [placeLabel, stackViewInformations]) + stackViewRight.translatesAutoresizingMaskIntoConstraints = false + stackViewRight.axis = .vertical + stackViewRight.distribution = .equalSpacing + stackViewRight.spacing = Self.VerticalSpacingDefault + + let stackViewMain = UIStackView(arrangedSubviews: [magnitudeLabel, stackViewRight]) + stackViewMain.translatesAutoresizingMaskIntoConstraints = false + stackViewMain.axis = .horizontal + stackViewMain.distribution = .fill + stackViewMain.spacing = Self.HorizontalSpacingDefault + containerView.addSubview(stackViewMain) + + stackViewMain.topAnchor.constraint(equalTo: titleTopAnchor).isActive = true + stackViewMain.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true + stackViewMain.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true + magnitudeLabel.widthAnchor.constraint(equalToConstant: 60.0).isActive = true + + previousView = stackViewMain + + if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) || informationTypes.contains(.intensityMap) { + let separator = addSeparator(constraintTo: previousView.bottomAnchor, constanst: Self.VerticalSpacingDefault) + + let stackViewReports = UIStackView() + stackViewReports.translatesAutoresizingMaskIntoConstraints = false + stackViewReports.axis = .vertical + stackViewReports.distribution = .equalSpacing + stackViewReports.alignment = .center + stackViewReports.spacing = Self.VerticalSpacingDefault + + if informationTypes.contains(.realtimeSmartphones) { + stackViewReports.addArrangedSubview(smartphonesLabel) + } + if informationTypes.contains(.reportUsers) { + stackViewReports.addArrangedSubview(alertsLabel) + } + if informationTypes.contains(.intensityMap) { + let buttonMap = EQNRoundedButton.make(title: "🎯 \(NSLocalizedString("shakemap", comment: ""))", target: self, action: #selector(intensityMapTapped(_:))) + stackViewReports.addArrangedSubview(buttonMap) + buttonMap.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true + buttonMap.leadingAnchor.constraint(equalTo: stackViewReports.leadingAnchor).isActive = true + buttonMap.trailingAnchor.constraint(equalTo: stackViewReports.trailingAnchor).isActive = true + } + + containerView.addSubview(stackViewReports) + stackViewReports.topAnchor.constraint(equalTo: separator.bottomAnchor, constant: Self.VerticalSpacingDefault).isActive = true + stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true + stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true + + previousView = stackViewReports + } + + previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true + + containerView.eqn_applyShadowAndRoundedCorners() + gradientView.eqn_applyRoundedCorners() + } + + private func updateUI() { + guard let seismic = seismic else { return } + + let viewModel = SeismicNetworkMinimalViewModel(seismic: seismic) + + gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1))) + + placeLabel.text = viewModel.place + placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor + magnitudeLabel.textColor = viewModel.colors.textColor + magnitudeLabel.text = viewModel.magnitude + timeLabel.text = "🕗 \(viewModel.time)" + distanceLabel.text = "📐 \(viewModel.distance)" + + if !viewModel.smartphones.isEmpty { + smartphonesLabel.text = "🚨 \(viewModel.smartphones)" + } + if !viewModel.users.isEmpty { + alertsLabel.text = "⚠️ \(viewModel.users)" + } + } + + // 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, + isPushSelected: Bool + ) { + self.seismic = seismic + self.isPushSelected = isPushSelected + + if seismic.preliminary.intValue > 0 { + informationTypes.insert(.preliminary) + } + if seismic.smartphoneNumber.intValue > 0 { + informationTypes.insert(.realtimeSmartphones) + } + if seismic.userNumber.intValue > 0 { + informationTypes.insert(.reportUsers) + } + if seismic.isoCode == "0" { + informationTypes.remove(.intensityMap) + } + + recreateUI() + updateUI() + } + + // MARK: - Actions + + @objc private func intensityMapTapped(_ sender: Any) { + delegate?.seismicNetworkCellDidTapIntensityMapDetail(self) + } +} diff --git a/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkTableViewCell.swift b/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkTableViewCell.swift index cbcd12e..7c5bd34 100644 --- a/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkTableViewCell.swift +++ b/Sources/Earthquake Network/Controllers/Seismic Networks/Cells/SeismicNetworkTableViewCell.swift @@ -11,32 +11,8 @@ import MapKit import CoreLocation import Shogun -protocol SeismicNetworkTableViewCellDelegate: AnyObject { - func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) - func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) - func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell) - func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkTableViewCell) - func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) - func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell) - func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell) -} -class SeismicNetworkTableViewCell: UITableViewCell { - - static let Identifier = "SeismicNetworkTableViewCell" - - /// Available informations to display inside the cell - enum InformationType: Int { - case preliminary - case time - case distance - case coordinate - case population - case realtimeSmartphones - case reportUsers - case intensityMap - case buttons - } +class SeismicNetworkTableViewCell: SeismicNetworkBaseTableViewCell { /// Available cell type enum DisplayType { @@ -45,15 +21,6 @@ class SeismicNetworkTableViewCell: UITableViewCell { /// Cell with map visible case mapExpanded } - - /// Delegate - weak var delegate: SeismicNetworkTableViewCellDelegate? - - // MARK: - Internal - - private static let DefaultButtonHeight: CGFloat = 34.0 - private static let VerticalSpacingDefault: CGFloat = 6.0 - private static let VerticalSpacingSmall: CGFloat = 2.0 /// Seismic to show private var seismic: EQNSisma? @@ -62,26 +29,7 @@ class SeismicNetworkTableViewCell: UITableViewCell { private var isPushSelected = false // MARK: - UI Components - - private lazy var containerView: UIView = { - let view = UIView(frame: .zero) - view.translatesAutoresizingMaskIntoConstraints = false - view.backgroundColor = .white - view.clipsToBounds = true - return view - }() - - private lazy var gradientView: UIImageView = { - // Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine - // creata ad-hoc con il gradiente desiderato. - // Le prove fatte utilizzando una view normale sono fallite perchè al momento di - // disegnare la view non abbiamo le misure corrette. - let view = UIImageView(frame: .zero) - view.translatesAutoresizingMaskIntoConstraints = false - view.contentMode = .scaleToFill - return view - }() - + private lazy var placeLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false @@ -208,20 +156,9 @@ class SeismicNetworkTableViewCell: UITableViewCell { // MARK: - Setup - private func setupUI() { - selectionStyle = .default - backgroundColor = .clear - - // container view - contentView.addSubview(containerView) - containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0).isActive = true - containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true - containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true - containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true + override func setupUI() { + super.setupUI() - containerView.addSubview(gradientView) - gradientView.constraint(to: containerView) - // this variable is used to keep track of the previous view, in order to attach proper constraints var previousView: UIView = containerView @@ -391,12 +328,6 @@ class SeismicNetworkTableViewCell: UITableViewCell { gradientView.eqn_applyRoundedCorners() } - 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 } @@ -427,7 +358,6 @@ class SeismicNetworkTableViewCell: UITableViewCell { alertsLabel.text = "⚠️ \(viewModel.users)" } - if displayType == .mapExpanded { // zoom based on population involved let longitudeSpan = mapSpanLongitude(population: seismic.population100km) @@ -488,52 +418,37 @@ class SeismicNetworkTableViewCell: UITableViewCell { // MARK: - Actions - @objc func shareTapped(_ sender: UIButton) { + @objc private func shareTapped(_ sender: UIButton) { delegate?.seismicNetworkCellDidTapShare(self) } - @objc func mapTapped(_ sender: UIButton) { + @objc private func mapTapped(_ sender: UIButton) { if displayType != .mapExpanded { delegate?.seismicNetworkCellDidTapMap(self) } } - @objc func calendarTapped(_ sender: UIButton) { + @objc private func calendarTapped(_ sender: UIButton) { delegate?.seismicNetworkCellDidTapCalendar(self) } - @objc func settingsTapped(_ sender: UIButton) { + @objc private func settingsTapped(_ sender: UIButton) { delegate?.seismicNetworkCellDidTapSettings(self) } - @objc func closeTapped(_ sender: UIButton) { + @objc private func closeTapped(_ sender: UIButton) { delegate?.seismicNetworkCellDidTapClose(self) } - @objc func mapDetailTapped(_ sender: Any) { + @objc private func mapDetailTapped(_ sender: Any) { delegate?.seismicNetworkCellDidTapMapDetail(self) } - @objc func intensityMapTapped(_ sender: Any) { + @objc private func intensityMapTapped(_ sender: Any) { delegate?.seismicNetworkCellDidTapIntensityMapDetail(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 - } /// Determines the zoom for the map, based on the involved population private func mapSpanLongitude(population: Double) -> CLLocationDegrees { diff --git a/Sources/Earthquake Network/Controllers/Seismic Networks/SeismicNetworkViewModel.swift b/Sources/Earthquake Network/Controllers/Seismic Networks/SeismicNetworkViewModel.swift index 40185e4..7d4325c 100644 --- a/Sources/Earthquake Network/Controllers/Seismic Networks/SeismicNetworkViewModel.swift +++ b/Sources/Earthquake Network/Controllers/Seismic Networks/SeismicNetworkViewModel.swift @@ -8,21 +8,62 @@ import Foundation +struct MagnitudeColors { + let textColor: UIColor + let startColor: UIColor + let endColor: UIColor +} + + +struct SeismicNetworkMinimalViewModel { + private let seismic: EQNSisma + let place: String + let isPreliminary: Bool + let magnitude: String + let time: String + let distance: String + let smartphones: String + let users: String + let colors: MagnitudeColors + + // MARK: - Init + + init(seismic: EQNSisma) { + self.seismic = seismic + self.place = seismic.place + let isPreliminary = seismic.preliminary.intValue > 0 + self.isPreliminary = isPreliminary + self.magnitude = String(format: "%.1f", seismic.magnitude.doubleValue) + + let time = EQNUtility.formattedString(forTimeDifference: Int(seismic.timeDifference)) + self.time = time + + let distanceRounded = Int(round(seismic.userDistance)) + self.distance = "\(distanceRounded) km" + + if seismic.smartphoneNumber.intValue > 0 { + self.smartphones = String(format: NSLocalizedString("official_smartphones", comment: ""), seismic.smartphoneNumber) + } else { + self.smartphones = "" + } + if seismic.userNumber.intValue > 0 { + self.users = String(format: NSLocalizedString("official_reports", comment: ""), seismic.userNumber) + } else { + self.users = "" + } + + self.colors = calculateColors(for: seismic.magnitude.doubleValue) + } +} + struct SeismicNetworkViewModel { - struct MagnitudeColors { - let textColor: UIColor - let startColor: UIColor - let endColor: UIColor - } - private let seismic: EQNSisma let place: String let network: String let isPreliminary: Bool let magnitude: String - let rawMagnitude: Double let depth: String let time: String let distance: String @@ -38,7 +79,6 @@ struct SeismicNetworkViewModel { self.seismic = seismic self.place = seismic.place self.network = seismic.provider - self.rawMagnitude = seismic.magnitude.doubleValue let isPreliminary = seismic.preliminary.intValue > 0 self.isPreliminary = isPreliminary @@ -67,7 +107,7 @@ struct SeismicNetworkViewModel { let coordinateText = EQNUtility.coordinateString(coordinate: seismic.coordinate.coordinate) self.coordinate = "\(coordinateText)" - let population = Self.formatPopulation(seismic.population100km) + let population = formatPopulation(seismic.population100km) self.population = String(format: NSLocalizedString("share_radius100", comment: ""), population) if seismic.smartphoneNumber.intValue > 0 { @@ -81,78 +121,8 @@ struct SeismicNetworkViewModel { self.users = "" } - self.colors = Self.calculateColors(for: seismic.magnitude.doubleValue) + self.colors = calculateColors(for: seismic.magnitude.doubleValue) } - - // MARK: - Private - - /// Format population value (ex. 1.5M, 2.4k) - private static 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 static 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: 160.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 .init(textColor: textColor, startColor: startColor, endColor: endColor) - } - } extension SeismicNetworkViewModel: Equatable { @@ -160,3 +130,72 @@ extension SeismicNetworkViewModel: Equatable { return lhs.seismic == rhs.seismic } } + +// MARK: - Helpers + +/// 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: 160.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 .init(textColor: textColor, startColor: startColor, endColor: endColor) +} + +/// 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 +} diff --git a/Sources/Earthquake Network/Controllers/Seismic Networks/SeismicNetworksViewController.swift b/Sources/Earthquake Network/Controllers/Seismic Networks/SeismicNetworksViewController.swift index b337b64..5cfb644 100644 --- a/Sources/Earthquake Network/Controllers/Seismic Networks/SeismicNetworksViewController.swift +++ b/Sources/Earthquake Network/Controllers/Seismic Networks/SeismicNetworksViewController.swift @@ -18,6 +18,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa case advertise(NativeAd) } + enum CardDisplayType: Int, CaseIterable { + case small + case full + case minimal + } + private static let SegueIdentifierFilters = "ShowFilters" private static let SegueIdentifierCardSettings = "ShowCardSettings" @@ -34,6 +40,11 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa /// Cells to display (must be seismics or ad banners) private var rows = [CellType]() + /// Type of cards to show + private var cardDisplayType: CardDisplayType { + get { AppPreferences.shared.seismicNetworksCardStyle } + set { AppPreferences.shared.seismicNetworksCardStyle = newValue } + } private var seismicViewModels = [SeismicNetworkViewModel]() /// Informations to display on a single cell private var informations: [SeismicNetworkTableViewCell.InformationType] { @@ -56,7 +67,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa // MARK: - UI - @IBOutlet private weak var expandeCollapseButton: UIBarButtonItem! + @IBOutlet private weak var displayModeButton: UIBarButtonItem! @IBOutlet private weak var sortButton: UIBarButtonItem! private var tableViewTopConstraint: NSLayoutConstraint? @@ -185,6 +196,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa tableView.estimatedRowHeight = 300.0 tableView.rowHeight = UITableView.automaticDimension tableView.registerCell(for: SeismicNetworkTableViewCell.self) + tableView.registerCell(for: SeismicNetworkMinimalTableViewCell.self) tableView.registerCell(for: SeismicNetworkAdvertiseTableViewCell.self) tableView.emptyDataSetSource = self tableView.separatorStyle = .none @@ -263,10 +275,13 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa private func refreshUI() { elaborateData() - if informations.contains(.buttons) { - expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-collapse") - } else { - expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-expand") + switch cardDisplayType { + case .small: + displayModeButton.image = UIImage(systemName: "1.square") + case .full: + displayModeButton.image = UIImage(systemName: "2.square") + case .minimal: + displayModeButton.image = UIImage(systemName: "3.square") } tableView.reloadData() @@ -631,10 +646,15 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa } @IBAction func collapseExpandTapped(_ sender: Any) { - if informations.contains(.buttons) { + cardDisplayType.next() + + switch cardDisplayType { + case .small: informations.removeAll(where: { $0 == .buttons }) - } else { + case .full: informations.append(.buttons) + case .minimal: + break } refreshUI() @@ -650,17 +670,25 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa 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 + switch cardDisplayType { + case .small, .full: + 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 .minimal: + let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkMinimalTableViewCell.self, for: indexPath) + let isPushSelected = isSeismicToHighlight(seismic: seismic) + cell.configure(with: seismic, isPushSelected: isPushSelected) + return cell } - - 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) @@ -753,9 +781,9 @@ extension SeismicNetworksViewController: NativeAdLoaderDelegate { } } -extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate { +extension SeismicNetworksViewController: SeismicNetworkBaseTableViewCellDelegate { - func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) { + func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkBaseTableViewCell) { 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 @@ -772,7 +800,7 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate { present(controller, animated: true) } - func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) { + func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkBaseTableViewCell) { guard let index = tableView.indexPath(for: cell) else { return } let indexToReloads = [openMapIndexPath, index].compactMap { $0 } @@ -781,29 +809,29 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate { tableView.reloadRows(at: indexToReloads, with: .automatic) } - func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell) { + func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkBaseTableViewCell) { guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return } showMapDetail(for: seismic) } - func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkTableViewCell) { + func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkBaseTableViewCell) { guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return } showIntensityMap(for: seismic) } - func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) { + func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkBaseTableViewCell) { guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return } openCalendar(for: seismic) } - func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell) { + func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkBaseTableViewCell) { performSegue(withIdentifier: Self.SegueIdentifierCardSettings, sender: nil) } - func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell) { + func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkBaseTableViewCell) { guard let index = tableView.indexPath(for: cell) else { return } let indexToReloads = [openMapIndexPath, index].compactMap { $0 } diff --git a/Sources/Earthquake Network/Libs/Extensions/Foundation+Extensions.swift b/Sources/Earthquake Network/Libs/Extensions/Foundation+Extensions.swift index 7664902..c92c901 100644 --- a/Sources/Earthquake Network/Libs/Extensions/Foundation+Extensions.swift +++ b/Sources/Earthquake Network/Libs/Extensions/Foundation+Extensions.swift @@ -37,3 +37,13 @@ extension CGFloat { self*2.0 } } + +extension CaseIterable where Self: Equatable { + mutating func next() { + let all = Self.allCases + let idx = all.firstIndex(of: self)! + let next = all.index(after: idx) + let newValue = all[next == all.endIndex ? all.startIndex : next] + self = newValue + } +} diff --git a/Sources/Earthquake Network/Models/AppPreferences.swift b/Sources/Earthquake Network/Models/AppPreferences.swift index 22a80ce..37a6ebe 100644 --- a/Sources/Earthquake Network/Models/AppPreferences.swift +++ b/Sources/Earthquake Network/Models/AppPreferences.swift @@ -52,4 +52,14 @@ class AppPreferences: NSObject { UserDefaults.standard.set(newValue.map { $0.rawValue }, forKey: UserDefaults.SeismicNetworksCardInformations) } } + + var seismicNetworksCardStyle: SeismicNetworksViewController.CardDisplayType { + get { + let saved = UserDefaults.standard.integer(forKey: UserDefaults.SeismicNetworksCardStyle) + return SeismicNetworksViewController.CardDisplayType(rawValue: saved) ?? .small + } + set { + UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaults.SeismicNetworksCardStyle) + } + } } diff --git a/Sources/Earthquake Network/Models/Commands/EQNUserDefaultsCommand.swift b/Sources/Earthquake Network/Models/Commands/EQNUserDefaultsCommand.swift index 91eb01a..8342d42 100644 --- a/Sources/Earthquake Network/Models/Commands/EQNUserDefaultsCommand.swift +++ b/Sources/Earthquake Network/Models/Commands/EQNUserDefaultsCommand.swift @@ -114,6 +114,9 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol { } AppPreferences.shared.seismicNetworksInformations = informations + let cardDisplayType: SeismicNetworksViewController.CardDisplayType = informations.contains(.buttons) ? .full : .small + AppPreferences.shared.seismicNetworksCardStyle = cardDisplayType + userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_9) } } diff --git a/Sources/Earthquake Network/Models/Map annotation/MapPinStyle.swift b/Sources/Earthquake Network/Models/Map annotation/MapPinStyle.swift index ffa5827..45a5150 100644 --- a/Sources/Earthquake Network/Models/Map annotation/MapPinStyle.swift +++ b/Sources/Earthquake Network/Models/Map annotation/MapPinStyle.swift @@ -12,12 +12,4 @@ enum MapPinStyle: Int, CaseIterable { case circle case light case full - - mutating func next() { - let all = Self.allCases - let idx = all.firstIndex(of: self)! - let next = all.index(after: idx) - let newValue = all[next == all.endIndex ? all.startIndex : next] - self = newValue - } } diff --git a/Sources/Earthquake Network/Storyboards/Base.lproj/Main.storyboard b/Sources/Earthquake Network/Storyboards/Base.lproj/Main.storyboard index 2a87e49..7b21211 100644 --- a/Sources/Earthquake Network/Storyboards/Base.lproj/Main.storyboard +++ b/Sources/Earthquake Network/Storyboards/Base.lproj/Main.storyboard @@ -21,9 +21,9 @@ - + - + @@ -32,15 +32,15 @@ - + - + - + @@ -621,6 +621,7 @@ +