From d254de8598df7352f13de38ede23560631866cb1 Mon Sep 17 00:00:00 2001 From: veliymolfar Date: Tue, 26 May 2020 17:06:14 +0300 Subject: [PATCH] add/edit map source dialog redesign --- OsmAnd/res/layout/edit_text_with_descr.xml | 42 ++ .../res/layout/fragment_edit_map_source.xml | 125 +++++ .../res/layout/point_editor_fragment_new.xml | 4 +- OsmAnd/res/layout/zoom_levels_with_descr.xml | 102 ++++ OsmAnd/res/values/colors.xml | 1 + OsmAnd/res/values/strings.xml | 17 + OsmAnd/res/values/styles.xml | 12 + .../src/net/osmand/plus/SQLiteTileSource.java | 8 +- .../plus/activities/MapActivityLayers.java | 26 +- .../download/ui/LocalIndexesFragment.java | 23 +- .../EditMapSourceDialogFragment.java | 458 ++++++++++++++++++ .../plus/mapsource/ExpireTimeBottomSheet.java | 119 +++++ .../mapsource/InputZoomLevelsBottomSheet.java | 193 ++++++++ .../MercatorProjectionBottomSheet.java | 119 +++++ .../TileStorageFormatBottomSheet.java | 119 +++++ .../rastermaps/OsmandRasterMapsPlugin.java | 144 +----- 16 files changed, 1332 insertions(+), 180 deletions(-) create mode 100644 OsmAnd/res/layout/edit_text_with_descr.xml create mode 100644 OsmAnd/res/layout/fragment_edit_map_source.xml create mode 100644 OsmAnd/res/layout/zoom_levels_with_descr.xml create mode 100644 OsmAnd/src/net/osmand/plus/mapsource/EditMapSourceDialogFragment.java create mode 100644 OsmAnd/src/net/osmand/plus/mapsource/ExpireTimeBottomSheet.java create mode 100644 OsmAnd/src/net/osmand/plus/mapsource/InputZoomLevelsBottomSheet.java create mode 100644 OsmAnd/src/net/osmand/plus/mapsource/MercatorProjectionBottomSheet.java create mode 100644 OsmAnd/src/net/osmand/plus/mapsource/TileStorageFormatBottomSheet.java diff --git a/OsmAnd/res/layout/edit_text_with_descr.xml b/OsmAnd/res/layout/edit_text_with_descr.xml new file mode 100644 index 0000000000..a1fe467b94 --- /dev/null +++ b/OsmAnd/res/layout/edit_text_with_descr.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/fragment_edit_map_source.xml b/OsmAnd/res/layout/fragment_edit_map_source.xml new file mode 100644 index 0000000000..b179cc5eb6 --- /dev/null +++ b/OsmAnd/res/layout/fragment_edit_map_source.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/point_editor_fragment_new.xml b/OsmAnd/res/layout/point_editor_fragment_new.xml index 53b1578816..5a08263989 100644 --- a/OsmAnd/res/layout/point_editor_fragment_new.xml +++ b/OsmAnd/res/layout/point_editor_fragment_new.xml @@ -76,7 +76,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:hint="@string/shared_string_name" - app:boxBackgroundColor="#4DCCCCCC"> + app:boxBackgroundColor="@color/input_layout_bg_color"> + app:boxBackgroundColor="@color/input_layout_bg_color"> + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/values/colors.xml b/OsmAnd/res/values/colors.xml index 59415b1a6a..dc88bfcfbc 100644 --- a/OsmAnd/res/values/colors.xml +++ b/OsmAnd/res/values/colors.xml @@ -465,5 +465,6 @@ #1AD28521 #80237BFF #80000000 + #4DCCCCCC \ No newline at end of file diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 8308881ea1..01a7f7b6f2 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -11,6 +11,23 @@ Thx - Hardy --> + Choose how downloaded tiles will be stored. + Expiration time in minutes. Cached tiles will be reloaded after specified time. + Leave this field empty if you do not need to reload tiles for this source. + \n\nOne day is 1440 minutes.\nOne week is 10 080 minutes.\nOne month is 43 829 minutes. + This parameters will affect the map display when used as a map type or overlay/underlay. + \n\n%1$s: the map will be limited to the selected zooms. \n\n%2$s: Zoom levels at which + tiles will be visible. Upscale or downscale will occur above or below the set values. + Set the minimum and maximum zoom level at which the online map will be displayed or loaded. + Storage format + Mercator Projection + Expire time + Edit online source + Enter or paste URL for online source. + Provide name for online map source. + SQLiteDB file + One image file per tile + Pseudo-Mercator projection Unsupported type World overview map (detailed) Could not find any such profiles. diff --git a/OsmAnd/res/values/styles.xml b/OsmAnd/res/values/styles.xml index 468bdfe57d..56605b7adf 100644 --- a/OsmAnd/res/values/styles.xml +++ b/OsmAnd/res/values/styles.xml @@ -789,4 +789,16 @@ + + + diff --git a/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java b/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java index c63609df37..1f821d7921 100644 --- a/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java +++ b/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java @@ -82,6 +82,8 @@ public class SQLiteTileSource implements ITileSource { if (is.getName().equalsIgnoreCase(sourceName)) { base = is; urlTemplate = is.getUrlTemplate(); + expirationTimeMillis = is.getExpirationTimeMillis(); + inversiveZoom = is.getInversiveZoom(); break; } } @@ -336,6 +338,8 @@ public class SQLiteTileSource implements ITileSource { } public void updateFromTileSourceTemplate(TileSourceTemplate r) { + db = ctx.getSQLiteAPI().getOrCreateDatabase( + ctx.getAppPath(TILES_INDEX_DIR).getAbsolutePath() + "/" + name + SQLITE_EXT, false); if (!onlyReadonlyAvailable) { int maxZoom = r.getMaximumZoomSupported(); int minZoom = r.getMinimumZoomSupported(); @@ -347,10 +351,10 @@ public class SQLiteTileSource implements ITileSource { if (getUrlTemplate() != null && !getUrlTemplate().equals(r.getUrlTemplate())) { db.execSQL("update info set " + URL + " = '" + r.getUrlTemplate() + "'"); } - if (r.getMinimumZoomSupported() != minZoom) { + if (minZoom != this.minZoom) { db.execSQL("update info set " + MIN_ZOOM + " = '" + minZoom + "'"); } - if (r.getMaximumZoomSupported() != maxZoom) { + if (maxZoom != this.maxZoom) { db.execSQL("update info set " + MAX_ZOOM + " = '" + maxZoom + "'"); } if (r.isEllipticYTile() != isEllipticYTile()) { diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java b/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java index ae8af82440..2f172a8bbd 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java @@ -445,12 +445,12 @@ public class MapActivityLayers { final String layerOsmVector = "LAYER_OSM_VECTOR"; final String layerInstallMore = "LAYER_INSTALL_MORE"; - final String layerEditInstall = "LAYER_EDIT"; + final String layerAdd = "LAYER_ADD"; entriesMap.put(layerOsmVector, getString(R.string.vector_data)); entriesMap.putAll(settings.getTileSourceEntries()); entriesMap.put(layerInstallMore, getString(R.string.install_more)); - entriesMap.put(layerEditInstall, getString(R.string.maps_define_edit)); + entriesMap.put(layerAdd, getString(R.string.shared_string_add)); final List> entriesMapList = new ArrayList<>(entriesMap.entrySet()); @@ -499,26 +499,8 @@ public class MapActivityLayers { updateMapSource(mapView, null); updateItem(it, adapter, null); break; - case layerEditInstall: - OsmandRasterMapsPlugin.defineNewEditLayer(activity, new ResultMatcher() { - - @Override - public boolean publish(TileSourceTemplate object) { - settings.MAP_TILE_SOURCES.set(object.getName()); - settings.MAP_ONLINE_DATA.set(true); - if(it != null) { - it.setDescription(object.getName()); - } - updateMapSource(mapView, settings.MAP_TILE_SOURCES); - return true; - } - - @Override - public boolean isCancelled() { - return false; - } - - }, null); + case layerAdd: + OsmandRasterMapsPlugin.defineNewEditLayer(activity.getSupportFragmentManager(), null, null); break; case layerInstallMore: OsmandRasterMapsPlugin.installMapLayers(activity, new ResultMatcher() { diff --git a/OsmAnd/src/net/osmand/plus/download/ui/LocalIndexesFragment.java b/OsmAnd/src/net/osmand/plus/download/ui/LocalIndexesFragment.java index bbb0f694e9..426cba0e2d 100644 --- a/OsmAnd/src/net/osmand/plus/download/ui/LocalIndexesFragment.java +++ b/OsmAnd/src/net/osmand/plus/download/ui/LocalIndexesFragment.java @@ -63,6 +63,7 @@ import net.osmand.plus.download.DownloadIndexesThread.DownloadEvents; import net.osmand.plus.download.IndexItem; import net.osmand.plus.helpers.FileNameTranslationHelper; import net.osmand.plus.inapp.InAppPurchaseHelper; +import net.osmand.plus.mapsource.EditMapSourceDialogFragment; import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.resources.IncrementalChangesManager; import net.osmand.util.Algorithms; @@ -81,7 +82,7 @@ import java.util.Set; import java.util.regex.Pattern; -public class LocalIndexesFragment extends OsmandExpandableListFragment implements DownloadEvents { +public class LocalIndexesFragment extends OsmandExpandableListFragment implements DownloadEvents, EditMapSourceDialogFragment.OnMapSourceUpdateListener { public static final Pattern ILLEGAL_FILE_NAME_CHARACTERS = Pattern.compile("[?:\"*|/<>]"); public static final Pattern ILLEGAL_PATH_NAME_CHARACTERS = Pattern.compile("[?:\"*|<>]"); @@ -242,19 +243,7 @@ public class LocalIndexesFragment extends OsmandExpandableListFragment implement confirm.setMessage(getString(R.string.clear_confirmation_msg, fn)); confirm.show(); } else if (resId == R.string.shared_string_edit) { - OsmandRasterMapsPlugin.defineNewEditLayer(getDownloadActivity(), - new ResultMatcher() { - @Override - public boolean isCancelled() { - return false; - } - - @Override - public boolean publish(TileSourceManager.TileSourceTemplate object) { - getDownloadActivity().reloadLocalIndexes(); - return true; - } - }, info.getFileName()); + OsmandRasterMapsPlugin.defineNewEditLayer(getDownloadActivity().getSupportFragmentManager(), this, info.getFileName()); } else if (resId == R.string.local_index_mi_restore) { new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.RESTORE_OPERATION).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, info); } else if (resId == R.string.shared_string_delete) { @@ -339,7 +328,7 @@ public class LocalIndexesFragment extends OsmandExpandableListFragment implement } } - private static File renameSQLiteFile(OsmandApplication ctx, File source, String newName, + public static File renameSQLiteFile(OsmandApplication ctx, File source, String newName, RenameCallback callback) { File dest = checkRenamePossibility(ctx, source, newName, false); if (dest == null) { @@ -405,6 +394,10 @@ public class LocalIndexesFragment extends OsmandExpandableListFragment implement return dest; } + @Override + public void onMapSourceUpdated() { + getDownloadActivity().reloadLocalIndexes(); + } public class LoadLocalIndexTask extends AsyncTask> implements AbstractLoadLocalIndexTask { diff --git a/OsmAnd/src/net/osmand/plus/mapsource/EditMapSourceDialogFragment.java b/OsmAnd/src/net/osmand/plus/mapsource/EditMapSourceDialogFragment.java new file mode 100644 index 0000000000..cf8381a309 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/mapsource/EditMapSourceDialogFragment.java @@ -0,0 +1,458 @@ +package net.osmand.plus.mapsource; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import net.osmand.AndroidUtils; +import net.osmand.IndexConstants; +import net.osmand.PlatformUtil; +import net.osmand.map.TileSourceManager; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.SQLiteTileSource; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.base.BaseOsmAndDialogFragment; +import net.osmand.plus.mapsource.InputZoomLevelsBottomSheet.OnZoomSetListener; +import net.osmand.plus.mapsource.ExpireTimeBottomSheet.OnExpireValueSetListener; +import net.osmand.plus.mapsource.MercatorProjectionBottomSheet.OnMercatorSelectedListener; +import net.osmand.plus.mapsource.TileStorageFormatBottomSheet.OnTileStorageFormatSelectedListener; +import net.osmand.util.Algorithms; + + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.logging.Log; + +import java.io.File; +import java.util.List; + +import static net.osmand.plus.download.ui.LocalIndexesFragment.ILLEGAL_FILE_NAME_CHARACTERS; +import static net.osmand.plus.download.ui.LocalIndexesFragment.renameSQLiteFile; + +public class EditMapSourceDialogFragment extends BaseOsmAndDialogFragment + implements OnZoomSetListener, OnExpireValueSetListener, OnMercatorSelectedListener, + OnTileStorageFormatSelectedListener, View.OnClickListener { + + public static final String TAG = EditMapSourceDialogFragment.class.getName(); + static final int EXPIRE_TIME_NEVER = -1; + private static final Log LOG = PlatformUtil.getLog(EditMapSourceDialogFragment.class); + private static final String MAPS_PLUGINS_URL = "https://osmand.net/features/online-maps-plugin"; + private static final String PNG_EXT = "png"; + private static final int MAX_ZOOM = 17; + private static final int MIN_ZOOM = 5; + private static final int TILE_SIZE = 256; + private static final int BIT_DENSITY = 16; + private static final int AVG_SIZE = 32000; + private static final String EDIT_LAYER_NAME_KEY = "edit_layer_name_key"; + private static final String MIN_ZOOM_KEY = "min_zoom_key"; + private static final String MAX_ZOOM_KEY = "max_zoom_key"; + private static final String EXPIRE_TIME_KEY = "expire_time_key"; + private static final String ELLIPTIC_KEY = "elliptic_key"; + private static final String SQLITE_DB_KEY = "sqlite_db_key"; + private OsmandApplication app; + private TextInputEditText nameEditText; + private TextInputEditText urlEditText; + private LinearLayout contentContainer; + private FrameLayout saveBtn; + private TextView saveBtnTitle; + private TileSourceManager.TileSourceTemplate template; + @Nullable + private String editedLayerName; + private String urlToLoad = ""; + private int minZoom = MIN_ZOOM; + private int maxZoom = MAX_ZOOM; + private int expireTimeMinutes = EXPIRE_TIME_NEVER; + private boolean elliptic = false; + private boolean sqliteDB = false; + private boolean nightMode; + + public static void showInstance(@NonNull FragmentManager fm, + @Nullable Fragment targetFragment, + @Nullable String editedLayerName) { + EditMapSourceDialogFragment fragment = new EditMapSourceDialogFragment(); + fragment.setTargetFragment(targetFragment, 0); + fragment.setEditedLayerName(editedLayerName); + fragment.show(fm, TAG); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + app = getMyApplication(); + nightMode = !app.getSettings().isLightContent(); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + if (savedInstanceState != null) { + editedLayerName = savedInstanceState.getString(EDIT_LAYER_NAME_KEY); + minZoom = savedInstanceState.getInt(MIN_ZOOM_KEY); + maxZoom = savedInstanceState.getInt(MAX_ZOOM_KEY); + expireTimeMinutes = savedInstanceState.getInt(EXPIRE_TIME_KEY); + elliptic = savedInstanceState.getBoolean(ELLIPTIC_KEY); + sqliteDB = savedInstanceState.getBoolean(SQLITE_DB_KEY); + } + View root = UiUtilities.getMaterialInflater(getContext(), nightMode).inflate(R.layout.fragment_edit_map_source, container, false); + Toolbar toolbar = root.findViewById(R.id.toolbar); + ImageView iconHelp = root.findViewById(R.id.toolbar_action); + Drawable closeDrawable = app.getUIUtilities().getIcon(R.drawable.ic_arrow_back, + nightMode ? R.color.active_buttons_and_links_text_dark : R.color.active_buttons_and_links_text_light); + Drawable helpDrawable = app.getUIUtilities().getIcon(R.drawable.ic_action_help, + nightMode ? R.color.active_buttons_and_links_text_dark : R.color.active_buttons_and_links_text_light); + iconHelp.setImageDrawable(helpDrawable); + iconHelp.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onHelpClick(); + } + }); + toolbar.setNavigationIcon(closeDrawable); + toolbar.setNavigationContentDescription(R.string.shared_string_close); + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showExitDialog(); + } + }); + int boxStrokeColor = nightMode + ? getResources().getColor(R.color.app_bar_color_light) + : getResources().getColor(R.color.active_buttons_and_links_bg_pressed_dark); + TextInputLayout nameInputLayout = root.findViewById(R.id.name_input_layout); + nameInputLayout.setBoxStrokeColor(boxStrokeColor); + nameEditText = root.findViewById(R.id.name_edit_text); + TextInputLayout urlInputLayout = root.findViewById(R.id.url_input_layout); + urlInputLayout.setBoxStrokeColor(boxStrokeColor); + urlEditText = root.findViewById(R.id.url_edit_text); + nameEditText.addTextChangedListener(getTextWatcher()); + urlEditText.addTextChangedListener(getTextWatcher()); + contentContainer = root.findViewById(R.id.content_container); + saveBtn = root.findViewById(R.id.save_button); + saveBtnTitle = root.findViewById(R.id.save_button_title); + saveBtn.setOnClickListener(this); + template = new TileSourceManager.TileSourceTemplate("", "", PNG_EXT, MAX_ZOOM, MIN_ZOOM, TILE_SIZE, BIT_DENSITY, AVG_SIZE); + if (editedLayerName != null) { + if (!editedLayerName.endsWith(IndexConstants.SQLITE_EXT)) { + File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + editedLayerName); + template = TileSourceManager.createTileSourceTemplate(f); + sqliteDB = false; + } else { + List knownTemplates = TileSourceManager.getKnownSourceTemplates(); + File tPath = app.getAppPath(IndexConstants.TILES_INDEX_DIR); + File dir = new File(tPath, editedLayerName); + SQLiteTileSource sqLiteTileSource = new SQLiteTileSource(app, dir, knownTemplates); + sqLiteTileSource.couldBeDownloadedFromInternet(); + template = new TileSourceManager.TileSourceTemplate(sqLiteTileSource.getName(), + sqLiteTileSource.getUrlTemplate(), PNG_EXT, sqLiteTileSource.getMaximumZoomSupported(), + sqLiteTileSource.getMinimumZoomSupported(), sqLiteTileSource.getTileSize(), + sqLiteTileSource.getBitDensity(), AVG_SIZE); + template.setExpirationTimeMinutes(sqLiteTileSource.getExpirationTimeMinutes()); + template.setEllipticYTile(sqLiteTileSource.isEllipticYTile()); + sqliteDB = true; + } + } + if (savedInstanceState == null) { + urlToLoad = template.getUrlTemplate(); + expireTimeMinutes = template.getExpirationTimeMinutes(); + minZoom = template.getMinimumZoomSupported(); + maxZoom = template.getMaximumZoomSupported(); + elliptic = template.isEllipticYTile(); + } + updateUi(); + return root; + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putString(EDIT_LAYER_NAME_KEY, editedLayerName); + outState.putInt(MIN_ZOOM_KEY, minZoom); + outState.putInt(MAX_ZOOM_KEY, maxZoom); + outState.putInt(EXPIRE_TIME_KEY, expireTimeMinutes); + outState.putBoolean(ELLIPTIC_KEY, elliptic); + outState.putBoolean(SQLITE_DB_KEY, sqliteDB); + super.onSaveInstanceState(outState); + } + + @Override + public void onResume() { + super.onResume(); + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setOnKeyListener(new DialogInterface.OnKeyListener() { + @Override + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { + if (keyCode == android.view.KeyEvent.KEYCODE_BACK) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + return true; + } else { + showExitDialog(); + return true; + } + } + return false; + } + }); + } + } + + @Override + public void onZoomSet(int min, int max) { + if (isAdded()) { + minZoom = min; + maxZoom = max; + updateDescription(ConfigurationItem.ZOOM_LEVELS); + } + } + + @Override + public void onExpireValueSet(int expireValue) { + if (isAdded()) { + expireTimeMinutes = expireValue; + updateDescription(ConfigurationItem.EXPIRE_TIME); + } + } + + @Override + public void onMercatorSelected(boolean elliptic) { + if (isAdded()) { + this.elliptic = elliptic; + updateDescription(ConfigurationItem.MERCATOR_PROJECTION); + } + } + + @Override + public void onStorageFormatSelected(boolean sqliteDb) { + if (isAdded()) { + this.sqliteDB = sqliteDb; + updateDescription(ConfigurationItem.STORAGE_FORMAT); + } + } + + @Override + public void onClick(View view) { + if (view.getId() == R.id.save_button) { + saveTemplate(); + dismiss(); + } + } + + private TextWatcher getTextWatcher() { + return new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + String s = charSequence.toString(); + if (Algorithms.isEmpty(s)) { + saveBtn.setEnabled(false); + saveBtnTitle.setEnabled(false); + } else { + saveBtn.setEnabled(true); + saveBtnTitle.setEnabled(true); + } + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }; + } + + private void saveTemplate() { + try { + String newName = nameEditText.getText().toString(); + String urlToLoad = urlEditText.getText().toString(); + template.setName(newName); + template.setUrlToLoad(urlToLoad.isEmpty() ? null : urlToLoad.replace("{$x}", "{1}").replace("{$y}", "{2}").replace("{$z}", "{0}")); + template.setMinZoom(minZoom); + template.setMaxZoom(maxZoom); + template.setEllipticYTile(elliptic); + template.setExpirationTimeMinutes(expireTimeMinutes); + File f = app.getAppPath(IndexConstants.TILES_INDEX_DIR + editedLayerName); + if (f.exists()) { + int extIndex = f.getName().lastIndexOf('.'); + String ext = extIndex == -1 ? "" : f.getName().substring(extIndex); + String originalName = extIndex == -1 ? f.getName() : f.getName().substring(0, extIndex); + if (!Algorithms.objectEquals(newName, originalName)) { + if (IndexConstants.SQLITE_EXT.equals(ext) && sqliteDB) { + renameSQLiteFile(app, f, newName, null); + } else if (!sqliteDB) { + f.renameTo(app.getAppPath(IndexConstants.TILES_INDEX_DIR + newName)); + } + } + } + if (sqliteDB) { + if (!f.exists() || f.isDirectory()) { + SQLiteTileSource sqLiteTileSource = + new SQLiteTileSource(app, newName, minZoom, + maxZoom, urlToLoad, "0,1,2,3", + elliptic, false, "", expireTimeMinutes > 0, + expireTimeMinutes * 60 * 1000L, false, "" + ); + sqLiteTileSource.createDataBase(); + } else { + List knownTemplates = TileSourceManager.getKnownSourceTemplates(); + SQLiteTileSource sqLiteTileSource = new SQLiteTileSource(app, f, knownTemplates); + sqLiteTileSource.couldBeDownloadedFromInternet(); + sqLiteTileSource.updateFromTileSourceTemplate(template); + } + } else { + getSettings().installTileSource(template); + } + Fragment fragment = getTargetFragment(); + if (fragment instanceof OnMapSourceUpdateListener) { + ((OnMapSourceUpdateListener) fragment).onMapSourceUpdated(); + } + } catch (RuntimeException e) { + LOG.error("Error on saving template " + e); + } + } + + private void updateUi() { + nameEditText.setText(editedLayerName != null ? editedLayerName.replace(IndexConstants.SQLITE_EXT, "") : ""); + urlEditText.setText(urlToLoad); + addConfigurationItems(ConfigurationItem.values()); + } + + private void onHelpClick() { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(MAPS_PLUGINS_URL)); + if (AndroidUtils.isIntentSafe(app, i)) { + startActivity(i); + } + } + + private void showExitDialog() { + Context themedContext = UiUtilities.getThemedContext(getActivity(), nightMode); + AlertDialog.Builder dismissDialog = new AlertDialog.Builder(themedContext); + dismissDialog.setTitle(getString(R.string.shared_string_dismiss)); + dismissDialog.setMessage(getString(R.string.exit_without_saving)); + dismissDialog.setNegativeButton(R.string.shared_string_cancel, null); + dismissDialog.setPositiveButton(R.string.shared_string_exit, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + dismissDialog.show(); + } + + private String getDescription(ConfigurationItem item) { + switch (item) { + case ZOOM_LEVELS: + String min = getString(R.string.ltr_or_rtl_combine_via_space, getString(R.string.shared_string_min), String.valueOf(minZoom)); + String max = getString(R.string.ltr_or_rtl_combine_via_space, getString(R.string.shared_string_max), String.valueOf(maxZoom)); + return getString(R.string.ltr_or_rtl_combine_via_bold_point, min, max); + case EXPIRE_TIME: + return expireTimeMinutes == EXPIRE_TIME_NEVER + ? getString(R.string.shared_string_never) + : getString(R.string.ltr_or_rtl_combine_via_space, String.valueOf(expireTimeMinutes), getString(R.string.osmand_parking_minute)); + case MERCATOR_PROJECTION: + return elliptic ? getString(R.string.edit_tilesource_elliptic_tile) : getString(R.string.pseudo_mercator_projection); + case STORAGE_FORMAT: + return sqliteDB ? getString(R.string.sqlite_db_file) : getString(R.string.one_image_per_tile); + default: + return ""; + } + } + + private View.OnClickListener getClickListener(final ConfigurationItem item) { + return new View.OnClickListener() { + @Override + public void onClick(View view) { + FragmentManager fm = getFragmentManager(); + if (fm != null) { + switch (item) { + case ZOOM_LEVELS: + InputZoomLevelsBottomSheet.showInstance( + fm, EditMapSourceDialogFragment.this, + R.string.map_source_zoom_levels, R.string.map_source_zoom_levels_descr, + minZoom, maxZoom + ); + break; + case EXPIRE_TIME: + ExpireTimeBottomSheet.showInstance(fm, EditMapSourceDialogFragment.this, expireTimeMinutes); + break; + case MERCATOR_PROJECTION: + MercatorProjectionBottomSheet.showInstance(fm, EditMapSourceDialogFragment.this, elliptic); + break; + case STORAGE_FORMAT: + TileStorageFormatBottomSheet.showInstance(fm, EditMapSourceDialogFragment.this, sqliteDB); + break; + } + } + } + }; + } + + private void addConfigurationItems(ConfigurationItem... items) { + LayoutInflater inflater = UiUtilities.getMaterialInflater(getContext(), nightMode); + for (ConfigurationItem item : items) { + View view = inflater.inflate(R.layout.list_item_ui_customization, null); + ((ImageView) view.findViewById(R.id.icon)).setImageDrawable(app.getUIUtilities().getIcon(item.iconRes, nightMode)); + ((TextView) view.findViewById(R.id.title)).setText(item.titleRes); + ((TextView) view.findViewById(R.id.sub_title)).setText(getDescription(item)); + view.setOnClickListener(getClickListener(item)); + contentContainer.addView(view); + } + } + + private void updateDescription(ConfigurationItem item) { + View view = contentContainer.getChildAt(ArrayUtils.indexOf(ConfigurationItem.values(), item)); + ((TextView) view.findViewById(R.id.sub_title)).setText(getDescription(item)); + } + + private enum ConfigurationItem { + ZOOM_LEVELS(R.drawable.ic_action_layers, R.string.shared_string_zoom_levels), + EXPIRE_TIME(R.drawable.ic_action_time_span, R.string.expire_time), + MERCATOR_PROJECTION(R.drawable.ic_world_globe_dark, R.string.mercator_projection), + STORAGE_FORMAT(R.drawable.ic_sdcard, R.string.storage_format); + + @DrawableRes + public int iconRes; + @StringRes + public int titleRes; + + ConfigurationItem(int iconRes, int titleRes) { + this.titleRes = titleRes; + this.iconRes = iconRes; + } + } + + private void setEditedLayerName(@Nullable String editedLayerName) { + this.editedLayerName = editedLayerName; + } + + public interface OnMapSourceUpdateListener { + void onMapSourceUpdated(); + } +} diff --git a/OsmAnd/src/net/osmand/plus/mapsource/ExpireTimeBottomSheet.java b/OsmAnd/src/net/osmand/plus/mapsource/ExpireTimeBottomSheet.java new file mode 100644 index 0000000000..1f645a3848 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/mapsource/ExpireTimeBottomSheet.java @@ -0,0 +1,119 @@ +package net.osmand.plus.mapsource; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import net.osmand.PlatformUtil; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.base.MenuBottomSheetDialogFragment; +import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem; +import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; +import net.osmand.util.Algorithms; + +import org.apache.commons.logging.Log; + +import static net.osmand.plus.mapsource.EditMapSourceDialogFragment.EXPIRE_TIME_NEVER; + +public class ExpireTimeBottomSheet extends MenuBottomSheetDialogFragment { + + public static final String TAG = ExpireTimeBottomSheet.class.getName(); + private static final Log LOG = PlatformUtil.getLog(ExpireTimeBottomSheet.class); + private static final String EXPIRE_VALUE_KEY = "expire_value_key"; + private int expireValue; + private TextInputEditText editText; + + public static void showInstance(@NonNull FragmentManager fm, + @Nullable Fragment targetFragment, + int expireValue) { + ExpireTimeBottomSheet bottomSheet = new ExpireTimeBottomSheet(); + bottomSheet.setTargetFragment(targetFragment, 0); + bottomSheet.setExpireValue(expireValue); + bottomSheet.show(fm, TAG); + } + + @Override + public void createMenuItems(Bundle savedInstanceState) { + if (savedInstanceState != null) { + expireValue = savedInstanceState.getInt(EXPIRE_VALUE_KEY, EXPIRE_TIME_NEVER); + } + LayoutInflater inflater = UiUtilities.getMaterialInflater(getContext(), nightMode); + TitleItem titleItem = new TitleItem(getString(R.string.expire_time)); + items.add(titleItem); + final View inputValueLayout = inflater.inflate(R.layout.edit_text_with_descr, null); + ((TextView) inputValueLayout.findViewById(R.id.dialog_descr)).setText(R.string.expire_time_descr); + editText = inputValueLayout.findViewById(R.id.value_edit_text); + if (expireValue > 0) { + editText.setText(String.valueOf(expireValue)); + } + int boxStrokeColor = nightMode + ? getResources().getColor(R.color.app_bar_color_light) + : getResources().getColor(R.color.active_buttons_and_links_bg_pressed_dark); + TextInputLayout textInputLayout = inputValueLayout.findViewById(R.id.value_input_layout); + textInputLayout.setBoxStrokeColor(boxStrokeColor); + final SimpleBottomSheetItem editTextItem = (SimpleBottomSheetItem) new SimpleBottomSheetItem.Builder() + .setCustomView(inputValueLayout) + .create(); + items.add(editTextItem); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putLong(EXPIRE_VALUE_KEY, getExpireValue()); + super.onSaveInstanceState(outState); + } + + @Override + protected void onRightBottomButtonClick() { + super.onRightBottomButtonClick(); + Fragment fragment = getTargetFragment(); + if (fragment instanceof OnExpireValueSetListener) { + ((OnExpireValueSetListener) fragment).onExpireValueSet(getExpireValue()); + } + dismiss(); + } + + @Override + protected int getDismissButtonTextId() { + return R.string.shared_string_cancel; + } + + @Override + protected int getRightBottomButtonTextId() { + return R.string.shared_string_apply; + } + + private int getExpireValue() { + int expireValue = EXPIRE_TIME_NEVER; + if (editText.getText() != null) { + String value = editText.getText().toString(); + if (!Algorithms.isEmpty(value)) { + try { + expireValue = Integer.parseInt(value); + } catch (RuntimeException e) { + LOG.error("Error parsing expire value: " + expireValue + " " + e); + } + } + } + return expireValue > 0 ? expireValue : EXPIRE_TIME_NEVER; + } + + private void setExpireValue(int expireValue) { + this.expireValue = expireValue; + } + + public interface OnExpireValueSetListener { + void onExpireValueSet(int expireValue); + } +} diff --git a/OsmAnd/src/net/osmand/plus/mapsource/InputZoomLevelsBottomSheet.java b/OsmAnd/src/net/osmand/plus/mapsource/InputZoomLevelsBottomSheet.java new file mode 100644 index 0000000000..8c11a97d70 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/mapsource/InputZoomLevelsBottomSheet.java @@ -0,0 +1,193 @@ +package net.osmand.plus.mapsource; + +import android.os.Bundle; +import android.text.SpannableString; +import android.text.Spanned; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.slider.Slider; + +import net.osmand.PlatformUtil; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.base.MenuBottomSheetDialogFragment; +import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem; +import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; +import net.osmand.plus.helpers.FontCache; +import net.osmand.plus.widgets.style.CustomTypefaceSpan; + +import org.apache.commons.logging.Log; + +import java.util.List; + +public class InputZoomLevelsBottomSheet extends MenuBottomSheetDialogFragment { + + public static final String TAG = InputZoomLevelsBottomSheet.class.getName(); + private static final Log LOG = PlatformUtil.getLog(InputZoomLevelsBottomSheet.class); + private static final String MIN_ZOOM_KEY = "min_zoom_key"; + private static final String MAX_ZOOM_KEY = "max_zoom_key"; + private static final String SLIDER_DESCR_RES_KEY = "slider_descr_key"; + private static final String DIALOG_DESCR_RES_KEY = "dialog_descr_key"; + private static final int SLIDER_FROM = 1; + private static final int SLIDER_TO = 20; + @StringRes + private int sliderDescrRes; + @StringRes + private int dialogDescrRes; + private int minZoom; + private int maxZoom; + + public static void showInstance(@NonNull FragmentManager fm, + @Nullable Fragment targetFragment, + int sliderDescr, + int dialogDescr, + int minZoom, + int maxZoom) { + InputZoomLevelsBottomSheet bottomSheet = new InputZoomLevelsBottomSheet(); + bottomSheet.setTargetFragment(targetFragment, 0); + bottomSheet.setSliderDescrRes(sliderDescr); + bottomSheet.setDialogDescrRes(dialogDescr); + bottomSheet.setMinZoom(Math.max(minZoom, SLIDER_FROM)); + bottomSheet.setMaxZoom(Math.min(maxZoom, SLIDER_TO)); + bottomSheet.show(fm, TAG); + } + + @Override + public void createMenuItems(Bundle savedInstanceState) { + if (savedInstanceState != null) { + minZoom = savedInstanceState.getInt(MIN_ZOOM_KEY); + maxZoom = savedInstanceState.getInt(MAX_ZOOM_KEY); + dialogDescrRes = savedInstanceState.getInt(DIALOG_DESCR_RES_KEY); + sliderDescrRes = savedInstanceState.getInt(SLIDER_DESCR_RES_KEY); + } + LayoutInflater inflater = UiUtilities.getInflater(requiredMyApplication(), nightMode); + TitleItem titleItem = new TitleItem(getString(R.string.shared_string_zoom_levels)); + items.add(titleItem); + final View sliderView = inflater.inflate(R.layout.zoom_levels_with_descr, null); + ((TextView) sliderView.findViewById(R.id.slider_descr)).setText(sliderDescrRes); + TextView dialogDescrTv = sliderView.findViewById(R.id.dialog_descr); + if (dialogDescrRes == R.string.map_source_zoom_levels_descr) { + String mapSource = getString(R.string.map_source); + String overlayUnderlay = getString(R.string.pref_overlay); + String dialogDesr = getString(dialogDescrRes, mapSource, overlayUnderlay); + dialogDescrTv.setText(createSpannableString(dialogDesr, mapSource, overlayUnderlay)); + } else { + dialogDescrTv.setText(getString(dialogDescrRes)); + } + final TextView minZoomValue = sliderView.findViewById(R.id.zoom_value_min); + minZoomValue.setText(String.valueOf(minZoom)); + final TextView maxZoomValue = sliderView.findViewById(R.id.zoom_value_max); + maxZoomValue.setText(String.valueOf(maxZoom)); + Slider slider = sliderView.findViewById(R.id.zoom_slider); + int colorProfileRes = requiredMyApplication().getSettings().getApplicationMode().getIconColorInfo().getColor(nightMode); + int colorProfile = ContextCompat.getColor(requiredMyApplication(), colorProfileRes); + UiUtilities.setupSlider(slider, nightMode, colorProfile, true); + slider.setValueFrom(SLIDER_FROM); + slider.setValueTo(SLIDER_TO); + slider.setValues((float) minZoom, (float) maxZoom); + slider.addOnChangeListener(new Slider.OnChangeListener() { + @Override + public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) { + List values = slider.getValues(); + if (values.size() > 0) { + minZoomValue.setText(String.valueOf(values.get(0).intValue())); + maxZoomValue.setText(String.valueOf(values.get(1).intValue())); + } + } + }); + slider.addOnSliderTouchListener(new Slider.OnSliderTouchListener() { + @Override + public void onStartTrackingTouch(@NonNull Slider slider) { + + } + + @Override + public void onStopTrackingTouch(@NonNull Slider slider) { + List values = slider.getValues(); + if (values.size() > 0) { + minZoom = values.get(0).intValue(); + maxZoom = values.get(1).intValue(); + } + } + }); + final SimpleBottomSheetItem sliderItem = (SimpleBottomSheetItem) new SimpleBottomSheetItem.Builder() + .setCustomView(sliderView) + .create(); + items.add(sliderItem); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(MIN_ZOOM_KEY, minZoom); + outState.putInt(MAX_ZOOM_KEY, maxZoom); + outState.putInt(SLIDER_DESCR_RES_KEY, sliderDescrRes); + outState.putInt(DIALOG_DESCR_RES_KEY, dialogDescrRes); + super.onSaveInstanceState(outState); + } + + @Override + protected void onRightBottomButtonClick() { + super.onRightBottomButtonClick(); + Fragment fragment = getTargetFragment(); + if (fragment instanceof OnZoomSetListener) { + ((OnZoomSetListener) fragment).onZoomSet(minZoom, maxZoom); + } + dismiss(); + } + + @Override + protected int getDismissButtonTextId() { + return R.string.shared_string_cancel; + } + + @Override + protected int getRightBottomButtonTextId() { + return R.string.shared_string_apply; + } + + private SpannableString createSpannableString(@NonNull String text, @NonNull String... textToStyle) { + SpannableString spannable = new SpannableString(text); + for (String t : textToStyle) { + try { + int startIndex = text.indexOf(t); + spannable.setSpan( + new CustomTypefaceSpan(FontCache.getRobotoMedium(requireContext())), + startIndex, + startIndex + t.length(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } catch (RuntimeException e) { + LOG.error("Error trying to find index of " + t + " " + e); + } + } + return spannable; + } + + private void setSliderDescrRes(int sliderDescrRes) { + this.sliderDescrRes = sliderDescrRes; + } + + private void setDialogDescrRes(int dialogDescrRes) { + this.dialogDescrRes = dialogDescrRes; + } + + private void setMinZoom(int minZoom) { + this.minZoom = minZoom; + } + + private void setMaxZoom(int maxZoom) { + this.maxZoom = maxZoom; + } + + public interface OnZoomSetListener { + void onZoomSet(int min, int max); + } +} diff --git a/OsmAnd/src/net/osmand/plus/mapsource/MercatorProjectionBottomSheet.java b/OsmAnd/src/net/osmand/plus/mapsource/MercatorProjectionBottomSheet.java new file mode 100644 index 0000000000..a1a9150132 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/mapsource/MercatorProjectionBottomSheet.java @@ -0,0 +1,119 @@ +package net.osmand.plus.mapsource; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.core.widget.NestedScrollView; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import net.osmand.plus.R; +import net.osmand.plus.base.MenuBottomSheetDialogFragment; +import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem; +import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; + +public class MercatorProjectionBottomSheet extends MenuBottomSheetDialogFragment { + + public static final String TAG = MercatorProjectionBottomSheet.class.getName(); + private static final String ELLIPTIC_KEY = "elliptic_key"; + private LinearLayout valuesContainer; + private MercatorProjection mercatorProjection; + + public static void showInstance(@NonNull FragmentManager fm, + @Nullable Fragment targetFragment, + boolean elliptic) { + MercatorProjectionBottomSheet bottomSheet = new MercatorProjectionBottomSheet(); + bottomSheet.setTargetFragment(targetFragment, 0); + bottomSheet.setMercatorProjection(elliptic); + bottomSheet.show(fm, TAG); + } + + @Override + public void createMenuItems(Bundle savedInstanceState) { + if (savedInstanceState != null) { + setMercatorProjection(savedInstanceState.getBoolean(ELLIPTIC_KEY)); + } + Context context = requireContext(); + TitleItem titleItem = new TitleItem(getString(R.string.mercator_projection)); + items.add(titleItem); + NestedScrollView nestedScrollView = new NestedScrollView(context); + valuesContainer = new LinearLayout(context); + valuesContainer.setLayoutParams((new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT))); + valuesContainer.setOrientation(LinearLayout.VERTICAL); + valuesContainer.setPadding(0, getResources().getDimensionPixelSize(R.dimen.bottom_sheet_content_padding_small), 0, 0); + for (int i = 0; i < MercatorProjection.values().length; i++) { + LayoutInflater.from(new ContextThemeWrapper(context, themeRes)) + .inflate(R.layout.bottom_sheet_item_with_radio_btn_left, valuesContainer, true); + } + nestedScrollView.addView(valuesContainer); + items.add(new BaseBottomSheetItem.Builder().setCustomView(nestedScrollView).create()); + populateValuesList(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putBoolean(ELLIPTIC_KEY, mercatorProjection == MercatorProjection.ELLIPTIC); + super.onSaveInstanceState(outState); + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + Fragment fragment = getTargetFragment(); + if (fragment instanceof OnMercatorSelectedListener) { + ((OnMercatorSelectedListener) fragment).onMercatorSelected(mercatorProjection == MercatorProjection.ELLIPTIC); + } + super.onDismiss(dialog); + } + + @Override + protected int getDismissButtonTextId() { + return R.string.shared_string_close; + } + + private void populateValuesList() { + for (int i = 0; i < MercatorProjection.values().length; i++) { + final MercatorProjection m = MercatorProjection.values()[i]; + boolean selected = mercatorProjection == m; + View view = valuesContainer.getChildAt(i); + ((CompoundButton) view.findViewById(R.id.compound_button)).setChecked(selected); + ((TextView) view.findViewById(R.id.title)).setText(m.titleRes); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mercatorProjection = m; + populateValuesList(); + } + }); + } + } + + private void setMercatorProjection(boolean elliptic) { + mercatorProjection = elliptic ? MercatorProjection.ELLIPTIC : MercatorProjection.PSEUDO; + } + + public enum MercatorProjection { + ELLIPTIC(R.string.edit_tilesource_elliptic_tile), + PSEUDO(R.string.pseudo_mercator_projection); + + @StringRes + public int titleRes; + + MercatorProjection(@StringRes int titleRes) { + this.titleRes = titleRes; + } + } + + public interface OnMercatorSelectedListener { + void onMercatorSelected(boolean elliptic); + } +} diff --git a/OsmAnd/src/net/osmand/plus/mapsource/TileStorageFormatBottomSheet.java b/OsmAnd/src/net/osmand/plus/mapsource/TileStorageFormatBottomSheet.java new file mode 100644 index 0000000000..2f42110430 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/mapsource/TileStorageFormatBottomSheet.java @@ -0,0 +1,119 @@ +package net.osmand.plus.mapsource; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.core.widget.NestedScrollView; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import net.osmand.plus.R; +import net.osmand.plus.base.MenuBottomSheetDialogFragment; +import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem; +import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; + +public class TileStorageFormatBottomSheet extends MenuBottomSheetDialogFragment { + + public static final String TAG = TileStorageFormatBottomSheet.class.getName(); + private static final String SQLITE_DB_KEY = "sqlite_db_key"; + private LinearLayout valuesContainer; + private TileStorageFormat tileStorageFormat; + + public static void showInstance(@NonNull FragmentManager fm, + @Nullable Fragment targetFragment, + boolean sqliteDb) { + TileStorageFormatBottomSheet bottomSheet = new TileStorageFormatBottomSheet(); + bottomSheet.setTargetFragment(targetFragment, 0); + bottomSheet.setTileStorageFormat(sqliteDb); + bottomSheet.show(fm, TAG); + } + + @Override + public void createMenuItems(Bundle savedInstanceState) { + if (savedInstanceState != null) { + setTileStorageFormat(savedInstanceState.getBoolean(SQLITE_DB_KEY)); + } + Context context = requireContext(); + TitleItem titleItem = new TitleItem(getString(R.string.mercator_projection)); + items.add(titleItem); + NestedScrollView nestedScrollView = new NestedScrollView(context); + valuesContainer = new LinearLayout(context); + valuesContainer.setLayoutParams((new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT))); + valuesContainer.setOrientation(LinearLayout.VERTICAL); + valuesContainer.setPadding(0, getResources().getDimensionPixelSize(R.dimen.bottom_sheet_content_padding_small), 0, 0); + for (int i = 0; i < TileStorageFormat.values().length; i++) { + LayoutInflater.from(new ContextThemeWrapper(context, themeRes)) + .inflate(R.layout.bottom_sheet_item_with_radio_btn_left, valuesContainer, true); + } + nestedScrollView.addView(valuesContainer); + items.add(new BaseBottomSheetItem.Builder().setCustomView(nestedScrollView).create()); + populateValuesList(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putBoolean(SQLITE_DB_KEY, tileStorageFormat == TileStorageFormat.SQLITE_DB); + super.onSaveInstanceState(outState); + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + Fragment fragment = getTargetFragment(); + if (fragment instanceof OnTileStorageFormatSelectedListener) { + ((OnTileStorageFormatSelectedListener) fragment).onStorageFormatSelected(tileStorageFormat == TileStorageFormat.SQLITE_DB); + } + super.onDismiss(dialog); + } + + @Override + protected int getDismissButtonTextId() { + return R.string.shared_string_close; + } + + private void populateValuesList() { + for (int i = 0; i < TileStorageFormat.values().length; i++) { + final TileStorageFormat m = TileStorageFormat.values()[i]; + boolean selected = tileStorageFormat == m; + View view = valuesContainer.getChildAt(i); + ((CompoundButton) view.findViewById(R.id.compound_button)).setChecked(selected); + ((TextView) view.findViewById(R.id.title)).setText(m.titleRes); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + tileStorageFormat = m; + populateValuesList(); + } + }); + } + } + + private void setTileStorageFormat(boolean sqliteDb) { + tileStorageFormat = sqliteDb ? TileStorageFormat.SQLITE_DB : TileStorageFormat.ONE_IMAGE_PER_TILE; + } + + public enum TileStorageFormat { + ONE_IMAGE_PER_TILE(R.string.one_image_per_tile), + SQLITE_DB(R.string.sqlite_db_file); + + @StringRes + public int titleRes; + + TileStorageFormat(@StringRes int titleRes) { + this.titleRes = titleRes; + } + } + + public interface OnTileStorageFormatSelectedListener { + void onStorageFormatSelected(boolean sqliteDb); + } +} diff --git a/OsmAnd/src/net/osmand/plus/rastermaps/OsmandRasterMapsPlugin.java b/OsmAnd/src/net/osmand/plus/rastermaps/OsmandRasterMapsPlugin.java index 9194949691..2d6d40700b 100644 --- a/OsmAnd/src/net/osmand/plus/rastermaps/OsmandRasterMapsPlugin.java +++ b/OsmAnd/src/net/osmand/plus/rastermaps/OsmandRasterMapsPlugin.java @@ -6,23 +6,17 @@ import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.view.ContextThemeWrapper; import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.AppCompatCheckBox; import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import net.osmand.AndroidUtils; -import net.osmand.IndexConstants; import net.osmand.ResultMatcher; import net.osmand.StateChangedListener; import net.osmand.map.ITileSource; @@ -38,10 +32,9 @@ import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings.CommonPreference; import net.osmand.plus.settings.backend.OsmandSettings.LayerTransparencySeekbarMode; import net.osmand.plus.R; -import net.osmand.plus.SQLiteTileSource; -import net.osmand.plus.UiUtilities; import net.osmand.plus.Version; import net.osmand.plus.activities.DownloadTilesDialog; +import net.osmand.plus.mapsource.EditMapSourceDialogFragment; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.MapActivityLayers; import net.osmand.plus.dashboard.DashboardOnMap.DashboardType; @@ -51,7 +44,6 @@ import net.osmand.plus.views.MapTileLayer; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.util.Algorithms; -import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -61,7 +53,6 @@ import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_U import static net.osmand.aidlapi.OsmAndCustomizationConstants.OVERLAY_MAP; import static net.osmand.aidlapi.OsmAndCustomizationConstants.UNDERLAY_MAP; import static net.osmand.plus.ContextMenuAdapter.makeDeleteAction; -import static net.osmand.plus.UiUtilities.CompoundButtonType.PROFILE_DEPENDENT; public class OsmandRasterMapsPlugin extends OsmandPlugin { @@ -483,133 +474,8 @@ public class OsmandRasterMapsPlugin extends OsmandPlugin { t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - public static void defineNewEditLayer(final Activity activity, final ResultMatcher resultMatcher, final String editedLayerName) { - final OsmandApplication app = (OsmandApplication) activity.getApplication(); - final OsmandSettings settings = app.getSettings(); - final Map entriesMap = settings.getTileSourceEntries(true); - final SQLiteTileSource[] sqLiteTileSource = new SQLiteTileSource[1]; - boolean nightMode = isNightMode(activity, app); - final int dp8 = AndroidUtils.dpToPx(app, 8f); - int textColorPrimary = ContextCompat.getColor(app, nightMode ? R.color.text_color_primary_dark : R.color.text_color_primary_light); - TileSourceTemplate ts = new TileSourceTemplate("NewMapnik", "http://mapnik.osmand.net/{0}/{1}/{2}.png", - "png", 17, 5, 256, 16, 32000); - final TileSourceTemplate[] result = new TileSourceTemplate[]{ts}; - AlertDialog.Builder bld = new AlertDialog.Builder(new ContextThemeWrapper(activity, getThemeRes(activity, app))); - View view = UiUtilities.getInflater(activity, isNightMode(activity, app)).inflate(R.layout.editing_tile_source, null); - final EditText name = (EditText) view.findViewById(R.id.Name); - final Spinner existing = (Spinner) view.findViewById(R.id.TileSourceSpinner); - final TextView existingHint = (TextView) view.findViewById(R.id.TileSourceHint); - final EditText urlToLoad = (EditText) view.findViewById(R.id.URLToLoad); - final EditText minZoom = (EditText) view.findViewById(R.id.MinZoom); - final EditText maxZoom = (EditText) view.findViewById(R.id.MaxZoom); - final EditText expire = (EditText) view.findViewById(R.id.ExpirationTime); - final AppCompatCheckBox elliptic = (AppCompatCheckBox) view.findViewById(R.id.EllipticMercator); - elliptic.setTextColor(textColorPrimary); - elliptic.setPadding(dp8, 0, 0, 0); - UiUtilities.setupCompoundButton(elliptic, nightMode, PROFILE_DEPENDENT); - updateTileSourceEditView(ts, name, urlToLoad, minZoom, maxZoom, expire, elliptic); - - final ArrayList templates = new ArrayList<>(entriesMap.keySet()); - templates.add(0, ""); - - ArrayAdapter adapter = new ArrayAdapter<>(view.getContext(), - android.R.layout.simple_spinner_item, - templates - ); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - existing.setAdapter(adapter); - TileSourceTemplate template; - if (editedLayerName != null) { - name.setFocusable(false); - name.setFocusableInTouchMode(false); - if (!editedLayerName.endsWith(IndexConstants.SQLITE_EXT)) { - File f = ((OsmandApplication) activity.getApplication()).getAppPath( - IndexConstants.TILES_INDEX_DIR + editedLayerName); - template = TileSourceManager.createTileSourceTemplate(f); - } else { - List knownTemplates = TileSourceManager.getKnownSourceTemplates(); - File tPath = app.getAppPath(IndexConstants.TILES_INDEX_DIR); - File dir = new File(tPath, editedLayerName); - sqLiteTileSource[0] = new SQLiteTileSource(app, dir, knownTemplates); - sqLiteTileSource[0].couldBeDownloadedFromInternet(); - template = new TileSourceManager.TileSourceTemplate(sqLiteTileSource[0].getName(), - sqLiteTileSource[0].getUrlTemplate(), "png", sqLiteTileSource[0].getMaximumZoomSupported(), - sqLiteTileSource[0].getMinimumZoomSupported(), sqLiteTileSource[0].getTileSize(), - sqLiteTileSource[0].getBitDensity(), 32000); - template.setExpirationTimeMinutes(sqLiteTileSource[0].getExpirationTimeMinutes()); - template.setEllipticYTile(sqLiteTileSource[0].isEllipticYTile()); - } - if (template != null) { - result[0] = template.copy(); - updateTileSourceEditView(result[0], name, urlToLoad, minZoom, maxZoom, expire, elliptic); - } - existingHint.setVisibility(View.GONE); - existing.setVisibility(View.GONE); - } - existing.setSelection(0); - existing.setOnItemSelectedListener(new OnItemSelectedListener() { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (position > 0) { - File f = ((OsmandApplication) activity.getApplication()).getAppPath(IndexConstants.TILES_INDEX_DIR + templates.get(position)); - TileSourceTemplate template = TileSourceManager.createTileSourceTemplate(f); - if (template != null) { - result[0] = template.copy(); - updateTileSourceEditView(result[0], name, urlToLoad, minZoom, maxZoom, expire, elliptic); - } - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - - bld.setView(view); - bld.setPositiveButton(R.string.shared_string_save, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - TileSourceTemplate r = result[0]; - try { - r.setName(name.getText().toString()); - r.setExpirationTimeMinutes(expire.getText().length() == 0 ? -1 : - Integer.parseInt(expire.getText().toString())); - r.setMinZoom(Integer.parseInt(minZoom.getText().toString())); - r.setMaxZoom(Integer.parseInt(maxZoom.getText().toString())); - r.setEllipticYTile(elliptic.isChecked()); - r.setUrlToLoad(urlToLoad.getText().toString().equals("") ? null : urlToLoad.getText().toString().replace("{$x}", "{1}") - .replace("{$y}", "{2}").replace("{$z}", "{0}")); - if (sqLiteTileSource[0] != null) { - sqLiteTileSource[0].updateFromTileSourceTemplate(r); - } else { - if (r.getName().length() > 0) { - if (settings.installTileSource(r)) { - Toast.makeText(activity, activity.getString(R.string.edit_tilesource_successfully, r.getName()), - Toast.LENGTH_SHORT).show(); - resultMatcher.publish(r); - } - } - } - } catch (RuntimeException e) { - Toast.makeText(activity, e.getMessage(), Toast.LENGTH_SHORT).show(); - } - } - }); - bld.setNegativeButton(R.string.shared_string_cancel, null); - bld.show(); - } - - private static void updateTileSourceEditView(TileSourceTemplate ts, EditText name, final EditText urlToLoad, final EditText minZoom, - final EditText maxZoom, EditText expire, final CheckBox elliptic) { - minZoom.setText(String.valueOf(ts.getMinimumZoomSupported())); - maxZoom.setText(String.valueOf(ts.getMaximumZoomSupported())); - name.setText(ts.getName()); - expire.setText(ts.getExpirationTimeMinutes() < 0 ? "" : ts.getExpirationTimeMinutes() + ""); - urlToLoad.setText(ts.getUrlTemplate() == null ? "" : - ts.getUrlTemplate().replace("{$x}", "{1}").replace("{$y}", "{2}").replace("{$z}", "{0}")); - elliptic.setChecked(ts.isEllipticYTile()); + public static void defineNewEditLayer(@NonNull FragmentManager fm, @Nullable Fragment targetFragment, @Nullable String editedLayerName) { + EditMapSourceDialogFragment.showInstance(fm, targetFragment, editedLayerName); } public MapTileLayer getUnderlayLayer() {