diff --git a/android/app/src/main/cpp/app/organicmaps/Framework.cpp b/android/app/src/main/cpp/app/organicmaps/Framework.cpp index d29a67a69e..0daa35680b 100644 --- a/android/app/src/main/cpp/app/organicmaps/Framework.cpp +++ b/android/app/src/main/cpp/app/organicmaps/Framework.cpp @@ -56,6 +56,8 @@ #include "base/math.hpp" #include "base/sunrise_sunset.hpp" +#include "ge0/url_generator.hpp" + #include "3party/open-location-code/openlocationcode.h" #include <memory> @@ -958,6 +960,15 @@ Java_app_organicmaps_Framework_nativeGetGe0Url(JNIEnv * env, jclass, jdouble lat return jni::ToJavaString(env, url); } +JNIEXPORT jstring JNICALL +Java_app_organicmaps_Framework_nativeGetGeoUri(JNIEnv * env, jclass, jdouble lat, jdouble lon, jdouble zoomLevel, jstring name) +{ + ::Framework * fr = frm(); + double const scale = (zoomLevel > 0 ? zoomLevel : fr->GetDrawScale()); + string const url = ge0::GenerateGeoUri(lat, lon, scale, jni::ToNativeString(env, name)); + return jni::ToJavaString(env, url); +} + JNIEXPORT jobject JNICALL Java_app_organicmaps_Framework_nativeGetDistanceAndAzimuth( JNIEnv * env, jclass, jdouble merX, jdouble merY, jdouble cLat, jdouble cLon, jdouble north) diff --git a/android/app/src/main/java/app/organicmaps/Framework.java b/android/app/src/main/java/app/organicmaps/Framework.java index b99bcaf85c..24cdac37ab 100644 --- a/android/app/src/main/java/app/organicmaps/Framework.java +++ b/android/app/src/main/java/app/organicmaps/Framework.java @@ -194,6 +194,7 @@ public class Framework public static native String nativeFormatSpeed(double speed); public static native String nativeGetGe0Url(double lat, double lon, double zoomLevel, String name); + public static native String nativeGetGeoUri(double lat, double lon, double zoomLevel, String name); public static native String nativeGetAddress(double lat, double lon); diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java index db2c6d6622..1cecb55493 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java @@ -2,6 +2,7 @@ package app.organicmaps.widget.placepage; import android.content.Context; import android.location.Location; +import android.net.Uri; import android.os.Bundle; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -238,6 +239,10 @@ public class PlacePageView extends Fragment implements View.OnClickListener, LinearLayout latlon = mFrame.findViewById(R.id.ll__place_latlon); latlon.setOnClickListener(this); + LinearLayout openIn = mFrame.findViewById(R.id.ll__place_open_in); + openIn.setOnClickListener(this); + openIn.setOnLongClickListener(this); + openIn.setVisibility(VISIBLE); mTvLatlon = mFrame.findViewById(R.id.tv__place_latlon); mWifi = mFrame.findViewById(R.id.ll__place_wifi); mTvWiFi = mFrame.findViewById(R.id.tv__place_wifi); @@ -579,6 +584,12 @@ public class PlacePageView extends Fragment implements View.OnClickListener, .apply(); refreshLatLon(); } + else if (id == R.id.ll__place_open_in) + { + final String uri = Framework.nativeGetGeoUri(mMapObject.getLat(), mMapObject.getLon(), + mMapObject.getScale(), mMapObject.getName()); + Utils.openUri(requireContext(), Uri.parse(uri)); + } else if (id == R.id.direction_frame) showBigDirection(); } @@ -614,6 +625,12 @@ public class PlacePageView extends Fragment implements View.OnClickListener, items.add(formatted); } } + else if (id == R.id.ll__place_open_in) + { + final String uri = Framework.nativeGetGeoUri(mMapObject.getLat(), mMapObject.getLon(), + mMapObject.getScale(), mMapObject.getName()); + PlacePageUtils.copyToClipboard(requireContext(), mFrame, uri); + } else if (id == R.id.ll__place_operator) items.add(mTvOperator.getText().toString()); else if (id == R.id.ll__place_network) diff --git a/android/app/src/main/res/drawable/ic_open_in.xml b/android/app/src/main/res/drawable/ic_open_in.xml new file mode 100644 index 0000000000..d7dabf4c19 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_open_in.xml @@ -0,0 +1,11 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal" + android:autoMirrored="true"> + <path + android:fillColor="@android:color/white" + android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L480,120L480,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,480L840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM388,628L332,572L704,200L560,200L560,120L840,120L840,400L760,400L760,256L388,628Z"/> +</vector> diff --git a/android/app/src/main/res/layout/place_page_details.xml b/android/app/src/main/res/layout/place_page_details.xml index 555693d7d7..849c73be0e 100644 --- a/android/app/src/main/res/layout/place_page_details.xml +++ b/android/app/src/main/res/layout/place_page_details.xml @@ -73,6 +73,8 @@ <!-- ToDo: Address is missing compared with iOS. It's shown in title but anyway .. --> <include layout="@layout/place_page_latlon"/> + + <include layout="@layout/place_page_open_in"/> </LinearLayout> <include diff --git a/android/app/src/main/res/layout/place_page_open_in.xml b/android/app/src/main/res/layout/place_page_open_in.xml new file mode 100644 index 0000000000..e7ae413819 --- /dev/null +++ b/android/app/src/main/res/layout/place_page_open_in.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/ll__place_open_in" + style="@style/PlacePageItemFrame" + android:tag="open_in" + tools:background="#20FF0000" + tools:visibility="visible"> + + <ImageView + android:id="@+id/iv__place_open_in" + style="@style/PlacePageMetadataIcon" + app:srcCompat="@drawable/ic_open_in" + app:tint="?colorAccent"/> + + <TextView + android:id="@+id/tv__place_open_in" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/open_in_app" + android:textAppearance="@style/MwmTextAppearance.PlacePage.Accent"/> +</LinearLayout> diff --git a/data/strings/strings.txt b/data/strings/strings.txt index 04ac0826a9..612cfa9681 100644 --- a/data/strings/strings.txt +++ b/data/strings/strings.txt @@ -30900,7 +30900,7 @@ [open_in_app] comment = Title for the "Open In Another App" button on the PlacePage. - tags = ios + tags = android,ios en = Open In Another App af = Maak oop in 'n ander toepassing ar = فتح في تطبيق آخر diff --git a/ge0/ge0_tests/url_generator_tests.cpp b/ge0/ge0_tests/url_generator_tests.cpp index 9d3c749a80..2cc6af79c9 100644 --- a/ge0/ge0_tests/url_generator_tests.cpp +++ b/ge0/ge0_tests/url_generator_tests.cpp @@ -1,6 +1,7 @@ #include "testing/testing.hpp" #include "ge0/url_generator.hpp" +#include "ge0/geo_url_parser.hpp" #include <string> @@ -9,6 +10,7 @@ using namespace std; namespace { int const kTestCoordBytes = 9; +double const kEps = 1e-10; } // namespace namespace ge0 @@ -339,4 +341,22 @@ UNIT_TEST(GenerateShortShowMapUrl_UnicodeMixedWithOtherChars) string res = GenerateShortShowMapUrl(0, 0, 19, "Back_in \xe2\x98\x84!\xd1\x8e\xd0\xbc"); TEST_EQUAL("om://8wAAAAAAAA/Back%20in_\xe2\x98\x84%21\xd1\x8e\xd0\xbc", res, ()); } + +UNIT_TEST(GenerateGeoUri_SmokeTest) +{ + string res = GenerateGeoUri(33.8904075, 35.5066454, 16.5, "Falafel M. Sahyoun"); + TEST_EQUAL("geo:33.8904075,35.5066454?z=16.5(Falafel%20M.%20Sahyoun)", res, ()); + + // geo:33.8904075,35.5066454?z=16.5(Falafel%20M.%20Sahyoun) + // geo:33.890408,35.506645?z=16.5(Falafel%20M.%20Sahyoun) + + geo::GeoURLInfo info; + geo::GeoParser parser; + TEST(parser.Parse(res, info), ()); + TEST_ALMOST_EQUAL_ABS(info.m_lat, 33.8904075, kEps, ()); + TEST_ALMOST_EQUAL_ABS(info.m_lon, 35.5066454, kEps, ()); + TEST_ALMOST_EQUAL_ABS(info.m_zoom, 16.5, kEps, ()); + TEST_EQUAL(info.m_label, "Falafel M. Sahyoun", ()); +} + } // namespace ge0 diff --git a/ge0/url_generator.cpp b/ge0/url_generator.cpp index 038b6fd968..afbf052220 100644 --- a/ge0/url_generator.cpp +++ b/ge0/url_generator.cpp @@ -1,8 +1,11 @@ #include "ge0/url_generator.hpp" +#include "coding/url.hpp" #include "base/assert.hpp" #include <cmath> +#include <iomanip> +#include <sstream> namespace { @@ -100,6 +103,17 @@ std::string GenerateShortShowMapUrl(double lat, double lon, double zoom, std::st return urlSample; } +std::string GenerateGeoUri(double lat, double lon, double zoom, std::string const & name) +{ + std::ostringstream oss; + oss << "geo:" << std::fixed << std::setprecision(7) << lat << ',' << lon << "?z=" << std::setprecision(1) << zoom; + + if (!name.empty()) + oss << '(' << url::UrlEncode(name) << ')'; + + return oss.str(); +} + char Base64Char(int x) { CHECK_GREATER_OR_EQUAL(x, 0, ()); diff --git a/ge0/url_generator.hpp b/ge0/url_generator.hpp index ee3e554517..2b0047cec1 100644 --- a/ge0/url_generator.hpp +++ b/ge0/url_generator.hpp @@ -19,6 +19,23 @@ inline static int const kMaxCoordBits = kMaxPointBytes * 3; // om://ZCoordba64/Name std::string GenerateShortShowMapUrl(double lat, double lon, double zoomLevel, std::string const & name); +// Generates a geo: uri. +// +// - https://datatracker.ietf.org/doc/html/rfc5870 +// - https://developer.android.com/guide/components/intents-common#Maps +// - https://developers.google.com/maps/documentation/urls/android-intents +// +// URL format: +// +// +-------------------------------- lat +// | +-------------------- lon +// | | +---- zoom +// | | | +-- url-encoded name +// | | | | +// | | | | +// geo:54.683486138,25.289361259&z=14(Forto%20dvaras) +std::string GenerateGeoUri(double lat, double lon, double zoom, std::string const & name); + // Exposed for testing. char Base64Char(int x); int LatToInt(double lat, int maxValue);