From 50e6376afd4427d01187b2e1757f6b555a0bc4c0 Mon Sep 17 00:00:00 2001 From: Eugene Nikolsky Date: Wed, 2 Apr 2025 20:46:13 +0300 Subject: [PATCH] [ios] Support multiple phone numbers 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 --- .../PlacePageData/Common/PlacePageInfoData.h | 2 +- .../PlacePageData/Common/PlacePageInfoData.mm | 18 ++++++++------ .../Classes/Share/MWMShareActivityItem.mm | 7 +++++- .../Components/ActionBarViewController.swift | 3 ++- .../PlacePageInfoViewController.swift | 8 +++---- .../UI/PlacePage/PlacePageInteractor.swift | 24 ++++++++++++++++++- 6 files changed, 47 insertions(+), 15 deletions(-) diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h index b7b96ec0d..e2d5439de 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h @@ -9,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly, nullable) NSString *openingHoursString; @property(nonatomic, readonly, nullable) OpeningHours *openingHours; -@property(nonatomic, readonly, nullable) PlacePagePhone *phone; +@property(nonatomic, readonly) NSArray *phones; @property(nonatomic, readonly, nullable) NSString *website; @property(nonatomic, readonly, nullable) NSString *wikipedia; @property(nonatomic, readonly, nullable) NSString *wikimediaCommons; diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm index d168573c9..f30d67826 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm @@ -47,14 +47,18 @@ NSString * GetLocalizedMetadataValueString(MapObject::MetadataID metaID, std::st break; case MetadataID::FMD_PHONE_NUMBER: { - NSString *phone = ToNSString(value); - NSString *filteredDigits = [[phone componentsSeparatedByCharactersInSet: - [[NSCharacterSet decimalDigitCharacterSet] invertedSet]] - componentsJoinedByString:@""]; - NSString *resultNumber = [phone hasPrefix:@"+"] ? [NSString stringWithFormat:@"+%@", filteredDigits] : filteredDigits; - NSURL *phoneUrl = [NSURL URLWithString:[NSString stringWithFormat:@"tel://%@", resultNumber]]; + NSArray *phones = [ToNSString(value) componentsSeparatedByString:@";"]; + NSMutableArray *placePhones = [NSMutableArray new]; + [phones enumerateObjectsUsingBlock:^(NSString * _Nonnull phone, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *filteredDigits = [[phone componentsSeparatedByCharactersInSet: + [[NSCharacterSet decimalDigitCharacterSet] invertedSet]] + componentsJoinedByString:@""]; + NSString *resultNumber = [phone hasPrefix:@"+"] ? [NSString stringWithFormat:@"+%@", filteredDigits] : filteredDigits; + NSURL *phoneUrl = [NSURL URLWithString:[NSString stringWithFormat:@"tel://%@", resultNumber]]; - _phone = [PlacePagePhone placePagePhoneWithPhone:phone andURL:phoneUrl]; + [placePhones addObject:[PlacePagePhone placePagePhoneWithPhone:phone andURL:phoneUrl]]; + }]; + _phones = [placePhones copy]; break; } case MetadataID::FMD_WEBSITE: _website = ToNSString(value); break; diff --git a/iphone/Maps/Classes/Share/MWMShareActivityItem.mm b/iphone/Maps/Classes/Share/MWMShareActivityItem.mm index 1391ec643..64ef1c9e9 100644 --- a/iphone/Maps/Classes/Share/MWMShareActivityItem.mm +++ b/iphone/Maps/Classes/Share/MWMShareActivityItem.mm @@ -136,11 +136,16 @@ NSString * httpGe0Url(NSString * shortUrl) stringWithFormat:@"%@ %@\n%@", L(@"my_position_share_email_subject"), url, ge0Url]; } + NSMutableArray *phones = [NSMutableArray new]; + [self.data.infoData.phones enumerateObjectsUsingBlock:^(PlacePagePhone * _Nonnull phone, NSUInteger idx, BOOL * _Nonnull stop) { + [phones addObject:phone.phone]; + }]; + NSMutableString * result = [L(@"sharing_call_action_look") mutableCopy]; std::vector strings{self.data.previewData.title, self.data.previewData.subtitle, self.data.previewData.secondarySubtitle, - self.data.infoData.phone.phone, + [phones componentsJoinedByString:@"; "], url, ge0Url}; diff --git a/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift b/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift index 1954964d9..e70431130 100644 --- a/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift +++ b/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift @@ -66,7 +66,8 @@ final class ActionBarViewController: UIViewController { if isRoutePlanning { buttons.append(.routeFrom) } - if placePageData.infoData?.phone != nil, AppInfo.shared().canMakeCalls { + let hasAnyPhones = !(placePageData.infoData?.phones ?? []).isEmpty + if hasAnyPhones, AppInfo.shared().canMakeCalls { buttons.append(.call) } if !isRoutePlanning { diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift index 92cc91c1c..1b84292a2 100644 --- a/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift +++ b/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift @@ -103,7 +103,7 @@ class PlacePageInfoViewController: UIViewController { }() private var rawOpeningHoursView: InfoItemViewController? - private var phoneView: InfoItemViewController? + private var phoneViews: [InfoItemViewController] = [] private var websiteView: InfoItemViewController? private var websiteMenuView: InfoItemViewController? private var wikipediaView: InfoItemViewController? @@ -157,12 +157,12 @@ class PlacePageInfoViewController: UIViewController { /// @todo Entrance is missing compared with Android. It's shown in title, but anyway .. - if let phone = placePageInfoData.phone { + phoneViews = placePageInfoData.phones.map({ phone in var cellStyle: Style = .regular if let phoneUrl = phone.url, UIApplication.shared.canOpenURL(phoneUrl) { cellStyle = .link } - phoneView = createInfoItem(phone.phone, + return createInfoItem(phone.phone, icon: UIImage(named: "ic_placepage_phone_number"), style: cellStyle, tapHandler: { [weak self] in @@ -171,7 +171,7 @@ class PlacePageInfoViewController: UIViewController { longPressHandler: { [weak self] in self?.delegate?.didCopy(phone.phone) }) - } + }) if let ppOperator = placePageInfoData.ppOperator { operatorView = createInfoItem(ppOperator, icon: UIImage(named: "ic_placepage_operator")) diff --git a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift index be1264c81..0ef25dd0e 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift @@ -188,7 +188,15 @@ extension PlacePageInteractor: ActionBarViewControllerDelegate { MWMPlacePageManagerHelper.addBookmark(placePageData) } case .call: - MWMPlacePageManagerHelper.call(placePageData.infoData?.phone) + // 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") @@ -251,6 +259,20 @@ extension PlacePageInteractor: ActionBarViewControllerDelegate { } 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