diff --git a/GPX.md b/GPX.md
deleted file mode 100644
index b3f1e441e4..0000000000
--- a/GPX.md
+++ /dev/null
@@ -1,157 +0,0 @@
-The OsmAnd's GPX file format conforms to the GPX 1.1 specification with additional data written as extensions. There are several sections of such data:
-
-## Track appearance
-The following parameters are used to customize the appearance of a track on the map. They are used inside the "gpx" tag and apply to all tracks contained in the gpx.
-#### Parameters
-* **show_arrows** [*true, false*] - show / hide arrows along the path line.
-* **width** [*thin, medium, bold, 1-24*] - width of the track line on the map. The thin, medium, and bold are style depended values (should be defined as currentTrackWidth attribute).
-* **color** [*#AARRGGBB, #RRGGBB*] - color of a track line on the map. Hex value.
-* **split_type** [*no_split, distance, time*] - split type for a track.
-* **split_interval** [*double*] - split interval for a track. Distance (meters), time (seconds).
-
-#### Example:
-```xml
-
-...
-
- true
- #4e4eff
- distance
- 2000.0
- bold
-
-
-```
-## Details of a track point (trkpt)
-Written to a gpx file while recording a track.
-* **speed** (meters per second)
-* **heading** (0-359 degrees)
-
-#### Example:
-```xml
-
- 203
-
- 3
-
- 273
- 5.02
-
-
-```
-
-## Calculated route(s)
-This data contains all details of a route built with **OsmAnd** (route segments, turns, road names, road types, restrictions, etc.). The route can be completely restored as if just built, even in the absence of the respective offline maps.
-
-A gpx file may contain several routes. Each of them is contained in a specific segment under **trkseg** / **extensions**. A gpx file is saved in this form when exporting a constructed route or when saving a track that consists of several separate segments via the **Plan a route** functionality.
-**Plan a route** also adds one (or several, in accordance with the number of contained separate segments / tracks) **rte** blocks to the gpx file, containing route key points (**rtept**).
-#### Gpx structure:
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ...
-
- ...
-
-
-
-```
-
-#### Example:
-```xml
-
-
- Fri 06 Nov 2020
-
-
- Fri 06 Nov 2020
-
-
- 0.801
-
-
- 0.998
-
-
- 1
-
-
- 0.963
-
-
- 0.899
-
-
- ....
-
-
-
-
-
-
-
- ...
-
-
-
-
-
-
-
- ...
-
-
-
-
-
-
-
-
- pedestrian
- 0
-
-
-
-
- pedestrian
- 24
-
-
-
-
- pedestrian
- 89
-
-
-
-
- pedestrian
- 121
-
-
-
-
-```
diff --git a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java
index 14d0abab79..861a7c9b93 100644
--- a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java
+++ b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java
@@ -274,6 +274,10 @@ public class Amenity extends MapObject {
return null;
}
+ public String getTagContent(String tag) {
+ return getTagContent(tag, null);
+ }
+
public String getTagContent(String tag, String lang) {
if (lang != null) {
String translateName = getAdditionalInfo(tag + ":" + lang);
diff --git a/OsmAnd/no_translate.xml b/OsmAnd/no_translate.xml
index 92d3a4034f..d5ecc7c149 100644
--- a/OsmAnd/no_translate.xml
+++ b/OsmAnd/no_translate.xml
@@ -40,7 +40,7 @@
items modified
OsmAnd Unlimited
Markers
- https://test.openplacereviews.org/
+ https://openplacereviews.org/
https://test.openplacereviews.org/
v8G8r9NLJZGMV4he5lwbQlz620FNVARKjI9Bm5UJ
jDvM95Ne1Bq2BDTmIfB6b3ZMxvdK87WGfp6DC07J
diff --git a/OsmAnd/res/layout/track_menu.xml b/OsmAnd/res/layout/track_menu.xml
index 671a9332f1..db0f8a47f9 100644
--- a/OsmAnd/res/layout/track_menu.xml
+++ b/OsmAnd/res/layout/track_menu.xml
@@ -91,6 +91,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java
index 842a36237e..6b9aa4d0a4 100644
--- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java
+++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java
@@ -210,6 +210,8 @@ public class OsmandApplication extends MultiDexApplication {
File mapillaryVectorTilesPath = new File(tilesPath, TileSourceManager.getMapillaryVectorSource().getName());
Algorithms.removeAllFiles(mapillaryRasterTilesPath);
Algorithms.removeAllFiles(mapillaryVectorTilesPath);
+ // Remove travel sqlite db files
+ removeSqliteDbTravelFiles();
}
checkPreferredLocale();
@@ -234,6 +236,17 @@ public class OsmandApplication extends MultiDexApplication {
return externalStorageDirectoryReadOnly;
}
+ private void removeSqliteDbTravelFiles() {
+ File[] files = getAppPath(IndexConstants.WIKIVOYAGE_INDEX_DIR).listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.getName().endsWith(IndexConstants.BINARY_WIKIVOYAGE_MAP_INDEX_EXT)) {
+ file.delete();
+ }
+ }
+ }
+ }
+
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
index 0de562fa2b..12f66f8c49 100644
--- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
+++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
@@ -1200,7 +1200,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(gpxFile.path);
}
- TrackAppearanceFragment.showInstance(this, selectedGpxFile);
+ TrackAppearanceFragment.showInstance(this, selectedGpxFile, null);
} else if (toShow instanceof QuadRect) {
QuadRect qr = (QuadRect) toShow;
mapView.fitRectToMap(qr.left, qr.right, qr.top, qr.bottom, (int) qr.width(), (int) qr.height(), 0);
diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java
index b5dd0dc3ea..258ed7be38 100644
--- a/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java
+++ b/OsmAnd/src/net/osmand/plus/activities/MapActivityActions.java
@@ -928,7 +928,7 @@ public class MapActivityActions implements DialogProvider {
MapActivity.clearPrevActivityIntent();
TravelHelper travelHelper = getMyApplication().getTravelHelper();
travelHelper.initializeDataOnAppStartup();
- if (!travelHelper.isAnyTravelBookPresent()) {
+ if (!travelHelper.isAnyTravelBookPresent() && !travelHelper.getBookmarksHelper().hasSavedArticles()) {
WikivoyageWelcomeDialogFragment.showInstance(mapActivity.getSupportFragmentManager());
} else {
Intent intent = new Intent(mapActivity, WikivoyageExploreActivity.class);
diff --git a/OsmAnd/src/net/osmand/plus/base/ContextMenuFragment.java b/OsmAnd/src/net/osmand/plus/base/ContextMenuFragment.java
index 4cc90afd96..4b625e9515 100644
--- a/OsmAnd/src/net/osmand/plus/base/ContextMenuFragment.java
+++ b/OsmAnd/src/net/osmand/plus/base/ContextMenuFragment.java
@@ -946,6 +946,11 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
protected void runLayoutListener() {
+ runLayoutListener(null);
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ protected void runLayoutListener(final Runnable runnable) {
if (view != null) {
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@@ -974,6 +979,9 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment
int menuState = getCurrentMenuState();
listener.onContextMenuStateChanged(ContextMenuFragment.this, menuState, menuState);
}
+ if (runnable != null) {
+ runnable.run();
+ }
}
}
});
diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java
index 183e41a84f..93f2bdf6b5 100644
--- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java
+++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java
@@ -421,7 +421,7 @@ public class MenuBuilder {
new Thread(new Runnable() {
@Override
public void run() {
- if (openDBAPI.checkPrivateKeyValid(baseUrl, name, privateKey)) {
+ if (openDBAPI.checkPrivateKeyValid(app, baseUrl, name, privateKey)) {
app.runInUIThread(new Runnable() {
@Override
public void run() {
diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/UploadPhotosAsyncTask.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/UploadPhotosAsyncTask.java
index 30932e8b42..1bb48ff1f0 100644
--- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/UploadPhotosAsyncTask.java
+++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/UploadPhotosAsyncTask.java
@@ -131,12 +131,12 @@ public class UploadPhotosAsyncTask extends AsyncTask {
try {
StringBuilder error = new StringBuilder();
String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
- String username = app.getSettings().OPR_USERNAME.get();
+ String name = app.getSettings().OPR_BLOCKCHAIN_NAME.get();
res = openDBAPI.uploadImage(
placeId,
baseUrl,
privateKey,
- username,
+ name,
response, error);
if (res != 200) {
app.showToastMessage(error.toString());
@@ -170,7 +170,7 @@ public class UploadPhotosAsyncTask extends AsyncTask {
String baseUrl = OPRConstants.getBaseUrl(app);
String name = app.getSettings().OPR_USERNAME.get();
String privateKey = app.getSettings().OPR_ACCESS_TOKEN.get();
- if (openDBAPI.checkPrivateKeyValid(baseUrl, name, privateKey)) {
+ if (openDBAPI.checkPrivateKeyValid(app, baseUrl, name, privateKey)) {
app.showToastMessage(R.string.cannot_upload_image);
} else {
app.runInUIThread(new Runnable() {
diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/SelectedGpxMenuController.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/SelectedGpxMenuController.java
index 427e0cce35..5ea0766469 100644
--- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/SelectedGpxMenuController.java
+++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/SelectedGpxMenuController.java
@@ -42,9 +42,8 @@ public class SelectedGpxMenuController extends MenuController {
leftTitleButtonController = new TitleButtonController() {
@Override
public void buttonPressed() {
- SelectedGpxFile selectedGpxFile = selectedGpxPoint.getSelectedGpxFile();
- mapActivity.getContextMenu().hide(false);
- TrackMenuFragment.showInstance(mapActivity, selectedGpxFile.getGpxFile().path, selectedGpxFile.isShowCurrentTrack());
+ mapContextMenu.hide(false);
+ TrackMenuFragment.showInstance(mapActivity, selectedGpxPoint.getSelectedGpxFile());
}
};
leftTitleButtonController.caption = mapActivity.getString(R.string.shared_string_open_track);
diff --git a/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java
index 789fc0aeb0..178e662176 100644
--- a/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java
+++ b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java
@@ -90,7 +90,7 @@ public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment {
if (mapActivity != null) {
hide();
SelectedGpxFile selectedGpxFile = app.getSavingTrackHelper().getCurrentTrack();
- TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile);
+ TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile, TripRecordingBottomSheet.this);
}
}
});
diff --git a/OsmAnd/src/net/osmand/plus/myplaces/EditTrackGroupDialogFragment.java b/OsmAnd/src/net/osmand/plus/myplaces/EditTrackGroupDialogFragment.java
index 19c398685c..fa7bff99ed 100644
--- a/OsmAnd/src/net/osmand/plus/myplaces/EditTrackGroupDialogFragment.java
+++ b/OsmAnd/src/net/osmand/plus/myplaces/EditTrackGroupDialogFragment.java
@@ -30,6 +30,7 @@ import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.data.FavouritePoint;
import net.osmand.plus.FavouritesDbHelper;
+import net.osmand.plus.GpxSelectionHelper;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem;
import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType;
@@ -70,19 +71,32 @@ public class EditTrackGroupDialogFragment extends MenuBottomSheetDialogFragment
public static final String TAG = EditTrackGroupDialogFragment.class.getSimpleName();
private OsmandApplication app;
+ private GpxSelectionHelper selectedGpxHelper;
+ private MapMarkersHelper mapMarkersHelper;
private GpxDisplayGroup group;
@Override
public void createMenuItems(Bundle savedInstanceState) {
- app = requiredMyApplication();
if (group == null) {
return;
}
+ app = requiredMyApplication();
+ selectedGpxHelper = app.getSelectedGpxHelper();
+ mapMarkersHelper = app.getMapMarkersHelper();
items.add(new TitleItem(getCategoryName(app, group.getName())));
+ GPXFile gpxFile = group.getGpx();
+
+ boolean currentTrack = group.getGpx().showCurrentTrack;
+
+ SelectedGpxFile selectedGpxFile;
+ if (currentTrack) {
+ selectedGpxFile = selectedGpxHelper.getSelectedCurrentRecordingTrack();
+ } else {
+ selectedGpxFile = selectedGpxHelper.getSelectedFileByPath(gpxFile.path);
+ }
boolean trackPoints = group.getType() == GpxDisplayItemType.TRACK_POINTS;
- SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(group.getGpx().path);
if (trackPoints && selectedGpxFile != null) {
items.add(createShowOnMapItem(selectedGpxFile));
}
@@ -92,7 +106,9 @@ public class EditTrackGroupDialogFragment extends MenuBottomSheetDialogFragment
}
items.add(new OptionsDividerItem(app));
-// items.add(createCopyToMarkersItem());
+ if (!currentTrack) {
+ items.add(createCopyToMarkersItem(gpxFile));
+ }
items.add(createCopyToFavoritesItem());
items.add(new OptionsDividerItem(app));
@@ -175,27 +191,51 @@ public class EditTrackGroupDialogFragment extends MenuBottomSheetDialogFragment
.create();
}
- private BaseBottomSheetItem createCopyToMarkersItem() {
+ private BaseBottomSheetItem createCopyToMarkersItem(final GPXFile gpxFile) {
+ final MapMarkersGroup markersGroup = getOrCreateMarkersGroup(gpxFile);
+ final Set categories = markersGroup.getWptCategories();
+ final boolean synced = categories != null && categories.contains(group.getName());
+
return new SimpleBottomSheetItem.Builder()
- .setIcon(getContentIcon(R.drawable.ic_action_copy))
- .setTitle(getString(R.string.copy_to_map_markers))
- .setLayoutId(R.layout.bottom_sheet_item_simple)
+ .setIcon(getContentIcon(synced ? R.drawable.ic_action_delete_dark : R.drawable.ic_action_copy))
+ .setTitle(getString(synced ? R.string.remove_from_map_markers : R.string.copy_to_map_markers))
.setLayoutId(R.layout.bottom_sheet_item_simple_pad_32dp)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
-// MapMarkersHelper markersHelper = app.getMapMarkersHelper();
-// MapMarkersGroup markersGroup = markersHelper.getMarkersGroup(group);
-// if (markersGroup != null) {
-// markersHelper.removeMarkersGroup(markersGroup);
-// } else {
-// markersHelper.addOrEnableGroup(group);
-// }
+ updateGroupWptCategory(gpxFile, markersGroup, categories, synced);
+ dismiss();
}
})
.create();
}
+ private void updateGroupWptCategory(GPXFile gpxFile, MapMarkersGroup markersGroup, Set categories, boolean synced) {
+ SelectedGpxFile selectedGpxFile = selectedGpxHelper.getSelectedFileByPath(gpxFile.path);
+ if (selectedGpxFile == null) {
+ selectedGpxHelper.selectGpxFile(gpxFile, true, false, false, false, false);
+ }
+ Set selectedCategories = new HashSet<>();
+ if (categories != null) {
+ selectedCategories.addAll(categories);
+ }
+ if (synced) {
+ selectedCategories.remove(group.getName());
+ } else {
+ selectedCategories.add(group.getName());
+ }
+ mapMarkersHelper.updateGroupWptCategories(markersGroup, selectedCategories);
+ mapMarkersHelper.runSynchronization(markersGroup);
+ }
+
+ private MapMarkersGroup getOrCreateMarkersGroup(GPXFile gpxFile) {
+ MapMarkersGroup markersGroup = mapMarkersHelper.getMarkersGroup(gpxFile);
+ if (markersGroup == null) {
+ markersGroup = mapMarkersHelper.addOrEnableGroup(gpxFile);
+ }
+ return markersGroup;
+ }
+
private BaseBottomSheetItem createCopyToFavoritesItem() {
return new SimpleBottomSheetItem.Builder()
.setIcon(getContentIcon(R.drawable.ic_action_copy))
diff --git a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java
index a16361c8bd..1fdeba64f0 100644
--- a/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java
+++ b/OsmAnd/src/net/osmand/plus/osmedit/opr/OpenDBAPI.java
@@ -3,10 +3,15 @@ package net.osmand.plus.osmedit.opr;
import android.net.TrafficStats;
import android.os.Build;
+import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
import net.osmand.PlatformUtil;
import net.osmand.osm.io.NetworkUtils;
+import net.osmand.plus.OsmandApplication;
+import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -27,6 +32,7 @@ import java.security.KeyPair;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -40,7 +46,7 @@ public class OpenDBAPI {
public static final String PURPOSE = "osmand-android";
private static final Log log = PlatformUtil.getLog(SecUtils.class);
private static final String checkLoginEndpoint = "api/auth/user-check-loginkey?";
- private static final String LOGIN_SUCCESS_MESSAGE = "{\"result\":\"OK\"}";
+ private static final String LOGIN_SUCCESS_MESSAGE = "\"result\":\"OK\"";
private static final int THREAD_ID = 11200;
/*
@@ -51,7 +57,7 @@ public class OpenDBAPI {
* Need to encode key
* Do not call on mainThread
*/
- public boolean checkPrivateKeyValid(String baseUrl, String username, String privateKey) {
+ public boolean checkPrivateKeyValid(OsmandApplication app, String baseUrl, String username, String privateKey) {
String url = null;
try {
String purposeParam = "purpose=" + PURPOSE;
@@ -65,9 +71,29 @@ public class OpenDBAPI {
} catch (UnsupportedEncodingException e) {
return false;
}
+
StringBuilder response = new StringBuilder();
- return (NetworkUtils.sendGetRequest(url,null,response) == null) &&
- response.toString().contains(LOGIN_SUCCESS_MESSAGE);
+ String error = NetworkUtils.sendGetRequest(url, null, response);
+ if (error == null) {
+ String responseStr = response.toString();
+ try {
+ Map map = new Gson().fromJson(
+ responseStr, new TypeToken>() {
+ }.getType()
+ );
+ if (!Algorithms.isEmpty(map) && map.containsKey("blockchain-name")) {
+ String blockchainName = map.get("blockchain-name");
+ app.getSettings().OPR_BLOCKCHAIN_NAME.set(blockchainName);
+ } else {
+ return false;
+ }
+ } catch (JsonSyntaxException e) {
+ return false;
+ }
+ return responseStr.contains(LOGIN_SUCCESS_MESSAGE);
+ } else {
+ return false;
+ }
}
public int uploadImage(String[] placeId, String baseUrl, String privateKey, String username, String image, StringBuilder sb) throws FailedVerificationException {
diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java
index dfaaea9468..0d3cddb136 100644
--- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java
+++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java
@@ -1171,6 +1171,9 @@ public class OsmandSettings {
public final OsmandPreference OPR_USERNAME =
new StringPreference(this, "opr_username_secret", "").makeGlobal();
+ public final OsmandPreference OPR_BLOCKCHAIN_NAME =
+ new StringPreference(this, "opr_blockchain_name", "").makeGlobal();
+
// this value boolean is synchronized with settings_pref.xml preference offline POI/Bugs edition
public final OsmandPreference OFFLINE_EDITION = new BooleanPreference(this, "offline_osm_editing", true).makeGlobal().makeShared();
public final OsmandPreference USE_DEV_URL = new BooleanPreference(this, "use_dev_url", false).makeGlobal().makeShared();
diff --git a/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java
index ddc102adc9..522103ac02 100644
--- a/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java
+++ b/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java
@@ -19,6 +19,7 @@ import androidx.activity.OnBackPressedCallback;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
@@ -37,6 +38,7 @@ import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.UiUtilities.DialogButtonType;
import net.osmand.plus.activities.MapActivity;
+import net.osmand.plus.base.ContextMenuFragment;
import net.osmand.plus.base.ContextMenuScrollFragment;
import net.osmand.plus.dialogs.GpxAppearanceAdapter;
import net.osmand.plus.dialogs.GpxAppearanceAdapter.AppearanceListItem;
@@ -165,16 +167,7 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
}
requireMyActivity().getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
public void handleOnBackPressed() {
- MapActivity mapActivity = getMapActivity();
- if (mapActivity != null) {
- TripRecordingBottomSheet fragment = mapActivity.getTripRecordingBottomSheet();
- if (fragment != null) {
- fragment.show();
- } else {
- mapActivity.launchPrevActivityIntent();
- }
- dismissImmediate();
- }
+ dismiss();
}
});
}
@@ -385,6 +378,14 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
return y;
}
+ @Override
+ public void onContextMenuDismiss(@NonNull ContextMenuFragment fragment) {
+ Fragment target = getTargetFragment();
+ if (target instanceof TripRecordingBottomSheet) {
+ ((TripRecordingBottomSheet) target).show();
+ }
+ }
+
private void updateAppearanceIcon() {
Drawable icon = getTrackIcon(app, trackDrawInfo.getWidth(), trackDrawInfo.isShowArrows(), trackDrawInfo.getColor());
trackIcon.setImageDrawable(icon);
@@ -725,11 +726,12 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
return totalScreenHeight - frameTotalHeight;
}
- public static boolean showInstance(@NonNull MapActivity mapActivity, @NonNull SelectedGpxFile selectedGpxFile) {
+ public static boolean showInstance(@NonNull MapActivity mapActivity, @NonNull SelectedGpxFile selectedGpxFile, Fragment target) {
try {
TrackAppearanceFragment fragment = new TrackAppearanceFragment();
- fragment.setSelectedGpxFile(selectedGpxFile);
fragment.setRetainInstance(true);
+ fragment.setSelectedGpxFile(selectedGpxFile);
+ fragment.setTargetFragment(target, 0);
mapActivity.getSupportFragmentManager()
.beginTransaction()
diff --git a/OsmAnd/src/net/osmand/plus/track/TrackMenuFragment.java b/OsmAnd/src/net/osmand/plus/track/TrackMenuFragment.java
index a551ba2330..9147de6de9 100644
--- a/OsmAnd/src/net/osmand/plus/track/TrackMenuFragment.java
+++ b/OsmAnd/src/net/osmand/plus/track/TrackMenuFragment.java
@@ -138,6 +138,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
private ImageView searchButton;
private EditText searchEditText;
private TextView toolbarTextView;
+ private ViewGroup headerContainer;
private View routeMenuTopShadowAll;
private BottomNavigationView bottomNav;
@@ -185,13 +186,17 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
@Override
public int getToolbarHeight() {
- return toolbarHeightPx;
+ return isPortrait() ? toolbarHeightPx : 0;
}
public float getMiddleStateKoef() {
return 0.5f;
}
+ public int getMinY() {
+ return getFullScreenTopPosY();
+ }
+
@Override
public int getSupportedMenuStatesPortrait() {
return MenuState.HEADER_ONLY | MenuState.HALF_SCREEN | MenuState.FULL_SCREEN;
@@ -226,11 +231,22 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
FragmentActivity activity = requireMyActivity();
activity.getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
public void handleOnBackPressed() {
- MapActivity mapActivity = getMapActivity();
- if (mapActivity != null) {
- mapActivity.launchPrevActivityIntent();
+ if (getCurrentMenuState() != MenuState.HEADER_ONLY && isPortrait()) {
+ openMenuHeaderOnly();
+ } else {
+ dismiss();
+
+ MapActivity mapActivity = getMapActivity();
+ if (mapActivity != null) {
+ MapContextMenu contextMenu = mapActivity.getContextMenu();
+ if (contextMenu.isActive() && contextMenu.getPointDescription() != null
+ && contextMenu.getPointDescription().isGpxPoint()) {
+ contextMenu.show();
+ } else {
+ mapActivity.launchPrevActivityIntent();
+ }
+ }
}
- dismiss();
}
});
}
@@ -249,6 +265,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
if (view != null) {
bottomNav = view.findViewById(R.id.bottom_navigation);
routeMenuTopShadowAll = view.findViewById(R.id.route_menu_top_shadow_all);
+ headerContainer = view.findViewById(R.id.header_container);
headerTitle = view.findViewById(R.id.title);
headerIcon = view.findViewById(R.id.icon_view);
toolbarContainer = view.findViewById(R.id.context_menu_toolbar_container);
@@ -258,7 +275,6 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
if (isPortrait()) {
AndroidUiHelper.updateVisibility(getTopShadow(), true);
- AndroidUiHelper.updateVisibility(view.findViewById(R.id.map_my_location_button), false);
} else {
int widthNoShadow = getLandscapeNoShadowWidth();
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(widthNoShadow, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -270,7 +286,8 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
setupToolbar();
updateHeader();
setupButtons(view);
- runLayoutListener();
+ updateCardsLayout();
+ calculateLayoutAndUpdateMenuState();
}
return view;
}
@@ -281,7 +298,6 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
}
private void updateHeader() {
- ViewGroup headerContainer = (ViewGroup) routeMenuTopShadowAll;
if (menuType == TrackMenuType.OVERVIEW) {
setHeaderTitle(gpxTitle, true);
if (overviewCard != null && overviewCard.getView() != null) {
@@ -428,6 +444,17 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
}
}
+ private void updateCardsLayout() {
+ FrameLayout bottomContainer = getBottomContainer();
+ if (menuType == TrackMenuType.OPTIONS) {
+ AndroidUtils.setBackground(app, bottomContainer, isNightMode(),
+ R.color.list_background_color_light, R.color.list_background_color_dark);
+ } else {
+ AndroidUtils.setBackground(app, bottomContainer, isNightMode(),
+ R.color.activity_background_color_light, R.color.activity_background_color_dark);
+ }
+ }
+
@Override
protected void calculateLayout(View view, boolean initLayout) {
menuTitleHeight = routeMenuTopShadowAll.getHeight()
@@ -463,7 +490,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
updateControlsVisibility(true);
}
if (currentMenuState != MenuState.FULL_SCREEN && (changed || !mapPositionAdjusted)) {
- adjustMapPosition(getViewY());
+ adjustMapPosition(getMenuStatePosY(currentMenuState));
}
}
@@ -659,7 +686,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
app.getSelectedGpxHelper().selectGpxFile(gpxFile, gpxFileSelected, false);
mapActivity.refreshMap();
} else if (buttonIndex == APPEARANCE_BUTTON_INDEX) {
- TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile);
+ TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile, this);
} else if (buttonIndex == DIRECTIONS_BUTTON_INDEX) {
MapActivityActions mapActions = mapActivity.getMapActions();
if (app.getRoutingHelper().isFollowingMode()) {
@@ -797,7 +824,7 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
@Override
protected void onHeaderClick() {
- adjustMapPosition(getViewY());
+ updateMenuState();
}
private void adjustMapPosition(int y) {
@@ -837,6 +864,8 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
menuType = type;
setupCards();
updateHeader();
+ updateCardsLayout();
+ calculateLayoutAndUpdateMenuState();
break;
}
}
@@ -845,6 +874,25 @@ public class TrackMenuFragment extends ContextMenuScrollFragment implements Card
});
}
+ private void calculateLayoutAndUpdateMenuState() {
+ runLayoutListener(new Runnable() {
+ @Override
+ public void run() {
+ updateMenuState();
+ }
+ });
+ }
+
+ private void updateMenuState() {
+ if (menuType == TrackMenuType.OPTIONS) {
+ openMenuFullScreen();
+ } else if (menuType == TrackMenuType.OVERVIEW) {
+ openMenuHeaderOnly();
+ } else {
+ openMenuHalfScreen();
+ }
+ }
+
@Override
public void updateContent() {
if (segmentsCard != null) {
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java
index 4ee8099c26..cced9d45ad 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java
@@ -1,6 +1,7 @@
package net.osmand.plus.wikivoyage.article;
import android.annotation.SuppressLint;
+import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
@@ -339,17 +340,20 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme
}
private void updateTrackButton(boolean processing, @Nullable GPXFile gpxFile) {
- if (processing) {
- trackButton.setVisibility(View.GONE);
- gpxProgress.setVisibility(View.VISIBLE);
- } else {
- if (gpxFile != null && gpxFile.getPointsSize() > 0) {
- trackButton.setVisibility(View.VISIBLE);
- trackButton.setText(getString(R.string.shared_string_gpx_points) + " (" + gpxFile.getPointsSize() + ")");
- } else {
+ Context ctx = getContext();
+ if (ctx != null) {
+ if (processing) {
trackButton.setVisibility(View.GONE);
+ gpxProgress.setVisibility(View.VISIBLE);
+ } else {
+ if (gpxFile != null && gpxFile.getPointsSize() > 0) {
+ trackButton.setVisibility(View.VISIBLE);
+ trackButton.setText(ctx.getString(R.string.shared_string_gpx_points) + " (" + gpxFile.getPointsSize() + ")");
+ } else {
+ trackButton.setVisibility(View.GONE);
+ }
+ gpxProgress.setVisibility(View.GONE);
}
- gpxProgress.setVisibility(View.GONE);
}
}
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java
new file mode 100644
index 0000000000..185fdc922c
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelGpx.java
@@ -0,0 +1,14 @@
+package net.osmand.plus.wikivoyage.data;
+
+public class TravelGpx extends TravelArticle {
+
+ public static final String DISTANCE = "distance";
+ public static final String DIFF_ELE_UP = "diff_ele_up";
+ public static final String DIFF_ELE_DOWN = "diff_ele_down";
+ public static final String USER = "user";
+
+ public String user;
+ public float totalDistance = 0;
+ public double diffElevationUp = 0;
+ public double diffElevationDown = 0;
+}
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java
index 02ee5a32c8..d95b9239ee 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelLocalDataHelper.java
@@ -1,6 +1,9 @@
package net.osmand.plus.wikivoyage.data;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -100,6 +103,10 @@ public class TravelLocalDataHelper {
}
}
+ public boolean hasSavedArticles() {
+ return !savedArticles.isEmpty() || dbHelper.hasSavedArticles();
+ }
+
@NonNull
public List getSavedArticles() {
return new ArrayList<>(savedArticles);
@@ -143,7 +150,8 @@ public class TravelLocalDataHelper {
@Nullable
private TravelArticle getArticle(String title, String lang) {
for (TravelArticle article : savedArticles) {
- if (article.title != null && article.title.equals(title) && article.lang != null && article.lang.equals(lang)) {
+ if (Algorithms.stringsEqual(article.title, title)
+ && Algorithms.stringsEqual(article.lang, lang)) {
return article;
}
}
@@ -438,6 +446,25 @@ public class TravelLocalDataHelper {
return res;
}
+ boolean hasSavedArticles() {
+ int count = 0;
+ SQLiteConnection conn = openConnection(true);
+ if (conn != null) {
+ try {
+ SQLiteCursor cursor = conn.rawQuery("SELECT COUNT(*) FROM " + BOOKMARKS_TABLE_NAME, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ count = cursor.getInt(0);
+ }
+ cursor.close();
+ }
+ } finally {
+ conn.close();
+ }
+ }
+ return count > 0;
+ }
+
void addSavedArticle(@NonNull TravelArticle article) {
String travelBook = article.getTravelBook(context);
if (travelBook == null) {
@@ -477,12 +504,12 @@ public class TravelLocalDataHelper {
SQLiteConnection conn = openConnection(false);
if (conn != null) {
try {
- conn.execSQL("DELETE FROM " + BOOKMARKS_TABLE_NAME +
- " WHERE " + BOOKMARKS_COL_ARTICLE_TITLE + " = ?" +
- " AND " + BOOKMARKS_COL_ROUTE_ID + " = ?" +
- " AND " + BOOKMARKS_COL_LANG + " = ?" +
- " AND " + BOOKMARKS_COL_TRAVEL_BOOK + " = ?",
- new Object[]{article.title, article.routeId, article.lang, travelBook});
+ String query = "DELETE FROM " + BOOKMARKS_TABLE_NAME +
+ " WHERE " + BOOKMARKS_COL_ARTICLE_TITLE + " = ?" +
+ " AND " + BOOKMARKS_COL_ROUTE_ID + " = ?" +
+ " AND " + BOOKMARKS_COL_LANG + ((article.lang != null) ? " = '" + article.lang + "'" : " IS NULL") +
+ " AND " + BOOKMARKS_COL_TRAVEL_BOOK + " = ?";
+ conn.execSQL(query, new Object[]{article.title, article.routeId, travelBook});
} finally {
conn.close();
}
@@ -537,7 +564,12 @@ public class TravelLocalDataHelper {
@NonNull
private TravelArticle readSavedArticle(SQLiteCursor cursor) {
- TravelArticle res = new TravelArticle();
+ TravelArticle res;
+ if (cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_LANG)) == null) {
+ res = new TravelGpx();
+ } else {
+ res = new TravelArticle();
+ }
res.title = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_ARTICLE_TITLE));
res.lang = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_LANG));
res.aggregatedPartOf = cursor.getString(cursor.getColumnIndex(BOOKMARKS_COL_IS_PART_OF));
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java
index 0f14896a94..f230929f1d 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java
@@ -2,6 +2,7 @@ package net.osmand.plus.wikivoyage.data;
import android.os.AsyncTask;
import android.text.TextUtils;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -13,6 +14,7 @@ import net.osmand.IndexConstants;
import net.osmand.OsmAndCollator;
import net.osmand.PlatformUtil;
import net.osmand.ResultMatcher;
+import net.osmand.binary.BinaryMapDataObject;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter;
import net.osmand.binary.BinaryMapIndexReader.SearchRequest;
@@ -46,8 +48,18 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import gnu.trove.iterator.TIntObjectIterator;
+
+import static net.osmand.GPXUtilities.Track;
+import static net.osmand.GPXUtilities.TrkSegment;
import static net.osmand.GPXUtilities.WptPt;
import static net.osmand.GPXUtilities.writeGpxFile;
+import static net.osmand.plus.helpers.GpxUiHelper.getGpxTitle;
+import static net.osmand.plus.wikivoyage.data.TravelGpx.DIFF_ELE_DOWN;
+import static net.osmand.plus.wikivoyage.data.TravelGpx.DIFF_ELE_UP;
+import static net.osmand.plus.wikivoyage.data.TravelGpx.DISTANCE;
+import static net.osmand.plus.wikivoyage.data.TravelGpx.USER;
+import static net.osmand.util.Algorithms.capitalizeFirstLetter;
public class TravelObfHelper implements TravelHelper {
@@ -55,9 +67,13 @@ public class TravelObfHelper implements TravelHelper {
private static final String WORLD_WIKIVOYAGE_FILE_NAME = "World_wikivoyage.travel.obf";
public static final String ROUTE_ARTICLE = "route_article";
public static final String ROUTE_ARTICLE_POINT = "route_article_point";
+ public static final String ROUTE_TRACK = "route_track";
public static final int POPULAR_ARTICLES_SEARCH_RADIUS = 100000;
public static final int ARTICLE_SEARCH_RADIUS = 50000;
+ public static final int GPX_TRACKS_SEARCH_RADIUS = 10000;
public static final int MAX_POPULAR_ARTICLES_COUNT = 30;
+ public static final String REF_TAG = "ref";
+ public static final String NAME_TAG = "name";
private final OsmandApplication app;
private final Collator collator;
@@ -91,47 +107,69 @@ public class TravelObfHelper implements TravelHelper {
public synchronized List loadPopularArticles() {
String lang = app.getLanguage();
List popularArticles = new ArrayList<>();
- for (BinaryMapIndexReader reader : getReaders()) {
+ final List> amenities = new ArrayList<>();
+ final LatLon location = app.getMapViewTrackingUtilities().getMapLocation();
+ for (final BinaryMapIndexReader reader : getReaders()) {
try {
- final LatLon location = app.getMapViewTrackingUtilities().getMapLocation();
- SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(
- location, POPULAR_ARTICLES_SEARCH_RADIUS, -1, getSearchFilter(false), null);
- List amenities = reader.searchPoi(req);
- if (amenities.size() > 0) {
- Collections.sort(amenities, new Comparator() {
- @Override
- public int compare(Amenity a1, Amenity a2) {
- int d1 = (int) (MapUtils.getDistance(a1.getLocation().getLatitude(), a1.getLocation().getLongitude(),
- location.getLatitude(), location.getLongitude()));
- int d2 = (int) (MapUtils.getDistance(a2.getLocation().getLatitude(), a2.getLocation().getLongitude(),
- location.getLatitude(), location.getLongitude()));
- return d1 < d2 ? -1 : (d1 == d2 ? 0 : 1);
- }
- });
- for (Amenity amenity : amenities) {
- if (!Algorithms.isEmpty(amenity.getName(lang))) {
- TravelArticle article = cacheTravelArticles(reader.getFile(), amenity, lang, false, null);
- if (article != null) {
- popularArticles.add(article);
- if (popularArticles.size() >= MAX_POPULAR_ARTICLES_COUNT) {
- break;
- }
- }
+ searchAmenity(amenities, location, reader, POPULAR_ARTICLES_SEARCH_RADIUS, -1, ROUTE_ARTICLE);
+ searchAmenity(amenities, location, reader, GPX_TRACKS_SEARCH_RADIUS, 15, ROUTE_TRACK);
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
+
+ if (amenities.size() > 0) {
+ Collections.sort(amenities, new Comparator>() {
+ @Override
+ public int compare(Pair article1, Pair article2) {
+ int d1 = (int) (MapUtils.getDistance(((Amenity) article1.second).getLocation(), location));
+ int d2 = (int) (MapUtils.getDistance(((Amenity) article2.second).getLocation(), location));
+ return d1 < d2 ? -1 : (d1 == d2 ? 0 : 1);
+ }
+ });
+ for (Pair amenity : amenities) {
+ if (!Algorithms.isEmpty(amenity.second.getName(lang))) {
+ TravelArticle article = cacheTravelArticles(amenity.first, amenity.second, lang, false, null);
+ if (article != null) {
+ popularArticles.add(article);
+ if (popularArticles.size() >= MAX_POPULAR_ARTICLES_COUNT) {
+ break;
}
}
}
- } catch (Exception e) {
- LOG.error(e.getMessage(), e);
}
}
this.popularArticles = popularArticles;
return popularArticles;
}
+ private void searchAmenity(final List> amenitiesList, LatLon location,
+ final BinaryMapIndexReader reader, int searchRadius, int zoom,
+ String searchFilter) throws IOException {
+ reader.searchPoi(BinaryMapIndexReader.buildSearchPoiRequest(
+ location, searchRadius, zoom, getSearchFilter(searchFilter), new ResultMatcher() {
+ @Override
+ public boolean publish(Amenity object) {
+ amenitiesList.add(new Pair<>(reader.getFile(), object));
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ }));
+ }
+
@Nullable
private TravelArticle cacheTravelArticles(File file, Amenity amenity, String lang, boolean readPoints, @Nullable GpxReadCallback callback) {
TravelArticle article = null;
- Map articles = readArticles(file, amenity);
+ Map articles;
+ if (ROUTE_TRACK.equals(amenity.getSubType())) {
+ articles = readRoutePoint(file, amenity);
+ } else {
+ articles = readArticles(file, amenity);
+ }
if (!Algorithms.isEmpty(articles)) {
TravelArticleIdentifier newArticleId = articles.values().iterator().next().generateIdentifier();
cachedArticles.put(newArticleId, articles);
@@ -140,12 +178,41 @@ public class TravelObfHelper implements TravelHelper {
return article;
}
+ private Map readRoutePoint(File file, Amenity amenity) {
+ Map articles = new HashMap<>();
+ TravelGpx res = new TravelGpx();
+ res.file = file;
+ String title = amenity.getName("en");
+ res.title = createTitle(Algorithms.isEmpty(title) ? amenity.getName() : title);
+ res.lat = amenity.getLocation().getLatitude();
+ res.lon = amenity.getLocation().getLongitude();
+ res.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID));
+ try {
+ res.totalDistance = Float.parseFloat(Algorithms.emptyIfNull(amenity.getTagContent(DISTANCE)));
+ } catch (NumberFormatException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ try {
+ res.diffElevationUp = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(DIFF_ELE_UP)));
+ } catch (NumberFormatException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ try {
+ res.diffElevationDown = Double.parseDouble(Algorithms.emptyIfNull(amenity.getTagContent(DIFF_ELE_DOWN)));
+ } catch (NumberFormatException e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ res.user = Algorithms.emptyIfNull(amenity.getTagContent(USER));
+ articles.put("en", res);
+ return articles;
+ }
+
@NonNull
- private SearchPoiTypeFilter getSearchFilter(final boolean articlePoints) {
+ private SearchPoiTypeFilter getSearchFilter(final String filterSubcategory) {
return new SearchPoiTypeFilter() {
@Override
public boolean accept(PoiCategory type, String subcategory) {
- return subcategory.equals(articlePoints ? ROUTE_ARTICLE_POINT : ROUTE_ARTICLE);
+ return subcategory.equals(filterSubcategory);
}
@Override
@@ -176,9 +243,9 @@ public class TravelObfHelper implements TravelHelper {
res.isParentOf = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IS_PARENT_OF, lang));
res.lat = amenity.getLocation().getLatitude();
res.lon = amenity.getLocation().getLongitude();
- res.imageTitle = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, null));
- res.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null));
- res.routeSource = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null));
+ res.imageTitle = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE));
+ res.routeId = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID));
+ res.routeSource = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE));
res.originalId = 0;
res.lang = lang;
res.contentsJson = Algorithms.emptyIfNull(amenity.getTagContent(Amenity.CONTENT_JSON, lang));
@@ -186,6 +253,82 @@ public class TravelObfHelper implements TravelHelper {
return res;
}
+ @Nullable
+ private GPXFile buildTravelGpxFile(@NonNull final TravelGpx article) {
+ String routeId = article.getRouteId();
+ final String ref = routeId.substring(routeId.length() - 3);
+ final List segmentList = new ArrayList<>();
+
+ for (BinaryMapIndexReader reader : getReaders()) {
+ try {
+ if (article.file != null && !article.file.equals(reader.getFile())) {
+ continue;
+ }
+ BinaryMapIndexReader.SearchRequest sr = BinaryMapIndexReader.buildSearchRequest(
+ 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, 15, null,
+ new ResultMatcher() {
+ @Override
+ public boolean publish(BinaryMapDataObject object) {
+ if (object.getPointsLength() > 1) {
+ if (getTagValue(object, REF_TAG).equals(ref)
+ && createTitle(getTagValue(object, NAME_TAG)).equals(article.title)) {
+ segmentList.add(object);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ });
+ reader.searchMapIndex(sr);
+ if (!Algorithms.isEmpty(segmentList)) {
+ break;
+ }
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ }
+ }
+ GPXFile gpxFile = null;
+ if (!segmentList.isEmpty()) {
+ Track track = new Track();
+ for (BinaryMapDataObject segment : segmentList) {
+ TrkSegment trkSegment = new TrkSegment();
+ for (int i = 0; i < segment.getPointsLength(); i++) {
+ WptPt point = new WptPt();
+ point.lat = MapUtils.get31LatitudeY(segment.getPoint31YTile(i));
+ point.lon = MapUtils.get31LongitudeX(segment.getPoint31XTile(i));
+ trkSegment.points.add(point);
+ }
+ track.segments.add(trkSegment);
+ }
+ gpxFile = new GPXFile(article.getTitle(), article.getLang(), "");
+ gpxFile.tracks = new ArrayList<>();
+ gpxFile.tracks.add(track);
+ }
+ article.gpxFile = gpxFile;
+ return gpxFile;
+ }
+
+ private String getTagValue(BinaryMapDataObject object, String tag) {
+ BinaryMapIndexReader.MapIndex mi = object.getMapIndex();
+ TIntObjectIterator it = object.getObjectNames().iterator();
+ while (it.hasNext()) {
+ it.advance();
+ BinaryMapIndexReader.TagValuePair tp = mi.decodeType(it.key());
+ if (tp.tag.equals(tag)) {
+ return it.value();
+ }
+ }
+ return "";
+ }
+
+ private String createTitle(String name) {
+ return capitalizeFirstLetter(getGpxTitle(name));
+ }
+
@NonNull
private synchronized List getPointList(@NonNull final TravelArticle article) {
final List pointList = new ArrayList<>();
@@ -197,14 +340,14 @@ public class TravelObfHelper implements TravelHelper {
}
SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0,
Algorithms.emptyIfNull(article.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE,
- getSearchFilter(true), new ResultMatcher() {
+ getSearchFilter(ROUTE_ARTICLE_POINT), new ResultMatcher() {
@Override
public boolean publish(Amenity amenity) {
String amenityLang = amenity.getTagSuffix(Amenity.LANG_YES + ":");
if (Algorithms.stringsEqual(lang, amenityLang)
&& Algorithms.stringsEqual(article.routeId,
- Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null)))) {
+ Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID)))) {
pointList.add(amenity);
}
return false;
@@ -246,7 +389,7 @@ public class TravelObfHelper implements TravelHelper {
}
String category = amenity.getTagSuffix("category_");
if (category != null) {
- wptPt.category = Algorithms.capitalizeFirstLetter(category);
+ wptPt.category = capitalizeFirstLetter(category);
}
return wptPt;
}
@@ -266,7 +409,7 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest searchRequest = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, searchQuery,
- 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(false), new ResultMatcher() {
+ 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(ROUTE_ARTICLE), new ResultMatcher() {
@Override
public boolean publish(Amenity object) {
List otherNames = object.getAllNames(false);
@@ -441,7 +584,7 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(
- 0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(false),
+ 0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchFilter(ROUTE_ARTICLE),
new ResultMatcher() {
boolean done = false;
@@ -485,7 +628,7 @@ public class TravelObfHelper implements TravelHelper {
@Nullable
private TravelArticle getCachedArticle(@NonNull TravelArticleIdentifier articleId, @NonNull String lang,
- boolean forceReadPoints, @Nullable GpxReadCallback callback) {
+ boolean readGpx, @Nullable GpxReadCallback callback) {
TravelArticle article = null;
Map articles = cachedArticles.get(articleId);
if (articles != null) {
@@ -502,9 +645,9 @@ public class TravelObfHelper implements TravelHelper {
}
}
if (article == null && articles == null) {
- article = findArticleById(articleId, lang, callback);
+ article = findArticleById(articleId, lang, readGpx, callback);
}
- if (article != null && forceReadPoints && !Algorithms.isEmpty(lang)) {
+ if (article != null && readGpx && !Algorithms.isEmpty(lang)) {
readGpxFile(article, callback);
}
return article;
@@ -518,8 +661,8 @@ public class TravelObfHelper implements TravelHelper {
}
}
- private TravelArticle findArticleById(@NonNull final TravelArticleIdentifier articleId,
- final String lang, @Nullable GpxReadCallback callback) {
+ private synchronized TravelArticle findArticleById(@NonNull final TravelArticleIdentifier articleId,
+ final String lang, boolean readGpx, @Nullable GpxReadCallback callback) {
TravelArticle article = null;
final boolean isDbArticle = articleId.file != null && articleId.file.getName().endsWith(IndexConstants.BINARY_WIKIVOYAGE_MAP_INDEX_EXT);
final List amenities = new ArrayList<>();
@@ -530,12 +673,13 @@ public class TravelObfHelper implements TravelHelper {
}
SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0,
Algorithms.emptyIfNull(articleId.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE,
- getSearchFilter(false), new ResultMatcher() {
+ getSearchFilter(ROUTE_ARTICLE), new ResultMatcher() {
boolean done = false;
@Override
public boolean publish(Amenity amenity) {
- if (Algorithms.stringsEqual(articleId.routeId, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null))) || isDbArticle) {
+ if (Algorithms.stringsEqual(articleId.routeId,
+ Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID))) || isDbArticle) {
amenities.add(amenity);
done = true;
}
@@ -562,7 +706,7 @@ public class TravelObfHelper implements TravelHelper {
LOG.error(e.getMessage());
}
if (!Algorithms.isEmpty(amenities)) {
- article = cacheTravelArticles(reader.getFile(), amenities.get(0), lang, true, callback);
+ article = cacheTravelArticles(reader.getFile(), amenities.get(0), lang, readGpx, callback);
}
}
return article;
@@ -585,7 +729,7 @@ public class TravelObfHelper implements TravelHelper {
@Nullable
@Override
- public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect,
+ public synchronized TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect,
@NonNull final String lang, boolean readGpx, @Nullable GpxReadCallback callback) {
TravelArticle article = null;
final List amenities = new ArrayList<>();
@@ -606,7 +750,7 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) {
try {
SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(
- x, y, title, left, right, top, bottom, getSearchFilter(false),
+ x, y, title, left, right, top, bottom, getSearchFilter(ROUTE_ARTICLE),
new ResultMatcher() {
boolean done = false;
@@ -693,7 +837,12 @@ public class TravelObfHelper implements TravelHelper {
@NonNull
@Override
public File createGpxFile(@NonNull TravelArticle article) {
- final GPXFile gpx = article.getGpxFile();
+ final GPXFile gpx;
+ if (article instanceof TravelGpx) {
+ gpx = buildTravelGpxFile((TravelGpx) article);
+ } else {
+ gpx = article.getGpxFile();
+ }
File file = app.getAppPath(IndexConstants.GPX_TRAVEL_DIR + getGPXName(article));
writeGpxFile(file, gpx);
return file;
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreRvAdapter.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreRvAdapter.java
index a95614bdb8..5341edcb9d 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreRvAdapter.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreRvAdapter.java
@@ -20,6 +20,8 @@ import net.osmand.plus.wikivoyage.explore.travelcards.StartEditingTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.StartEditingTravelCard.StartEditingTravelVH;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelDownloadUpdateCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelDownloadUpdateCard.DownloadUpdateVH;
+import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard;
+import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard.TravelGpxVH;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelNeededMapsCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelNeededMapsCard.NeededMapsVH;
@@ -48,6 +50,9 @@ public class ExploreRvAdapter extends RecyclerView.Adapter 0; i--) {
BaseTravelCard o = items.get(i);
- if (o instanceof ArticleTravelCard) {
+ if (o instanceof ArticleTravelCard || o instanceof TravelGpxCard) {
return i;
}
}
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java
index f4e4408cba..c13c7fb22f 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/ExploreTabFragment.java
@@ -29,6 +29,7 @@ import net.osmand.plus.download.DownloadResources;
import net.osmand.plus.download.DownloadValidationManager;
import net.osmand.plus.download.IndexItem;
import net.osmand.plus.wikivoyage.data.TravelArticle;
+import net.osmand.plus.wikivoyage.data.TravelGpx;
import net.osmand.plus.wikivoyage.data.TravelHelper;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
import net.osmand.plus.wikivoyage.explore.travelcards.ArticleTravelCard;
@@ -37,6 +38,7 @@ import net.osmand.plus.wikivoyage.explore.travelcards.HeaderTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.OpenBetaTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.StartEditingTravelCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelDownloadUpdateCard;
+import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard;
import net.osmand.plus.wikivoyage.explore.travelcards.TravelNeededMapsCard;
import java.io.IOException;
@@ -173,12 +175,15 @@ public class ExploreTabFragment extends BaseOsmAndFragment implements DownloadEv
if (!Version.isPaidVersion(app) && !OpenBetaTravelCard.isClosed()) {
items.add(new OpenBetaTravelCard(activity, nightMode));
}
- if (app.getTravelHelper().isAnyTravelBookPresent()) {
+ List popularArticles = app.getTravelHelper().getPopularArticles();
+ if (!popularArticles.isEmpty()) {
items.add(new HeaderTravelCard(app, nightMode, getString(R.string.popular_destinations)));
-
- List popularArticles = app.getTravelHelper().getPopularArticles();
for (TravelArticle article : popularArticles) {
- items.add(new ArticleTravelCard(app, nightMode, article, activity.getSupportFragmentManager()));
+ if (article instanceof TravelGpx) {
+ items.add(new TravelGpxCard(app, nightMode, (TravelGpx) article, getActivity()));
+ } else {
+ items.add(new ArticleTravelCard(app, nightMode, article, activity.getSupportFragmentManager()));
+ }
}
}
items.add(new StartEditingTravelCard(activity, nightMode));
diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java
index 8996e4d375..a01de6e6cb 100644
--- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java
+++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesRvAdapter.java
@@ -7,6 +7,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
@@ -16,16 +18,20 @@ import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.RequestCreator;
+import net.osmand.AndroidUtils;
import net.osmand.PicassoUtils;
+import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
-import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
+import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.widgets.tools.CropCircleTransformation;
import net.osmand.plus.wikipedia.WikiArticleHelper;
import net.osmand.plus.wikivoyage.WikivoyageUtils;
import net.osmand.plus.wikivoyage.data.TravelArticle;
+import net.osmand.plus.wikivoyage.data.TravelGpx;
import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper;
+import net.osmand.plus.wikivoyage.explore.travelcards.TravelGpxCard;
import java.util.ArrayList;
import java.util.List;
@@ -33,7 +39,8 @@ import java.util.List;
public class SavedArticlesRvAdapter extends RecyclerView.Adapter {
private static final int HEADER_TYPE = 0;
- private static final int ITEM_TYPE = 1;
+ private static final int ARTICLE_TYPE = 1;
+ private static final int GPX_TYPE = 2;
private final OsmandApplication app;
private final OsmandSettings settings;
@@ -45,6 +52,7 @@ public class SavedArticlesRvAdapter extends RecyclerView.Adapter