438 lines
15 KiB
Swift
438 lines
15 KiB
Swift
protocol PlacePageInteractorProtocol: AnyObject {
|
|
func viewWillAppear()
|
|
func viewWillDisappear()
|
|
func updateTopBound(_ bound: CGFloat, duration: TimeInterval)
|
|
}
|
|
|
|
class PlacePageInteractor: NSObject {
|
|
var presenter: PlacePagePresenterProtocol?
|
|
weak var viewController: UIViewController?
|
|
weak var mapViewController: MapViewController?
|
|
weak var trackActivePointPresenter: TrackActivePointPresenter?
|
|
|
|
private let bookmarksManager = BookmarksManager.shared()
|
|
private var placePageData: PlacePageData
|
|
private var viewWillAppearIsCalledForTheFirstTime = false
|
|
|
|
init(viewController: UIViewController, data: PlacePageData, mapViewController: MapViewController) {
|
|
self.placePageData = data
|
|
self.viewController = viewController
|
|
self.mapViewController = mapViewController
|
|
super.init()
|
|
addToBookmarksManagerObserverList()
|
|
subscribeOnTrackActivePointUpdatesIfNeeded()
|
|
}
|
|
|
|
deinit {
|
|
removeFromBookmarksManagerObserverList()
|
|
}
|
|
|
|
private func updatePlacePageIfNeeded() {
|
|
func updatePlacePage() {
|
|
FrameworkHelper.updatePlacePageData()
|
|
placePageData.updateBookmarkStatus()
|
|
}
|
|
|
|
switch placePageData.objectType {
|
|
case .POI, .trackRecording:
|
|
break
|
|
case .bookmark:
|
|
guard let bookmarkData = placePageData.bookmarkData, bookmarksManager.hasBookmark(bookmarkData.bookmarkId) else {
|
|
presenter?.closeAnimated()
|
|
return
|
|
}
|
|
updatePlacePage()
|
|
case .track:
|
|
guard let trackData = placePageData.trackData, bookmarksManager.hasTrack(trackData.trackId) else {
|
|
presenter?.closeAnimated()
|
|
return
|
|
}
|
|
updatePlacePage()
|
|
@unknown default:
|
|
fatalError("Unknown object type")
|
|
}
|
|
}
|
|
|
|
private func subscribeOnTrackActivePointUpdatesIfNeeded() {
|
|
unsubscribeFromTrackActivePointUpdates()
|
|
guard placePageData.objectType == .track, let trackData = placePageData.trackData else { return }
|
|
bookmarksManager.setElevationActivePointChanged(trackData.trackId) { [weak self] distance in
|
|
self?.trackActivePointPresenter?.updateActivePointDistance(distance)
|
|
trackData.updateActivePointDistance(distance)
|
|
}
|
|
bookmarksManager.setElevationMyPositionChanged(trackData.trackId) { [weak self] distance in
|
|
self?.trackActivePointPresenter?.updateMyPositionDistance(distance)
|
|
}
|
|
}
|
|
|
|
private func unsubscribeFromTrackActivePointUpdates() {
|
|
bookmarksManager.resetElevationActivePointChanged()
|
|
bookmarksManager.resetElevationMyPositionChanged()
|
|
}
|
|
|
|
private func addToBookmarksManagerObserverList() {
|
|
bookmarksManager.add(self)
|
|
}
|
|
|
|
private func removeFromBookmarksManagerObserverList() {
|
|
bookmarksManager.remove(self)
|
|
}
|
|
}
|
|
|
|
extension PlacePageInteractor: PlacePageInteractorProtocol {
|
|
func viewWillAppear() {
|
|
// Skip data reloading on the first appearance, to avoid unnecessary updates.
|
|
guard viewWillAppearIsCalledForTheFirstTime else {
|
|
viewWillAppearIsCalledForTheFirstTime = true
|
|
return
|
|
}
|
|
updatePlacePageIfNeeded()
|
|
}
|
|
|
|
func viewWillDisappear() {
|
|
unsubscribeFromTrackActivePointUpdates()
|
|
}
|
|
|
|
func updateTopBound(_ bound: CGFloat, duration: TimeInterval) {
|
|
mapViewController?.setPlacePageTopBound(bound, duration: duration)
|
|
}
|
|
}
|
|
|
|
// MARK: - PlacePageInfoViewControllerDelegate
|
|
|
|
extension PlacePageInteractor: PlacePageInfoViewControllerDelegate {
|
|
var shouldShowOpenInApp: Bool {
|
|
!OpenInApplication.availableApps.isEmpty
|
|
}
|
|
|
|
func didPressCall(to phone: PlacePagePhone) {
|
|
MWMPlacePageManagerHelper.call(phone)
|
|
}
|
|
|
|
func didPressWebsite() {
|
|
MWMPlacePageManagerHelper.openWebsite(placePageData)
|
|
}
|
|
|
|
func didPressWebsiteMenu() {
|
|
MWMPlacePageManagerHelper.openWebsiteMenu(placePageData)
|
|
}
|
|
|
|
func didPressWikipedia() {
|
|
MWMPlacePageManagerHelper.openWikipedia(placePageData)
|
|
}
|
|
|
|
func didPressWikimediaCommons() {
|
|
MWMPlacePageManagerHelper.openWikimediaCommons(placePageData)
|
|
}
|
|
|
|
func didPressFediverse() {
|
|
MWMPlacePageManagerHelper.openFediverse(placePageData)
|
|
}
|
|
|
|
func didPressFacebook() {
|
|
MWMPlacePageManagerHelper.openFacebook(placePageData)
|
|
}
|
|
|
|
func didPressInstagram() {
|
|
MWMPlacePageManagerHelper.openInstagram(placePageData)
|
|
}
|
|
|
|
func didPressTwitter() {
|
|
MWMPlacePageManagerHelper.openTwitter(placePageData)
|
|
}
|
|
|
|
func didPressVk() {
|
|
MWMPlacePageManagerHelper.openVk(placePageData)
|
|
}
|
|
|
|
func didPressLine() {
|
|
MWMPlacePageManagerHelper.openLine(placePageData)
|
|
}
|
|
|
|
func didPressBluesky() {
|
|
MWMPlacePageManagerHelper.openBluesky(placePageData)
|
|
}
|
|
|
|
func didPressPanoramax() {
|
|
MWMPlacePageManagerHelper.openPanoramax(placePageData)
|
|
}
|
|
|
|
func didPressEmail() {
|
|
MWMPlacePageManagerHelper.openEmail(placePageData)
|
|
}
|
|
|
|
func didCopy(_ content: String) {
|
|
UIPasteboard.general.string = content
|
|
let message = String(format: L("copied_to_clipboard"), content)
|
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
|
Toast.show(withText: message, alignment: .bottom)
|
|
}
|
|
|
|
func didPressOpenInApp(from sourceView: UIView) {
|
|
let availableApps = OpenInApplication.availableApps
|
|
guard !availableApps.isEmpty else {
|
|
LOG(.warning, "Applications selection sheet should not be presented when the list of available applications is empty.")
|
|
return
|
|
}
|
|
let openInAppActionSheet = UIAlertController.presentInAppActionSheet(from: sourceView, apps: availableApps) { [weak self] selectedApp in
|
|
guard let self else { return }
|
|
let link = selectedApp.linkWith(coordinates: self.placePageData.locationCoordinate, destinationName: self.placePageData.previewData.title)
|
|
self.mapViewController?.openUrl(link, externally: true)
|
|
}
|
|
presenter?.showAlert(openInAppActionSheet)
|
|
}
|
|
}
|
|
|
|
// MARK: - WikiDescriptionViewControllerDelegate
|
|
|
|
extension PlacePageInteractor: WikiDescriptionViewControllerDelegate {
|
|
func didPressMore() {
|
|
MWMPlacePageManagerHelper.showPlaceDescription(placePageData.wikiDescriptionHtml)
|
|
}
|
|
}
|
|
|
|
// MARK: - PlacePageButtonsViewControllerDelegate
|
|
|
|
extension PlacePageInteractor: PlacePageButtonsViewControllerDelegate {
|
|
func didPressHotels() {
|
|
MWMPlacePageManagerHelper.openDescriptionUrl(placePageData)
|
|
}
|
|
|
|
func didPressAddPlace() {
|
|
MWMPlacePageManagerHelper.addPlace(placePageData.locationCoordinate)
|
|
}
|
|
|
|
func didPressEditPlace() {
|
|
MWMPlacePageManagerHelper.editPlace()
|
|
}
|
|
|
|
func didPressAddBusiness() {
|
|
MWMPlacePageManagerHelper.addBusiness()
|
|
}
|
|
}
|
|
|
|
// MARK: - PlacePageEditBookmarkOrTrackViewControllerDelegate
|
|
|
|
extension PlacePageInteractor: PlacePageEditBookmarkOrTrackViewControllerDelegate {
|
|
func didUpdate(color: UIColor, category: MWMMarkGroupID, for data: PlacePageEditData) {
|
|
switch data {
|
|
case .bookmark(let bookmarkData):
|
|
let bookmarkColor = BookmarkColor.bookmarkColor(from: color) ?? bookmarkData.color
|
|
MWMPlacePageManagerHelper.updateBookmark(placePageData, color: bookmarkColor, category: category)
|
|
case .track:
|
|
MWMPlacePageManagerHelper.updateTrack(placePageData, color: color, category: category)
|
|
}
|
|
}
|
|
|
|
func didPressEdit(_ data: PlacePageEditData) {
|
|
switch data {
|
|
case .bookmark:
|
|
MWMPlacePageManagerHelper.editBookmark(placePageData)
|
|
case .track:
|
|
MWMPlacePageManagerHelper.editTrack(placePageData)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - ActionBarViewControllerDelegate
|
|
|
|
extension PlacePageInteractor: ActionBarViewControllerDelegate {
|
|
func actionBar(_ actionBar: ActionBarViewController, didPressButton type: ActionBarButtonType) {
|
|
switch type {
|
|
case .booking:
|
|
MWMPlacePageManagerHelper.book(placePageData)
|
|
case .bookingSearch:
|
|
MWMPlacePageManagerHelper.searchBookingHotels(placePageData)
|
|
case .bookmark:
|
|
if placePageData.bookmarkData != nil {
|
|
MWMPlacePageManagerHelper.removeBookmark(placePageData)
|
|
} else {
|
|
MWMPlacePageManagerHelper.addBookmark(placePageData)
|
|
}
|
|
case .call:
|
|
// since `.call` is a case in an obj-c enum, it can't have associated data, so there is no easy way to
|
|
// pass the exact phone, and we have to ask the user here which one to use, if there are multiple ones
|
|
let phones = placePageData.infoData?.phones ?? []
|
|
let hasOnePhoneNumber = phones.count == 1
|
|
if hasOnePhoneNumber {
|
|
MWMPlacePageManagerHelper.call(phones[0])
|
|
} else if (phones.count > 1) {
|
|
showPhoneNumberPicker(phones, handler: MWMPlacePageManagerHelper.call)
|
|
}
|
|
case .download:
|
|
guard let mapNodeAttributes = placePageData.mapNodeAttributes else {
|
|
fatalError("Download button can't be displayed if mapNodeAttributes is empty")
|
|
}
|
|
switch mapNodeAttributes.nodeStatus {
|
|
case .downloading, .inQueue, .applying:
|
|
Storage.shared().cancelDownloadNode(mapNodeAttributes.countryId)
|
|
case .notDownloaded, .partly, .error:
|
|
Storage.shared().downloadNode(mapNodeAttributes.countryId)
|
|
case .undefined, .onDiskOutOfDate, .onDisk:
|
|
fatalError("Download button shouldn't be displayed when node is in these states")
|
|
@unknown default:
|
|
fatalError()
|
|
}
|
|
case .opentable:
|
|
fatalError("Opentable is not supported and will be deleted")
|
|
case .routeAddStop:
|
|
MWMPlacePageManagerHelper.routeAddStop(placePageData)
|
|
case .routeFrom:
|
|
MWMPlacePageManagerHelper.route(from: placePageData)
|
|
case .routeRemoveStop:
|
|
MWMPlacePageManagerHelper.routeRemoveStop(placePageData)
|
|
case .routeTo:
|
|
MWMPlacePageManagerHelper.route(to: placePageData)
|
|
case .avoidToll:
|
|
MWMPlacePageManagerHelper.avoidToll()
|
|
case .avoidDirty:
|
|
MWMPlacePageManagerHelper.avoidDirty()
|
|
case .avoidFerry:
|
|
MWMPlacePageManagerHelper.avoidFerry()
|
|
case .more:
|
|
fatalError("More button should've been handled in ActionBarViewContoller")
|
|
case .track:
|
|
guard placePageData.trackData != nil else { return }
|
|
// TODO: (KK) This is temporary solution. Remove the dialog and use the MWMPlacePageManagerHelper.removeTrack
|
|
// directly here when the track recovery mechanism will be implemented.
|
|
showTrackDeletionConfirmationDialog()
|
|
case .saveTrackRecording:
|
|
// TODO: (KK) pass name typed by user
|
|
TrackRecordingManager.shared.stopAndSave() { [weak self] result in
|
|
switch result {
|
|
case .success:
|
|
break
|
|
case .trackIsEmpty:
|
|
self?.presenter?.closeAnimated()
|
|
}
|
|
}
|
|
case .notSaveTrackRecording:
|
|
TrackRecordingManager.shared.stop() { [weak self] result in
|
|
self?.presenter?.closeAnimated()
|
|
}
|
|
@unknown default:
|
|
fatalError()
|
|
}
|
|
}
|
|
|
|
private func showTrackDeletionConfirmationDialog() {
|
|
let alert = UIAlertController(title: nil, message: L("placepage_delete_track_confirmation_alert_message"), preferredStyle: .actionSheet)
|
|
let deleteAction = UIAlertAction(title: L("delete"), style: .destructive) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
guard self.placePageData.trackData != nil else {
|
|
fatalError("The track data should not be nil during the track deletion")
|
|
}
|
|
MWMPlacePageManagerHelper.removeTrack(self.placePageData)
|
|
self.presenter?.closeAnimated()
|
|
}
|
|
let cancelAction = UIAlertAction(title: L("cancel"), style: .cancel)
|
|
alert.addAction(deleteAction)
|
|
alert.addAction(cancelAction)
|
|
guard let viewController else { return }
|
|
iPadSpecific {
|
|
alert.popoverPresentationController?.sourceView = viewController.view
|
|
alert.popoverPresentationController?.sourceRect = viewController.view.frame
|
|
}
|
|
viewController.present(alert, animated: true)
|
|
}
|
|
|
|
private func showPhoneNumberPicker(_ phones: [PlacePagePhone], handler: @escaping (PlacePagePhone) -> Void) {
|
|
guard let viewController else { return }
|
|
|
|
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
phones.forEach({phone in
|
|
alert.addAction(UIAlertAction(title: phone.phone, style: .default, handler: { _ in
|
|
handler(phone)
|
|
}))
|
|
})
|
|
alert.addAction(UIAlertAction(title: L("cancel"), style: .cancel))
|
|
|
|
viewController.present(alert, animated: true)
|
|
}
|
|
}
|
|
|
|
// MARK: - ElevationProfileViewControllerDelegate
|
|
|
|
extension PlacePageInteractor: ElevationProfileViewControllerDelegate {
|
|
func openDifficultyPopup() {
|
|
MWMPlacePageManagerHelper.openElevationDifficultPopup(placePageData)
|
|
}
|
|
|
|
func updateMapPoint(_ point: CLLocationCoordinate2D, distance: Double) {
|
|
guard let trackData = placePageData.trackData, trackData.elevationProfileData?.isTrackRecording == false else { return }
|
|
bookmarksManager.setElevationActivePoint(point, distance: distance, trackId: trackData.trackId)
|
|
placePageData.trackData?.updateActivePointDistance(distance)
|
|
}
|
|
}
|
|
|
|
// MARK: - PlacePageHeaderViewController
|
|
|
|
extension PlacePageInteractor: PlacePageHeaderViewControllerDelegate {
|
|
func previewDidPressClose() {
|
|
presenter?.closeAnimated()
|
|
}
|
|
|
|
func previewDidPressExpand() {
|
|
presenter?.showNextStop()
|
|
}
|
|
|
|
func previewDidPressShare(from sourceView: UIView) {
|
|
guard let mapViewController else { return }
|
|
switch placePageData.objectType {
|
|
case .POI, .bookmark:
|
|
let shareViewController = ActivityViewController.share(forPlacePage: placePageData)
|
|
shareViewController.present(inParentViewController: mapViewController, anchorView: sourceView)
|
|
case .track:
|
|
presenter?.showShareTrackMenu()
|
|
default:
|
|
guard let coordinates = LocationManager.lastLocation()?.coordinate else {
|
|
viewController?.present(UIAlertController.unknownCurrentPosition(), animated: true, completion: nil)
|
|
return
|
|
}
|
|
let activity = ActivityViewController.share(forMyPosition: coordinates)
|
|
activity.present(inParentViewController: mapViewController, anchorView: sourceView)
|
|
}
|
|
}
|
|
|
|
func previewDidPressExportTrack(_ type: KmlFileType, from sourceView: UIView) {
|
|
guard let trackId = placePageData.trackData?.trackId else {
|
|
fatalError("Track data should not be nil during the track export")
|
|
}
|
|
bookmarksManager.shareTrack(trackId, fileType: type) { [weak self] status, url in
|
|
guard let self, let mapViewController else { return }
|
|
switch status {
|
|
case .success:
|
|
guard let url else { fatalError("Invalid sharing url") }
|
|
let shareViewController = ActivityViewController.share(for: url, message: self.placePageData.previewData.title!) { _,_,_,_ in
|
|
self.bookmarksManager.finishSharing()
|
|
}
|
|
shareViewController.present(inParentViewController: mapViewController, anchorView: sourceView)
|
|
case .emptyCategory:
|
|
self.showAlert(withTitle: L("bookmarks_error_title_share_empty"),
|
|
message: L("bookmarks_error_message_share_empty"))
|
|
case .archiveError, .fileError:
|
|
self.showAlert(withTitle: L("dialog_routing_system_error"),
|
|
message: L("bookmarks_error_message_share_general"))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func showAlert(withTitle title: String, message: String) {
|
|
MWMAlertViewController.activeAlert().presentInfoAlert(title, text: message)
|
|
}
|
|
}
|
|
|
|
// MARK: - BookmarksObserver
|
|
extension PlacePageInteractor: BookmarksObserver {
|
|
func onBookmarksLoadFinished() {
|
|
updatePlacePageIfNeeded()
|
|
}
|
|
|
|
func onBookmarksCategoryDeleted(_ groupId: MWMMarkGroupID) {
|
|
guard let bookmarkGroupId = placePageData.bookmarkData?.bookmarkGroupId else { return }
|
|
if bookmarkGroupId == groupId {
|
|
presenter?.closeAnimated()
|
|
}
|
|
}
|
|
}
|
|
|