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,38 @@
import Foundation
struct CPConstants {
struct TemplateKey {
static let map = "map_type"
static let alert = "alert_type"
static let list = "list_type"
}
struct TemplateType {
static let main = "main"
static let navigation = "navigation"
static let preview = "preview"
static let previewAccepted = "preview_accepted"
static let previewSettings = "preview_settings"
static let redirectRoute = "redirect_route"
static let restoreRoute = "restore_route"
static let downloadMap = "download_map"
}
struct ListItemType {
static let history = "history"
static let bookmarks = "bookmarks"
static let bookmarkLists = "bookmark_lists"
static let searchResults = "search_results"
}
struct Maneuvers {
static let primary = "primary"
static let secondary = "secondary"
}
struct Trip {
static let start = "start_point"
static let end = "end_point"
static let errorCode = "error_code"
static let missedCountries = "countries"
}
}

View file

@ -0,0 +1,7 @@
import Foundation
enum CPViewPortState: Int {
case `default`
case preview
case navigation
}

View file

@ -0,0 +1,366 @@
import CarPlay
import Contacts
protocol CarPlayRouterListener: AnyObject {
func didCreateRoute(routeInfo: RouteInfo,
trip: CPTrip)
func didUpdateRouteInfo(_ routeInfo: RouteInfo, forTrip trip: CPTrip)
func didFailureBuildRoute(forTrip trip: CPTrip, code: RouterResultCode, countries: [String])
func routeDidFinish(_ trip: CPTrip)
}
@objc(MWMCarPlayRouter)
final class CarPlayRouter: NSObject {
private let listenerContainer: ListenerContainer<CarPlayRouterListener>
private var routeSession: CPNavigationSession?
private var initialSpeedCamSettings: SpeedCameraManagerMode
var currentTrip: CPTrip? {
return routeSession?.trip
}
var previewTrip: CPTrip?
var speedCameraMode: SpeedCameraManagerMode {
return RoutingManager.routingManager.speedCameraMode
}
override init() {
listenerContainer = ListenerContainer<CarPlayRouterListener>()
initialSpeedCamSettings = RoutingManager.routingManager.speedCameraMode
super.init()
}
func addListener(_ listener: CarPlayRouterListener) {
listenerContainer.addListener(listener)
}
func removeListener(_ listener: CarPlayRouterListener) {
listenerContainer.removeListener(listener)
}
func subscribeToEvents() {
RoutingManager.routingManager.add(self)
}
func unsubscribeFromEvents() {
RoutingManager.routingManager.remove(self)
}
func completeRouteAndRemovePoints() {
let manager = RoutingManager.routingManager
manager.stopRoutingAndRemoveRoutePoints(true)
manager.deleteSavedRoutePoints()
manager.apply(routeType: .vehicle)
previewTrip = nil
}
func rebuildRoute() {
guard let trip = previewTrip else { return }
do {
try RoutingManager.routingManager.buildRoute()
} catch let error as NSError {
listenerContainer.forEach({
let code = RouterResultCode(rawValue: UInt(error.code)) ?? .internalError
$0.didFailureBuildRoute(forTrip: trip, code: code, countries: [])
})
}
}
func buildRoute(trip: CPTrip) {
completeRouteAndRemovePoints()
previewTrip = trip
guard let info = trip.userInfo as? [String: MWMRoutePoint] else {
listenerContainer.forEach({
$0.didFailureBuildRoute(forTrip: trip, code: .routeNotFound, countries: [])
})
return
}
guard let startPoint = info[CPConstants.Trip.start],
let endPoint = info[CPConstants.Trip.end] else {
listenerContainer.forEach({
var code: RouterResultCode!
if info[CPConstants.Trip.end] == nil {
code = .endPointNotFound
} else {
code = .startPointNotFound
}
$0.didFailureBuildRoute(forTrip: trip, code: code, countries: [])
})
return
}
let manager = RoutingManager.routingManager
manager.add(routePoint: startPoint)
manager.add(routePoint: endPoint)
do {
try manager.buildRoute()
} catch let error as NSError {
listenerContainer.forEach({
let code = RouterResultCode(rawValue: UInt(error.code)) ?? .internalError
$0.didFailureBuildRoute(forTrip: trip, code: code, countries: [])
})
}
}
func updateStartPointAndRebuild(trip: CPTrip) {
let manager = RoutingManager.routingManager
previewTrip = trip
guard let info = trip.userInfo as? [String: MWMRoutePoint] else {
listenerContainer.forEach({
$0.didFailureBuildRoute(forTrip: trip, code: .routeNotFound, countries: [])
})
return
}
guard let startPoint = info[CPConstants.Trip.start] else {
listenerContainer.forEach({
$0.didFailureBuildRoute(forTrip: trip, code: .startPointNotFound, countries: [])
})
return
}
manager.add(routePoint: startPoint)
manager.apply(routeType: .vehicle)
do {
try manager.buildRoute()
} catch let error as NSError {
listenerContainer.forEach({
let code = RouterResultCode(rawValue: UInt(error.code)) ?? .internalError
$0.didFailureBuildRoute(forTrip: trip, code: code, countries: [])
})
}
}
func startRoute() {
let manager = RoutingManager.routingManager
manager.startRoute()
}
func setupCarPlaySpeedCameraMode() {
if case .auto = initialSpeedCamSettings {
RoutingManager.routingManager.speedCameraMode = .always
}
}
func setupInitialSpeedCameraMode() {
RoutingManager.routingManager.speedCameraMode = initialSpeedCamSettings
}
func updateSpeedCameraMode(_ mode: SpeedCameraManagerMode) {
initialSpeedCamSettings = mode
RoutingManager.routingManager.speedCameraMode = mode
}
func restoreTripPreviewOnCarplay(beforeRootTemplateDidAppear: Bool) {
guard MWMRouter.isRestoreProcessCompleted() else {
DispatchQueue.main.async { [weak self] in
self?.restoreTripPreviewOnCarplay(beforeRootTemplateDidAppear: false)
}
return
}
let manager = RoutingManager.routingManager
MWMRouter.hideNavigationMapControls()
guard manager.isRoutingActive,
let startPoint = manager.startPoint,
let endPoint = manager.endPoint else {
completeRouteAndRemovePoints()
return
}
let trip = createTrip(startPoint: startPoint,
endPoint: endPoint,
routeInfo: manager.routeInfo)
previewTrip = trip
if manager.type != .vehicle {
CarPlayService.shared.showRecoverRouteAlert(trip: trip, isTypeCorrect: false)
return
}
if !startPoint.isMyPosition {
CarPlayService.shared.showRecoverRouteAlert(trip: trip, isTypeCorrect: true)
return
}
if beforeRootTemplateDidAppear {
CarPlayService.shared.preparedToPreviewTrips = [trip]
} else {
CarPlayService.shared.preparePreview(trips: [trip])
}
}
func restoredNavigationSession() -> (CPTrip, RouteInfo)? {
let manager = RoutingManager.routingManager
if manager.isOnRoute,
manager.type == .vehicle,
let startPoint = manager.startPoint,
let endPoint = manager.endPoint,
let routeInfo = manager.routeInfo {
MWMRouter.hideNavigationMapControls()
let trip = createTrip(startPoint: startPoint,
endPoint: endPoint,
routeInfo: routeInfo)
previewTrip = trip
return (trip, routeInfo)
}
return nil
}
}
// MARK: - Navigation session management
extension CarPlayRouter {
func startNavigationSession(forTrip trip: CPTrip, template: CPMapTemplate) {
routeSession = template.startNavigationSession(for: trip)
routeSession?.pauseTrip(for: .loading, description: nil)
updateUpcomingManeuvers()
RoutingManager.routingManager.setOnNewTurnCallback { [weak self] in
self?.updateUpcomingManeuvers()
}
}
func cancelTrip() {
routeSession?.cancelTrip()
routeSession = nil
completeRouteAndRemovePoints()
RoutingManager.routingManager.resetOnNewTurnCallback()
}
func finishTrip() {
routeSession?.finishTrip()
routeSession = nil
completeRouteAndRemovePoints()
RoutingManager.routingManager.resetOnNewTurnCallback()
}
func updateUpcomingManeuvers() {
let maneuvers = createUpcomingManeuvers()
routeSession?.upcomingManeuvers = maneuvers
}
func updateEstimates() {
guard let routeSession = routeSession,
let routeInfo = RoutingManager.routingManager.routeInfo,
let primaryManeuver = routeSession.upcomingManeuvers.first,
let estimates = createEstimates(routeInfo) else {
return
}
routeSession.updateEstimates(estimates, for: primaryManeuver)
}
private func createEstimates(_ routeInfo: RouteInfo) -> CPTravelEstimates? {
let measurement = Measurement(value: routeInfo.distanceToTurn, unit: routeInfo.turnUnits)
return CPTravelEstimates(distanceRemaining: measurement, timeRemaining: 0.0)
}
private func createUpcomingManeuvers() -> [CPManeuver] {
guard let routeInfo = RoutingManager.routingManager.routeInfo else {
return []
}
var maneuvers = [CPManeuver]()
let primaryManeuver = CPManeuver()
primaryManeuver.userInfo = CPConstants.Maneuvers.primary
var instructionVariant = routeInfo.streetName
if routeInfo.roundExitNumber != 0 {
let ordinalExitNumber = NumberFormatter.localizedString(from: NSNumber(value: routeInfo.roundExitNumber),
number: .ordinal)
let exitNumber = String(format: L("carplay_roundabout_exit"),
arguments: [ordinalExitNumber])
instructionVariant = instructionVariant.isEmpty ? exitNumber : (exitNumber + ", " + instructionVariant)
}
primaryManeuver.instructionVariants = [instructionVariant]
if let imageName = routeInfo.turnImageName,
let symbol = UIImage(named: imageName) {
primaryManeuver.symbolImage = symbol
}
if let estimates = createEstimates(routeInfo) {
primaryManeuver.initialTravelEstimates = estimates
}
maneuvers.append(primaryManeuver)
if let imageName = routeInfo.nextTurnImageName,
let symbol = UIImage(named: imageName) {
let secondaryManeuver = CPManeuver()
secondaryManeuver.userInfo = CPConstants.Maneuvers.secondary
secondaryManeuver.instructionVariants = [L("then_turn")]
secondaryManeuver.symbolImage = symbol
maneuvers.append(secondaryManeuver)
}
return maneuvers
}
func createTrip(startPoint: MWMRoutePoint, endPoint: MWMRoutePoint, routeInfo: RouteInfo? = nil) -> CPTrip {
let startPlacemark = MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: startPoint.latitude,
longitude: startPoint.longitude))
let endPlacemark = MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: endPoint.latitude,
longitude: endPoint.longitude),
addressDictionary: [CNPostalAddressStreetKey: endPoint.subtitle ?? ""])
let startItem = MKMapItem(placemark: startPlacemark)
let endItem = MKMapItem(placemark: endPlacemark)
endItem.name = endPoint.title
let routeChoice = CPRouteChoice(summaryVariants: [" "], additionalInformationVariants: [], selectionSummaryVariants: [])
routeChoice.userInfo = routeInfo
let trip = CPTrip(origin: startItem, destination: endItem, routeChoices: [routeChoice])
trip.userInfo = [CPConstants.Trip.start: startPoint, CPConstants.Trip.end: endPoint]
return trip
}
}
// MARK: - RoutingManagerListener implementation
extension CarPlayRouter: RoutingManagerListener {
func updateCameraInfo(isCameraOnRoute: Bool, speedLimitMps limit: Double) {
CarPlayService.shared.updateCameraUI(isCameraOnRoute: isCameraOnRoute, speedLimitMps: limit < 0 ? nil : limit)
}
func processRouteBuilderEvent(with code: RouterResultCode, countries: [String]) {
guard let trip = previewTrip else {
return
}
switch code {
case .noError, .hasWarnings:
let manager = RoutingManager.routingManager
if manager.isRouteFinished {
listenerContainer.forEach({
$0.routeDidFinish(trip)
})
return
}
if let info = manager.routeInfo {
previewTrip?.routeChoices.first?.userInfo = info
if routeSession == nil {
listenerContainer.forEach({
$0.didCreateRoute(routeInfo: info,
trip: trip)
})
} else {
listenerContainer.forEach({
$0.didUpdateRouteInfo(info, forTrip: trip)
})
updateUpcomingManeuvers()
}
}
default:
listenerContainer.forEach({
$0.didFailureBuildRoute(forTrip: trip, code: code, countries: countries)
})
}
}
func didLocationUpdate(_ notifications: [String]) {
guard let trip = previewTrip else { return }
let manager = RoutingManager.routingManager
if manager.isRouteFinished {
listenerContainer.forEach({
$0.routeDidFinish(trip)
})
return
}
guard let routeInfo = manager.routeInfo,
manager.isRoutingActive else { return }
listenerContainer.forEach({
$0.didUpdateRouteInfo(routeInfo, forTrip: trip)
})
let tts = MWMTextToSpeech.tts()!
if manager.isOnRoute && tts.active {
tts.playTurnNotifications(notifications)
tts.playWarningSound()
}
}
}

View file

@ -0,0 +1,805 @@
import CarPlay
import Contacts
@objc(MWMCarPlayService)
final class CarPlayService: NSObject {
@objc static let shared = CarPlayService()
@objc var isCarplayActivated: Bool = false
private var searchService: CarPlaySearchService?
private var router: CarPlayRouter?
private var window: CPWindow?
private var interfaceController: CPInterfaceController?
private var sessionConfiguration: CPSessionConfiguration?
var currentPositionMode: MWMMyPositionMode = .pendingPosition
var isSpeedCamActivated: Bool {
set {
router?.updateSpeedCameraMode(newValue ? .always: .never)
}
get {
let mode: SpeedCameraManagerMode = router?.speedCameraMode ?? .never
return mode == .always ? true : false
}
}
var isKeyboardLimited: Bool {
return sessionConfiguration?.limitedUserInterfaces.contains(.keyboard) ?? false
}
private var carplayVC: CarPlayMapViewController? {
return window?.rootViewController as? CarPlayMapViewController
}
private var rootMapTemplate: CPMapTemplate? {
return interfaceController?.rootTemplate as? CPMapTemplate
}
var preparedToPreviewTrips: [CPTrip] = []
var isUserPanMap: Bool = false
private var searchText = ""
@objc func setup(window: CPWindow, interfaceController: CPInterfaceController) {
isCarplayActivated = true
self.window = window
self.interfaceController = interfaceController
self.interfaceController?.delegate = self
let configuration = CPSessionConfiguration(delegate: self)
sessionConfiguration = configuration
searchService = CarPlaySearchService()
let router = CarPlayRouter()
router.addListener(self)
router.subscribeToEvents()
router.setupCarPlaySpeedCameraMode()
self.router = router
MWMRouter.unsubscribeFromEvents()
applyRootViewController()
if let sessionData = router.restoredNavigationSession() {
applyNavigationRootTemplate(trip: sessionData.0, routeInfo: sessionData.1)
} else {
applyBaseRootTemplate()
router.restoreTripPreviewOnCarplay(beforeRootTemplateDidAppear: true)
}
updateContentStyle(configuration.contentStyle)
FrameworkHelper.updatePositionArrowOffset(false, offset: (Int32(window.height * window.screen.scale)/3))
CarPlayWindowScaleAdjuster.updateAppearance(
fromWindow: MapsAppDelegate.theApp().window,
toWindow: window,
isCarplayActivated: true
)
}
private var savedInterfaceController: CPInterfaceController?
@objc func showOnPhone() {
savedInterfaceController = interfaceController
switchScreenToPhone()
showPhoneModeAlert()
}
@objc func showOnCarplay() {
if let window, let savedInterfaceController {
setup(window: window, interfaceController: savedInterfaceController)
}
}
private func showPhoneModeAlert() {
let switchToCarAction = CPAlertAction(
title: L("car_continue_in_the_car"),
style: .default,
handler: { [weak self] _ in
self?.savedInterfaceController?.dismissTemplate(animated: false)
self?.showOnCarplay()
}
)
let alert = CPAlertTemplate(
titleVariants: [L("car_used_on_the_phone_screen")],
actions: [switchToCarAction]
)
savedInterfaceController?.dismissTemplate(animated: false)
savedInterfaceController?.presentTemplate(alert, animated: false)
}
private func switchScreenToPhone() {
if let carplayVC = carplayVC {
carplayVC.removeMapView()
}
if let mvc = MapViewController.shared() {
mvc.disableCarPlayRepresentation()
mvc.remove(self)
}
router?.removeListener(self)
router?.unsubscribeFromEvents()
router?.setupInitialSpeedCameraMode()
MWMRouter.subscribeToEvents()
isCarplayActivated = false
if router?.currentTrip != nil {
MWMRouter.showNavigationMapControls()
} else if router?.previewTrip != nil {
MWMRouter.rebuild(withBestRouter: true)
}
searchService = nil
router = nil
sessionConfiguration = nil
interfaceController = nil
ThemeManager.invalidate()
FrameworkHelper.updatePositionArrowOffset(true, offset: 0)
if let window {
CarPlayWindowScaleAdjuster.updateAppearance(
fromWindow: window,
toWindow: MapsAppDelegate.theApp().window,
isCarplayActivated: false
)
}
}
@objc func destroy() {
if isCarplayActivated {
switchScreenToPhone()
}
savedInterfaceController = nil
}
@objc func interfaceStyle() -> UIUserInterfaceStyle {
if let window = window,
window.traitCollection.userInterfaceIdiom == .carPlay {
return rootTemplateStyle == .dark ? .dark : .light
}
return .unspecified
}
@available(iOS 13.0, *)
private func updateContentStyle(_ contentStyle: CPContentStyle) {
rootTemplateStyle = contentStyle == .dark ? .dark : .light
// Update the current map style in accordance with the CarPLay content theme.
ThemeManager.invalidate()
}
private var rootTemplateStyle: CPTripEstimateStyle = .light {
didSet {
(interfaceController?.rootTemplate as? CPMapTemplate)?.tripEstimateStyle = rootTemplateStyle
}
}
private func applyRootViewController() {
guard let window = window else { return }
let carplaySotyboard = UIStoryboard.instance(.carPlay)
let carplayVC = carplaySotyboard.instantiateInitialViewController() as! CarPlayMapViewController
window.rootViewController = carplayVC
if let mapVC = MapViewController.shared() {
currentPositionMode = mapVC.currentPositionMode
mapVC.enableCarPlayRepresentation()
carplayVC.addMapView(mapVC.mapView, mapButtonSafeAreaLayoutGuide: window.mapButtonSafeAreaLayoutGuide)
mapVC.add(self)
}
}
private func applyBaseRootTemplate() {
let mapTemplate = MapTemplateBuilder.buildBaseTemplate(positionMode: currentPositionMode)
mapTemplate.mapDelegate = self
mapTemplate.tripEstimateStyle = rootTemplateStyle
interfaceController?.setRootTemplate(mapTemplate, animated: true)
FrameworkHelper.rotateMap(0.0, animated: false)
}
private func applyNavigationRootTemplate(trip: CPTrip, routeInfo: RouteInfo) {
let mapTemplate = MapTemplateBuilder.buildNavigationTemplate()
mapTemplate.mapDelegate = self
interfaceController?.setRootTemplate(mapTemplate, animated: true)
router?.startNavigationSession(forTrip: trip, template: mapTemplate)
if let estimates = createEstimates(routeInfo: routeInfo) {
mapTemplate.tripEstimateStyle = rootTemplateStyle
mapTemplate.updateEstimates(estimates, for: trip)
}
if let carplayVC = carplayVC {
carplayVC.updateCurrentSpeed(routeInfo.speedMps, speedLimitMps: routeInfo.speedLimitMps)
carplayVC.showSpeedControl()
}
}
func pushTemplate(_ templateToPush: CPTemplate, animated: Bool) {
if let interfaceController = interfaceController {
switch templateToPush {
case let list as CPListTemplate:
list.delegate = self
case let search as CPSearchTemplate:
search.delegate = self
case let map as CPMapTemplate:
map.mapDelegate = self
default:
break
}
interfaceController.pushTemplate(templateToPush, animated: animated)
}
}
func popTemplate(animated: Bool) {
interfaceController?.popTemplate(animated: animated)
}
func presentAlert(_ template: CPAlertTemplate, animated: Bool) {
interfaceController?.dismissTemplate(animated: false)
interfaceController?.presentTemplate(template, animated: animated)
}
func cancelCurrentTrip() {
router?.cancelTrip()
if let carplayVC = carplayVC {
carplayVC.hideSpeedControl()
}
updateMapTemplateUIToBase()
}
func updateCameraUI(isCameraOnRoute: Bool, speedLimitMps limit: Double?) {
if let carplayVC = carplayVC {
carplayVC.updateCameraInfo(isCameraOnRoute: isCameraOnRoute, speedLimitMps: limit)
}
}
func updateMapTemplateUIToBase() {
guard let mapTemplate = rootMapTemplate else {
return
}
MapTemplateBuilder.configureBaseUI(mapTemplate: mapTemplate)
if currentPositionMode == .pendingPosition {
mapTemplate.leadingNavigationBarButtons = []
} else if currentPositionMode == .follow || currentPositionMode == .followAndRotate {
MapTemplateBuilder.setupDestinationButton(mapTemplate: mapTemplate)
} else {
MapTemplateBuilder.setupRecenterButton(mapTemplate: mapTemplate)
}
updateVisibleViewPortState(.default)
FrameworkHelper.rotateMap(0.0, animated: true)
}
func updateMapTemplateUIToTripFinished(_ trip: CPTrip) {
guard let mapTemplate = rootMapTemplate else {
return
}
mapTemplate.leadingNavigationBarButtons = []
mapTemplate.trailingNavigationBarButtons = []
mapTemplate.mapButtons = []
let doneAction = CPAlertAction(title: L("done"), style: .default) { [unowned self] _ in
self.updateMapTemplateUIToBase()
}
var subtitle = ""
if let locationName = trip.destination.name {
subtitle = locationName
}
if let address = trip.destination.placemark.postalAddress?.street {
subtitle = subtitle + "\n" + address
}
let alert = CPNavigationAlert(titleVariants: [L("trip_finished")],
subtitleVariants: [subtitle],
image: nil,
primaryAction: doneAction,
secondaryAction: nil,
duration: 0)
mapTemplate.present(navigationAlert: alert, animated: true)
}
func updateVisibleViewPortState(_ state: CPViewPortState) {
guard let carplayVC = carplayVC else {
return
}
carplayVC.updateVisibleViewPortState(state)
}
func updateRouteAfterChangingSettings() {
router?.rebuildRoute()
}
@objc func showNoMapAlert() {
guard let mapTemplate = interfaceController?.topTemplate as? CPMapTemplate,
let info = mapTemplate.userInfo as? MapInfo,
info.type == CPConstants.TemplateType.main else {
return
}
let alert = CPAlertTemplate(titleVariants: [L("download_map_carplay")], actions: [])
alert.userInfo = [CPConstants.TemplateKey.alert: CPConstants.TemplateType.downloadMap]
presentAlert(alert, animated: true)
}
@objc func hideNoMapAlert() {
if let presentedTemplate = interfaceController?.presentedTemplate,
let info = presentedTemplate.userInfo as? [String: String],
let alertType = info[CPConstants.TemplateKey.alert],
alertType == CPConstants.TemplateType.downloadMap {
interfaceController?.dismissTemplate(animated: true)
}
}
}
// MARK: - CPInterfaceControllerDelegate implementation
extension CarPlayService: CPInterfaceControllerDelegate {
func templateWillAppear(_ aTemplate: CPTemplate, animated: Bool) {
guard let info = aTemplate.userInfo as? MapInfo else {
return
}
switch info.type {
case CPConstants.TemplateType.main:
updateVisibleViewPortState(.default)
case CPConstants.TemplateType.preview:
updateVisibleViewPortState(.preview)
case CPConstants.TemplateType.navigation:
updateVisibleViewPortState(.navigation)
case CPConstants.TemplateType.previewSettings:
aTemplate.userInfo = MapInfo(type: CPConstants.TemplateType.preview)
default:
break
}
}
func templateDidAppear(_ aTemplate: CPTemplate, animated: Bool) {
guard let mapTemplate = aTemplate as? CPMapTemplate,
let info = aTemplate.userInfo as? MapInfo else {
return
}
if !preparedToPreviewTrips.isEmpty && info.type == CPConstants.TemplateType.main {
preparePreview(trips: preparedToPreviewTrips)
preparedToPreviewTrips = []
return
}
if info.type == CPConstants.TemplateType.preview, let trips = info.trips {
showPreview(mapTemplate: mapTemplate, trips: trips)
}
}
func templateWillDisappear(_ aTemplate: CPTemplate, animated: Bool) {
guard let info = aTemplate.userInfo as? MapInfo else {
return
}
if info.type == CPConstants.TemplateType.preview {
router?.completeRouteAndRemovePoints()
}
}
func templateDidDisappear(_ aTemplate: CPTemplate, animated: Bool) {
guard !preparedToPreviewTrips.isEmpty,
let info = aTemplate.userInfo as? [String: String],
let alertType = info[CPConstants.TemplateKey.alert],
alertType == CPConstants.TemplateType.redirectRoute ||
alertType == CPConstants.TemplateType.restoreRoute else {
return
}
preparePreview(trips: preparedToPreviewTrips)
preparedToPreviewTrips = []
}
}
// MARK: - CPSessionConfigurationDelegate implementation
extension CarPlayService: CPSessionConfigurationDelegate {
func sessionConfiguration(_ sessionConfiguration: CPSessionConfiguration,
limitedUserInterfacesChanged limitedUserInterfaces: CPLimitableUserInterface) {
}
@available(iOS 13.0, *)
func sessionConfiguration(_ sessionConfiguration: CPSessionConfiguration,
contentStyleChanged contentStyle: CPContentStyle) {
// Handle the CarPlay content style changing triggered by the 'Always Show Dark Maps' toggle.
updateContentStyle(contentStyle)
}
}
// MARK: - CPMapTemplateDelegate implementation
extension CarPlayService: CPMapTemplateDelegate {
public func mapTemplateDidShowPanningInterface(_ mapTemplate: CPMapTemplate) {
isUserPanMap = false
MapTemplateBuilder.configurePanUI(mapTemplate: mapTemplate)
FrameworkHelper.stopLocationFollow()
}
public func mapTemplateDidDismissPanningInterface(_ mapTemplate: CPMapTemplate) {
if let info = mapTemplate.userInfo as? MapInfo,
info.type == CPConstants.TemplateType.navigation {
MapTemplateBuilder.configureNavigationUI(mapTemplate: mapTemplate)
} else {
MapTemplateBuilder.configureBaseUI(mapTemplate: mapTemplate)
}
FrameworkHelper.switchMyPositionMode()
}
@objc(mapTemplate:panEndedWithDirection:)
func mapTemplate(_ mapTemplate: CPMapTemplate, panEndedWith direction: Int) {
var offset = UIOffset(horizontal: 0.0, vertical: 0.0)
let offsetStep: CGFloat = 0.25
let panDirection = CPMapTemplate.PanDirection(rawValue: direction)
if panDirection.contains(.up) { offset.vertical -= offsetStep }
if panDirection.contains(.down) { offset.vertical += offsetStep }
if panDirection.contains(.left) { offset.horizontal += offsetStep }
if panDirection.contains(.right) { offset.horizontal -= offsetStep }
FrameworkHelper.moveMap(offset)
isUserPanMap = true
}
@objc(mapTemplate:panWithDirection:)
func mapTemplate(_ mapTemplate: CPMapTemplate, panWith direction: Int) {
var offset = UIOffset(horizontal: 0.0, vertical: 0.0)
let offsetStep: CGFloat = 0.1
let panDirection = CPMapTemplate.PanDirection(rawValue: direction)
if panDirection.contains(.up) { offset.vertical -= offsetStep }
if panDirection.contains(.down) { offset.vertical += offsetStep }
if panDirection.contains(.left) { offset.horizontal += offsetStep }
if panDirection.contains(.right) { offset.horizontal -= offsetStep }
FrameworkHelper.moveMap(offset)
isUserPanMap = true
}
func mapTemplate(_ mapTemplate: CPMapTemplate, didUpdatePanGestureWithTranslation translation: CGPoint, velocity: CGPoint) {
let scaleFactor = self.carplayVC?.mapView?.contentScaleFactor ?? 1
FrameworkHelper.scrollMap(toDistanceX:-scaleFactor * translation.x, andY:-scaleFactor * translation.y);
}
func mapTemplate(_ mapTemplate: CPMapTemplate, startedTrip trip: CPTrip, using routeChoice: CPRouteChoice) {
guard let info = routeChoice.userInfo as? RouteInfo else {
if let info = routeChoice.userInfo as? [String: Any],
let code = info[CPConstants.Trip.errorCode] as? RouterResultCode,
let countries = info[CPConstants.Trip.missedCountries] as? [String] {
showErrorAlert(code: code, countries: countries)
}
return
}
mapTemplate.userInfo = MapInfo(type: CPConstants.TemplateType.previewAccepted)
mapTemplate.hideTripPreviews()
guard let router = router,
let interfaceController = interfaceController,
let rootMapTemplate = rootMapTemplate else {
return
}
MapTemplateBuilder.configureNavigationUI(mapTemplate: rootMapTemplate)
if interfaceController.templates.count > 1 {
interfaceController.popToRootTemplate(animated: false)
}
router.startNavigationSession(forTrip: trip, template: rootMapTemplate)
router.startRoute()
if let estimates = createEstimates(routeInfo: info) {
rootMapTemplate.updateEstimates(estimates, for: trip)
}
if let carplayVC = carplayVC {
carplayVC.updateCurrentSpeed(info.speedMps, speedLimitMps: info.speedLimitMps)
carplayVC.showSpeedControl()
}
updateVisibleViewPortState(.navigation)
}
func mapTemplate(_ mapTemplate: CPMapTemplate, displayStyleFor maneuver: CPManeuver) -> CPManeuverDisplayStyle {
if let type = maneuver.userInfo as? String,
type == CPConstants.Maneuvers.secondary {
return .trailingSymbol
}
return .leadingSymbol
}
func mapTemplate(_ mapTemplate: CPMapTemplate,
selectedPreviewFor trip: CPTrip,
using routeChoice: CPRouteChoice) {
guard let previewTrip = router?.previewTrip, previewTrip == trip else {
applyUndefinedEstimates(template: mapTemplate, trip: trip)
router?.buildRoute(trip: trip)
return
}
guard let info = routeChoice.userInfo as? RouteInfo,
let estimates = createEstimates(routeInfo: info) else {
applyUndefinedEstimates(template: mapTemplate, trip: trip)
router?.rebuildRoute()
return
}
mapTemplate.updateEstimates(estimates, for: trip)
routeChoice.userInfo = nil
router?.rebuildRoute()
}
}
// MARK: - CPListTemplateDelegate implementation
extension CarPlayService: CPListTemplateDelegate {
func listTemplate(_ listTemplate: CPListTemplate, didSelect item: CPListItem, completionHandler: @escaping () -> Void) {
if let userInfo = item.userInfo as? ListItemInfo {
switch userInfo.type {
case CPConstants.ListItemType.history:
let locale = window?.textInputMode?.primaryLanguage ?? "en"
guard let searchService = searchService else {
completionHandler()
return
}
searchService.searchText(item.text ?? "", forInputLocale: locale, completionHandler: { [weak self] results in
guard let self = self else { return }
let template = ListTemplateBuilder.buildListTemplate(for: .searchResults(results: results))
completionHandler()
self.pushTemplate(template, animated: true)
})
case CPConstants.ListItemType.bookmarkLists where userInfo.metadata is CategoryInfo:
let metadata = userInfo.metadata as! CategoryInfo
let template = ListTemplateBuilder.buildListTemplate(for: .bookmarks(category: metadata.category))
completionHandler()
pushTemplate(template, animated: true)
case CPConstants.ListItemType.bookmarks where userInfo.metadata is BookmarkInfo:
let metadata = userInfo.metadata as! BookmarkInfo
let bookmark = MWMCarPlayBookmarkObject(bookmarkId: metadata.bookmarkId)
preparePreview(forBookmark: bookmark)
completionHandler()
case CPConstants.ListItemType.searchResults where userInfo.metadata is SearchResultInfo:
let metadata = userInfo.metadata as! SearchResultInfo
preparePreviewForSearchResults(selectedRow: metadata.originalRow)
completionHandler()
default:
completionHandler()
}
}
}
}
// MARK: - CPSearchTemplateDelegate implementation
extension CarPlayService: CPSearchTemplateDelegate {
func searchTemplate(_ searchTemplate: CPSearchTemplate, updatedSearchText searchText: String, completionHandler: @escaping ([CPListItem]) -> Void) {
self.searchText = searchText
let locale = window?.textInputMode?.primaryLanguage ?? "en"
guard let searchService = searchService else {
completionHandler([])
return
}
searchService.searchText(self.searchText, forInputLocale: locale, completionHandler: { results in
var items = [CPListItem]()
for object in results {
let item = CPListItem(text: object.title, detailText: object.address)
item.userInfo = ListItemInfo(type: CPConstants.ListItemType.searchResults,
metadata: SearchResultInfo(originalRow: object.originalRow))
items.append(item)
}
completionHandler(items)
})
}
func searchTemplate(_ searchTemplate: CPSearchTemplate, selectedResult item: CPListItem, completionHandler: @escaping () -> Void) {
searchService?.saveLastQuery()
if let info = item.userInfo as? ListItemInfo,
let metadata = info.metadata as? SearchResultInfo {
preparePreviewForSearchResults(selectedRow: metadata.originalRow)
}
completionHandler()
}
func searchTemplateSearchButtonPressed(_ searchTemplate: CPSearchTemplate) {
let locale = window?.textInputMode?.primaryLanguage ?? "en"
guard let searchService = searchService else {
return
}
searchService.searchText(searchText, forInputLocale: locale, completionHandler: { [weak self] results in
guard let self = self else { return }
let template = ListTemplateBuilder.buildListTemplate(for: .searchResults(results: results))
self.pushTemplate(template, animated: true)
})
}
}
// MARK: - CarPlayRouterListener implementation
extension CarPlayService: CarPlayRouterListener {
func didCreateRoute(routeInfo: RouteInfo, trip: CPTrip) {
guard let currentTemplate = interfaceController?.topTemplate as? CPMapTemplate,
let info = currentTemplate.userInfo as? MapInfo,
info.type == CPConstants.TemplateType.preview else {
return
}
if let estimates = createEstimates(routeInfo: routeInfo) {
currentTemplate.updateEstimates(estimates, for: trip)
}
}
func didUpdateRouteInfo(_ routeInfo: RouteInfo, forTrip trip: CPTrip) {
if let carplayVC = carplayVC {
carplayVC.updateCurrentSpeed(routeInfo.speedMps, speedLimitMps: routeInfo.speedLimitMps)
}
guard let router = router,
let template = rootMapTemplate else {
return
}
router.updateEstimates()
if let estimates = createEstimates(routeInfo: routeInfo) {
template.updateEstimates(estimates, for: trip)
}
trip.routeChoices.first?.userInfo = routeInfo
}
func didFailureBuildRoute(forTrip trip: CPTrip, code: RouterResultCode, countries: [String]) {
guard let template = interfaceController?.topTemplate as? CPMapTemplate else { return }
trip.routeChoices.first?.userInfo = [CPConstants.Trip.errorCode: code, CPConstants.Trip.missedCountries: countries]
applyUndefinedEstimates(template: template, trip: trip)
}
func routeDidFinish(_ trip: CPTrip) {
if router?.currentTrip == nil { return }
router?.finishTrip()
if let carplayVC = carplayVC {
carplayVC.hideSpeedControl()
}
updateMapTemplateUIToTripFinished(trip)
}
}
// MARK: - LocationModeListener implementation
extension CarPlayService: LocationModeListener {
func processMyPositionStateModeEvent(_ mode: MWMMyPositionMode) {
currentPositionMode = mode
guard let rootMapTemplate = rootMapTemplate,
let info = rootMapTemplate.userInfo as? MapInfo,
info.type == CPConstants.TemplateType.main else {
return
}
switch mode {
case .follow, .followAndRotate:
if !rootMapTemplate.isPanningInterfaceVisible {
MapTemplateBuilder.setupDestinationButton(mapTemplate: rootMapTemplate)
}
case .notFollow:
if !rootMapTemplate.isPanningInterfaceVisible {
MapTemplateBuilder.setupRecenterButton(mapTemplate: rootMapTemplate)
}
case .pendingPosition, .notFollowNoPosition:
rootMapTemplate.leadingNavigationBarButtons = []
}
}
}
// MARK: - Alerts and Trip Previews
extension CarPlayService {
func preparePreviewForSearchResults(selectedRow row: Int) {
var results = searchService?.lastResults ?? []
if let currentItemIndex = results.firstIndex(where: { $0.originalRow == row }) {
let item = results.remove(at: currentItemIndex)
results.insert(item, at: 0)
} else {
results.insert(MWMCarPlaySearchResultObject(forRow: row), at: 0)
}
if let router = router,
let startPoint = MWMRoutePoint(lastLocationAndType: .start,
intermediateIndex: 0) {
let endPoints = results.compactMap({ MWMRoutePoint(cgPoint: $0.mercatorPoint,
title: $0.title,
subtitle: $0.address,
type: .finish,
intermediateIndex: 0) })
let trips = endPoints.map({ router.createTrip(startPoint: startPoint, endPoint: $0) })
if router.currentTrip == nil {
preparePreview(trips: trips)
} else {
showRerouteAlert(trips: trips)
}
}
}
func preparePreview(forBookmark bookmark: MWMCarPlayBookmarkObject) {
if let router = router,
let startPoint = MWMRoutePoint(lastLocationAndType: .start,
intermediateIndex: 0),
let endPoint = MWMRoutePoint(cgPoint: bookmark.mercatorPoint,
title: bookmark.prefferedName,
subtitle: bookmark.address,
type: .finish,
intermediateIndex: 0) {
let trip = router.createTrip(startPoint: startPoint, endPoint: endPoint)
if router.currentTrip == nil {
preparePreview(trips: [trip])
} else {
showRerouteAlert(trips: [trip])
}
}
}
func preparePreview(trips: [CPTrip]) {
let mapTemplate = MapTemplateBuilder.buildTripPreviewTemplate(forTrips: trips)
if let interfaceController = interfaceController {
mapTemplate.mapDelegate = self
if interfaceController.templates.count > 1 {
interfaceController.popToRootTemplate(animated: false)
}
interfaceController.pushTemplate(mapTemplate, animated: false)
}
}
func showPreview(mapTemplate: CPMapTemplate, trips: [CPTrip]) {
let tripTextConfig = CPTripPreviewTextConfiguration(startButtonTitle: L("trip_start"),
additionalRoutesButtonTitle: nil,
overviewButtonTitle: nil)
mapTemplate.showTripPreviews(trips, textConfiguration: tripTextConfig)
}
func createEstimates(routeInfo: RouteInfo) -> CPTravelEstimates? {
let measurement = Measurement(value: routeInfo.targetDistance, unit: routeInfo.targetUnits)
return CPTravelEstimates(distanceRemaining: measurement, timeRemaining: routeInfo.timeToTarget)
}
func applyUndefinedEstimates(template: CPMapTemplate, trip: CPTrip) {
let measurement = Measurement(value: -1,
unit: UnitLength.meters)
let estimates = CPTravelEstimates(distanceRemaining: measurement,
timeRemaining: -1)
template.updateEstimates(estimates, for: trip)
}
func showRerouteAlert(trips: [CPTrip]) {
let yesAction = CPAlertAction(title: L("yes"), style: .default, handler: { [unowned self] _ in
self.router?.cancelTrip()
self.updateMapTemplateUIToBase()
self.preparedToPreviewTrips = trips
self.interfaceController?.dismissTemplate(animated: true)
})
let noAction = CPAlertAction(title: L("no"), style: .cancel, handler: { [unowned self] _ in
self.interfaceController?.dismissTemplate(animated: true)
})
let alert = CPAlertTemplate(titleVariants: [L("redirect_route_alert")], actions: [noAction, yesAction])
alert.userInfo = [CPConstants.TemplateKey.alert: CPConstants.TemplateType.redirectRoute]
presentAlert(alert, animated: true)
}
func showKeyboardAlert() {
let okAction = CPAlertAction(title: L("ok"), style: .default, handler: { [unowned self] _ in
self.interfaceController?.dismissTemplate(animated: true)
})
let alert = CPAlertTemplate(titleVariants: [L("keyboard_availability_alert")], actions: [okAction])
presentAlert(alert, animated: true)
}
func showErrorAlert(code: RouterResultCode, countries: [String]) {
var titleVariants = [String]()
switch code {
case .noCurrentPosition:
titleVariants = ["\(L("dialog_routing_check_gps_carplay"))"]
case .startPointNotFound:
titleVariants = ["\(L("dialog_routing_change_start_carplay"))"]
case .endPointNotFound:
titleVariants = ["\(L("dialog_routing_change_end_carplay"))"]
case .routeNotFoundRedressRouteError,
.routeNotFound,
.inconsistentMWMandRoute:
titleVariants = ["\(L("dialog_routing_unable_locate_route_carplay"))"]
case .routeFileNotExist,
.fileTooOld,
.needMoreMaps,
.pointsInDifferentMWM:
titleVariants = ["\(L("dialog_routing_download_files_carplay"))"]
case .internalError,
.intermediatePointNotFound:
titleVariants = ["\(L("dialog_routing_system_error_carplay"))"]
case .noError,
.cancelled,
.hasWarnings,
.transitRouteNotFoundNoNetwork,
.transitRouteNotFoundTooLongPedestrian:
return
}
let okAction = CPAlertAction(title: L("ok"), style: .cancel, handler: { [unowned self] _ in
self.interfaceController?.dismissTemplate(animated: true)
})
let alert = CPAlertTemplate(titleVariants: titleVariants, actions: [okAction])
presentAlert(alert, animated: true)
}
func showRecoverRouteAlert(trip: CPTrip, isTypeCorrect: Bool) {
let yesAction = CPAlertAction(title: L("ok"), style: .default, handler: { [unowned self] _ in
var info = trip.userInfo as? [String: MWMRoutePoint]
if let startPoint = MWMRoutePoint(lastLocationAndType: .start,
intermediateIndex: 0) {
info?[CPConstants.Trip.start] = startPoint
}
trip.userInfo = info
self.preparedToPreviewTrips = [trip]
self.router?.updateStartPointAndRebuild(trip: trip)
self.interfaceController?.dismissTemplate(animated: true)
})
let noAction = CPAlertAction(title: L("cancel"), style: .cancel, handler: { [unowned self] _ in
FrameworkHelper.rotateMap(0.0, animated: false)
self.router?.completeRouteAndRemovePoints()
self.interfaceController?.dismissTemplate(animated: true)
})
let title = isTypeCorrect ? L("dialog_routing_rebuild_from_current_location_carplay") : L("dialog_routing_rebuild_for_vehicle_carplay")
let alert = CPAlertTemplate(titleVariants: [title], actions: [noAction, yesAction])
alert.userInfo = [CPConstants.TemplateKey.alert: CPConstants.TemplateType.restoreRoute]
presentAlert(alert, animated: true)
}
}

View file

@ -0,0 +1,51 @@
import Foundation
import UIKit
enum CarPlayWindowScaleAdjuster {
static func updateAppearance(
fromWindow sourceWindow: UIWindow,
toWindow destinationWindow: UIWindow,
isCarplayActivated: Bool
) {
let sourceContentScale = sourceWindow.screen.scale;
let destinationContentScale = destinationWindow.screen.scale;
if abs(sourceContentScale - destinationContentScale) > 0.1 {
if isCarplayActivated {
updateVisualScale(to: destinationContentScale)
} else {
updateVisualScaleToMain()
}
}
}
private static func updateVisualScale(to scale: CGFloat) {
if isGraphicContextInitialized {
mapViewController?.mapView.updateVisualScale(to: scale)
} else {
DispatchQueue.main.async {
updateVisualScale(to: scale)
}
}
}
private static func updateVisualScaleToMain() {
if isGraphicContextInitialized {
mapViewController?.mapView.updateVisualScaleToMain()
} else {
DispatchQueue.main.async {
updateVisualScaleToMain()
}
}
}
private static var isGraphicContextInitialized: Bool {
return mapViewController?.mapView.graphicContextInitialized ?? false
}
private static var mapViewController: MapViewController? {
return MapViewController.shared()
}
}

View file

@ -0,0 +1,93 @@
import Foundation
class CarplayPlaceholderView: UIView {
private let containerView = UIView()
private let imageView = UIImageView()
private let descriptionLabel = UILabel()
private let switchButton = UIButton(type: .system);
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
addSubview(containerView)
imageView.image = UIImage(named: "ic_carplay_activated")
imageView.contentMode = .scaleAspectFit
containerView.addSubview(imageView)
descriptionLabel.text = L("car_used_on_the_car_screen")
descriptionLabel.font = UIFont.bold24()
descriptionLabel.textAlignment = .center
descriptionLabel.numberOfLines = 0
containerView.addSubview(descriptionLabel)
switchButton.setTitle(L("car_continue_on_the_phone"), for: .normal)
switchButton.addTarget(self, action: #selector(onSwitchButtonTap), for: .touchUpInside)
switchButton.titleLabel?.font = UIFont.medium16()
switchButton.titleLabel?.lineBreakMode = .byWordWrapping
switchButton.titleLabel?.textAlignment = .center
switchButton.layer.cornerRadius = 8
containerView.addSubview(switchButton)
updateColors()
setupConstraints()
}
override func applyTheme() {
super.applyTheme()
updateColors()
}
private func updateColors() {
backgroundColor = UIColor.carplayPlaceholderBackground()
descriptionLabel.textColor = UIColor.blackSecondaryText()
switchButton.backgroundColor = UIColor.linkBlue()
switchButton.setTitleColor(UIColor.whitePrimaryText(), for: .normal)
}
@objc private func onSwitchButtonTap(_ sender: UIButton) {
CarPlayService.shared.showOnPhone()
}
private func setupConstraints() {
translatesAutoresizingMaskIntoConstraints = false
containerView.translatesAutoresizingMaskIntoConstraints = false
imageView.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
switchButton.translatesAutoresizingMaskIntoConstraints = false
let horizontalPadding: CGFloat = 24
NSLayoutConstraint.activate([
containerView.centerYAnchor.constraint(equalTo: centerYAnchor),
containerView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
containerView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor),
imageView.topAnchor.constraint(equalTo: containerView.topAnchor),
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
imageView.widthAnchor.constraint(equalToConstant: 160),
imageView.heightAnchor.constraint(equalToConstant: 160),
descriptionLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 32),
descriptionLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalPadding),
descriptionLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalPadding),
switchButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 24),
switchButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalPadding),
switchButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalPadding),
switchButton.heightAnchor.constraint(equalToConstant: 48),
switchButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
}
}

View file

@ -0,0 +1,13 @@
NS_ASSUME_NONNULL_BEGIN
@interface MWMCarPlaySearchResultObject : NSObject
@property(assign, nonatomic, readonly) NSInteger originalRow;
@property(strong, nonatomic, readonly) NSString *title;
@property(strong, nonatomic, readonly) NSString *address;
@property(assign, nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property(assign, nonatomic, readonly) CGPoint mercatorPoint;
- (instancetype)initForRow:(NSInteger)row;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,43 @@
#import "MWMCarPlaySearchResultObject.h"
#import "MWMSearch.h"
#import "SearchResult.h"
#import "SwiftBridge.h"
#include "search/result.hpp"
#include "indexer/classificator.hpp"
#include "geometry/mercator.hpp"
#include "platform/localization.hpp"
@interface MWMCarPlaySearchResultObject()
@property(assign, nonatomic, readwrite) NSInteger originalRow;
@property(strong, nonatomic, readwrite) NSString *title;
@property(strong, nonatomic, readwrite) NSString *address;
@property(assign, nonatomic, readwrite) CLLocationCoordinate2D coordinate;
@property(assign, nonatomic, readwrite) CGPoint mercatorPoint;
@end
@implementation MWMCarPlaySearchResultObject
- (instancetype)initForRow:(NSInteger)row {
self = [super init];
if (self) {
self.originalRow = row;
NSInteger containerIndex = [MWMSearch containerIndexWithRow:row];
SearchItemType type = [MWMSearch resultTypeWithRow:row];
if (type == SearchItemTypeRegular) {
auto const & result = [MWMSearch resultWithContainerIndex:containerIndex];
self.title = result.titleText;
self.address = result.addressText;
self.coordinate = result.coordinate;
auto const pivot = mercator::FromLatLon(result.coordinate.latitude, result.coordinate.longitude);
self.mercatorPoint = CGPointMake(pivot.x, pivot.y);
return self;
}
}
return nil;
}
@end

View file

@ -0,0 +1,17 @@
NS_ASSUME_NONNULL_BEGIN
@class MWMCarPlaySearchResultObject;
API_AVAILABLE(ios(12.0))
NS_SWIFT_NAME(CarPlaySearchService)
@interface MWMCarPlaySearchService : NSObject
@property(strong, nonatomic, readonly) NSArray<MWMCarPlaySearchResultObject *> *lastResults;
- (instancetype)init;
- (void)searchText:(NSString *)text
forInputLocale:(NSString *)inputLocale
completionHandler:(void (^)(NSArray<MWMCarPlaySearchResultObject *> *searchResults))completionHandler;
- (void)saveLastQuery;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,65 @@
#import "MWMCarPlaySearchService.h"
#import "MWMCarPlaySearchResultObject.h"
#import "MWMSearch.h"
#import "SwiftBridge.h"
API_AVAILABLE(ios(12.0))
@interface MWMCarPlaySearchService ()<MWMSearchObserver>
@property(strong, nonatomic, nullable) void (^completionHandler)(NSArray<MWMCarPlaySearchResultObject *> *searchResults);
@property(strong, nonatomic, nullable) NSString *lastQuery;
@property(strong, nonatomic, nullable) NSString *inputLocale;
@property(strong, nonatomic, readwrite) NSArray<MWMCarPlaySearchResultObject *> *lastResults;
@end
@implementation MWMCarPlaySearchService
- (instancetype)init {
self = [super init];
if (self) {
[MWMSearch addObserver:self];
self.lastResults = @[];
}
return self;
}
- (void)searchText:(NSString *)text
forInputLocale:(NSString *)inputLocale
completionHandler:(void (^)(NSArray<MWMCarPlaySearchResultObject *> *searchResults))completionHandler {
self.lastQuery = text;
self.inputLocale = inputLocale;
self.lastResults = @[];
self.completionHandler = completionHandler;
/// @todo Didn't find pure category request in CarPlay.
[MWMSearch setSearchMode:SearchModeEverywhere];
SearchQuery * query = [[SearchQuery alloc] init:text locale:inputLocale source:SearchTextSourceTypedText];
[MWMSearch searchQuery:query];
}
- (void)saveLastQuery {
if (self.lastQuery != nil && self.inputLocale != nil) {
SearchQuery * query = [[SearchQuery alloc] init:self.lastQuery locale:self.inputLocale source:SearchTextSourceTypedText];
[MWMSearch saveQuery:query];
}
}
#pragma mark - MWMSearchObserver
- (void)onSearchCompleted {
void (^completionHandler)(NSArray<MWMCarPlaySearchResultObject *> *searchResults) = self.completionHandler;
if (completionHandler == nil) { return; }
NSMutableArray<MWMCarPlaySearchResultObject *> *results = [NSMutableArray array];
NSInteger count = [MWMSearch resultsCount];
for (NSInteger row = 0; row < count; row++) {
MWMCarPlaySearchResultObject *result = [[MWMCarPlaySearchResultObject alloc] initForRow:row];
if (result != nil) { [results addObject:result]; }
}
self.lastResults = results;
completionHandler(results);
self.completionHandler = nil;
}
@end

View file

@ -0,0 +1,129 @@
import CarPlay
final class ListTemplateBuilder {
enum ListTemplateType {
case history
case bookmarkLists
case bookmarks(category: BookmarkGroup)
case searchResults(results: [MWMCarPlaySearchResultObject])
}
enum BarButtonType {
case bookmarks
case search
}
// MARK: - CPListTemplate builder
class func buildListTemplate(for type: ListTemplateType) -> CPListTemplate {
var title = ""
var trailingNavigationBarButtons = [CPBarButton]()
switch type {
case .history:
title = L("pick_destination")
let bookmarksButton = buildBarButton(type: .bookmarks) { _ in
let listTemplate = ListTemplateBuilder.buildListTemplate(for: .bookmarkLists)
CarPlayService.shared.pushTemplate(listTemplate, animated: true)
}
let searchButton = buildBarButton(type: .search) { _ in
if CarPlayService.shared.isKeyboardLimited {
CarPlayService.shared.showKeyboardAlert()
} else {
let searchTemplate = SearchTemplateBuilder.buildSearchTemplate()
CarPlayService.shared.pushTemplate(searchTemplate, animated: true)
}
}
trailingNavigationBarButtons = [searchButton, bookmarksButton]
case .bookmarkLists:
title = L("bookmarks")
case .searchResults:
title = L("search_results")
case .bookmarks(let category):
title = category.title
}
let sections = buildSectionsForType(type)
let template = CPListTemplate(title: title, sections: sections)
template.trailingNavigationBarButtons = trailingNavigationBarButtons
return template
}
private class func buildSectionsForType(_ type: ListTemplateType) -> [CPListSection] {
switch type {
case .history:
return buildHistorySections()
case .bookmarks(let category):
return buildBookmarksSections(categoryId: category.categoryId)
case .bookmarkLists:
return buildBookmarkListsSections()
case .searchResults(let results):
return buildSearchResultsSections(results)
}
}
private class func buildHistorySections() -> [CPListSection] {
let searchQueries = FrameworkHelper.obtainLastSearchQueries()
let items = searchQueries.map({ (text) -> CPListItem in
let item = CPListItem(text: text, detailText: nil, image: UIImage(named: "recent"))
item.userInfo = ListItemInfo(type: CPConstants.ListItemType.history,
metadata: nil)
return item
})
return [CPListSection(items: items)]
}
private class func buildBookmarkListsSections() -> [CPListSection] {
let bookmarkManager = BookmarksManager.shared()
let categories = bookmarkManager.sortedUserCategories()
let items: [CPListItem] = categories.compactMap({ category in
if category.bookmarksCount == 0 { return nil }
let placesString = category.placesCountTitle()
let item = CPListItem(text: category.title, detailText: placesString)
item.userInfo = ListItemInfo(type: CPConstants.ListItemType.bookmarkLists,
metadata: CategoryInfo(category: category))
return item
})
return [CPListSection(items: items)]
}
private class func buildBookmarksSections(categoryId: MWMMarkGroupID) -> [CPListSection] {
let bookmarkManager = BookmarksManager.shared()
let bookmarks = bookmarkManager.bookmarks(forCategory: categoryId)
var items = bookmarks.map({ (bookmark) -> CPListItem in
let item = CPListItem(text: bookmark.prefferedName, detailText: bookmark.address)
item.userInfo = ListItemInfo(type: CPConstants.ListItemType.bookmarks,
metadata: BookmarkInfo(categoryId: categoryId,
bookmarkId: bookmark.bookmarkId))
return item
})
let maxItemCount = CPListTemplate.maximumItemCount - 1
if items.count >= maxItemCount {
items = Array(items.prefix(maxItemCount))
let cropWarning = CPListItem(text: L("not_all_shown_bookmarks_carplay"), detailText: L("switch_to_phone_bookmarks_carplay"))
cropWarning.isEnabled = false
items.append(cropWarning)
}
return [CPListSection(items: items)]
}
private class func buildSearchResultsSections(_ results: [MWMCarPlaySearchResultObject]) -> [CPListSection] {
var items = [CPListItem]()
for object in results {
let item = CPListItem(text: object.title, detailText: object.address)
item.userInfo = ListItemInfo(type: CPConstants.ListItemType.searchResults,
metadata: SearchResultInfo(originalRow: object.originalRow))
items.append(item)
}
return [CPListSection(items: items)]
}
// MARK: - CPBarButton builder
private class func buildBarButton(type: BarButtonType, action: ((CPBarButton) -> Void)?) -> CPBarButton {
switch type {
case .bookmarks:
return CPBarButton(image: UIImage(systemName: "list.star")!, handler: action)
case .search:
return CPBarButton(image: UIImage(systemName: "keyboard.fill")!, handler: action)
}
}
}

View file

@ -0,0 +1,205 @@
import CarPlay
final class MapTemplateBuilder {
enum MapButtonType {
case startPanning
case zoomIn
case zoomOut
}
enum BarButtonType {
case dismissPaning
case destination
case recenter
case settings
case mute
case unmute
case redirectRoute
case endRoute
}
private enum Constants {
static let carPlayGuidanceBackgroundColor = UIColor(46, 100, 51, 1.0)
}
// MARK: - CPMapTemplate builders
class func buildBaseTemplate(positionMode: MWMMyPositionMode) -> CPMapTemplate {
let mapTemplate = CPMapTemplate()
mapTemplate.hidesButtonsWithNavigationBar = false
configureBaseUI(mapTemplate: mapTemplate)
if positionMode == .pendingPosition {
mapTemplate.leadingNavigationBarButtons = []
} else if positionMode == .follow || positionMode == .followAndRotate {
setupDestinationButton(mapTemplate: mapTemplate)
} else {
setupRecenterButton(mapTemplate: mapTemplate)
}
return mapTemplate
}
class func buildNavigationTemplate() -> CPMapTemplate {
let mapTemplate = CPMapTemplate()
mapTemplate.hidesButtonsWithNavigationBar = false
configureNavigationUI(mapTemplate: mapTemplate)
return mapTemplate
}
class func buildTripPreviewTemplate(forTrips trips: [CPTrip]) -> CPMapTemplate {
let mapTemplate = CPMapTemplate()
mapTemplate.userInfo = MapInfo(type: CPConstants.TemplateType.preview, trips: trips)
mapTemplate.mapButtons = []
mapTemplate.leadingNavigationBarButtons = []
let settingsButton = buildBarButton(type: .settings) { _ in
mapTemplate.userInfo = MapInfo(type: CPConstants.TemplateType.previewSettings)
let gridTemplate = SettingsTemplateBuilder.buildGridTemplate()
CarPlayService.shared.pushTemplate(gridTemplate, animated: true)
}
mapTemplate.trailingNavigationBarButtons = [settingsButton]
return mapTemplate
}
// MARK: - MapTemplate UI configs
class func configureBaseUI(mapTemplate: CPMapTemplate) {
mapTemplate.userInfo = MapInfo(type: CPConstants.TemplateType.main)
let panningButton = buildMapButton(type: .startPanning) { _ in
mapTemplate.showPanningInterface(animated: true)
}
let zoomInButton = buildMapButton(type: .zoomIn) { _ in
FrameworkHelper.zoomMap(.in)
}
let zoomOutButton = buildMapButton(type: .zoomOut) { _ in
FrameworkHelper.zoomMap(.out)
}
mapTemplate.mapButtons = [panningButton, zoomInButton, zoomOutButton]
let settingsButton = buildBarButton(type: .settings) { _ in
let gridTemplate = SettingsTemplateBuilder.buildGridTemplate()
CarPlayService.shared.pushTemplate(gridTemplate, animated: true)
}
mapTemplate.trailingNavigationBarButtons = [settingsButton]
}
class func configurePanUI(mapTemplate: CPMapTemplate) {
let zoomInButton = buildMapButton(type: .zoomIn) { _ in
FrameworkHelper.zoomMap(.in)
}
let zoomOutButton = buildMapButton(type: .zoomOut) { _ in
FrameworkHelper.zoomMap(.out)
}
mapTemplate.mapButtons = [zoomInButton, zoomOutButton]
let doneButton = buildBarButton(type: .dismissPaning) { _ in
mapTemplate.dismissPanningInterface(animated: true)
}
mapTemplate.leadingNavigationBarButtons = []
mapTemplate.trailingNavigationBarButtons = [doneButton]
}
class func configureNavigationUI(mapTemplate: CPMapTemplate) {
mapTemplate.userInfo = MapInfo(type: CPConstants.TemplateType.navigation)
let panningButton = buildMapButton(type: .startPanning) { _ in
mapTemplate.showPanningInterface(animated: true)
}
mapTemplate.mapButtons = [panningButton]
setupMuteAndRedirectButtons(template: mapTemplate)
let endButton = buildBarButton(type: .endRoute) { _ in
CarPlayService.shared.cancelCurrentTrip()
}
mapTemplate.trailingNavigationBarButtons = [endButton]
mapTemplate.guidanceBackgroundColor = Constants.carPlayGuidanceBackgroundColor
}
// MARK: - Conditional navigation buttons
class func setupDestinationButton(mapTemplate: CPMapTemplate) {
let destinationButton = buildBarButton(type: .destination) { _ in
let listTemplate = ListTemplateBuilder.buildListTemplate(for: .history)
CarPlayService.shared.pushTemplate(listTemplate, animated: true)
}
mapTemplate.leadingNavigationBarButtons = [destinationButton]
}
class func setupRecenterButton(mapTemplate: CPMapTemplate) {
let recenterButton = buildBarButton(type: .recenter) { _ in
FrameworkHelper.switchMyPositionMode()
}
mapTemplate.leadingNavigationBarButtons = [recenterButton]
}
private class func setupMuteAndRedirectButtons(template: CPMapTemplate) {
let redirectButton = buildBarButton(type: .redirectRoute) { _ in
let listTemplate = ListTemplateBuilder.buildListTemplate(for: .history)
CarPlayService.shared.pushTemplate(listTemplate, animated: true)
}
if MWMTextToSpeech.isTTSEnabled() {
let muteButton = buildBarButton(type: .mute) { _ in
MWMTextToSpeech.tts().active = false
setupUnmuteAndRedirectButtons(template: template)
}
template.leadingNavigationBarButtons = [muteButton, redirectButton]
} else {
template.leadingNavigationBarButtons = [redirectButton]
}
}
private class func setupUnmuteAndRedirectButtons(template: CPMapTemplate) {
let redirectButton = buildBarButton(type: .redirectRoute) { _ in
let listTemplate = ListTemplateBuilder.buildListTemplate(for: .history)
CarPlayService.shared.pushTemplate(listTemplate, animated: true)
}
if MWMTextToSpeech.isTTSEnabled() {
let unmuteButton = buildBarButton(type: .unmute) { _ in
MWMTextToSpeech.tts().active = true
setupMuteAndRedirectButtons(template: template)
}
template.leadingNavigationBarButtons = [unmuteButton, redirectButton]
} else {
template.leadingNavigationBarButtons = [redirectButton]
}
}
// MARK: - CPMapButton builder
private class func buildMapButton(type: MapButtonType, action: ((CPMapButton) -> Void)?) -> CPMapButton {
let button = CPMapButton(handler: action)
switch type {
case .startPanning:
button.image = UIImage(systemName: "arrow.up.and.down.and.arrow.left.and.right")
case .zoomIn:
button.image = UIImage(systemName: "plus")
case .zoomOut:
button.image = UIImage(systemName: "minus")
}
// Remove code below once Apple has fixed its issue with the button background
if #unavailable(iOS 26) {
switch type {
case .startPanning:
button.focusedImage = UIImage(systemName: "smallcircle.filled.circle.fill")
case .zoomIn:
button.focusedImage = UIImage(systemName: "plus.circle.fill")
case .zoomOut:
button.focusedImage = UIImage(systemName: "minus.circle.fill")
}
}
return button
}
// MARK: - CPBarButton builder
private class func buildBarButton(type: BarButtonType, action: ((CPBarButton) -> Void)?) -> CPBarButton {
switch type {
case .dismissPaning:
return CPBarButton(title: L("done"), handler: action)
case .destination:
return CPBarButton(title: L("pick_destination"), handler: action)
case .recenter:
return CPBarButton(title: L("follow_my_position"), handler: action)
case .settings:
return CPBarButton(image: UIImage(systemName: "gearshape.fill")!, handler: action)
case .mute:
return CPBarButton(image: UIImage(systemName: "speaker.wave.3")!, handler: action)
case .unmute:
return CPBarButton(image: UIImage(systemName: "speaker.slash")!, handler: action)
case .redirectRoute:
return CPBarButton(image: UIImage(named: "ic_carplay_redirect_route")!, handler: action)
case .endRoute:
return CPBarButton(title: L("navigation_stop_button").capitalized, handler: action)
}
}
}

View file

@ -0,0 +1,9 @@
import CarPlay
final class SearchTemplateBuilder {
// MARK: - CPSearchTemplate builder
class func buildSearchTemplate() -> CPSearchTemplate {
let template = CPSearchTemplate()
return template
}
}

View file

@ -0,0 +1,157 @@
import CarPlay
final class SettingsTemplateBuilder {
// MARK: - CPGridTemplate builder
class func buildGridTemplate() -> CPGridTemplate {
let actions = SettingsTemplateBuilder.buildGridButtons()
let gridTemplate = CPGridTemplate(title: L("settings"),
gridButtons: actions)
return gridTemplate
}
private class func buildGridButtons() -> [CPGridButton] {
let options = RoutingOptions()
return [createTollButton(options: options),
createUnpavedButton(options: options),
createPavedButton(options: options),
createMotorwayButton(options: options),
createFerryButton(options: options),
createStepsButton(options: options),
createSpeedcamButton()]
}
// MARK: - CPGridButton builders
private class func createTollButton(options: RoutingOptions) -> CPGridButton {
var tollIconName = "tolls.circle"
if options.avoidToll { tollIconName += ".slash" }
let configuration = UIImage.SymbolConfiguration(textStyle: .title1)
var image = UIImage(named: tollIconName, in: nil, with: configuration)!
if #unavailable(iOS 26) {
image = image.withTintColor(.white, renderingMode: .alwaysTemplate)
image = UIImage(data: image.pngData()!)!.withRenderingMode(.alwaysTemplate)
}
let tollButton = CPGridButton(titleVariants: [L("avoid_tolls")], image: image) { _ in
options.avoidToll = !options.avoidToll
options.save()
CarPlayService.shared.updateRouteAfterChangingSettings()
CarPlayService.shared.popTemplate(animated: true)
}
return tollButton
}
private class func createUnpavedButton(options: RoutingOptions) -> CPGridButton {
var unpavedIconName = "unpaved.circle"
if options.avoidDirty && !options.avoidPaved { unpavedIconName += ".slash" }
let configuration = UIImage.SymbolConfiguration(textStyle: .title1)
var image = UIImage(named: unpavedIconName, in: nil, with: configuration)!
if #unavailable(iOS 26) {
image = image.withTintColor(.white, renderingMode: .alwaysTemplate)
image = UIImage(data: image.pngData()!)!.withRenderingMode(.alwaysTemplate)
}
let unpavedButton = CPGridButton(titleVariants: [L("avoid_unpaved")], image: image) { _ in
options.avoidDirty = !options.avoidDirty
if options.avoidDirty {
options.avoidPaved = false
}
options.save()
CarPlayService.shared.updateRouteAfterChangingSettings()
CarPlayService.shared.popTemplate(animated: true)
}
unpavedButton.isEnabled = !options.avoidPaved
return unpavedButton
}
private class func createPavedButton(options: RoutingOptions) -> CPGridButton {
var pavedIconName = "paved.circle"
if options.avoidPaved && !options.avoidDirty { pavedIconName += ".slash" }
let configuration = UIImage.SymbolConfiguration(textStyle: .title1)
var image = UIImage(named: pavedIconName, in: nil, with: configuration)!
if #unavailable(iOS 26) {
image = image.withTintColor(.white, renderingMode: .alwaysTemplate)
image = UIImage(data: image.pngData()!)!.withRenderingMode(.alwaysTemplate)
}
let pavedButton = CPGridButton(titleVariants: [L("avoid_paved")], image: image) { _ in
options.avoidPaved = !options.avoidPaved
if options.avoidPaved {
options.avoidDirty = false
}
options.save()
CarPlayService.shared.updateRouteAfterChangingSettings()
CarPlayService.shared.popTemplate(animated: true)
}
pavedButton.isEnabled = !options.avoidDirty
return pavedButton
}
private class func createMotorwayButton(options: RoutingOptions) -> CPGridButton {
var motorwayIconName = "motorways.circle"
if options.avoidMotorway { motorwayIconName += ".slash" }
let configuration = UIImage.SymbolConfiguration(textStyle: .title1)
var image = UIImage(named: motorwayIconName, in: nil, with: configuration)!
if #unavailable(iOS 26) {
image = image.withTintColor(.white, renderingMode: .alwaysTemplate)
image = UIImage(data: image.pngData()!)!.withRenderingMode(.alwaysTemplate)
}
let motorwayButton = CPGridButton(titleVariants: [L("avoid_motorways")], image: image) { _ in
options.avoidMotorway = !options.avoidMotorway
options.save()
CarPlayService.shared.updateRouteAfterChangingSettings()
CarPlayService.shared.popTemplate(animated: true)
}
return motorwayButton
}
private class func createFerryButton(options: RoutingOptions) -> CPGridButton {
var ferryIconName = "ferries.circle"
if options.avoidFerry { ferryIconName += ".slash" }
let configuration = UIImage.SymbolConfiguration(textStyle: .title1)
var image = UIImage(named: ferryIconName, in: nil, with: configuration)!
if #unavailable(iOS 26) {
image = image.withTintColor(.white, renderingMode: .alwaysTemplate)
image = UIImage(data: image.pngData()!)!.withRenderingMode(.alwaysTemplate)
}
let ferryButton = CPGridButton(titleVariants: [L("avoid_ferry")], image: image) { _ in
options.avoidFerry = !options.avoidFerry
options.save()
CarPlayService.shared.updateRouteAfterChangingSettings()
CarPlayService.shared.popTemplate(animated: true)
}
return ferryButton
}
private class func createStepsButton(options: RoutingOptions) -> CPGridButton {
var stepsIconName = "steps.circle"
if options.avoidSteps { stepsIconName += ".slash" }
let configuration = UIImage.SymbolConfiguration(textStyle: .title1)
var image = UIImage(named: stepsIconName, in: nil, with: configuration)!
if #unavailable(iOS 26) {
image = image.withTintColor(.white, renderingMode: .alwaysTemplate)
image = UIImage(data: image.pngData()!)!.withRenderingMode(.alwaysTemplate)
}
let stepsButton = CPGridButton(titleVariants: [L("avoid_steps")], image: image) { _ in
options.avoidSteps = !options.avoidSteps
options.save()
CarPlayService.shared.updateRouteAfterChangingSettings()
CarPlayService.shared.popTemplate(animated: true)
}
return stepsButton
}
private class func createSpeedcamButton() -> CPGridButton {
var speedcamIconName = "speedcamera"
let isSpeedCamActivated = CarPlayService.shared.isSpeedCamActivated
if !isSpeedCamActivated { speedcamIconName += ".slash" }
let configuration = UIImage.SymbolConfiguration(textStyle: .title1)
var image = UIImage(named: speedcamIconName, in: nil, with: configuration)!
if #unavailable(iOS 26) {
image = image.withTintColor(.white, renderingMode: .alwaysTemplate)
image = UIImage(data: image.pngData()!)!.withRenderingMode(.alwaysTemplate)
}
let speedButton = CPGridButton(titleVariants: [L("speedcams_alert_title_carplay_1"), L("speedcams_alert_title_carplay_2")], image: image) { _ in
CarPlayService.shared.isSpeedCamActivated = !isSpeedCamActivated
CarPlayService.shared.popTemplate(animated: true)
}
return speedButton
}
}

View file

@ -0,0 +1,9 @@
struct BookmarkInfo: InfoMetadata {
let categoryId: UInt64
let bookmarkId: UInt64
init(categoryId: UInt64, bookmarkId: UInt64) {
self.categoryId = categoryId
self.bookmarkId = bookmarkId
}
}

View file

@ -0,0 +1,7 @@
struct CategoryInfo: InfoMetadata {
let category: BookmarkGroup
init(category: BookmarkGroup) {
self.category = category
}
}

View file

@ -0,0 +1,11 @@
protocol InfoMetadata {}
struct ListItemInfo {
let type: String
let metadata: InfoMetadata?
init(type: String, metadata: InfoMetadata?) {
self.type = type
self.metadata = metadata
}
}

View file

@ -0,0 +1,9 @@
struct MapInfo {
let type: String
let trips: [CPTrip]?
init(type: String, trips: [CPTrip]? = nil) {
self.type = type
self.trips = trips
}
}

View file

@ -0,0 +1,57 @@
@objc(MWMRouteInfo)
class RouteInfo: NSObject {
let timeToTarget: TimeInterval
let targetDistance: Double
let targetUnits: UnitLength
let distanceToTurn: Double
let turnUnits: UnitLength
let streetName: String
let turnImageName: String?
let nextTurnImageName: String?
let speedMps: Double
let speedLimitMps: Double?
let roundExitNumber: Int
@objc init(timeToTarget: TimeInterval,
targetDistance: Double,
targetUnitsIndex: UInt8,
distanceToTurn: Double,
turnUnitsIndex: UInt8,
streetName: String,
turnImageName: String?,
nextTurnImageName: String?,
speedMps: Double,
speedLimitMps: Double,
roundExitNumber: Int) {
self.timeToTarget = timeToTarget
self.targetDistance = targetDistance
self.targetUnits = RouteInfo.unitLength(for: targetUnitsIndex)
self.distanceToTurn = distanceToTurn
self.turnUnits = RouteInfo.unitLength(for: turnUnitsIndex)
self.streetName = streetName;
self.turnImageName = turnImageName
self.nextTurnImageName = nextTurnImageName
self.speedMps = speedMps
// speedLimitMps >= 0 means known limited speed.
self.speedLimitMps = speedLimitMps < 0 ? nil : speedLimitMps
self.roundExitNumber = roundExitNumber
}
/// > Warning: Order of enum values MUST BE the same with
/// > native ``Distance::Units`` enum (see platform/distance.hpp for details).
class func unitLength(for targetUnitsIndex: UInt8) -> UnitLength {
switch targetUnitsIndex {
case 0:
return .meters
case 1:
return .kilometers
case 2:
return .feet
case 3:
return .miles
default:
return .meters
}
}
}

View file

@ -0,0 +1,7 @@
struct SearchResultInfo: InfoMetadata {
let originalRow: Int
init(originalRow: Int) {
self.originalRow = originalRow
}
}

View file

@ -0,0 +1,18 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface CircleView : UIView
- (id)initWithFrame:(CGRect)frame andColor:(UIColor *)color;
- (id)initWithFrame:(CGRect)frame andColor:(UIColor *)color andImageName:(nullable NSString *)imageName;
+ (UIImage *)createCircleImageWithFrameSize:(CGFloat)frameSize andDiameter:(CGFloat)diameter andColor:(UIColor *)color;
+ (UIImage *)createCircleImageWithDiameter:(CGFloat)diameter andColor:(UIColor *)color;
+ (UIImage *)createCircleImageWithDiameter:(CGFloat)diameter
andColor:(UIColor *)color
andImageName:(NSString *)imageName;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,80 @@
#import <UIKit/UIKit.h>
#import "CircleView.h"
#import <QuartzCore/QuartzCore.h>
@interface CircleView()
@property(nonatomic) UIColor *circleColor;
@property(nonatomic) UIImage *image;
@end
@implementation CircleView
- (id)initWithFrame:(CGRect)frame andColor:(UIColor *)color {
return [self initWithFrame:frame andColor:color andImageName:nil];
}
- (id)initWithFrame:(CGRect)frame andColor:(UIColor *)color andImageName:(nullable NSString *)imageName {
self = [super initWithFrame:frame];
if (self)
{
_circleColor = color;
if (imageName)
_image = [UIImage imageNamed:imageName];
self.opaque = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, rect);
CGContextSetFillColor(ctx, CGColorGetComponents(self.circleColor.CGColor));
CGContextFillPath(ctx);
if (self.image)
[self.image drawInRect:CGRectMake(3, 3, rect.size.width - 6, rect.size.height - 6)];
}
+ (UIImage *)createCircleImageWithFrameSize:(CGFloat)frameSize
andDiameter:(CGFloat)diameter
andColor:(UIColor *)color
andImageName:(nullable NSString *)imageName {
UIView *circleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frameSize, frameSize)];
circleView.backgroundColor = UIColor.clearColor;
CircleView *circle = [[self alloc] initWithFrame:CGRectMake(0.5, 0.5, diameter - 1, diameter - 1)
andColor:color
andImageName:imageName];
circle.center = circleView.center;
[circleView addSubview:circle];
return [self imageWithView:circleView];
}
+ (UIImage *)createCircleImageWithFrameSize:(CGFloat)frameSize
andDiameter:(CGFloat)diameter
andColor:(UIColor *)color {
return [self createCircleImageWithFrameSize:frameSize andDiameter:diameter andColor:color andImageName:nil];
}
+ (UIImage *)createCircleImageWithDiameter:(CGFloat)diameter andColor:(UIColor *)color {
return [self createCircleImageWithFrameSize:diameter andDiameter:diameter andColor:color andImageName:nil];
}
+ (UIImage *)createCircleImageWithDiameter:(CGFloat)diameter
andColor:(UIColor *)color
andImageName:(NSString *)imageName {
return [self createCircleImageWithFrameSize:diameter andDiameter:diameter andColor:color andImageName:imageName];
}
+ (UIImage *)imageWithView:(UIView *)view {
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:view.bounds.size];
UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
[view.layer renderInContext:rendererContext.CGContext];
}];
return image;
}
@end

View file

@ -0,0 +1,37 @@
@objc(MWMBorderedButton)
final class BorderedButton: UIButton {
private var borderColor: UIColor?
private var borderHighlightedColor: UIColor?
private var borderDisabledColor: UIColor?
@objc func setBorderColor(_ color: UIColor) {
borderColor = color
}
@objc func setBorderHighlightedColor(_ color: UIColor) {
borderHighlightedColor = color
}
@objc func setBorderDisabledColor(_ color: UIColor) {
borderDisabledColor = color
}
private func updateBorder() {
if !isEnabled {
layer.borderColor = borderDisabledColor?.cgColor ?? titleColor(for: .disabled)?.cgColor
} else if isHighlighted {
layer.borderColor = borderHighlightedColor?.cgColor ?? titleColor(for: .highlighted)?.cgColor
} else {
layer.borderColor = borderColor?.cgColor ?? titleColor(for: .normal)?.cgColor
}
}
override var isEnabled: Bool {
didSet { updateBorder() }
}
override var isHighlighted: Bool {
didSet { updateBorder() }
}
}

View file

@ -0,0 +1,112 @@
class Checkmark: UIControl {
private let imageView = UIImageView(frame: .zero)
@IBInspectable
var offImage: UIImage? {
didSet {
updateImage(animated: false)
}
}
@IBInspectable
var onImage: UIImage? {
didSet {
updateImage(animated: false)
}
}
@IBInspectable
var offTintColor: UIColor? {
didSet {
updateTintColor()
}
}
@IBInspectable
var onTintColor: UIColor? {
didSet {
updateTintColor()
}
}
@IBInspectable
var isChecked: Bool = false {
didSet {
updateImage(animated: true)
updateTintColor()
}
}
override var isHighlighted: Bool {
didSet {
imageView.tintColor = isHighlighted ? tintColor.blending(with: UIColor(white: 0, alpha: 0.5)) : nil
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initViews()
}
private func initViews() {
addSubview(imageView)
addTarget(self, action: #selector(onTouch), for: .touchUpInside)
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.sizeToFit()
var left: CGFloat = 0;
var top: CGFloat = 0;
var width: CGFloat = imageView.width;
var height: CGFloat = imageView.height;
switch contentHorizontalAlignment {
case .right: fallthrough
case .trailing:
left = floor(bounds.width - imageView.width)
case .center:
left = floor((bounds.width - width) / 2)
case .fill:
width = bounds.width
default:
left = 0
}
switch contentVerticalAlignment {
case .top:
top = 0
case .bottom:
top = floor(bounds.height - height)
case .center:
top = floor((bounds.height - height) / 2)
case .fill:
height = bounds.height
@unknown default:
fatalError("Unexpected case in contentVerticalAlignment switch")
}
imageView.frame = CGRect(x: left, y: top, width: width, height: height)
}
@objc func onTouch() {
isChecked = !isChecked
sendActions(for: .valueChanged)
}
private func updateImage(animated: Bool) {
self.imageView.image = self.isChecked ? self.onImage : self.offImage
}
private func updateTintColor() {
tintColor = isChecked ? onTintColor : offTintColor
}
}

View file

@ -0,0 +1,48 @@
// Label shows copy popup menu on tap or long tap.
class CopyableLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
self.sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.sharedInit()
}
private func sharedInit() {
self.isUserInteractionEnabled = true
self.gestureRecognizers = [
UILongPressGestureRecognizer(target: self, action: #selector(self.showMenu)),
UITapGestureRecognizer(target: self, action: #selector(self.showMenu))
]
}
@objc func showMenu(_ recognizer: UILongPressGestureRecognizer) {
self.becomeFirstResponder()
let menu = UIMenuController.shared
let locationOfTouchInLabel = recognizer.location(in: self)
if !menu.isMenuVisible {
var rect = bounds
rect.origin = locationOfTouchInLabel
rect.size = CGSize(width: 1, height: 1)
menu.showMenu(from: self, rect: rect)
}
}
override func copy(_ sender: Any?) {
UIPasteboard.general.string = text
UIMenuController.shared.hideMenu()
}
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(UIResponderStandardEditActions.copy)
}
}

View file

@ -0,0 +1,42 @@
@objc(MWMDimBackground)
final class DimBackground: SolidTouchView {
private let mainView: UIView
private var tapAction: () -> Void
@objc init(mainView: UIView, tapAction: @escaping () -> Void) {
self.mainView = mainView
self.tapAction = tapAction
super.init(frame: mainView.superview!.bounds)
setStyle(.fadeBackground)
autoresizingMask = [.flexibleWidth, .flexibleHeight]
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func setVisible(_ visible: Bool, completion: (() -> Void)?) {
if visible {
let sv = mainView.superview!
frame = sv.bounds
sv.insertSubview(self, belowSubview: mainView)
alpha = 0
} else {
alpha = 0.8
}
UIView.animate(withDuration: kDefaultAnimationDuration,
animations: { self.alpha = visible ? 0.8 : 0 },
completion: { _ in
if !visible {
self.removeFromSuperview()
}
completion?()
})
}
@objc
private func onTap() {
tapAction()
}
}

View file

@ -0,0 +1,13 @@
@IBDesignable
class LeftAlignedIconButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
contentHorizontalAlignment = .left
let availableSpace = bounds.inset(by: contentEdgeInsets)
let imageWidth = imageView?.frame.width ?? 0
let titleWidth = titleLabel?.frame.width ?? 0
let availableWidth = availableSpace.width - imageEdgeInsets.right - imageWidth * 2 - titleWidth
titleEdgeInsets = UIEdgeInsets(top: 0, left: floor(availableWidth) / 2, bottom: 0, right: 0)
}
}

View file

@ -0,0 +1,5 @@
class LinkTextView : UITextView {
override var canBecomeFirstResponder: Bool {
return false;
}
}

View file

@ -0,0 +1,11 @@
#include "geometry/point2d.hpp"
@interface MWMAddPlaceNavigationBar : SolidTouchView
+ (void)showInSuperview:(UIView *)superview
isBusiness:(BOOL)isBusiness
position:(m2::PointD const *)optionalPosition
doneBlock:(MWMVoidBlock)done
cancelBlock:(MWMVoidBlock)cancel;
@end

View file

@ -0,0 +1,76 @@
#import "MWMAddPlaceNavigationBar.h"
#include <CoreApi/Framework.h>
@interface MWMAddPlaceNavigationBar ()
@property(copy, nonatomic) MWMVoidBlock doneBlock;
@property(copy, nonatomic) MWMVoidBlock cancelBlock;
@property(assign, nonatomic) NSLayoutConstraint* topConstraint;
@end
@implementation MWMAddPlaceNavigationBar
+ (void)showInSuperview:(UIView *)superview
isBusiness:(BOOL)isBusiness
position:(m2::PointD const *)optionalPosition
doneBlock:(MWMVoidBlock)done
cancelBlock:(MWMVoidBlock)cancel
{
MWMAddPlaceNavigationBar * navBar =
[NSBundle.mainBundle loadNibNamed:self.className owner:nil options:nil].firstObject;
navBar.width = superview.width;
navBar.doneBlock = done;
navBar.cancelBlock = cancel;
navBar.translatesAutoresizingMaskIntoConstraints = false;
[superview addSubview:navBar];
navBar.topConstraint = [navBar.topAnchor constraintEqualToAnchor:superview.topAnchor];
navBar.topConstraint.active = true;
navBar.topConstraint.constant = -navBar.height;
[navBar.trailingAnchor constraintEqualToAnchor:superview.trailingAnchor].active = true;
[navBar.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor].active = true;
[navBar show:isBusiness position:optionalPosition];
}
- (void)show:(BOOL)enableBounds position:(m2::PointD const *)optionalPosition
{
auto & f = GetFramework();
f.EnableChoosePositionMode(true /* enable */, enableBounds, optionalPosition);
f.BlockTapEvents(true);
[UIView animateWithDuration:kDefaultAnimationDuration animations:^
{
self.topConstraint.constant = 0;
}];
}
- (void)dismissWithBlock:(MWMVoidBlock)block
{
auto & f = GetFramework();
f.EnableChoosePositionMode(false /* enable */, false /* enableBounds */, nullptr /* optionalPosition */);
f.BlockTapEvents(false);
[UIView animateWithDuration:kDefaultAnimationDuration animations:^
{
self.topConstraint.constant = -self.height;
}
completion:^(BOOL finished)
{
[self removeFromSuperview];
block();
}];
}
- (IBAction)doneTap
{
[self dismissWithBlock:self.doneBlock];
}
- (IBAction)cancelTap
{
[self dismissWithBlock:self.cancelBlock];
}
@end

View file

@ -0,0 +1,137 @@
<?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="retina4_7" 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"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="BJ6-My-yAy" customClass="MWMAddPlaceNavigationBar" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IhK-ce-TXk">
<rect key="frame" x="0.0" y="0.0" width="320" height="64"/>
<color key="backgroundColor" red="0.1176470588" green="0.58823529409999997" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="PrimaryBackground"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ZKD-Be-eMp">
<rect key="frame" x="0.0" y="20" width="320" height="44"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" horizontalCompressionResistancePriority="1000" contentHorizontalAlignment="right" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="zs9-MX-bIe">
<rect key="frame" x="258" y="12" width="54" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="19" id="psK-YU-lUF"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Готово"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="NavigationBarItem"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="continue_button"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="doneTap" destination="BJ6-My-yAy" eventType="touchUpInside" id="hS1-Tz-6Zo"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="Местоположение" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8So-22-JS1">
<rect key="frame" x="90" y="12" width="140" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="JFT-iW-rJY"/>
<constraint firstAttribute="height" constant="20" id="mun-d1-wdT"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="NavigationBarItem"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="editor_add_select_location"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" horizontalCompressionResistancePriority="1000" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="boX-sU-7DW">
<rect key="frame" x="8" y="12" width="62" height="19"/>
<constraints>
<constraint firstAttribute="height" constant="19" id="KDz-RG-UH2"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Отмена"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="NavigationBarItem"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="cancel"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="cancelTap" destination="BJ6-My-yAy" eventType="touchUpInside" id="f2O-El-juu"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.1176470588" green="0.58823529409999997" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="5VI-KR-aXh"/>
<constraint firstItem="zs9-MX-bIe" firstAttribute="top" secondItem="ZKD-Be-eMp" secondAttribute="top" constant="12" id="BNr-Gr-o3j"/>
<constraint firstItem="8So-22-JS1" firstAttribute="top" secondItem="ZKD-Be-eMp" secondAttribute="top" constant="12" id="CCm-fM-FcD"/>
<constraint firstItem="boX-sU-7DW" firstAttribute="top" secondItem="ZKD-Be-eMp" secondAttribute="top" constant="12" id="Ofu-8n-bBo"/>
<constraint firstItem="boX-sU-7DW" firstAttribute="leading" secondItem="ZKD-Be-eMp" secondAttribute="leading" constant="8" id="Sbg-dn-iro"/>
<constraint firstAttribute="trailing" secondItem="zs9-MX-bIe" secondAttribute="trailing" constant="8" id="jiJ-gk-0bx"/>
<constraint firstItem="8So-22-JS1" firstAttribute="centerX" secondItem="ZKD-Be-eMp" secondAttribute="centerX" id="m6T-st-PPz"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="PrimaryBackground"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="UsM-Al-MCC">
<rect key="frame" x="0.0" y="64" width="320" height="56"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Потяните карту, чтобы выбрать правильное местоположение места. " lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="R6E-kO-6Lb">
<rect key="frame" x="14" y="16" width="292" height="30"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" systemColor="darkTextColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular12:blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="editor_focus_map_on_location"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<color key="backgroundColor" red="0.96078431372549022" green="0.96078431372549022" blue="0.96078431372549022" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="R6E-kO-6Lb" firstAttribute="leading" secondItem="UsM-Al-MCC" secondAttribute="leadingMargin" constant="6" id="F0k-Ua-uzh"/>
<constraint firstItem="R6E-kO-6Lb" firstAttribute="top" secondItem="UsM-Al-MCC" secondAttribute="topMargin" constant="8" id="G1O-ji-PsY"/>
<constraint firstAttribute="height" constant="56" id="RVj-Yg-4Hc"/>
<constraint firstAttribute="trailingMargin" secondItem="R6E-kO-6Lb" secondAttribute="trailing" constant="6" id="cRp-da-FUC"/>
<constraint firstAttribute="bottomMargin" secondItem="R6E-kO-6Lb" secondAttribute="bottom" constant="2" id="e1g-Jr-sY9"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MenuBackground"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="tvs-e8-vSU"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="top" secondItem="IhK-ce-TXk" secondAttribute="top" id="B9q-oF-68E"/>
<constraint firstItem="UsM-Al-MCC" firstAttribute="leading" secondItem="BJ6-My-yAy" secondAttribute="leading" id="C3j-SM-MSO"/>
<constraint firstItem="ZKD-Be-eMp" firstAttribute="leading" secondItem="BJ6-My-yAy" secondAttribute="leading" id="Ckp-sr-B7c"/>
<constraint firstItem="ZKD-Be-eMp" firstAttribute="top" secondItem="tvs-e8-vSU" secondAttribute="top" id="Drs-L4-75n"/>
<constraint firstItem="IhK-ce-TXk" firstAttribute="leading" secondItem="tvs-e8-vSU" secondAttribute="leading" id="E1h-R5-QaY"/>
<constraint firstAttribute="bottom" secondItem="UsM-Al-MCC" secondAttribute="bottom" id="Eii-Wt-WAj"/>
<constraint firstItem="tvs-e8-vSU" firstAttribute="trailing" secondItem="IhK-ce-TXk" secondAttribute="trailing" id="FcR-B0-Kwb"/>
<constraint firstAttribute="trailing" secondItem="UsM-Al-MCC" secondAttribute="trailing" id="FrY-zu-zwd"/>
<constraint firstItem="UsM-Al-MCC" firstAttribute="top" secondItem="IhK-ce-TXk" secondAttribute="bottom" id="Tfo-lj-gPb"/>
<constraint firstAttribute="trailing" secondItem="ZKD-Be-eMp" secondAttribute="trailing" id="Yrh-qN-0sk"/>
<constraint firstItem="IhK-ce-TXk" firstAttribute="bottom" secondItem="ZKD-Be-eMp" secondAttribute="bottom" id="fae-mk-c1a"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="321.60000000000002" y="248.27586206896552"/>
</view>
</objects>
<resources>
<systemColor name="darkTextColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,17 @@
typedef NS_ENUM(NSUInteger, MWMButtonColoring)
{
MWMButtonColoringOther,
MWMButtonColoringBlue,
MWMButtonColoringBlack,
MWMButtonColoringWhite,
MWMButtonColoringWhiteText,
MWMButtonColoringGray,
MWMButtonColoringRed
};
@interface MWMButton : UIButton
@property (copy, nonatomic) NSString * imageName;
@property (nonatomic) MWMButtonColoring coloring;
@end

View file

@ -0,0 +1,153 @@
#import "MWMButton.h"
#import "SwiftBridge.h"
static NSString * const kDefaultPattern = @"%@_%@";
static NSString * const kHighlightedPattern = @"%@_highlighted_%@";
static NSString * const kSelectedPattern = @"%@_selected_%@";
@implementation MWMButton
- (void)setImageName:(NSString *)imageName
{
_imageName = imageName;
[self setDefaultImages];
}
// This method is overridden by MWMButtonRenderer.swift
//- (void)applyTheme
//{
// [self changeColoringToOpposite];
// [super applyTheme];
//}
- (void)setColoring:(MWMButtonColoring)coloring
{
_coloring = coloring;
[self setEnabled:self.enabled];
}
- (void)changeColoringToOpposite
{
if (self.coloring == MWMButtonColoringOther)
{
if (self.imageName)
{
[self setDefaultImages];
self.imageView.image = [self imageForState:self.state];
}
return;
}
if (self.state == UIControlStateNormal)
[self setDefaultTintColor];
else if (self.state == UIControlStateHighlighted)
[self setHighlighted:YES];
else if (self.state == UIControlStateSelected)
[self setSelected:YES];
}
- (void)setDefaultImages
{
NSString * postfix = [UIColor isNightMode] ? @"dark" : @"light";
[self setImage:[UIImage imageNamed:[NSString stringWithFormat:kDefaultPattern, self.imageName, postfix]] forState:UIControlStateNormal];
[self setImage:[UIImage imageNamed:[NSString stringWithFormat:kHighlightedPattern, self.imageName, postfix]] forState:UIControlStateHighlighted];
[self setImage:[UIImage imageNamed:[NSString stringWithFormat:kSelectedPattern, self.imageName, postfix]] forState:UIControlStateSelected];
}
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
if (highlighted)
{
switch (self.coloring)
{
case MWMButtonColoringBlue:
self.tintColor = [UIColor linkBlueHighlighted];
break;
case MWMButtonColoringBlack:
self.tintColor = [UIColor blackHintText];
break;
case MWMButtonColoringGray:
self.tintColor = [UIColor blackDividers];
break;
case MWMButtonColoringWhiteText:
self.tintColor = [UIColor whitePrimaryTextHighlighted];
break;
case MWMButtonColoringRed:
self.tintColor = [UIColor buttonRed];
break;
case MWMButtonColoringWhite:
case MWMButtonColoringOther:
break;
}
}
else
{
if (self.selected)
[self setSelected:YES];
else
[self setEnabled:self.enabled];
}
}
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
if (selected)
{
switch (self.coloring)
{
case MWMButtonColoringBlack:
self.tintColor = [UIColor linkBlue];
break;
case MWMButtonColoringWhite:
case MWMButtonColoringWhiteText:
case MWMButtonColoringBlue:
case MWMButtonColoringOther:
case MWMButtonColoringGray:
case MWMButtonColoringRed:
break;
}
}
else
{
[self setEnabled:self.enabled];
}
}
- (void)setEnabled:(BOOL)enabled
{
[super setEnabled:enabled];
if (!enabled)
self.tintColor = [UIColor lightGrayColor];
else
[self setDefaultTintColor];
}
- (void)setDefaultTintColor
{
switch (self.coloring)
{
case MWMButtonColoringBlack:
self.tintColor = [UIColor blackSecondaryText];
break;
case MWMButtonColoringWhite:
self.tintColor = [UIColor white];
break;
case MWMButtonColoringWhiteText:
self.tintColor = [UIColor whitePrimaryText];
break;
case MWMButtonColoringBlue:
self.tintColor = [UIColor linkBlue];
break;
case MWMButtonColoringGray:
self.tintColor = [UIColor blackHintText];
break;
case MWMButtonColoringRed:
self.tintColor = [UIColor red];
break;
case MWMButtonColoringOther:
self.imageView.image = [self imageForState:UIControlStateNormal];
break;
}
}
@end

View file

@ -0,0 +1,5 @@
#import "MWMController.h"
@interface MWMCollectionViewController : UICollectionViewController<MWMController>
@end

View file

@ -0,0 +1,33 @@
#import "MWMCollectionViewController.h"
#import "MWMAlertViewController.h"
#import "MapViewController.h"
#import "SwiftBridge.h"
@interface MWMCollectionViewController ()
@property(nonatomic, readwrite) MWMAlertViewController * alertController;
@end
@implementation MWMCollectionViewController
- (BOOL)prefersStatusBarHidden { return NO; }
- (void)viewDidLoad
{
[super viewDidLoad];
self.collectionView.styleName = @"PressBackground";
[self.navigationController.navigationBar setTranslucent:NO];
}
#pragma mark - Properties
- (BOOL)hasNavigationBar { return YES; }
- (MWMAlertViewController *)alertController
{
if (!_alertController)
_alertController = [[MWMAlertViewController alloc] initWithViewController:self];
return _alertController;
}
@end

View file

@ -0,0 +1,9 @@
@class MWMAlertViewController;
@protocol MWMController <NSObject>
@property (nonatomic, readonly) BOOL hasNavigationBar;
@property (nonatomic, readonly) MWMAlertViewController * alertController;
@end

View file

@ -0,0 +1,5 @@
#import <MessageUI/MessageUI.h>
@interface MWMMailViewController : MFMailComposeViewController
@end

View file

@ -0,0 +1,18 @@
#import "MWMMailViewController.h"
@implementation MWMMailViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationBar.tintColor = UIColor.whiteColor;
self.navigationBar.barTintColor = UIColor.primary;
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
- (UIViewController *)childViewControllerForStatusBarStyle { return nil; }
@end

View file

@ -0,0 +1,3 @@
@interface MWMNavigationController : UINavigationController
@end

View file

@ -0,0 +1,86 @@
#import "MWMNavigationController.h"
#import "MWMController.h"
#import "SwiftBridge.h"
#import <SafariServices/SafariServices.h>
@interface MWMNavigationController () <UINavigationControllerDelegate>
@end
@implementation MWMNavigationController
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
self.navigationItem.leftBarButtonItem.tintColor = [UIColor whitePrimaryText];
self.navigationItem.rightBarButtonItem.tintColor = [UIColor whitePrimaryText];
[MWMThemeManager invalidate];
}
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
if ([viewController isKindOfClass:[SFSafariViewController class]])
{
[navigationController setNavigationBarHidden:YES animated:animated];
return;
}
if ([viewController conformsToProtocol:@protocol(MWMController)]) {
id<MWMController> vc = (id<MWMController>)viewController;
[navigationController setNavigationBarHidden:!vc.hasNavigationBar animated:animated];
}
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
UIViewController * topVC = self.viewControllers.lastObject;
[self setupNavigationBackButtonItemFor:topVC];
[super pushViewController:viewController animated:animated];
}
- (void)setViewControllers:(NSArray<UIViewController *> *)viewControllers animated:(BOOL)animated {
[viewControllers enumerateObjectsUsingBlock:^(UIViewController * vc, NSUInteger idx, BOOL * stop) {
if (idx == viewControllers.count - 1)
return;
[self setupNavigationBackButtonItemFor:vc];
}];
[super setViewControllers:viewControllers animated:animated];
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange: previousTraitCollection];
// Update the app theme when the device appearance is changing.
if ((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
|| (self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass) || (self.traitCollection.userInterfaceStyle != previousTraitCollection.userInterfaceStyle)) {
[MWMThemeManager invalidate];
}
}
- (BOOL)shouldAutorotate
{
return YES;
}
- (void)setupNavigationBackButtonItemFor:(UIViewController *)viewController {
if (@available(iOS 14.0, *)) {
viewController.navigationItem.backButtonDisplayMode = UINavigationItemBackButtonDisplayModeMinimal;
} else {
viewController.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@""
style:UIBarButtonItemStylePlain
target:nil
action:nil];
}
}
@end

View file

@ -0,0 +1,3 @@
@interface MWMStartButton : UIButton
@end

View file

@ -0,0 +1,5 @@
#import "MWMStartButton.h"
@implementation MWMStartButton
@end

View file

@ -0,0 +1,3 @@
@interface MWMStopButton : UIButton
@end

View file

@ -0,0 +1,5 @@
#import "MWMStopButton.h"
@implementation MWMStopButton
@end

View file

@ -0,0 +1,15 @@
#import "MWMController.h"
NS_ASSUME_NONNULL_BEGIN
@interface MWMTableViewController : UITableViewController <MWMController>
@end
@interface UITableView (MWMTableViewController)
- (UITableViewCell *)dequeueDefaultCellForIndexPath:(NSIndexPath *)indexPath;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,86 @@
#import "MapsAppDelegate.h"
#import "MapViewController.h"
#import "MWMAlertViewController.h"
#import "MWMTableViewCell.h"
#import "MWMTableViewController.h"
#import "SwiftBridge.h"
static CGFloat const kMaxEstimatedTableViewCellHeight = 100.0;
@interface MWMTableViewController ()
@property (nonatomic, readwrite) MWMAlertViewController * alertController;
@end
@implementation MWMTableViewController
- (BOOL)prefersStatusBarHidden
{
return NO;
}
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
[self fixHeaderAndFooterFontsInDarkMode:view];
}
- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section
{
[self fixHeaderAndFooterFontsInDarkMode:view];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return kMaxEstimatedTableViewCellHeight;
}
// Fix table section header font color for all tables, including Setting and Route Options.
- (void)fixHeaderAndFooterFontsInDarkMode:(UIView*)headerView {
if ([headerView isKindOfClass: [UITableViewHeaderFooterView class]]) {
UITableViewHeaderFooterView* header = (UITableViewHeaderFooterView *)headerView;
header.textLabel.textColor = [UIColor blackSecondaryText];
if (self.tableView.style == UITableViewStyleGrouped) {
header.detailTextLabel.textColor = [UIColor blackSecondaryText];
}
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.insetsContentViewsToSafeArea = YES;
self.tableView.styleName = @"TableView:PressBackground";
[self.navigationController.navigationBar setTranslucent:NO];
[self.tableView registerClass:[MWMTableViewCell class]
forCellReuseIdentifier:[UITableViewCell className]];
[self.tableView registerClass:[MWMTableViewSubtitleCell class]
forCellReuseIdentifier:[MWMTableViewSubtitleCell className]];
}
#pragma mark - Properties
- (BOOL)hasNavigationBar
{
return YES;
}
- (MWMAlertViewController *)alertController
{
if (!_alertController)
_alertController = [[MWMAlertViewController alloc] initWithViewController:self];
return _alertController;
}
@end
@implementation UITableView (MWMTableViewController)
- (UITableViewCell *)dequeueDefaultCellForIndexPath:(NSIndexPath *)indexPath {
return [self dequeueReusableCellWithIdentifier:UITableViewCell.className forIndexPath:indexPath];
}
@end

View file

@ -0,0 +1,4 @@
#import "MWMController.h"
@interface MWMViewController : UIViewController <MWMController>
@end

View file

@ -0,0 +1,40 @@
#import "MapsAppDelegate.h"
#import "MapViewController.h"
#import "MWMAlertViewController.h"
#import "MWMViewController.h"
@interface MWMViewController ()
@property (nonatomic, readwrite) MWMAlertViewController * alertController;
@end
@implementation MWMViewController
- (BOOL)prefersStatusBarHidden
{
return NO;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController.navigationBar setTranslucent:NO];
}
#pragma mark - Properties
- (BOOL)hasNavigationBar
{
return YES;
}
- (MWMAlertViewController *)alertController
{
if (!_alertController)
_alertController = [[MWMAlertViewController alloc] initWithViewController:self];
return _alertController;
}
@end

View file

@ -0,0 +1,27 @@
final class AlertPresentationController: DimmedModalPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
let f = super.frameOfPresentedViewInContainerView
let s = presentedViewController.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
let r = CGRect(x: 0, y: 0, width: s.width, height: s.height)
return r.offsetBy(dx: (f.width - r.width) / 2, dy: (f.height - r.height) / 2)
}
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
presentedViewController.view.layer.setCornerRadius(.modalSheet)
presentedViewController.view.clipsToBounds = true
guard let containerView = containerView, let presentedView = presentedView else { return }
containerView.addSubview(presentedView)
presentedView.center = containerView.center
presentedView.frame = frameOfPresentedViewInContainerView
presentedView.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin]
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
super.presentationTransitionDidEnd(completed)
guard let presentedView = presentedView else { return }
if completed {
presentedView.removeFromSuperview()
}
}
}

View file

@ -0,0 +1,21 @@
final class CoverVerticalDismissalAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: 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 originFrame = transitionContext.finalFrame(for: toVC)
let finalFrame = originFrame.offsetBy(dx: 0, dy: originFrame.height)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
animations: {
fromVC.view.frame = finalFrame
}) { finished in
fromVC.view.removeFromSuperview()
transitionContext.completeTransition(finished)
}
}
}

View file

@ -0,0 +1,39 @@
final class CoverVerticalModalTransitioning: NSObject, UIViewControllerTransitioningDelegate {
private var height: CGFloat
init(presentationHeight: CGFloat) {
height = presentationHeight
}
func animationController(forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CoverVerticalPresentationAnimator()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CoverVerticalDismissalAnimator()
}
func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController? {
return PresentationController(presentedViewController: presented, presenting: presenting, presentationHeight: height)
}
}
fileprivate final class PresentationController: DimmedModalPresentationController {
private var height: CGFloat
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, presentationHeight: CGFloat) {
height = presentationHeight
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
required init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, cancellable: Bool = true) {
fatalError("init(presentedViewController:presenting:cancellable:) has not been implemented")
}
override var frameOfPresentedViewInContainerView: CGRect {
let f = super.frameOfPresentedViewInContainerView
return CGRect(x: 0, y: f.height - height, width: f.width, height: height)
}
}

View file

@ -0,0 +1,21 @@
final class CoverVerticalPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toVC = transitionContext.viewController(forKey: .to) else { return }
let containerView = transitionContext.containerView
let finalFrame = transitionContext.finalFrame(for: toVC)
let originFrame = finalFrame.offsetBy(dx: 0, dy: finalFrame.height)
containerView.addSubview(toVC.view)
toVC.view.frame = originFrame
toVC.view.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
UIView.animate(withDuration: transitionDuration(using: transitionContext),
animations: {
toVC.view.frame = finalFrame
}) { transitionContext.completeTransition($0) }
}
}

View file

@ -0,0 +1,50 @@
class DimmedModalPresentationController: UIPresentationController {
private lazy var onTapGr: UITapGestureRecognizer = {
return UITapGestureRecognizer(target: self, action: #selector(onTap))
}()
private lazy var dimView: UIView = {
let view = UIView()
view.setStyle(.blackStatusBarBackground)
if isCancellable {
view.addGestureRecognizer(onTapGr)
}
return view
}()
let isCancellable: Bool
required init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, cancellable: Bool = true) {
isCancellable = cancellable
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
@objc private func onTap() {
presentingViewController.dismiss(animated: true, completion: nil)
}
override func presentationTransitionWillBegin() {
guard let containerView = containerView else { return }
containerView.addSubview(dimView)
dimView.frame = containerView.bounds
dimView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
dimView.alpha = 0
presentingViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
self.dimView.alpha = 1
})
}
override func presentationTransitionDidEnd(_ completed: Bool) {
if !completed { dimView.removeFromSuperview() }
}
override func dismissalTransitionWillBegin() {
presentingViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
self.dimView.alpha = 0
})
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
if completed { dimView.removeFromSuperview() }
}
}

View file

@ -0,0 +1,15 @@
final class FadeInAnimatedTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let presentedView = transitionContext.view(forKey: .to) else { return }
presentedView.alpha = 0
UIView.animate(withDuration: transitionDuration(using: transitionContext),
animations: {
presentedView.alpha = 1
}) { transitionContext.completeTransition($0) }
}
}

View file

@ -0,0 +1,15 @@
final class FadeOutAnimatedTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let presentedView = transitionContext.view(forKey: .from) else { return }
UIView.animate(withDuration: transitionDuration(using: transitionContext),
animations: {
presentedView.alpha = 0
}) { finished in
transitionContext.completeTransition(finished)
}
}
}

View file

@ -0,0 +1,26 @@
class FadeTransitioning<T: DimmedModalPresentationController>: NSObject, UIViewControllerTransitioningDelegate {
let presentedTransitioning = FadeInAnimatedTransitioning()
let dismissedTransitioning = FadeOutAnimatedTransitioning()
let isCancellable: Bool
init(cancellable: Bool = true) {
isCancellable = cancellable
super.init()
}
func animationController(forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return presentedTransitioning
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return dismissedTransitioning
}
func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController? {
return T(presentedViewController: presented, presenting: presenting, cancellable: isCancellable)
}
}

View file

@ -0,0 +1,34 @@
final class IPadModalPresentationController: DimmedModalPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = containerView else { return CGRect.zero }
let screenSize = UIScreen.main.bounds
let contentSize = presentedViewController.preferredContentSize
let r = alternative(iPhone: containerView.bounds,
iPad: CGRect(x: screenSize.width/2 - contentSize.width/2,
y: screenSize.height/2 - contentSize.height/2,
width: contentSize.width,
height: contentSize.height))
return r
}
override func containerViewWillLayoutSubviews() {
presentedView?.frame = frameOfPresentedViewInContainerView
}
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
presentedViewController.view.layer.setCornerRadius(.buttonDefault)
presentedViewController.view.clipsToBounds = true
guard let containerView = containerView, let presentedView = presentedView else { return }
containerView.addSubview(presentedView)
presentedView.frame = frameOfPresentedViewInContainerView
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
super.presentationTransitionDidEnd(completed)
guard let presentedView = presentedView else { return }
if completed {
presentedView.removeFromSuperview()
}
}
}

View file

@ -0,0 +1,33 @@
final class PromoBookingPresentationController: DimmedModalPresentationController {
let sideMargin: CGFloat = 32.0
let maxWidth: CGFloat = 310.0
override var frameOfPresentedViewInContainerView: CGRect {
let f = super.frameOfPresentedViewInContainerView
let estimatedWidth = min(maxWidth, f.width - (sideMargin * 2.0))
let s = presentedViewController.view.systemLayoutSizeFitting(CGSize(width: estimatedWidth, height: f.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
let r = CGRect(x: (f.width - s.width) / 2, y: (f.height - s.height) / 2, width: s.width, height: s.height)
return r
}
override func containerViewWillLayoutSubviews() {
presentedView?.frame = frameOfPresentedViewInContainerView
}
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
presentedViewController.view.layer.setCornerRadius(.buttonDefault)
presentedViewController.view.clipsToBounds = true
guard let containerView = containerView, let presentedView = presentedView else { return }
containerView.addSubview(presentedView)
presentedView.frame = frameOfPresentedViewInContainerView
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
super.presentationTransitionDidEnd(completed)
guard let presentedView = presentedView else { return }
if completed {
presentedView.removeFromSuperview()
}
}
}

View file

@ -0,0 +1,316 @@
fileprivate class ContentCell: UICollectionViewCell {
var view: UIView? {
didSet {
if let view = view, view != oldValue {
oldValue?.removeFromSuperview()
view.frame = contentView.bounds
contentView.addSubview(view)
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
if let view = view {
view.frame = contentView.bounds
}
}
}
fileprivate class HeaderCell: UICollectionViewCell {
private let label = UILabel()
private var selectedAttributes: [NSAttributedString.Key : Any] = [:]
private var deselectedAttributes: [NSAttributedString.Key : Any] = [:]
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(label)
label.textAlignment = .center
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
contentView.addSubview(label)
label.textAlignment = .center
}
override var isSelected: Bool {
didSet {
label.attributedText = NSAttributedString(string: label.text ?? "",
attributes: isSelected ? selectedAttributes : deselectedAttributes)
}
}
override func prepareForReuse() {
super.prepareForReuse()
label.attributedText = nil
}
override func layoutSubviews() {
super.layoutSubviews()
label.frame = contentView.bounds
}
func configureWith(selectedAttributes: [NSAttributedString.Key : Any],
deselectedAttributes: [NSAttributedString.Key : Any],
text: String) {
self.selectedAttributes = selectedAttributes
self.deselectedAttributes = deselectedAttributes
label.attributedText = NSAttributedString(string: text.uppercased(),
attributes: deselectedAttributes)
}
}
protocol TabViewDataSource: AnyObject {
func numberOfPages(in tabView: TabView) -> Int
func tabView(_ tabView: TabView, viewAt index: Int) -> UIView
func tabView(_ tabView: TabView, titleAt index: Int) -> String?
}
protocol TabViewDelegate: AnyObject {
func tabView(_ tabView: TabView, didSelectTabAt index: Int)
}
@objcMembers
@objc(MWMTabView)
class TabView: UIView {
private enum CellId {
static let content = "contentCell"
static let header = "headerCell"
}
private let tabsLayout = UICollectionViewFlowLayout()
private let tabsContentLayout = UICollectionViewFlowLayout()
private let tabsCollectionView: UICollectionView
private let tabsContentCollectionView: UICollectionView
private let headerView = UIView()
private let slidingView = UIView()
private var slidingViewLeft: NSLayoutConstraint!
private var slidingViewWidth: NSLayoutConstraint!
private lazy var pageCount = { return self.dataSource?.numberOfPages(in: self) ?? 0; }()
var selectedIndex: Int?
private var lastSelectedIndex: Int?
weak var dataSource: TabViewDataSource?
weak var delegate: TabViewDelegate?
var barTintColor = UIColor.white {
didSet {
headerView.backgroundColor = barTintColor
}
}
var selectedHeaderTextAttributes: [NSAttributedString.Key : Any] = [
.foregroundColor : UIColor.white,
.font : UIFont.systemFont(ofSize: 14, weight: .semibold)
] {
didSet {
tabsCollectionView.reloadData()
}
}
var deselectedHeaderTextAttributes: [NSAttributedString.Key : Any] = [
.foregroundColor : UIColor.gray,
.font : UIFont.systemFont(ofSize: 14, weight: .semibold)
] {
didSet {
tabsCollectionView.reloadData()
}
}
var contentFrame: CGRect {
safeAreaLayoutGuide.layoutFrame
}
override var tintColor: UIColor! {
didSet {
slidingView.backgroundColor = tintColor
}
}
override init(frame: CGRect) {
tabsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: tabsLayout)
tabsContentCollectionView = UICollectionView(frame: .zero, collectionViewLayout: tabsContentLayout)
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
tabsCollectionView = UICollectionView(frame: .zero, collectionViewLayout: tabsLayout)
tabsContentCollectionView = UICollectionView(frame: .zero, collectionViewLayout: tabsContentLayout)
super.init(coder: aDecoder)
configure()
}
private func configure() {
backgroundColor = .white
configureHeader()
configureContent()
addSubview(tabsContentCollectionView)
addSubview(headerView)
configureLayoutContraints()
}
private func configureHeader() {
tabsLayout.scrollDirection = .horizontal
tabsLayout.minimumLineSpacing = 0
tabsLayout.minimumInteritemSpacing = 0
tabsCollectionView.register(HeaderCell.self, forCellWithReuseIdentifier: CellId.header)
tabsCollectionView.dataSource = self
tabsCollectionView.delegate = self
tabsCollectionView.backgroundColor = .clear
slidingView.backgroundColor = tintColor
headerView.backgroundColor = barTintColor
headerView.addSubview(tabsCollectionView)
headerView.addSubview(slidingView)
headerView.addSeparator(.bottom)
}
private func configureContent() {
tabsContentLayout.scrollDirection = .horizontal
tabsContentLayout.minimumLineSpacing = 0
tabsContentLayout.minimumInteritemSpacing = 0
tabsContentCollectionView.register(ContentCell.self, forCellWithReuseIdentifier: CellId.content)
tabsContentCollectionView.dataSource = self
tabsContentCollectionView.delegate = self
tabsContentCollectionView.isPagingEnabled = true
tabsContentCollectionView.bounces = false
tabsContentCollectionView.showsVerticalScrollIndicator = false
tabsContentCollectionView.showsHorizontalScrollIndicator = false
tabsContentCollectionView.backgroundColor = .clear
}
private func configureLayoutContraints() {
tabsCollectionView.translatesAutoresizingMaskIntoConstraints = false;
tabsContentCollectionView.translatesAutoresizingMaskIntoConstraints = false
headerView.translatesAutoresizingMaskIntoConstraints = false
slidingView.translatesAutoresizingMaskIntoConstraints = false
headerView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
headerView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
headerView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor).isActive = true
headerView.heightAnchor.constraint(equalToConstant: 46).isActive = true
tabsContentCollectionView.leftAnchor.constraint(equalTo: safeAreaLayoutGuide.leftAnchor).isActive = true
tabsContentCollectionView.rightAnchor.constraint(equalTo: safeAreaLayoutGuide.rightAnchor).isActive = true
tabsContentCollectionView.topAnchor.constraint(equalTo: headerView.bottomAnchor).isActive = true
tabsContentCollectionView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor).isActive = true
tabsCollectionView.leftAnchor.constraint(equalTo: headerView.leftAnchor).isActive = true
tabsCollectionView.rightAnchor.constraint(equalTo: headerView.rightAnchor).isActive = true
tabsCollectionView.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true
tabsCollectionView.bottomAnchor.constraint(equalTo: slidingView.topAnchor).isActive = true
slidingView.heightAnchor.constraint(equalToConstant: 3).isActive = true
slidingView.bottomAnchor.constraint(equalTo: headerView.bottomAnchor).isActive = true
slidingViewLeft = slidingView.leftAnchor.constraint(equalTo: safeAreaLayoutGuide.leftAnchor)
slidingViewLeft.isActive = true
slidingViewWidth = slidingView.widthAnchor.constraint(equalToConstant: 0)
slidingViewWidth.isActive = true
}
override func layoutSubviews() {
tabsLayout.invalidateLayout()
tabsContentLayout.invalidateLayout()
super.layoutSubviews()
assert(pageCount > 0)
slidingViewWidth.constant = pageCount > 0 ? contentFrame.width / CGFloat(pageCount) : 0
slidingViewLeft.constant = pageCount > 0 ? contentFrame.width / CGFloat(pageCount) * CGFloat(selectedIndex ?? 0) : 0
tabsCollectionView.layoutIfNeeded()
tabsContentCollectionView.layoutIfNeeded()
if let selectedIndex = selectedIndex {
tabsContentCollectionView.scrollToItem(at: IndexPath(item: selectedIndex, section: 0),
at: .left,
animated: false)
}
}
}
extension TabView : UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pageCount
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell = UICollectionViewCell()
if collectionView == tabsContentCollectionView {
cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellId.content, for: indexPath)
if let contentCell = cell as? ContentCell {
contentCell.view = dataSource?.tabView(self, viewAt: indexPath.item)
}
}
if collectionView == tabsCollectionView {
cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellId.header, for: indexPath)
if let headerCell = cell as? HeaderCell {
let title = dataSource?.tabView(self, titleAt: indexPath.item) ?? ""
headerCell.configureWith(selectedAttributes: selectedHeaderTextAttributes,
deselectedAttributes: deselectedHeaderTextAttributes,
text: title)
if indexPath.item == selectedIndex {
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
}
}
}
return cell
}
}
extension TabView : UICollectionViewDelegateFlowLayout {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentSize.width > 0 {
let scrollOffset = scrollView.contentOffset.x / scrollView.contentSize.width
slidingViewLeft.constant = scrollOffset * contentFrame.width
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
lastSelectedIndex = selectedIndex
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
selectedIndex = Int(round(scrollView.contentOffset.x / scrollView.bounds.width))
if let selectedIndex = selectedIndex, selectedIndex != lastSelectedIndex {
delegate?.tabView(self, didSelectTabAt: selectedIndex)
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (collectionView == tabsCollectionView) {
let isSelected = selectedIndex == indexPath.item
if !isSelected {
selectedIndex = indexPath.item
tabsContentCollectionView.scrollToItem(at: indexPath, at: .left, animated: true)
delegate?.tabView(self, didSelectTabAt: selectedIndex!)
}
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if (collectionView == tabsCollectionView) {
collectionView.deselectItem(at: indexPath, animated: false)
}
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let bounds = collectionView.bounds.inset(by: collectionView.adjustedContentInset)
if collectionView == tabsContentCollectionView {
return bounds.size
} else {
return CGSize(width: bounds.width / CGFloat(pageCount),
height: bounds.height)
}
}
}

View file

@ -0,0 +1,36 @@
class TabViewController: MWMViewController {
var viewControllers: [UIViewController] = [] {
didSet {
viewControllers.forEach {
self.addChild($0)
$0.didMove(toParent: self)
}
}
}
var tabView: TabView {
get {
return view as! TabView
}
}
override func loadView() {
let v = TabView()
v.dataSource = self
view = v
}
}
extension TabViewController: TabViewDataSource {
func numberOfPages(in tabView: TabView) -> Int {
return viewControllers.count
}
func tabView(_ tabView: TabView, viewAt index: Int) -> UIView {
return viewControllers[index].view
}
func tabView(_ tabView: TabView, titleAt index: Int) -> String? {
return viewControllers[index].title
}
}

View file

@ -0,0 +1,88 @@
@IBDesignable
class VerticallyAlignedButton: UIControl {
@IBInspectable
var image: UIImage? {
didSet {
imageView.image = image
}
}
@IBInspectable
var title: String? {
didSet {
if localizedText == nil {
titleLabel.text = title
}
}
}
@IBInspectable
var localizedText: String? {
didSet {
if let localizedText = localizedText {
titleLabel.text = L(localizedText)
}
}
}
@IBInspectable
var spacing: CGFloat = 4 {
didSet {
spacingConstraint.constant = spacing
}
}
@IBInspectable
var numberOfLines: Int {
get {
return titleLabel.numberOfLines
}
set {
titleLabel.numberOfLines = newValue
}
}
private lazy var spacingConstraint: NSLayoutConstraint = {
let spacingConstraint = titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: spacing)
return spacingConstraint
}()
lazy var titleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.textAlignment = .center
titleLabel.translatesAutoresizingMaskIntoConstraints = false
return titleLabel
}()
lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
addSubview(titleLabel)
addSubview(imageView)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: topAnchor),
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor),
spacingConstraint,
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
titleLabel.topAnchor.constraint(equalTo: bottomAnchor)
])
}
}

View file

@ -0,0 +1,18 @@
#import "MWMAlert+CPP.h"
#import "MWMAlertViewController.h"
#include "routing/router.hpp"
#include "routing/routing_callbacks.hpp"
#include "storage/storage.hpp"
#include "storage/storage_defines.hpp"
@interface MWMAlertViewController (CPP)
- (void)presentAlert:(routing::RouterResultCode)type;
- (void)presentDownloaderAlertWithCountries:(storage::CountriesSet const &)countries
code:(routing::RouterResultCode)code
cancelBlock:(nonnull MWMVoidBlock)cancelBlock
downloadBlock:(nonnull MWMDownloadBlock)downloadBlock
downloadCompleteBlock:(nonnull MWMVoidBlock)downloadCompleteBlock;
@end

View file

@ -0,0 +1,60 @@
#import "MWMAlert.h"
#import "MWMMobileInternetAlert.h"
#import "MWMViewController.h"
@interface MWMAlertViewController : MWMViewController
+ (nonnull MWMAlertViewController *)activeAlertController;
@property(weak, nonatomic, readonly) UIViewController *_Null_unspecified ownerViewController;
- (nonnull instancetype)initWithViewController:(nonnull UIViewController *)viewController;
- (void)presentPoint2PointAlertWithOkBlock:(nonnull MWMVoidBlock)okBlock needToRebuild:(BOOL)needToRebuild;
- (void)presentRoutingDisclaimerAlertWithOkBlock:(nonnull nonnull MWMVoidBlock)block;
- (void)presentDisabledLocationAlert;
- (void)presentLocationAlertWithCancelBlock:(MWMVoidBlock _Nonnull )cancelBlock;
- (void)presentLocationServicesDisabledAlert;
- (void)presentLocationServiceNotSupportedAlert;
- (void)presentNoConnectionAlert;
- (void)presentDeleteMapProhibitedAlert;
- (void)presentUnsavedEditsAlertWithOkBlock:(nonnull MWMVoidBlock)okBlock;
- (void)presentNoWiFiAlertWithOkBlock:(nullable MWMVoidBlock)okBlock andCancelBlock:(nullable MWMVoidBlock)cancelBlock;
- (void)presentIncorrectFeauturePositionAlert;
- (void)presentNotEnoughSpaceAlert;
- (void)presentInvalidUserNameOrPasswordAlert;
- (void)presentDownloaderNoConnectionAlertWithOkBlock:(nonnull MWMVoidBlock)okBlock
cancelBlock:(nonnull MWMVoidBlock)cancelBlock;
- (void)presentDownloaderNotEnoughSpaceAlert;
- (void)presentDownloaderInternalErrorAlertWithOkBlock:(nonnull MWMVoidBlock)okBlock
cancelBlock:(nonnull MWMVoidBlock)cancelBlock;
- (void)presentPlaceDoesntExistAlertWithBlock:(nonnull MWMStringBlock)block;
- (void)presentResetChangesAlertWithBlock:(nonnull MWMVoidBlock)block;
- (void)presentDeleteFeatureAlertWithBlock:(nonnull MWMVoidBlock)block;
- (void)presentPersonalInfoWarningAlertWithBlock:(nonnull MWMVoidBlock)block;
- (void)presentTrackWarningAlertWithCancelBlock:(nonnull MWMVoidBlock)block;
- (void)presentMobileInternetAlertWithBlock:(nonnull MWMMobileInternetAlertCompletionBlock)block;
- (void)presentInfoAlert:(nonnull NSString *)title text:(nonnull NSString *)text;
- (void)presentInfoAlert:(nonnull NSString *)title;
- (void)presentCreateBookmarkCategoryAlertWithMaxCharacterNum:(NSUInteger)max
minCharacterNum:(NSUInteger)min
callback:(nonnull MWMCheckStringBlock)callback;
- (void)presentBookmarkConversionErrorAlert;
- (void)presentBugReportAlertWithTitle:(nonnull NSString *)title;
- (void)presentDefaultAlertWithTitle:(nonnull NSString *)title
message:(nullable NSString *)message
rightButtonTitle:(nonnull NSString *)rightButtonTitle
leftButtonTitle:(nullable NSString *)leftButtonTitle
rightButtonAction:(nullable MWMVoidBlock)action;
- (void)closeAlert:(nullable MWMVoidBlock)completion;
- (nonnull instancetype)init __attribute__((unavailable("call -initWithViewController: instead!")));
+ (nonnull instancetype)new __attribute__((unavailable("call -initWithViewController: instead!")));
- (nonnull instancetype)initWithCoder:(nonnull NSCoder *)aDecoder
__attribute__((unavailable("call -initWithViewController: instead!")));
- (nonnull instancetype)initWithNibName:(nullable NSString *)nibNameOrNil
bundle:(nullable NSBundle *)nibBundleOrNil
__attribute__((unavailable("call -initWithViewController: instead!")));
@end

View file

@ -0,0 +1,278 @@
#import "MWMAlertViewController+CPP.h"
#import "MWMController.h"
#import "MWMDownloadTransitMapAlert.h"
#import "MWMLocationAlert.h"
#import "MapViewController.h"
#import "MapsAppDelegate.h"
#import "SwiftBridge.h"
static NSString *const kAlertControllerNibIdentifier = @"MWMAlertViewController";
@interface MWMAlertViewController () <UIGestureRecognizerDelegate>
@property(weak, nonatomic, readwrite) UIViewController *ownerViewController;
@end
@implementation MWMAlertViewController
+ (nonnull MWMAlertViewController *)activeAlertController {
UIViewController *tvc = [MapViewController sharedController];
ASSERT([tvc conformsToProtocol:@protocol(MWMController)], ());
UIViewController<MWMController> *mwmController = static_cast<UIViewController<MWMController> *>(tvc);
return mwmController.alertController;
}
- (nonnull instancetype)initWithViewController:(nonnull UIViewController *)viewController {
self = [super initWithNibName:kAlertControllerNibIdentifier bundle:nil];
if (self)
_ownerViewController = viewController;
return self;
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
auto const orient = size.width > size.height ? UIInterfaceOrientationLandscapeLeft : UIInterfaceOrientationPortrait;
[coordinator
animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
for (MWMAlert *alert in self.view.subviews)
[alert rotate:orient duration:context.transitionDuration];
}
completion:^(id<UIViewControllerTransitionCoordinatorContext> context){
}];
}
#pragma mark - Actions
- (void)presentLocationAlertWithCancelBlock:(MWMVoidBlock)cancelBlock {
[self displayAlert:[MWMAlert locationAlertWithCancelBlock:cancelBlock]];
}
- (void)presentPoint2PointAlertWithOkBlock:(nonnull MWMVoidBlock)okBlock needToRebuild:(BOOL)needToRebuild {
[self displayAlert:[MWMAlert point2PointAlertWithOkBlock:okBlock needToRebuild:needToRebuild]];
}
- (void)presentLocationServiceNotSupportedAlert {
[self displayAlert:[MWMAlert locationServiceNotSupportedAlert]];
}
- (void)presentNoConnectionAlert {
[self displayAlert:[MWMAlert noConnectionAlert]];
}
- (void)presentDeleteMapProhibitedAlert {
[self displayAlert:[MWMAlert deleteMapProhibitedAlert]];
}
- (void)presentUnsavedEditsAlertWithOkBlock:(nonnull MWMVoidBlock)okBlock {
[self displayAlert:[MWMAlert unsavedEditsAlertWithOkBlock:okBlock]];
}
- (void)presentNoWiFiAlertWithOkBlock:(nullable MWMVoidBlock)okBlock andCancelBlock:(MWMVoidBlock)cancelBlock {
[self displayAlert:[MWMAlert noWiFiAlertWithOkBlock:okBlock andCancelBlock:cancelBlock]];
}
- (void)presentIncorrectFeauturePositionAlert {
[self displayAlert:[MWMAlert incorrectFeaturePositionAlert]];
}
- (void)presentNotEnoughSpaceAlert {
[self displayAlert:[MWMAlert notEnoughSpaceAlert]];
}
- (void)presentInvalidUserNameOrPasswordAlert {
[self displayAlert:[MWMAlert invalidUserNameOrPasswordAlert]];
}
- (void)presentDownloaderAlertWithCountries:(storage::CountriesSet const &)countries
code:(routing::RouterResultCode)code
cancelBlock:(MWMVoidBlock)cancelBlock
downloadBlock:(MWMDownloadBlock)downloadBlock
downloadCompleteBlock:(MWMVoidBlock)downloadCompleteBlock {
[self displayAlert:[MWMAlert downloaderAlertWithAbsentCountries:countries
code:code
cancelBlock:cancelBlock
downloadBlock:downloadBlock
downloadCompleteBlock:downloadCompleteBlock]];
}
- (void)presentRoutingDisclaimerAlertWithOkBlock:(MWMVoidBlock)block {
[self displayAlert:[MWMAlert routingDisclaimerAlertWithOkBlock:block]];
}
- (void)presentDisabledLocationAlert {
[self displayAlert:[MWMAlert disabledLocationAlert]];
}
- (void)presentLocationServicesDisabledAlert; {
[self displayAlert:[MWMAlert locationServicesDisabledAlert]];
}
- (void)presentAlert:(routing::RouterResultCode)type {
[self displayAlert:[MWMAlert alert:type]];
}
- (void)presentDownloaderNoConnectionAlertWithOkBlock:(nonnull MWMVoidBlock)okBlock
cancelBlock:(nonnull MWMVoidBlock)cancelBlock {
[self displayAlert:[MWMAlert downloaderNoConnectionAlertWithOkBlock:okBlock cancelBlock:cancelBlock]];
}
- (void)presentDownloaderNotEnoughSpaceAlert {
[self displayAlert:[MWMAlert downloaderNotEnoughSpaceAlert]];
}
- (void)presentDownloaderInternalErrorAlertWithOkBlock:(nonnull MWMVoidBlock)okBlock
cancelBlock:(nonnull MWMVoidBlock)cancelBlock {
[self displayAlert:[MWMAlert downloaderInternalErrorAlertWithOkBlock:okBlock cancelBlock:cancelBlock]];
}
- (void)presentPlaceDoesntExistAlertWithBlock:(MWMStringBlock)block {
[self displayAlert:[MWMAlert placeDoesntExistAlertWithBlock:block]];
}
- (void)presentResetChangesAlertWithBlock:(MWMVoidBlock)block {
[self displayAlert:[MWMAlert resetChangesAlertWithBlock:block]];
}
- (void)presentDeleteFeatureAlertWithBlock:(MWMVoidBlock)block {
[self displayAlert:[MWMAlert deleteFeatureAlertWithBlock:block]];
}
- (void)presentPersonalInfoWarningAlertWithBlock:(nonnull MWMVoidBlock)block {
[self displayAlert:[MWMAlert personalInfoWarningAlertWithBlock:block]];
}
- (void)presentTrackWarningAlertWithCancelBlock:(nonnull MWMVoidBlock)block {
[self displayAlert:[MWMAlert trackWarningAlertWithCancelBlock:block]];
}
- (void)presentMobileInternetAlertWithBlock:(nonnull MWMMobileInternetAlertCompletionBlock)block {
[self displayAlert:[MWMMobileInternetAlert alertWithBlock:block]];
}
- (void)presentInfoAlert:(nonnull NSString *)title text:(nonnull NSString *)text {
[self displayAlert:[MWMAlert infoAlert:title text:text]];
}
- (void)presentInfoAlert:(nonnull NSString *)title {
[self displayAlert:[MWMAlert infoAlert:title text:nil]];
}
- (void)presentCreateBookmarkCategoryAlertWithMaxCharacterNum:(NSUInteger)max
minCharacterNum:(NSUInteger)min
callback:(nonnull MWMCheckStringBlock)callback {
auto alert =
static_cast<MWMBCCreateCategoryAlert *>([MWMAlert createBookmarkCategoryAlertWithMaxCharacterNum:max
minCharacterNum:min
callback:callback]);
[self displayAlert:alert];
dispatch_async(dispatch_get_main_queue(), ^{
[alert.textField becomeFirstResponder];
});
}
- (void)presentSpinnerAlertWithTitle:(nonnull NSString *)title cancel:(nullable MWMVoidBlock)cancel {
[self displayAlert:[MWMAlert spinnerAlertWithTitle:title cancel:cancel]];
}
- (void)presentBookmarkConversionErrorAlert {
[self displayAlert:[MWMAlert bookmarkConversionErrorAlert]];
}
- (void)presentTagsLoadingErrorAlertWithOkBlock:(nonnull MWMVoidBlock)okBlock
cancelBlock:(nonnull MWMVoidBlock)cancelBlock {
[self displayAlert:[MWMAlert tagsLoadingErrorAlertWithOkBlock:okBlock cancelBlock:cancelBlock]];
}
- (void)presentBugReportAlertWithTitle:(nonnull NSString *)title {
[self displayAlert:[MWMAlert bugReportAlertWithTitle:title]];
}
- (void)presentDefaultAlertWithTitle:(nonnull NSString *)title
message:(nullable NSString *)message
rightButtonTitle:(nonnull NSString *)rightButtonTitle
leftButtonTitle:(nullable NSString *)leftButtonTitle
rightButtonAction:(nullable MWMVoidBlock)action {
[self displayAlert:[MWMAlert defaultAlertWithTitle:title
message:message
rightButtonTitle:rightButtonTitle
leftButtonTitle:leftButtonTitle
rightButtonAction:action]];
}
- (void)displayAlert:(MWMAlert *)alert {
UIViewController *ownerVC = self.ownerViewController;
if (ownerVC.navigationController != nil) {
ownerVC = ownerVC.navigationController;
}
BOOL isOwnerLoaded = ownerVC.isViewLoaded;
if (!isOwnerLoaded) {
return;
}
// TODO(igrechuhin): Remove this check on location manager refactoring.
// Workaround for current location manager duplicate error alerts.
if ([alert isKindOfClass:[MWMLocationAlert class]]) {
for (MWMAlert *view in self.view.subviews) {
if ([view isKindOfClass:[MWMLocationAlert class]])
return;
}
}
[UIView animateWithDuration:kDefaultAnimationDuration
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
for (MWMAlert *view in self.view.subviews) {
if (view != alert)
view.alpha = 0.0;
}
}
completion:nil];
[self willMoveToParentViewController:NULL];
[self.view removeFromSuperview];
[self removeFromParentViewController];
alert.alertController = self;
[ownerVC addChildViewController:self];
self.view.frame = CGRectMake(0, 0, ownerVC.view.frame.size.width, ownerVC.view.frame.size.height);
[ownerVC.view addSubview:self.view];
[self didMoveToParentViewController:ownerVC];
alert.alpha = 0.;
[self.view addSubview:alert];
CGFloat const scale = 1.1;
alert.transform = CGAffineTransformMakeScale(scale, scale);
[UIView animateWithDuration:kDefaultAnimationDuration
animations:^{
self.view.alpha = 1.;
alert.alpha = 1.;
alert.transform = CGAffineTransformIdentity;
}];
[[MapsAppDelegate theApp].window endEditing:YES];
}
- (void)closeAlert:(nullable MWMVoidBlock)completion {
NSArray *subviews = self.view.subviews;
MWMAlert *closeAlert = subviews.lastObject;
MWMAlert *showAlert = (subviews.count >= 2 ? subviews[subviews.count - 2] : nil);
[UIView animateWithDuration:kDefaultAnimationDuration
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
closeAlert.alpha = 0.;
if (showAlert)
showAlert.alpha = 1.;
else
self.view.alpha = 0.;
}
completion:^(BOOL finished) {
[closeAlert removeFromSuperview];
if (!showAlert) {
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
if (completion)
completion();
}];
}
@end

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<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="MWMAlertViewController">
<connections>
<outlet property="view" destination="PTX-3C-07U" id="OUb-QU-9sV"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view multipleTouchEnabled="YES" contentMode="scaleToFill" id="PTX-3C-07U" customClass="SolidTouchView" propertyAccessControl="all">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.40000000000000002" colorSpace="custom" customColorSpace="sRGB"/>
<gestureRecognizers/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="1tF-gt-bLj"/>
<point key="canvasLocation" x="528" y="235"/>
</view>
</objects>
</document>

View file

@ -0,0 +1,19 @@
#import "MWMAlert.h"
#include "routing/router.hpp"
#include "routing/routing_callbacks.hpp"
#include "storage/storage.hpp"
#include "storage/storage_defines.hpp"
using MWMDownloadBlock = void (^)(storage::CountriesVec const &, MWMVoidBlock);
@interface MWMAlert (CPP)
+ (MWMAlert *)alert:(routing::RouterResultCode)type;
+ (MWMAlert *)downloaderAlertWithAbsentCountries:(storage::CountriesSet const &)countries
code:(routing::RouterResultCode)code
cancelBlock:(MWMVoidBlock)cancelBlock
downloadBlock:(MWMDownloadBlock)downloadBlock
downloadCompleteBlock:(MWMVoidBlock)downloadCompleteBlock;
@end

View file

@ -0,0 +1,51 @@
@class MWMAlertViewController;
@interface MWMAlert : UIView
@property(weak, nonatomic) MWMAlertViewController *alertController;
+ (MWMAlert *)locationAlertWithCancelBlock:(MWMVoidBlock)cancelBlock;
+ (MWMAlert *)routingDisclaimerAlertWithOkBlock:(MWMVoidBlock)block;
+ (MWMAlert *)disabledLocationAlert;
+ (MWMAlert *)locationServicesDisabledAlert;
+ (MWMAlert *)noWiFiAlertWithOkBlock:(MWMVoidBlock)okBlock andCancelBlock:(MWMVoidBlock)cancelBlock;
+ (MWMAlert *)noConnectionAlert;
+ (MWMAlert *)deleteMapProhibitedAlert;
+ (MWMAlert *)unsavedEditsAlertWithOkBlock:(MWMVoidBlock)okBlock;
+ (MWMAlert *)locationServiceNotSupportedAlert;
+ (MWMAlert *)incorrectFeaturePositionAlert;
+ (MWMAlert *)notEnoughSpaceAlert;
+ (MWMAlert *)invalidUserNameOrPasswordAlert;
+ (MWMAlert *)point2PointAlertWithOkBlock:(MWMVoidBlock)okBlock needToRebuild:(BOOL)needToRebuild;
+ (MWMAlert *)downloaderNoConnectionAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock;
+ (MWMAlert *)downloaderNotEnoughSpaceAlert;
+ (MWMAlert *)downloaderInternalErrorAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock;
+ (MWMAlert *)placeDoesntExistAlertWithBlock:(MWMStringBlock)block;
+ (MWMAlert *)resetChangesAlertWithBlock:(MWMVoidBlock)block;
+ (MWMAlert *)deleteFeatureAlertWithBlock:(MWMVoidBlock)block;
+ (MWMAlert *)personalInfoWarningAlertWithBlock:(MWMVoidBlock)block;
+ (MWMAlert *)trackWarningAlertWithCancelBlock:(MWMVoidBlock)block;
+ (MWMAlert *)infoAlert:(NSString *)title text:(NSString *)text;
+ (MWMAlert *)createBookmarkCategoryAlertWithMaxCharacterNum:(NSUInteger)max
minCharacterNum:(NSUInteger)min
callback:(MWMCheckStringBlock)callback;
+ (MWMAlert *)spinnerAlertWithTitle:(NSString *)title cancel:(MWMVoidBlock)cancel;
+ (MWMAlert *)bookmarkConversionErrorAlert;
+ (MWMAlert *)tagsLoadingErrorAlertWithOkBlock:okBlock cancelBlock:cancelBlock;
+ (MWMAlert *)bugReportAlertWithTitle:(NSString *)title;
+ (MWMAlert *)defaultAlertWithTitle:(NSString *)title
message:(NSString *)message
rightButtonTitle:(NSString *)rightButtonTitle
leftButtonTitle:(NSString *)leftButtonTitle
rightButtonAction:(MWMVoidBlock)action;
- (void)close:(MWMVoidBlock)completion;
- (void)setNeedsCloseAlertAfterEnterBackground;
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation;
- (void)rotate:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
- (void)addControllerViewToWindow;
@end

View file

@ -0,0 +1,234 @@
#import "MWMAlert+CPP.h"
#import "MWMAlertViewController.h"
#import "MWMDefaultAlert.h"
#import "MWMDownloadTransitMapAlert.h"
#import "MWMLocationAlert.h"
#import "MWMPlaceDoesntExistAlert.h"
#import "MWMRoutingDisclaimerAlert.h"
#import "SwiftBridge.h"
@implementation MWMAlert
+ (MWMAlert *)locationAlertWithCancelBlock:(MWMVoidBlock)cancelBlock {
return [MWMLocationAlert alertWithCancelBlock:cancelBlock];
}
+ (MWMAlert *)point2PointAlertWithOkBlock:(MWMVoidBlock)block needToRebuild:(BOOL)needToRebuild {
return [MWMDefaultAlert point2PointAlertWithOkBlock:block needToRebuild:needToRebuild];
}
+ (MWMAlert *)routingDisclaimerAlertWithOkBlock:(MWMVoidBlock)block {
return [MWMRoutingDisclaimerAlert alertWithOkBlock:block];
}
+ (MWMAlert *)disabledLocationAlert {
return [MWMDefaultAlert disabledLocationAlert];
}
+ (MWMAlert *)locationServicesDisabledAlert {
return [LocationServicesDisabledAlert alert];
}
+ (MWMAlert *)noWiFiAlertWithOkBlock:(MWMVoidBlock)okBlock andCancelBlock:(MWMVoidBlock)cancelBlock {
return [MWMDefaultAlert noWiFiAlertWithOkBlock:okBlock andCancelBlock:cancelBlock];
}
+ (MWMAlert *)noConnectionAlert {
return [MWMDefaultAlert noConnectionAlert];
}
+ (MWMAlert *)deleteMapProhibitedAlert {
return [MWMDefaultAlert deleteMapProhibitedAlert];
}
+ (MWMAlert *)unsavedEditsAlertWithOkBlock:(MWMVoidBlock)okBlock {
return [MWMDefaultAlert unsavedEditsAlertWithOkBlock:okBlock];
}
+ (MWMAlert *)locationServiceNotSupportedAlert {
return [MWMDefaultAlert locationServiceNotSupportedAlert];
}
+ (MWMAlert *)downloaderAlertWithAbsentCountries:(storage::CountriesSet const &)countries
code:(routing::RouterResultCode)code
cancelBlock:(MWMVoidBlock)cancelBlock
downloadBlock:(MWMDownloadBlock)downloadBlock
downloadCompleteBlock:(MWMVoidBlock)downloadCompleteBlock {
return [MWMDownloadTransitMapAlert downloaderAlertWithMaps:countries
code:code
cancelBlock:cancelBlock
downloadBlock:downloadBlock
downloadCompleteBlock:downloadCompleteBlock];
}
+ (MWMAlert *)alert:(routing::RouterResultCode)type {
switch (type) {
case routing::RouterResultCode::NoCurrentPosition:
return [MWMDefaultAlert noCurrentPositionAlert];
case routing::RouterResultCode::StartPointNotFound:
return [MWMDefaultAlert startPointNotFoundAlert];
case routing::RouterResultCode::EndPointNotFound:
return [MWMDefaultAlert endPointNotFoundAlert];
case routing::RouterResultCode::PointsInDifferentMWM:
return [MWMDefaultAlert pointsInDifferentMWMAlert];
case routing::RouterResultCode::TransitRouteNotFoundNoNetwork:
return [MWMDefaultAlert routeNotFoundNoPublicTransportAlert];
case routing::RouterResultCode::TransitRouteNotFoundTooLongPedestrian:
return [MWMDefaultAlert routeNotFoundTooLongPedestrianAlert];
case routing::RouterResultCode::RouteNotFoundRedressRouteError:
case routing::RouterResultCode::RouteNotFound:
case routing::RouterResultCode::InconsistentMWMandRoute:
return [MWMDefaultAlert routeNotFoundAlert];
case routing::RouterResultCode::RouteFileNotExist:
case routing::RouterResultCode::FileTooOld:
return [MWMDefaultAlert routeFileNotExistAlert];
case routing::RouterResultCode::InternalError:
return [MWMDefaultAlert internalRoutingErrorAlert];
case routing::RouterResultCode::Cancelled:
case routing::RouterResultCode::NoError:
case routing::RouterResultCode::HasWarnings:
case routing::RouterResultCode::NeedMoreMaps:
return nil;
case routing::RouterResultCode::IntermediatePointNotFound:
return [MWMDefaultAlert intermediatePointNotFoundAlert];
}
}
+ (MWMAlert *)incorrectFeaturePositionAlert {
return [MWMDefaultAlert incorrectFeaturePositionAlert];
}
+ (MWMAlert *)notEnoughSpaceAlert {
return [MWMDefaultAlert notEnoughSpaceAlert];
}
+ (MWMAlert *)invalidUserNameOrPasswordAlert {
return [MWMDefaultAlert invalidUserNameOrPasswordAlert];
}
+ (MWMAlert *)downloaderNoConnectionAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock {
return [MWMDefaultAlert downloaderNoConnectionAlertWithOkBlock:okBlock cancelBlock:cancelBlock];
}
+ (MWMAlert *)downloaderNotEnoughSpaceAlert {
return [MWMDefaultAlert downloaderNotEnoughSpaceAlert];
}
+ (MWMAlert *)downloaderInternalErrorAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock {
return [MWMDefaultAlert downloaderInternalErrorAlertWithOkBlock:okBlock cancelBlock:cancelBlock];
}
+ (MWMAlert *)placeDoesntExistAlertWithBlock:(MWMStringBlock)block {
return [MWMPlaceDoesntExistAlert alertWithBlock:block];
}
+ (MWMAlert *)resetChangesAlertWithBlock:(MWMVoidBlock)block {
return [MWMDefaultAlert resetChangesAlertWithBlock:block];
}
+ (MWMAlert *)deleteFeatureAlertWithBlock:(MWMVoidBlock)block {
return [MWMDefaultAlert deleteFeatureAlertWithBlock:block];
}
+ (MWMAlert *)personalInfoWarningAlertWithBlock:(MWMVoidBlock)block {
return [MWMDefaultAlert personalInfoWarningAlertWithBlock:block];
}
+ (MWMAlert *)trackWarningAlertWithCancelBlock:(MWMVoidBlock)block {
return [MWMDefaultAlert trackWarningAlertWithCancelBlock:block];
}
+ (MWMAlert *)infoAlert:(NSString *)title text:(NSString *)text {
return [MWMDefaultAlert infoAlert:title text:text];
}
+ (MWMAlert *)createBookmarkCategoryAlertWithMaxCharacterNum:(NSUInteger)max
minCharacterNum:(NSUInteger)min
callback:(MWMCheckStringBlock)callback {
return [MWMBCCreateCategoryAlert alertWithMaxCharachersNum:max minCharactersNum:min callback:callback];
}
+ (MWMAlert *)spinnerAlertWithTitle:(NSString *)title cancel:(MWMVoidBlock)cancel {
return [MWMSpinnerAlert alertWithTitle:title cancel:cancel];
}
+ (MWMAlert *)bookmarkConversionErrorAlert {
return [MWMDefaultAlert bookmarkConversionErrorAlert];
}
+ (MWMAlert *)tagsLoadingErrorAlertWithOkBlock:okBlock cancelBlock:cancelBlock {
return [MWMDefaultAlert tagsLoadingErrorAlertWithOkBlock:okBlock cancelBlock:cancelBlock];
}
+ (MWMAlert *)bugReportAlertWithTitle:(NSString *)title {
return [MWMDefaultAlert bugReportAlertWithTitle:title];
}
+ (MWMAlert *)defaultAlertWithTitle:(NSString *)title
message:(NSString *)message
rightButtonTitle:(NSString *)rightButtonTitle
leftButtonTitle:(NSString *)leftButtonTitle
rightButtonAction:(MWMVoidBlock)action {
return [MWMDefaultAlert defaultAlertWithTitle:title
message:message
rightButtonTitle:rightButtonTitle
leftButtonTitle:leftButtonTitle
rightButtonAction:action
log:nil];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
// Should override this method if you want custom relayout after rotation.
}
- (void)close:(MWMVoidBlock)completion {
[self.alertController closeAlert:completion];
}
- (void)setNeedsCloseAlertAfterEnterBackground {
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(applicationDidEnterBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self];
}
- (void)applicationDidEnterBackground {
// Should close alert when application entered background.
[self close:nil];
}
- (void)rotate:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if ([self respondsToSelector:@selector(willRotateToInterfaceOrientation:)])
[self willRotateToInterfaceOrientation:toInterfaceOrientation];
}
- (void)addControllerViewToWindow {
UIWindow *window = UIApplication.sharedApplication.delegate.window;
UIView *view = self.alertController.view;
[window addSubview:view];
view.frame = window.bounds;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.frame = self.superview.bounds;
[super layoutSubviews];
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (self.traitCollection.userInterfaceStyle != previousTraitCollection.userInterfaceStyle) {
[self updateViewStyle:self];
}
}
- (void)updateViewStyle:(UIView *)view {
if (!view)
return;
for (UIView *subview in view.subviews)
[self updateViewStyle:subview];
[view applyTheme];
}
@end

View file

@ -0,0 +1,151 @@
@objc(MWMBCCreateCategoryAlert)
final class BCCreateCategoryAlert: MWMAlert {
private enum State {
case valid
case tooFewSymbols
case tooManySymbols
case nameAlreadyExists
}
@IBOutlet private(set) weak var textField: UITextField!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var textFieldContainer: UIView!
@IBOutlet private weak var centerHorizontaly: NSLayoutConstraint!
@IBOutlet private weak var errorLabel: UILabel!
@IBOutlet private weak var charactersCountLabel: UILabel!
@IBOutlet private weak var rightButton: UIButton!
private var maxCharactersNum: UInt?
private var minCharactersNum: UInt?
private var callback: MWMCheckStringBlock?
@objc static func alert(maxCharachersNum: UInt,
minCharactersNum: UInt,
callback: @escaping MWMCheckStringBlock) -> BCCreateCategoryAlert? {
guard let alert = Bundle.main.loadNibNamed(className(), owner: nil, options: nil)?.first
as? BCCreateCategoryAlert else {
assertionFailure()
return nil
}
alert.titleLabel.text = L("bookmarks_create_new_group")
let text = L("create").capitalized
for s in [.normal, .highlighted, .disabled] as [UIControl.State] {
alert.rightButton.setTitle(text, for: s)
}
alert.maxCharactersNum = maxCharachersNum
alert.minCharactersNum = minCharactersNum
alert.callback = callback
alert.process(state: .tooFewSymbols)
alert.formatCharactersCountText()
MWMKeyboard.add(alert)
return alert
}
@IBAction private func leftButtonTap() {
MWMKeyboard.remove(self)
close(nil)
}
@IBAction private func rightButtonTap() {
guard let callback = callback, let text = textField.text else {
assertionFailure()
return
}
if callback(text) {
MWMKeyboard.remove(self)
close(nil)
} else {
process(state: .nameAlreadyExists)
}
}
@IBAction private func editingChanged(sender: UITextField) {
formatCharactersCountText()
process(state: checkState())
}
private func checkState() -> State {
let count = textField.text!.count
if count <= minCharactersNum! {
return .tooFewSymbols
}
if count > maxCharactersNum! {
return .tooManySymbols
}
return .valid
}
private func process(state: State) {
let style: TextColorStyleSheet
switch state {
case .valid:
style = .blackHint
rightButton.isEnabled = true
errorLabel.isHidden = true
case .tooFewSymbols:
style = .blackHint
errorLabel.isHidden = true
rightButton.isEnabled = false
case .tooManySymbols:
style = .buttonRed
errorLabel.isHidden = false
errorLabel.text = L("bookmarks_error_title_list_name_too_long")
rightButton.isEnabled = false
case .nameAlreadyExists:
style = .buttonRed
errorLabel.isHidden = false
errorLabel.text = L("bookmarks_error_title_list_name_already_taken")
rightButton.isEnabled = false
}
charactersCountLabel.setFontStyleAndApply(style)
textFieldContainer.layer.borderColor = charactersCountLabel.textColor.cgColor
}
private func formatCharactersCountText() {
let count = textField.text!.count
charactersCountLabel.text = String(count) + " / " + String(maxCharactersNum!)
}
}
extension BCCreateCategoryAlert: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if checkState() == .valid {
rightButtonTap()
}
return true
}
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
let str = textField.text as NSString?
let newStr = str?.replacingCharacters(in: range, with: string)
guard let count = newStr?.count else {
return true
}
if count > maxCharactersNum! + 1 {
return false
}
return true
}
}
extension BCCreateCategoryAlert: MWMKeyboardObserver {
func onKeyboardAnimation() {
centerHorizontaly.constant = -MWMKeyboard.keyboardHeight() / 2
layoutIfNeeded()
}
func onKeyboardWillAnimate() {
setNeedsLayout()
}
}

View file

@ -0,0 +1,202 @@
<?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="retina4_7" 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"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="QQB-Ml-wwB" customClass="MWMBCCreateCategoryAlert" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QG2-Qb-EKf" userLabel="ContainerView">
<rect key="frame" x="47.5" y="248" width="280" height="171.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4T3-au-6h5" userLabel="Title">
<rect key="frame" x="20" y="20" width="240" height="21.5"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="4gN-jJ-fkn"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="dpW-YS-s4x"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
<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="medium18:blackPrimaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="bookmarks_create_new_group"/>
</userDefinedRuntimeAttributes>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="otE-Ct-TPM">
<rect key="frame" x="16" y="61.5" width="248" height="28"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="xlp-ke-FxI">
<rect key="frame" x="4" y="0.0" width="240" height="28"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="T3H-0E-q2e"/>
</constraints>
<edgeInsets key="layoutMargins" top="4" left="4" bottom="4" right="4"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences" autocorrectionType="no" returnKeyType="done" textContentType="name"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background:regular14:blackPrimaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedPlaceholder" value="bookmark_set_name"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="editingChangedWithSender:" destination="QQB-Ml-wwB" eventType="editingChanged" id="Rab-rj-UaD"/>
<outlet property="delegate" destination="QQB-Ml-wwB" id="7Hk-7k-Biw"/>
</connections>
</textField>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="497-yd-ibe"/>
<constraint firstItem="xlp-ke-FxI" firstAttribute="centerY" secondItem="otE-Ct-TPM" secondAttribute="centerY" id="LK1-93-zgY"/>
<constraint firstAttribute="width" constant="248" id="hLe-SE-FJ2"/>
<constraint firstItem="xlp-ke-FxI" firstAttribute="centerX" secondItem="otE-Ct-TPM" secondAttribute="centerX" id="sOC-II-DCo"/>
<constraint firstItem="xlp-ke-FxI" firstAttribute="height" secondItem="otE-Ct-TPM" secondAttribute="height" id="tj3-TS-dmp"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertViewTextFieldContainer"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uxx-Di-f1Q" userLabel="hDivider">
<rect key="frame" x="0.0" y="127.5" width="280" 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="9Ur-cw-cWk"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Wxi-5V-caq" userLabel="left">
<rect key="frame" x="0.0" y="127.5" width="140" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="140" id="PcO-3k-jAQ"/>
<constraint firstAttribute="height" constant="44" id="sIn-W3-LqB"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="left">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="cancel"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="leftButtonTap" destination="QQB-Ml-wwB" eventType="touchUpInside" id="q1X-dI-dSs"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eg6-8K-8yh" userLabel="right">
<rect key="frame" x="140" y="127.5" width="140" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="Pk8-S5-m8D"/>
<constraint firstAttribute="width" constant="140" id="m9E-k8-sQh"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<state key="normal" title="right">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="create"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="FlatNormalTransButton:medium17"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="rightButtonTap" destination="QQB-Ml-wwB" eventType="touchUpInside" id="HT2-dB-W2f"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QQD-2O-DHw" userLabel="vDivider">
<rect key="frame" x="139.5" y="127.5" width="1" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="Lhw-ff-5Vq"/>
<constraint firstAttribute="height" constant="44" id="eaZ-L1-nnD"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This name is too long" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dX9-2y-ZEd" userLabel="Error label">
<rect key="frame" x="16" y="95.5" width="112" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="W4K-74-Pug"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="198" id="uYL-oU-tYf"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular11:buttonRedText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="69 / 60" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WKj-KX-xnU" userLabel="Count label">
<rect key="frame" x="225.5" y="95.5" width="38.5" height="20"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="aJd-wn-gwl"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular11:buttonRedText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="4T3-au-6h5" firstAttribute="top" secondItem="QG2-Qb-EKf" secondAttribute="top" constant="20" id="2l5-Sp-9hA"/>
<constraint firstItem="dX9-2y-ZEd" firstAttribute="top" secondItem="otE-Ct-TPM" secondAttribute="bottom" constant="6" id="3N9-5v-z4Y"/>
<constraint firstAttribute="bottom" secondItem="Wxi-5V-caq" secondAttribute="bottom" id="3Ze-v3-100"/>
<constraint firstAttribute="trailing" secondItem="eg6-8K-8yh" secondAttribute="trailing" id="6oa-ch-DMu"/>
<constraint firstItem="Wxi-5V-caq" firstAttribute="leading" secondItem="QG2-Qb-EKf" secondAttribute="leading" id="6v3-kv-oqt"/>
<constraint firstItem="uxx-Di-f1Q" firstAttribute="top" secondItem="dX9-2y-ZEd" secondAttribute="bottom" constant="12" id="7Bf-UJ-7mU"/>
<constraint firstItem="uxx-Di-f1Q" firstAttribute="centerX" secondItem="QG2-Qb-EKf" secondAttribute="centerX" id="CSk-xb-Fd3"/>
<constraint firstAttribute="width" constant="280" id="CwP-uI-Ukp"/>
<constraint firstItem="uxx-Di-f1Q" firstAttribute="width" secondItem="QG2-Qb-EKf" secondAttribute="width" id="Fcb-Hq-n6a"/>
<constraint firstItem="otE-Ct-TPM" firstAttribute="top" secondItem="4T3-au-6h5" secondAttribute="bottom" constant="20" id="NAI-NR-TFE"/>
<constraint firstItem="otE-Ct-TPM" firstAttribute="centerX" secondItem="QG2-Qb-EKf" secondAttribute="centerX" id="Y2k-u1-PgF"/>
<constraint firstItem="Wxi-5V-caq" firstAttribute="top" secondItem="uxx-Di-f1Q" secondAttribute="bottom" constant="-1" id="Yo8-tt-8Q6"/>
<constraint firstAttribute="trailing" secondItem="WKj-KX-xnU" secondAttribute="trailing" constant="16" id="aRt-r1-EwI"/>
<constraint firstItem="4T3-au-6h5" firstAttribute="centerX" secondItem="QG2-Qb-EKf" secondAttribute="centerX" id="cTf-xg-SpW"/>
<constraint firstItem="dX9-2y-ZEd" firstAttribute="leading" secondItem="QG2-Qb-EKf" secondAttribute="leading" constant="16" id="gyf-Hq-FLl"/>
<constraint firstItem="QQD-2O-DHw" firstAttribute="centerX" secondItem="QG2-Qb-EKf" secondAttribute="centerX" id="sUU-uz-fhn"/>
<constraint firstAttribute="bottom" secondItem="QQD-2O-DHw" secondAttribute="bottom" id="vgE-JK-Ong"/>
<constraint firstAttribute="bottom" secondItem="eg6-8K-8yh" secondAttribute="bottom" id="vzg-UL-heQ"/>
<constraint firstItem="WKj-KX-xnU" firstAttribute="top" secondItem="otE-Ct-TPM" secondAttribute="bottom" constant="6" id="zK8-7y-xuI"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertView"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="QG2-Qb-EKf" firstAttribute="centerY" secondItem="QQB-Ml-wwB" secondAttribute="centerY" id="od7-2i-rqy"/>
<constraint firstItem="QG2-Qb-EKf" firstAttribute="centerX" secondItem="QQB-Ml-wwB" secondAttribute="centerX" id="zjO-HS-7Up"/>
</constraints>
<viewLayoutGuide key="safeArea" id="oQA-N9-AkF"/>
<connections>
<outlet property="centerHorizontaly" destination="od7-2i-rqy" id="lVF-DA-PUg"/>
<outlet property="charactersCountLabel" destination="WKj-KX-xnU" id="X0L-zL-ZcF"/>
<outlet property="errorLabel" destination="dX9-2y-ZEd" id="XLx-WJ-h4h"/>
<outlet property="rightButton" destination="eg6-8K-8yh" id="YGF-Zh-u1y"/>
<outlet property="textField" destination="xlp-ke-FxI" id="pBV-Ne-ekt"/>
<outlet property="textFieldContainer" destination="otE-Ct-TPM" id="Nf8-ly-qdn"/>
<outlet property="titleLabel" destination="4T3-au-6h5" id="ye3-HE-xqN"/>
</connections>
<point key="canvasLocation" x="304.5" y="171.5"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
</resources>
</document>

View file

@ -0,0 +1,45 @@
#import "MWMAlert.h"
@interface MWMDefaultAlert : MWMAlert
+ (instancetype)routeNotFoundAlert;
+ (instancetype)routeNotFoundNoPublicTransportAlert;
+ (instancetype)routeNotFoundTooLongPedestrianAlert;
+ (instancetype)routeFileNotExistAlert;
+ (instancetype)endPointNotFoundAlert;
+ (instancetype)startPointNotFoundAlert;
+ (instancetype)intermediatePointNotFoundAlert;
+ (instancetype)internalRoutingErrorAlert;
+ (instancetype)incorrectFeaturePositionAlert;
+ (instancetype)notEnoughSpaceAlert;
+ (instancetype)invalidUserNameOrPasswordAlert;
+ (instancetype)noCurrentPositionAlert;
+ (instancetype)pointsInDifferentMWMAlert;
+ (instancetype)disabledLocationAlert;
+ (instancetype)noWiFiAlertWithOkBlock:(MWMVoidBlock)okBlock andCancelBlock:(MWMVoidBlock)cancelBlock;
+ (instancetype)noConnectionAlert;
+ (instancetype)deleteMapProhibitedAlert;
+ (instancetype)unsavedEditsAlertWithOkBlock:(MWMVoidBlock)okBlock;
+ (instancetype)locationServiceNotSupportedAlert;
+ (instancetype)point2PointAlertWithOkBlock:(MWMVoidBlock)okBlock needToRebuild:(BOOL)needToRebuild;
+ (instancetype)downloaderNoConnectionAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock;
+ (instancetype)downloaderNotEnoughSpaceAlert;
+ (instancetype)downloaderInternalErrorAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock;
+ (instancetype)resetChangesAlertWithBlock:(MWMVoidBlock)block;
+ (instancetype)deleteFeatureAlertWithBlock:(MWMVoidBlock)block;
+ (instancetype)personalInfoWarningAlertWithBlock:(MWMVoidBlock)block;
+ (instancetype)trackWarningAlertWithCancelBlock:(MWMVoidBlock)block;
+ (instancetype)infoAlert:(NSString *)title text:(NSString *)text;
+ (instancetype)convertBookmarksWithCount:(NSUInteger)count okBlock:(MWMVoidBlock)okBlock;
+ (instancetype)bookmarkConversionErrorAlert;
+ (instancetype)tagsLoadingErrorAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock;
+ (instancetype)bugReportAlertWithTitle:(NSString *)title;
+ (instancetype)defaultAlertWithTitle:(NSString *)title
message:(NSString *)message
rightButtonTitle:(NSString *)rightButtonTitle
leftButtonTitle:(NSString *)leftButtonTitle
rightButtonAction:(MWMVoidBlock)action
log:(NSString *)log;
@end

View file

@ -0,0 +1,404 @@
#import "MWMDefaultAlert.h"
#import "SwiftBridge.h"
#include <CoreApi/Framework.h>
static CGFloat const kDividerTopConstant = -8.;
@interface MWMDefaultAlert ()
@property(weak, nonatomic) IBOutlet UILabel *messageLabel;
@property(weak, nonatomic) IBOutlet UIButton *rightButton;
@property(weak, nonatomic) IBOutlet UIButton *leftButton;
@property(weak, nonatomic) IBOutlet UILabel *titleLabel;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint *rightButtonWidth;
@property(copy, nonatomic) MWMVoidBlock leftButtonAction;
@property(copy, nonatomic, readwrite) MWMVoidBlock rightButtonAction;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint *dividerTop;
@property(weak, nonatomic) IBOutlet UIView *vDivider;
@end
static NSString *const kDefaultAlertNibName = @"MWMDefaultAlert";
@implementation MWMDefaultAlert
+ (instancetype)routeFileNotExistAlert {
return [self defaultAlertWithTitle:L(@"dialog_routing_download_files")
message:L(@"dialog_routing_download_and_update_all")
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Route File Not Exist Alert"];
}
+ (instancetype)routeNotFoundAlert {
return [self defaultAlertWithTitle:L(@"dialog_routing_unable_locate_route")
message:L(@"dialog_routing_change_start_or_end")
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Route File Not Exist Alert"];
}
+ (instancetype)routeNotFoundNoPublicTransportAlert {
return [self defaultAlertWithTitle:L(@"transit_not_found")
message:nil
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"transit_not_found"];
}
+ (instancetype)routeNotFoundTooLongPedestrianAlert {
return [self defaultAlertWithTitle:L(@"dialog_pedestrian_route_is_long_header")
message:L(@"dialog_pedestrian_route_is_long_message")
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Long Pedestrian Route Alert"];
}
+ (instancetype)locationServiceNotSupportedAlert {
return [self defaultAlertWithTitle:L(@"current_location_unknown_error_title")
message:L(@"current_location_unknown_error_message")
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Location Service Not Supported Alert"];
}
+ (instancetype)noConnectionAlert {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"common_check_internet_connection_dialog")
message:nil
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"No Connection Alert"];
[alert setNeedsCloseAlertAfterEnterBackground];
return alert;
}
+ (instancetype)deleteMapProhibitedAlert {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"downloader_delete_map")
message:L(@"downloader_delete_map_while_routing_dialog")
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Delete Map Prohibited Alert"];
[alert setNeedsCloseAlertAfterEnterBackground];
return alert;
}
+ (instancetype)unsavedEditsAlertWithOkBlock:(MWMVoidBlock)okBlock {
return [self defaultAlertWithTitle:L(@"please_note")
message:L(@"downloader_delete_map_dialog")
rightButtonTitle:L(@"delete")
leftButtonTitle:L(@"cancel")
rightButtonAction:okBlock
log:@"Editor unsaved changes on delete"];
}
+ (instancetype)noWiFiAlertWithOkBlock:(MWMVoidBlock)okBlock andCancelBlock:(MWMVoidBlock)cancelBlock {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"download_over_mobile_header")
message:L(@"download_over_mobile_message")
rightButtonTitle:L(@"use_cellular_data")
leftButtonTitle:L(@"cancel")
rightButtonAction:okBlock
log:@"No WiFi Alert"];
alert.leftButtonAction = cancelBlock;
[alert setNeedsCloseAlertAfterEnterBackground];
return alert;
}
+ (instancetype)endPointNotFoundAlert {
NSString *message = [NSString
stringWithFormat:@"%@\n\n%@", L(@"dialog_routing_end_not_determined"), L(@"dialog_routing_select_closer_end")];
return [self defaultAlertWithTitle:L(@"dialog_routing_change_end")
message:message
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"End Point Not Found Alert"];
}
+ (instancetype)startPointNotFoundAlert {
NSString *message = [NSString
stringWithFormat:@"%@\n\n%@", L(@"dialog_routing_start_not_determined"), L(@"dialog_routing_select_closer_start")];
return [self defaultAlertWithTitle:L(@"dialog_routing_change_start")
message:message
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Start Point Not Found Alert"];
}
+ (instancetype)intermediatePointNotFoundAlert {
return [self defaultAlertWithTitle:L(@"dialog_routing_change_intermediate")
message:L(@"dialog_routing_intermediate_not_determined")
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Intermediate Point Not Found Alert"];
}
+ (instancetype)internalRoutingErrorAlert {
NSString *message =
[NSString stringWithFormat:@"%@\n\n%@", L(@"dialog_routing_application_error"), L(@"dialog_routing_try_again")];
return [self defaultAlertWithTitle:L(@"dialog_routing_system_error")
message:message
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Internal Routing Error Alert"];
}
+ (instancetype)incorrectFeaturePositionAlert {
return [self defaultAlertWithTitle:L(@"dialog_incorrect_feature_position")
message:L(@"message_invalid_feature_position")
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Incorrect Feature Possition Alert"];
}
+ (instancetype)notEnoughSpaceAlert {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"downloader_no_space_title")
message:L(@"migration_no_space_message")
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Not Enough Space Alert"];
[alert setNeedsCloseAlertAfterEnterBackground];
return alert;
}
+ (instancetype)invalidUserNameOrPasswordAlert {
return [self defaultAlertWithTitle:L(@"invalid_username_or_password")
message:nil
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Invalid User Name or Password Alert"];
}
+ (instancetype)noCurrentPositionAlert {
NSString *message = [NSString stringWithFormat:@"%@\n\n%@", L(@"dialog_routing_error_location_not_found"),
L(@"dialog_routing_location_turn_wifi")];
return [self defaultAlertWithTitle:L(@"dialog_routing_check_gps")
message:message
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"No Current Position Alert"];
}
+ (instancetype)disabledLocationAlert {
MWMVoidBlock action = ^{
GetFramework().SwitchMyPositionNextMode();
};
return [self defaultAlertWithTitle:L(@"dialog_routing_location_turn_on")
message:L(@"dialog_routing_location_unknown_turn_on")
rightButtonTitle:L(@"turn_on")
leftButtonTitle:L(@"later")
rightButtonAction:action
log:@"Disabled Location Alert"];
}
+ (instancetype)pointsInDifferentMWMAlert {
return [self defaultAlertWithTitle:L(@"routing_failed_cross_mwm_building")
message:nil
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Points In Different MWM Alert"];
}
+ (instancetype)point2PointAlertWithOkBlock:(MWMVoidBlock)okBlock needToRebuild:(BOOL)needToRebuild {
if (needToRebuild) {
return [self defaultAlertWithTitle:L(@"p2p_only_from_current")
message:L(@"p2p_reroute_from_current")
rightButtonTitle:L(@"ok")
leftButtonTitle:L(@"cancel")
rightButtonAction:okBlock
log:@"Default Alert"];
} else {
return [self defaultAlertWithTitle:L(@"p2p_only_from_current")
message:nil
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Default Alert"];
}
}
+ (instancetype)downloaderNoConnectionAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"downloader_status_failed")
message:L(@"common_check_internet_connection_dialog")
rightButtonTitle:L(@"downloader_retry")
leftButtonTitle:L(@"cancel")
rightButtonAction:okBlock
log:@"Downloader No Connection Alert"];
alert.leftButtonAction = cancelBlock;
[alert setNeedsCloseAlertAfterEnterBackground];
return alert;
}
+ (instancetype)downloaderNotEnoughSpaceAlert {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"downloader_no_space_title")
message:L(@"downloader_no_space_message")
rightButtonTitle:L(@"close")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Downloader Not Enough Space Alert"];
[alert setNeedsCloseAlertAfterEnterBackground];
return alert;
}
+ (instancetype)downloaderInternalErrorAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"migration_download_error_dialog")
message:nil
rightButtonTitle:L(@"downloader_retry")
leftButtonTitle:L(@"cancel")
rightButtonAction:okBlock
log:@"Downloader Internal Error Alert"];
alert.leftButtonAction = cancelBlock;
[alert setNeedsCloseAlertAfterEnterBackground];
return alert;
}
+ (instancetype)resetChangesAlertWithBlock:(MWMVoidBlock)block {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"editor_reset_edits_message")
message:nil
rightButtonTitle:L(@"editor_reset_edits_button")
leftButtonTitle:L(@"cancel")
rightButtonAction:block
log:@"Reset changes alert"];
return alert;
}
+ (instancetype)deleteFeatureAlertWithBlock:(MWMVoidBlock)block {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"editor_remove_place_message")
message:nil
rightButtonTitle:L(@"editor_remove_place_button")
leftButtonTitle:L(@"cancel")
rightButtonAction:block
log:@"Delete feature alert"];
return alert;
}
+ (instancetype)personalInfoWarningAlertWithBlock:(MWMVoidBlock)block {
NSString *message = [NSString
stringWithFormat:@"%@\n%@", L(@"editor_share_to_all_dialog_message_1"), L(@"editor_share_to_all_dialog_message_2")];
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"editor_share_to_all_dialog_title")
message:message
rightButtonTitle:L(@"editor_report_problem_send_button")
leftButtonTitle:L(@"cancel")
rightButtonAction:block
log:@"Personal info warning alert"];
return alert;
}
+ (instancetype)trackWarningAlertWithCancelBlock:(MWMVoidBlock)block {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"recent_track_background_dialog_title")
message:L(@"recent_track_background_dialog_message")
rightButtonTitle:L(@"off_recent_track_background_button")
leftButtonTitle:L(@"continue_button")
rightButtonAction:block
log:@"Track warning alert"];
return alert;
}
+ (instancetype)infoAlert:(NSString *)title text:(NSString *)text {
return [self defaultAlertWithTitle:title
message:text
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:@"Info Alert"];
}
+ (instancetype)convertBookmarksWithCount:(NSUInteger)count okBlock:(MWMVoidBlock)okBlock {
return [self defaultAlertWithTitle:L(@"bookmarks_detect_title")
message:[NSString stringWithFormat:L(@"bookmarks_detect_message"), count]
rightButtonTitle:L(@"button_convert")
leftButtonTitle:L(@"cancel")
rightButtonAction:okBlock
log:nil];
}
+ (instancetype)bookmarkConversionErrorAlert {
return [self defaultAlertWithTitle:L(@"bookmarks_convert_error_title")
message:L(@"bookmarks_convert_error_message")
rightButtonTitle:L(@"ok")
leftButtonTitle:nil
rightButtonAction:nil
log:nil];
}
+ (instancetype)tagsLoadingErrorAlertWithOkBlock:(MWMVoidBlock)okBlock cancelBlock:(MWMVoidBlock)cancelBlock {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:L(@"title_error_downloading_bookmarks")
message:L(@"tags_loading_error_subtitle")
rightButtonTitle:L(@"downloader_retry")
leftButtonTitle:L(@"cancel")
rightButtonAction:okBlock
log:nil];
alert.leftButtonAction = cancelBlock;
[alert setNeedsCloseAlertAfterEnterBackground];
return alert;
}
+ (instancetype)bugReportAlertWithTitle:(NSString *)title {
MWMDefaultAlert *alert = [self defaultAlertWithTitle:title
message:L(@"bugreport_alert_message")
rightButtonTitle:L(@"report_a_bug")
leftButtonTitle:L(@"cancel")
rightButtonAction:^{ [MailComposer sendBugReportWithTitle:title]; }
log:nil];
[alert setNeedsCloseAlertAfterEnterBackground];
return alert;
}
+ (instancetype)defaultAlertWithTitle:(NSString *)title
message:(NSString *)message
rightButtonTitle:(NSString *)rightButtonTitle
leftButtonTitle:(NSString *)leftButtonTitle
rightButtonAction:(MWMVoidBlock)action
log:(NSString *)log {
if (log) {
LOG(LINFO, ([log UTF8String]));
}
MWMDefaultAlert *alert = [NSBundle.mainBundle loadNibNamed:kDefaultAlertNibName owner:self options:nil].firstObject;
alert.titleLabel.text = title;
alert.messageLabel.text = message;
if (!message) {
alert.dividerTop.constant = kDividerTopConstant;
[alert layoutIfNeeded];
}
[alert.rightButton setTitle:rightButtonTitle forState:UIControlStateNormal];
[alert.rightButton setTitle:rightButtonTitle forState:UIControlStateDisabled];
alert.rightButtonAction = action;
if (leftButtonTitle) {
[alert.leftButton setTitle:leftButtonTitle forState:UIControlStateNormal];
[alert.leftButton setTitle:leftButtonTitle forState:UIControlStateDisabled];
} else {
alert.vDivider.hidden = YES;
alert.leftButton.hidden = YES;
alert.rightButtonWidth.constant = [alert.subviews.firstObject width];
}
return alert;
}
#pragma mark - Actions
- (IBAction)rightButtonTap {
[self close:self.rightButtonAction];
}
- (IBAction)leftButtonTap {
[self close:self.leftButtonAction];
}
@end

View file

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="MWMDefaultAlert" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zso-DD-6SG" userLabel="ContainerView">
<rect key="frame" x="47.5" y="264.5" width="280" height="138.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Grl-z5-2cE" userLabel="Title">
<rect key="frame" x="20" y="20" width="240" height="21.5"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="0SG-Lk-wkv"/>
<constraint firstAttribute="width" constant="240" id="9rm-Av-9eY"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
<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="medium18:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Message" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0D6-oV-Lio" userLabel="Message">
<rect key="frame" x="20" y="53.5" width="240" height="20"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="dG8-qs-ZWQ"/>
<constraint firstAttribute="width" constant="240" id="wbf-hD-V6a"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="f8D-qe-39W" userLabel="hDivider">
<rect key="frame" x="0.0" y="93.5" width="280" 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="HTM-C2-dMq"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="jAg-cp-4Ms" userLabel="left">
<rect key="frame" x="0.0" y="94.5" width="140" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="cancelButton"/>
<constraints>
<constraint firstAttribute="width" constant="140" id="4bW-b0-naB"/>
<constraint firstAttribute="height" constant="44" id="RRQ-jI-El3"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="left">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="ok"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="leftButtonTap" destination="iN0-l3-epB" eventType="touchUpInside" id="xE7-aE-OzM"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5H4-oD-Dex" userLabel="vDivider">
<rect key="frame" x="139" y="94" width="1" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="8r3-72-asP"/>
<constraint firstAttribute="width" constant="1" id="ORs-aP-K10"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RSB-i6-pEP" userLabel="right">
<rect key="frame" x="140" y="94.5" width="140" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="OKButton"/>
<constraints>
<constraint firstAttribute="width" constant="140" id="C3i-gd-pxv"/>
<constraint firstAttribute="height" constant="44" id="dta-cI-rDi"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<state key="normal" title="right">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="ok"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="rightButtonTap" destination="iN0-l3-epB" eventType="touchUpInside" id="UO8-7c-q1i"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.88" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Grl-z5-2cE" firstAttribute="top" secondItem="zso-DD-6SG" secondAttribute="top" constant="20" id="5iy-IL-Non"/>
<constraint firstAttribute="bottom" secondItem="RSB-i6-pEP" secondAttribute="bottom" id="7PX-qC-iuH"/>
<constraint firstItem="jAg-cp-4Ms" firstAttribute="top" secondItem="f8D-qe-39W" secondAttribute="bottom" id="BZX-XF-Cwq"/>
<constraint firstItem="RSB-i6-pEP" firstAttribute="top" secondItem="f8D-qe-39W" secondAttribute="bottom" id="FV6-rs-AaS"/>
<constraint firstAttribute="width" constant="280" id="H9k-u4-6Xx"/>
<constraint firstAttribute="centerX" secondItem="0D6-oV-Lio" secondAttribute="centerX" id="HOk-vG-jd5"/>
<constraint firstItem="0D6-oV-Lio" firstAttribute="top" secondItem="Grl-z5-2cE" secondAttribute="bottom" constant="12" id="QBn-bf-ANB"/>
<constraint firstAttribute="bottom" secondItem="jAg-cp-4Ms" secondAttribute="bottom" id="QhB-d8-4KJ"/>
<constraint firstItem="RSB-i6-pEP" firstAttribute="leading" secondItem="5H4-oD-Dex" secondAttribute="trailing" id="SbQ-E0-P7N"/>
<constraint firstAttribute="centerX" secondItem="f8D-qe-39W" secondAttribute="centerX" id="TuK-ia-Gl9"/>
<constraint firstItem="f8D-qe-39W" firstAttribute="top" secondItem="0D6-oV-Lio" secondAttribute="bottom" constant="20" id="XWN-lh-LsI"/>
<constraint firstAttribute="bottom" secondItem="5H4-oD-Dex" secondAttribute="bottom" constant="0.5" id="el1-Te-FrO"/>
<constraint firstAttribute="width" secondItem="f8D-qe-39W" secondAttribute="width" id="rtN-ma-JgY"/>
<constraint firstItem="jAg-cp-4Ms" firstAttribute="leading" secondItem="zso-DD-6SG" secondAttribute="leading" id="x4L-bc-J3N"/>
<constraint firstAttribute="trailing" secondItem="RSB-i6-pEP" secondAttribute="trailing" id="zAm-vx-0g5"/>
<constraint firstAttribute="centerX" secondItem="Grl-z5-2cE" secondAttribute="centerX" id="zap-Oc-W3i"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertView"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="centerY" secondItem="zso-DD-6SG" secondAttribute="centerY" id="2Yk-mx-NtS"/>
<constraint firstAttribute="centerX" secondItem="zso-DD-6SG" secondAttribute="centerX" id="rvU-lH-fCc"/>
</constraints>
<viewLayoutGuide key="safeArea" id="4wG-7A-b6h"/>
<connections>
<outlet property="dividerTop" destination="XWN-lh-LsI" id="Ken-WZ-bMm"/>
<outlet property="leftButton" destination="jAg-cp-4Ms" id="UpD-3H-PmN"/>
<outlet property="messageLabel" destination="0D6-oV-Lio" id="1pY-jt-AFL"/>
<outlet property="rightButton" destination="RSB-i6-pEP" id="DC9-9z-U2K"/>
<outlet property="rightButtonWidth" destination="C3i-gd-pxv" id="YMb-U5-4iJ"/>
<outlet property="titleLabel" destination="Grl-z5-2cE" id="p6A-DJ-xjR"/>
<outlet property="vDivider" destination="5H4-oD-Dex" id="7xY-ln-bYK"/>
</connections>
<point key="canvasLocation" x="305" y="172"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
</resources>
</document>

View file

@ -0,0 +1,14 @@
#import "MWMDefaultAlert.h"
@interface MWMDefaultAlert (Protected)
+ (nonnull instancetype)defaultAlertWithTitle:(nonnull NSString *)title
message:(nullable NSString *)message
rightButtonTitle:(nonnull NSString *)rightButtonTitle
leftButtonTitle:(nullable NSString *)leftButtonTitle
rightButtonAction:(nullable MWMVoidBlock)action
log:(nullable NSString *)log;
@property(copy, nonatomic, readonly, nullable) MWMVoidBlock rightButtonAction;
@end

View file

@ -0,0 +1,16 @@
#import "MWMAlert+CPP.h"
#include "routing/routing_callbacks.hpp"
#include "storage/storage_defines.hpp"
@interface MWMDownloadTransitMapAlert : MWMAlert
+ (instancetype)downloaderAlertWithMaps:(storage::CountriesSet const &)countries
code:(routing::RouterResultCode)code
cancelBlock:(MWMVoidBlock)cancelBlock
downloadBlock:(MWMDownloadBlock)downloadBlock
downloadCompleteBlock:(MWMVoidBlock)downloadCompleteBlock;
- (void)showDownloadDetail:(UIButton *)sender;
@end

View file

@ -0,0 +1,352 @@
#import "MWMDownloadTransitMapAlert.h"
#import "MWMCircularProgress.h"
#import "MWMDownloaderDialogCell.h"
#import "MWMDownloaderDialogHeader.h"
#import "MWMStorage+UI.h"
#import "SwiftBridge.h"
#include <CoreApi/Framework.h>
#include "platform/downloader_defines.hpp"
namespace
{
NSString * const kDownloadTransitMapAlertNibName = @"MWMDownloadTransitMapAlert";
CGFloat const kCellHeight = 32.;
CGFloat const kHeaderHeight = 44.;
CGFloat const kMinimumOffset = 20.;
CGFloat const kAnimationDuration = .05;
} // namespace
@interface MWMDownloadTransitMapAlert () <UITableViewDataSource, UITableViewDelegate, MWMStorageObserver, MWMCircularProgressProtocol>
@property(copy, nonatomic) MWMVoidBlock cancelBlock;
@property(copy, nonatomic) MWMDownloadBlock downloadBlock;
@property(copy, nonatomic) MWMVoidBlock downloadCompleteBlock;
@property (nonatomic) MWMCircularProgress * progress;
@property (weak, nonatomic) IBOutlet UIView * containerView;
@property (weak, nonatomic) IBOutlet UILabel * titleLabel;
@property (weak, nonatomic) IBOutlet UILabel * messageLabel;
@property (weak, nonatomic) IBOutlet UITableView * dialogsTableView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint * tableViewHeight;
@property (nonatomic) MWMDownloaderDialogHeader * listHeader;
@property (nonatomic) BOOL listExpanded;
@property (weak, nonatomic) IBOutlet UIView * progressWrapper;
@property (weak, nonatomic) IBOutlet UIView * hDivider;
@property (weak, nonatomic) IBOutlet UIView * vDivider;
@property (weak, nonatomic) IBOutlet UIButton * leftButton;
@property (weak, nonatomic) IBOutlet UIButton * rightButton;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint * dialogsBottomOffset;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint * progressWrapperBottomOffset;
@property (copy, nonatomic) NSArray<NSString *> * countriesNames;
@property (copy, nonatomic) NSString * countriesSize;
@end
@implementation MWMDownloadTransitMapAlert
{
storage::CountriesVec m_countries;
}
+ (instancetype)downloaderAlertWithMaps:(storage::CountriesSet const &)countries
code:(routing::RouterResultCode)code
cancelBlock:(MWMVoidBlock)cancelBlock
downloadBlock:(MWMDownloadBlock)downloadBlock
downloadCompleteBlock:(MWMVoidBlock)downloadCompleteBlock
{
MWMDownloadTransitMapAlert * alert = [self alertWithCountries:countries];
switch (code)
{
case routing::RouterResultCode::InconsistentMWMandRoute:
case routing::RouterResultCode::RouteNotFound:
case routing::RouterResultCode::RouteFileNotExist:
alert.titleLabel.text = L(@"dialog_routing_download_files");
alert.messageLabel.text = L(@"dialog_routing_download_and_update_all");
break;
case routing::RouterResultCode::FileTooOld:
alert.titleLabel.text = L(@"dialog_routing_download_files");
alert.messageLabel.text = L(@"dialog_routing_download_and_update_maps");
break;
case routing::RouterResultCode::NeedMoreMaps:
alert.titleLabel.text = L(@"dialog_routing_download_and_build_cross_route");
alert.messageLabel.text = L(@"dialog_routing_download_cross_route");
break;
default:
NSAssert(false, @"Incorrect code!");
break;
}
alert.cancelBlock = cancelBlock;
alert.downloadBlock = downloadBlock;
alert.downloadCompleteBlock = downloadCompleteBlock;
return alert;
}
+ (instancetype)alertWithCountries:(storage::CountriesSet const &)countries
{
NSAssert(!countries.empty(), @"countries can not be empty.");
MWMDownloadTransitMapAlert * alert =
[NSBundle.mainBundle loadNibNamed:kDownloadTransitMapAlertNibName owner:nil options:nil]
.firstObject;
alert->m_countries = storage::CountriesVec(countries.begin(), countries.end());
[alert configure];
[alert updateCountriesList];
[[MWMStorage sharedStorage] addObserver:alert];
return alert;
}
- (void)configure
{
[self.dialogsTableView registerNibWithCellClass:[MWMDownloaderDialogCell class]];
self.listExpanded = NO;
CALayer * containerViewLayer = self.containerView.layer;
containerViewLayer.shouldRasterize = YES;
containerViewLayer.rasterizationScale = [[UIScreen mainScreen] scale];
[self.dialogsTableView reloadData];
}
- (void)updateCountriesList
{
auto const & s = GetFramework().GetStorage();
m_countries.erase(
remove_if(m_countries.begin(), m_countries.end(),
[&s](storage::CountryId const & countryId) { return s.HasLatestVersion(countryId); }),
m_countries.end());
NSMutableArray<NSString *> * titles = [@[] mutableCopy];
MwmSize totalSize = 0;
for (auto const & countryId : m_countries)
{
storage::NodeAttrs attrs;
s.GetNodeAttrs(countryId, attrs);
[titles addObject:@(attrs.m_nodeLocalName.c_str())];
totalSize += attrs.m_mwmSize;
}
self.countriesNames = titles;
self.countriesSize = formattedSize(totalSize);
}
#pragma mark - MWMCircularProgressProtocol
- (void)progressButtonPressed:(nonnull MWMCircularProgress *)progress
{
for (auto const & countryId : m_countries)
[[MWMStorage sharedStorage] cancelDownloadNode:@(countryId.c_str())];
[self cancelButtonTap];
}
#pragma mark - MWMStorageObserver
- (void)processCountryEvent:(NSString *)countryId
{
if (find(m_countries.begin(), m_countries.end(), countryId.UTF8String) == m_countries.end())
return;
if (self.rightButton.hidden)
{
auto const & s = GetFramework().GetStorage();
auto const & p = GetFramework().GetDownloadingPolicy();
if (s.CheckFailedCountries(m_countries))
{
if (p.IsAutoRetryDownloadFailed())
[self close:nil];
return;
}
auto const overallProgress = s.GetOverallProgress(m_countries);
// Test if downloading has finished by comparing downloaded and total sizes.
if (overallProgress.m_bytesDownloaded == overallProgress.m_bytesTotal)
[self close:self.downloadCompleteBlock];
}
else
{
[self updateCountriesList];
[self.dialogsTableView reloadSections:[NSIndexSet indexSetWithIndex:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
if (m_countries.empty())
[self close:self.downloadCompleteBlock];
}
}
- (void)processCountry:(NSString *)countryId
downloadedBytes:(uint64_t)downloadedBytes
totalBytes:(uint64_t)totalBytes
{
if (!self.rightButton.hidden ||
find(m_countries.begin(), m_countries.end(), countryId.UTF8String) == m_countries.end())
return;
auto const overallProgress = GetFramework().GetStorage().GetOverallProgress(m_countries);
CGFloat const progressValue =
static_cast<CGFloat>(overallProgress.m_bytesDownloaded) / overallProgress.m_bytesTotal;
self.progress.progress = progressValue;
self.titleLabel.text = [NSString stringWithFormat:@"%@%@%%", L(@"downloading"), @(floor(progressValue * 100))];
}
#pragma mark - Actions
- (IBAction)cancelButtonTap
{
[self close:self.cancelBlock];
}
- (IBAction)downloadButtonTap
{
[self updateCountriesList];
if (m_countries.empty())
{
[self close:self.downloadCompleteBlock];
return;
}
self.downloadBlock(m_countries, ^{
self.titleLabel.text = L(@"downloading");
self.messageLabel.hidden = YES;
self.progressWrapper.hidden = NO;
self.progress.state = MWMCircularProgressStateSpinner;
self.hDivider.hidden = YES;
self.vDivider.hidden = YES;
self.leftButton.hidden = YES;
self.rightButton.hidden = YES;
self.dialogsBottomOffset.priority = UILayoutPriorityDefaultHigh;
self.progressWrapperBottomOffset.priority = UILayoutPriorityDefaultHigh;
[UIView animateWithDuration:kAnimationDuration
animations:^{
[self layoutSubviews];
}];
});
}
- (void)showDownloadDetail:(UIButton *)sender
{
self.listExpanded = sender.selected;
}
- (void)setListExpanded:(BOOL)listExpanded
{
_listExpanded = listExpanded;
[self layoutIfNeeded];
auto const updateCells = ^(BOOL show)
{
for (MWMDownloaderDialogCell * cell in self.dialogsTableView.visibleCells)
{
cell.titleLabel.alpha = show ? 1. : 0.;
}
[self.dialogsTableView refresh];
};
if (listExpanded)
{
CGFloat const actualHeight = kCellHeight * m_countries.size() + kHeaderHeight;
CGFloat const height = [self bounded:actualHeight withHeight:self.superview.height];
self.tableViewHeight.constant = height;
self.dialogsTableView.scrollEnabled = actualHeight > self.tableViewHeight.constant;
[UIView animateWithDuration:kAnimationDuration animations:^{ [self layoutSubviews]; }
completion:^(BOOL finished)
{
[UIView animateWithDuration:kDefaultAnimationDuration animations:^{ updateCells(YES); }];
}];
}
else
{
self.tableViewHeight.constant = kHeaderHeight;
self.dialogsTableView.scrollEnabled = NO;
updateCells(NO);
[UIView animateWithDuration:kAnimationDuration animations:^{ [self layoutSubviews]; }];
}
}
- (CGFloat)bounded:(CGFloat)f withHeight:(CGFloat)h
{
CGFloat const currentHeight = [self.subviews.firstObject height];
CGFloat const maximumHeight = h - 2. * kMinimumOffset;
CGFloat const availableHeight = maximumHeight - currentHeight;
return MIN(f, availableHeight + self.tableViewHeight.constant);
}
- (void)invalidateTableConstraintWithHeight:(CGFloat)height
{
if (self.listExpanded)
{
CGFloat const actualHeight = kCellHeight * m_countries.size() + kHeaderHeight;
self.tableViewHeight.constant = [self bounded:actualHeight withHeight:height];
self.dialogsTableView.scrollEnabled = actualHeight > self.tableViewHeight.constant;
}
else
{
self.tableViewHeight.constant = kHeaderHeight;
}
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation
{
CGFloat const height = UIInterfaceOrientationIsLandscape(orientation) ? MIN(self.superview.width, self.superview.height) : MAX(self.superview.width, self.superview.height);
[self invalidateTableConstraintWithHeight:height];
}
- (void)close:(MWMVoidBlock)completion
{
[[MWMStorage sharedStorage] removeObserver:self];
[super close:completion];
}
#pragma mark - UITableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return m_countries.size();
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return kHeaderHeight;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return kCellHeight;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return self.listHeader;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
UIView * view = [[UIView alloc] init];
view.styleName = @"BlackOpaqueBackground";
return view;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Class cls = [MWMDownloaderDialogCell class];
auto cell = static_cast<MWMDownloaderDialogCell *>(
[tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]);
cell.titleLabel.text = self.countriesNames[indexPath.row];
return cell;
}
#pragma mark - Properties
- (MWMDownloaderDialogHeader *)listHeader
{
if (!_listHeader)
_listHeader = [MWMDownloaderDialogHeader headerForOwnerAlert:self];
[_listHeader setTitle:[NSString stringWithFormat:@"%@ (%@)", L(@"downloader_status_maps"), @(m_countries.size())]
size:self.countriesSize];
return _listHeader;
}
- (MWMCircularProgress *)progress
{
if (!_progress)
{
_progress = [MWMCircularProgress downloaderProgressForParentView:self.progressWrapper];
_progress.delegate = self;
}
return _progress;
}
@end

View file

@ -0,0 +1,192 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<view contentMode="scaleToFill" id="jqI-GQ-yDh" customClass="MWMDownloadTransitMapAlert" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Hay-Qx-kVw" userLabel="ContainerView">
<rect key="frame" x="47.5" y="218.5" width="280" height="230"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MvX-7q-CIH" userLabel="Title">
<rect key="frame" x="20" y="20" width="240" height="29"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="29" id="018-Nz-384"/>
<constraint firstAttribute="width" constant="240" id="cHd-lJ-pXe"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
<color key="textColor" red="0.12941176470588234" green="0.12941176470588234" blue="0.12941176470588234" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<size key="shadowOffset" width="0.0" height="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="routing_download_maps_along"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium18:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bco-zh-ekL">
<rect key="frame" x="122" y="65" width="36" height="36"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="36" id="0Pg-8w-QZa"/>
<constraint firstAttribute="width" constant="36" id="cjU-dH-SAG"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Message" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Q3X-9G-3PT">
<rect key="frame" x="23" y="61" width="234" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="234" id="YLG-PG-WHS"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="17" id="iOI-5t-pEY"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="routing_requires_all_map"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<tableView clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="1000" alwaysBounceVertical="YES" scrollEnabled="NO" style="grouped" separatorStyle="none" rowHeight="32" sectionHeaderHeight="44" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="1lL-cj-2oS">
<rect key="frame" x="0.0" y="98" width="280" height="88"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="88" id="92H-Th-Xgt"/>
</constraints>
<color key="separatorColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<color key="sectionIndexBackgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<color key="sectionIndexTrackingBackgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="dataSource" destination="jqI-GQ-yDh" id="8mP-kY-rEz"/>
<outlet property="delegate" destination="jqI-GQ-yDh" id="9Fy-MW-IYa"/>
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2nQ-k3-Rx3" userLabel="hDivider">
<rect key="frame" x="0.0" y="186" width="280" height="1"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="280" id="YGc-0s-8yD"/>
<constraint firstAttribute="height" constant="1" id="l1s-jg-dng"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="q87-qQ-0rn">
<rect key="frame" x="140" y="186" width="140" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="downloadNowButton"/>
<constraints>
<constraint firstAttribute="width" constant="140" id="1Io-1t-Odn"/>
<constraint firstAttribute="height" constant="44" id="CeP-fJ-qZs"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<state key="normal" title="right">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="download"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="downloadButtonTap" destination="jqI-GQ-yDh" eventType="touchUpInside" id="wBd-0C-U51"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dhS-fg-rNl">
<rect key="frame" x="0.0" y="186" width="140" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="notNowButton"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="7gw-Uu-UEK"/>
<constraint firstAttribute="width" constant="140" id="EMA-LM-Zje"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="left">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="later"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="cancelButtonTap" destination="jqI-GQ-yDh" eventType="touchUpInside" id="xii-qA-4BV"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Jjh-VP-emP" userLabel="vDivider">
<rect key="frame" x="139" y="186" width="1" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="8kQ-Yd-0D3"/>
<constraint firstAttribute="height" constant="44" id="vYd-td-NOl"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.88" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="dhS-fg-rNl" firstAttribute="top" secondItem="1lL-cj-2oS" secondAttribute="bottom" priority="500" id="2aT-au-ilL"/>
<constraint firstAttribute="bottom" secondItem="dhS-fg-rNl" secondAttribute="bottom" id="2bS-ac-iDr"/>
<constraint firstItem="bco-zh-ekL" firstAttribute="centerX" secondItem="Hay-Qx-kVw" secondAttribute="centerX" id="3xy-PQ-9lP"/>
<constraint firstAttribute="bottom" secondItem="q87-qQ-0rn" secondAttribute="bottom" id="3yg-r8-Lm3"/>
<constraint firstAttribute="bottom" secondItem="1lL-cj-2oS" secondAttribute="bottom" priority="250" id="58e-JY-U6g"/>
<constraint firstAttribute="centerX" secondItem="2nQ-k3-Rx3" secondAttribute="centerX" id="5S3-Lh-pMR"/>
<constraint firstAttribute="trailing" secondItem="q87-qQ-0rn" secondAttribute="trailing" id="Bud-qr-UTy"/>
<constraint firstItem="bco-zh-ekL" firstAttribute="top" secondItem="MvX-7q-CIH" secondAttribute="bottom" constant="16" id="D9b-za-yb8"/>
<constraint firstItem="q87-qQ-0rn" firstAttribute="top" secondItem="1lL-cj-2oS" secondAttribute="bottom" priority="500" id="Lpk-cS-FHG"/>
<constraint firstItem="Q3X-9G-3PT" firstAttribute="top" secondItem="MvX-7q-CIH" secondAttribute="bottom" constant="12" id="PN3-Vl-yKe"/>
<constraint firstAttribute="width" constant="280" id="PoR-0E-Yd5"/>
<constraint firstItem="1lL-cj-2oS" firstAttribute="top" secondItem="bco-zh-ekL" secondAttribute="bottom" priority="250" constant="20" id="QQh-zT-bWE"/>
<constraint firstAttribute="centerX" secondItem="1lL-cj-2oS" secondAttribute="centerX" id="Td0-Pa-b2s"/>
<constraint firstAttribute="centerX" secondItem="MvX-7q-CIH" secondAttribute="centerX" id="Uwa-a8-9bd"/>
<constraint firstAttribute="bottom" secondItem="Jjh-VP-emP" secondAttribute="bottom" id="YL8-fj-ENd"/>
<constraint firstAttribute="bottom" secondItem="2nQ-k3-Rx3" secondAttribute="bottom" constant="43" id="YPn-NL-TPQ"/>
<constraint firstItem="q87-qQ-0rn" firstAttribute="leading" secondItem="Jjh-VP-emP" secondAttribute="trailing" id="hwW-Pw-fPz"/>
<constraint firstItem="dhS-fg-rNl" firstAttribute="leading" secondItem="Hay-Qx-kVw" secondAttribute="leading" id="hzI-MA-Jal"/>
<constraint firstItem="1lL-cj-2oS" firstAttribute="width" secondItem="Hay-Qx-kVw" secondAttribute="width" id="i3L-zR-0ay"/>
<constraint firstAttribute="centerX" secondItem="Q3X-9G-3PT" secondAttribute="centerX" id="l5p-jN-Vvq"/>
<constraint firstItem="MvX-7q-CIH" firstAttribute="top" secondItem="Hay-Qx-kVw" secondAttribute="top" constant="20" id="xBi-k5-Mze"/>
<constraint firstItem="1lL-cj-2oS" firstAttribute="top" secondItem="Q3X-9G-3PT" secondAttribute="bottom" priority="500" constant="20" id="xQk-cL-cLn"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertView"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="centerY" secondItem="Hay-Qx-kVw" secondAttribute="centerY" id="5Z9-NF-BEW"/>
<constraint firstAttribute="centerX" secondItem="Hay-Qx-kVw" secondAttribute="centerX" id="e0c-Aj-Anh"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ZL2-Bv-eBx"/>
<connections>
<outlet property="containerView" destination="Hay-Qx-kVw" id="YBK-zz-Ejj"/>
<outlet property="dialogsBottomOffset" destination="58e-JY-U6g" id="D2s-m8-XQl"/>
<outlet property="dialogsTableView" destination="1lL-cj-2oS" id="QPh-bX-GfH"/>
<outlet property="hDivider" destination="2nQ-k3-Rx3" id="5BD-2K-Qcc"/>
<outlet property="leftButton" destination="dhS-fg-rNl" id="t6u-5i-zwZ"/>
<outlet property="messageLabel" destination="Q3X-9G-3PT" id="AUA-bn-mAy"/>
<outlet property="progressWrapper" destination="bco-zh-ekL" id="AB4-SS-xCc"/>
<outlet property="progressWrapperBottomOffset" destination="QQh-zT-bWE" id="vBy-LT-WOC"/>
<outlet property="rightButton" destination="q87-qQ-0rn" id="91J-mu-sXT"/>
<outlet property="tableViewHeight" destination="92H-Th-Xgt" id="qnm-KF-Hb7"/>
<outlet property="titleLabel" destination="MvX-7q-CIH" id="hej-5l-48z"/>
<outlet property="vDivider" destination="Jjh-VP-emP" id="bNj-9y-l3s"/>
</connections>
<point key="canvasLocation" x="295" y="401"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
</resources>
</document>

View file

@ -0,0 +1,7 @@
#import "MWMTableViewCell.h"
@interface MWMDownloaderDialogCell : MWMTableViewCell
@property (weak, nonatomic) IBOutlet UILabel * titleLabel;
@end

View file

@ -0,0 +1,5 @@
#import "MWMDownloaderDialogCell.h"
@implementation MWMDownloaderDialogCell
@end

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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 clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="MWMDownloaderDialogCell" rowHeight="33" id="LWd-Zy-XGd" customClass="MWMDownloaderDialogCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="280" height="33"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="LWd-Zy-XGd" id="gf8-bS-tvq">
<rect key="frame" x="0.0" y="0.0" width="280" height="33"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1B4-T1-g4X">
<rect key="frame" x="20" y="8" width="240" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="u5A-6l-INO"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<color key="backgroundColor" red="0.90196078431372551" green="0.90196078431372551" blue="0.90196078431372551" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="centerX" secondItem="1B4-T1-g4X" secondAttribute="centerX" id="2AW-qe-sgg"/>
<constraint firstAttribute="centerY" secondItem="1B4-T1-g4X" secondAttribute="centerY" id="TD9-6y-WM8"/>
<constraint firstAttribute="trailing" secondItem="1B4-T1-g4X" secondAttribute="trailing" constant="20" id="ruG-JF-m2y"/>
<constraint firstItem="1B4-T1-g4X" firstAttribute="leading" secondItem="gf8-bS-tvq" secondAttribute="leading" constant="20" id="sZo-pt-LJL"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="PressBackground"/>
</userDefinedRuntimeAttributes>
</tableViewCellContentView>
<color key="backgroundColor" red="0.90196078431372551" green="0.90196078431372551" blue="0.90196078431372551" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="hHf-it-4Q9"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="PressBackground"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="titleLabel" destination="1B4-T1-g4X" id="m4n-H9-1Ta"/>
</connections>
<point key="canvasLocation" x="235" y="180"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,14 @@
#import <UIKit/UIKit.h>
@class MWMDownloadTransitMapAlert;
@interface MWMDownloaderDialogHeader : UIView
@property (weak, nonatomic) IBOutlet UIButton * headerButton;
@property (weak, nonatomic) IBOutlet UIImageView * expandImage;
+ (instancetype)headerForOwnerAlert:(MWMDownloadTransitMapAlert *)alert;
- (void)layoutSizeLabel;
- (void)setTitle:(NSString *)title size:(NSString *)size;
@end

View file

@ -0,0 +1,53 @@
#import "MWMDownloaderDialogHeader.h"
#import "MWMDownloadTransitMapAlert.h"
static NSString * const kDownloaderDialogHeaderNibName = @"MWMDownloaderDialogHeader";
@interface MWMDownloaderDialogHeader ()
@property (weak, nonatomic) IBOutlet UILabel * title;
@property (weak, nonatomic) IBOutlet UILabel * size;
@property (weak, nonatomic) IBOutlet UIView * dividerView;
@property (weak, nonatomic) MWMDownloadTransitMapAlert * ownerAlert;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint * sizeTrailing;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint * titleLeading;
@end
@implementation MWMDownloaderDialogHeader
+ (instancetype)headerForOwnerAlert:(MWMDownloadTransitMapAlert *)alert
{
MWMDownloaderDialogHeader * header =
[NSBundle.mainBundle loadNibNamed:kDownloaderDialogHeaderNibName owner:nil options:nil]
.firstObject;
header.ownerAlert = alert;
return header;
}
- (IBAction)headerButtonTap:(UIButton *)sender
{
BOOL const currentState = sender.selected;
sender.selected = !currentState;
self.dividerView.hidden = currentState;
[UIView animateWithDuration:kDefaultAnimationDuration animations:^
{
self.expandImage.transform = sender.selected ? CGAffineTransformMakeRotation(M_PI) : CGAffineTransformIdentity;
}];
[self.ownerAlert showDownloadDetail:sender];
}
- (void)layoutSizeLabel
{
if (self.expandImage.hidden)
self.sizeTrailing.constant = self.titleLeading.constant;
[self layoutIfNeeded];
}
- (void)setTitle:(NSString *)title size:(NSString *)size
{
self.title.text = title;
self.size.text = size;
}
@end

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" 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="15704"/>
<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"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="MWMDownloaderDialogHeader" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="280" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hh9-2l-gu2">
<rect key="frame" x="20" y="14" width="160" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="160" id="4WE-p5-RdF"/>
<constraint firstAttribute="height" constant="16" id="FQE-Zk-gXr"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0977349653840065" green="0.0977320596575737" blue="0.09773370623588562" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_arrow_gray_down" translatesAutoresizingMaskIntoConstraints="NO" id="eqn-NQ-zOk">
<rect key="frame" x="248" y="8" width="28" height="28"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="CkX-aX-oaf"/>
<constraint firstAttribute="height" constant="28" id="mLX-ow-5er"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMGray"/>
</userDefinedRuntimeAttributes>
</imageView>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tqG-fM-g6J">
<rect key="frame" x="0.0" y="43" width="280" 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="Vc3-uM-vHv"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="size" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="m8f-ps-pFV">
<rect key="frame" x="180" y="14" width="64" height="16"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="7Hg-8z-e56"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="64" id="hct-Jz-zGl"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0977349653840065" green="0.0977320596575737" blue="0.09773370623588562" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eIt-Yb-1Yl">
<rect key="frame" x="0.0" y="0.0" width="280" height="44"/>
<state key="normal">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<connections>
<action selector="headerButtonTap:" destination="iN0-l3-epB" eventType="touchUpInside" id="SW0-Mx-TVi"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.059999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="eIt-Yb-1Yl" secondAttribute="bottom" id="61x-d3-FtO"/>
<constraint firstAttribute="trailing" secondItem="eqn-NQ-zOk" secondAttribute="trailing" constant="4" id="8Xd-cJ-b0r"/>
<constraint firstAttribute="trailing" secondItem="tqG-fM-g6J" secondAttribute="trailing" id="9Xc-Au-fuY"/>
<constraint firstItem="eqn-NQ-zOk" firstAttribute="centerY" secondItem="m8f-ps-pFV" secondAttribute="centerY" id="DOo-JP-5fj"/>
<constraint firstAttribute="centerY" secondItem="m8f-ps-pFV" secondAttribute="centerY" id="F82-yF-6kJ"/>
<constraint firstAttribute="centerY" secondItem="hh9-2l-gu2" secondAttribute="centerY" id="GYb-1Q-mMG"/>
<constraint firstItem="eIt-Yb-1Yl" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="Jdx-uy-XaA"/>
<constraint firstAttribute="trailing" secondItem="eIt-Yb-1Yl" secondAttribute="trailing" id="MSS-uK-Dby"/>
<constraint firstItem="eIt-Yb-1Yl" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="Xxs-g9-ygX"/>
<constraint firstItem="hh9-2l-gu2" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" id="ZfI-xz-HEx"/>
<constraint firstAttribute="trailing" secondItem="m8f-ps-pFV" secondAttribute="trailing" constant="36" id="kwN-VG-nEr"/>
<constraint firstItem="tqG-fM-g6J" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="rDI-mo-gnf"/>
<constraint firstItem="tqG-fM-g6J" firstAttribute="top" secondItem="m8f-ps-pFV" secondAttribute="bottom" constant="13" id="v0U-Qb-rW1"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="gAS-Hf-5F8"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="BlackOpaqueBackground"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="dividerView" destination="tqG-fM-g6J" id="eOr-3y-kbn"/>
<outlet property="expandImage" destination="eqn-NQ-zOk" id="fGc-MN-ss7"/>
<outlet property="headerButton" destination="eIt-Yb-1Yl" id="f0E-0A-6UG"/>
<outlet property="size" destination="m8f-ps-pFV" id="5ax-Hb-xEm"/>
<outlet property="sizeTrailing" destination="kwN-VG-nEr" id="K52-ZK-LuH"/>
<outlet property="title" destination="hh9-2l-gu2" id="JNQ-ls-kgI"/>
<outlet property="titleLeading" destination="ZfI-xz-HEx" id="Vzg-Cd-ztu"/>
</connections>
<point key="canvasLocation" x="202" y="259"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
<image name="ic_arrow_gray_down" width="28" height="28"/>
</resources>
</document>

View file

@ -0,0 +1,18 @@
import UIKit
final class LocationServicesDisabledAlert: MWMAlert {
@objc
class func alert() -> LocationServicesDisabledAlert? {
guard let alert = Bundle.main.loadNibNamed("LocationServicesDisabledAlert", owner: nil)?.first as? LocationServicesDisabledAlert else {
assertionFailure("Error: LocationServicesDisabledAlert failed lo load from nib.")
return nil
}
alert.setNeedsCloseAfterEnterBackground()
return alert
}
@IBAction func okButtonDidTap(_ sender: Any) {
close(nil)
}
}

View file

@ -0,0 +1,268 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.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"/>
<view contentMode="scaleToFill" id="WR7-Hs-CVJ" customClass="LocationServicesDisabledAlert" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="539" height="770"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="W9l-ze-azg" userLabel="ContainerView">
<rect key="frame" x="30" y="255" width="479" height="285"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lMb-vR-q3Y" userLabel="Title">
<rect key="frame" x="20" y="15" width="439" height="22"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="22" id="MGn-Ke-8G8"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
<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="medium18:blackPrimaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_header"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Message" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fz4-9d-5tq" userLabel="Message">
<rect key="frame" x="20" y="47" width="439" height="20"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="jCV-Ko-ByO"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_message"/>
</userDefinedRuntimeAttributes>
</label>
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" axis="vertical" distribution="equalSpacing" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="PjR-yG-eRZ">
<rect key="frame" x="20" y="77" width="439" height="127"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="vWN-wX-veH" userLabel="OpenSettings">
<rect key="frame" x="0.0" y="0.0" width="439" height="28"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_settings" translatesAutoresizingMaskIntoConstraints="NO" id="Ya1-ek-kDX" userLabel="settings">
<rect key="frame" x="0.0" y="0.0" width="28" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="LKZ-GV-grJ"/>
<constraint firstAttribute="width" constant="28" id="t73-v6-puf"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1. Open Settings" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-x6-g2s" userLabel="openSettings">
<rect key="frame" x="38" y="5.6666666666666856" width="401" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_1"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="q6r-CN-tgz" userLabel="SelectPrivacy">
<rect key="frame" x="0.0" y="33" width="439" height="28"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_privacy" translatesAutoresizingMaskIntoConstraints="NO" id="7fg-Gm-P0f" userLabel="settings">
<rect key="frame" x="0.0" y="0.0" width="28" height="28"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="tintColor" systemColor="systemBlueColor"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="guV-Tr-7TT"/>
<constraint firstAttribute="height" constant="28" id="srW-6x-82R"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2. Select Privacy" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="973-fi-RfE" userLabel="openSettings">
<rect key="frame" x="38" y="5.6666666666666856" width="401" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_on_device_2"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="W0F-rf-Edh" userLabel="SelectLocationServices">
<rect key="frame" x="0.0" y="66" width="439" height="28"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_geoposition" translatesAutoresizingMaskIntoConstraints="NO" id="jzE-sK-mEB" userLabel="geoposition">
<rect key="frame" x="0.0" y="0.0" width="28" height="28"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="HQ9-9t-v50"/>
<constraint firstAttribute="height" constant="28" id="dY9-Jf-hIB"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3. Select Location Services" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hhw-jx-9Fe" userLabel="geoposition">
<rect key="frame" x="38" y="5.6666666666666856" width="401" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_on_device_3"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="pf7-7u-B0E" userLabel="TurnOnLocationServices">
<rect key="frame" x="0.0" y="99" width="439" height="28"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_swither" translatesAutoresizingMaskIntoConstraints="NO" id="UcF-g2-Faq" userLabel="turnon">
<rect key="frame" x="0.0" y="0.0" width="28" height="28"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="GKu-yv-3JW"/>
<constraint firstAttribute="height" constant="28" id="lNW-jN-VJf"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="4. Turn On Location Services" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Q1u-dB-Esr" userLabel="select &quot;Always&quot;">
<rect key="frame" x="38" y="5.6666666666666856" width="401" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_on_device_4"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
</stackView>
</subviews>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Additional message" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fc4-12-xx4" userLabel="Additional message">
<rect key="frame" x="20" y="214" width="439" height="17"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_on_device_additional_message"/>
</userDefinedRuntimeAttributes>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3eJ-wI-Y11" userLabel="hDivider">
<rect key="frame" x="0.0" y="241" width="479" 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="wiR-YE-iVH"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="WVU-qc-efT" userLabel="okButton">
<rect key="frame" x="0.0" y="241" width="479" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="0vj-j6-oga"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="ok">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="ok"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="okButtonDidTap:" destination="WR7-Hs-CVJ" eventType="touchUpInside" id="2Th-Md-oau"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.88" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="2jy-XF-Yc6"/>
<constraint firstItem="PjR-yG-eRZ" firstAttribute="bottom" secondItem="fc4-12-xx4" secondAttribute="top" constant="-10" id="Bda-Vu-S3B"/>
<constraint firstItem="3eJ-wI-Y11" firstAttribute="top" secondItem="WVU-qc-efT" secondAttribute="top" id="DWH-KY-Gih"/>
<constraint firstAttribute="width" secondItem="3eJ-wI-Y11" secondAttribute="width" id="Dqh-aw-vv1"/>
<constraint firstAttribute="bottom" secondItem="WVU-qc-efT" secondAttribute="bottom" id="Hbe-e3-xsI"/>
<constraint firstItem="lMb-vR-q3Y" firstAttribute="leading" secondItem="W9l-ze-azg" secondAttribute="leading" constant="20" id="KLk-P2-6HX"/>
<constraint firstItem="fc4-12-xx4" firstAttribute="bottom" secondItem="WVU-qc-efT" secondAttribute="top" constant="-10" id="MTa-xK-xfb"/>
<constraint firstItem="lMb-vR-q3Y" firstAttribute="top" secondItem="W9l-ze-azg" secondAttribute="top" constant="15" id="NWh-CB-SSg"/>
<constraint firstAttribute="trailing" secondItem="fc4-12-xx4" secondAttribute="trailing" constant="20" id="Noy-do-AD5"/>
<constraint firstItem="WVU-qc-efT" firstAttribute="leading" secondItem="W9l-ze-azg" secondAttribute="leading" id="P4b-ex-cIo"/>
<constraint firstItem="fc4-12-xx4" firstAttribute="leading" secondItem="W9l-ze-azg" secondAttribute="leading" constant="20" id="UWg-4r-gTh"/>
<constraint firstItem="PjR-yG-eRZ" firstAttribute="leading" secondItem="W9l-ze-azg" secondAttribute="leading" constant="20" id="Xig-e5-dMF"/>
<constraint firstAttribute="trailing" secondItem="lMb-vR-q3Y" secondAttribute="trailing" constant="20" id="ZjY-OS-5Ar"/>
<constraint firstItem="fz4-9d-5tq" firstAttribute="top" secondItem="lMb-vR-q3Y" secondAttribute="bottom" constant="10" id="alo-B9-UKo"/>
<constraint firstItem="PjR-yG-eRZ" firstAttribute="top" secondItem="fz4-9d-5tq" secondAttribute="bottom" constant="10" id="ceU-Nn-UvW"/>
<constraint firstAttribute="trailing" secondItem="PjR-yG-eRZ" secondAttribute="trailing" constant="20" id="rb5-li-7z9"/>
<constraint firstAttribute="centerX" secondItem="fz4-9d-5tq" secondAttribute="centerX" id="t2L-hx-Wvu"/>
<constraint firstAttribute="trailing" secondItem="WVU-qc-efT" secondAttribute="trailing" id="uf8-cg-WgD"/>
<constraint firstItem="fz4-9d-5tq" firstAttribute="leading" secondItem="W9l-ze-azg" secondAttribute="leading" constant="20" id="v2M-33-BDA"/>
<constraint firstAttribute="centerX" secondItem="3eJ-wI-Y11" secondAttribute="centerX" id="wRK-vV-cs9"/>
<constraint firstAttribute="trailing" secondItem="fz4-9d-5tq" secondAttribute="trailing" constant="20" id="wz1-Ok-gIi"/>
<constraint firstAttribute="centerX" secondItem="lMb-vR-q3Y" secondAttribute="centerX" id="yjM-dk-hoP"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertView"/>
</userDefinedRuntimeAttributes>
<variation key="default">
<mask key="constraints">
<exclude reference="2jy-XF-Yc6"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=regular">
<mask key="constraints">
<include reference="2jy-XF-Yc6"/>
</mask>
</variation>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="nuQ-1f-mMT"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="W9l-ze-azg" firstAttribute="centerY" secondItem="nuQ-1f-mMT" secondAttribute="centerY" id="1Le-Bj-m03"/>
<constraint firstItem="nuQ-1f-mMT" firstAttribute="trailing" secondItem="W9l-ze-azg" secondAttribute="trailing" constant="30" id="1wd-E9-kcQ"/>
<constraint firstItem="nuQ-1f-mMT" firstAttribute="bottom" relation="lessThanOrEqual" secondItem="W9l-ze-azg" secondAttribute="bottom" constant="30" id="9vB-UV-vTP"/>
<constraint firstItem="W9l-ze-azg" firstAttribute="leading" secondItem="nuQ-1f-mMT" secondAttribute="leading" constant="30" id="Bvm-sK-Baz"/>
<constraint firstItem="W9l-ze-azg" firstAttribute="top" relation="greaterThanOrEqual" secondItem="nuQ-1f-mMT" secondAttribute="top" constant="30" id="Eg7-7g-IAE"/>
<constraint firstItem="nuQ-1f-mMT" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="W9l-ze-azg" secondAttribute="bottom" constant="30" id="qIv-d3-sJM"/>
<constraint firstItem="W9l-ze-azg" firstAttribute="top" relation="lessThanOrEqual" secondItem="nuQ-1f-mMT" secondAttribute="top" constant="30" id="zs3-3Q-gZU"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<variation key="default">
<mask key="constraints">
<exclude reference="1wd-E9-kcQ"/>
<exclude reference="9vB-UV-vTP"/>
<exclude reference="Bvm-sK-Baz"/>
<exclude reference="zs3-3Q-gZU"/>
</mask>
</variation>
<variation key="widthClass=compact">
<mask key="constraints">
<include reference="1wd-E9-kcQ"/>
<include reference="Bvm-sK-Baz"/>
</mask>
</variation>
<variation key="widthClass=regular">
<mask key="constraints">
<include reference="9vB-UV-vTP"/>
<include reference="zs3-3Q-gZU"/>
</mask>
</variation>
<point key="canvasLocation" x="115.26717557251908" y="-30.281690140845072"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
<image name="ic_geoposition" width="28" height="28"/>
<image name="ic_privacy" width="17" height="17"/>
<image name="ic_settings" width="28" height="28"/>
<image name="ic_swither" width="30" height="29"/>
<systemColor name="systemBlueColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,7 @@
#import "MWMAlert.h"
@interface MWMLocationAlert : MWMAlert
+ (instancetype)alertWithCancelBlock:(MWMVoidBlock)cancelBlock;
@end

View file

@ -0,0 +1,39 @@
#import "MWMLocationAlert.h"
#import "MWMAlertViewController.h"
static NSString * const kLocationAlertNibName = @"MWMLocationAlert";
@interface MWMLocationAlert ()
@property (weak, nonatomic) IBOutlet UIButton * rightButton;
@property (nullable, nonatomic) MWMVoidBlock cancelBlock;
@end
@implementation MWMLocationAlert
+ (instancetype)alertWithCancelBlock:(MWMVoidBlock)cancelBlock
{
MWMLocationAlert * alert =
[NSBundle.mainBundle loadNibNamed:kLocationAlertNibName owner:nil options:nil].firstObject;
[alert setNeedsCloseAlertAfterEnterBackground];
alert.cancelBlock = cancelBlock;
return alert;
}
- (IBAction)settingsTap
{
[self close:^{
NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
UIApplication * a = UIApplication.sharedApplication;
if ([a canOpenURL:url])
[a openURL:url options:@{} completionHandler:nil];
}];
}
- (IBAction)closeTap
{
[self close:self.cancelBlock];
}
@end

View file

@ -0,0 +1,227 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<view contentMode="scaleToFill" id="mnY-7K-ids" customClass="MWMLocationAlert" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8dg-2G-Gkm" userLabel="ContainerView">
<rect key="frame" x="47.5" y="198.5" width="280" height="291.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D5k-he-a1F" userLabel="Title">
<rect key="frame" x="20" y="20" width="240" height="22"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="gGL-U8-TMu"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="22" id="h5g-o2-ynw"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
<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="localizedText" value="location_services_disabled_header"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium18:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Message" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0fV-Pc-ec6" userLabel="Message">
<rect key="frame" x="20" y="53.5" width="240" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="bjX-A8-pbd"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="kX0-pg-KbR"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_message"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oy8-sO-eim" userLabel="hDivider">
<rect key="frame" x="0.0" y="247.5" width="280" 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="v6N-0M-ODb"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="opl-lk-5kD" userLabel="left">
<rect key="frame" x="0.0" y="247.5" width="140" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="140" id="Ant-We-sLi"/>
<constraint firstAttribute="height" constant="44" id="eOg-pv-nif"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="left">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="cancel"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="closeTap" destination="mnY-7K-ids" eventType="touchUpInside" id="X82-1U-rec"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1Ho-Pk-bDe" userLabel="right">
<rect key="frame" x="140" y="247.5" width="140" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="140" id="5wg-5d-hPp"/>
<constraint firstAttribute="height" constant="44" id="Mdl-Ix-DGD"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<state key="normal" title="right">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="settings"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="settingsTap" destination="mnY-7K-ids" eventType="touchUpInside" id="y48-t3-n6f"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oO4-NM-mFk" userLabel="vDivider">
<rect key="frame" x="140" y="247.5" width="1" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="mcR-0M-Ul0"/>
<constraint firstAttribute="height" constant="44" id="zL6-eK-WfS"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_settings" translatesAutoresizingMaskIntoConstraints="NO" id="icc-OP-m6N" userLabel="settings">
<rect key="frame" x="20" y="101.5" width="28" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="Ush-HH-2TA"/>
<constraint firstAttribute="width" constant="28" id="r2M-fp-8z4"/>
</constraints>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_geoposition" translatesAutoresizingMaskIntoConstraints="NO" id="S4o-n9-iX5" userLabel="geoposition">
<rect key="frame" x="20" y="145.5" width="28" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="Gdg-65-WkZ"/>
<constraint firstAttribute="width" constant="28" id="Wf3-ar-r44"/>
</constraints>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_select" translatesAutoresizingMaskIntoConstraints="NO" id="4hl-PR-x4M" userLabel="turnon">
<rect key="frame" x="20" y="189.5" width="28" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="A5h-wL-BHj"/>
<constraint firstAttribute="width" constant="28" id="mOL-nA-Afc"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1. Откройте Настройки" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EHh-Zz-Us5" userLabel="openSettings">
<rect key="frame" x="56" y="105.5" width="204" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="204" id="4JO-6s-aaI"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="jD6-K3-YYh"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_1"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2. Нажмите Геопозиция" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="stQ-KI-WeP" userLabel="openSettings">
<rect key="frame" x="56" y="149.5" width="204" height="20"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="AAw-Ri-mbT"/>
<constraint firstAttribute="width" constant="204" id="cNm-sH-jni"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_2"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3. Выберите &quot;Всегда&quot;" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eiz-Ie-23H" userLabel="select &quot;Always&quot;">
<rect key="frame" x="56" y="193.5" width="204" height="20"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="bIH-9x-S0s"/>
<constraint firstAttribute="width" constant="204" id="qBs-0m-j1B"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="location_services_disabled_3"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.88" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="opl-lk-5kD" secondAttribute="bottom" id="0Mz-4Z-230"/>
<constraint firstAttribute="width" secondItem="oy8-sO-eim" secondAttribute="width" id="3MR-Nx-zJb"/>
<constraint firstItem="EHh-Zz-Us5" firstAttribute="centerY" secondItem="icc-OP-m6N" secondAttribute="centerY" id="3SF-wh-w9N"/>
<constraint firstItem="eiz-Ie-23H" firstAttribute="leading" secondItem="4hl-PR-x4M" secondAttribute="trailing" constant="8" id="4DN-XY-UYe"/>
<constraint firstItem="EHh-Zz-Us5" firstAttribute="leading" secondItem="icc-OP-m6N" secondAttribute="trailing" constant="8" id="4EP-kd-cxV"/>
<constraint firstItem="opl-lk-5kD" firstAttribute="top" secondItem="eiz-Ie-23H" secondAttribute="bottom" constant="34" id="6RO-Fl-s2K"/>
<constraint firstItem="stQ-KI-WeP" firstAttribute="centerY" secondItem="S4o-n9-iX5" secondAttribute="centerY" id="9bR-F2-lU7"/>
<constraint firstItem="icc-OP-m6N" firstAttribute="top" secondItem="0fV-Pc-ec6" secondAttribute="bottom" constant="28" id="C8m-mW-KEF"/>
<constraint firstAttribute="trailing" secondItem="1Ho-Pk-bDe" secondAttribute="trailing" id="CTo-Fd-m0E"/>
<constraint firstAttribute="width" constant="280" id="Fif-Pa-YZH"/>
<constraint firstAttribute="centerX" secondItem="oy8-sO-eim" secondAttribute="centerX" id="Gbp-Y5-rsD"/>
<constraint firstItem="oy8-sO-eim" firstAttribute="top" secondItem="eiz-Ie-23H" secondAttribute="bottom" constant="34" id="JJw-71-2in"/>
<constraint firstItem="0fV-Pc-ec6" firstAttribute="top" secondItem="D5k-he-a1F" secondAttribute="bottom" constant="11.5" id="Jth-x6-IhP"/>
<constraint firstItem="1Ho-Pk-bDe" firstAttribute="top" secondItem="eiz-Ie-23H" secondAttribute="bottom" constant="34" id="LUY-3a-Inq"/>
<constraint firstAttribute="bottom" secondItem="1Ho-Pk-bDe" secondAttribute="bottom" id="NE1-oz-CLF"/>
<constraint firstItem="4hl-PR-x4M" firstAttribute="leading" secondItem="8dg-2G-Gkm" secondAttribute="leading" constant="20" id="Q7v-Re-gje"/>
<constraint firstAttribute="centerX" secondItem="D5k-he-a1F" secondAttribute="centerX" id="XX2-IK-R6K"/>
<constraint firstItem="D5k-he-a1F" firstAttribute="top" secondItem="8dg-2G-Gkm" secondAttribute="top" constant="20" id="Z54-gN-f1U"/>
<constraint firstAttribute="bottom" secondItem="oO4-NM-mFk" secondAttribute="bottom" id="aNg-ua-Xvg"/>
<constraint firstItem="icc-OP-m6N" firstAttribute="leading" secondItem="8dg-2G-Gkm" secondAttribute="leading" constant="20" id="cIX-d5-lZJ"/>
<constraint firstAttribute="centerX" secondItem="0fV-Pc-ec6" secondAttribute="centerX" id="lTc-yR-XqA"/>
<constraint firstItem="eiz-Ie-23H" firstAttribute="centerY" secondItem="4hl-PR-x4M" secondAttribute="centerY" id="n0S-Sb-bFh"/>
<constraint firstItem="4hl-PR-x4M" firstAttribute="top" secondItem="stQ-KI-WeP" secondAttribute="bottom" constant="20" id="nov-7z-XFm"/>
<constraint firstItem="S4o-n9-iX5" firstAttribute="top" secondItem="EHh-Zz-Us5" secondAttribute="bottom" constant="20" id="qqy-xa-ojy"/>
<constraint firstItem="S4o-n9-iX5" firstAttribute="leading" secondItem="8dg-2G-Gkm" secondAttribute="leading" constant="20" id="stf-om-BNZ"/>
<constraint firstItem="stQ-KI-WeP" firstAttribute="leading" secondItem="S4o-n9-iX5" secondAttribute="trailing" constant="8" id="uyt-fW-FYM"/>
<constraint firstItem="oO4-NM-mFk" firstAttribute="leading" secondItem="opl-lk-5kD" secondAttribute="trailing" id="z1h-Dd-0AD"/>
<constraint firstItem="opl-lk-5kD" firstAttribute="leading" secondItem="8dg-2G-Gkm" secondAttribute="leading" id="z9J-7w-u9C"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertView"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="centerY" secondItem="8dg-2G-Gkm" secondAttribute="centerY" constant="-10.5" id="W3z-HA-6ee"/>
<constraint firstAttribute="centerX" secondItem="8dg-2G-Gkm" secondAttribute="centerX" id="t4o-pT-rEC"/>
</constraints>
<viewLayoutGuide key="safeArea" id="k9W-qh-TkJ"/>
<connections>
<outlet property="rightButton" destination="1Ho-Pk-bDe" id="xO5-oe-a3h"/>
</connections>
<point key="canvasLocation" x="305" y="172"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
<image name="ic_geoposition" width="28" height="28"/>
<image name="ic_select" width="28" height="28"/>
<image name="ic_settings" width="28" height="28"/>
</resources>
</document>

View file

@ -0,0 +1,7 @@
#import "MWMAlert.h"
@interface MWMPlaceDoesntExistAlert : MWMAlert
+ (instancetype)alertWithBlock:(MWMStringBlock)block;
@end

View file

@ -0,0 +1,46 @@
#import "MWMPlaceDoesntExistAlert.h"
#import "MWMKeyboard.h"
@interface MWMPlaceDoesntExistAlert ()<MWMKeyboardObserver>
@property(weak, nonatomic) IBOutlet UITextField * textField;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * centerHorizontaly;
@property(copy, nonatomic) MWMStringBlock block;
@end
@implementation MWMPlaceDoesntExistAlert
+ (instancetype)alertWithBlock:(MWMStringBlock)block
{
MWMPlaceDoesntExistAlert * alert =
[NSBundle.mainBundle loadNibNamed:[self className] owner:nil options:nil].firstObject;
alert.block = block;
[MWMKeyboard addObserver:alert];
return alert;
}
- (IBAction)rightButtonTap
{
[self.textField resignFirstResponder];
[self close:^{
self.block(self.textField.text);
}];
}
- (IBAction)leftButtonTap
{
[self.textField resignFirstResponder];
[self close:nil];
}
#pragma mark - MWMKeyboard
- (void)onKeyboardAnimation
{
self.centerHorizontaly.constant = -[MWMKeyboard keyboardHeight] / 2;
[self layoutIfNeeded];
}
- (void)onKeyboardWillAnimate { [self setNeedsLayout]; }
@end

View file

@ -0,0 +1,181 @@
<?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="retina4_7" 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"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="bh1-8l-voy" customClass="MWMPlaceDoesntExistAlert">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XjH-qf-m6h" userLabel="ContainerView">
<rect key="frame" x="47.5" y="250.5" width="280" height="166"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NwN-e5-YQR" userLabel="Title">
<rect key="frame" x="20" y="20" width="240" height="22"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="Bz8-HZ-yfK"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="22" id="JWn-Ze-jrX"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
<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="medium18:blackPrimaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="editor_place_doesnt_exist"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Message" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VvH-Bb-fV9" userLabel="Message">
<rect key="frame" x="20" y="54" width="240" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="Gau-b5-VxP"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="VWx-20-JCg"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="editor_place_doesnt_exist_description"/>
</userDefinedRuntimeAttributes>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="npf-dH-f50">
<rect key="frame" x="16" y="86" width="248" height="24"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="0f8-vD-nJT">
<rect key="frame" x="4" y="0.0" width="240" height="24"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="uql-Jo-4VK"/>
</constraints>
<edgeInsets key="layoutMargins" top="4" left="4" bottom="4" right="4"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertViewTextField"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedPlaceholder" value="editor_comment_hint"/>
</userDefinedRuntimeAttributes>
</textField>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="0f8-vD-nJT" firstAttribute="centerX" secondItem="npf-dH-f50" secondAttribute="centerX" id="Pm7-RK-HAq"/>
<constraint firstItem="0f8-vD-nJT" firstAttribute="centerY" secondItem="npf-dH-f50" secondAttribute="centerY" id="T6e-qA-hJ1"/>
<constraint firstAttribute="height" constant="24" id="ZAd-qX-QyG"/>
<constraint firstAttribute="width" constant="248" id="x63-Bq-F0w"/>
<constraint firstItem="0f8-vD-nJT" firstAttribute="height" secondItem="npf-dH-f50" secondAttribute="height" id="zHi-xB-aCQ"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertViewTextFieldContainer"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kEx-DM-ynC" userLabel="hDivider">
<rect key="frame" x="0.0" y="122" width="280" 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="JKX-3f-YE4"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="zaM-0A-71d" userLabel="left">
<rect key="frame" x="0.0" y="122" width="140" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="JRt-9h-Ljb"/>
<constraint firstAttribute="width" constant="140" id="y86-N4-bGg"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="left">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="cancel"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="leftButtonTap" destination="bh1-8l-voy" eventType="touchUpInside" id="Ge7-Dx-Pf3"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MkK-sG-n6m" userLabel="right">
<rect key="frame" x="140" y="122" width="140" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="2ra-bP-J3a"/>
<constraint firstAttribute="width" constant="140" id="pNb-5U-J8Z"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<state key="normal" title="right">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="editor_report_problem_send_button"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="rightButtonTap" destination="bh1-8l-voy" eventType="touchUpInside" id="4FZ-zi-TAB"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Tyn-yU-dk3" userLabel="vDivider">
<rect key="frame" x="139.5" y="122" width="1" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="Bva-2f-zNE"/>
<constraint firstAttribute="height" constant="44" id="jEO-cN-3PZ"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="MkK-sG-n6m" secondAttribute="trailing" id="21Y-zv-8JR"/>
<constraint firstItem="NwN-e5-YQR" firstAttribute="top" secondItem="XjH-qf-m6h" secondAttribute="top" constant="20" id="6pQ-wv-c4Z"/>
<constraint firstItem="VvH-Bb-fV9" firstAttribute="top" secondItem="NwN-e5-YQR" secondAttribute="bottom" constant="12" id="EAW-Ft-SfJ"/>
<constraint firstAttribute="bottom" secondItem="MkK-sG-n6m" secondAttribute="bottom" id="K0E-y1-z0P"/>
<constraint firstAttribute="bottom" secondItem="zaM-0A-71d" secondAttribute="bottom" id="KzG-74-cDe"/>
<constraint firstItem="kEx-DM-ynC" firstAttribute="width" secondItem="XjH-qf-m6h" secondAttribute="width" id="Ldw-GJ-Ulm"/>
<constraint firstAttribute="bottom" secondItem="Tyn-yU-dk3" secondAttribute="bottom" id="Mb3-VE-bBR"/>
<constraint firstItem="Tyn-yU-dk3" firstAttribute="centerX" secondItem="XjH-qf-m6h" secondAttribute="centerX" id="PkD-5c-ltY"/>
<constraint firstItem="zaM-0A-71d" firstAttribute="leading" secondItem="XjH-qf-m6h" secondAttribute="leading" id="RCZ-fs-HwM"/>
<constraint firstItem="kEx-DM-ynC" firstAttribute="top" secondItem="npf-dH-f50" secondAttribute="bottom" constant="12" id="TD5-DZ-mt2"/>
<constraint firstItem="NwN-e5-YQR" firstAttribute="centerX" secondItem="XjH-qf-m6h" secondAttribute="centerX" id="ZbO-Kn-lsV"/>
<constraint firstItem="npf-dH-f50" firstAttribute="top" secondItem="VvH-Bb-fV9" secondAttribute="bottom" constant="12" id="apH-SR-1Ir"/>
<constraint firstItem="zaM-0A-71d" firstAttribute="top" secondItem="kEx-DM-ynC" secondAttribute="bottom" constant="-1" id="cPx-SM-J1B"/>
<constraint firstItem="VvH-Bb-fV9" firstAttribute="centerX" secondItem="XjH-qf-m6h" secondAttribute="centerX" id="mPb-7n-Ns1"/>
<constraint firstItem="npf-dH-f50" firstAttribute="centerX" secondItem="XjH-qf-m6h" secondAttribute="centerX" id="nfD-rk-FZo"/>
<constraint firstAttribute="width" constant="280" id="uve-xM-xfj"/>
<constraint firstItem="kEx-DM-ynC" firstAttribute="centerX" secondItem="XjH-qf-m6h" secondAttribute="centerX" id="vop-cl-2u7"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertView"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="XjH-qf-m6h" firstAttribute="centerX" secondItem="bh1-8l-voy" secondAttribute="centerX" id="Ez4-IC-X2C"/>
<constraint firstItem="XjH-qf-m6h" firstAttribute="centerY" secondItem="bh1-8l-voy" secondAttribute="centerY" id="ihl-fu-ac9"/>
</constraints>
<viewLayoutGuide key="safeArea" id="4Cb-Xf-v8p"/>
<connections>
<outlet property="centerHorizontaly" destination="ihl-fu-ac9" id="f11-9c-PMh"/>
<outlet property="textField" destination="0f8-vD-nJT" id="tm8-Pb-b1x"/>
</connections>
<point key="canvasLocation" x="305" y="172"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
</resources>
</document>

View file

@ -0,0 +1,19 @@
#import "MWMAlert.h"
typedef NS_ENUM(NSInteger, MWMMobileInternetAlertResult) {
MWMMobileInternetAlertResultAlways,
MWMMobileInternetAlertResultToday,
MWMMobileInternetAlertResultNotToday
};
NS_ASSUME_NONNULL_BEGIN
typedef void(^MWMMobileInternetAlertCompletionBlock)(MWMMobileInternetAlertResult result);
@interface MWMMobileInternetAlert : MWMAlert
+ (instancetype)alertWithBlock:(MWMMobileInternetAlertCompletionBlock)block;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,40 @@
#import "MWMMobileInternetAlert.h"
@interface MWMMobileInternetAlert ()
@property(copy, nonatomic) MWMMobileInternetAlertCompletionBlock completionBlock;
@end
@implementation MWMMobileInternetAlert
+ (instancetype)alertWithBlock:(MWMMobileInternetAlertCompletionBlock)block;
{
MWMMobileInternetAlert * alert =
[NSBundle.mainBundle loadNibNamed:[self className] owner:nil options:nil].firstObject;
alert.completionBlock = block;
return alert;
}
- (IBAction)alwaysTap
{
[self close:^{
self.completionBlock(MWMMobileInternetAlertResultAlways);
}];
}
- (IBAction)askTap
{
[self close:^{
self.completionBlock(MWMMobileInternetAlertResultToday);
}];
}
- (IBAction)neverTap
{
[self close:^{
self.completionBlock(MWMMobileInternetAlertResultNotToday);
}];
}
@end

View file

@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<view contentMode="scaleToFill" id="Tlh-TS-tkk" customClass="MWMMobileInternetAlert" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Tx8-cY-Dyg" userLabel="ContainerView">
<rect key="frame" x="47.5" y="219.5" width="280" height="228.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="REz-A0-Vg9" userLabel="Title">
<rect key="frame" x="20" y="20" width="240" height="21.5"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="QEO-gJ-c3y"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="RsI-at-W6p"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
<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="medium18:blackPrimaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="mobile_data"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Message" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGq-0K-ugB" userLabel="Message">
<rect key="frame" x="20" y="53.5" width="240" height="20"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="F3h-lQ-gSS"/>
<constraint firstAttribute="width" constant="240" id="XgA-ox-stk"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="mobile_data_dialog"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0vG-Ty-4hj" userLabel="button1">
<rect key="frame" x="0.0" y="94.5" width="280" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="RyA-xn-gAo"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Использовать всегда">
<color key="titleColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="mobile_data_option_always"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="bold17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="alwaysTap" destination="Tlh-TS-tkk" eventType="touchUpInside" id="fuJ-lY-I2V"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d5L-hE-z56" userLabel="button2">
<rect key="frame" x="0.0" y="139.5" width="280" height="44"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Спрашивать">
<color key="titleColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="mobile_data_option_today"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="askTap" destination="Tlh-TS-tkk" eventType="touchUpInside" id="brA-aR-BSr"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Xff-AT-bjO" userLabel="button3">
<rect key="frame" x="0.0" y="184.5" width="280" height="44"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Никогда не использовать">
<color key="titleColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="mobile_data_option_not_today"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="neverTap" destination="Tlh-TS-tkk" eventType="touchUpInside" id="wMh-1n-svo"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="VZe-Gu-rIx" userLabel="hDivider1">
<rect key="frame" x="0.0" y="93.5" width="280" 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="J0l-nh-ng5"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pNk-h2-LSK" userLabel="hDivider2">
<rect key="frame" x="0.0" y="138.5" width="280" height="1"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ZZ7-CE-a8J" userLabel="hDivider3">
<rect key="frame" x="0.0" y="183.5" width="280" height="1"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.88" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="d5L-hE-z56" firstAttribute="leading" secondItem="0vG-Ty-4hj" secondAttribute="leading" id="0nb-Mk-riG"/>
<constraint firstAttribute="centerX" secondItem="REz-A0-Vg9" secondAttribute="centerX" id="1H6-10-IWz"/>
<constraint firstItem="VZe-Gu-rIx" firstAttribute="top" secondItem="fGq-0K-ugB" secondAttribute="bottom" constant="20" id="7Sc-TP-Opl"/>
<constraint firstItem="0vG-Ty-4hj" firstAttribute="top" secondItem="VZe-Gu-rIx" secondAttribute="bottom" id="8lH-Op-m0b"/>
<constraint firstItem="d5L-hE-z56" firstAttribute="trailing" secondItem="0vG-Ty-4hj" secondAttribute="trailing" id="9RH-ob-nxK"/>
<constraint firstAttribute="width" secondItem="VZe-Gu-rIx" secondAttribute="width" id="CF8-wO-Qmv"/>
<constraint firstItem="ZZ7-CE-a8J" firstAttribute="top" secondItem="d5L-hE-z56" secondAttribute="bottom" priority="500" id="Ccg-Zb-Jdz"/>
<constraint firstItem="Xff-AT-bjO" firstAttribute="leading" secondItem="0vG-Ty-4hj" secondAttribute="leading" id="FF7-dF-mRZ"/>
<constraint firstItem="REz-A0-Vg9" firstAttribute="top" secondItem="Tx8-cY-Dyg" secondAttribute="top" constant="20" id="FgB-En-Kgp"/>
<constraint firstItem="0vG-Ty-4hj" firstAttribute="trailing" secondItem="VZe-Gu-rIx" secondAttribute="trailing" id="INy-WF-7W6"/>
<constraint firstAttribute="centerX" secondItem="VZe-Gu-rIx" secondAttribute="centerX" id="KrD-nj-Oh6"/>
<constraint firstItem="Xff-AT-bjO" firstAttribute="top" secondItem="ZZ7-CE-a8J" secondAttribute="bottom" id="NXi-bS-mbk"/>
<constraint firstItem="0vG-Ty-4hj" firstAttribute="leading" secondItem="VZe-Gu-rIx" secondAttribute="leading" id="O7A-Ma-3gA"/>
<constraint firstItem="pNk-h2-LSK" firstAttribute="top" secondItem="0vG-Ty-4hj" secondAttribute="bottom" id="OS9-H6-KDF"/>
<constraint firstItem="pNk-h2-LSK" firstAttribute="height" secondItem="VZe-Gu-rIx" secondAttribute="height" id="Z3c-z0-QUD"/>
<constraint firstItem="ZZ7-CE-a8J" firstAttribute="leading" secondItem="VZe-Gu-rIx" secondAttribute="leading" id="apm-L8-gZW"/>
<constraint firstItem="ZZ7-CE-a8J" firstAttribute="trailing" secondItem="VZe-Gu-rIx" secondAttribute="trailing" id="bXV-jy-LQ5"/>
<constraint firstItem="d5L-hE-z56" firstAttribute="height" secondItem="0vG-Ty-4hj" secondAttribute="height" id="d0s-RF-Hth"/>
<constraint firstItem="pNk-h2-LSK" firstAttribute="trailing" secondItem="VZe-Gu-rIx" secondAttribute="trailing" id="dKB-th-WyI"/>
<constraint firstAttribute="centerX" secondItem="fGq-0K-ugB" secondAttribute="centerX" id="hum-18-a8G"/>
<constraint firstItem="Xff-AT-bjO" firstAttribute="trailing" secondItem="0vG-Ty-4hj" secondAttribute="trailing" id="ibO-5L-PH1"/>
<constraint firstAttribute="bottom" secondItem="Xff-AT-bjO" secondAttribute="bottom" id="kZ3-1x-e5J"/>
<constraint firstItem="pNk-h2-LSK" firstAttribute="leading" secondItem="VZe-Gu-rIx" secondAttribute="leading" id="kv8-5K-4MW"/>
<constraint firstItem="Xff-AT-bjO" firstAttribute="height" secondItem="0vG-Ty-4hj" secondAttribute="height" id="net-N9-aSB"/>
<constraint firstAttribute="width" constant="280" id="oBl-6m-Exe"/>
<constraint firstItem="d5L-hE-z56" firstAttribute="top" secondItem="pNk-h2-LSK" secondAttribute="bottom" id="psC-4J-kSw"/>
<constraint firstItem="ZZ7-CE-a8J" firstAttribute="height" secondItem="VZe-Gu-rIx" secondAttribute="height" id="wqS-Qh-ept"/>
<constraint firstItem="fGq-0K-ugB" firstAttribute="top" secondItem="REz-A0-Vg9" secondAttribute="bottom" constant="12" id="xjY-UE-eIh"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertView"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="centerY" secondItem="Tx8-cY-Dyg" secondAttribute="centerY" id="Zpv-vh-Jxs"/>
<constraint firstAttribute="centerX" secondItem="Tx8-cY-Dyg" secondAttribute="centerX" id="oWX-V9-KBj"/>
</constraints>
<viewLayoutGuide key="safeArea" id="y7G-G6-vSl"/>
<point key="canvasLocation" x="304.5" y="171.5"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
</resources>
</document>

View file

@ -0,0 +1,7 @@
#import "MWMAlert.h"
@interface MWMRoutingDisclaimerAlert : MWMAlert
+ (instancetype)alertWithOkBlock:(MWMVoidBlock)block;
@end

View file

@ -0,0 +1,74 @@
#import "MWMRoutingDisclaimerAlert.h"
#import "MWMAlertViewController.h"
static CGFloat const kMinimumOffset = 20.;
@interface MWMRoutingDisclaimerAlert ()
@property(weak, nonatomic) IBOutlet UITextView * textView;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * textViewHeight;
@property(copy, nonatomic) MWMVoidBlock okBlock;
@end
@implementation MWMRoutingDisclaimerAlert
+ (instancetype)alertWithOkBlock:(MWMVoidBlock)block
{
MWMRoutingDisclaimerAlert * alert =
[NSBundle.mainBundle loadNibNamed:[self className] owner:nil options:nil].firstObject;
NSString * message = [NSString stringWithFormat:@"%@\n\n%@\n\n%@\n\n%@\n\n%@",
L(@"dialog_routing_disclaimer_priority"),
L(@"dialog_routing_disclaimer_precision"),
L(@"dialog_routing_disclaimer_recommendations"),
L(@"dialog_routing_disclaimer_borders"),
L(@"dialog_routing_disclaimer_beware")];
alert.textView.attributedText =
[[NSAttributedString alloc] initWithString:message
attributes:@{
NSFontAttributeName : UIFont.regular14,
NSForegroundColorAttributeName : UIColor.blackSecondaryText
}];
[alert.textView sizeToFit];
UIWindow * window = UIApplication.sharedApplication.keyWindow;
[alert invalidateTextViewHeight:alert.textView.height withHeight:window.height];
alert.okBlock = block;
return alert;
}
- (IBAction)okTap
{
[self close:self.okBlock];
}
- (IBAction)cancelTap
{
[self close:nil];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation
{
UIView * superview = self.superview ?: UIApplication.sharedApplication.keyWindow;
CGFloat const height = UIInterfaceOrientationIsLandscape(orientation)
? MIN(superview.width, superview.height)
: MAX(superview.width, superview.height);
[self invalidateTextViewHeight:self.textView.contentSize.height withHeight:height];
}
- (void)invalidateTextViewHeight:(CGFloat)textViewHeight withHeight:(CGFloat)height
{
self.textViewHeight.constant = [self bounded:textViewHeight withHeight:height];
self.textView.scrollEnabled = textViewHeight > self.textViewHeight.constant;
[self layoutIfNeeded];
}
- (CGFloat)bounded:(CGFloat)f withHeight:(CGFloat)h
{
CGFloat const currentHeight = [self.subviews.firstObject height];
CGFloat const maximumHeight = h - 2. * kMinimumOffset;
CGFloat const availableHeight = maximumHeight - currentHeight;
return MIN(f, availableHeight + self.textViewHeight.constant);
}
@end

View file

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<view contentMode="scaleToFill" id="Ie9-JJ-hnZ" customClass="MWMRoutingDisclaimerAlert" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ogu-AE-Ohh" userLabel="ContainerView">
<rect key="frame" x="47.5" y="265" width="280" height="137.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="S3s-j7-v6E" userLabel="Title">
<rect key="frame" x="20" y="20" width="240" height="21.5"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="uht-Xi-vWk"/>
<constraint firstAttribute="width" constant="240" id="wsn-Cq-kJT"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
<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="localizedText" value="dialog_routing_disclaimer_title"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium18:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" usesAttributedText="YES" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lbr-Xd-hLF">
<rect key="frame" x="20" y="52.5" width="240" height="20"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="GeJ-mk-jko"/>
<constraint firstAttribute="height" constant="20" id="XLp-um-S4A"/>
</constraints>
<attributedString key="attributedText">
<fragment content="Message">
<attributes>
<color key="NSColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<font key="NSFont" size="14" name="HelveticaNeue"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
</attributes>
</fragment>
</attributedString>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences" autocorrectionType="no" spellCheckingType="no"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</textView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XIL-q9-MMp" userLabel="hDivider">
<rect key="frame" x="0.0" y="92.5" width="280" 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="g4E-tB-Y4R"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aCY-1G-NL5" userLabel="right">
<rect key="frame" x="140.5" y="93.5" width="139.5" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="acceptButton"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="AAv-pX-uuc"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<state key="normal" title="right">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="accept"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="okTap" destination="Ie9-JJ-hnZ" eventType="touchUpInside" id="vfz-Rd-Kh6"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JfV-qu-qmI" userLabel="left">
<rect key="frame" x="0.0" y="93.5" width="139.5" height="44"/>
<accessibility key="accessibilityConfiguration" identifier="cancelButton"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/>
<state key="normal" title="left">
<color key="titleColor" red="0.090196078430000007" green="0.61960784310000006" blue="0.30196078430000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="decline"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="cancelTap" destination="Ie9-JJ-hnZ" eventType="touchUpInside" id="KWl-Dn-BpU"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="930-J1-9o0" userLabel="vDivider">
<rect key="frame" x="139.5" y="93.5" width="1" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="mf2-e7-Qg6"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.88" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="lbr-Xd-hLF" firstAttribute="top" secondItem="S3s-j7-v6E" secondAttribute="bottom" constant="11" id="20T-2E-mmf"/>
<constraint firstAttribute="width" secondItem="XIL-q9-MMp" secondAttribute="width" id="3m7-Ha-HlN"/>
<constraint firstItem="XIL-q9-MMp" firstAttribute="top" secondItem="lbr-Xd-hLF" secondAttribute="bottom" constant="20" id="4k1-g8-WZV"/>
<constraint firstAttribute="trailing" secondItem="aCY-1G-NL5" secondAttribute="trailing" id="BcN-zR-SdX"/>
<constraint firstItem="930-J1-9o0" firstAttribute="bottom" secondItem="aCY-1G-NL5" secondAttribute="bottom" id="F4d-Td-XJg"/>
<constraint firstItem="JfV-qu-qmI" firstAttribute="width" secondItem="aCY-1G-NL5" secondAttribute="width" id="KAH-qc-1f1"/>
<constraint firstAttribute="width" constant="280" id="KpN-ir-OWh"/>
<constraint firstAttribute="centerX" secondItem="lbr-Xd-hLF" secondAttribute="centerX" id="KuP-DA-FWV"/>
<constraint firstItem="JfV-qu-qmI" firstAttribute="bottom" secondItem="aCY-1G-NL5" secondAttribute="bottom" id="MPL-wr-BbK"/>
<constraint firstItem="aCY-1G-NL5" firstAttribute="leading" secondItem="930-J1-9o0" secondAttribute="trailing" id="N4p-JX-gzl"/>
<constraint firstAttribute="centerX" secondItem="S3s-j7-v6E" secondAttribute="centerX" id="Oag-6s-ais"/>
<constraint firstItem="S3s-j7-v6E" firstAttribute="top" secondItem="Ogu-AE-Ohh" secondAttribute="top" constant="20" id="OoP-Qp-YIb"/>
<constraint firstItem="JfV-qu-qmI" firstAttribute="leading" secondItem="Ogu-AE-Ohh" secondAttribute="leading" id="VYG-iz-HL8"/>
<constraint firstAttribute="bottom" secondItem="aCY-1G-NL5" secondAttribute="bottom" id="WB8-od-eAG"/>
<constraint firstItem="930-J1-9o0" firstAttribute="top" secondItem="aCY-1G-NL5" secondAttribute="top" id="aP7-3d-zW1"/>
<constraint firstItem="JfV-qu-qmI" firstAttribute="top" secondItem="aCY-1G-NL5" secondAttribute="top" id="b8D-tm-ZFK"/>
<constraint firstAttribute="centerX" secondItem="XIL-q9-MMp" secondAttribute="centerX" id="bas-rx-OTg"/>
<constraint firstItem="930-J1-9o0" firstAttribute="leading" secondItem="JfV-qu-qmI" secondAttribute="trailing" id="gTM-qA-I1T"/>
<constraint firstItem="aCY-1G-NL5" firstAttribute="top" secondItem="XIL-q9-MMp" secondAttribute="bottom" id="swb-Yx-lVo"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertView"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="centerY" secondItem="Ogu-AE-Ohh" secondAttribute="centerY" id="Kg9-hX-fvu"/>
<constraint firstAttribute="centerX" secondItem="Ogu-AE-Ohh" secondAttribute="centerX" id="dkW-i0-CZ2"/>
</constraints>
<viewLayoutGuide key="safeArea" id="n9h-Fb-ugd"/>
<connections>
<outlet property="textView" destination="lbr-Xd-hLF" id="ru3-Uu-UKw"/>
<outlet property="textViewHeight" destination="XLp-um-S4A" id="GmU-gr-jlY"/>
</connections>
<point key="canvasLocation" x="305" y="172"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
</resources>
</document>

View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<view contentMode="scaleToFill" id="zbk-bu-cTT" customClass="MWMSpinnerAlert" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fZW-Y8-F8A" userLabel="ContainerView">
<rect key="frame" x="47.5" y="264.5" width="280" height="138.5"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6Ik-Gs-Yc7" userLabel="button1">
<rect key="frame" x="0.0" y="94.5" width="280" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="ejP-Qr-J5O"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="cancel">
<color key="titleColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" backgroundImage="dialog_btn_press"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="cancel"/>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="bold17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="tap" destination="zbk-bu-cTT" eventType="touchUpInside" id="a2c-ED-0jy"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lX1-uE-UHN" userLabel="hDivider1">
<rect key="frame" x="0.0" y="93.5" width="280" 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="f9v-KZ-u2L"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kq1-MC-Bth">
<rect key="frame" x="124" y="16" width="32" height="32"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="BsB-R0-OVr"/>
<constraint firstAttribute="width" constant="32" id="ElH-YQ-cGZ"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JHp-iN-vQc" userLabel="Title">
<rect key="frame" x="20" y="56" width="240" height="21.5"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="20" id="57O-O2-CsM"/>
<constraint firstAttribute="width" constant="240" id="M8o-cf-xqy"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
<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="medium18:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.88" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="6Ik-Gs-Yc7" firstAttribute="top" secondItem="lX1-uE-UHN" secondAttribute="bottom" id="04f-3J-sOv"/>
<constraint firstItem="kq1-MC-Bth" firstAttribute="centerX" secondItem="fZW-Y8-F8A" secondAttribute="centerX" id="1bI-tg-lK9"/>
<constraint firstAttribute="width" constant="280" id="7A5-aI-a0x"/>
<constraint firstAttribute="centerX" secondItem="lX1-uE-UHN" secondAttribute="centerX" id="DKt-je-czo"/>
<constraint firstItem="kq1-MC-Bth" firstAttribute="top" secondItem="fZW-Y8-F8A" secondAttribute="top" constant="16" id="Ft7-Dd-lYK"/>
<constraint firstAttribute="width" secondItem="lX1-uE-UHN" secondAttribute="width" id="Qpr-nH-Na5"/>
<constraint firstAttribute="bottom" secondItem="6Ik-Gs-Yc7" secondAttribute="bottom" id="Tqv-1O-YVs"/>
<constraint firstItem="lX1-uE-UHN" firstAttribute="top" secondItem="JHp-iN-vQc" secondAttribute="bottom" constant="16" id="b0T-2G-xsF"/>
<constraint firstItem="6Ik-Gs-Yc7" firstAttribute="trailing" secondItem="lX1-uE-UHN" secondAttribute="trailing" id="tYJ-fJ-ov9"/>
<constraint firstItem="6Ik-Gs-Yc7" firstAttribute="leading" secondItem="lX1-uE-UHN" secondAttribute="leading" id="vDN-x9-Sw8"/>
<constraint firstAttribute="centerX" secondItem="JHp-iN-vQc" secondAttribute="centerX" id="wwC-8B-KBV"/>
<constraint firstItem="JHp-iN-vQc" firstAttribute="top" secondItem="kq1-MC-Bth" secondAttribute="bottom" constant="8" id="yW9-DY-uxC"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="AlertView"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="centerX" secondItem="fZW-Y8-F8A" secondAttribute="centerX" id="Ll6-go-lGy"/>
<constraint firstAttribute="centerY" secondItem="fZW-Y8-F8A" secondAttribute="centerY" id="Sz5-hL-nLu"/>
</constraints>
<viewLayoutGuide key="safeArea" id="fPw-k5-Wbc"/>
<connections>
<outlet property="cancelButton" destination="6Ik-Gs-Yc7" id="PcZ-Qo-ycv"/>
<outlet property="cancelHeight" destination="ejP-Qr-J5O" id="sxx-S8-gDN"/>
<outlet property="divider" destination="lX1-uE-UHN" id="BGf-W6-2UF"/>
<outlet property="progressView" destination="kq1-MC-Bth" id="FK3-oI-lWX"/>
<outlet property="title" destination="JHp-iN-vQc" id="6lQ-ps-HJD"/>
</connections>
<point key="canvasLocation" x="304.5" y="171.5"/>
</view>
</objects>
<resources>
<image name="dialog_btn_press" width="280" height="44"/>
</resources>
</document>

View file

@ -0,0 +1,43 @@
@objc(MWMSpinnerAlert)
final class SpinnerAlert: MWMAlert {
@IBOutlet private weak var progressView: UIView!
@IBOutlet private weak var title: UILabel!
@IBOutlet private weak var cancelHeight: NSLayoutConstraint!
@IBOutlet private weak var cancelButton: UIButton!
@IBOutlet private weak var divider: UIView!
private var cancel: MWMVoidBlock?
private lazy var progress: MWMCircularProgress = {
var p = MWMCircularProgress.downloaderProgress(forParentView: progressView)
return p
}()
@objc static func alert(title: String, cancel: MWMVoidBlock?) -> SpinnerAlert? {
guard let alert = Bundle.main.loadNibNamed(className(), owner: nil, options: nil)?.first
as? SpinnerAlert else {
assertionFailure()
return nil
}
alert.title.text = title
alert.progress.state = .spinner
alert.progress.setCancelButtonHidden()
if let cancel = cancel {
alert.cancel = cancel
} else {
alert.cancelHeight.constant = 0
alert.cancelButton.isHidden = true
alert.divider.isHidden = true
alert.setNeedsLayout()
}
return alert
}
@IBAction private func tap() {
close(cancel)
}
}

View file

@ -0,0 +1,122 @@
@objc
final class Toast: NSObject {
@objc
enum Alignment: Int {
case bottom
case top
}
private enum Constants {
static let presentationDuration: TimeInterval = 3
static let animationDuration: TimeInterval = kDefaultAnimationDuration
static let bottomOffset: CGFloat = 63
static let topOffset: CGFloat = 50
static let horizontalOffset: CGFloat = 16
static let labelOffsets = UIEdgeInsets(top: 10, left: 14, bottom: -10, right: -14)
static let maxWidth: CGFloat = 400
}
private static var toasts: [Toast] = []
private var blurView: UIVisualEffectView
private init(_ text: String) {
blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
blurView.setStyle(.toastBackground)
blurView.isUserInteractionEnabled = false
blurView.alpha = 0
blurView.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.text = text
label.setStyle(.toastLabel)
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
blurView.contentView.addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: blurView.contentView.leadingAnchor, constant: Constants.labelOffsets.left),
label.trailingAnchor.constraint(equalTo: blurView.contentView.trailingAnchor, constant: Constants.labelOffsets.right),
label.topAnchor.constraint(equalTo: blurView.contentView.topAnchor, constant: Constants.labelOffsets.top),
label.bottomAnchor.constraint(equalTo: blurView.contentView.bottomAnchor, constant: Constants.labelOffsets.bottom)
])
}
// MARK: - Public methods
@objc
static func show(withText text: String) {
show(withText: text, alignment: .bottom)
}
@objc
static func show(withText text: String, alignment: Alignment) {
show(withText: text, alignment: alignment, pinToSafeArea: true)
}
@objc
static func show(withText text: String, alignment: Alignment, pinToSafeArea: Bool) {
let toast = Toast(text)
toasts.append(toast)
toast.show(withAlignment: alignment, pinToSafeArea: pinToSafeArea)
}
@objc
static func hideAll() {
toasts.forEach { $0.hide() }
}
// MARK: - Private methods
private func show(withAlignment alignment: Alignment, pinToSafeArea: Bool) {
Self.hideAll()
guard let view = (UIApplication.shared.connectedScenes.filter { $0.activationState == .foregroundActive }.first(where: { $0 is UIWindowScene }) as? UIWindowScene)?.keyWindow else { return }
view.addSubview(blurView)
let leadingConstraint = blurView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: Constants.horizontalOffset)
let trailingConstraint = blurView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -Constants.horizontalOffset)
let maxWidthConstraint = blurView.widthAnchor.constraint(equalToConstant: Constants.maxWidth).withPriority(.defaultLow)
let verticalConstraint: NSLayoutConstraint
switch alignment {
case .bottom:
verticalConstraint = blurView.bottomAnchor.constraint(equalTo: pinToSafeArea ? view.safeAreaLayoutGuide.bottomAnchor : view.bottomAnchor,
constant: -Constants.bottomOffset)
case .top:
verticalConstraint = blurView.topAnchor.constraint(equalTo: pinToSafeArea ? view.safeAreaLayoutGuide.topAnchor : view.topAnchor,
constant: Constants.topOffset)
}
NSLayoutConstraint.activate([
leadingConstraint,
trailingConstraint,
maxWidthConstraint,
verticalConstraint,
blurView.centerXAnchor.constraint(equalTo: pinToSafeArea ? view.safeAreaLayoutGuide.centerXAnchor : view.centerXAnchor)
])
UIView.animate(withDuration: Constants.animationDuration, animations: {
self.blurView.alpha = 1
} , completion: { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.presentationDuration) {
self.hide()
}
})
}
private func hide() {
if self.blurView.superview != nil {
UIView.animate(withDuration: Constants.animationDuration,
animations: { self.blurView.alpha = 0 }) { [self] _ in
self.blurView.removeFromSuperview()
Self.toasts.removeAll(where: { $0 === self }) }
}
}
}
extension NSLayoutConstraint {
func withPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint {
self.priority = priority
return self
}
}

Some files were not shown because too many files have changed in this diff Show more