From 0e60e71a7fee8cdbb7193a9e4fda806f6b9a9475 Mon Sep 17 00:00:00 2001 From: Sergiy Kozyr Date: Mon, 20 Feb 2023 16:53:47 +0200 Subject: [PATCH] [Android][iOS] Social pages editor fix (#3248) * Removed hardcoded domains from UI (Android + iOS) code. Moved to native code `Framework.nativeGetPoiContactUrl()`. Automatically add domain name to social pages when upload changeset to OSM API. Removed "@" symbol for social contacts on Android and iOS. Simplified social contacts URLs on PlacePage. Only part of URL is shown. Fixed parsing of fb.com/profile.php?id=34496388 URLs Adde Line social network to iPhone. Added function strings::EndsWith(std::string const & s1, std::string_view s2); Introduced many constexpr variables in validate_and_format_contacts.cpp Added more tests Signed-off-by: S. Kozyr --- android/jni/app/organicmaps/Framework.cpp | 10 ++ android/jni/app/organicmaps/editor/Editor.cpp | 8 + android/src/app/organicmaps/Framework.java | 7 +- .../src/app/organicmaps/editor/Editor.java | 1 - .../placepage/PlacePageLinksFragment.java | 73 ++------ base/base_tests/string_utils_test.cpp | 12 ++ base/string_utils.cpp | 7 +- base/string_utils.hpp | 1 + data/editor.config | 2 + editor/editor_tests/editor_config_test.cpp | 1 + editor/editor_tests/xml_feature_test.cpp | 86 +++++++++ editor/xml_feature.cpp | 6 +- .../validate_and_format_contacts_test.cpp | 42 ++++- indexer/validate_and_format_contacts.cpp | 170 ++++++++++++++---- indexer/validate_and_format_contacts.hpp | 9 +- .../PlacePageData/Common/PlacePageInfoData.h | 1 + .../PlacePageData/Common/PlacePageInfoData.mm | 3 + .../ic_placepage_line.imageset/Contents.json | 26 +++ .../ic_placepage_line.png | Bin 0 -> 695 bytes .../ic_placepage_line@2x.png | Bin 0 -> 1446 bytes .../ic_placepage_line@3x.png | Bin 0 -> 2216 bytes .../Maps/UI/Editor/MWMEditorViewController.mm | 16 +- .../PlacePageInfoViewController.swift | 16 +- .../UI/PlacePage/PlacePageInteractor.swift | 4 + .../PlacePageManager/MWMPlacePageManager.mm | 20 ++- .../MWMPlacePageManagerHelper.h | 1 + .../MWMPlacePageManagerHelper.mm | 5 + 27 files changed, 412 insertions(+), 115 deletions(-) create mode 100644 iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/Contents.json create mode 100644 iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/ic_placepage_line.png create mode 100644 iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/ic_placepage_line@2x.png create mode 100644 iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/ic_placepage_line@3x.png diff --git a/android/jni/app/organicmaps/Framework.cpp b/android/jni/app/organicmaps/Framework.cpp index ad17613965..209e0b1608 100644 --- a/android/jni/app/organicmaps/Framework.cpp +++ b/android/jni/app/organicmaps/Framework.cpp @@ -1650,6 +1650,16 @@ Java_app_organicmaps_Framework_nativeDeleteBookmarkFromMapObject(JNIEnv * env, j return usermark_helper::CreateMapObject(env, g_framework->GetPlacePageInfo()); } +JNIEXPORT jstring JNICALL +Java_app_organicmaps_Framework_nativeGetPoiContactUrl(JNIEnv *env, jclass, jint id) +{ + auto const metaID = static_cast(id); + string_view const value = g_framework->GetPlacePageInfo().GetMetadata(metaID); + if (osm::isSocialContactTag(metaID)) + return jni::ToJavaString(env, osm::socialContactToURL(metaID, value)); + return jni::ToJavaString(env, value); +} + JNIEXPORT void JNICALL Java_app_organicmaps_Framework_nativeTurnOnChoosePositionMode(JNIEnv *, jclass, jboolean isBusiness, jboolean applyPosition) { diff --git a/android/jni/app/organicmaps/editor/Editor.cpp b/android/jni/app/organicmaps/editor/Editor.cpp index cbe58bb013..c4b295822f 100644 --- a/android/jni/app/organicmaps/editor/Editor.cpp +++ b/android/jni/app/organicmaps/editor/Editor.cpp @@ -100,6 +100,14 @@ Java_app_organicmaps_editor_Editor_nativeGetMetadata(JNIEnv * env, jclass, jint { auto const metaID = static_cast(id); ASSERT_LESS(metaID, osm::MapObject::MetadataID::FMD_COUNT, ()); + if (osm::isSocialContactTag(metaID)) + { + auto const value = g_editableMapObject.GetMetadata(metaID); + if (value.find('/') == std::string::npos) // `value` contains pagename. + return jni::ToJavaString(env, value); + // `value` contains URL. + return jni::ToJavaString(env, osm::socialContactToURL(metaID, value)); + } return jni::ToJavaString(env, g_editableMapObject.GetMetadata(metaID)); } diff --git a/android/src/app/organicmaps/Framework.java b/android/src/app/organicmaps/Framework.java index 05fbae2eae..9b39a3704c 100644 --- a/android/src/app/organicmaps/Framework.java +++ b/android/src/app/organicmaps/Framework.java @@ -335,13 +335,12 @@ public class Framework public static native boolean nativeIsIsolinesLayerEnabled(); - public static native void nativeSetGuidesLayerEnabled(boolean enabled); - - public static native boolean nativeIsGuidesLayerEnabled(); - @NonNull public static native MapObject nativeDeleteBookmarkFromMapObject(); + @NonNull + public static native String nativeGetPoiContactUrl(int metadataType); + public static native void nativeZoomToPoint(double lat, double lon, int zoom, boolean animate); /** diff --git a/android/src/app/organicmaps/editor/Editor.java b/android/src/app/organicmaps/editor/Editor.java index 19a303d9c6..66cb1e7691 100644 --- a/android/src/app/organicmaps/editor/Editor.java +++ b/android/src/app/organicmaps/editor/Editor.java @@ -72,7 +72,6 @@ public final class Editor public static native String nativeGetMetadata(int id); public static native boolean nativeIsMetadataValid(int id, String value); public static native void nativeSetMetadata(int id, String value); - public static native String nativeGetOpeningHours(); public static native void nativeSetOpeningHours(String openingHours); public static String nativeGetPhone() diff --git a/android/src/app/organicmaps/widget/placepage/PlacePageLinksFragment.java b/android/src/app/organicmaps/widget/placepage/PlacePageLinksFragment.java index bc70897337..d43f7dc08b 100644 --- a/android/src/app/organicmaps/widget/placepage/PlacePageLinksFragment.java +++ b/android/src/app/organicmaps/widget/placepage/PlacePageLinksFragment.java @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; +import app.organicmaps.Framework; import app.organicmaps.R; import app.organicmaps.bookmarks.data.MapObject; import app.organicmaps.bookmarks.data.Metadata; @@ -145,7 +146,7 @@ public class PlacePageLinksFragment extends Fragment implements Observer= 0) - refreshMetadataOrHide("https://" + webDomain + "/" + socialPage, view, tvSocialPage); - else - refreshMetadataOrHide("@" + socialPage, view, tvSocialPage); - } - - private void refreshSocialPageLink(View view, TextView tvSocialPage, String socialPage) - { - if (TextUtils.isEmpty(socialPage)) - view.setVisibility(GONE); - else if (socialPage.indexOf('/') >= 0) - refreshMetadataOrHide("https://" + socialPage, view, tvSocialPage); - else - refreshMetadataOrHide("@" + socialPage, view, tvSocialPage); + final String socialPage = mapObject.getMetadata(metaType); + refreshMetadataOrHide(socialPage, view, tvSocialPage); } private static String getWebsiteUrl(MapObject mapObject) @@ -229,15 +202,6 @@ public class PlacePageLinksFragment extends Fragment implements Observer + @@ -167,6 +168,7 @@ + diff --git a/editor/editor_tests/editor_config_test.cpp b/editor/editor_tests/editor_config_test.cpp index d2932e5d81..e609043f06 100644 --- a/editor/editor_tests/editor_config_test.cpp +++ b/editor/editor_tests/editor_config_test.cpp @@ -21,6 +21,7 @@ UNIT_TEST(EditorConfig_TypeDescription) EType::FMD_CONTACT_INSTAGRAM, EType::FMD_CONTACT_TWITTER, EType::FMD_CONTACT_VK, + EType::FMD_CONTACT_LINE, }; pugi::xml_document doc; diff --git a/editor/editor_tests/xml_feature_test.cpp b/editor/editor_tests/xml_feature_test.cpp index 5764d7d208..dac94b0812 100644 --- a/editor/editor_tests/xml_feature_test.cpp +++ b/editor/editor_tests/xml_feature_test.cpp @@ -480,3 +480,89 @@ UNIT_TEST(XMLFeature_Diet) ft.SetCuisine(""); TEST_EQUAL(ft.GetCuisine(), "", ()); } + +UNIT_TEST(XMLFeature_SocialContactsProcessing) +{ + { + std::string const nightclubStr = R"( + + + + + + + + )"; + + editor::XMLFeature xmlFeature(nightclubStr); + + osm::EditableMapObject emo; + editor::FromXML(xmlFeature, emo); + + // Read and write "contact:facebook" to apply normalization. + std::string contactFacebook(emo.GetMetadata(feature::Metadata::FMD_CONTACT_FACEBOOK)); + emo.SetMetadata(osm::MapObject::MetadataID::FMD_CONTACT_FACEBOOK, contactFacebook); + + // Read and write "contact:instagram" to apply normalization. + std::string contactInstagram(emo.GetMetadata(feature::Metadata::FMD_CONTACT_INSTAGRAM)); + emo.SetMetadata(osm::MapObject::MetadataID::FMD_CONTACT_INSTAGRAM, contactInstagram); + + // Read and write "contact:line" to apply normalization. + std::string contactLine(emo.GetMetadata(feature::Metadata::FMD_CONTACT_LINE)); + emo.SetMetadata(osm::MapObject::MetadataID::FMD_CONTACT_LINE, contactLine); + + auto convertedFt = editor::ToXML(emo, true); + + TEST(convertedFt.HasTag("contact:facebook"), ()); + TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "https://facebook.com/pages/Stereo-Plaza/118100041593935", ()); + + TEST(convertedFt.HasTag("contact:instagram"), ()); + TEST_EQUAL(convertedFt.GetTagValue("contact:instagram"), "https://instagram.com/p/CSy87IhMhfm", ()); + + TEST(convertedFt.HasTag("contact:line"), ()); + TEST_EQUAL(convertedFt.GetTagValue("contact:line"), "https://liff.line.me/1645278921-kWRPP32q/?accountId=673watcr", ()); + } +} + +UNIT_TEST(XMLFeature_SocialContactsProcessing_clean) +{ + { + std::string const nightclubStr = R"( + + + + + + + + )"; + + editor::XMLFeature xmlFeature(nightclubStr); + + osm::EditableMapObject emo; + editor::FromXML(xmlFeature, emo); + + // Read and write "contact:facebook" to apply normalization. + std::string contactFacebook(emo.GetMetadata(feature::Metadata::FMD_CONTACT_FACEBOOK)); + emo.SetMetadata(osm::MapObject::MetadataID::FMD_CONTACT_FACEBOOK, contactFacebook); + + // Read and write "contact:instagram" to apply normalization. + std::string contactInstagram(emo.GetMetadata(feature::Metadata::FMD_CONTACT_INSTAGRAM)); + emo.SetMetadata(osm::MapObject::MetadataID::FMD_CONTACT_INSTAGRAM, contactInstagram); + + // Read and write "contact:line" to apply normalization. + std::string contactLine(emo.GetMetadata(feature::Metadata::FMD_CONTACT_LINE)); + emo.SetMetadata(osm::MapObject::MetadataID::FMD_CONTACT_LINE, contactLine); + + auto convertedFt = editor::ToXML(emo, true); + + TEST(convertedFt.HasTag("contact:facebook"), ()); + TEST_EQUAL(convertedFt.GetTagValue("contact:facebook"), "PierreCardinPeru.oficial", ()); + + TEST(convertedFt.HasTag("contact:instagram"), ()); + TEST_EQUAL(convertedFt.GetTagValue("contact:instagram"), "fraback.genusswelt", ()); + + TEST(convertedFt.HasTag("contact:line"), ()); + TEST_EQUAL(convertedFt.GetTagValue("contact:line"), "015qevdv", ()); + } +} diff --git a/editor/xml_feature.cpp b/editor/xml_feature.cpp index 5ac1b1ba85..51021c8376 100644 --- a/editor/xml_feature.cpp +++ b/editor/xml_feature.cpp @@ -3,6 +3,7 @@ #include "indexer/classificator.hpp" #include "indexer/editable_map_object.hpp" #include "indexer/ftypes_matcher.hpp" +#include "indexer/validate_and_format_contacts.hpp" #include "coding/string_utf8_multilang.hpp" @@ -606,7 +607,10 @@ XMLFeature ToXML(osm::EditableMapObject const & object, bool serializeType) object.ForEachMetadataItem([&toFeature](string_view tag, string_view value) { - toFeature.SetTagValue(tag, value); + if (osm::isSocialContactTag(tag) && value.find('/') != std::string::npos) + toFeature.SetTagValue(tag, osm::socialContactToURL(tag, value)); + else + toFeature.SetTagValue(tag, value); }); return toFeature; diff --git a/indexer/indexer_tests/validate_and_format_contacts_test.cpp b/indexer/indexer_tests/validate_and_format_contacts_test.cpp index ea8212f9bf..536876effb 100644 --- a/indexer/indexer_tests/validate_and_format_contacts_test.cpp +++ b/indexer/indexer_tests/validate_and_format_contacts_test.cpp @@ -9,11 +9,15 @@ UNIT_TEST(EditableMapObject_ValidateAndFormat_facebook) TEST_EQUAL(osm::ValidateAndFormat_facebook(""), "", ()); TEST_EQUAL(osm::ValidateAndFormat_facebook("facebook.com/OpenStreetMap"), "OpenStreetMap", ()); TEST_EQUAL(osm::ValidateAndFormat_facebook("www.facebook.com/OpenStreetMap"), "OpenStreetMap", ()); + TEST_EQUAL(osm::ValidateAndFormat_facebook("www.facebook.fr/OpenStreetMap"), "OpenStreetMap", ()); TEST_EQUAL(osm::ValidateAndFormat_facebook("http://facebook.com/OpenStreetMap"), "OpenStreetMap", ()); TEST_EQUAL(osm::ValidateAndFormat_facebook("https://facebook.com/OpenStreetMap"), "OpenStreetMap", ()); TEST_EQUAL(osm::ValidateAndFormat_facebook("http://www.facebook.com/OpenStreetMap"), "OpenStreetMap", ()); TEST_EQUAL(osm::ValidateAndFormat_facebook("https://www.facebook.com/OpenStreetMap"), "OpenStreetMap", ()); + TEST_EQUAL(osm::ValidateAndFormat_facebook("https://de-de.facebook.de/Open_Street_Map"), "Open_Street_Map", ()); TEST_EQUAL(osm::ValidateAndFormat_facebook("https://en-us.facebook.com/OpenStreetMap"), "OpenStreetMap", ()); + TEST_EQUAL(osm::ValidateAndFormat_facebook("https://de-de.facebook.com/profile.php?id=100085707580841"), "100085707580841", ()); + TEST_EQUAL(osm::ValidateAndFormat_facebook("http://facebook.com/profile.php?share=app&id=100086487430889#m"), "100086487430889", ()); TEST_EQUAL(osm::ValidateAndFormat_facebook("some.good.page"), "some.good.page", ()); TEST_EQUAL(osm::ValidateAndFormat_facebook("@tree-house-interiors"), "tree-house-interiors", ()); @@ -61,14 +65,16 @@ UNIT_TEST(EditableMapObject_ValidateAndFormat_twitter) UNIT_TEST(EditableMapObject_ValidateAndFormat_vk) { TEST_EQUAL(osm::ValidateAndFormat_vk("vk.com/id404"), "id404", ()); - TEST_EQUAL(osm::ValidateAndFormat_vk("vkontakte.ru/id404"), "id404", ()); + TEST_EQUAL(osm::ValidateAndFormat_vk("vkontakte.ru/id4321"), "id4321", ()); + TEST_EQUAL(osm::ValidateAndFormat_vk("www.vkontakte.ru/id43210"), "id43210", ()); TEST_EQUAL(osm::ValidateAndFormat_vk("www.vk.com/id404"), "id404", ()); - TEST_EQUAL(osm::ValidateAndFormat_vk("http://vk.com/id404"), "id404", ()); - TEST_EQUAL(osm::ValidateAndFormat_vk("https://vk.com/id404"), "id404", ()); - TEST_EQUAL(osm::ValidateAndFormat_vk("https://vkontakte.ru/id404"), "id404", ()); - TEST_EQUAL(osm::ValidateAndFormat_vk("http://www.vk.com/id404"), "id404", ()); - TEST_EQUAL(osm::ValidateAndFormat_vk("https://www.vk.com/id404"), "id404", ()); - TEST_EQUAL(osm::ValidateAndFormat_vk("https://www.vk.com/id405/"), "id405", ()); + TEST_EQUAL(osm::ValidateAndFormat_vk("http://vk.com/ozon"), "ozon", ()); + TEST_EQUAL(osm::ValidateAndFormat_vk("https://vk.com/sklad169"), "sklad169", ()); + TEST_EQUAL(osm::ValidateAndFormat_vk("https://vkontakte.ru/id4321"), "id4321", ()); + TEST_EQUAL(osm::ValidateAndFormat_vk("https://www.vkontakte.ru/id4321"), "id4321", ()); + TEST_EQUAL(osm::ValidateAndFormat_vk("http://www.vk.com/ugona.net.expert"), "ugona.net.expert", ()); + TEST_EQUAL(osm::ValidateAndFormat_vk("https://www.vk.com/7cvetov18"), "7cvetov18", ()); + TEST_EQUAL(osm::ValidateAndFormat_vk("https://www.vk.com/7cvetov18/"), "7cvetov18", ()); TEST_EQUAL(osm::ValidateAndFormat_vk("@22ab.cdef"), "22ab.cdef", ()); TEST_EQUAL(osm::ValidateAndFormat_vk("instagram.com/hello_world"), "", ()); @@ -81,6 +87,7 @@ UNIT_TEST(EditableMapObject_ValidateAndFormat_contactLine) TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://line.me/ti/p/@dgxs9r6wad"), "dgxs9r6wad", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://line.me/ti/p/%40vne5uwke17"), "vne5uwke17", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("http://line.me/R/ti/p/bfsg1a8x9u"), "bfsg1a8x9u", ()); + TEST_EQUAL(osm::ValidateAndFormat_contactLine("line.me/R/ti/p/bfsg1a8x9u"), "bfsg1a8x9u", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://line.me/R/ti/p/gdltt7s380"), "gdltt7s380", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://line.me/R/ti/p/@sdb2pb3lsg"), "sdb2pb3lsg", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://line.me/R/ti/p/%40b30h5mdj11"), "b30h5mdj11", ()); @@ -91,6 +98,9 @@ UNIT_TEST(EditableMapObject_ValidateAndFormat_contactLine) TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://line.me/R/home/public/profile?id=r90ck7n1rq"), "r90ck7n1rq", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://page.line.me/fom5198h"), "fom5198h", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://page.line.me/qn58n8g?web=mobile"), "qn58n8g", ()); + TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://page.line.me/?accountId=673watcr"), "673watcr", ()); + TEST_EQUAL(osm::ValidateAndFormat_contactLine("page.line.me/?accountId=673watcr"), "673watcr", ()); + TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://liff.line.me/1645278921-kWRPP32q/?accountId=673watcr"), "liff.line.me/1645278921-kWRPP32q/?accountId=673watcr", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("https://abc.line.me/en/some/page?id=xaladqv"), "abc.line.me/en/some/page?id=xaladqv", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("@abcd"), "abcd", ()); TEST_EQUAL(osm::ValidateAndFormat_contactLine("@-hyphen-test-"), "-hyphen-test-", ()); @@ -103,11 +113,14 @@ UNIT_TEST(EditableMapObject_ValidateFacebookPage) TEST(osm::ValidateFacebookPage(""), ()); TEST(osm::ValidateFacebookPage("facebook.com/OpenStreetMap"), ()); TEST(osm::ValidateFacebookPage("www.facebook.com/OpenStreetMap"), ()); + TEST(osm::ValidateFacebookPage("www.facebook.fr/OpenStreetMap"), ()); TEST(osm::ValidateFacebookPage("http://facebook.com/OpenStreetMap"), ()); TEST(osm::ValidateFacebookPage("https://facebook.com/OpenStreetMap"), ()); TEST(osm::ValidateFacebookPage("http://www.facebook.com/OpenStreetMap"), ()); TEST(osm::ValidateFacebookPage("https://www.facebook.com/OpenStreetMap"), ()); + TEST(osm::ValidateFacebookPage("https://de-de.facebook.de/OpenStreetMap"), ()); TEST(osm::ValidateFacebookPage("https://en-us.facebook.com/OpenStreetMap"), ()); + TEST(osm::ValidateFacebookPage("https://www.facebook.com/profile.php?id=100085707580841"), ()); TEST(osm::ValidateFacebookPage("OpenStreetMap"), ()); TEST(osm::ValidateFacebookPage("some.good.page"), ()); TEST(osm::ValidateFacebookPage("Quaama-Volunteer-Bushfire-Brigade-526790054021506"), ()); @@ -248,3 +261,18 @@ UNIT_TEST(EditableMapObject_ValidateLinePage) TEST(!osm::ValidateLinePage("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), ()); TEST(!osm::ValidateLinePage("https://line.com/ti/p/invalid-domain"), ()); } + +UNIT_TEST(EditableMapObject_socialContactToURL) +{ + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_INSTAGRAM, "some_page_name"), "https://instagram.com/some_page_name", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_INSTAGRAM, "p/BvkgKZNDbqN"), "https://instagram.com/p/BvkgKZNDbqN", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_FACEBOOK, "100086487430889"), "https://facebook.com/100086487430889", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_FACEBOOK, "nova.poshta.official"), "https://facebook.com/nova.poshta.official", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_FACEBOOK, "pg/ESQ-336537783591903/about"), "https://facebook.com/pg/ESQ-336537783591903/about", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_TWITTER, "carmelopizza"), "https://twitter.com/carmelopizza", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_TWITTER, "demhamburguesa/status/688001869269078016"), "https://twitter.com/demhamburguesa/status/688001869269078016", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_VK, "beerhousebar"), "https://vk.com/beerhousebar", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_VK, "wall-41524_29351"), "https://vk.com/wall-41524_29351", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_LINE, "a26235875"), "https://line.me/R/ti/p/@a26235875", ()); + TEST_EQUAL(osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_LINE, "liff.line.me/1645278921-kWRPP32q/?accountId=673watcr"), "https://liff.line.me/1645278921-kWRPP32q/?accountId=673watcr", ()); +} \ No newline at end of file diff --git a/indexer/validate_and_format_contacts.cpp b/indexer/validate_and_format_contacts.cpp index 00c4ee462d..f544a452bb 100644 --- a/indexer/validate_and_format_contacts.cpp +++ b/indexer/validate_and_format_contacts.cpp @@ -17,22 +17,50 @@ static auto const s_badVkRegex = regex(R"(^\d\d\d.+$)"); static auto const s_goodVkRegex = regex(R"(^[A-Za-z0-9_.]{5,32}$)"); static auto const s_lineRegex = regex(R"(^[a-z0-9-_.]{4,20}$)"); -char const * const kWebsiteProtocols[] = {"http://", "https://"}; -size_t const kWebsiteProtocolDefaultIndex = 0; +constexpr string_view kFacebook{"contact:facebook"}; +constexpr string_view kInstagram{"contact:instagram"}; +constexpr string_view kTwitter{"contact:twitter"}; +constexpr string_view kVk{"contact:vk"}; +constexpr string_view kLine{"contact:line"}; + +constexpr string_view kProfilePhp{"profile.php"}; + +// Domains constants. +constexpr string_view kFbDot{"fb."}; +constexpr string_view kFacebookDot{"facebook."}; +constexpr string_view kInstagramCom{"instagram.com"}; +constexpr string_view kDotInstagramCom{".instagram.com"}; +constexpr string_view kTwitterCom{"twitter.com"}; +constexpr string_view kDotTwitterCom{".twitter.com"}; +constexpr string_view kVkCom{"vk.com"}; +constexpr string_view kVkontakteRu{"vkontakte.ru"}; +constexpr string_view kDotVkCom{".vk.com"}; +constexpr string_view kDotVkontakteRu{".vkontakte.ru"}; +constexpr string_view kLineMe{"line.me"}; +constexpr string_view kPageLineMe{"page.line.me"}; +constexpr string_view kDotLineMe{".line.me"}; + +// URLs constants +constexpr string_view kUrlFacebook{"https://facebook.com/"}; +constexpr string_view kUrlInstagram{"https://instagram.com/"}; +constexpr string_view kUrlTwitter{"https://twitter.com/"}; +constexpr string_view kUrlVk{"https://vk.com/"}; +constexpr string_view kUrlLine{"https://line.me/R/ti/p/@"}; +constexpr string_view kHttp{"http://"}; +constexpr string_view kHttps{"https://"}; size_t GetProtocolNameLength(string const & website) { - for (auto const & protocol : kWebsiteProtocols) - { - if (strings::StartsWith(website, protocol)) - return strlen(protocol); - } + if (strings::StartsWith(website, kHttp)) + return kHttp.size(); + if (strings::StartsWith(website, kHttps)) + return kHttps.size(); return 0; } bool IsProtocolSpecified(string const & website) { - return GetProtocolNameLength(website) > 0; + return strings::StartsWith(website, kHttp) || strings::StartsWith(website, kHttps); } // TODO: Current implementation looks only for restricted symbols from ASCII block ignoring @@ -60,7 +88,7 @@ bool containsInvalidFBSymbol(string const & facebookPage, size_t startIndex = 0) std::string ValidateAndFormat_website(std::string const & v) { if (!v.empty() && !IsProtocolSpecified(v)) - return kWebsiteProtocols[kWebsiteProtocolDefaultIndex] + v; + return string{kHttp}.append(v); return v; } @@ -90,12 +118,18 @@ string ValidateAndFormat_facebook(string const & facebookPage) url::Url const url = url::Url::FromString(facebookPage); string const domain = strings::MakeLowerCase(url.GetHost()); // Check Facebook domain name. - if (strings::EndsWith(domain, "facebook.com") || strings::EndsWith(domain, "fb.com") - || strings::EndsWith(domain, "fb.me") || strings::EndsWith(domain, "facebook.de") - || strings::EndsWith(domain, "facebook.fr")) + if (strings::StartsWith(domain, kFacebookDot) || strings::StartsWith(domain, kFbDot) || + domain.find(".facebook.") != string::npos || domain.find(".fb.") != string::npos) { auto webPath = url.GetPath(); - // Strip last '/' symbol + // In case of https://www.facebook.com/profile.php?id=100085707580841 extract only ID. + if (strings::StartsWith(webPath, kProfilePhp)) + { + std::string const * id = url.GetParamValue("id"); + return (id ? *id : std::string()); + } + + // Strip last '/' symbol. webPath.erase(webPath.find_last_not_of('/') + 1); return webPath; } @@ -120,8 +154,8 @@ string ValidateAndFormat_instagram(string const & instagramPage) url::Url const url = url::Url::FromString(instagramPage); string const domain = strings::MakeLowerCase(url.GetHost()); - // Check Instagram domain name. - if (domain == "instagram.com" || strings::EndsWith(domain, ".instagram.com")) + // Check Instagram domain name: "instagram.com" or "*.instagram.com". + if (domain == kInstagramCom || strings::EndsWith(domain, kDotInstagramCom)) { auto webPath = url.GetPath(); // Strip last '/' symbol. @@ -149,12 +183,12 @@ string ValidateAndFormat_twitter(string const & twitterPage) url::Url const url = url::Url::FromString(twitterPage); string const domain = strings::MakeLowerCase(url.GetHost()); - // Check Twitter domain name. - if (domain == "twitter.com" || strings::EndsWith(domain, ".twitter.com")) + // Check Twitter domain name: "twitter.com" or "*.twitter.com". + if (domain == kTwitterCom || strings::EndsWith(domain, kDotTwitterCom)) { auto webPath = url.GetPath(); - // Strip last '/' symbol and first '@' symbol + // Strip last '/' symbol and first '@' symbol. webPath.erase(webPath.find_last_not_of('/') + 1); webPath.erase(0, webPath.find_first_not_of('@')); @@ -190,9 +224,9 @@ string ValidateAndFormat_vk(string const & vkPage) url::Url const url = url::Url::FromString(vkPage); string const domain = strings::MakeLowerCase(url.GetHost()); - // Check VK domain name. - if (domain == "vk.com" || strings::EndsWith(domain, ".vk.com") || - domain == "vkontakte.ru" || strings::EndsWith(domain, ".vkontakte.ru")) + // Check VK domain name: "vk.com" or "vkontakte.ru" or "*.vk.com" or "*.vkontakte.ru". + if (domain == kVkCom || strings::EndsWith(domain, kDotVkCom) || + domain == kVkontakteRu || strings::EndsWith(domain, kDotVkontakteRu)) { auto webPath = url.GetPath(); // Strip last '/' symbol. @@ -239,13 +273,18 @@ string ValidateAndFormat_contactLine(string const & linePage) url::Url const url = url::Url::FromString(linePage); string const domain = strings::MakeLowerCase(url.GetHost()); // Check Line domain name. - if (domain == "page.line.me") + if (domain == kPageLineMe) { + // Parse https://page.line.me/?accountId={LINE ID} + std::string const * id = url.GetParamValue("accountId"); + if (id != nullptr) + return *id; + // Parse https://page.line.me/{LINE ID} string lineId = url.GetPath(); return stripAtSymbol(lineId); } - else if (domain == "line.me" || strings::EndsWith(domain, ".line.me")) + else if (domain == kLineMe || strings::EndsWith(domain, kDotLineMe)) { auto webPath = url.GetPath(); if (strings::StartsWith(webPath, "R/ti/p/")) @@ -265,14 +304,15 @@ string ValidateAndFormat_contactLine(string const & linePage) // Parse https://line.me/R/home/public/main?id={LINE ID without @} // and https://line.me/R/home/public/profile?id={LINE ID without @} std::string const * id = url.GetParamValue("id"); - return (id? *id : std::string()); + return (id ? *id : std::string()); } else { - if (strings::StartsWith(linePage, "http://")) + if (strings::StartsWith(linePage, kHttp)) return linePage.substr(7); - if (strings::StartsWith(linePage, "https://")) + if (strings::StartsWith(linePage, kHttps)) return linePage.substr(8); + return linePage; } } @@ -289,7 +329,7 @@ bool ValidateWebsite(string const & site) if (startPos >= site.size()) return false; - // Site should contain at least one dot but not at the begining/end. + // Site should contain at least one dot but not at the beginning/end. if ('.' == site[startPos] || '.' == site.back()) return false; @@ -320,7 +360,8 @@ bool ValidateFacebookPage(string const & page) return false; string const domain = strings::MakeLowerCase(url::Url::FromString(page).GetHost()); - return (strings::StartsWith(domain, "facebook.") || strings::StartsWith(domain, "fb.") || + // Validate domain name: "facebook.*" or "fb.*" or "*.facebook.*" or "*.fb.*". + return (strings::StartsWith(domain, kFacebookDot) || strings::StartsWith(domain, kFbDot) || domain.find(".facebook.") != string::npos || domain.find(".fb.") != string::npos); } @@ -337,7 +378,7 @@ bool ValidateInstagramPage(string const & page) return false; string const domain = strings::MakeLowerCase(url::Url::FromString(page).GetHost()); - return domain == "instagram.com" || strings::EndsWith(domain, ".instagram.com"); + return domain == kInstagramCom || strings::EndsWith(domain, kDotInstagramCom); } bool ValidateTwitterPage(string const & page) @@ -349,7 +390,7 @@ bool ValidateTwitterPage(string const & page) return regex_match(page, s_twitterRegex); // Rules are defined here: https://stackoverflow.com/q/11361044 string const domain = strings::MakeLowerCase(url::Url::FromString(page).GetHost()); - return domain == "twitter.com" || strings::EndsWith(domain, ".twitter.com"); + return domain == kTwitterCom || strings::EndsWith(domain, kDotTwitterCom); } bool ValidateVkPage(string const & page) @@ -382,8 +423,8 @@ bool ValidateVkPage(string const & page) return false; string const domain = strings::MakeLowerCase(url::Url::FromString(page).GetHost()); - return domain == "vk.com" || strings::EndsWith(domain, ".vk.com") - || domain == "vkontakte.ru" || strings::EndsWith(domain, ".vkontakte.ru"); + return domain == kVkCom || strings::EndsWith(domain, kDotVkCom) + || domain == kVkontakteRu || strings::EndsWith(domain, kDotVkontakteRu); } bool ValidateLinePage(string const & page) @@ -406,7 +447,70 @@ bool ValidateLinePage(string const & page) string const domain = strings::MakeLowerCase(url::Url::FromString(page).GetHost()); // Check Line domain name. - return (domain == "line.me" || strings::EndsWith(domain, ".line.me")); + return (domain == kLineMe || strings::EndsWith(domain, kDotLineMe)); +} + +bool isSocialContactTag(string_view tag) +{ + return tag == kInstagram || tag == kFacebook || tag == kTwitter || tag == kVk || tag == kLine; +} + +bool isSocialContactTag(MapObject::MetadataID const metaID) +{ + return metaID == MapObject::MetadataID::FMD_CONTACT_INSTAGRAM || + metaID == MapObject::MetadataID::FMD_CONTACT_FACEBOOK || + metaID == MapObject::MetadataID::FMD_CONTACT_TWITTER || + metaID == MapObject::MetadataID::FMD_CONTACT_VK || + metaID == MapObject::MetadataID::FMD_CONTACT_LINE; +} + +// Functions ValidateAndFormat_{facebook,instagram,twitter,vk}(...) by default strip domain name +// from OSM data and user input. This function prepends domain name to generate full URL. +string socialContactToURL(string_view tag, string_view value) +{ + ASSERT(!value.empty(), ()); + + if (tag == kInstagram) + return string{kUrlInstagram}.append(value); + if (tag == kFacebook) + return string{kUrlFacebook}.append(value); + if (tag == kTwitter) + return string{kUrlTwitter}.append(value); + if (tag == kVk) + return string{kUrlVk}.append(value); + if (tag == kLine) + { + if (value.find('/') == string::npos) // 'value' is a username. + return string{kUrlLine}.append(value); + else // 'value' is an URL. + return string{kHttps}.append(value); + } + + return string{value}; +} + +string socialContactToURL(MapObject::MetadataID metaID, string_view value) +{ + ASSERT(!value.empty(), ()); + + switch (metaID) + { + case MapObject::MetadataID::FMD_CONTACT_INSTAGRAM: + return string{kUrlInstagram}.append(value); + case MapObject::MetadataID::FMD_CONTACT_FACEBOOK: + return string{kUrlFacebook}.append(value); + case MapObject::MetadataID::FMD_CONTACT_TWITTER: + return string{kUrlTwitter}.append(value); + case MapObject::MetadataID::FMD_CONTACT_VK: + return string{kUrlVk}.append(value); + case MapObject::MetadataID::FMD_CONTACT_LINE: + if (value.find('/') == string::npos) // 'value' is a username. + return string{kUrlLine}.append(value); + else // 'value' is an URL. + return string{kHttps}.append(value); + default: + return string{value}; + } } } // namespace osm diff --git a/indexer/validate_and_format_contacts.hpp b/indexer/validate_and_format_contacts.hpp index 2d2612b15b..7e430c1656 100644 --- a/indexer/validate_and_format_contacts.hpp +++ b/indexer/validate_and_format_contacts.hpp @@ -2,6 +2,8 @@ #include +#include "map_object.hpp" + namespace osm { std::string ValidateAndFormat_website(std::string const & v); @@ -17,4 +19,9 @@ bool ValidateInstagramPage(std::string const & v); bool ValidateTwitterPage(std::string const & v); bool ValidateVkPage(std::string const & v); bool ValidateLinePage(std::string const & v); -} + +bool isSocialContactTag(std::string_view tag); +bool isSocialContactTag(osm::MapObject::MetadataID const metaID); +std::string socialContactToURL(std::string_view tag, std::string_view value); +std::string socialContactToURL(osm::MapObject::MetadataID metaID, std::string_view value); +} // namespace osm diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h index 9533f55927..a5da3f0f29 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.h @@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly, nullable) NSString *instagram; @property(nonatomic, readonly, nullable) NSString *twitter; @property(nonatomic, readonly, nullable) NSString *vk; +@property(nonatomic, readonly, nullable) NSString *line; @property(nonatomic, readonly, nullable) NSString *email; @property(nonatomic, readonly, nullable) NSURL *emailUrl; @property(nonatomic, readonly, nullable) NSString *cuisine; diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm index 8e265d8ac1..22690d7f60 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageInfoData.mm @@ -4,6 +4,8 @@ #import +#include "indexer/validate_and_format_contacts.hpp" + #include "map/place_page_info.hpp" using namespace place_page; @@ -56,6 +58,7 @@ using namespace osm; case MetadataID::FMD_CONTACT_INSTAGRAM: _instagram = ToNSString(value); break; case MetadataID::FMD_CONTACT_TWITTER: _twitter = ToNSString(value); break; case MetadataID::FMD_CONTACT_VK: _vk = ToNSString(value); break; + case MetadataID::FMD_CONTACT_LINE: _line = ToNSString(value); break; case MetadataID::FMD_OPERATOR: _ppOperator = ToNSString(value); break; case MetadataID::FMD_INTERNET: _wifiAvailable = (rawData.GetInternet() == osm::Internet::No) diff --git a/iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/Contents.json b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/Contents.json new file mode 100644 index 0000000000..62e2d1321d --- /dev/null +++ b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "ic_placepage_line.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ic_placepage_line@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_placepage_line@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/ic_placepage_line.png b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/ic_placepage_line.png new file mode 100644 index 0000000000000000000000000000000000000000..f7d4ffcd7887c8c97c6be042c23cd5e7af3fb031 GIT binary patch literal 695 zcmV;o0!aOdP)0003>0qKSLq5uE@8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10yIfPK~zYIy_U^O7Eu(3pLsP2#ZWAYh(2slEuX&H?*?HLOhEfHB}kk|dA+vZyWU9UG@xYD)}kBy1iEkf?`%hbv59 zQT11V!T$sU7);c0U~7)!Z(sy?0K5Vw!}vO24{#Vb0yO2s8AqMY$^C70OWv=m+tlHl zSktI8b&Rv>$vjX7_>}siPRyBlz&Z73Sy%);9%2**USBROf>w(H=hW?CQm5J%1?g2c zs3+7!?NAS>J?iQ3Yzn^J8LP|$zKH+>>S**mqMiwULu#Kore0HDs`u1`k^j5Jz)D6o zJ{HK>Phg1j%&qA=@Fm!4npiwB3s~gPo(0YWm!h!`zzY`kC_sA=V2|1w-UWKq&+6Uq zEnt!)_ko+y*fHQTuopmW19txty-t;$4Yo=soKCehJWXnQus5k~>gvd^IoK{_#I3t6 zKB?;qq&8FPW_79zQ1!XmwG7x=(~8Z~l0en>YJUmXM)ma)@p6GR)%IWA6|4KziJUlv zfwA7IyCP6Uy{P^wFn6XxTGC~-6X*`G9l$7X6lI+CBoLrWy+V2!_vJZ9nrsH1 zk$>Ya)^JJxnw}=6RrqL`0m>>p*iHM!MyooHHEo0&39T9$;Ur$K>0MT9^*dIWzOO8_ d`cG(($rm(?1LgT z5Gp7s=;MzdD*Iy>jG{sWOQ{HgqRkQ{qVS^v3!-#4SYz$W6mzX~)!o&ou-aYMWuN|- zxpw#NjB{r0%)RCV!|t5(o^zh(o;h>g_nl)zK++UoF)$mL;9u2I?J`0SaM-_^femK% zQ=*84RwQm5uo<{E(X>|{UjqxxtTSJ5QlPK? z3*fr)^GX#vfLb%_2FMjuyutrp9Ka0V_56(iNU8<4R*F$Zs5P^u{0y$FD3xrm62K%~ z4kX)4PJc|v0e4oKMTS^dh3BDUf!~4cz<0oY;5Xn8ziupWK5!{84e#YPHUkcGs_uqmuR&TuV-6Y-U@4RUy@v6!p~@VmB{} zG`P>;G)Y&P+3uop`G3##uRO-#J&t*s{Qrd!{&Kv>bas)=a0WjyL!5d^*8r1Uh{tig z?gpMIDq90=3b5US_qO~J_!TcdGaWeTD41Elq}zO=g*L;A`Z^NO|3l8Kq$)`({C-P8muo~H2W`MQya>J3%>Irk zZUV3#s7ry1b3~sM{gQr=v|0{Mf}{nKx>6k9IigRBVZhb?bslhkh_DfO>A(h(zxV=C z&NSfJL4}fL0dE3Rvlui7^kva;00yPqC5@7_8fYRTaX?>=@SI25*bN{lC+S}Q{s|In z-Z9WoML@ij5oSpmjrRq2Mw!MuRE3w+SyYC>9xlTe#b`1~lkq-@or$<2XJ&19vv{VV z$;|fV_nc+?mtQ$kP!Im5YGy6KI~ApqF5Wh?W*B^&awMMrnEAnijL{0z4LMF}X1#c4 zv$t1>SQ=;nZqJK@BEQc+DLW5XTV`UVLO>&MgY)?TdFNBMrRdpbg{_jV57QADoXXoL z7Xx#E@xVpED5qa3eRKl{fet($c7vJiJ5_^!0p!DLm*a2b>;M1&07*qoM6N<$f>!;P ATmS$7 literal 0 HcmV?d00001 diff --git a/iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/ic_placepage_line@3x.png b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_line.imageset/ic_placepage_line@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..d2cb0541328a3ae8ce7da60527ffd6db04127f60 GIT binary patch literal 2216 zcmV;Z2v_%sP)~MObu0Z*6U5Zgc=cZ*F#Fa&%>6Aa`kWXdq>JXK7|GV{dIBQ&vYHbZ;O~ zPDdbPZ*F#Fa&%>KEGgq7000N;NkldyF1c8ODFlvTY#(ZN-8WZ9&wc#RiL1 zTcM(YMV7^Q+ufTi7D9{)JA-Q91$Z+5=9d^2C!U$W`$nK|!y=jmL|yyrbOa` z4LAkpOk=MC90pzio(G-2ha^H0WPO~#Y@T|U_G!Lcu10yR7xbM z?JI!q12a>**WNq>+$w4PXgkIV8eQ6V1E0;wv3BQIz!&3g1q}tYJquV3OfSH(c4ilF zm88Gc+gMjn+w*{51Ct7Ltexovx+FbbyJ3W&w$B584@_=>W9>{IaEYWXLkouqYI_p! zXW;A>I@ZqY2Hq!W??6FE%?|f4CT25C1C|XfICh5GUO?z~jnO<^lGZ{bsBHm%0nRJa zt2X9uz${5e2QK+;#>C9ROrQ$}@1aY|^X0fm?mUU`pZ^x%Ea2_HJAo5g>}Mn9bpkU=bl?c^IIsqI z4A?B`f6W-MeKIf`_z-X<@Bzvbc!q3030&CXhBpH%fcqu=H&4U1-vnGoc+k$u({=)$ z!uEEzjy1MF*s7zmeUa_;Ej+&M*zRdTLr>VA(<&kB2s_XArWPFM&f0hRym=M)5-?ZN zlLfcc5J?+>Ilxzd{YAD_q@ddfLjFY3(E{5^grp;q?gB0#yk-|dQG%`pE^HLlE)z*R zfO&+TMeYyA8g+a+@4_frMACaQ}!BR7DwFE|DtAm8cG&H7uZ(P8D@kWq?M~pMDSQI5J(Q*rMi1ji^0Yi0{S5t^24%pop^Jaj*(Yh6jkuin z*W_5T&eyz&kmS{f#l%m7I3Vdchk2y)feFArqHRwDmk{CryK5^-ddc=&;2c6+c4M^Z zMnYCzT$_K7*`6(O?`)|!2W(Hvcv#hCznE02RI8;6M-chJNP?Rw-f zrnJamDr38|5co(|BY6ng0j#k7h8%2_g6*?_JM*xW*D&(A1W_|xE@fc*B;WyZnkbxS z=EyZ9>ugts=mhu~A=NP#^3HAghV9#Owpk=g2`55s6p?-RUE4o&=v!76+us9j$=hyG z2Ym~$+F=4grLe6?%&!+=zsR$w3)tc?%efNR9tZrCg2aSk1qC=0Snn`aUh3K<4&(Z3 zfG-r;POI*7R|C)5zN?H^RomwfazH;`bX%=_aEu4O4D7VM%J!AECluYN?JhzNK}#lq z$#xI%(U~IYBdosj8^Y3PJ$V_9dKupQ+4;_ND0&{}#4t9We&R!Mp!jS<^t0>2=K zck?ExoS?PbE9r)2D%ie?Fpsp1F59k3cfNsZZ6}&pYa(!xRZW~DJJof4-X#+Vg7x6mbK1s)(LG>23 z-b8i7c~zqaxFCA412w^s=nDefgn2<@#M4Ju{ikne;fQFHq-}&D;50MPUN7tg7DyT~ zJ+p53PST$U8O6H_cB~!g0WOyGM7@m-
2V6%by3v#R-SqaRJuHw4H2AC!7lXM*+ zk-v=D$5mkqaG9iql42JpXyyY|0e@^#fJ4*gloPPpHjc q@Dpn7Dsn#|(7v1DpHOQ$`2K&|DZ #include "platform/localization.hpp" +#include "indexer/validate_and_format_contacts.hpp" namespace { @@ -416,10 +417,15 @@ void registerCellsForTableView(std::vector const & cells, UITab icon:(NSString * _Nonnull)icon placeholder:(NSString * _Nonnull)name { + MetadataID metaId = static_cast(cellID); + NSString* value = ToNSString(m_mapObject.GetMetadata(metaId)); + if (osm::isSocialContactTag(metaId) && [value containsString:@"/"]) + value = ToNSString(osm::socialContactToURL(metaId, [value UTF8String])); + MWMEditorTextTableViewCell * tCell = static_cast(cell); [tCell configWithDelegate:self icon:[UIImage imageNamed:icon] - text:ToNSString(m_mapObject.GetMetadata(static_cast(cellID))) + text:value placeholder:name keyboardType:UIKeyboardTypeDefault capitalization:UITextAutocapitalizationTypeSentences]; @@ -662,6 +668,14 @@ void registerCellsForTableView(std::vector const & cells, UITab placeholder:L(@"vk")]; break; } + case MetadataID::FMD_CONTACT_LINE: + { + [self configTextViewCell:cell + cellID:cellID + icon:@"ic_placepage_line" + placeholder:L(@"line")]; + break; + } case MWMEditorCellTypeNote: { MWMNoteCell * tCell = static_cast(cell); diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift index 8f2428e6d7..a94cddb255 100644 --- a/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift +++ b/iphone/Maps/UI/PlacePage/Components/PlacePageInfoViewController.swift @@ -58,6 +58,7 @@ protocol PlacePageInfoViewControllerDelegate: AnyObject { func didPressInstagram() func didPressTwitter() func didPressVk() + func didPressLine() func didPressEmail() } @@ -84,6 +85,7 @@ class PlacePageInfoViewController: UIViewController { private var instagramView: InfoItemViewController? private var twitterView: InfoItemViewController? private var vkView: InfoItemViewController? + private var lineView: InfoItemViewController? private var cuisineView: InfoItemViewController? private var operatorView: InfoItemViewController? private var wifiView: InfoItemViewController? @@ -166,28 +168,34 @@ class PlacePageInfoViewController: UIViewController { } if let facebook = placePageInfoData.facebook { - facebookView = createInfoItem("@" + facebook, icon: UIImage(named: "ic_placepage_facebook"), style: .link) { [weak self] in + facebookView = createInfoItem(facebook, icon: UIImage(named: "ic_placepage_facebook"), style: .link) { [weak self] in self?.delegate?.didPressFacebook() } } if let instagram = placePageInfoData.instagram { - instagramView = createInfoItem("@" + instagram, icon: UIImage(named: "ic_placepage_instagram"), style: .link) { [weak self] in + instagramView = createInfoItem(instagram, icon: UIImage(named: "ic_placepage_instagram"), style: .link) { [weak self] in self?.delegate?.didPressInstagram() } } if let twitter = placePageInfoData.twitter { - twitterView = createInfoItem("@" + twitter, icon: UIImage(named: "ic_placepage_twitter"), style: .link) { [weak self] in + twitterView = createInfoItem(twitter, icon: UIImage(named: "ic_placepage_twitter"), style: .link) { [weak self] in self?.delegate?.didPressTwitter() } } if let vk = placePageInfoData.vk { - vkView = createInfoItem("@" + vk, icon: UIImage(named: "ic_placepage_vk"), style: .link) { [weak self] in + vkView = createInfoItem(vk, icon: UIImage(named: "ic_placepage_vk"), style: .link) { [weak self] in self?.delegate?.didPressVk() } } + + if let line = placePageInfoData.line { + lineView = createInfoItem(line, icon: UIImage(named: "ic_placepage_line"), style: .link) { [weak self] in + self?.delegate?.didPressLine() + } + } if let address = placePageInfoData.address { addressView = createInfoItem(address, icon: UIImage(named: "ic_placepage_adress")) diff --git a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift index 5eec841fcb..95cd6a50fd 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift @@ -57,6 +57,10 @@ extension PlacePageInteractor: PlacePageInfoViewControllerDelegate { MWMPlacePageManagerHelper.openVk(placePageData) } + func didPressLine() { + MWMPlacePageManagerHelper.openLine(placePageData) + } + func didPressEmail() { MWMPlacePageManagerHelper.openEmail(placePageData) } diff --git a/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManager.mm b/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManager.mm index d4d7b34603..a3f09197a7 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManager.mm +++ b/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManager.mm @@ -10,9 +10,12 @@ #import "location_util.h" #import +#import #include "platform/downloader_defines.hpp" +#include "indexer/validate_and_format_contacts.hpp" + using namespace storage; @interface MWMPlacePageManager () @@ -238,19 +241,28 @@ using namespace storage; } - (void)openFacebook:(PlacePageData *)data { - [self.ownerViewController openUrl:[NSString stringWithFormat:@"https://m.facebook.com/%@", data.infoData.facebook]]; + std::string const fullUrl = osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_FACEBOOK, [data.infoData.facebook UTF8String]); + [self.ownerViewController openUrl:ToNSString(fullUrl)]; } - (void)openInstagram:(PlacePageData *)data { - [self.ownerViewController openUrl:[NSString stringWithFormat:@"https://instagram.com/%@", data.infoData.instagram]]; + std::string const fullUrl = osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_INSTAGRAM, [data.infoData.instagram UTF8String]); + [self.ownerViewController openUrl:ToNSString(fullUrl)]; } - (void)openTwitter:(PlacePageData *)data { - [self.ownerViewController openUrl:[NSString stringWithFormat:@"https://mobile.twitter.com/%@", data.infoData.twitter]]; + std::string const fullUrl = osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_TWITTER, [data.infoData.twitter UTF8String]); + [self.ownerViewController openUrl:ToNSString(fullUrl)]; } - (void)openVk:(PlacePageData *)data { - [self.ownerViewController openUrl:[NSString stringWithFormat:@"https://vk.com/%@", data.infoData.vk]]; + std::string const fullUrl = osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_VK, [data.infoData.vk UTF8String]); + [self.ownerViewController openUrl:ToNSString(fullUrl)]; +} + +- (void)openLine:(PlacePageData *)data { + std::string const fullUrl = osm::socialContactToURL(osm::MapObject::MetadataID::FMD_CONTACT_LINE, [data.infoData.line UTF8String]); + [self.ownerViewController openUrl:ToNSString(fullUrl)]; } - (void)openEmail:(PlacePageData *)data { diff --git a/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManagerHelper.h b/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManagerHelper.h index 669eea8ca2..13fffc6b67 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManagerHelper.h +++ b/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManagerHelper.h @@ -15,6 +15,7 @@ + (void)openInstagram:(PlacePageData *)data; + (void)openTwitter:(PlacePageData *)data; + (void)openVk:(PlacePageData *)data; ++ (void)openLine:(PlacePageData *)data; + (void)call:(PlacePageData *)data; + (void)showAllFacilities:(PlacePageData *)data; + (void)showPlaceDescription:(NSString *)htmlString; diff --git a/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManagerHelper.mm b/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManagerHelper.mm index 099d1272eb..6fe4622cda 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManagerHelper.mm +++ b/iphone/Maps/UI/PlacePage/PlacePageManager/MWMPlacePageManagerHelper.mm @@ -22,6 +22,7 @@ - (void)openInstagram:(PlacePageData *)data; - (void)openTwitter:(PlacePageData *)data; - (void)openVk:(PlacePageData *)data; +- (void)openLine:(PlacePageData *)data; - (void)call:(PlacePageData *)data; - (void)showAllFacilities:(PlacePageData *)data; - (void)showPlaceDescription:(NSString *)htmlString; @@ -98,6 +99,10 @@ [[MWMMapViewControlsManager manager].placePageManager openVk:data]; } ++ (void)openLine:(PlacePageData *)data { + [[MWMMapViewControlsManager manager].placePageManager openLine:data]; +} + + (void)call:(PlacePageData *)data { [[MWMMapViewControlsManager manager].placePageManager call:data]; }