Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,11 @@
#ifndef MWMBottomMenuState_h
#define MWMBottomMenuState_h
typedef NS_ENUM(NSUInteger, MWMBottomMenuState) {
MWMBottomMenuStateHidden,
MWMBottomMenuStateInactive,
MWMBottomMenuStateActive,
MWMBottomMenuStateLayers
};
#endif /* MWMBottomMenuState_h */

View file

@ -0,0 +1,36 @@
@objc class BottomMenuBuilder: NSObject {
@objc static func buildMenu(mapViewController: MapViewController,
controlsManager: MWMMapViewControlsManager,
delegate: BottomMenuDelegate) -> UIViewController {
return BottomMenuBuilder.build(mapViewController: mapViewController,
controlsManager: controlsManager,
delegate: delegate,
sections: [.layers, .items])
}
@objc static func buildLayers(mapViewController: MapViewController,
controlsManager: MWMMapViewControlsManager,
delegate: BottomMenuDelegate) -> UIViewController {
return BottomMenuBuilder.build(mapViewController: mapViewController,
controlsManager: controlsManager,
delegate: delegate,
sections: [.layers])
}
private static func build(mapViewController: MapViewController,
controlsManager: MWMMapViewControlsManager,
delegate: BottomMenuDelegate,
sections: [BottomMenuPresenter.Sections]) -> UIViewController {
let viewController = BottomMenuViewController(nibName: nil, bundle: nil)
let interactor = BottomMenuInteractor(viewController: viewController,
mapViewController: mapViewController,
controlsManager: controlsManager,
delegate: delegate)
let presenter = BottomMenuPresenter(view: viewController, interactor: interactor, sections: sections)
interactor.presenter = presenter
viewController.presenter = presenter
return viewController
}
}

View file

@ -0,0 +1,101 @@
protocol BottomMenuInteractorProtocol: AnyObject {
func close()
func addPlace()
func downloadMaps()
func donate()
func openHelp()
func openSettings()
func shareLocation(cell: BottomMenuItemCell)
func toggleTrackRecording()
}
@objc protocol BottomMenuDelegate {
func actionDownloadMaps(_ mode: MWMMapDownloaderMode)
func addPlace()
func didFinishAddingPlace()
}
class BottomMenuInteractor {
weak var presenter: BottomMenuPresenterProtocol?
private weak var viewController: UIViewController?
private weak var mapViewController: MapViewController?
private weak var delegate: BottomMenuDelegate?
private weak var controlsManager: MWMMapViewControlsManager?
private let trackRecorder: TrackRecordingManager = .shared
init(viewController: UIViewController,
mapViewController: MapViewController,
controlsManager: MWMMapViewControlsManager,
delegate: BottomMenuDelegate) {
self.viewController = viewController
self.mapViewController = mapViewController
self.delegate = delegate
self.controlsManager = controlsManager
}
}
extension BottomMenuInteractor: BottomMenuInteractorProtocol {
func close() {
guard let controlsManager = controlsManager else {
fatalError()
}
controlsManager.menuState = controlsManager.menuRestoreState
}
func addPlace() {
delegate?.addPlace()
}
func donate() {
close()
guard var url = SettingsBridge.donateUrl() else { return }
if url == "https://www.comaps.app/donate/" {
url = L("translated_om_site_url") + "donate/"
}
viewController?.openUrl(url, externally: true)
}
func downloadMaps() {
close()
delegate?.actionDownloadMaps(.downloaded)
}
func openHelp() {
close()
mapViewController?.openAbout()
}
func openSettings() {
close()
mapViewController?.openSettings()
}
func shareLocation(cell: BottomMenuItemCell) {
guard let coordinates = LocationManager.lastLocation()?.coordinate else {
viewController?.present(UIAlertController.unknownCurrentPosition(), animated: true, completion: nil)
return
}
guard let viewController = viewController else { return }
let vc = ActivityViewController.share(forMyPosition: coordinates)
vc.present(inParentViewController: viewController, anchorView: cell.anchorView)
}
func toggleTrackRecording() {
close()
let mapViewController = MapViewController.shared()!
switch trackRecorder.recordingState {
case .active:
mapViewController.showTrackRecordingPlacePage()
case .inactive:
trackRecorder.start { result in
switch result {
case .success:
mapViewController.showTrackRecordingPlacePage()
case .failure:
break
}
}
}
}
}

View file

@ -0,0 +1,178 @@
protocol BottomMenuPresenterProtocol: UITableViewDelegate, UITableViewDataSource {
func onClosePressed()
func cellToHighlightIndexPath() -> IndexPath?
func setCellHighlighted(_ highlighted: Bool)
}
class BottomMenuPresenter: NSObject {
enum CellType: Int, CaseIterable {
case addPlace
case recordTrack
case share
case donate
case downloadMaps
case settings
case help
}
enum Sections: Int {
case layers
case items
}
private weak var view: BottomMenuViewProtocol?
private let interactor: BottomMenuInteractorProtocol
private let sections: [Sections]
private var menuCells: [CellType]
private let trackRecorder = TrackRecordingManager.shared
private var cellToHighlight: CellType?
init(view: BottomMenuViewProtocol,
interactor: BottomMenuInteractorProtocol,
sections: [Sections]) {
self.view = view
self.interactor = interactor
self.sections = sections
self.menuCells = []
self.cellToHighlight = Self.getCellToHighlight()
super.init()
}
private static func getCellToHighlight() -> CellType? {
let featureToHighlightData = DeepLinkHandler.shared.getInAppFeatureHighlightData()
guard let featureToHighlightData, featureToHighlightData.urlType == .menu else { return nil }
switch featureToHighlightData.feature {
case .trackRecorder: return .recordTrack
default: return nil
}
}
}
extension BottomMenuPresenter: BottomMenuPresenterProtocol {
func onClosePressed() {
interactor.close()
}
func cellToHighlightIndexPath() -> IndexPath? {
// Highlighting is enabled only for the .items section.
guard let cellToHighlight,
let sectionIndex = sections.firstIndex(of: .items),
let cellIndex = menuCells.firstIndex(of: cellToHighlight) else { return nil }
return IndexPath(row: cellIndex, section: sectionIndex)
}
func setCellHighlighted(_ highlighted: Bool) {
cellToHighlight = nil
}
}
//MARK: -- UITableViewDataSource
extension BottomMenuPresenter {
func numberOfSections(in tableView: UITableView) -> Int {
sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch sections[section] {
case .layers:
return 1
case .items:
let leftButtonType = Settings.leftButtonType
menuCells = CellType.allCases.filter { cell in
if cell == .donate {
return false
} else if leftButtonType == .addPlace, cell == .addPlace {
return false
} else if leftButtonType == .recordTrack, cell == .recordTrack {
return false
} else if leftButtonType == .help, cell == .help {
return false
} else if leftButtonType == .settings, cell == .settings {
return false
}
return true
}
return menuCells.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch sections[indexPath.section] {
case .layers:
let cell = tableView.dequeueReusableCell(cell: BottomMenuLayersCell.self)!
cell.onClose = { [weak self] in self?.onClosePressed() }
if sections.count > 1 {
cell.addSeparator(.bottom)
}
return cell
case .items:
let cell = tableView.dequeueReusableCell(cell: BottomMenuItemCell.self)!
switch menuCells[indexPath.row] {
case .addPlace:
let enabled = MWMNavigationDashboardManager.shared().state == .hidden && FrameworkHelper.canEditMapAtViewportCenter()
cell.configure(imageName: "plus",
title: L("placepage_add_place_button"),
enabled: enabled)
case .recordTrack:
cell.configure(imageName: "track", title: L("start_track_recording"))
case .downloadMaps:
cell.configure(imageName: "ic_menu_download",
title: L("download_maps"),
badgeCount: MapsAppDelegate.theApp().badgeNumber())
case .donate:
cell.configure(imageName: "ic_menu_donate",
title: L("donate"))
case .help:
cell.configure(imageName: "help",
title: L("help"))
case .settings:
cell.configure(imageName: "gearshape.fill",
title: L("settings"))
case .share:
cell.configure(imageName: "square.and.arrow.up",
title: L("share_my_location"))
}
return cell
}
}
}
//MARK: -- UITableViewDelegate
extension BottomMenuPresenter {
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if let cell = tableView.cellForRow(at: indexPath) as? BottomMenuItemCell {
return cell.isEnabled ? indexPath : nil
}
return indexPath
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch sections[indexPath.section] {
case .layers:
return;
case .items:
switch menuCells[indexPath.row] {
case .addPlace:
interactor.addPlace()
case .recordTrack:
interactor.toggleTrackRecording()
case .downloadMaps:
interactor.downloadMaps()
case .donate:
interactor.donate()
case .help:
interactor.openHelp()
case .settings:
interactor.openSettings()
case .share:
if let cell = tableView.cellForRow(at: indexPath) as? BottomMenuItemCell {
interactor.shareLocation(cell: cell)
}
}
}
}
}

View file

@ -0,0 +1,100 @@
protocol BottomMenuViewProtocol: AnyObject {
var presenter: BottomMenuPresenterProtocol? { get set }
}
class BottomMenuViewController: MWMViewController {
var presenter: BottomMenuPresenterProtocol?
private let transitioningManager = BottomMenuTransitioningManager()
@IBOutlet var tableView: UITableView!
@IBOutlet var heightConstraint: NSLayoutConstraint!
@IBOutlet var bottomConstraint: NSLayoutConstraint!
lazy var chromeView: UIView = {
let view = UIView()
view.setStyle(.presentationBackground)
return view
}()
weak var containerView: UIView! {
didSet {
containerView.insertSubview(chromeView, at: 0)
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.layer.setCornerRadius(.buttonDefault, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner])
tableView.sectionFooterHeight = 0
tableView.dataSource = presenter
tableView.delegate = presenter
tableView.registerNib(cell: BottomMenuItemCell.self)
tableView.registerNib(cell: BottomMenuLayersCell.self)
NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: nil) { _ in
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let cellToHighlight = presenter?.cellToHighlightIndexPath() {
tableView.cellForRow(at: cellToHighlight)?.highlight()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.setNeedsLayout()
tableView.layoutIfNeeded()
heightConstraint.constant = min(tableView.contentSize.height, view.height)
tableView.isScrollEnabled = tableView.contentSize.height > heightConstraint.constant;
}
@IBAction func onClosePressed(_ sender: Any) {
presenter?.onClosePressed()
}
@IBAction func onPan(_ sender: UIPanGestureRecognizer) {
let yOffset = sender.translation(in: view.superview).y
let yVelocity = sender.velocity(in: view.superview).y
sender.setTranslation(CGPoint.zero, in: view.superview)
bottomConstraint.constant = min(bottomConstraint.constant - yOffset, 0);
let alpha = 1.0 - abs(bottomConstraint.constant / tableView.height)
self.chromeView.alpha = alpha
let state = sender.state
if state == .ended || state == .cancelled {
if yVelocity > 0 || (yVelocity == 0 && alpha < 0.8) {
presenter?.onClosePressed()
} else {
let duration = min(kDefaultAnimationDuration, TimeInterval(self.bottomConstraint.constant / yVelocity))
self.view.layoutIfNeeded()
UIView.animate(withDuration: duration) {
self.chromeView.alpha = 1
self.bottomConstraint.constant = 0
self.view.layoutIfNeeded()
}
}
}
}
override var transitioningDelegate: UIViewControllerTransitioningDelegate? {
get { return transitioningManager }
set { }
}
override var modalPresentationStyle: UIModalPresentationStyle {
get { return .custom }
set { }
}
}
extension BottomMenuViewController: BottomMenuViewProtocol {
}

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="BottomMenuViewController" customModule="CoMaps" customModuleProvider="target">
<connections>
<outlet property="bottomConstraint" destination="Crm-Ym-Ikk" id="K0d-Ad-Q13"/>
<outlet property="heightConstraint" destination="dYV-fi-iGj" id="chn-o3-rhF"/>
<outlet property="tableView" destination="L4F-0e-1B7" id="dHQ-DU-QPO"/>
<outlet property="view" destination="iN0-l3-epB" id="nOL-DH-swt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="DD7-rW-ckP">
<rect key="frame" x="0.0" y="0.0" width="414" height="862"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outletCollection property="gestureRecognizers" destination="sXH-Kv-ZnQ" appends="YES" id="u82-AO-mZ1"/>
</connections>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="L4F-0e-1B7">
<rect key="frame" x="0.0" y="562" width="414" height="300"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstAttribute="width" constant="350" id="Jyo-UJ-ltJ"/>
<constraint firstAttribute="height" priority="750" constant="300" id="dYV-fi-iGj"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="Jyo-UJ-ltJ"/>
</mask>
</variation>
<variation key="heightClass=compact">
<mask key="constraints">
<include reference="Jyo-UJ-ltJ"/>
</mask>
</variation>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="M4g-vy-YtE" userLabel="Bottom View">
<rect key="frame" x="0.0" y="862" width="414" height="34"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="L4F-0e-1B7" secondAttribute="bottom" id="Crm-Ym-Ikk"/>
<constraint firstItem="M4g-vy-YtE" firstAttribute="top" secondItem="L4F-0e-1B7" secondAttribute="bottom" id="E7M-j3-lrN"/>
<constraint firstItem="DD7-rW-ckP" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="LOj-yu-5nE"/>
<constraint firstItem="M4g-vy-YtE" firstAttribute="trailing" secondItem="L4F-0e-1B7" secondAttribute="trailing" id="PdB-CC-VOI"/>
<constraint firstItem="DD7-rW-ckP" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="Vxc-mf-jVJ"/>
<constraint firstAttribute="trailing" secondItem="DD7-rW-ckP" secondAttribute="trailing" id="WAo-c4-geW"/>
<constraint firstItem="M4g-vy-YtE" firstAttribute="leading" secondItem="L4F-0e-1B7" secondAttribute="leading" id="cjE-84-gfc"/>
<constraint firstItem="DD7-rW-ckP" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="deF-nX-Ae5"/>
<constraint firstItem="L4F-0e-1B7" firstAttribute="top" relation="greaterThanOrEqual" secondItem="iN0-l3-epB" secondAttribute="top" id="fDu-HA-dhq"/>
<constraint firstItem="L4F-0e-1B7" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="g7h-Yh-azG"/>
<constraint firstAttribute="bottom" secondItem="M4g-vy-YtE" secondAttribute="bottom" priority="750" id="hFA-7p-XKe"/>
<constraint firstItem="L4F-0e-1B7" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="t8e-ZM-EdJ"/>
</constraints>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<variation key="heightClass=compact">
<mask key="constraints">
<exclude reference="g7h-Yh-azG"/>
</mask>
</variation>
<connections>
<outletCollection property="gestureRecognizers" destination="Rdk-jI-mZR" appends="YES" id="bbV-GO-4iF"/>
</connections>
<point key="canvasLocation" x="137.68115942028987" y="153.34821428571428"/>
</view>
<tapGestureRecognizer cancelsTouchesInView="NO" id="sXH-Kv-ZnQ">
<connections>
<action selector="onClosePressed:" destination="-1" id="nMr-L4-IGY"/>
</connections>
</tapGestureRecognizer>
<panGestureRecognizer delaysTouchesBegan="YES" delaysTouchesEnded="NO" minimumNumberOfTouches="1" id="Rdk-jI-mZR">
<connections>
<action selector="onPan:" destination="-1" id="yGW-xa-dnH"/>
</connections>
</panGestureRecognizer>
</objects>
</document>

View file

@ -0,0 +1,47 @@
import UIKit
class BottomMenuItemCell: UITableViewCell {
@IBOutlet private var label: UILabel!
@IBOutlet private var badgeBackground: UIView!
@IBOutlet private var badgeCountLabel: UILabel!
@IBOutlet private var separator: UIView!
@IBOutlet private var icon: UIImageView!
@IBOutlet private var badgeSpacingConstraint: NSLayoutConstraint!
@IBOutlet private var badgeBackgroundWidthConstraint: NSLayoutConstraint!
var anchorView: UIView {
get {
return icon
}
}
private(set) var isEnabled: Bool = true
func configure(imageName: String, title: String, badgeCount: UInt = .zero, enabled: Bool = true) {
if imageName == "help" {
icon.image = Settings.LeftButtonType.help.image
} else if imageName == "plus" {
icon.image = Settings.LeftButtonType.addPlace.image
} else if imageName == "track" {
icon.image = Settings.LeftButtonType.recordTrack.image
} else if imageName == "ic_menu_download" || imageName == "ic_menu_donate" {
icon.image = UIImage(named: imageName)
} else {
let configuration = UIImage.SymbolConfiguration(pointSize: 22, weight: .semibold)
icon.image = UIImage(systemName: imageName, withConfiguration: configuration)!
}
label.text = title
badgeBackground.isHidden = badgeCount == 0
badgeCountLabel.text = "\(badgeCount)"
if badgeCount == 0 {
badgeSpacingConstraint.constant = 0
badgeBackgroundWidthConstraint.constant = 0
} else {
badgeSpacingConstraint.constant = 8
badgeBackgroundWidthConstraint.constant = 32
}
isEnabled = enabled
icon.setStyleAndApply(isEnabled ? .black : .gray)
label.setFontStyleAndApply(isEnabled ? .blackPrimary : .blackHint)
}
}

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="48" id="gZi-cd-LgO" customClass="BottomMenuItemCell" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="48"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="gZi-cd-LgO" id="rsu-1s-Lsp">
<rect key="frame" x="0.0" y="0.0" width="414" height="48"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_menu_download" translatesAutoresizingMaskIntoConstraints="NO" id="8oJ-8z-qRL">
<rect key="frame" x="16" y="10" width="28" height="28"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="Hth-JK-nv2"/>
<constraint firstAttribute="height" constant="28" id="zil-Ys-XYB"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMBlack"/>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5Uc-o1-PsF">
<rect key="frame" x="60" y="14" width="298" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular16:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="con-tP-3dJ" userLabel="DownloadBadgeBackground">
<rect key="frame" x="366" y="14" width="32" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FT9-8n-RZm" userLabel="DownloadBadgeCount">
<rect key="frame" x="0.0" y="0.0" width="32" height="20"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="FT9-8n-RZm" secondAttribute="trailing" id="Ge3-P2-idS"/>
<constraint firstAttribute="bottom" secondItem="FT9-8n-RZm" secondAttribute="bottom" id="Kgn-y3-dBT"/>
<constraint firstItem="FT9-8n-RZm" firstAttribute="top" secondItem="con-tP-3dJ" secondAttribute="top" id="LaN-Vb-w2N"/>
<constraint firstAttribute="height" constant="20" id="Y5v-7h-SGf"/>
<constraint firstItem="FT9-8n-RZm" firstAttribute="leading" secondItem="con-tP-3dJ" secondAttribute="leading" id="pE4-Qs-ctY"/>
<constraint firstAttribute="width" constant="32" id="ubK-0L-pDn"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Badge"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4OJ-wN-dY4" userLabel="Separator">
<rect key="frame" x="60" y="47" width="354" height="1"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="tDM-AP-ern"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="5Uc-o1-PsF" firstAttribute="leading" secondItem="4OJ-wN-dY4" secondAttribute="leading" id="9g1-Uo-zCa"/>
<constraint firstAttribute="bottom" secondItem="8oJ-8z-qRL" secondAttribute="bottom" constant="10" id="KJP-IG-4FK"/>
<constraint firstItem="FT9-8n-RZm" firstAttribute="leading" secondItem="5Uc-o1-PsF" secondAttribute="trailing" constant="8" id="RJA-NS-MXP"/>
<constraint firstItem="8oJ-8z-qRL" firstAttribute="top" secondItem="rsu-1s-Lsp" secondAttribute="top" constant="10" id="bab-2f-YZY"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="4OJ-wN-dY4" secondAttribute="trailing" id="3Vq-qn-yhm"/>
<constraint firstItem="4OJ-wN-dY4" firstAttribute="leading" secondItem="gZi-cd-LgO" secondAttribute="leading" constant="60" id="7wy-Q8-lja"/>
<constraint firstAttribute="trailing" secondItem="con-tP-3dJ" secondAttribute="trailing" constant="16" id="Bm7-vh-vYz"/>
<constraint firstItem="8oJ-8z-qRL" firstAttribute="leading" secondItem="gZi-cd-LgO" secondAttribute="leading" constant="16" id="I7K-bz-QOC"/>
<constraint firstAttribute="bottom" secondItem="4OJ-wN-dY4" secondAttribute="bottom" id="SVQ-VP-J4d"/>
<constraint firstItem="con-tP-3dJ" firstAttribute="centerY" secondItem="gZi-cd-LgO" secondAttribute="centerY" id="UY4-uw-Kda"/>
<constraint firstItem="5Uc-o1-PsF" firstAttribute="centerY" secondItem="gZi-cd-LgO" secondAttribute="centerY" id="hVt-zE-3iE"/>
<constraint firstItem="8oJ-8z-qRL" firstAttribute="centerY" secondItem="gZi-cd-LgO" secondAttribute="centerY" id="u2b-9I-SLh"/>
</constraints>
<connections>
<outlet property="badgeBackground" destination="con-tP-3dJ" id="vH5-Rd-uqS"/>
<outlet property="badgeBackgroundWidthConstraint" destination="ubK-0L-pDn" id="D3B-sV-eGO"/>
<outlet property="badgeCountLabel" destination="FT9-8n-RZm" id="HLY-2S-do9"/>
<outlet property="badgeSpacingConstraint" destination="RJA-NS-MXP" id="Hpf-eq-1C1"/>
<outlet property="icon" destination="8oJ-8z-qRL" id="qcD-Kv-c5l"/>
<outlet property="label" destination="5Uc-o1-PsF" id="y4l-b6-MCt"/>
<outlet property="separator" destination="4OJ-wN-dY4" id="Kkv-HO-0eK"/>
</connections>
<point key="canvasLocation" x="337.68115942028987" y="527.00892857142856"/>
</tableViewCell>
</objects>
<resources>
<image name="ic_menu_download" width="28" height="28"/>
</resources>
</document>

View file

@ -0,0 +1,37 @@
final class BottomMenuLayerButton: VerticallyAlignedButton {
private var badgeView: UIView?
private let badgeSize = CGSize(width: 12, height: 12)
private let badgeOffset = CGPoint(x: -3, y: 3)
var isBadgeHidden: Bool = true{
didSet {
if oldValue != isBadgeHidden {
updateBadge()
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.layer.masksToBounds = true
updateBadge()
}
private func updateBadge() {
if isBadgeHidden {
badgeView?.removeFromSuperview()
badgeView = nil
} else {
if badgeView == nil {
badgeView = UIView()
badgeView?.setStyle(.badge)
addSubview(badgeView!)
}
let imageFrame = imageView.frame
badgeView?.frame = CGRect(x:imageFrame.minX + imageFrame.width - badgeSize.width / 2 + badgeOffset.x,
y:imageFrame.minY - badgeSize.height/2 + badgeOffset.y,
width: badgeSize.width,
height: badgeSize.height)
}
}
}

View file

@ -0,0 +1,107 @@
import UIKit
class BottomMenuLayersCell: UITableViewCell {
@IBOutlet weak var closeButton: CircleImageButton!
@IBOutlet private var subwayButton: BottomMenuLayerButton! {
didSet {
updateSubwayButton()
}
}
@IBOutlet private var isoLinesButton: BottomMenuLayerButton! {
didSet {
updateIsoLinesButton()
}
}
@IBOutlet private var outdoorButton: BottomMenuLayerButton! {
didSet {
updateOutdoorButton()
}
}
var onClose: (()->())?
override func awakeFromNib() {
super.awakeFromNib()
MapOverlayManager.add(self)
closeButton.setImage(UIImage(named: "ic_close"))
setupButtons()
}
private func setupButtons() {
outdoorButton.setupWith(image: UIImage(resource: .btnMenuOutdoors), text: L("button_layer_outdoor"))
isoLinesButton.setupWith(image: UIImage(resource: .btnMenuIsomaps), text: L("button_layer_isolines"))
subwayButton.setupWith(image: UIImage(resource: .btnMenuSubway), text: L("button_layer_subway"))
}
deinit {
MapOverlayManager.remove(self)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
private func updateSubwayButton() {
let enabled = MapOverlayManager.transitEnabled()
subwayButton.setStyleAndApply(styleFor(enabled))
}
private func updateIsoLinesButton() {
let enabled = MapOverlayManager.isoLinesEnabled()
isoLinesButton.setStyleAndApply(styleFor(enabled))
}
private func updateOutdoorButton() {
let enabled = MapOverlayManager.outdoorEnabled()
outdoorButton.setStyleAndApply(styleFor(enabled))
}
@IBAction func onCloseButtonPressed(_ sender: Any) {
onClose?()
}
@IBAction func onSubwayButton(_ sender: Any) {
let enable = !MapOverlayManager.transitEnabled()
MapOverlayManager.setTransitEnabled(enable)
}
@IBAction func onIsoLinesButton(_ sender: Any) {
let enable = !MapOverlayManager.isoLinesEnabled()
MapOverlayManager.setIsoLinesEnabled(enable)
}
@IBAction func onOutdoorButton(_ sender: Any) {
let enable = !MapOverlayManager.outdoorEnabled()
MapOverlayManager.setOutdoorEnabled(enable)
}
}
extension BottomMenuLayersCell: MapOverlayManagerObserver {
func onTransitStateUpdated() {
updateSubwayButton()
}
func onIsoLinesStateUpdated() {
updateIsoLinesButton()
}
func onOutdoorStateUpdated() {
updateOutdoorButton()
}
}
private extension BottomMenuLayersCell {
func styleFor(_ enabled: Bool) -> MapStyleSheet {
enabled ? .mapMenuButtonEnabled : .mapMenuButtonDisabled
}
}
private extension BottomMenuLayerButton {
func setupWith(image: UIImage, text: String) {
self.image = image
spacing = 10
numberOfLines = 2
localizedText = text
}
}

View file

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="ipad9_7" orientation="landscape" layout="fullscreen" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" rowHeight="165" id="KGk-i7-Jjw" customClass="BottomMenuLayersCell" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="340" height="165"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="340" height="165"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="W1i-v6-zbz">
<rect key="frame" x="0.0" y="0.0" width="340" height="50"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Map Layers" textAlignment="natural" lineBreakMode="clip" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vuk-dn-n2c">
<rect key="frame" x="119.5" y="13" width="101.5" height="24"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="blackPrimaryText:bold22"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="layers_title"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2xW-dK-D9y" customClass="CircleImageButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="296" y="11" width="28" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="BD2-bz-n13"/>
<constraint firstAttribute="width" constant="28" id="Thu-MY-dQm"/>
</constraints>
<connections>
<action selector="onCloseButtonPressed:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="8vd-Pg-Suh"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="00h-1i-skR"/>
<constraint firstItem="2xW-dK-D9y" firstAttribute="leading" secondItem="Vuk-dn-n2c" secondAttribute="trailing" constant="8" id="4PG-Fm-yqS"/>
<constraint firstItem="Vuk-dn-n2c" firstAttribute="centerY" secondItem="W1i-v6-zbz" secondAttribute="centerY" id="4du-pr-7hv"/>
<constraint firstAttribute="height" constant="45" id="Ez1-s5-1EO"/>
<constraint firstItem="Vuk-dn-n2c" firstAttribute="centerX" secondItem="W1i-v6-zbz" secondAttribute="centerX" id="XEG-CK-41Y"/>
<constraint firstItem="Vuk-dn-n2c" firstAttribute="leading" secondItem="W1i-v6-zbz" secondAttribute="leading" constant="16" id="kSJ-Wa-nYA"/>
<constraint firstAttribute="trailing" secondItem="2xW-dK-D9y" secondAttribute="trailing" constant="16" id="kae-50-2nG"/>
<constraint firstItem="2xW-dK-D9y" firstAttribute="centerY" secondItem="Vuk-dn-n2c" secondAttribute="centerY" id="wCu-O0-cz8"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="00h-1i-skR"/>
<exclude reference="Ez1-s5-1EO"/>
<exclude reference="XEG-CK-41Y"/>
</mask>
</variation>
<variation key="heightClass=compact">
<mask key="constraints">
<include reference="Ez1-s5-1EO"/>
</mask>
</variation>
<variation key="heightClass=regular">
<mask key="constraints">
<include reference="00h-1i-skR"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=regular">
<mask key="constraints">
<include reference="XEG-CK-41Y"/>
<exclude reference="kSJ-Wa-nYA"/>
<exclude reference="4PG-Fm-yqS"/>
</mask>
</variation>
</view>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="sRd-zd-xSl">
<rect key="frame" x="16" y="58" width="308" height="64"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g13-pK-Eig" userLabel="Outdoor Button" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="102.5" height="64"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onOutdoorButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="UQ2-jj-fPc"/>
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="edA-Mo-3Vx" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="102.5" y="0.0" width="103" height="64"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onIsoLinesButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="3LS-C2-2Mc"/>
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4US-fZ-cyg" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="205.5" y="0.0" width="102.5" height="64"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onSubwayButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="xxM-kP-gT1"/>
</connections>
</view>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="70" id="d0H-kE-IWx"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="nea-IB-ZkL"/>
<constraint firstAttribute="height" constant="64" id="t9j-kf-yze"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="d0H-kE-IWx"/>
<exclude reference="t9j-kf-yze"/>
</mask>
</variation>
<variation key="heightClass=compact">
<mask key="constraints">
<include reference="d0H-kE-IWx"/>
</mask>
</variation>
<variation key="heightClass=regular">
<mask key="constraints">
<include reference="t9j-kf-yze"/>
</mask>
</variation>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="W1i-v6-zbz" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="6Tm-PS-VWZ"/>
<constraint firstItem="sRd-zd-xSl" firstAttribute="centerX" secondItem="W1i-v6-zbz" secondAttribute="centerX" id="7AE-Qf-1L3"/>
<constraint firstAttribute="trailing" secondItem="W1i-v6-zbz" secondAttribute="trailing" id="7ka-f7-sqc"/>
<constraint firstItem="sRd-zd-xSl" firstAttribute="top" secondItem="W1i-v6-zbz" secondAttribute="bottom" constant="8" id="PTe-W7-QnR"/>
<constraint firstItem="W1i-v6-zbz" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="RLh-Jr-7dm"/>
<constraint firstAttribute="trailing" secondItem="sRd-zd-xSl" secondAttribute="trailing" priority="750" constant="16" id="Z8f-X6-r4N"/>
<constraint firstItem="sRd-zd-xSl" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" priority="750" constant="16" id="hJE-fg-IEX"/>
<constraint firstAttribute="bottom" secondItem="sRd-zd-xSl" secondAttribute="bottom" constant="26" id="iwo-vC-EI9"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outlet property="closeButton" destination="2xW-dK-D9y" id="RQI-hb-JpS"/>
<outlet property="isoLinesButton" destination="edA-Mo-3Vx" id="qoC-8w-EqY"/>
<outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/>
<outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/>
</connections>
<point key="canvasLocation" x="137.6953125" y="201.953125"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,36 @@
final class BottomMenuPresentationController: UIPresentationController {
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
(presentedViewController as? BottomMenuViewController)?.chromeView.frame = containerView!.bounds
presentedView?.frame = frameOfPresentedViewInContainerView
}
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
guard let presentedViewController = presentedViewController as? BottomMenuViewController,
let coordinator = presentedViewController.transitionCoordinator,
let containerView = containerView else { return }
containerView.addSubview(presentedView!)
presentedViewController.containerView = containerView
presentedViewController.chromeView.frame = containerView.bounds
presentedViewController.chromeView.alpha = 0
coordinator.animate(alongsideTransition: { _ in
presentedViewController.chromeView.alpha = 1
}, completion: nil)
}
override func dismissalTransitionWillBegin() {
super.dismissalTransitionWillBegin()
guard let presentedViewController = presentedViewController as? BottomMenuViewController,
let coordinator = presentedViewController.transitionCoordinator,
let presentedView = presentedView else { return }
coordinator.animate(alongsideTransition: { _ in
presentedViewController.chromeView.alpha = 0
}, completion: { _ in
presentedView.removeFromSuperview()
})
}
}

View file

@ -0,0 +1,35 @@
final class BottomMenuTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
private let isPresentation: Bool
init(isPresentation: Bool) {
self.isPresentation = isPresentation
super.init()
}
func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else { return }
let animatingVC = isPresentation ? toVC : fromVC
guard let animatingView = animatingVC.view else { return }
let finalFrameForVC = transitionContext.finalFrame(for: animatingVC)
var initialFrameForVC = finalFrameForVC
initialFrameForVC.origin.y += initialFrameForVC.size.height
let initialFrame = isPresentation ? initialFrameForVC : finalFrameForVC
let finalFrame = isPresentation ? finalFrameForVC : initialFrameForVC
animatingView.frame = initialFrame
UIView.animate(withDuration: transitionDuration(using: transitionContext),
animations: { animatingView.frame = finalFrame },
completion: { _ in
transitionContext.completeTransition(true)
})
}
}

View file

@ -0,0 +1,14 @@
final class BottomMenuTransitioningManager: NSObject, UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source _: UIViewController) -> UIPresentationController? {
BottomMenuPresentationController(presentedViewController: presented,
presenting: presenting)
}
func animationController(forPresented _: UIViewController, presenting _: UIViewController, source _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return BottomMenuTransitioning(isPresentation: true)
}
func animationController(forDismissed _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return BottomMenuTransitioning(isPresentation: false)
}
}

View file

@ -0,0 +1,14 @@
@objc class BottomTabBarBuilder: NSObject {
@objc static func build(mapViewController: MapViewController, controlsManager: MWMMapViewControlsManager) -> BottomTabBarViewController {
let viewController = BottomTabBarViewController(nibName: nil, bundle: nil)
let interactor = BottomTabBarInteractor(viewController: viewController,
mapViewController: mapViewController,
controlsManager: controlsManager)
let presenter = BottomTabBarPresenter(interactor: interactor)
interactor.presenter = presenter
viewController.presenter = presenter
return viewController
}
}

View file

@ -0,0 +1,32 @@
import UIKit
class BottomTabBarButton: MWMButton {
@objc override func applyTheme() {
if styleName.isEmpty {
setStyle(.bottomTabBarButton)
}
for style in StyleManager.shared.getStyle(styleName) where !style.isEmpty && !style.hasExclusion(view: self) {
BottomTabBarButtonRenderer.render(self, style: style)
}
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: kExtendedTabBarTappableMargin, dy: kExtendedTabBarTappableMargin).contains(point)
}
}
class BottomTabBarButtonRenderer {
class func render(_ control: BottomTabBarButton, style: Style) {
UIViewRenderer.renderShadow(control, style: style)
UIViewRenderer.renderBorder(control, style: style)
if let coloring = style.coloring {
control.coloring = coloring
}
if let backgroundColor = style.backgroundColor {
control.backgroundColor = backgroundColor
}
}
}

View file

@ -0,0 +1,75 @@
protocol BottomTabBarInteractorProtocol: AnyObject {
func openSearch()
func openLeftButton()
func openBookmarks()
func openMenu()
}
class BottomTabBarInteractor {
weak var presenter: BottomTabBarPresenterProtocol?
private weak var viewController: UIViewController?
private weak var mapViewController: MapViewController?
private weak var controlsManager: MWMMapViewControlsManager?
private let searchManager: SearchOnMapManager
init(viewController: UIViewController, mapViewController: MapViewController, controlsManager: MWMMapViewControlsManager) {
self.viewController = viewController
self.mapViewController = mapViewController
self.controlsManager = controlsManager
self.searchManager = mapViewController.searchManager
}
}
extension BottomTabBarInteractor: BottomTabBarInteractorProtocol {
func openSearch() {
searchManager.isSearching ? searchManager.close() : searchManager.startSearching(isRouting: false)
}
func openLeftButton() {
switch Settings.leftButtonType {
case .addPlace:
if let delegate = controlsManager as? BottomMenuDelegate {
delegate.addPlace()
}
case .settings:
mapViewController?.openSettings()
case .recordTrack:
let mapViewController = MapViewController.shared()!
let trackRecorder: TrackRecordingManager = .shared
switch trackRecorder.recordingState {
case .active:
mapViewController.showTrackRecordingPlacePage()
case .inactive:
trackRecorder.start { result in
switch result {
case .success:
mapViewController.showTrackRecordingPlacePage()
case .failure:
break
}
}
}
default:
mapViewController?.openAbout()
}
}
func openBookmarks() {
mapViewController?.bookmarksCoordinator.open()
}
func openMenu() {
guard let state = controlsManager?.menuState else {
fatalError("ERROR: Failed to retrieve the current MapViewControlsManager's state.")
}
switch state {
case .inactive: controlsManager?.menuState = .active
case .active: controlsManager?.menuState = .inactive
case .hidden:
// When the current controls manager's state is hidden, accidental taps on the menu button during the hiding animation should be skipped.
break;
case .layers: fallthrough
@unknown default: fatalError("ERROR: Unexpected MapViewControlsManager's state: \(state)")
}
}
}

View file

@ -0,0 +1,37 @@
protocol BottomTabBarPresenterProtocol: AnyObject {
func configure()
func onLeftButtonPressed()
func onSearchButtonPressed()
func onBookmarksButtonPressed()
func onMenuButtonPressed()
}
class BottomTabBarPresenter: NSObject {
private let interactor: BottomTabBarInteractorProtocol
init(interactor: BottomTabBarInteractorProtocol) {
self.interactor = interactor
}
}
extension BottomTabBarPresenter: BottomTabBarPresenterProtocol {
func configure() {
}
func onLeftButtonPressed() {
interactor.openLeftButton()
}
func onSearchButtonPressed() {
interactor.openSearch()
}
func onBookmarksButtonPressed() {
interactor.openBookmarks()
}
func onMenuButtonPressed() {
interactor.openMenu()
}
}

View file

@ -0,0 +1,28 @@
let kExtendedTabBarTappableMargin: CGFloat = -15
final class BottomTabBarView: SolidTouchView {
@IBOutlet var mainButtonsView: ExtendedBottomTabBarContainerView!
override var placePageAreaAffectDirections: MWMAvailableAreaAffectDirections {
return alternative(iPhone: [], iPad: [.bottom])
}
override var widgetsAreaAffectDirections: MWMAvailableAreaAffectDirections {
return [.bottom]
}
override var sideButtonsAreaAffectDirections: MWMAvailableAreaAffectDirections {
return [.bottom]
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: kExtendedTabBarTappableMargin, dy: kExtendedTabBarTappableMargin).contains(point)
}
}
final class ExtendedBottomTabBarContainerView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: kExtendedTabBarTappableMargin, dy: kExtendedTabBarTappableMargin).contains(point)
}
}

View file

@ -0,0 +1,131 @@
class BottomTabBarViewController: UIViewController {
var presenter: BottomTabBarPresenterProtocol!
@IBOutlet var leftButton: MWMButton?
@IBOutlet var searchButton: MWMButton?
@IBOutlet var searchConstraintWithLeftButton: NSLayoutConstraint?
@IBOutlet var searchConstraintWithoutLeftButton: NSLayoutConstraint?
@IBOutlet var bookmarksButton: MWMButton?
@IBOutlet var bookmarksConstraintWithLeftButton: NSLayoutConstraint?
@IBOutlet var bookmarksConstraintWithoutLeftButton: NSLayoutConstraint?
@IBOutlet var moreButton: MWMButton?
@IBOutlet var downloadBadge: UIView?
private var avaliableArea = CGRect.zero
@objc var isHidden: Bool = false {
didSet {
updateFrame(animated: true)
}
}
@objc var isApplicationBadgeHidden: Bool = true {
didSet {
updateBadge()
}
}
var tabBarView: BottomTabBarView {
return view as! BottomTabBarView
}
@objc static var controller: BottomTabBarViewController? {
return MWMMapViewControlsManager.manager()?.tabBarController
}
override func viewDidLoad() {
super.viewDidLoad()
presenter.configure()
NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: nil) { _ in
DispatchQueue.main.async {
self.updateLeftButton()
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
leftButton?.imageView?.contentMode = .scaleAspectFit
updateBadge()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
updateLeftButton()
}
static func updateAvailableArea(_ frame: CGRect) {
BottomTabBarViewController.controller?.updateAvailableArea(frame)
}
@IBAction func onSearchButtonPressed(_ sender: Any) {
presenter.onSearchButtonPressed()
}
@IBAction func onLeftButtonPressed(_ sender: Any) {
presenter.onLeftButtonPressed()
}
@IBAction func onBookmarksButtonPressed(_ sender: Any) {
presenter.onBookmarksButtonPressed()
}
@IBAction func onMenuButtonPressed(_ sender: Any) {
presenter.onMenuButtonPressed()
}
private func updateAvailableArea(_ frame:CGRect) {
avaliableArea = frame
updateFrame(animated: false)
self.view.layoutIfNeeded()
}
private func updateFrame(animated: Bool) {
if avaliableArea == .zero {
return
}
let newFrame = CGRect(x: avaliableArea.minX,
y: isHidden ? avaliableArea.minY + avaliableArea.height : avaliableArea.minY,
width: avaliableArea.width,
height: avaliableArea.height)
let alpha:CGFloat = isHidden ? 0 : 1
if animated {
UIView.animate(withDuration: kDefaultAnimationDuration,
delay: 0,
options: [.beginFromCurrentState],
animations: {
self.view.frame = newFrame
self.view.alpha = alpha
}, completion: nil)
} else {
self.view.frame = newFrame
self.view.alpha = alpha
}
}
private func updateLeftButton() {
let leftButtonType = Settings.leftButtonType
if leftButtonType == .hidden {
leftButton?.isHidden = true
if let searchConstraintWithLeftButton, let searchConstraintWithoutLeftButton, let bookmarksConstraintWithLeftButton, let bookmarksConstraintWithoutLeftButton {
NSLayoutConstraint.activate([searchConstraintWithoutLeftButton, bookmarksConstraintWithoutLeftButton])
NSLayoutConstraint.deactivate([searchConstraintWithLeftButton, bookmarksConstraintWithLeftButton])
}
} else {
leftButton?.isHidden = false
leftButton?.setTitle(nil, for: .normal)
leftButton?.setImage(leftButtonType.image, for: .normal)
leftButton?.accessibilityLabel = leftButtonType.description;
if let searchConstraintWithLeftButton, let searchConstraintWithoutLeftButton, let bookmarksConstraintWithLeftButton, let bookmarksConstraintWithoutLeftButton {
NSLayoutConstraint.activate([searchConstraintWithLeftButton, bookmarksConstraintWithLeftButton])
NSLayoutConstraint.deactivate([searchConstraintWithoutLeftButton, bookmarksConstraintWithoutLeftButton])
}
}
}
private func updateBadge() {
downloadBadge?.isHidden = isApplicationBadgeHidden
}
}

View file

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24128" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="BottomTabBarViewController" customModule="CoMaps" customModuleProvider="target">
<connections>
<outlet property="bookmarksButton" destination="dgG-ki-3tB" id="md5-3T-9tb"/>
<outlet property="bookmarksConstraintWithLeftButton" destination="Jc7-nc-elY" id="gW7-8e-E6m"/>
<outlet property="bookmarksConstraintWithoutLeftButton" destination="NRb-vj-MFg" id="C3Z-Ia-D6i"/>
<outlet property="downloadBadge" destination="uDI-ZC-4wx" id="fAf-cy-Ozn"/>
<outlet property="leftButton" destination="dzf-7Z-N6a" id="LMZ-H7-ftQ"/>
<outlet property="moreButton" destination="svD-yi-GrZ" id="kjk-ZW-nZN"/>
<outlet property="searchButton" destination="No0-ld-JX3" id="m5F-UT-j94"/>
<outlet property="searchConstraintWithLeftButton" destination="tDb-w1-ueQ" id="WaI-Xb-1bu"/>
<outlet property="searchConstraintWithoutLeftButton" destination="cQg-jW-uSD" id="cMy-EC-G07"/>
<outlet property="view" destination="zuH-WU-hiP" id="eoa-4I-wKs"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="zuH-WU-hiP" customClass="BottomTabBarView" customModule="CoMaps" customModuleProvider="target" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="373" height="84"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vum-s3-PHx" userLabel="MainButtons" customClass="ExtendedBottomTabBarContainerView" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="373" height="48"/>
<subviews>
<button opaque="NO" contentMode="scaleAspectFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dzf-7Z-N6a" userLabel="LeftButton" customClass="BottomTabBarButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="22.5" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="helpButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="dzf-7Z-N6a" secondAttribute="height" id="qNJ-0K-sK0"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="30"/>
<inset key="imageEdgeInsets" minX="9" minY="9" maxX="9" maxY="9"/>
<state key="normal" image="info.circle" catalog="system"/>
<connections>
<action selector="onLeftButtonPressed:" destination="-1" eventType="touchUpInside" id="1gx-P2-sRJ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="249" horizontalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="No0-ld-JX3" userLabel="Search" customClass="BottomTabBarButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="116" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="searchButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="No0-ld-JX3" secondAttribute="height" id="2bW-fc-Hsh"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu_search"/>
<connections>
<action selector="onSearchButtonPressed:" destination="-1" eventType="touchUpInside" id="0D5-RB-HBQ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleAspectFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dgG-ki-3tB" userLabel="Bookmarks" customClass="BottomTabBarButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="209" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="bookmarksButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="dgG-ki-3tB" secondAttribute="height" id="o3b-it-lrV"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu_bookmark_list"/>
<connections>
<action selector="onBookmarksButtonPressed:" destination="-1" eventType="touchUpInside" id="9Z1-eg-xth"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleAspectFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="svD-yi-GrZ" userLabel="Menu" customClass="BottomTabBarButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="302.5" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="menuButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="svD-yi-GrZ" secondAttribute="height" id="gmG-3a-Mqe"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu"/>
<connections>
<action selector="onMenuButtonPressed:" destination="-1" eventType="touchUpInside" id="rzb-y4-nR1"/>
</connections>
</button>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uDI-ZC-4wx" userLabel="DownloadBadge">
<rect key="frame" x="329.5" y="11" width="10" height="10"/>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" notEnabled="YES"/>
</accessibility>
<constraints>
<constraint firstAttribute="width" constant="10" id="tEP-Xi-qnU"/>
<constraint firstAttribute="height" constant="10" id="wNg-5Z-7AO"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Badge"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<accessibility key="accessibilityConfiguration" identifier="MainButtons"/>
<constraints>
<constraint firstAttribute="height" constant="48" id="69A-eu-uLp"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="8nL-zT-Y7b"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="9eR-I7-7at"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="Fde-um-JL6"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="1.25" id="Jc7-nc-elY"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="JjT-sc-hIY"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" priority="900" id="NRb-vj-MFg"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="1.75" id="Q0b-gd-HwS"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="Rs8-Hl-CAc"/>
<constraint firstItem="uDI-ZC-4wx" firstAttribute="centerX" secondItem="svD-yi-GrZ" secondAttribute="centerX" constant="8" id="XNb-Ba-Hn7"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="Zug-zY-KIX"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="0.25" priority="900" id="cQg-jW-uSD"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="sja-hO-YY3"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="0.75" id="tDb-w1-ueQ"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="0.25" id="u3G-gY-98J"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="yTg-8g-H1p"/>
<constraint firstItem="uDI-ZC-4wx" firstAttribute="centerY" secondItem="svD-yi-GrZ" secondAttribute="centerY" constant="-8" id="yq3-ui-IaL"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="cQg-jW-uSD"/>
<exclude reference="NRb-vj-MFg"/>
</mask>
</variation>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="aaw-Hz-zma"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="vum-s3-PHx" firstAttribute="top" secondItem="zuH-WU-hiP" secondAttribute="top" id="PQS-ro-25e"/>
<constraint firstItem="vum-s3-PHx" firstAttribute="leading" secondItem="zuH-WU-hiP" secondAttribute="leading" id="kza-JN-Dul"/>
<constraint firstAttribute="trailing" secondItem="vum-s3-PHx" secondAttribute="trailing" id="sM6-P2-rN9"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="mainButtonsView" destination="vum-s3-PHx" id="fBi-DA-orA"/>
</connections>
<point key="canvasLocation" x="72" y="254"/>
</view>
</objects>
<resources>
<image name="ic_menu" width="48" height="48"/>
<image name="ic_menu_bookmark_list" width="48" height="48"/>
<image name="ic_menu_search" width="48" height="48"/>
<image name="info.circle" catalog="system" width="128" height="123"/>
</resources>
</document>