From 2b630964d03f41c108eb6a554d5c5bd17812e63a Mon Sep 17 00:00:00 2001 From: NoelClick Date: Wed, 3 Dec 2025 12:18:12 -0800 Subject: [PATCH] [android] Unify custom map server dialog logic and UI - Add setting icon for custom map server download option. - Extract shared custom map server dialog helper. - Clean up based on reviewers' feedback. Signed-off-by: NoelClick --- CONTRIBUTORS | 1 + .../DownloadResourcesLegacyActivity.java | 64 ++------------ .../dialog/CustomMapServerDialog.java | 86 +++++++++++++++++++ .../settings/SettingsPrefsFragment.java | 75 ++-------------- .../main/res/drawable/ic_cloud_download.xml | 11 +++ .../layout/activity_download_resources.xml | 2 +- .../res/layout/dialog_custom_map_server.xml | 3 +- android/app/src/main/res/values/strings.xml | 8 +- android/app/src/main/res/xml/prefs_main.xml | 4 +- android/sdk/build.gradle | 1 + .../sdk/DownloadResourcesLegacyActivity.cpp | 18 ++-- .../java/app/organicmaps/sdk/Framework.java | 16 ++-- .../java/app/organicmaps/sdk/OrganicMaps.java | 4 +- libs/platform/platform.cpp | 5 -- libs/storage/map_files_downloader.cpp | 19 ++-- 15 files changed, 147 insertions(+), 170 deletions(-) create mode 100644 android/app/src/main/java/app/organicmaps/dialog/CustomMapServerDialog.java create mode 100644 android/app/src/main/res/drawable/ic_cloud_download.xml diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 0494e23a6..c2899d4d4 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -10,6 +10,7 @@ CoMaps contributors: Bastian Greshake Tzovaras clover sage Harry Bond +NoelClick thesupertechie vikiawv Yannik Bloscheck diff --git a/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java b/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java index d1b9edcb8..eed64ac96 100644 --- a/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java +++ b/android/app/src/main/java/app/organicmaps/DownloadResourcesLegacyActivity.java @@ -13,15 +13,11 @@ import static app.organicmaps.sdk.DownloadResourcesLegacyActivity.nativeStartNex import android.annotation.SuppressLint; import android.app.Dialog; import android.content.ComponentName; -import android.content.DialogInterface; import android.content.Intent; -import android.content.SharedPreferences; import android.location.Location; import android.os.Bundle; -import android.preference.PreferenceManager; import android.text.TextUtils; import android.view.View; -import android.widget.Button; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -29,18 +25,16 @@ import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import androidx.appcompat.app.AlertDialog; import androidx.core.view.ViewCompat; import com.google.android.material.button.MaterialButton; import com.google.android.material.checkbox.MaterialCheckBox; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.progressindicator.LinearProgressIndicator; -import com.google.android.material.textfield.TextInputEditText; -import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textview.MaterialTextView; import app.organicmaps.base.BaseMwmFragmentActivity; +import app.organicmaps.dialog.CustomMapServerDialog; import app.organicmaps.downloader.MapManagerHelper; import app.organicmaps.intent.Factory; import app.organicmaps.sdk.Framework; @@ -281,7 +275,11 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity mChbDownloadCountry = findViewById(R.id.chb_download_country); mBtnAdvanced = findViewById(R.id.btn_advanced); - mBtnAdvanced.setOnClickListener(v -> openCustomServerDialog()); + mBtnAdvanced.setOnClickListener(v -> { + CustomMapServerDialog.show(this, url -> { + prepareFilesDownload(false); + }); + }); mBtnAdvanced.setEnabled(true); mBtnListeners = new View.OnClickListener[BTN_COUNT]; @@ -414,56 +412,6 @@ public class DownloadResourcesLegacyActivity extends BaseMwmFragmentActivity } } - private void openCustomServerDialog() - { - View dialogView = getLayoutInflater().inflate(R.layout.dialog_custom_map_server, null); - TextInputLayout til = dialogView.findViewById(R.id.til_custom_map_server); - TextInputEditText edit = dialogView.findViewById(R.id.edit_custom_map_server); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - String current = prefs.getString(getString(R.string.pref_custom_map_download_url), ""); - edit.setText(current); - - MaterialAlertDialogBuilder builder = - new MaterialAlertDialogBuilder(this, R.style.MwmTheme_AlertDialog) - .setTitle(R.string.download_resources_custom_url_title) - .setMessage(R.string.download_resources_custom_url_message) - .setView(dialogView) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.save, null); - - AlertDialog dialog = builder.create(); - dialog.setOnShowListener(d -> { - Button ok = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - ok.setOnClickListener(v -> { - String url = edit.getText() != null ? edit.getText().toString().trim() : ""; - - if (!url.isEmpty() - && !url.startsWith("http://") - && !url.startsWith("https://")) { - til.setError(getString(R.string.download_resources_custom_url_error_scheme)); - return; - } - - til.setError(null); - - prefs.edit() - .putString(getString(R.string.pref_custom_map_download_url), url) - .apply(); - - // Apply to native + reset meta configs - Framework.applyCustomMapDownloadUrl(this, url); - - // Recompute total bytes (it can change with another server) - prepareFilesDownload(false); - - dialog.dismiss(); - }); - }); - - dialog.show(); - } - private void showErrorDialog(int result) { if (mAlertDialog != null && mAlertDialog.isShowing()) diff --git a/android/app/src/main/java/app/organicmaps/dialog/CustomMapServerDialog.java b/android/app/src/main/java/app/organicmaps/dialog/CustomMapServerDialog.java new file mode 100644 index 000000000..5aa0d4751 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/dialog/CustomMapServerDialog.java @@ -0,0 +1,86 @@ +package app.organicmaps.dialog; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceManager; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import app.organicmaps.R; +import app.organicmaps.sdk.Framework; + +public final class CustomMapServerDialog +{ + public interface OnUrlAppliedListener + { + void onUrlApplied(@NonNull String url); + } + + private CustomMapServerDialog() {} + + public static void show(@NonNull Context context, + @Nullable OnUrlAppliedListener listener) + { + View dialogView = LayoutInflater.from(context) + .inflate(R.layout.dialog_custom_map_server, null); + TextInputLayout til = dialogView.findViewById(R.id.til_custom_map_server); + TextInputEditText edit = dialogView.findViewById(R.id.edit_custom_map_server); + + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(context); + String current = prefs.getString(context.getString(R.string.pref_custom_map_download_url), ""); + edit.setText(current); + + MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(context, R.style.MwmTheme_AlertDialog) + .setTitle(R.string.download_resources_custom_url_title) + .setMessage(R.string.download_resources_custom_url_message) + .setView(dialogView) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.save, null); + + AlertDialog dialog = builder.create(); + dialog.setOnShowListener(dlg -> { + Button ok = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + ok.setOnClickListener(v -> { + String url = edit.getText() != null ? edit.getText().toString().trim() : ""; + + if (!url.isEmpty() + && !url.startsWith("http://") + && !url.startsWith("https://")) + { + til.setError(context.getString(R.string.download_resources_custom_url_error_scheme)); + return; + } + + til.setError(null); + + String normalizedUrl = Framework.normalizeServerUrl(url); + + prefs.edit() + .putString(context.getString(R.string.pref_custom_map_download_url), normalizedUrl) + .apply(); + + // Apply to native + Framework.applyCustomMapDownloadUrl(context, normalizedUrl); + + if (listener != null) + listener.onUrlApplied(normalizedUrl); + + dialog.dismiss(); + }); + }); + + dialog.show(); + } +} diff --git a/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java b/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java index 0a90eb017..0f5ba4ae7 100644 --- a/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java +++ b/android/app/src/main/java/app/organicmaps/settings/SettingsPrefsFragment.java @@ -3,17 +3,13 @@ package app.organicmaps.settings; import static app.organicmaps.leftbutton.LeftButtonsHolder.DISABLE_BUTTON_CODE; import android.annotation.SuppressLint; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; -import android.view.LayoutInflater; import android.view.View; -import android.widget.Button; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; @@ -21,11 +17,10 @@ import androidx.preference.PreferenceManager; import androidx.preference.TwoStatePreference; import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.textfield.TextInputEditText; -import com.google.android.material.textfield.TextInputLayout; import app.organicmaps.MwmApplication; import app.organicmaps.R; +import app.organicmaps.dialog.CustomMapServerDialog; import app.organicmaps.downloader.OnmapDownloader; import app.organicmaps.editor.LanguagesFragment; import app.organicmaps.editor.ProfileActivity; @@ -555,79 +550,25 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()); String current = prefs.getString(getString(R.string.pref_custom_map_download_url), ""); - String normalized = current.trim(); - if (!normalized.isEmpty() && !normalized.endsWith("/")) - normalized = normalized + "/"; + String normalizedUrl = Framework.normalizeServerUrl(current); // Initial summary - customUrlPref.setSummary(normalized.isEmpty() + customUrlPref.setSummary(normalizedUrl.isEmpty() ? getString(R.string.download_resources_custom_url_summary_none) - : normalized); + : normalizedUrl); // Sync native - Framework.applyCustomMapDownloadUrl(requireContext(), normalized); + Framework.applyCustomMapDownloadUrl(requireContext(), normalizedUrl); // Show dialog customUrlPref.setOnPreferenceClickListener(preference -> { - openCustomServerDialog(customUrlPref); - return true; - }); - } - - private void openCustomServerDialog(Preference pref) - { - View dialogView = LayoutInflater.from(requireContext()) - .inflate(R.layout.dialog_custom_map_server, null); - TextInputLayout til = dialogView.findViewById(R.id.til_custom_map_server); - TextInputEditText edit = dialogView.findViewById(R.id.edit_custom_map_server); - - SharedPreferences prefs = - PreferenceManager.getDefaultSharedPreferences(requireContext()); - String current = prefs.getString(getString(R.string.pref_custom_map_download_url), ""); - edit.setText(current); - - MaterialAlertDialogBuilder builder = - new MaterialAlertDialogBuilder(requireContext(), R.style.MwmTheme_AlertDialog) - .setTitle(R.string.download_resources_custom_url_title) - .setMessage(R.string.download_resources_custom_url_message) - .setView(dialogView) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.save, null); - - AlertDialog dialog = builder.create(); - dialog.setOnShowListener(dlg -> { - Button ok = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - ok.setOnClickListener(v -> { - String url = edit.getText() != null ? edit.getText().toString().trim() : ""; - - if (!url.isEmpty() - && !url.startsWith("http://") - && !url.startsWith("https://")) { - til.setError(getString(R.string.download_resources_custom_url_error_scheme)); - return; - } - - til.setError(null); - - if (!url.isEmpty() && !url.endsWith("/")) - url = url + "/"; - - prefs.edit() - .putString(getString(R.string.pref_custom_map_download_url), url) - .apply(); - - // Apply native - Framework.applyCustomMapDownloadUrl(requireContext(), url); - - pref.setSummary(url.isEmpty() + CustomMapServerDialog.show(requireContext(), url -> { + preference.setSummary(url.isEmpty() ? getString(R.string.download_resources_custom_url_summary_none) : url); - - dialog.dismiss(); }); + return true; }); - - dialog.show(); } private void removePreference(@NonNull String categoryKey, @NonNull Preference preference) diff --git a/android/app/src/main/res/drawable/ic_cloud_download.xml b/android/app/src/main/res/drawable/ic_cloud_download.xml new file mode 100644 index 000000000..e43bc779a --- /dev/null +++ b/android/app/src/main/res/drawable/ic_cloud_download.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/android/app/src/main/res/layout/activity_download_resources.xml b/android/app/src/main/res/layout/activity_download_resources.xml index 1d7a618aa..94b35a94a 100644 --- a/android/app/src/main/res/layout/activity_download_resources.xml +++ b/android/app/src/main/res/layout/activity_download_resources.xml @@ -17,7 +17,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" app:icon="@drawable/ic_settings" - app:iconTint="?colorOnSurface" + app:iconTint="?iconTint" android:contentDescription="@string/download_resources_custom_url_title" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/android/app/src/main/res/layout/dialog_custom_map_server.xml b/android/app/src/main/res/layout/dialog_custom_map_server.xml index cb8dd891c..88fb8ae04 100644 --- a/android/app/src/main/res/layout/dialog_custom_map_server.xml +++ b/android/app/src/main/res/layout/dialog_custom_map_server.xml @@ -10,7 +10,8 @@ android:paddingTop="@dimen/margin_base" android:paddingBottom="@dimen/margin_half" android:hint="@string/download_resources_custom_url_title" - app:placeholderText="@string/download_resources_custom_url_hint"> + app:placeholderText="@string/download_resources_custom_url_hint" + app:endIconMode="clear_text"> A map needs to be downloaded to view and navigate the area.\nDownload maps for areas you want to travel. Edit the list to add a description - Custom Map Server - Override the default map download server used for map downloads. Leave empty to use CoMaps default server. - https://maps.comaps.app/ - Advanced… Custom Map Server Override the default map download server used for map downloads. Leave empty to use CoMaps default server. - https://maps.comaps.app/ + https://cdn-fi-1.comaps.app/ Not set - Please enter a full URL starting with http:// or https:// and ending with /. + Please enter a full URL starting with https:// and ending with / diff --git a/android/app/src/main/res/xml/prefs_main.xml b/android/app/src/main/res/xml/prefs_main.xml index a8c298954..d263a8b70 100644 --- a/android/app/src/main/res/xml/prefs_main.xml +++ b/android/app/src/main/res/xml/prefs_main.xml @@ -214,8 +214,8 @@ android:order="2"/> diff --git a/android/sdk/build.gradle b/android/sdk/build.gradle index 5deec3488..42bcc1fde 100644 --- a/android/sdk/build.gradle +++ b/android/sdk/build.gradle @@ -124,6 +124,7 @@ dependencies { implementation libs.androidx.lifecycle.process implementation libs.androidx.media implementation libs.androidx.recyclerview + implementation libs.androidx.preference implementation libs.android.material testImplementation libs.junit diff --git a/android/sdk/src/main/cpp/app/organicmaps/sdk/DownloadResourcesLegacyActivity.cpp b/android/sdk/src/main/cpp/app/organicmaps/sdk/DownloadResourcesLegacyActivity.cpp index 339329c06..8efcc73c4 100644 --- a/android/sdk/src/main/cpp/app/organicmaps/sdk/DownloadResourcesLegacyActivity.cpp +++ b/android/sdk/src/main/cpp/app/organicmaps/sdk/DownloadResourcesLegacyActivity.cpp @@ -42,16 +42,16 @@ using namespace std::placeholders; namespace { - std::unique_ptr & LegacyDownloader() - { - static auto downloader = storage::GetDownloader(); - return downloader; - } +std::unique_ptr & LegacyDownloader() +{ + static auto downloader = storage::GetDownloader(); + return downloader; +} - static std::vector g_filesToDownload; - static int g_totalDownloadedBytes; - static int g_totalBytesToDownload; - static std::shared_ptr g_currentRequest; +static std::vector g_filesToDownload; +static int g_totalDownloadedBytes; +static int g_totalBytesToDownload; +static std::shared_ptr g_currentRequest; } // namespace extern "C" diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/Framework.java b/android/sdk/src/main/java/app/organicmaps/sdk/Framework.java index 577685a07..c6bb4a1b2 100644 --- a/android/sdk/src/main/java/app/organicmaps/sdk/Framework.java +++ b/android/sdk/src/main/java/app/organicmaps/sdk/Framework.java @@ -356,14 +356,16 @@ public class Framework public static void applyCustomMapDownloadUrl(@NonNull Context context, @Nullable String url) { - String normalizedUrl = url != null ? url.trim() : ""; - - // Normalize - if (!normalizedUrl.isEmpty() && !normalizedUrl.endsWith("/")) - normalizedUrl = normalizedUrl + "/"; - - nativeSetCustomMapDownloadUrl(normalizedUrl); + nativeSetCustomMapDownloadUrl(normalizeServerUrl(url)); // Reset the legacy downloader too (world/coasts). app.organicmaps.sdk.DownloadResourcesLegacyActivity.nativeResetMetaConfig(); } + + public static String normalizeServerUrl(@Nullable String url) + { + String out = url != null ? url.trim() : ""; + if (!out.isEmpty() && !out.endsWith("/")) + out = out + "/"; + return out; + } } diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/OrganicMaps.java b/android/sdk/src/main/java/app/organicmaps/sdk/OrganicMaps.java index 8d7a4a5cf..079725c13 100644 --- a/android/sdk/src/main/java/app/organicmaps/sdk/OrganicMaps.java +++ b/android/sdk/src/main/java/app/organicmaps/sdk/OrganicMaps.java @@ -2,12 +2,13 @@ package app.organicmaps.sdk; import android.content.Context; import android.content.SharedPreferences; -import android.preference.PreferenceManager; import androidx.annotation.NonNull; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ProcessLifecycleOwner; +import androidx.preference.PreferenceManager; + import app.organicmaps.sdk.bookmarks.data.BookmarkManager; import app.organicmaps.sdk.bookmarks.data.Icon; import app.organicmaps.sdk.downloader.Android7RootCertificateWorkaround; @@ -27,6 +28,7 @@ import app.organicmaps.sdk.util.SharedPropertiesUtils; import app.organicmaps.sdk.util.StorageUtils; import app.organicmaps.sdk.util.log.Logger; import app.organicmaps.sdk.util.log.LogsManager; + import java.io.IOException; public final class OrganicMaps implements DefaultLifecycleObserver diff --git a/libs/platform/platform.cpp b/libs/platform/platform.cpp index 34065998b..741c09e77 100644 --- a/libs/platform/platform.cpp +++ b/libs/platform/platform.cpp @@ -49,11 +49,6 @@ bool GetFileTypeChecked(std::string const & path, Platform::EFileType & type) } } // namespace -namespace -{ - std::string g_customMetaServerUrl; -} // namespace - // static Platform::EError Platform::ErrnoToError() { diff --git a/libs/storage/map_files_downloader.cpp b/libs/storage/map_files_downloader.cpp index 58c6eea29..c9e067f11 100644 --- a/libs/storage/map_files_downloader.cpp +++ b/libs/storage/map_files_downloader.cpp @@ -156,24 +156,16 @@ std::string GetAcceptLanguage() MetaConfig MapFilesDownloader::LoadMetaConfig() { Platform & pl = GetPlatform(); - std::optional metaConfig; + // If user sets a custom download server, skip metaserver entirely. std::string const customServer = pl.CustomMapServerUrl(); - if (!customServer.empty()) { LOG(LINFO, ("Using custom map server URL:", customServer)); - // Reuse default meta settings (timeouts, other endpoints) and override servers - metaConfig = downloader::ParseMetaConfig(pl.DefaultUrlsJSON()); - CHECK(metaConfig, ()); - - metaConfig->m_serversList.clear(); - metaConfig->m_serversList.push_back(customServer); - - LOG(LINFO, ("Got servers list (custom server):", metaConfig->m_serversList)); - CHECK(!metaConfig->m_serversList.empty(), ()); - return *metaConfig; + MetaConfig metaConfig; + metaConfig.m_serversList = {customServer}; + return metaConfig; } std::string const metaServerUrl = pl.MetaServerUrl(); @@ -190,7 +182,7 @@ MetaConfig MapFilesDownloader::LoadMetaConfig() request.RunHttpRequest(httpResult); } - metaConfig = downloader::ParseMetaConfig(httpResult); + auto metaConfig = downloader::ParseMetaConfig(httpResult); if (!metaConfig) { metaConfig = downloader::ParseMetaConfig(pl.DefaultUrlsJSON()); @@ -201,6 +193,7 @@ MetaConfig MapFilesDownloader::LoadMetaConfig() { LOG(LINFO, ("Got servers list:", metaConfig->m_serversList)); } + CHECK(!metaConfig->m_serversList.empty(), ()); return *metaConfig; }