mirror of
https://codeberg.org/comaps/comaps
synced 2026-01-06 04:24:29 +00:00
Multiple phone numbers should be [separated with `;`][parsing] in OSM `phone` values. This commit adds support for parsing and displaying such phone numbers individually. Example POI with three phone numbers: https://www.openstreetmap.org/way/233417266. Before this change, the phone was displayed as one value and trying to call it would fail because all the digits were concatenated together, resulting in an invalid number. For the POI above, the program tried to call `tel://+150332563111503325879018008756807`. This change fixes the parsing of `FMD_PHONE_NUMBER` into an array of phone numbers. That required updates in a few areas: - the POI details view (`PlacePageInfoViewController`) now displays every phone number as a separate row, each with a clickable link for that number; long-click to copy also works. - the share info preparation (`MWMShareActivityItem`) displays phone numbers separated with `; `, which provides a better phone detection for iOS. - the Call button (`PlacePageInteractor`) now has to ask the user which number to call if there are more than one. I tested this on an iPhone 15 Pro, iOS 17.2 simulator (temporarily commenting the "can make phone call" checks). Note: the Editing screen wasn't updated in order to keep this PR smaller. Fixes https://git.omaps.dev/organicmaps/organicmaps/issues/2458. The corresponding fix for Android was in https://github.com/organicmaps/organicmaps/pull/845. [parsing]: https://wiki.openstreetmap.org/wiki/Key:phone#Parsing_phone_numbers Signed-off-by: Eugene Nikolsky <omaps@egeek.me>
357 lines
12 KiB
Swift
357 lines
12 KiB
Swift
protocol PlacePageInteractorProtocol: AnyObject {
|
|
func viewWillAppear()
|
|
func updateTopBound(_ bound: CGFloat, duration: TimeInterval)
|
|
}
|
|
|
|
class PlacePageInteractor: NSObject {
|
|
var presenter: PlacePagePresenterProtocol?
|
|
weak var viewController: UIViewController?
|
|
weak var mapViewController: MapViewController?
|
|
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()
|
|
}
|
|
|
|
deinit {
|
|
removeFromBookmarksManagerObserverList()
|
|
}
|
|
|
|
private func updatePlacePageIfNeeded() {
|
|
let isBookmark = placePageData.bookmarkData != nil && bookmarksManager.hasBookmark(placePageData.bookmarkData!.bookmarkId)
|
|
let isTrack = placePageData.trackData != nil && bookmarksManager.hasTrack(placePageData.trackData!.trackId)
|
|
guard isBookmark || isTrack else {
|
|
presenter?.closeAnimated()
|
|
return
|
|
}
|
|
FrameworkHelper.updatePlacePageData()
|
|
placePageData.updateBookmarkStatus()
|
|
}
|
|
|
|
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 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 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 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.toast(withText: message).show(withAlignment: .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 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: This is temporary solution. Remove the dialog and use the MWMPlacePageManagerHelper.removeTrack
|
|
// directly here when the track recovery mechanism will be implemented.
|
|
showTrackDeletionConfirmationDialog()
|
|
@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 trackId = placePageData.trackData?.trackId else { return }
|
|
BookmarksManager.shared().setElevationActivePoint(point, distance: distance, trackId: trackId)
|
|
}
|
|
}
|
|
|
|
// 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:
|
|
fatalError()
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
|