add/edit map source dialog redesign

This commit is contained in:
veliymolfar 2020-05-26 17:06:14 +03:00
parent b45e432eb2
commit d254de8598
16 changed files with 1332 additions and 180 deletions

View file

@ -0,0 +1,42 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/value_input_layout"
style="@style/InputLayoutStyle.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding"
android:minHeight="@dimen/favorites_list_item_height"
app:endIconMode="clear_text"
app:hintAnimationEnabled="false"
app:hintEnabled="false">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/value_edit_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/shared_string_never"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/dialog_descr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding"
android:lineSpacingExtra="@dimen/line_spacing_extra_description"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
tools:text="@string/expire_time_descr" />
</LinearLayout>

View file

@ -0,0 +1,125 @@
<?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:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_height"
android:background="?attr/actionModeBackground"
android:theme="?attr/toolbar_theme"
app:title="@string/edit_online_source"
osmand:titleTextColor="?android:textColorPrimary">
<ImageView
android:id="@+id/toolbar_action"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_gravity="end"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
tools:src="@drawable/ic_action_help" />
</androidx.appcompat.widget.Toolbar>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/name_input_layout"
style="@style/InputLayoutStyle.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/content_padding"
android:hint="@string/shared_string_name"
app:helperText="@string/online_map_name_helper_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/name_edit_text"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url_input_layout"
style="@style/InputLayoutStyle.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding"
android:hint="@string/edit_tilesource_url_to_load"
app:helperText="@string/online_map_url_helper_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/url_edit_text"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.textfield.TextInputLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/divider_color_basic" />
<LinearLayout
android:id="@+id/content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</LinearLayout>
</ScrollView>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/divider_color_basic" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/bg_color"
android:minHeight="@dimen/dialog_button_ex_height"
android:paddingStart="@dimen/content_padding"
android:paddingTop="@dimen/content_padding_small"
android:paddingEnd="@dimen/content_padding"
android:paddingBottom="@dimen/content_padding_small">
<FrameLayout
android:id="@+id/save_button"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center"
android:background="?attr/dlg_btn_primary">
<TextView
android:id="@+id/save_button_title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="?attr/selectableItemBackground"
android:gravity="center"
android:text="@string/shared_string_save"
android:textColor="?attr/dlg_btn_primary_text" />
</FrameLayout>
</FrameLayout>
</LinearLayout>

View file

@ -76,7 +76,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:hint="@string/shared_string_name" android:hint="@string/shared_string_name"
app:boxBackgroundColor="#4DCCCCCC"> app:boxBackgroundColor="@color/input_layout_bg_color">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/name_edit" android:id="@+id/name_edit"
@ -129,7 +129,7 @@
android:layout_marginRight="@dimen/content_padding" android:layout_marginRight="@dimen/content_padding"
android:layout_weight="1" android:layout_weight="1"
android:hint="@string/shared_string_description" android:hint="@string/shared_string_description"
app:boxBackgroundColor="#4DCCCCCC"> app:boxBackgroundColor="@color/input_layout_bg_color">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/description_edit" android:id="@+id/description_edit"

View file

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/slider_descr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding"
android:lineSpacingExtra="@dimen/line_spacing_extra_description"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
tools:text="@string/terrain_slider_description" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="@dimen/content_padding"
android:paddingTop="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding">
<net.osmand.plus.widgets.TextViewEx
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:lineSpacingExtra="@dimen/line_spacing_extra_description"
android:text="@string/shared_string_min"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size" />
<net.osmand.plus.widgets.TextViewEx
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:lineSpacingExtra="@dimen/line_spacing_extra_description"
android:text="@string/shared_string_max"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/zoom_value_min"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="wrap_content"
android:gravity="start"
android:lineSpacingExtra="@dimen/line_spacing_extra_description"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_list_text_size"
tools:text="3" />
<com.google.android.material.slider.Slider
android:id="@+id/zoom_slider"
style="@style/Widget.Styled.Slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_weight="1"
android:stepSize="1" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/zoom_value_max"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="wrap_content"
android:gravity="end"
android:lineSpacingExtra="@dimen/line_spacing_extra_description"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_list_text_size"
tools:text="19" />
</LinearLayout>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/dialog_descr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding"
android:lineSpacingExtra="@dimen/line_spacing_extra_description"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
tools:text="@string/terrain_slider_description" />
</LinearLayout>

View file

@ -465,5 +465,6 @@
<color name="switch_button_active_dark">#1AD28521</color> <color name="switch_button_active_dark">#1AD28521</color>
<color name="switch_button_active_stroke">#80237BFF</color> <color name="switch_button_active_stroke">#80237BFF</color>
<color name="empty_hint_bg">#80000000</color> <color name="empty_hint_bg">#80000000</color>
<color name="input_layout_bg_color">#4DCCCCCC</color>
</resources> </resources>

View file

@ -11,6 +11,23 @@
Thx - Hardy Thx - Hardy
--> -->
<string name="tiles_storage_descr">Choose how downloaded tiles will be stored.</string>
<string name="expire_time_descr">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.</string>
<string name="map_source_zoom_levels_descr">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.</string>
<string name="map_source_zoom_levels">Set the minimum and maximum zoom level at which the online map will be displayed or loaded.</string>
<string name="storage_format">Storage format</string>
<string name="mercator_projection">Mercator Projection</string>
<string name="expire_time">Expire time</string>
<string name="edit_online_source">Edit online source</string>
<string name="online_map_url_helper_text">Enter or paste URL for online source.</string>
<string name="online_map_name_helper_text">Provide name for online map source.</string>
<string name="sqlite_db_file">SQLiteDB file</string>
<string name="one_image_per_tile">One image file per tile</string>
<string name="pseudo_mercator_projection">Pseudo-Mercator projection</string>
<string name="unsupported_type_error">Unsupported type</string> <string name="unsupported_type_error">Unsupported type</string>
<string name="index_item_world_basemap_detailed">World overview map (detailed)</string> <string name="index_item_world_basemap_detailed">World overview map (detailed)</string>
<string name="profiles_for_action_not_found">Could not find any such profiles.</string> <string name="profiles_for_action_not_found">Could not find any such profiles.</string>

View file

@ -789,4 +789,16 @@
<style name="ThemeOverlay.AppCompat.ActionBar"> <style name="ThemeOverlay.AppCompat.ActionBar">
<item name="android:padding">0dp</item> <item name="android:padding">0dp</item>
</style> </style>
<style name="InputLayoutStyle.FilledBox" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
<item name="android:minHeight">@dimen/favorites_list_item_height</item>
<item name="android:textColorHint">?android:textColorSecondary</item>
<item name="boxBackgroundColor">@color/input_layout_bg_color</item>
<item name="errorEnabled">true</item>
<item name="helperTextEnabled">true</item>
<item name="helperTextTextColor">?android:textColorSecondary</item>
<item name="hintAnimationEnabled">true</item>
<item name="hintTextColor">?android:textColorSecondary</item>
</style>
</resources> </resources>

View file

@ -82,6 +82,8 @@ public class SQLiteTileSource implements ITileSource {
if (is.getName().equalsIgnoreCase(sourceName)) { if (is.getName().equalsIgnoreCase(sourceName)) {
base = is; base = is;
urlTemplate = is.getUrlTemplate(); urlTemplate = is.getUrlTemplate();
expirationTimeMillis = is.getExpirationTimeMillis();
inversiveZoom = is.getInversiveZoom();
break; break;
} }
} }
@ -336,6 +338,8 @@ public class SQLiteTileSource implements ITileSource {
} }
public void updateFromTileSourceTemplate(TileSourceTemplate r) { public void updateFromTileSourceTemplate(TileSourceTemplate r) {
db = ctx.getSQLiteAPI().getOrCreateDatabase(
ctx.getAppPath(TILES_INDEX_DIR).getAbsolutePath() + "/" + name + SQLITE_EXT, false);
if (!onlyReadonlyAvailable) { if (!onlyReadonlyAvailable) {
int maxZoom = r.getMaximumZoomSupported(); int maxZoom = r.getMaximumZoomSupported();
int minZoom = r.getMinimumZoomSupported(); int minZoom = r.getMinimumZoomSupported();
@ -347,10 +351,10 @@ public class SQLiteTileSource implements ITileSource {
if (getUrlTemplate() != null && !getUrlTemplate().equals(r.getUrlTemplate())) { if (getUrlTemplate() != null && !getUrlTemplate().equals(r.getUrlTemplate())) {
db.execSQL("update info set " + URL + " = '" + 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 + "'"); db.execSQL("update info set " + MIN_ZOOM + " = '" + minZoom + "'");
} }
if (r.getMaximumZoomSupported() != maxZoom) { if (maxZoom != this.maxZoom) {
db.execSQL("update info set " + MAX_ZOOM + " = '" + maxZoom + "'"); db.execSQL("update info set " + MAX_ZOOM + " = '" + maxZoom + "'");
} }
if (r.isEllipticYTile() != isEllipticYTile()) { if (r.isEllipticYTile() != isEllipticYTile()) {

View file

@ -445,12 +445,12 @@ public class MapActivityLayers {
final String layerOsmVector = "LAYER_OSM_VECTOR"; final String layerOsmVector = "LAYER_OSM_VECTOR";
final String layerInstallMore = "LAYER_INSTALL_MORE"; 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.put(layerOsmVector, getString(R.string.vector_data));
entriesMap.putAll(settings.getTileSourceEntries()); entriesMap.putAll(settings.getTileSourceEntries());
entriesMap.put(layerInstallMore, getString(R.string.install_more)); 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<Entry<String, String>> entriesMapList = new ArrayList<>(entriesMap.entrySet()); final List<Entry<String, String>> entriesMapList = new ArrayList<>(entriesMap.entrySet());
@ -499,26 +499,8 @@ public class MapActivityLayers {
updateMapSource(mapView, null); updateMapSource(mapView, null);
updateItem(it, adapter, null); updateItem(it, adapter, null);
break; break;
case layerEditInstall: case layerAdd:
OsmandRasterMapsPlugin.defineNewEditLayer(activity, new ResultMatcher<TileSourceTemplate>() { OsmandRasterMapsPlugin.defineNewEditLayer(activity.getSupportFragmentManager(), null, null);
@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);
break; break;
case layerInstallMore: case layerInstallMore:
OsmandRasterMapsPlugin.installMapLayers(activity, new ResultMatcher<TileSourceTemplate>() { OsmandRasterMapsPlugin.installMapLayers(activity, new ResultMatcher<TileSourceTemplate>() {

View file

@ -63,6 +63,7 @@ import net.osmand.plus.download.DownloadIndexesThread.DownloadEvents;
import net.osmand.plus.download.IndexItem; import net.osmand.plus.download.IndexItem;
import net.osmand.plus.helpers.FileNameTranslationHelper; import net.osmand.plus.helpers.FileNameTranslationHelper;
import net.osmand.plus.inapp.InAppPurchaseHelper; import net.osmand.plus.inapp.InAppPurchaseHelper;
import net.osmand.plus.mapsource.EditMapSourceDialogFragment;
import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin;
import net.osmand.plus.resources.IncrementalChangesManager; import net.osmand.plus.resources.IncrementalChangesManager;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -81,7 +82,7 @@ import java.util.Set;
import java.util.regex.Pattern; 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_FILE_NAME_CHARACTERS = Pattern.compile("[?:\"*|/<>]");
public static final Pattern ILLEGAL_PATH_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.setMessage(getString(R.string.clear_confirmation_msg, fn));
confirm.show(); confirm.show();
} else if (resId == R.string.shared_string_edit) { } else if (resId == R.string.shared_string_edit) {
OsmandRasterMapsPlugin.defineNewEditLayer(getDownloadActivity(), OsmandRasterMapsPlugin.defineNewEditLayer(getDownloadActivity().getSupportFragmentManager(), this, info.getFileName());
new ResultMatcher<TileSourceManager.TileSourceTemplate>() {
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean publish(TileSourceManager.TileSourceTemplate object) {
getDownloadActivity().reloadLocalIndexes();
return true;
}
}, info.getFileName());
} else if (resId == R.string.local_index_mi_restore) { } else if (resId == R.string.local_index_mi_restore) {
new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.RESTORE_OPERATION).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, info); new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.RESTORE_OPERATION).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, info);
} else if (resId == R.string.shared_string_delete) { } 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) { RenameCallback callback) {
File dest = checkRenamePossibility(ctx, source, newName, false); File dest = checkRenamePossibility(ctx, source, newName, false);
if (dest == null) { if (dest == null) {
@ -405,6 +394,10 @@ public class LocalIndexesFragment extends OsmandExpandableListFragment implement
return dest; return dest;
} }
@Override
public void onMapSourceUpdated() {
getDownloadActivity().reloadLocalIndexes();
}
public class LoadLocalIndexTask extends AsyncTask<Void, LocalIndexInfo, List<LocalIndexInfo>> public class LoadLocalIndexTask extends AsyncTask<Void, LocalIndexInfo, List<LocalIndexInfo>>
implements AbstractLoadLocalIndexTask { implements AbstractLoadLocalIndexTask {

View file

@ -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<TileSourceManager.TileSourceTemplate> 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<TileSourceManager.TileSourceTemplate> 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();
}
}

View file

@ -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);
}
}

View file

@ -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<Float> 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<Float> 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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -6,23 +6,17 @@ import android.graphics.drawable.Drawable;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.View; import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter; 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 android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatCheckBox;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.IndexConstants;
import net.osmand.ResultMatcher; import net.osmand.ResultMatcher;
import net.osmand.StateChangedListener; import net.osmand.StateChangedListener;
import net.osmand.map.ITileSource; 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.CommonPreference;
import net.osmand.plus.settings.backend.OsmandSettings.LayerTransparencySeekbarMode; import net.osmand.plus.settings.backend.OsmandSettings.LayerTransparencySeekbarMode;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.SQLiteTileSource;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.Version; import net.osmand.plus.Version;
import net.osmand.plus.activities.DownloadTilesDialog; import net.osmand.plus.activities.DownloadTilesDialog;
import net.osmand.plus.mapsource.EditMapSourceDialogFragment;
import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.MapActivityLayers; import net.osmand.plus.activities.MapActivityLayers;
import net.osmand.plus.dashboard.DashboardOnMap.DashboardType; 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.plus.views.OsmandMapTileView;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; 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.OVERLAY_MAP;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.UNDERLAY_MAP; import static net.osmand.aidlapi.OsmAndCustomizationConstants.UNDERLAY_MAP;
import static net.osmand.plus.ContextMenuAdapter.makeDeleteAction; import static net.osmand.plus.ContextMenuAdapter.makeDeleteAction;
import static net.osmand.plus.UiUtilities.CompoundButtonType.PROFILE_DEPENDENT;
public class OsmandRasterMapsPlugin extends OsmandPlugin { public class OsmandRasterMapsPlugin extends OsmandPlugin {
@ -483,133 +474,8 @@ public class OsmandRasterMapsPlugin extends OsmandPlugin {
t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
public static void defineNewEditLayer(final Activity activity, final ResultMatcher<TileSourceTemplate> resultMatcher, final String editedLayerName) { public static void defineNewEditLayer(@NonNull FragmentManager fm, @Nullable Fragment targetFragment, @Nullable String editedLayerName) {
final OsmandApplication app = (OsmandApplication) activity.getApplication(); EditMapSourceDialogFragment.showInstance(fm, targetFragment, editedLayerName);
final OsmandSettings settings = app.getSettings();
final Map<String, String> 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<String> templates = new ArrayList<>(entriesMap.keySet());
templates.add(0, "");
ArrayAdapter<String> 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<TileSourceTemplate> 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 MapTileLayer getUnderlayLayer() { public MapTileLayer getUnderlayLayer() {