From 38a0fb1f2766234b63bf0f986fca2758315c5f98 Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Mon, 28 Dec 2020 03:24:16 +0200 Subject: [PATCH 01/28] Remove unnecessary split from TrackActivityFragmentAdapter --- OsmAnd/res/layout/gpx_item_list_header.xml | 55 ---- .../res/menu/track_menu_bottom_navigation.xml | 21 ++ .../osmand/plus/activities/TrackActivity.java | 9 - .../SelectedGpxMenuController.java | 20 +- .../myplaces/SplitSegmentDialogFragment.java | 19 -- .../TrackActivityFragmentAdapter.java | 255 +----------------- .../plus/myplaces/TrackSegmentFragment.java | 11 - 7 files changed, 37 insertions(+), 353 deletions(-) create mode 100644 OsmAnd/res/menu/track_menu_bottom_navigation.xml diff --git a/OsmAnd/res/layout/gpx_item_list_header.xml b/OsmAnd/res/layout/gpx_item_list_header.xml index 715dd7789e..16545ca0b4 100644 --- a/OsmAnd/res/layout/gpx_item_list_header.xml +++ b/OsmAnd/res/layout/gpx_item_list_header.xml @@ -109,61 +109,6 @@ android:layout_marginEnd="@dimen/content_padding" android:layout_marginStart="@dimen/content_padding" /> - - - - - - - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/activities/TrackActivity.java b/OsmAnd/src/net/osmand/plus/activities/TrackActivity.java index 12e5edf12a..483b0c0e3f 100644 --- a/OsmAnd/src/net/osmand/plus/activities/TrackActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/TrackActivity.java @@ -266,15 +266,6 @@ public class TrackActivity extends TabActivity { return false; } - public void updateSplitView() { - for (WeakReference f : fragList) { - Fragment frag = f.get(); - if (frag instanceof TrackSegmentFragment) { - ((TrackSegmentFragment) frag).updateSplitView(); - } - } - } - public void updateHeader(Fragment sender) { for (WeakReference f : fragList) { Fragment frag = f.get(); diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/SelectedGpxMenuController.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/SelectedGpxMenuController.java index f2895d6c10..af83eff49a 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/SelectedGpxMenuController.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/controllers/SelectedGpxMenuController.java @@ -17,6 +17,7 @@ import net.osmand.plus.GpxSelectionHelper; import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; +import net.osmand.plus.Version; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.TrackActivity; import net.osmand.plus.helpers.GpxUiHelper; @@ -25,6 +26,7 @@ import net.osmand.plus.mapcontextmenu.builders.SelectedGpxMenuBuilder; import net.osmand.plus.myplaces.SaveCurrentTrackTask; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.track.SaveGpxAsyncTask.SaveGpxListener; +import net.osmand.plus.track.TrackMenuFragment; import net.osmand.util.Algorithms; import java.io.File; @@ -45,15 +47,21 @@ public class SelectedGpxMenuController extends MenuController { leftTitleButtonController = new TitleButtonController() { @Override public void buttonPressed() { - Intent intent = new Intent(mapActivity, mapActivity.getMyApplication().getAppCustomization().getTrackActivity()); + OsmandApplication app = mapActivity.getMyApplication(); SelectedGpxFile selectedGpxFile = selectedGpxPoint.getSelectedGpxFile(); - if (selectedGpxFile.isShowCurrentTrack()) { - intent.putExtra(TrackActivity.CURRENT_RECORDING, true); + if (Version.isDeveloperVersion(app)) { + mapActivity.getContextMenu().hide(false); + TrackMenuFragment.showInstance(mapActivity, selectedGpxFile.getGpxFile().path, selectedGpxFile.isShowCurrentTrack()); } else { - intent.putExtra(TrackActivity.TRACK_FILE_NAME, selectedGpxFile.getGpxFile().path); + Intent intent = new Intent(mapActivity, mapActivity.getMyApplication().getAppCustomization().getTrackActivity()); + if (selectedGpxFile.isShowCurrentTrack()) { + intent.putExtra(TrackActivity.CURRENT_RECORDING, true); + } else { + intent.putExtra(TrackActivity.TRACK_FILE_NAME, selectedGpxFile.getGpxFile().path); + } + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + mapActivity.startActivity(intent); } - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mapActivity.startActivity(intent); } }; leftTitleButtonController.caption = mapActivity.getString(R.string.shared_string_open_track); diff --git a/OsmAnd/src/net/osmand/plus/myplaces/SplitSegmentDialogFragment.java b/OsmAnd/src/net/osmand/plus/myplaces/SplitSegmentDialogFragment.java index 5115e7f63e..587df134d7 100644 --- a/OsmAnd/src/net/osmand/plus/myplaces/SplitSegmentDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/myplaces/SplitSegmentDialogFragment.java @@ -1,7 +1,6 @@ package net.osmand.plus.myplaces; import android.app.Dialog; -import android.content.DialogInterface; import android.content.res.ColorStateList; import android.graphics.Paint; import android.graphics.Rect; @@ -434,24 +433,6 @@ public class SplitSegmentDialogFragment extends DialogFragment { return false; } - @Override - public void dismiss() { - TrackActivity trackActivity = getTrackActivity(); - if (trackActivity != null) { - trackActivity.updateSplitView(); - } - super.dismiss(); - } - - @Override - public void onCancel(DialogInterface dialog) { - TrackActivity trackActivity = getTrackActivity(); - if (trackActivity != null) { - trackActivity.updateSplitView(); - } - super.onCancel(dialog); - } - private class SplitSegmentsAdapter extends ArrayAdapter { SplitSegmentsAdapter(List items) { diff --git a/OsmAnd/src/net/osmand/plus/myplaces/TrackActivityFragmentAdapter.java b/OsmAnd/src/net/osmand/plus/myplaces/TrackActivityFragmentAdapter.java index aa27b43c0c..9e8f69e38c 100644 --- a/OsmAnd/src/net/osmand/plus/myplaces/TrackActivityFragmentAdapter.java +++ b/OsmAnd/src/net/osmand/plus/myplaces/TrackActivityFragmentAdapter.java @@ -6,7 +6,6 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.os.AsyncTask; import android.os.Bundle; import android.text.Html; import android.text.TextUtils; @@ -14,9 +13,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.ListView; @@ -47,8 +44,6 @@ import net.osmand.plus.GPXDatabase.GpxDataItem; import net.osmand.plus.GpxSelectionHelper; import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType; -import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; -import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; @@ -57,9 +52,6 @@ import net.osmand.plus.dialogs.GpxAppearanceAdapter; import net.osmand.plus.myplaces.TrackBitmapDrawer.TrackBitmapDrawerListener; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.track.GpxSplitType; -import net.osmand.plus.track.SplitTrackAsyncTask; -import net.osmand.plus.track.SplitTrackAsyncTask.SplitTrackListener; import net.osmand.plus.widgets.tools.CropCircleTransformation; import net.osmand.plus.wikipedia.WikiArticleHelper; import net.osmand.plus.wikivoyage.WikivoyageUtils; @@ -71,8 +63,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import gnu.trove.list.array.TIntArrayList; - import static net.osmand.plus.dialogs.ConfigureMapMenu.CURRENT_TRACK_COLOR_ATTR; public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { @@ -82,10 +72,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { private ListView listView; private GpxDisplayItemType[] filterTypes; - private List options = new ArrayList<>(); - private List distanceSplit = new ArrayList<>(); - private TIntArrayList timeSplit = new TIntArrayList(); - private int selectedSplitInterval; private boolean updateEnable; private View headerView; private SwitchCompat vis; @@ -108,7 +94,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { private View lineTextLayout; private View overlayView; - ListPopupWindow splitListPopupWindow; ListPopupWindow colorListPopupWindow; TrackActivityFragmentAdapter(@NonNull OsmandApplication app, @@ -268,10 +253,8 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { showTemporaryObjectOnMap(getRect()); } }); - final View splitColorView = headerView.findViewById(R.id.split_color_view); final View appearanceView = headerView.findViewById(R.id.appearance_view); final View divider = headerView.findViewById(R.id.divider); - final View splitIntervalView = headerView.findViewById(R.id.split_interval_view); vis = (SwitchCompat) headerView.findViewById(R.id.showOnMapToggle); final View bottomDivider = headerView.findViewById(R.id.bottom_divider); GPXFile gpxFile = getGpx(); @@ -297,13 +280,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { @Override public void onClick(View v) { vis.toggle(); - if (!vis.isChecked()) { - selectedSplitInterval = 0; - } setTrackVisibilityOnMap(vis.isChecked()); - if (!showMapOnly) { - updateSplitIntervalView(splitIntervalView); - } TrackActivity trackActivity = getTrackActivity(); if (trackActivity != null) { trackActivity.updateHeader(fragment); @@ -312,45 +289,12 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { } }); - splitColorView.setVisibility(View.GONE); if (showMapOnly) { - splitIntervalView.setVisibility(View.GONE); appearanceView.setVisibility(View.GONE); divider.setVisibility(View.GONE); bottomDivider.setVisibility(View.VISIBLE); } else { - bottomDivider.setVisibility(View.GONE); - if (hasPath) { - if (!gpxFile.showCurrentTrack && listItemsCount > 0) { - prepareSplitIntervalAdapterData(); - setupSplitIntervalView(splitIntervalView); - updateSplitIntervalView(splitIntervalView); - splitIntervalView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - TrackActivity activity = getTrackActivity(); - if (activity != null) { - ListAdapter adapter = new ArrayAdapter<>(activity, R.layout.popup_list_text_item, options); - OnItemClickListener itemClickListener = new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - selectedSplitInterval = position; - setTrackVisibilityOnMap(vis.isChecked()); - splitListPopupWindow.dismiss(); - updateSplitIntervalView(splitIntervalView); - } - }; - splitListPopupWindow = createPopupWindow(activity, splitIntervalView, adapter, itemClickListener); - splitListPopupWindow.show(); - } - } - }); - splitIntervalView.setVisibility(View.VISIBLE); - } else { - splitIntervalView.setVisibility(View.GONE); - } appearanceView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -363,6 +307,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { appearanceView.setVisibility(View.GONE); divider.setVisibility(View.GONE); } + bottomDivider.setVisibility(View.GONE); } updateTrackColor(); } @@ -594,17 +539,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { private void setTrackVisibilityOnMap(boolean visible) { GPXFile gpxFile = getGpx(); if (gpxFile != null) { - GpxSelectionHelper gpxHelper = app.getSelectedGpxHelper(); - SelectedGpxFile sf = gpxHelper.selectGpxFile(gpxFile, visible, false); - if (gpxFile.hasTrkPt()) { - List groups = getDisplayGroups(); - if (groups.size() > 0) { - updateSplit(groups, visible ? sf : null); - if (getGpxDataItem() != null) { - updateSplitInDatabase(); - } - } - } + app.getSelectedGpxHelper().selectGpxFile(gpxFile, visible, false); } } @@ -665,52 +600,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { return groups; } - private void setupSplitIntervalView(View view) { - final TextView title = (TextView) view.findViewById(R.id.split_interval_title); - final TextView text = (TextView) view.findViewById(R.id.split_interval_text); - final ImageView img = (ImageView) view.findViewById(R.id.split_interval_arrow); - int colorId; - final List groups = getDisplayGroups(); - if (groups.size() > 0) { - colorId = app.getSettings().isLightContent() ? - R.color.text_color_primary_light : R.color.text_color_primary_dark; - } else { - colorId = app.getSettings().isLightContent() ? - R.color.text_color_secondary_light : R.color.text_color_secondary_dark; - } - int color = app.getResources().getColor(colorId); - title.setTextColor(color); - text.setTextColor(color); - img.setImageDrawable(app.getUIUtilities().getIcon(R.drawable.ic_action_arrow_drop_down, colorId)); - } - - private void updateSplitIntervalView(View view) { - final TextView text = (TextView) view.findViewById(R.id.split_interval_text); - selectedSplitInterval = getSelectedSplitInterval(); - if (selectedSplitInterval == 0) { - text.setText(app.getString(R.string.shared_string_none)); - } else { - text.setText(options.get(selectedSplitInterval)); - } - } - - private int getSelectedSplitInterval() { - if (getGpxDataItem() == null) { - return 0; - } - int splitType = getGpxDataItem().getSplitType(); - double splitInterval = getGpxDataItem().getSplitInterval(); - int position = 0; - - if (splitType == GpxSplitType.DISTANCE.getType()) { - position = distanceSplit.indexOf(splitInterval); - } else if (splitType == GpxSplitType.TIME.getType()) { - position = timeSplit.indexOf((int) splitInterval); - } - - return Math.max(position, 0); - } - private void updateTrackColor() { int color = getGpxDataItem() != null ? getGpxDataItem().getColor() : 0; GPXFile gpxFile = getGpx(); @@ -740,146 +629,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { return list; } - private void prepareSplitIntervalAdapterData() { - final List groups = getDisplayGroups(); - - options.add(app.getString(R.string.shared_string_none)); - distanceSplit.add(-1d); - timeSplit.add(-1); - addOptionSplit(30, true, groups); // 50 feet, 20 yards, 20 - // m - addOptionSplit(60, true, groups); // 100 feet, 50 yards, - // 50 m - addOptionSplit(150, true, groups); // 200 feet, 100 yards, - // 100 m - addOptionSplit(300, true, groups); // 500 feet, 200 yards, - // 200 m - addOptionSplit(600, true, groups); // 1000 feet, 500 yards, - // 500 m - addOptionSplit(1500, true, groups); // 2000 feet, 1000 yards, 1 km - addOptionSplit(3000, true, groups); // 1 mi, 2 km - addOptionSplit(6000, true, groups); // 2 mi, 5 km - addOptionSplit(15000, true, groups); // 5 mi, 10 km - - addOptionSplit(15, false, groups); - addOptionSplit(30, false, groups); - addOptionSplit(60, false, groups); - addOptionSplit(120, false, groups); - addOptionSplit(150, false, groups); - addOptionSplit(300, false, groups); - addOptionSplit(600, false, groups); - addOptionSplit(900, false, groups); - addOptionSplit(1800, false, groups); - addOptionSplit(3600, false, groups); - } - - private void updateSplit(@NonNull List groups, @Nullable final SelectedGpxFile selectedGpx) { - GPXFile gpxFile = getGpx(); - TrackActivity activity = getTrackActivity(); - GpxSplitType gpxSplitType = getGpxSplitType(); - if (activity != null && gpxSplitType != null && gpxFile != null) { - int timeSplit = 0; - double distanceSplit = 0; - if (gpxSplitType != GpxSplitType.NO_SPLIT && !gpxFile.showCurrentTrack) { - timeSplit = this.timeSplit.get(selectedSplitInterval); - distanceSplit = this.distanceSplit.get(selectedSplitInterval); - } - SplitTrackListener splitTrackListener = new SplitTrackListener() { - - @Override - public void trackSplittingStarted() { - TrackActivity activity = getTrackActivity(); - if (activity != null) { - activity.setSupportProgressBarIndeterminateVisibility(true); - } - } - - @Override - public void trackSplittingFinished() { - TrackActivity activity = getTrackActivity(); - if (activity != null) { - if (AndroidUtils.isActivityNotDestroyed(activity)) { - activity.setSupportProgressBarIndeterminateVisibility(false); - } - if (selectedGpx != null) { - List groups = getDisplayGroups(); - selectedGpx.setDisplayGroups(groups, app); - } - } - } - }; - new SplitTrackAsyncTask(app, gpxSplitType, groups, splitTrackListener, activity.isJoinSegments(), - timeSplit, distanceSplit).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - private GpxSplitType getGpxSplitType() { - if (selectedSplitInterval == 0) { - return GpxSplitType.NO_SPLIT; - } else if (distanceSplit.get(selectedSplitInterval) > 0) { - return GpxSplitType.DISTANCE; - } else if (timeSplit.get(selectedSplitInterval) > 0) { - return GpxSplitType.TIME; - } - return null; - } - - private void addOptionSplit(int value, boolean distance, @NonNull List model) { - if (model.size() > 0) { - if (distance) { - double dvalue = OsmAndFormatter.calculateRoundedDist(value, app); - options.add(OsmAndFormatter.getFormattedDistanceInterval(app, value)); - distanceSplit.add(dvalue); - timeSplit.add(-1); - if (Math.abs(model.get(0).getSplitDistance() - dvalue) < 1) { - selectedSplitInterval = distanceSplit.size() - 1; - } - } else { - options.add(OsmAndFormatter.getFormattedTimeInterval(app, value)); - distanceSplit.add(-1d); - timeSplit.add(value); - if (model.get(0).getSplitTime() == value) { - selectedSplitInterval = distanceSplit.size() - 1; - } - } - } - } - - private void updateSplitInDatabase() { - double splitInterval = 0; - GpxSplitType splitType = null; - if (selectedSplitInterval == 0) { - splitType = GpxSplitType.NO_SPLIT; - splitInterval = 0; - } else if (distanceSplit.get(selectedSplitInterval) > 0) { - splitType = GpxSplitType.DISTANCE; - splitInterval = distanceSplit.get(selectedSplitInterval); - } else if (timeSplit.get(selectedSplitInterval) > 0) { - splitType = GpxSplitType.TIME; - splitInterval = timeSplit.get(selectedSplitInterval); - } - GpxDataItem item = getGpxDataItem(); - if (item != null && splitType != null) { - app.getGpxDbHelper().updateSplit(item, splitType, splitInterval); - } - } - - public void updateSplitView() { - GPXFile gpxFile = getGpx(); - if (gpxFile != null) { - SelectedGpxFile sf = app.getSelectedGpxHelper().selectGpxFile(gpxFile, - ((SwitchCompat) headerView.findViewById(R.id.showOnMapToggle)).isChecked(), false); - final List groups = getDisplayGroups(); - if (groups.size() > 0) { - updateSplit(groups, ((SwitchCompat) headerView.findViewById(R.id.showOnMapToggle)).isChecked() ? sf : null); - if (getGpxDataItem() != null) { - updateSplitInDatabase(); - } - } - updateSplitIntervalView(headerView.findViewById(R.id.split_interval_view)); - } - } - public void hideTransparentOverlay() { overlayView.setVisibility(View.GONE); } diff --git a/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java b/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java index e686c08f5b..0d9d960cd7 100644 --- a/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java +++ b/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java @@ -219,9 +219,6 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit altitudePopupMenu.dismiss(); } if (fragmentAdapter != null) { - if (fragmentAdapter.splitListPopupWindow != null) { - fragmentAdapter.splitListPopupWindow.dismiss(); - } if (fragmentAdapter.colorListPopupWindow != null) { fragmentAdapter.colorListPopupWindow.dismiss(); } @@ -269,12 +266,6 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit } } - public void updateSplitView() { - if (fragmentAdapter != null) { - fragmentAdapter.updateSplitView(); - } - } - @Nullable private List flatten(List groups) { return fragmentAdapter != null ? fragmentAdapter.flatten(groups) : null; @@ -807,7 +798,6 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit public void onClick(View v) { TrackActivity activity = getTrackActivity(); if (activity != null && activity.setJoinSegments(!activity.isJoinSegments())) { - updateSplitView(); for (int i = 0; i < getCount(); i++) { View view = getViewAtPosition(i); updateJoinGapsInfo(view, i); @@ -904,7 +894,6 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit public void onClick(View v) { TrackActivity activity = getTrackActivity(); if (activity != null && activity.setJoinSegments(!activity.isJoinSegments())) { - updateSplitView(); for (int i = 0; i < getCount(); i++) { View view = getViewAtPosition(i); updateJoinGapsInfo(view, i); From 9ef0d4ae7ea954f0958b67290741823d6156784d Mon Sep 17 00:00:00 2001 From: Vitaliy Date: Tue, 29 Dec 2020 17:23:13 +0200 Subject: [PATCH 02/28] Add TrackMenuFragment and refactor GPXItemPagerAdapter --- OsmAnd/res/layout/track_menu.xml | 127 ++ .../res/layout/track_segments_container.xml | 6 + .../res/menu/track_menu_bottom_navigation.xml | 16 +- .../osmand/plus/activities/TrackActivity.java | 104 +- .../osmand/plus/base/ContextMenuFragment.java | 4 +- .../SaveGpxRouteAsyncTask.java | 5 + .../plus/myplaces/DeletePointsTask.java | 88 ++ .../plus/myplaces/GPXItemPagerAdapter.java | 695 ++++++++++ .../osmand/plus/myplaces/GPXTabItemType.java | 32 + .../plus/myplaces/SegmentActionsListener.java | 27 + .../plus/myplaces/SegmentGPXAdapter.java | 91 ++ .../myplaces/SplitSegmentDialogFragment.java | 70 +- .../TrackActivityFragmentAdapter.java | 73 +- .../plus/myplaces/TrackPointFragment.java | 161 +-- .../plus/myplaces/TrackSegmentFragment.java | 1139 +++-------------- .../net/osmand/plus/track/SegmentsCard.java | 57 + .../osmand/plus/track/TrackDisplayHelper.java | 179 +++ .../osmand/plus/track/TrackMenuFragment.java | 596 +++++++++ 18 files changed, 2188 insertions(+), 1282 deletions(-) create mode 100644 OsmAnd/res/layout/track_menu.xml create mode 100644 OsmAnd/res/layout/track_segments_container.xml create mode 100644 OsmAnd/src/net/osmand/plus/myplaces/DeletePointsTask.java create mode 100644 OsmAnd/src/net/osmand/plus/myplaces/GPXItemPagerAdapter.java create mode 100644 OsmAnd/src/net/osmand/plus/myplaces/GPXTabItemType.java create mode 100644 OsmAnd/src/net/osmand/plus/myplaces/SegmentActionsListener.java create mode 100644 OsmAnd/src/net/osmand/plus/myplaces/SegmentGPXAdapter.java create mode 100644 OsmAnd/src/net/osmand/plus/track/SegmentsCard.java create mode 100644 OsmAnd/src/net/osmand/plus/track/TrackDisplayHelper.java create mode 100644 OsmAnd/src/net/osmand/plus/track/TrackMenuFragment.java diff --git a/OsmAnd/res/layout/track_menu.xml b/OsmAnd/res/layout/track_menu.xml new file mode 100644 index 0000000000..598372776f --- /dev/null +++ b/OsmAnd/res/layout/track_menu.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/track_segments_container.xml b/OsmAnd/res/layout/track_segments_container.xml new file mode 100644 index 0000000000..12b9a6bdff --- /dev/null +++ b/OsmAnd/res/layout/track_segments_container.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/OsmAnd/res/menu/track_menu_bottom_navigation.xml b/OsmAnd/res/menu/track_menu_bottom_navigation.xml index 9012899f57..609c250c1a 100644 --- a/OsmAnd/res/menu/track_menu_bottom_navigation.xml +++ b/OsmAnd/res/menu/track_menu_bottom_navigation.xml @@ -1,9 +1,9 @@ - + + + + - + + + + \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/activities/TrackActivity.java b/OsmAnd/src/net/osmand/plus/activities/TrackActivity.java index 483b0c0e3f..839823c369 100644 --- a/OsmAnd/src/net/osmand/plus/activities/TrackActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/TrackActivity.java @@ -43,6 +43,7 @@ import net.osmand.plus.myplaces.TrackPointFragment; import net.osmand.plus.myplaces.TrackSegmentFragment; import net.osmand.plus.settings.backend.OsmAndAppCustomization; import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.track.TrackDisplayHelper; import net.osmand.plus.views.AddGpxPointBottomSheetHelper.NewGpxPoint; import java.io.File; @@ -57,18 +58,14 @@ public class TrackActivity extends TabActivity { public static final String OPEN_TRACKS_LIST = "OPEN_TRACKS_LIST"; public static final String CURRENT_RECORDING = "CURRENT_RECORDING"; public static final String SHOW_TEMPORARILY = "SHOW_TEMPORARILY"; - protected List> fragList = new ArrayList<>(); + private OsmandApplication app; + private TrackDisplayHelper displayHelper; private TrackBitmapDrawer trackBitmapDrawer; - private File file = null; - private GPXFile gpxFile; - private GpxDataItem gpxDataItem; private LockableViewPager viewPager; - private long modifiedTime = -1; + private final List> fragList = new ArrayList<>(); - private List displayGroups; - private List originalGroups = new ArrayList<>(); private boolean stopped = false; private boolean openPointsTab = false; private boolean openTracksList = false; @@ -85,14 +82,15 @@ public class TrackActivity extends TabActivity { finish(); return; } + displayHelper = new TrackDisplayHelper(app); if (intent.hasExtra(TRACK_FILE_NAME)) { - file = new File(intent.getStringExtra(TRACK_FILE_NAME)); + displayHelper.setFile(new File(intent.getStringExtra(TRACK_FILE_NAME))); } ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { - if (file != null) { - String fn = file.getName().replace(IndexConstants.GPX_FILE_EXT, "").replace("/", " ").replace("_", " "); + if (getFile() != null) { + String fn = getFile().getName().replace(IndexConstants.GPX_FILE_EXT, "").replace("/", " ").replace("_", " "); actionBar.setTitle(fn); } else { actionBar.setTitle(getString(R.string.shared_string_currently_recording_track)); @@ -109,11 +107,19 @@ public class TrackActivity extends TabActivity { setContentView(R.layout.track_content); } + public TrackDisplayHelper getDisplayHelper() { + return displayHelper; + } + @Nullable public TrackBitmapDrawer getTrackBitmapDrawer() { return trackBitmapDrawer; } + public File getFile() { + return displayHelper.getFile(); + } + public void addPoint(PointDescription pointDescription) { Intent currentIntent = getIntent(); if (currentIntent != null) { @@ -122,7 +128,7 @@ public class TrackActivity extends TabActivity { final OsmandSettings settings = app.getSettings(); GPXFile gpx = getGpx(); LatLon location = settings.getLastKnownMapLocation(); - QuadRect rect = getRect(); + QuadRect rect = displayHelper.getRect(); NewGpxPoint newGpxPoint = new NewGpxPoint(gpx, pointDescription, rect); if (gpx != null && location != null) { settings.setMapLocationToShow(location.getLatitude(), location.getLongitude(), @@ -152,53 +158,20 @@ public class TrackActivity extends TabActivity { } } - public QuadRect getRect() { - if (getGpx() != null) { - return getGpx().getRect(); - } else { - return new QuadRect(0, 0, 0, 0); - } - } - protected void setGpxDataItem(GpxDataItem gpxDataItem) { - this.gpxDataItem = gpxDataItem; + displayHelper.setGpxDataItem(gpxDataItem); } protected void setGpx(GPXFile result) { - this.gpxFile = result; - if (file == null) { - this.gpxFile = getMyApplication().getSavingTrackHelper().getCurrentGpx(); - } + displayHelper.setGpx(result); } public List getGpxFile(boolean useDisplayGroups) { - if (gpxFile == null) { - return new ArrayList<>(); - } - if (gpxFile.modifiedTime != modifiedTime) { - modifiedTime = gpxFile.modifiedTime; - GpxSelectionHelper selectedGpxHelper = ((OsmandApplication) getApplication()).getSelectedGpxHelper(); - displayGroups = selectedGpxHelper.collectDisplayGroups(gpxFile); - originalGroups.clear(); - for (GpxDisplayGroup g : displayGroups) { - originalGroups.add(g.cloneInstance()); - } - if (file != null) { - SelectedGpxFile sf = selectedGpxHelper.getSelectedFileByPath(gpxFile.path); - if (sf != null && file != null && sf.getDisplayGroups(app) != null) { - displayGroups = sf.getDisplayGroups(app); - } - } - } - if (useDisplayGroups) { - return displayGroups; - } else { - return originalGroups; - } + return displayHelper.getGpxFile(useDisplayGroups); } @Override - public void onAttachFragment(Fragment fragment) { + public void onAttachFragment(@NonNull Fragment fragment) { fragList.add(new WeakReference<>(fragment)); if (trackBitmapDrawer != null && fragment instanceof TrackBitmapDrawerListener) { trackBitmapDrawer.addListener((TrackBitmapDrawerListener) fragment); @@ -218,7 +191,7 @@ public class TrackActivity extends TabActivity { } @Override - protected void onSaveInstanceState(Bundle outState) { + protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); if (viewPager.getCurrentItem() == 1) { outState.putBoolean(OPEN_POINTS_TAB, true); @@ -314,23 +287,23 @@ public class TrackActivity extends TabActivity { @Nullable public GPXFile getGpx() { - return gpxFile; + return displayHelper.getGpx(); } @Nullable public GpxDataItem getGpxDataItem() { - return gpxDataItem; + return displayHelper.getGpxDataItem(); } - private void onGPXFileReady(@Nullable GPXFile gpxFile) { + public void onGPXFileReady(@Nullable GPXFile gpxFile) { setGpx(gpxFile); - setGpxDataItem(file != null ? app.getGpxDbHelper().getItem(file) : null); + setGpxDataItem(getFile() != null ? app.getGpxDbHelper().getItem(getFile()) : null); WindowManager mgr = (WindowManager) getSystemService(Context.WINDOW_SERVICE); if (gpxFile != null && mgr != null) { DisplayMetrics dm = new DisplayMetrics(); mgr.getDefaultDisplay().getMetrics(dm); - trackBitmapDrawer = new TrackBitmapDrawer(app, gpxFile, getGpxDataItem(), getRect(), dm.density, dm.widthPixels, AndroidUtils.dpToPx(app, 152f)); + trackBitmapDrawer = new TrackBitmapDrawer(app, gpxFile, getGpxDataItem(), displayHelper.getRect(), dm.density, dm.widthPixels, AndroidUtils.dpToPx(app, 152f)); } for (WeakReference f : fragList) { @@ -397,23 +370,6 @@ public class TrackActivity extends TabActivity { } } - public boolean setJoinSegments(boolean joinSegments) { - if (gpxDataItem != null) { - boolean updated = app.getGpxDbHelper().updateJoinSegments(gpxDataItem, joinSegments); - - SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(gpxFile.path); - if (updated && selectedGpxFile != null) { - selectedGpxFile.setJoinSegments(joinSegments); - } - return updated; - } - return false; - } - - public boolean isJoinSegments() { - return gpxDataItem != null && gpxDataItem.isJoinSegments(); - } - private static class GPXFileLoaderTask extends AsyncTask { private OsmandApplication app; @@ -428,7 +384,7 @@ public class TrackActivity extends TabActivity { GPXFileLoaderTask(@NonNull TrackActivity activity) { this.activityRef = new WeakReference<>(activity); app = activity.getMyApplication(); - file = activity.file; + file = activity.getDisplayHelper().getFile(); } protected void onPreExecute() { @@ -477,11 +433,11 @@ public class TrackActivity extends TabActivity { if (activity != null) { activity.setSupportProgressBarIndeterminateVisibility(false); if (result != null) { - final GpxSelectionHelper helper = app.getSelectedGpxHelper(); + GpxSelectionHelper helper = app.getSelectedGpxHelper(); if (showTemporarily) { helper.selectGpxFile(result, false, false); } else { - final SelectedGpxFile selectedGpx = helper.getSelectedFileByPath(result.path); + SelectedGpxFile selectedGpx = helper.getSelectedFileByPath(result.path); if (selectedGpx != null && result.error == null) { selectedGpx.setGpxFile(result, app); } diff --git a/OsmAnd/src/net/osmand/plus/base/ContextMenuFragment.java b/OsmAnd/src/net/osmand/plus/base/ContextMenuFragment.java index 06ea4dbef9..4f6097897f 100644 --- a/OsmAnd/src/net/osmand/plus/base/ContextMenuFragment.java +++ b/OsmAnd/src/net/osmand/plus/base/ContextMenuFragment.java @@ -70,7 +70,7 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment private OnLayoutChangeListener containerLayoutListener; private View topShadow; private ViewGroup topView; - private View bottomScrollView; + private ViewGroup bottomScrollView; private LinearLayout cardsContainer; private FrameLayout bottomContainer; @@ -259,7 +259,7 @@ public abstract class ContextMenuFragment extends BaseOsmAndFragment return bottomContainer; } - public View getBottomScrollView() { + public ViewGroup getBottomScrollView() { return bottomScrollView; } diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/SaveGpxRouteAsyncTask.java b/OsmAnd/src/net/osmand/plus/measurementtool/SaveGpxRouteAsyncTask.java index a73d8667cc..ea85fe9fdd 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/SaveGpxRouteAsyncTask.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/SaveGpxRouteAsyncTask.java @@ -14,6 +14,7 @@ import net.osmand.GPXUtilities.Route; import net.osmand.GPXUtilities.Track; import net.osmand.GPXUtilities.TrkSegment; import net.osmand.GPXUtilities.WptPt; +import net.osmand.GPXUtilities.Metadata; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.Version; @@ -91,6 +92,10 @@ class SaveGpxRouteAsyncTask extends AsyncTask { backupFile = FileUtils.backupFile(app, outFile); String trackName = Algorithms.getFileNameWithoutExtension(outFile); GPXFile gpx = generateGpxFile(measurementLayer, editingCtx, trackName, gpxFile); + if (gpxFile.metadata != null) { + gpx.metadata = new Metadata(); + gpx.metadata.getExtensionsToWrite().putAll(gpxFile.metadata.getExtensionsToRead()); + } if (!gpx.showCurrentTrack) { res = GPXUtilities.writeGpxFile(outFile, gpx); } diff --git a/OsmAnd/src/net/osmand/plus/myplaces/DeletePointsTask.java b/OsmAnd/src/net/osmand/plus/myplaces/DeletePointsTask.java new file mode 100644 index 0000000000..e1d3dffea4 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/myplaces/DeletePointsTask.java @@ -0,0 +1,88 @@ +package net.osmand.plus.myplaces; + +import android.os.AsyncTask; + +import net.osmand.GPXUtilities; +import net.osmand.GPXUtilities.GPXFile; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.activities.SavingTrackHelper; +import net.osmand.plus.mapmarkers.MapMarkersGroup; +import net.osmand.plus.mapmarkers.MapMarkersHelper; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.Set; + +class DeletePointsTask extends AsyncTask { + + private OsmandApplication app; + private GPXFile gpx; + private Set selectedItems; + private WeakReference listenerRef; + + DeletePointsTask(OsmandApplication app, GPXFile gpxFile, Set selectedItems, OnPointsDeleteListener listener) { + this.app = app; + this.gpx = gpxFile; + this.selectedItems = selectedItems; + this.listenerRef = new WeakReference<>(listener); + } + + @Override + protected void onPreExecute() { + OnPointsDeleteListener listener = listenerRef.get(); + if (listener != null) { + listener.onPointsDeletionStarted(); + } + } + + @Override + protected Void doInBackground(Void... params) { + SavingTrackHelper savingTrackHelper = app.getSavingTrackHelper(); + if (gpx != null) { + for (GpxDisplayItem item : selectedItems) { + if (gpx.showCurrentTrack) { + savingTrackHelper.deletePointData(item.locationStart); + } else { + if (item.group.getType() == GpxDisplayItemType.TRACK_POINTS) { + gpx.deleteWptPt(item.locationStart); + } else if (item.group.getType() == GpxDisplayItemType.TRACK_ROUTE_POINTS) { + gpx.deleteRtePt(item.locationStart); + } + } + } + if (!gpx.showCurrentTrack) { + GPXUtilities.writeGpxFile(new File(gpx.path), gpx); + boolean selected = app.getSelectedGpxHelper().getSelectedFileByPath(gpx.path) != null; + if (selected) { + app.getSelectedGpxHelper().setGpxFileToDisplay(gpx); + } + } + syncGpx(gpx); + } + return null; + } + + private void syncGpx(GPXFile gpxFile) { + MapMarkersHelper helper = app.getMapMarkersHelper(); + MapMarkersGroup group = helper.getMarkersGroup(gpxFile); + if (group != null) { + helper.runSynchronization(group); + } + } + + @Override + protected void onPostExecute(Void aVoid) { + OnPointsDeleteListener listener = listenerRef.get(); + if (listener != null) { + listener.onPointsDeleted(); + } + } + + public interface OnPointsDeleteListener { + void onPointsDeletionStarted(); + + void onPointsDeleted(); + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/myplaces/GPXItemPagerAdapter.java b/OsmAnd/src/net/osmand/plus/myplaces/GPXItemPagerAdapter.java new file mode 100644 index 0000000000..465f85b617 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/myplaces/GPXItemPagerAdapter.java @@ -0,0 +1,695 @@ +package net.osmand.plus.myplaces; + +import android.annotation.SuppressLint; +import android.graphics.Matrix; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.SwitchCompat; +import androidx.viewpager.widget.PagerAdapter; + +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.highlight.Highlight; +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; +import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture; +import com.github.mikephil.charting.listener.OnChartGestureListener; +import com.github.mikephil.charting.listener.OnChartValueSelectedListener; + +import net.osmand.GPXUtilities; +import net.osmand.GPXUtilities.GPXFile; +import net.osmand.GPXUtilities.GPXTrackAnalysis; +import net.osmand.GPXUtilities.Track; +import net.osmand.GPXUtilities.TrkSegment; +import net.osmand.GPXUtilities.WptPt; +import net.osmand.plus.GPXDatabase.GpxDataItem; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; +import net.osmand.plus.OsmAndFormatter; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.helpers.GpxUiHelper; +import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType; +import net.osmand.plus.helpers.GpxUiHelper.LineGraphType; +import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet; +import net.osmand.plus.track.TrackDisplayHelper; +import net.osmand.plus.views.controls.PagerSlidingTabStrip; +import net.osmand.plus.views.controls.PagerSlidingTabStrip.CustomTabProvider; +import net.osmand.plus.views.controls.WrapContentHeightViewPager.ViewAtPositionInterface; +import net.osmand.util.Algorithms; +import net.osmand.util.MapUtils; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.ALTITUDE; +import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SLOPE; +import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SPEED; + +public class GPXItemPagerAdapter extends PagerAdapter implements CustomTabProvider, ViewAtPositionInterface { + + private OsmandApplication app; + private UiUtilities iconsCache; + private TrackDisplayHelper displayHelper; + private Map> dataSetsMap = new HashMap<>(); + + private WptPt selectedWpt; + private TrkSegment segment; + private GpxDisplayItem gpxItem; + private GPXTabItemType[] tabTypes; + + private PagerSlidingTabStrip tabs; + private SparseArray views = new SparseArray<>(); + private SegmentActionsListener actionsListener; + + private boolean chartClicked; + + + public GPXItemPagerAdapter(@NonNull PagerSlidingTabStrip tabs, + @NonNull GpxDisplayItem gpxItem, + @NonNull TrackDisplayHelper displayHelper, + @NonNull SegmentActionsListener actionsListener) { + super(); + this.tabs = tabs; + this.gpxItem = gpxItem; + this.displayHelper = displayHelper; + this.actionsListener = actionsListener; + app = (OsmandApplication) tabs.getContext().getApplicationContext(); + iconsCache = app.getUIUtilities(); + fetchTabTypes(); + } + + private void fetchTabTypes() { + List tabTypeList = new ArrayList<>(); + tabTypeList.add(GPXTabItemType.GPX_TAB_ITEM_GENERAL); + if (gpxItem != null && gpxItem.analysis != null) { + if (gpxItem.analysis.hasElevationData) { + tabTypeList.add(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE); + } + if (gpxItem.analysis.isSpeedSpecified()) { + tabTypeList.add(GPXTabItemType.GPX_TAB_ITEM_SPEED); + } + } + tabTypes = tabTypeList.toArray(new GPXTabItemType[0]); + } + + private List getDataSets(LineChart chart, GPXTabItemType tabType, + LineGraphType firstType, LineGraphType secondType) { + List dataSets = dataSetsMap.get(tabType); + if (dataSets == null && chart != null) { + GPXTrackAnalysis analysis = gpxItem.analysis; + GpxDataItem gpxDataItem = displayHelper.getGpxDataItem(); + boolean calcWithoutGaps = gpxItem.isGeneralTrack() && gpxDataItem != null && !gpxDataItem.isJoinSegments(); + dataSets = GpxUiHelper.getDataSets(chart, app, analysis, firstType, secondType, calcWithoutGaps); + dataSetsMap.put(tabType, dataSets); + } + return dataSets; + } + + private TrkSegment getTrackSegment(LineChart chart) { + if (segment == null) { + LineData lineData = chart.getLineData(); + List ds = lineData != null ? lineData.getDataSets() : null; + if (ds != null && ds.size() > 0) { + for (GPXUtilities.Track t : gpxItem.group.getGpx().tracks) { + for (TrkSegment s : t.segments) { + if (s.points.size() > 0 && s.points.get(0).equals(gpxItem.analysis.locationStart)) { + segment = s; + break; + } + } + if (segment != null) { + break; + } + } + } + } + return segment; + } + + private WptPt getPoint(LineChart chart, float pos) { + WptPt wpt = null; + LineData lineData = chart.getLineData(); + List ds = lineData != null ? lineData.getDataSets() : null; + if (ds != null && ds.size() > 0) { + TrkSegment segment = getTrackSegment(chart); + OrderedLineDataSet dataSet = (OrderedLineDataSet) ds.get(0); + if (gpxItem.chartAxisType == GPXDataSetAxisType.TIME) { + float time = pos * 1000; + for (WptPt p : segment.points) { + if (p.time - gpxItem.analysis.startTime >= time) { + wpt = p; + break; + } + } + } else { + float distance = pos * dataSet.getDivX(); + double totalDistance = 0; + for (int i = 0; i < segment.points.size(); i++) { + WptPt currentPoint = segment.points.get(i); + if (i != 0) { + WptPt previousPoint = segment.points.get(i - 1); + totalDistance += MapUtils.getDistance(previousPoint.lat, previousPoint.lon, currentPoint.lat, currentPoint.lon); + } + if (currentPoint.distance >= distance || Math.abs(totalDistance - distance) < 0.1) { + wpt = currentPoint; + break; + } + } + } + } + return wpt; + } + + @Override + public int getCount() { + return tabTypes.length; + } + + @Override + public CharSequence getPageTitle(int position) { + return tabTypes[position].toHumanString(app); + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + GPXTabItemType tabType = tabTypes[position]; + View view = getViewForTab(container, tabType); + GPXFile gpxFile = displayHelper.getGpx(); + if (gpxFile != null && gpxItem != null) { + GPXTrackAnalysis analysis = gpxItem.analysis; + LineChart chart = view.findViewById(R.id.chart); + setupChart(view, chart); + + switch (tabType) { + case GPX_TAB_ITEM_GENERAL: + setupGeneralTab(view, chart, analysis, gpxFile, position); + break; + case GPX_TAB_ITEM_ALTITUDE: + setupAltitudeTab(view, chart, analysis, gpxFile, position); + break; + case GPX_TAB_ITEM_SPEED: + setupSpeedTab(view, chart, analysis, gpxFile, position); + break; + } + } + container.addView(view, 0); + views.put(position, view); + return view; + } + + private View getViewForTab(@NonNull ViewGroup container, @NonNull GPXTabItemType tabType) { + LayoutInflater inflater = LayoutInflater.from(container.getContext()); + switch (tabType) { + case GPX_TAB_ITEM_ALTITUDE: + return inflater.inflate(R.layout.gpx_item_altitude, container, false); + case GPX_TAB_ITEM_SPEED: + return inflater.inflate(R.layout.gpx_item_speed, container, false); + case GPX_TAB_ITEM_GENERAL: + default: + return inflater.inflate(R.layout.gpx_item_general, container, false); + } + } + + private void setupSpeedTab(View view, LineChart chart, GPXTrackAnalysis analysis, GPXFile gpxFile, int position) { + if (analysis != null && analysis.isSpeedSpecified()) { + if (analysis.hasSpeedData) { + GpxUiHelper.setupGPXChart(app, chart, 4); + chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_SPEED, SPEED, null))); + updateChart(chart); + chart.setVisibility(View.VISIBLE); + } else { + chart.setVisibility(View.GONE); + } + ((ImageView) view.findViewById(R.id.average_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_speed)); + ((ImageView) view.findViewById(R.id.max_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_max_speed)); + ((ImageView) view.findViewById(R.id.time_moving_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_time_span)); + ((ImageView) view.findViewById(R.id.distance_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_polygom_dark)); + + String avg = OsmAndFormatter.getFormattedSpeed(analysis.avgSpeed, app); + String max = OsmAndFormatter.getFormattedSpeed(analysis.maxSpeed, app); + + ((TextView) view.findViewById(R.id.average_text)).setText(avg); + ((TextView) view.findViewById(R.id.max_text)).setText(max); + + view.findViewById(R.id.gpx_join_gaps_container).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (displayHelper.setJoinSegments(!displayHelper.isJoinSegments())) { + for (int i = 0; i < getCount(); i++) { + View view = getViewAtPosition(i); + updateJoinGapsInfo(view, i); + } + } + } + }); + } else { + chart.setVisibility(View.GONE); + view.findViewById(R.id.average_max).setVisibility(View.GONE); + view.findViewById(R.id.list_divider).setVisibility(View.GONE); + view.findViewById(R.id.time_distance).setVisibility(View.GONE); + } + updateJoinGapsInfo(view, position); + view.findViewById(R.id.analyze_on_map).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + openAnalyzeOnMap(GPXTabItemType.GPX_TAB_ITEM_SPEED); + } + }); + if (gpxFile.showCurrentTrack) { + view.findViewById(R.id.split_interval).setVisibility(View.GONE); + } else { + view.findViewById(R.id.split_interval).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openSplitIntervalScreen(); + } + }); + } + ImageView overflowMenu = view.findViewById(R.id.overflow_menu); + if (!gpxItem.group.getTrack().generalTrack) { + setupOptionsPopupMenu(overflowMenu, false); + } else { + overflowMenu.setVisibility(View.GONE); + } + } + + private void setupOptionsPopupMenu(ImageView overflowMenu, final boolean confirmDeletion) { + overflowMenu.setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_overflow_menu_white)); + overflowMenu.setVisibility(View.VISIBLE); + overflowMenu.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + actionsListener.showOptionsPopupMenu(view, getTrkSegment(), confirmDeletion); + } + }); + } + + private void setupAltitudeTab(View view, LineChart chart, GPXTrackAnalysis analysis, GPXFile gpxFile, int position) { + if (analysis != null) { + if (analysis.hasElevationData) { + GpxUiHelper.setupGPXChart(app, chart, 4); + chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_ALTITUDE, ALTITUDE, SLOPE))); + updateChart(chart); + chart.setVisibility(View.VISIBLE); + } else { + chart.setVisibility(View.GONE); + } + ((ImageView) view.findViewById(R.id.average_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_altitude_average)); + ((ImageView) view.findViewById(R.id.range_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_altitude_average)); + ((ImageView) view.findViewById(R.id.ascent_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_altitude_ascent)); + ((ImageView) view.findViewById(R.id.descent_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_altitude_descent)); + + String min = OsmAndFormatter.getFormattedAlt(analysis.minElevation, app); + String max = OsmAndFormatter.getFormattedAlt(analysis.maxElevation, app); + String asc = OsmAndFormatter.getFormattedAlt(analysis.diffElevationUp, app); + String desc = OsmAndFormatter.getFormattedAlt(analysis.diffElevationDown, app); + + ((TextView) view.findViewById(R.id.average_text)) + .setText(OsmAndFormatter.getFormattedAlt(analysis.avgElevation, app)); + ((TextView) view.findViewById(R.id.range_text)).setText(String.format("%s - %s", min, max)); + ((TextView) view.findViewById(R.id.ascent_text)).setText(asc); + ((TextView) view.findViewById(R.id.descent_text)).setText(desc); + + view.findViewById(R.id.gpx_join_gaps_container).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (displayHelper.setJoinSegments(!displayHelper.isJoinSegments())) { + for (int i = 0; i < getCount(); i++) { + View view = getViewAtPosition(i); + updateJoinGapsInfo(view, i); + } + } + } + }); + } else { + chart.setVisibility(View.GONE); + view.findViewById(R.id.average_range).setVisibility(View.GONE); + view.findViewById(R.id.list_divider).setVisibility(View.GONE); + view.findViewById(R.id.ascent_descent).setVisibility(View.GONE); + } + updateJoinGapsInfo(view, position); + view.findViewById(R.id.analyze_on_map).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + openAnalyzeOnMap(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE); + } + }); + if (gpxFile.showCurrentTrack) { + view.findViewById(R.id.split_interval).setVisibility(View.GONE); + } else { + view.findViewById(R.id.split_interval).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openSplitIntervalScreen(); + } + }); + } + ImageView overflowMenu = view.findViewById(R.id.overflow_menu); + if (!gpxItem.group.getTrack().generalTrack) { + setupOptionsPopupMenu(overflowMenu, false); + } else { + overflowMenu.setVisibility(View.GONE); + } + } + + private void setupGeneralTab(View view, LineChart chart, GPXTrackAnalysis analysis, GPXFile gpxFile, int position) { + if (analysis != null) { + if (analysis.hasElevationData || analysis.hasSpeedData) { + GpxUiHelper.setupGPXChart(app, chart, 4); + chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_GENERAL, ALTITUDE, SPEED))); + updateChart(chart); + chart.setVisibility(View.VISIBLE); + } else { + chart.setVisibility(View.GONE); + } + + ((ImageView) view.findViewById(R.id.distance_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_polygom_dark)); + ((ImageView) view.findViewById(R.id.duration_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_time_span)); + ((ImageView) view.findViewById(R.id.start_time_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_time_start)); + ((ImageView) view.findViewById(R.id.end_time_icon)) + .setImageDrawable(iconsCache.getThemedIcon(R.drawable.ic_action_time_end)); + + view.findViewById(R.id.gpx_join_gaps_container).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (displayHelper.setJoinSegments(!displayHelper.isJoinSegments())) { + actionsListener.updateContent(); + for (int i = 0; i < getCount(); i++) { + View view = getViewAtPosition(i); + updateJoinGapsInfo(view, i); + } + } + } + }); + if (analysis.timeSpan > 0) { + DateFormat tf = SimpleDateFormat.getTimeInstance(DateFormat.SHORT); + DateFormat df = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM); + + Date start = new Date(analysis.startTime); + ((TextView) view.findViewById(R.id.start_time_text)).setText(tf.format(start)); + ((TextView) view.findViewById(R.id.start_date_text)).setText(df.format(start)); + Date end = new Date(analysis.endTime); + ((TextView) view.findViewById(R.id.end_time_text)).setText(tf.format(end)); + ((TextView) view.findViewById(R.id.end_date_text)).setText(df.format(end)); + } else { + view.findViewById(R.id.list_divider).setVisibility(View.GONE); + view.findViewById(R.id.start_end_time).setVisibility(View.GONE); + } + } else { + chart.setVisibility(View.GONE); + view.findViewById(R.id.distance_time_span).setVisibility(View.GONE); + view.findViewById(R.id.list_divider).setVisibility(View.GONE); + view.findViewById(R.id.start_end_time).setVisibility(View.GONE); + } + updateJoinGapsInfo(view, position); + view.findViewById(R.id.analyze_on_map).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + openAnalyzeOnMap(GPXTabItemType.GPX_TAB_ITEM_GENERAL); + } + }); + if (gpxFile.showCurrentTrack) { + view.findViewById(R.id.split_interval).setVisibility(View.GONE); + } else { + view.findViewById(R.id.split_interval).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openSplitIntervalScreen(); + } + }); + } + ImageView overflowMenu = view.findViewById(R.id.overflow_menu); + if (!gpxItem.group.getTrack().generalTrack) { + setupOptionsPopupMenu(overflowMenu, true); + } else { + overflowMenu.setVisibility(View.GONE); + } + } + + private void setupChart(final View view, final LineChart chart) { + chart.setHighlightPerDragEnabled(chartClicked); + chart.setOnClickListener(new View.OnClickListener() { + @SuppressLint("ClickableViewAccessibility") + @Override + public void onClick(View view) { + if (!chartClicked) { + chartClicked = true; + if (selectedWpt != null) { + actionsListener.onPointSelected(selectedWpt.lat, selectedWpt.lon); + } + } + } + }); + chart.setOnTouchListener(new View.OnTouchListener() { + + private float listViewYPos; + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (chartClicked) { + actionsListener.onChartTouch(); + if (!chart.isHighlightPerDragEnabled()) { + chart.setHighlightPerDragEnabled(true); + } + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + listViewYPos = event.getRawY(); + break; + case MotionEvent.ACTION_MOVE: + actionsListener.scrollBy(Math.round(listViewYPos - event.getRawY())); + listViewYPos = event.getRawY(); + break; + } + } + return false; + } + }); + chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() { + @Override + public void onValueSelected(Entry e, Highlight h) { + WptPt wpt = getPoint(chart, h.getX()); + selectedWpt = wpt; + if (chartClicked && wpt != null) { + actionsListener.onPointSelected(wpt.lat, wpt.lon); + } + } + + @Override + public void onNothingSelected() { + + } + }); + chart.setOnChartGestureListener(new OnChartGestureListener() { + + float highlightDrawX = -1; + + @Override + public void onChartGestureStart(MotionEvent me, ChartGesture lastPerformedGesture) { + if (chart.getHighlighted() != null && chart.getHighlighted().length > 0) { + highlightDrawX = chart.getHighlighted()[0].getDrawX(); + } else { + highlightDrawX = -1; + } + } + + @Override + public void onChartGestureEnd(MotionEvent me, ChartGesture lastPerformedGesture) { + gpxItem.chartMatrix = new Matrix(chart.getViewPortHandler().getMatrixTouch()); + Highlight[] highlights = chart.getHighlighted(); + if (highlights != null && highlights.length > 0) { + gpxItem.chartHighlightPos = highlights[0].getX(); + } else { + gpxItem.chartHighlightPos = -1; + } + if (chartClicked) { + for (int i = 0; i < getCount(); i++) { + View v = getViewAtPosition(i); + if (v != view) { + updateChart(i); + } + } + } + } + + @Override + public void onChartLongPressed(MotionEvent me) { + } + + @Override + public void onChartDoubleTapped(MotionEvent me) { + } + + @Override + public void onChartSingleTapped(MotionEvent me) { + } + + @Override + public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) { + } + + @Override + public void onChartScale(MotionEvent me, float scaleX, float scaleY) { + } + + @Override + public void onChartTranslate(MotionEvent me, float dX, float dY) { + if (chartClicked && highlightDrawX != -1) { + Highlight h = chart.getHighlightByTouchPoint(highlightDrawX, 0f); + if (h != null) { + chart.highlightValue(h); + WptPt wpt = getPoint(chart, h.getX()); + if (wpt != null) { + actionsListener.onPointSelected(wpt.lat, wpt.lon); + } + } + } + } + }); + } + + @Override + public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) { + views.remove(position); + collection.removeView((View) view); + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } + + @Override + public View getCustomTabView(@NonNull ViewGroup parent, int position) { + View tab = LayoutInflater.from(parent.getContext()).inflate(R.layout.gpx_tab, parent, false); + tab.setTag(tabTypes[position].name()); + deselect(tab); + return tab; + } + + @Override + public void select(View tab) { + GPXTabItemType tabType = GPXTabItemType.valueOf((String) tab.getTag()); + ImageView img = tab.findViewById(R.id.tab_image); + switch (tabs.getTabSelectionType()) { + case ALPHA: + img.setAlpha(tabs.getTabTextSelectedAlpha()); + break; + case SOLID_COLOR: + img.setImageDrawable(iconsCache.getPaintedIcon(tabType.getIconId(), tabs.getTextColor())); + break; + } + } + + @Override + public void deselect(View tab) { + GPXTabItemType tabType = GPXTabItemType.valueOf((String) tab.getTag()); + ImageView img = tab.findViewById(R.id.tab_image); + switch (tabs.getTabSelectionType()) { + case ALPHA: + img.setAlpha(tabs.getTabTextAlpha()); + break; + case SOLID_COLOR: + img.setImageDrawable(iconsCache.getPaintedIcon(tabType.getIconId(), tabs.getTabInactiveTextColor())); + break; + } + } + + @Override + public View getViewAtPosition(int position) { + return views.get(position); + } + + void updateChart(int position) { + View view = getViewAtPosition(position); + if (view != null) { + updateChart((LineChart) view.findViewById(R.id.chart)); + } + } + + void updateJoinGapsInfo(View view, int position) { + if (view != null) { + GPXTrackAnalysis analysis = gpxItem.analysis; + GPXTabItemType tabType = tabTypes[position]; + boolean visible = gpxItem.isGeneralTrack() && analysis != null && tabType.equals(GPXTabItemType.GPX_TAB_ITEM_GENERAL); + AndroidUiHelper.updateVisibility(view.findViewById(R.id.gpx_join_gaps_container), visible); + boolean joinSegments = displayHelper.isJoinSegments(); + ((SwitchCompat) view.findViewById(R.id.gpx_join_gaps_switch)).setChecked(joinSegments); + if (analysis != null) { + if (tabType.equals(GPXTabItemType.GPX_TAB_ITEM_GENERAL)) { + float totalDistance = !joinSegments && gpxItem.isGeneralTrack() ? analysis.totalDistanceWithoutGaps : analysis.totalDistance; + float timeSpan = !joinSegments && gpxItem.isGeneralTrack() ? analysis.timeSpanWithoutGaps : analysis.timeSpan; + + ((TextView) view.findViewById(R.id.distance_text)).setText(OsmAndFormatter.getFormattedDistance(totalDistance, app)); + ((TextView) view.findViewById(R.id.duration_text)).setText(Algorithms.formatDuration((int) (timeSpan / 1000), app.accessibilityEnabled())); + } else if (tabType.equals(GPXTabItemType.GPX_TAB_ITEM_SPEED)) { + long timeMoving = !joinSegments && gpxItem.isGeneralTrack() ? analysis.timeMovingWithoutGaps : analysis.timeMoving; + float totalDistanceMoving = !joinSegments && gpxItem.isGeneralTrack() ? analysis.totalDistanceMovingWithoutGaps : analysis.totalDistanceMoving; + + ((TextView) view.findViewById(R.id.time_moving_text)).setText(Algorithms.formatDuration((int) (timeMoving / 1000), app.accessibilityEnabled())); + ((TextView) view.findViewById(R.id.distance_text)).setText(OsmAndFormatter.getFormattedDistance(totalDistanceMoving, app)); + } + } + } + } + + void updateChart(LineChart chart) { + if (chart != null && !chart.isEmpty()) { + if (gpxItem.chartMatrix != null) { + chart.getViewPortHandler().refresh(new Matrix(gpxItem.chartMatrix), chart, true); + } + if (gpxItem.chartHighlightPos != -1) { + chart.highlightValue(gpxItem.chartHighlightPos, 0); + } else { + chart.highlightValue(null); + } + } + } + + private TrkSegment getTrkSegment() { + for (Track track : gpxItem.group.getGpx().tracks) { + if (!track.generalTrack && !gpxItem.isGeneralTrack() || track.generalTrack && gpxItem.isGeneralTrack()) { + for (TrkSegment segment : track.segments) { + if (segment.points.size() > 0 && segment.points.get(0).equals(gpxItem.analysis.locationStart)) { + return segment; + } + } + } + } + return null; + } + + void openAnalyzeOnMap(GPXTabItemType tabType) { + List ds = getDataSets(null, tabType, null, null); + actionsListener.openAnalyzeOnMap(gpxItem, ds, tabType); + } + + private void openSplitIntervalScreen() { + actionsListener.openSplitInterval(gpxItem, getTrkSegment()); + } +} diff --git a/OsmAnd/src/net/osmand/plus/myplaces/GPXTabItemType.java b/OsmAnd/src/net/osmand/plus/myplaces/GPXTabItemType.java new file mode 100644 index 0000000000..408981d9b0 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/myplaces/GPXTabItemType.java @@ -0,0 +1,32 @@ +package net.osmand.plus.myplaces; + +import android.content.Context; + +import androidx.annotation.DrawableRes; +import androidx.annotation.StringRes; + +import net.osmand.plus.R; + +public enum GPXTabItemType { + + GPX_TAB_ITEM_GENERAL(R.string.shared_string_overview, R.drawable.ic_action_polygom_dark), + GPX_TAB_ITEM_ALTITUDE(R.string.altitude, R.drawable.ic_action_altitude_average), + GPX_TAB_ITEM_SPEED(R.string.map_widget_speed, R.drawable.ic_action_speed); + + private final int iconId; + private final int titleId; + + GPXTabItemType(@StringRes int titleId, @DrawableRes int iconId) { + this.iconId = iconId; + this.titleId = titleId; + } + + @DrawableRes + public int getIconId() { + return iconId; + } + + public String toHumanString(Context ctx) { + return ctx.getString(titleId); + } +} diff --git a/OsmAnd/src/net/osmand/plus/myplaces/SegmentActionsListener.java b/OsmAnd/src/net/osmand/plus/myplaces/SegmentActionsListener.java new file mode 100644 index 0000000000..d5ebc2235f --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/myplaces/SegmentActionsListener.java @@ -0,0 +1,27 @@ +package net.osmand.plus.myplaces; + +import android.view.View; + +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; + +import net.osmand.GPXUtilities.TrkSegment; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; + +import java.util.List; + +public interface SegmentActionsListener { + + void updateContent(); + + void onChartTouch(); + + void scrollBy(int px); + + void onPointSelected(double lat, double lon); + + void openSplitInterval(GpxDisplayItem gpxItem, TrkSegment trkSegment); + + void showOptionsPopupMenu(View view, TrkSegment trkSegment, boolean confirmDeletion); + + void openAnalyzeOnMap(GpxDisplayItem gpxItem, List dataSets, GPXTabItemType tabType); +} diff --git a/OsmAnd/src/net/osmand/plus/myplaces/SegmentGPXAdapter.java b/OsmAnd/src/net/osmand/plus/myplaces/SegmentGPXAdapter.java new file mode 100644 index 0000000000..b5e7996874 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/myplaces/SegmentGPXAdapter.java @@ -0,0 +1,91 @@ +package net.osmand.plus.myplaces; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import net.osmand.AndroidUtils; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.track.TrackDisplayHelper; +import net.osmand.plus.views.controls.PagerSlidingTabStrip; +import net.osmand.plus.views.controls.WrapContentHeightViewPager; + +import java.util.List; + +public class SegmentGPXAdapter extends ArrayAdapter { + + private OsmandApplication app; + private TrackDisplayHelper displayHelper; + private SegmentActionsListener listener; + private boolean nightMode; + + public SegmentGPXAdapter(@NonNull Context context, @NonNull List items, + @NonNull TrackDisplayHelper displayHelper, + @NonNull SegmentActionsListener listener, + boolean nightMode) { + super(context, R.layout.gpx_list_item_tab_content, items); + this.app = (OsmandApplication) context.getApplicationContext(); + this.displayHelper = displayHelper; + this.listener = listener; + this.nightMode = nightMode; + } + + @Override + public boolean isEmpty() { + return false; + } + + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + View row = convertView; + boolean create = false; + if (row == null) { + create = true; + row = createGpxTabsView(displayHelper, parent, listener, nightMode); + } + GpxDisplayItem item = getItem(position); + if (item != null) { + WrapContentHeightViewPager pager = row.findViewById(R.id.pager); + PagerSlidingTabStrip tabLayout = row.findViewById(R.id.sliding_tabs); + + pager.setAdapter(new GPXItemPagerAdapter(tabLayout, item, displayHelper, listener)); + if (create) { + tabLayout.setViewPager(pager); + } else { + tabLayout.notifyDataSetChanged(true); + } + } + return row; + } + + public static View createGpxTabsView(TrackDisplayHelper displayHelper, ViewGroup root, + SegmentActionsListener listener, boolean nightMode) { + Context context = root.getContext(); + View row = UiUtilities.getInflater(context, nightMode).inflate(R.layout.gpx_list_item_tab_content, root, false); + + PagerSlidingTabStrip tabLayout = row.findViewById(R.id.sliding_tabs); + tabLayout.setTabBackground(R.color.color_transparent); + tabLayout.setIndicatorColorResource(nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light); + tabLayout.setIndicatorBgColorResource(nightMode ? R.color.divider_color_dark : R.color.divider_color_light); + tabLayout.setIndicatorHeight(AndroidUtils.dpToPx(context, 1f)); + if (!nightMode) { + tabLayout.setTextColor(tabLayout.getIndicatorColor()); + tabLayout.setTabInactiveTextColor(ContextCompat.getColor(row.getContext(), R.color.text_color_secondary_light)); + } + tabLayout.setTextSize(AndroidUtils.spToPx(context, 12f)); + tabLayout.setShouldExpand(true); + WrapContentHeightViewPager pager = row.findViewById(R.id.pager); + pager.setSwipeable(false); + pager.setOffscreenPageLimit(2); + + return row; + } +} diff --git a/OsmAnd/src/net/osmand/plus/myplaces/SplitSegmentDialogFragment.java b/OsmAnd/src/net/osmand/plus/myplaces/SplitSegmentDialogFragment.java index 587df134d7..c981610015 100644 --- a/OsmAnd/src/net/osmand/plus/myplaces/SplitSegmentDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/myplaces/SplitSegmentDialogFragment.java @@ -23,14 +23,18 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.ListPopupWindow; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; import net.osmand.AndroidUtils; import net.osmand.GPXUtilities; import net.osmand.GPXUtilities.GPXFile; import net.osmand.GPXUtilities.GPXTrackAnalysis; +import net.osmand.GPXUtilities.TrkSegment; import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType; @@ -39,8 +43,8 @@ import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; -import net.osmand.plus.activities.TrackActivity; import net.osmand.plus.helpers.FontCache; +import net.osmand.plus.track.TrackDisplayHelper; import net.osmand.util.Algorithms; import java.text.DateFormat; @@ -54,7 +58,9 @@ import gnu.trove.list.array.TIntArrayList; public class SplitSegmentDialogFragment extends DialogFragment { public final static String TAG = "SPLIT_SEGMENT_DIALOG_FRAGMENT"; + private OsmandApplication app; + private TrackDisplayHelper displayHelper; private SplitSegmentsAdapter adapter; private View headerView; @@ -78,8 +84,8 @@ public class SplitSegmentDialogFragment extends DialogFragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - TrackActivity trackActivity = requireTrackActivity(); - app = trackActivity.getMyApplication(); + FragmentActivity activity = requireActivity(); + app = (OsmandApplication) activity.getApplication(); ic = app.getUIUtilities(); boolean isLightTheme = app.getSettings().isLightContent(); int themeId = isLightTheme ? R.style.OsmandLightTheme : R.style.OsmandDarkTheme; @@ -89,9 +95,8 @@ public class SplitSegmentDialogFragment extends DialogFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - TrackActivity trackActivity = requireTrackActivity(); listView.setBackgroundColor(getResources().getColor( - trackActivity.getMyApplication().getSettings().isLightContent() ? R.color.activity_background_color_light + app.getSettings().isLightContent() ? R.color.activity_background_color_light : R.color.activity_background_color_dark)); } @@ -103,7 +108,7 @@ public class SplitSegmentDialogFragment extends DialogFragment { minMaxSpeedPaint.setStyle(Paint.Style.FILL); minMaxSpeedTextBounds = new Rect(); - TrackActivity trackActivity = requireTrackActivity(); + AppCompatActivity trackActivity = (AppCompatActivity) getActivity(); final View view = trackActivity.getLayoutInflater().inflate(R.layout.split_segments_layout, container, false); Toolbar toolbar = (Toolbar) view.findViewById(R.id.split_interval_toolbar); @@ -250,7 +255,7 @@ public class SplitSegmentDialogFragment extends DialogFragment { } public void updateContent() { - if (getTrackActivity() != null) { + if (getActivity() != null) { adapter.clear(); adapter.setNotifyOnChange(false); adapter.add(gpxItem); @@ -297,18 +302,7 @@ public class SplitSegmentDialogFragment extends DialogFragment { @Nullable private GPXFile getGpx() { - TrackActivity trackActivity = getTrackActivity(); - return trackActivity != null ? trackActivity.getGpx() : null; - } - - @Nullable - public TrackActivity getTrackActivity() { - return (TrackActivity) getActivity(); - } - - @NonNull - public TrackActivity requireTrackActivity() { - return (TrackActivity) requireActivity(); + return displayHelper.getGpx(); } private void prepareSplitIntervalAdapterData() { @@ -346,7 +340,7 @@ public class SplitSegmentDialogFragment extends DialogFragment { @NonNull private List getDisplayGroups() { - return filterGroups(true); + return displayHelper.getDisplayGroups(filterTypes); } private void addOptionSplit(int value, boolean distance, List model) { @@ -374,32 +368,13 @@ public class SplitSegmentDialogFragment extends DialogFragment { } } - @NonNull - private List filterGroups(boolean useDisplayGroups) { - List groups = new ArrayList<>(); - if (getTrackActivity() != null) { - List result = getTrackActivity().getGpxFile(useDisplayGroups); - for (GpxDisplayGroup group : result) { - boolean add = hasFilterType(group.getType()); - if (add) { - groups.add(group); - } - - } - } - return groups; - } - @NonNull private List getSplitSegments() { - TrackActivity trackActivity = getTrackActivity(); List splitSegments = new ArrayList<>(); - if (trackActivity != null) { - List result = trackActivity.getGpxFile(true); - if (result != null && result.size() > 0 && trkSegment.points.size() > 0) { - for (GpxDisplayGroup group : result) { - splitSegments.addAll(collectDisplayItemsFromGroup(group)); - } + List result = displayHelper.getGpxFile(true); + if (result != null && result.size() > 0 && trkSegment.points.size() > 0) { + for (GpxDisplayGroup group : result) { + splitSegments.addAll(collectDisplayItemsFromGroup(group)); } } return splitSegments; @@ -445,7 +420,7 @@ public class SplitSegmentDialogFragment extends DialogFragment { @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { GpxDisplayItem currentGpxDisplayItem = getItem(position); - TrackActivity trackActivity = requireTrackActivity(); + FragmentActivity trackActivity = requireActivity(); if (convertView == null) { convertView = trackActivity.getLayoutInflater().inflate(R.layout.gpx_split_segment_fragment, parent, false); } @@ -731,14 +706,15 @@ public class SplitSegmentDialogFragment extends DialogFragment { } } - public static boolean showInstance(@NonNull TrackActivity trackActivity, @NonNull GpxDisplayItem gpxItem, GPXUtilities.TrkSegment trkSegment) { + public static boolean showInstance(@NonNull FragmentManager fragmentManager, @NonNull TrackDisplayHelper displayHelper, + @NonNull GpxDisplayItem gpxItem, @NonNull TrkSegment trkSegment) { try { SplitSegmentDialogFragment fragment = new SplitSegmentDialogFragment(); fragment.setGpxItem(gpxItem); fragment.setTrkSegment(trkSegment); fragment.setRetainInstance(true); - fragment.setJoinSegments(trackActivity.isJoinSegments()); - fragment.show(trackActivity.getSupportFragmentManager(), TAG); + fragment.displayHelper = displayHelper; + fragment.show(fragmentManager, TAG); return true; } catch (RuntimeException e) { return false; diff --git a/OsmAnd/src/net/osmand/plus/myplaces/TrackActivityFragmentAdapter.java b/OsmAnd/src/net/osmand/plus/myplaces/TrackActivityFragmentAdapter.java index 9e8f69e38c..cdceb3f704 100644 --- a/OsmAnd/src/net/osmand/plus/myplaces/TrackActivityFragmentAdapter.java +++ b/OsmAnd/src/net/osmand/plus/myplaces/TrackActivityFragmentAdapter.java @@ -39,10 +39,7 @@ import net.osmand.GPXUtilities.GPXFile; import net.osmand.GPXUtilities.WptPt; import net.osmand.PicassoUtils; import net.osmand.data.PointDescription; -import net.osmand.data.QuadRect; import net.osmand.plus.GPXDatabase.GpxDataItem; -import net.osmand.plus.GpxSelectionHelper; -import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; @@ -52,6 +49,7 @@ import net.osmand.plus.dialogs.GpxAppearanceAdapter; import net.osmand.plus.myplaces.TrackBitmapDrawer.TrackBitmapDrawerListener; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.track.TrackDisplayHelper; import net.osmand.plus.widgets.tools.CropCircleTransformation; import net.osmand.plus.wikipedia.WikiArticleHelper; import net.osmand.plus.wikivoyage.WikivoyageUtils; @@ -59,8 +57,6 @@ import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment; import net.osmand.plus.wikivoyage.data.TravelArticle; import net.osmand.render.RenderingRulesStorage; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import static net.osmand.plus.dialogs.ConfigureMapMenu.CURRENT_TRACK_COLOR_ATTR; @@ -71,6 +67,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { private Fragment fragment; private ListView listView; private GpxDisplayItemType[] filterTypes; + private TrackDisplayHelper displayHelper; private boolean updateEnable; private View headerView; @@ -99,11 +96,13 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { TrackActivityFragmentAdapter(@NonNull OsmandApplication app, @NonNull Fragment fragment, @NonNull ListView listView, + @NonNull TrackDisplayHelper displayHelper, @NonNull GpxDisplayItemType... filterTypes) { this.app = app; this.fragment = fragment; this.listView = listView; this.filterTypes = filterTypes; + this.displayHelper = displayHelper; } public void onActivityCreated(Bundle savedInstanceState) { @@ -191,14 +190,12 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { } private GPXFile getGpx() { - TrackActivity activity = getTrackActivity(); - return activity != null ? activity.getGpx() : null; + return displayHelper.getGpx(); } @Nullable private GpxDataItem getGpxDataItem() { - TrackActivity activity = getTrackActivity(); - return activity != null ? activity.getGpxDataItem() : null; + return displayHelper.getGpxDataItem(); } private void showTrackBitmapProgress() { @@ -250,7 +247,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - showTemporaryObjectOnMap(getRect()); + showTemporaryObjectOnMap(displayHelper.getRect()); } }); final View appearanceView = headerView.findViewById(R.id.appearance_view); @@ -258,7 +255,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { vis = (SwitchCompat) headerView.findViewById(R.id.showOnMapToggle); final View bottomDivider = headerView.findViewById(R.id.bottom_divider); GPXFile gpxFile = getGpx(); - boolean gpxFileSelected = isGpxFileSelected(gpxFile); + boolean gpxFileSelected = isGpxFileSelected(app, gpxFile); boolean hasPath = gpxFile != null && (gpxFile.tracks.size() > 0 || gpxFile.routes.size() > 0); TrackActivity activity = getTrackActivity(); @@ -318,7 +315,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { GPXFile gpx = getGpx(); WptPt pointToShow = gpx != null ? gpx.findPointToShow() : null; if (activity != null && pointToShow != null) { - boolean gpxFileSelected = isGpxFileSelected(gpx); + boolean gpxFileSelected = isGpxFileSelected(app, gpx); if (!gpxFileSelected) { Intent intent = activity.getIntent(); if (intent != null) { @@ -530,7 +527,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { } } - public boolean isGpxFileSelected(GPXFile gpxFile) { + public static boolean isGpxFileSelected(OsmandApplication app, GPXFile gpxFile) { return gpxFile != null && ((gpxFile.showCurrentTrack && app.getSelectedGpxHelper().getSelectedCurrentRecordingTrack() != null) || (gpxFile.path != null && app.getSelectedGpxHelper().getSelectedFileByPath(gpxFile.path) != null)); @@ -543,12 +540,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { } } - @Nullable - private QuadRect getRect() { - TrackActivity activity = getTrackActivity(); - return activity != null ? activity.getRect() : null; - } - public boolean isShowOnMap() { return vis != null && vis.isChecked(); } @@ -564,42 +555,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { menuFab.setVisibility(visible ? View.VISIBLE : View.GONE); } - @NonNull - public List getOriginalGroups() { - return filterGroups(false); - } - - @NonNull - public List getDisplayGroups() { - return filterGroups(true); - } - - private boolean hasFilterType(GpxDisplayItemType filterType) { - for (GpxDisplayItemType type : filterTypes) { - if (type == filterType) { - return true; - } - } - return false; - } - - @NonNull - private List filterGroups(boolean useDisplayGroups) { - List groups = new ArrayList<>(); - TrackActivity activity = getTrackActivity(); - if (activity != null) { - List result = activity.getGpxFile(useDisplayGroups); - for (GpxDisplayGroup group : result) { - boolean add = hasFilterType(group.getType()); - if (add) { - groups.add(group); - } - - } - } - return groups; - } - private void updateTrackColor() { int color = getGpxDataItem() != null ? getGpxDataItem().getColor() : 0; GPXFile gpxFile = getGpx(); @@ -621,14 +576,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { } } - public List flatten(List groups) { - ArrayList list = new ArrayList<>(); - for (GpxDisplayGroup g : groups) { - list.addAll(g.getModifiableList()); - } - return list; - } - public void hideTransparentOverlay() { overlayView.setVisibility(View.GONE); } diff --git a/OsmAnd/src/net/osmand/plus/myplaces/TrackPointFragment.java b/OsmAnd/src/net/osmand/plus/myplaces/TrackPointFragment.java index 10f36953c6..82fbfb02b6 100644 --- a/OsmAnd/src/net/osmand/plus/myplaces/TrackPointFragment.java +++ b/OsmAnd/src/net/osmand/plus/myplaces/TrackPointFragment.java @@ -41,7 +41,6 @@ import com.google.android.material.snackbar.Snackbar; import net.osmand.AndroidUtils; import net.osmand.Collator; -import net.osmand.GPXUtilities; import net.osmand.GPXUtilities.GPXFile; import net.osmand.GPXUtilities.WptPt; import net.osmand.OsmAndCollator; @@ -59,7 +58,6 @@ import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.OsmandActionBarActivity; import net.osmand.plus.activities.OsmandBaseExpandableListAdapter; -import net.osmand.plus.activities.SavingTrackHelper; import net.osmand.plus.activities.TrackActivity; import net.osmand.plus.base.OsmandExpandableListFragment; import net.osmand.plus.base.PointImageDrawable; @@ -67,14 +65,16 @@ import net.osmand.plus.helpers.GpxUiHelper; import net.osmand.plus.mapmarkers.CoordinateInputDialogFragment; import net.osmand.plus.mapmarkers.MapMarkersGroup; import net.osmand.plus.mapmarkers.MapMarkersHelper; +import net.osmand.plus.myplaces.DeletePointsTask.OnPointsDeleteListener; import net.osmand.plus.myplaces.TrackBitmapDrawer.TrackBitmapDrawerListener; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.track.SaveGpxAsyncTask.SaveGpxListener; +import net.osmand.plus.track.TrackDisplayHelper; +import net.osmand.plus.track.TrackMenuFragment; import net.osmand.plus.widgets.TextViewEx; import net.osmand.util.Algorithms; import java.io.File; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -88,7 +88,7 @@ import java.util.Map.Entry; import java.util.Set; -public class TrackPointFragment extends OsmandExpandableListFragment implements TrackBitmapDrawerListener { +public class TrackPointFragment extends OsmandExpandableListFragment implements TrackBitmapDrawerListener, OnPointsDeleteListener { public static final int SEARCH_ID = -1; public static final int DELETE_ID = 2; @@ -105,6 +105,8 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements private OsmandApplication app; private TrackActivityFragmentAdapter fragmentAdapter; final private PointGPXAdapter adapter = new PointGPXAdapter(); + private GpxDisplayItemType[] filterTypes = new GpxDisplayItemType[] {GpxDisplayItemType.TRACK_POINTS, GpxDisplayItemType.TRACK_ROUTE_POINTS}; + private TrackDisplayHelper displayHelper; private boolean selectionMode; private LinkedHashMap> selectedItems = new LinkedHashMap<>(); @@ -120,6 +122,13 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.app = getMyApplication(); + + FragmentActivity activity = getActivity(); + if (activity instanceof TrackActivity) { + displayHelper = ((TrackActivity) activity).getDisplayHelper(); + } else if (getTargetFragment() instanceof TrackMenuFragment) { + displayHelper = ((TrackMenuFragment) getTargetFragment()).getDisplayHelper(); + } } @Override @@ -135,8 +144,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements mainView = inflater.inflate(R.layout.track_points_tree, container, false); ExpandableListView listView = (ExpandableListView) mainView.findViewById(android.R.id.list); - fragmentAdapter = new TrackActivityFragmentAdapter(app, this, listView, - GpxDisplayItemType.TRACK_POINTS, GpxDisplayItemType.TRACK_ROUTE_POINTS); + fragmentAdapter = new TrackActivityFragmentAdapter(app, this, listView, displayHelper, filterTypes); fragmentAdapter.setShowMapOnly(true); fragmentAdapter.setTrackBitmapSelectionSupported(false); fragmentAdapter.setShowDescriptionCard(true); @@ -165,13 +173,8 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements if (optionsMenu != null) { optionsMenu.close(); } - if (fragmentAdapter != null) { - if (fragmentAdapter.splitListPopupWindow != null) { - fragmentAdapter.splitListPopupWindow.dismiss(); - } - if (fragmentAdapter.colorListPopupWindow != null) { - fragmentAdapter.colorListPopupWindow.dismiss(); - } + if (fragmentAdapter != null && fragmentAdapter.colorListPopupWindow != null) { + fragmentAdapter.colorListPopupWindow.dismiss(); } } @@ -192,7 +195,6 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements } } - private int getSelectedItemsCount() { int count = 0; for (Set set : selectedItems.values()) { @@ -213,21 +215,14 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements return result; } - @Nullable - public TrackActivity getTrackActivity() { - return (TrackActivity) getActivity(); - } - @Nullable private GPXFile getGpx() { - TrackActivity activity = getTrackActivity(); - return activity != null ? activity.getGpx() : null; + return displayHelper.getGpx(); } @Nullable private GpxDataItem getGpxDataItem() { - TrackActivity activity = getTrackActivity(); - return activity != null ? activity.getGpxDataItem() : null; + return displayHelper.getGpxDataItem(); } private void expandAllGroups() { @@ -250,7 +245,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements @Nullable private List getOriginalGroups() { - return fragmentAdapter != null ? fragmentAdapter.getOriginalGroups() : null; + return displayHelper.getOriginalGroups(filterTypes); } public void setContent() { @@ -348,7 +343,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements } }; } - + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.clear(); @@ -495,13 +490,13 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements } private void deleteItems() { - new DeletePointsTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new DeletePointsTask(app, getGpx(), getSelectedItems(), this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private void addOrRemoveMapMarkersSyncGroup() { final MapMarkersHelper markersHelper = app.getMapMarkersHelper(); - TrackActivity activity = getTrackActivity(); + FragmentActivity activity = getActivity(); if (activity == null) { return; } @@ -536,7 +531,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements .setAction(R.string.shared_string_undo, new View.OnClickListener() { @Override public void onClick(View v) { - TrackActivity trackActivity = getTrackActivity(); + FragmentActivity trackActivity = getActivity(); if (trackActivity != null) { if (markersRemoved) { if (gpxFile != null) { @@ -606,7 +601,7 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements } private void selectFavoritesImpl() { - TrackActivity activity = getTrackActivity(); + FragmentActivity activity = getActivity(); if (activity != null && getSelectedItemsCount() > 0) { AlertDialog.Builder b = new AlertDialog.Builder(activity); final EditText editText = new EditText(activity); @@ -684,14 +679,11 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements } else { GPXFile gpx = item.group.getGpx(); if (gpx != null) { - TrackActivity trackActivity = getTrackActivity(); - if (trackActivity != null && fragmentAdapter != null) { - boolean gpxFileSelected = fragmentAdapter.isGpxFileSelected(gpx); - if (!gpxFileSelected) { - Intent intent = trackActivity.getIntent(); - if (intent != null) { - intent.putExtra(TrackActivity.SHOW_TEMPORARILY, true); - } + FragmentActivity activity = getActivity(); + if (activity != null && !TrackActivityFragmentAdapter.isGpxFileSelected(app, gpx)) { + Intent intent = activity.getIntent(); + if (intent != null) { + intent.putExtra(TrackActivity.SHOW_TEMPORARILY, true); } } app.getSelectedGpxHelper().setGpxFileToDisplay(gpx); @@ -741,6 +733,23 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements } } + @Override + public void onPointsDeletionStarted() { + showProgressBar(); + } + + @Override + public void onPointsDeleted() { + selectedItems.clear(); + selectedGroups.clear(); + + hideProgressBar(); + List groups = getOriginalGroups(); + if (groups != null) { + adapter.synchronizeGroups(groups); + } + } + class PointGPXAdapter extends OsmandBaseExpandableListAdapter implements Filterable { Map> itemGroups = new LinkedHashMap<>(); @@ -1199,9 +1208,6 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements public class PointsFilter extends Filter { - PointsFilter() { - } - @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); @@ -1243,77 +1249,4 @@ public class TrackPointFragment extends OsmandExpandableListFragment implements expandAllGroups(); } } - - private static class DeletePointsTask extends AsyncTask { - - private OsmandApplication app; - private WeakReference fragmentRef; - private GPXFile gpx; - private Set selectedItems; - - DeletePointsTask(TrackPointFragment fragment) { - this.app = fragment.getMyApplication(); - this.fragmentRef = new WeakReference<>(fragment); - this.gpx = fragment.getGpx(); - this.selectedItems = fragment.getSelectedItems(); - } - - @Override - protected void onPreExecute() { - TrackPointFragment fragment = fragmentRef.get(); - if (fragment != null) { - fragment.showProgressBar(); - } - } - - @Override - protected Void doInBackground(Void... params) { - SavingTrackHelper savingTrackHelper = app.getSavingTrackHelper(); - if (gpx != null) { - for (GpxDisplayItem item : selectedItems) { - if (gpx.showCurrentTrack) { - savingTrackHelper.deletePointData(item.locationStart); - } else { - if (item.group.getType() == GpxDisplayItemType.TRACK_POINTS) { - gpx.deleteWptPt(item.locationStart); - } else if (item.group.getType() == GpxDisplayItemType.TRACK_ROUTE_POINTS) { - gpx.deleteRtePt(item.locationStart); - } - } - } - if (!gpx.showCurrentTrack) { - GPXUtilities.writeGpxFile(new File(gpx.path), gpx); - boolean selected = app.getSelectedGpxHelper().getSelectedFileByPath(gpx.path) != null; - if (selected) { - app.getSelectedGpxHelper().setGpxFileToDisplay(gpx); - } - } - syncGpx(gpx); - } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - TrackPointFragment fragment = fragmentRef.get(); - if (fragment != null) { - fragment.selectedItems.clear(); - fragment.selectedGroups.clear(); - - fragment.hideProgressBar(); - List groups = fragment.getOriginalGroups(); - if (groups != null) { - fragment.adapter.synchronizeGroups(groups); - } - } - } - - private void syncGpx(GPXFile gpxFile) { - MapMarkersHelper helper = app.getMapMarkersHelper(); - MapMarkersGroup group = helper.getMarkersGroup(gpxFile); - if (group != null) { - helper.runSynchronization(group); - } - } - } -} +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java b/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java index 0d9d960cd7..4541cf810a 100644 --- a/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java +++ b/OsmAnd/src/net/osmand/plus/myplaces/TrackSegmentFragment.java @@ -1,111 +1,79 @@ package net.osmand.plus.myplaces; -import android.annotation.SuppressLint; -import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; -import android.graphics.Matrix; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; -import android.util.SparseArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.ImageView; import android.widget.ListView; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.SwitchCompat; -import androidx.core.content.ContextCompat; import androidx.fragment.app.FragmentActivity; -import androidx.viewpager.widget.PagerAdapter; +import androidx.fragment.app.FragmentManager; -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.highlight.Highlight; import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture; -import com.github.mikephil.charting.listener.OnChartGestureListener; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; import net.osmand.AndroidUtils; -import net.osmand.GPXUtilities; import net.osmand.GPXUtilities.GPXFile; -import net.osmand.GPXUtilities.GPXTrackAnalysis; import net.osmand.GPXUtilities.Track; import net.osmand.GPXUtilities.TrkSegment; import net.osmand.GPXUtilities.WptPt; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; -import net.osmand.plus.GPXDatabase.GpxDataItem; import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType; import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; -import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.TrackActivity; import net.osmand.plus.base.OsmAndListFragment; -import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.helpers.GpxUiHelper; -import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType; import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType; -import net.osmand.plus.helpers.GpxUiHelper.LineGraphType; import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet; import net.osmand.plus.myplaces.TrackBitmapDrawer.TrackBitmapDrawerListener; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.track.SaveGpxAsyncTask; import net.osmand.plus.track.SaveGpxAsyncTask.SaveGpxListener; -import net.osmand.plus.views.controls.PagerSlidingTabStrip; -import net.osmand.plus.views.controls.PagerSlidingTabStrip.CustomTabProvider; -import net.osmand.plus.views.controls.WrapContentHeightViewPager; -import net.osmand.plus.views.controls.WrapContentHeightViewPager.ViewAtPositionInterface; +import net.osmand.plus.track.TrackDisplayHelper; +import net.osmand.plus.track.TrackMenuFragment; import net.osmand.plus.widgets.IconPopupMenu; -import net.osmand.util.Algorithms; -import net.osmand.util.MapUtils; import java.io.File; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.ALTITUDE; -import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SLOPE; -import static net.osmand.plus.helpers.GpxUiHelper.LineGraphType.SPEED; - -public class TrackSegmentFragment extends OsmAndListFragment implements TrackBitmapDrawerListener { +public class TrackSegmentFragment extends OsmAndListFragment implements TrackBitmapDrawerListener, SegmentActionsListener { private OsmandApplication app; + private TrackDisplayHelper displayHelper; private TrackActivityFragmentAdapter fragmentAdapter; private SegmentGPXAdapter adapter; - + private final GpxDisplayItemType[] filterTypes = new GpxDisplayItemType[] {GpxDisplayItemType.TRACK_SEGMENT}; + private IconPopupMenu optionsPopupMenu; private boolean updateEnable; - private boolean chartClicked; - - private IconPopupMenu generalPopupMenu; - private IconPopupMenu altitudePopupMenu; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.app = getMyApplication(); + + FragmentActivity activity = getActivity(); + if (activity instanceof TrackActivity) { + displayHelper = ((TrackActivity) activity).getDisplayHelper(); + } else if (getTargetFragment() instanceof TrackMenuFragment) { + displayHelper = ((TrackMenuFragment) getTargetFragment()).getDisplayHelper(); + } } @Override @@ -119,18 +87,18 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.track_segments_tree, container, false); - ListView listView = (ListView) view.findViewById(android.R.id.list); + ListView listView = view.findViewById(android.R.id.list); listView.setDivider(null); listView.setDividerHeight(0); - fragmentAdapter = new TrackActivityFragmentAdapter(app, this, listView, - GpxDisplayItemType.TRACK_SEGMENT); + fragmentAdapter = new TrackActivityFragmentAdapter(app, this, listView, displayHelper, filterTypes); fragmentAdapter.setShowMapOnly(false); fragmentAdapter.setTrackBitmapSelectionSupported(true); fragmentAdapter.setShowDescriptionCard(false); fragmentAdapter.onCreateView(view); - adapter = new SegmentGPXAdapter(inflater.getContext(), new ArrayList()); + boolean nightMode = !app.getSettings().isLightContent(); + adapter = new SegmentGPXAdapter(view.getContext(), new ArrayList(), displayHelper, this, nightMode); setListAdapter(adapter); return view; @@ -146,14 +114,9 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit } @Override - public boolean onOptionsItemSelected(MenuItem item) { - return super.onOptionsItemSelected(item); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + public void onCreateOptionsMenu(Menu menu, @NonNull MenuInflater inflater) { menu.clear(); - GPXFile gpxFile = getGpx(); + GPXFile gpxFile = displayHelper.getGpx(); if (gpxFile != null) { if (gpxFile.path != null && !gpxFile.showCurrentTrack) { Drawable shareIcon = app.getUIUtilities().getIcon((R.drawable.ic_action_gshare_dark)); @@ -162,7 +125,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - GPXFile gpx = getGpx(); + GPXFile gpx = displayHelper.getGpx(); FragmentActivity activity = getActivity(); if (activity != null && gpx != null) { GpxUiHelper.shareGpx(activity, new File(gpx.path)); @@ -189,18 +152,6 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit } } - @Nullable - private GPXFile getGpx() { - TrackActivity activity = getTrackActivity(); - return activity != null ? activity.getGpx() : null; - } - - @Nullable - private GpxDataItem getGpxDataItem() { - TrackActivity activity = getTrackActivity(); - return activity != null ? activity.getGpxDataItem() : null; - } - @Override public void onResume() { super.onResume(); @@ -212,16 +163,11 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit public void onPause() { super.onPause(); setUpdateEnable(false); - if (generalPopupMenu != null) { - generalPopupMenu.dismiss(); + if (optionsPopupMenu != null) { + optionsPopupMenu.dismiss(); } - if (altitudePopupMenu != null) { - altitudePopupMenu.dismiss(); - } - if (fragmentAdapter != null) { - if (fragmentAdapter.colorListPopupWindow != null) { - fragmentAdapter.colorListPopupWindow.dismiss(); - } + if (fragmentAdapter != null && fragmentAdapter.colorListPopupWindow != null) { + fragmentAdapter.colorListPopupWindow.dismiss(); } } @@ -242,45 +188,12 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit } } - public void updateContent() { - adapter.clear(); - List groups = getOriginalGroups(); - if (groups != null) { - adapter.setNotifyOnChange(false); - List items = flatten(groups); - if (items != null) { - for (GpxDisplayItem i : items) { - adapter.add(i); - } - adapter.notifyDataSetChanged(); - if (getActivity() != null) { - updateHeader(); - } - } - } - } - public void updateHeader() { if (fragmentAdapter != null) { fragmentAdapter.updateHeader(adapter.getCount()); } } - @Nullable - private List flatten(List groups) { - return fragmentAdapter != null ? fragmentAdapter.flatten(groups) : null; - } - - @Nullable - private List getOriginalGroups() { - return fragmentAdapter != null ? fragmentAdapter.getOriginalGroups() : null; - } - - @Nullable - private List getDisplayGroups() { - return fragmentAdapter != null ? fragmentAdapter.getDisplayGroups() : null; - } - @Override public void onTrackBitmapDrawing() { if (fragmentAdapter != null) { @@ -307,894 +220,172 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit } } - private class SegmentGPXAdapter extends ArrayAdapter { - - SegmentGPXAdapter(@NonNull Context context, @NonNull List items) { - super(context, R.layout.gpx_list_item_tab_content, items); + @Override + public void updateContent() { + adapter.clear(); + adapter.setNotifyOnChange(false); + List groups = displayHelper.getOriginalGroups(filterTypes); + for (GpxDisplayItem displayItem : TrackDisplayHelper.flatten(groups)) { + adapter.add(displayItem); } - - @Override - public boolean isEmpty() { - return false; - } - - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - View row = convertView; - PagerSlidingTabStrip tabLayout; - WrapContentHeightViewPager pager; - boolean create = false; - if (row == null) { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - row = inflater.inflate(R.layout.gpx_list_item_tab_content, parent, false); - - boolean light = app.getSettings().isLightContent(); - tabLayout = (PagerSlidingTabStrip) row.findViewById(R.id.sliding_tabs); - tabLayout.setTabBackground(R.color.color_transparent); - tabLayout.setIndicatorColorResource(light ? R.color.active_color_primary_light : R.color.active_color_primary_dark); - tabLayout.setIndicatorBgColorResource(light ? R.color.divider_color_light : R.color.divider_color_dark); - tabLayout.setIndicatorHeight(AndroidUtils.dpToPx(app, 1f)); - if (light) { - tabLayout.setTextColor(tabLayout.getIndicatorColor()); - tabLayout.setTabInactiveTextColor(ContextCompat.getColor(row.getContext(), R.color.text_color_secondary_light)); - } - tabLayout.setTextSize(AndroidUtils.spToPx(app, 12f)); - tabLayout.setShouldExpand(true); - pager = (WrapContentHeightViewPager) row.findViewById(R.id.pager); - pager.setSwipeable(false); - pager.setOffscreenPageLimit(2); - create = true; - } else { - tabLayout = (PagerSlidingTabStrip) row.findViewById(R.id.sliding_tabs); - pager = (WrapContentHeightViewPager) row.findViewById(R.id.pager); - } - GpxDisplayItem item = getItem(position); - if (item != null) { - pager.setAdapter(new GPXItemPagerAdapter(tabLayout, item)); - if (create) { - tabLayout.setViewPager(pager); - } else { - tabLayout.notifyDataSetChanged(true); - } - } - return row; + adapter.notifyDataSetChanged(); + if (getActivity() != null) { + updateHeader(); } } - private enum GPXTabItemType { - GPX_TAB_ITEM_GENERAL, - GPX_TAB_ITEM_ALTITUDE, - GPX_TAB_ITEM_SPEED + @Override + public void onChartTouch() { + getListView().requestDisallowInterceptTouchEvent(true); } - private class GPXItemPagerAdapter extends PagerAdapter implements CustomTabProvider, ViewAtPositionInterface { + @Override + public void scrollBy(int px) { + getListView().setSelectionFromTop(getListView().getFirstVisiblePosition(), getListView().getChildAt(0).getTop() - px); + } - protected SparseArray views = new SparseArray<>(); - private PagerSlidingTabStrip tabs; - private GpxDisplayItem gpxItem; - private GPXTabItemType[] tabTypes; - private String[] titles; - private Map> dataSetsMap = new HashMap<>(); - private TrkSegment segment; - private float listViewYPos; - private WptPt selectedWpt; - - GPXItemPagerAdapter(PagerSlidingTabStrip tabs, GpxDisplayItem gpxItem) { - super(); - this.tabs = tabs; - this.gpxItem = gpxItem; - fetchTabTypes(); + @Override + public void onPointSelected(double lat, double lon) { + if (fragmentAdapter != null) { + fragmentAdapter.updateSelectedPoint(lat, lon); } + } - private void fetchTabTypes() { + @Override + public void openSplitInterval(GpxDisplayItem gpxItem, TrkSegment trkSegment) { + FragmentManager fragmentManager = getFragmentManager(); + if (fragmentManager != null) { + SplitSegmentDialogFragment.showInstance(fragmentManager, displayHelper, gpxItem, trkSegment); + } + } - List tabTypeList = new ArrayList<>(); - tabTypeList.add(GPXTabItemType.GPX_TAB_ITEM_GENERAL); - if (gpxItem != null && gpxItem.analysis != null) { - if (gpxItem.analysis.hasElevationData) { - tabTypeList.add(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE); - } - if (gpxItem.analysis.isSpeedSpecified()) { - tabTypeList.add(GPXTabItemType.GPX_TAB_ITEM_SPEED); - } + @Override + public void openAnalyzeOnMap(GpxDisplayItem gpxItem, List dataSets, GPXTabItemType tabType) { + LatLon location = null; + WptPt wpt = null; + gpxItem.chartTypes = null; + if (dataSets != null && dataSets.size() > 0) { + gpxItem.chartTypes = new GPXDataSetType[dataSets.size()]; + for (int i = 0; i < dataSets.size(); i++) { + OrderedLineDataSet orderedDataSet = (OrderedLineDataSet) dataSets.get(i); + gpxItem.chartTypes[i] = orderedDataSet.getDataSetType(); } - tabTypes = tabTypeList.toArray(new GPXTabItemType[0]); - - Context context = tabs.getContext(); - titles = new String[tabTypes.length]; - for (int i = 0; i < titles.length; i++) { - switch (tabTypes[i]) { - case GPX_TAB_ITEM_GENERAL: - titles[i] = context.getString(R.string.shared_string_overview); - break; - case GPX_TAB_ITEM_ALTITUDE: - titles[i] = context.getString(R.string.altitude); - break; - case GPX_TAB_ITEM_SPEED: - titles[i] = context.getString(R.string.map_widget_speed); - break; - } - } - } - - private List getDataSets(LineChart chart, - GPXTabItemType tabType, - LineGraphType firstType, - LineGraphType secondType) { - List dataSets = dataSetsMap.get(tabType); - if (dataSets == null && chart != null) { - GPXTrackAnalysis analysis = gpxItem.analysis; - GpxDataItem gpxDataItem = getGpxDataItem(); - boolean calcWithoutGaps = gpxItem.isGeneralTrack() && gpxDataItem != null && !gpxDataItem.isJoinSegments(); - dataSets = GpxUiHelper.getDataSets(chart, app, analysis, firstType, secondType, calcWithoutGaps); - dataSetsMap.put(tabType, dataSets); - } - return dataSets; - } - - private TrkSegment getTrackSegment(LineChart chart) { - if (segment == null) { - LineData lineData = chart.getLineData(); - List ds = lineData != null ? lineData.getDataSets() : null; - if (ds != null && ds.size() > 0) { - for (GPXUtilities.Track t : gpxItem.group.getGpx().tracks) { - for (TrkSegment s : t.segments) { - if (s.points.size() > 0 && s.points.get(0).equals(gpxItem.analysis.locationStart)) { - segment = s; - break; - } - } - if (segment != null) { - break; - } - } - } - } - return segment; - } - - private WptPt getPoint(LineChart chart, float pos) { - WptPt wpt = null; - LineData lineData = chart.getLineData(); - List ds = lineData != null ? lineData.getDataSets() : null; - if (ds != null && ds.size() > 0) { - TrkSegment segment = getTrackSegment(chart); - OrderedLineDataSet dataSet = (OrderedLineDataSet) ds.get(0); - if (gpxItem.chartAxisType == GPXDataSetAxisType.TIME) { - float time = pos * 1000; - for (WptPt p : segment.points) { - if (p.time - gpxItem.analysis.startTime >= time) { - wpt = p; - break; - } - } - } else { - float distance = pos * dataSet.getDivX(); - double totalDistance = 0; - for (int i = 0; i < segment.points.size(); i++) { - WptPt currentPoint = segment.points.get(i); - if (i != 0) { - WptPt previousPoint = segment.points.get(i - 1); - totalDistance += MapUtils.getDistance(previousPoint.lat, previousPoint.lon, currentPoint.lat, currentPoint.lon); - } - if (currentPoint.distance >= distance || Math.abs(totalDistance - distance) < 0.1) { - wpt = currentPoint; - break; - } - } - } - } - return wpt; - } - - private void scrollBy(int px) { - getListView().setSelectionFromTop(getListView().getFirstVisiblePosition(), getListView().getChildAt(0).getTop() - px); - } - - @Override - public int getCount() { - return tabTypes.length; - } - - @Override - public CharSequence getPageTitle(int position) { - return titles[position]; - } - - @NonNull - @Override - public Object instantiateItem(@NonNull ViewGroup container, int position) { - - GPXTabItemType tabType = tabTypes[position]; - final View view; - LayoutInflater inflater = LayoutInflater.from(container.getContext()); - switch (tabType) { - case GPX_TAB_ITEM_GENERAL: - view = inflater.inflate(R.layout.gpx_item_general, container, false); - break; - case GPX_TAB_ITEM_ALTITUDE: - view = inflater.inflate(R.layout.gpx_item_altitude, container, false); - break; - case GPX_TAB_ITEM_SPEED: - view = inflater.inflate(R.layout.gpx_item_speed, container, false); - break; - default: - view = inflater.inflate(R.layout.gpx_item_general, container, false); - break; - } - GPXFile gpxFile = getGpx(); - if (gpxFile != null && gpxItem != null) { - GPXTrackAnalysis analysis = gpxItem.analysis; - final LineChart chart = (LineChart) view.findViewById(R.id.chart); - chart.setHighlightPerDragEnabled(chartClicked); - chart.setOnClickListener(new View.OnClickListener() { - @SuppressLint("ClickableViewAccessibility") - @Override - public void onClick(View view) { - if (!chartClicked) { - chartClicked = true; - if (selectedWpt != null && fragmentAdapter != null) { - fragmentAdapter.updateSelectedPoint(selectedWpt.lat, selectedWpt.lon); - } - } - } - }); - chart.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (chartClicked) { - getListView().requestDisallowInterceptTouchEvent(true); - if (!chart.isHighlightPerDragEnabled()) { - chart.setHighlightPerDragEnabled(true); - } - switch (event.getAction()) { - case android.view.MotionEvent.ACTION_DOWN: - listViewYPos = event.getRawY(); - break; - case android.view.MotionEvent.ACTION_MOVE: - scrollBy(Math.round(listViewYPos - event.getRawY())); - listViewYPos = event.getRawY(); - break; - } - } - return false; - } - }); - chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() { - @Override - public void onValueSelected(Entry e, Highlight h) { - WptPt wpt = getPoint(chart, h.getX()); - selectedWpt = wpt; - if (chartClicked && wpt != null && fragmentAdapter != null) { - fragmentAdapter.updateSelectedPoint(wpt.lat, wpt.lon); - } - } - - @Override - public void onNothingSelected() { - - } - }); - chart.setOnChartGestureListener(new OnChartGestureListener() { - - float highlightDrawX = -1; - - @Override - public void onChartGestureStart(MotionEvent me, ChartGesture lastPerformedGesture) { - if (chart.getHighlighted() != null && chart.getHighlighted().length > 0) { - highlightDrawX = chart.getHighlighted()[0].getDrawX(); - } else { - highlightDrawX = -1; - } - } - - @Override - public void onChartGestureEnd(MotionEvent me, ChartGesture lastPerformedGesture) { - gpxItem.chartMatrix = new Matrix(chart.getViewPortHandler().getMatrixTouch()); - Highlight[] highlights = chart.getHighlighted(); - if (highlights != null && highlights.length > 0) { - gpxItem.chartHighlightPos = highlights[0].getX(); - } else { - gpxItem.chartHighlightPos = -1; - } - if (chartClicked) { - for (int i = 0; i < getCount(); i++) { - View v = getViewAtPosition(i); - if (v != view) { - updateChart(i); - } - } - } - } - - @Override - public void onChartLongPressed(MotionEvent me) { - } - - @Override - public void onChartDoubleTapped(MotionEvent me) { - } - - @Override - public void onChartSingleTapped(MotionEvent me) { - } - - @Override - public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) { - } - - @Override - public void onChartScale(MotionEvent me, float scaleX, float scaleY) { - } - - @Override - public void onChartTranslate(MotionEvent me, float dX, float dY) { - if (chartClicked && highlightDrawX != -1) { - Highlight h = chart.getHighlightByTouchPoint(highlightDrawX, 0f); - if (h != null) { - chart.highlightValue(h); - WptPt wpt = getPoint(chart, h.getX()); - if (wpt != null && fragmentAdapter != null) { - fragmentAdapter.updateSelectedPoint(wpt.lat, wpt.lon); - } - } - } - } - }); - - final UiUtilities ic = app.getUIUtilities(); - switch (tabType) { - case GPX_TAB_ITEM_GENERAL: - if (analysis != null) { - if (analysis.hasElevationData || analysis.hasSpeedData) { - GpxUiHelper.setupGPXChart(app, chart, 4); - chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_GENERAL, ALTITUDE, SPEED))); - updateChart(chart); - chart.setVisibility(View.VISIBLE); - } else { - chart.setVisibility(View.GONE); - } - - ((ImageView) view.findViewById(R.id.distance_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_polygom_dark)); - ((ImageView) view.findViewById(R.id.duration_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_time_span)); - ((ImageView) view.findViewById(R.id.start_time_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_time_start)); - ((ImageView) view.findViewById(R.id.end_time_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_time_end)); - - view.findViewById(R.id.gpx_join_gaps_container).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - TrackActivity activity = getTrackActivity(); - if (activity != null && activity.setJoinSegments(!activity.isJoinSegments())) { - updateContent(); - for (int i = 0; i < getCount(); i++) { - View view = getViewAtPosition(i); - updateJoinGapsInfo(view, i); - } - } - } - }); - if (analysis.timeSpan > 0) { - DateFormat tf = SimpleDateFormat.getTimeInstance(DateFormat.SHORT); - DateFormat df = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM); - - Date start = new Date(analysis.startTime); - ((TextView) view.findViewById(R.id.start_time_text)).setText(tf.format(start)); - ((TextView) view.findViewById(R.id.start_date_text)).setText(df.format(start)); - Date end = new Date(analysis.endTime); - ((TextView) view.findViewById(R.id.end_time_text)).setText(tf.format(end)); - ((TextView) view.findViewById(R.id.end_date_text)).setText(df.format(end)); - } else { - view.findViewById(R.id.list_divider).setVisibility(View.GONE); - view.findViewById(R.id.start_end_time).setVisibility(View.GONE); - } - } else { - chart.setVisibility(View.GONE); - view.findViewById(R.id.distance_time_span).setVisibility(View.GONE); - view.findViewById(R.id.list_divider).setVisibility(View.GONE); - view.findViewById(R.id.start_end_time).setVisibility(View.GONE); - } - updateJoinGapsInfo(view, position); - view.findViewById(R.id.analyze_on_map).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - openAnalyzeOnMap(GPXTabItemType.GPX_TAB_ITEM_GENERAL); - } - }); - if (gpxFile.showCurrentTrack) { - view.findViewById(R.id.split_interval).setVisibility(View.GONE); - } else { - view.findViewById(R.id.split_interval).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - openSplitIntervalScreen(); - } - }); - } - if (!gpxItem.group.getTrack().generalTrack) { - ((ImageView) view.findViewById(R.id.overflow_menu)).setImageDrawable(ic.getThemedIcon(R.drawable.ic_overflow_menu_white)); - view.findViewById(R.id.overflow_menu).setVisibility(View.VISIBLE); - view.findViewById(R.id.overflow_menu).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - TrackActivity activity = getTrackActivity(); - if (activity != null) { - generalPopupMenu = new IconPopupMenu(activity, view.findViewById(R.id.overflow_menu)); - Menu menu = generalPopupMenu.getMenu(); - generalPopupMenu.getMenuInflater().inflate(R.menu.track_segment_menu, menu); - menu.findItem(R.id.action_edit).setIcon(ic.getThemedIcon(R.drawable.ic_action_edit_dark)); - menu.findItem(R.id.action_delete).setIcon(ic.getThemedIcon(R.drawable.ic_action_remove_dark)); - generalPopupMenu.setOnMenuItemClickListener(new IconPopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - int i = item.getItemId(); - if (i == R.id.action_edit) { - editSegment(); - return true; - } else if (i == R.id.action_delete) { - TrackActivity activity = getTrackActivity(); - if (activity != null) { - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setMessage(R.string.recording_delete_confirm); - builder.setPositiveButton(R.string.shared_string_yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - deleteAndSaveSegment(); - } - }); - builder.setNegativeButton(R.string.shared_string_cancel, null); - builder.show(); - } - return true; - } - return false; - } - }); - generalPopupMenu.show(); - } - } - }); - } else { - view.findViewById(R.id.overflow_menu).setVisibility(View.GONE); - } - - break; - case GPX_TAB_ITEM_ALTITUDE: - if (analysis != null) { - if (analysis.hasElevationData) { - GpxUiHelper.setupGPXChart(app, chart, 4); - chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_ALTITUDE, ALTITUDE, SLOPE))); - updateChart(chart); - chart.setVisibility(View.VISIBLE); - } else { - chart.setVisibility(View.GONE); - } - ((ImageView) view.findViewById(R.id.average_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_altitude_average)); - ((ImageView) view.findViewById(R.id.range_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_altitude_average)); - ((ImageView) view.findViewById(R.id.ascent_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_altitude_ascent)); - ((ImageView) view.findViewById(R.id.descent_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_altitude_descent)); - - String min = OsmAndFormatter.getFormattedAlt(analysis.minElevation, app); - String max = OsmAndFormatter.getFormattedAlt(analysis.maxElevation, app); - String asc = OsmAndFormatter.getFormattedAlt(analysis.diffElevationUp, app); - String desc = OsmAndFormatter.getFormattedAlt(analysis.diffElevationDown, app); - - ((TextView) view.findViewById(R.id.average_text)) - .setText(OsmAndFormatter.getFormattedAlt(analysis.avgElevation, app)); - ((TextView) view.findViewById(R.id.range_text)).setText(String.format("%s - %s", min, max)); - ((TextView) view.findViewById(R.id.ascent_text)).setText(asc); - ((TextView) view.findViewById(R.id.descent_text)).setText(desc); - - view.findViewById(R.id.gpx_join_gaps_container).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - TrackActivity activity = getTrackActivity(); - if (activity != null && activity.setJoinSegments(!activity.isJoinSegments())) { - for (int i = 0; i < getCount(); i++) { - View view = getViewAtPosition(i); - updateJoinGapsInfo(view, i); - } - } - } - }); - } else { - chart.setVisibility(View.GONE); - view.findViewById(R.id.average_range).setVisibility(View.GONE); - view.findViewById(R.id.list_divider).setVisibility(View.GONE); - view.findViewById(R.id.ascent_descent).setVisibility(View.GONE); - } - updateJoinGapsInfo(view, position); - view.findViewById(R.id.analyze_on_map).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - openAnalyzeOnMap(GPXTabItemType.GPX_TAB_ITEM_ALTITUDE); - } - }); - if (gpxFile.showCurrentTrack) { - view.findViewById(R.id.split_interval).setVisibility(View.GONE); - } else { - view.findViewById(R.id.split_interval).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - openSplitIntervalScreen(); - } - }); - } - if (!gpxItem.group.getTrack().generalTrack) { - ((ImageView) view.findViewById(R.id.overflow_menu)).setImageDrawable(ic.getThemedIcon(R.drawable.ic_overflow_menu_white)); - view.findViewById(R.id.overflow_menu).setVisibility(View.VISIBLE); - view.findViewById(R.id.overflow_menu).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - TrackActivity activity = getTrackActivity(); - if (activity != null) { - altitudePopupMenu = new IconPopupMenu(activity, view.findViewById(R.id.overflow_menu)); - Menu menu = altitudePopupMenu.getMenu(); - altitudePopupMenu.getMenuInflater().inflate(R.menu.track_segment_menu, menu); - menu.findItem(R.id.action_edit).setIcon(ic.getThemedIcon(R.drawable.ic_action_edit_dark)); - menu.findItem(R.id.action_delete).setIcon(ic.getThemedIcon(R.drawable.ic_action_remove_dark)); - altitudePopupMenu.setOnMenuItemClickListener(new IconPopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - int i = item.getItemId(); - if (i == R.id.action_edit) { - editSegment(); - return true; - } else if (i == R.id.action_delete) { - deleteAndSaveSegment(); - return true; - } - return false; - } - }); - altitudePopupMenu.show(); - } - } - }); - } else { - view.findViewById(R.id.overflow_menu).setVisibility(View.GONE); - } - - break; - case GPX_TAB_ITEM_SPEED: - if (analysis != null && analysis.isSpeedSpecified()) { - if (analysis.hasSpeedData) { - GpxUiHelper.setupGPXChart(app, chart, 4); - chart.setData(new LineData(getDataSets(chart, GPXTabItemType.GPX_TAB_ITEM_SPEED, SPEED, null))); - updateChart(chart); - chart.setVisibility(View.VISIBLE); - } else { - chart.setVisibility(View.GONE); - } - ((ImageView) view.findViewById(R.id.average_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_speed)); - ((ImageView) view.findViewById(R.id.max_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_max_speed)); - ((ImageView) view.findViewById(R.id.time_moving_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_time_span)); - ((ImageView) view.findViewById(R.id.distance_icon)) - .setImageDrawable(ic.getThemedIcon(R.drawable.ic_action_polygom_dark)); - - String avg = OsmAndFormatter.getFormattedSpeed(analysis.avgSpeed, app); - String max = OsmAndFormatter.getFormattedSpeed(analysis.maxSpeed, app); - - ((TextView) view.findViewById(R.id.average_text)).setText(avg); - ((TextView) view.findViewById(R.id.max_text)).setText(max); - - view.findViewById(R.id.gpx_join_gaps_container).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - TrackActivity activity = getTrackActivity(); - if (activity != null && activity.setJoinSegments(!activity.isJoinSegments())) { - for (int i = 0; i < getCount(); i++) { - View view = getViewAtPosition(i); - updateJoinGapsInfo(view, i); - } - } - } - }); - } else { - chart.setVisibility(View.GONE); - view.findViewById(R.id.average_max).setVisibility(View.GONE); - view.findViewById(R.id.list_divider).setVisibility(View.GONE); - view.findViewById(R.id.time_distance).setVisibility(View.GONE); - } - updateJoinGapsInfo(view, position); - view.findViewById(R.id.analyze_on_map).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - openAnalyzeOnMap(GPXTabItemType.GPX_TAB_ITEM_SPEED); - } - }); - if (gpxFile.showCurrentTrack) { - view.findViewById(R.id.split_interval).setVisibility(View.GONE); - } else { - view.findViewById(R.id.split_interval).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - openSplitIntervalScreen(); - } - }); - } - if (!gpxItem.group.getTrack().generalTrack) { - ((ImageView) view.findViewById(R.id.overflow_menu)).setImageDrawable(ic.getThemedIcon(R.drawable.ic_overflow_menu_white)); - view.findViewById(R.id.overflow_menu).setVisibility(View.VISIBLE); - view.findViewById(R.id.overflow_menu).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - TrackActivity activity = getTrackActivity(); - if (activity != null) { - IconPopupMenu popupMenu = new IconPopupMenu(activity, view.findViewById(R.id.overflow_menu)); - Menu menu = popupMenu.getMenu(); - popupMenu.getMenuInflater().inflate(R.menu.track_segment_menu, menu); - menu.findItem(R.id.action_edit).setIcon(ic.getThemedIcon(R.drawable.ic_action_edit_dark)); - menu.findItem(R.id.action_delete).setIcon(ic.getThemedIcon(R.drawable.ic_action_remove_dark)); - popupMenu.setOnMenuItemClickListener(new IconPopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - int i = item.getItemId(); - if (i == R.id.action_edit) { - editSegment(); - return true; - } else if (i == R.id.action_delete) { - deleteAndSaveSegment(); - return true; - } - return false; - } - }); - popupMenu.show(); - } - } - }); - } else { - view.findViewById(R.id.overflow_menu).setVisibility(View.GONE); - } - - break; - } - } - container.addView(view, 0); - views.put(position, view); - return view; - } - - private void editSegment() { - TrkSegment segment = getTrkSegment(); - if (segment != null && fragmentAdapter != null) { - fragmentAdapter.addNewGpxData(); - } - } - - private void deleteAndSaveSegment() { - TrackActivity trackActivity = getTrackActivity(); - if (trackActivity != null && deleteSegment()) { - GPXFile gpx = getGpx(); - if (gpx != null && fragmentAdapter != null) { - boolean showOnMap = fragmentAdapter.isShowOnMap(); - SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().selectGpxFile(gpx, showOnMap, false); - saveGpx(showOnMap ? selectedGpxFile : null, gpx); - } - } - } - - private boolean deleteSegment() { - TrkSegment segment = getTrkSegment(); - if (segment != null) { - GPXFile gpx = getGpx(); - if (gpx != null) { - return gpx.removeTrkSegment(segment); - } - } - return false; - } - - @Override - public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) { - views.remove(position); - collection.removeView((View) view); - } - - @Override - public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { - return view == object; - } - - @Override - public View getCustomTabView(@NonNull ViewGroup parent, int position) { - View tab = LayoutInflater.from(parent.getContext()).inflate(R.layout.gpx_tab, parent, false); - tab.setTag(tabTypes[position].name()); - deselect(tab); - return tab; - } - - private int getImageId(GPXTabItemType tabType) { - int imageId; - switch (tabType) { - case GPX_TAB_ITEM_GENERAL: - imageId = R.drawable.ic_action_polygom_dark; - break; - case GPX_TAB_ITEM_ALTITUDE: - imageId = R.drawable.ic_action_altitude_average; - break; - case GPX_TAB_ITEM_SPEED: - imageId = R.drawable.ic_action_speed; - break; - default: - imageId = R.drawable.ic_action_folder_stroke; - } - return imageId; - } - - @Override - public void select(View tab) { - GPXTabItemType tabType = GPXTabItemType.valueOf((String) tab.getTag()); - ImageView img = (ImageView) tab.findViewById(R.id.tab_image); - int imageId = getImageId(tabType); - switch (tabs.getTabSelectionType()) { - case ALPHA: - img.setAlpha(tabs.getTabTextSelectedAlpha()); - break; - case SOLID_COLOR: - img.setImageDrawable(app.getUIUtilities().getPaintedIcon(imageId, tabs.getTextColor())); - break; - } - } - - @Override - public void deselect(View tab) { - GPXTabItemType tabType = GPXTabItemType.valueOf((String) tab.getTag()); - ImageView img = (ImageView) tab.findViewById(R.id.tab_image); - int imageId = getImageId(tabType); - switch (tabs.getTabSelectionType()) { - case ALPHA: - img.setAlpha(tabs.getTabTextAlpha()); - break; - case SOLID_COLOR: - img.setImageDrawable(app.getUIUtilities().getPaintedIcon(imageId, tabs.getTabInactiveTextColor())); - break; - } - } - - @Override - public View getViewAtPosition(int position) { - return views.get(position); - } - - void updateChart(int position) { - View view = getViewAtPosition(position); - if (view != null) { - updateChart((LineChart) view.findViewById(R.id.chart)); - } - } - - void updateJoinGapsInfo(View view, int position) { - TrackActivity activity = getTrackActivity(); - if (view != null && activity != null) { - GPXTrackAnalysis analysis = gpxItem.analysis; - GPXTabItemType tabType = tabTypes[position]; - boolean visible = gpxItem.isGeneralTrack() && analysis != null && tabType.equals(GPXTabItemType.GPX_TAB_ITEM_GENERAL); - AndroidUiHelper.updateVisibility(view.findViewById(R.id.gpx_join_gaps_container), visible); - boolean joinSegments = activity.isJoinSegments(); - ((SwitchCompat) view.findViewById(R.id.gpx_join_gaps_switch)).setChecked(joinSegments); - if (analysis != null) { - if (tabType.equals(GPXTabItemType.GPX_TAB_ITEM_GENERAL)) { - float totalDistance = !joinSegments && gpxItem.isGeneralTrack() ? analysis.totalDistanceWithoutGaps : analysis.totalDistance; - float timeSpan = !joinSegments && gpxItem.isGeneralTrack() ? analysis.timeSpanWithoutGaps : analysis.timeSpan; - - ((TextView) view.findViewById(R.id.distance_text)).setText(OsmAndFormatter.getFormattedDistance(totalDistance, app)); - ((TextView) view.findViewById(R.id.duration_text)).setText(Algorithms.formatDuration((int) (timeSpan / 1000), app.accessibilityEnabled())); - } else if (tabType.equals(GPXTabItemType.GPX_TAB_ITEM_SPEED)) { - long timeMoving = !joinSegments && gpxItem.isGeneralTrack() ? analysis.timeMovingWithoutGaps : analysis.timeMoving; - float totalDistanceMoving = !joinSegments && gpxItem.isGeneralTrack() ? analysis.totalDistanceMovingWithoutGaps : analysis.totalDistanceMoving; - - ((TextView) view.findViewById(R.id.time_moving_text)).setText(Algorithms.formatDuration((int) (timeMoving / 1000), app.accessibilityEnabled())); - ((TextView) view.findViewById(R.id.distance_text)).setText(OsmAndFormatter.getFormattedDistance(totalDistanceMoving, app)); - } - } - } - } - - void updateChart(LineChart chart) { - if (chart != null && !chart.isEmpty()) { - if (gpxItem.chartMatrix != null) { - chart.getViewPortHandler().refresh(new Matrix(gpxItem.chartMatrix), chart, true); - } - if (gpxItem.chartHighlightPos != -1) { - chart.highlightValue(gpxItem.chartHighlightPos, 0); - } else { - chart.highlightValue(null); - } - } - } - - private TrkSegment getTrkSegment() { - for (Track t : gpxItem.group.getGpx().tracks) { - if (!t.generalTrack && !gpxItem.isGeneralTrack() || t.generalTrack && gpxItem.isGeneralTrack()) { + if (gpxItem.chartHighlightPos != -1) { + TrkSegment segment = null; + for (Track t : gpxItem.group.getGpx().tracks) { for (TrkSegment s : t.segments) { if (s.points.size() > 0 && s.points.get(0).equals(gpxItem.analysis.locationStart)) { - return s; - } - } - } - } - return null; - } - - void openAnalyzeOnMap(GPXTabItemType tabType) { - LatLon location = null; - WptPt wpt = null; - gpxItem.chartTypes = null; - List ds = getDataSets(null, tabType, null, null); - if (ds != null && ds.size() > 0) { - gpxItem.chartTypes = new GPXDataSetType[ds.size()]; - for (int i = 0; i < ds.size(); i++) { - OrderedLineDataSet orderedDataSet = (OrderedLineDataSet) ds.get(i); - gpxItem.chartTypes[i] = orderedDataSet.getDataSetType(); - } - if (gpxItem.chartHighlightPos != -1) { - TrkSegment segment = null; - for (Track t : gpxItem.group.getGpx().tracks) { - for (TrkSegment s : t.segments) { - if (s.points.size() > 0 && s.points.get(0).equals(gpxItem.analysis.locationStart)) { - segment = s; - break; - } - } - if (segment != null) { + segment = s; break; } } if (segment != null) { - OrderedLineDataSet dataSet = (OrderedLineDataSet) ds.get(0); - float distance = gpxItem.chartHighlightPos * dataSet.getDivX(); - for (WptPt p : segment.points) { - if (p.distance >= distance) { - wpt = p; - break; - } - } - if (wpt != null) { - location = new LatLon(wpt.lat, wpt.lon); + break; + } + } + if (segment != null) { + OrderedLineDataSet dataSet = (OrderedLineDataSet) dataSets.get(0); + float distance = gpxItem.chartHighlightPos * dataSet.getDivX(); + for (WptPt p : segment.points) { + if (p.distance >= distance) { + wpt = p; + break; } } + if (wpt != null) { + location = new LatLon(wpt.lat, wpt.lon); + } } } - if (location == null) { - location = new LatLon(gpxItem.locationStart.lat, gpxItem.locationStart.lon); - } - if (wpt != null) { - gpxItem.locationOnMap = wpt; - } else { - gpxItem.locationOnMap = gpxItem.locationStart; - } - - final OsmandSettings settings = app.getSettings(); - settings.setMapLocationToShow(location.getLatitude(), location.getLongitude(), - settings.getLastKnownMapZoom(), - new PointDescription(PointDescription.POINT_TYPE_WPT, gpxItem.name), - false, - gpxItem); - - MapActivity.launchMapActivityMoveToTop(getActivity()); + } + if (location == null) { + location = new LatLon(gpxItem.locationStart.lat, gpxItem.locationStart.lon); + } + if (wpt != null) { + gpxItem.locationOnMap = wpt; + } else { + gpxItem.locationOnMap = gpxItem.locationStart; } - private void openSplitIntervalScreen() { - TrackActivity activity = getTrackActivity(); - if (activity != null) { - SplitSegmentDialogFragment.showInstance(activity, gpxItem, getTrkSegment()); + OsmandSettings settings = app.getSettings(); + settings.setMapLocationToShow(location.getLatitude(), location.getLongitude(), + settings.getLastKnownMapZoom(), + new PointDescription(PointDescription.POINT_TYPE_WPT, gpxItem.name), + false, + gpxItem); + + MapActivity.launchMapActivityMoveToTop(getActivity()); + } + + @Override + public void showOptionsPopupMenu(View view, final TrkSegment segment, final boolean confirmDeletion) { + FragmentActivity activity = getActivity(); + if (activity != null) { + optionsPopupMenu = new IconPopupMenu(activity, view.findViewById(R.id.overflow_menu)); + Menu menu = optionsPopupMenu.getMenu(); + optionsPopupMenu.getMenuInflater().inflate(R.menu.track_segment_menu, menu); + menu.findItem(R.id.action_edit).setIcon(app.getUIUtilities().getThemedIcon(R.drawable.ic_action_edit_dark)); + menu.findItem(R.id.action_delete).setIcon(app.getUIUtilities().getThemedIcon(R.drawable.ic_action_remove_dark)); + optionsPopupMenu.setOnMenuItemClickListener(new IconPopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + int i = item.getItemId(); + if (i == R.id.action_edit) { + editSegment(segment); + return true; + } else if (i == R.id.action_delete) { + FragmentActivity activity = getActivity(); + if (!confirmDeletion) { + deleteAndSaveSegment(segment); + } else if (activity != null) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setMessage(R.string.recording_delete_confirm); + builder.setPositiveButton(R.string.shared_string_yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteAndSaveSegment(segment); + } + }); + builder.setNegativeButton(R.string.shared_string_cancel, null); + builder.show(); + } + return true; + } + return false; + } + }); + optionsPopupMenu.show(); + } + } + + private void editSegment(TrkSegment segment) { + if (segment != null && fragmentAdapter != null) { + fragmentAdapter.addNewGpxData(); + } + } + + private void deleteAndSaveSegment(TrkSegment segment) { + TrackActivity trackActivity = getTrackActivity(); + if (trackActivity != null && deleteSegment(segment)) { + GPXFile gpx = displayHelper.getGpx(); + if (gpx != null && fragmentAdapter != null) { + boolean showOnMap = fragmentAdapter.isShowOnMap(); + SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().selectGpxFile(gpx, showOnMap, false); + saveGpx(showOnMap ? selectedGpxFile : null, gpx); } } } + private boolean deleteSegment(TrkSegment segment) { + if (segment != null) { + GPXFile gpx = displayHelper.getGpx(); + if (gpx != null) { + return gpx.removeTrkSegment(segment); + } + } + return false; + } + private void saveGpx(final SelectedGpxFile selectedGpxFile, GPXFile gpxFile) { new SaveGpxAsyncTask(new File(gpxFile.path), gpxFile, new SaveGpxListener() { @Override @@ -1210,7 +401,7 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit TrackActivity activity = getTrackActivity(); if (activity != null) { if (selectedGpxFile != null) { - List groups = getDisplayGroups(); + List groups = displayHelper.getDisplayGroups(filterTypes); if (groups != null) { selectedGpxFile.setDisplayGroups(groups, app); selectedGpxFile.processPoints(app); diff --git a/OsmAnd/src/net/osmand/plus/track/SegmentsCard.java b/OsmAnd/src/net/osmand/plus/track/SegmentsCard.java new file mode 100644 index 0000000000..3a6fce7271 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/track/SegmentsCard.java @@ -0,0 +1,57 @@ +package net.osmand.plus.track; + +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; + +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType; +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.myplaces.GPXItemPagerAdapter; +import net.osmand.plus.myplaces.SegmentActionsListener; +import net.osmand.plus.myplaces.SegmentGPXAdapter; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; +import net.osmand.plus.views.controls.PagerSlidingTabStrip; +import net.osmand.plus.views.controls.WrapContentHeightViewPager; + +import java.util.List; + +public class SegmentsCard extends BaseCard { + + private TrackDisplayHelper displayHelper; + private GpxDisplayItemType[] filterTypes = new GpxDisplayItemType[] {GpxDisplayItemType.TRACK_SEGMENT}; + private SegmentGPXAdapter adapter; + private SegmentActionsListener listener; + + public SegmentsCard(@NonNull MapActivity mapActivity, @NonNull TrackDisplayHelper displayHelper, + @NonNull SegmentActionsListener listener) { + super(mapActivity); + this.displayHelper = displayHelper; + this.listener = listener; + } + + @Override + public int getCardLayoutId() { + return R.layout.track_segments_container; + } + + @Override + protected void updateContent() { + ViewGroup container = (ViewGroup) view; + container.removeAllViews(); + List items = TrackDisplayHelper.flatten(displayHelper.getOriginalGroups(filterTypes)); + for (GpxDisplayItem displayItem : items) { + View segmentView = SegmentGPXAdapter.createGpxTabsView(displayHelper, container, listener, nightMode); + + WrapContentHeightViewPager pager = segmentView.findViewById(R.id.pager); + PagerSlidingTabStrip tabLayout = segmentView.findViewById(R.id.sliding_tabs); + + pager.setAdapter(new GPXItemPagerAdapter(tabLayout, displayItem, displayHelper, listener)); + tabLayout.setViewPager(pager); + + container.addView(segmentView); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/track/TrackDisplayHelper.java b/OsmAnd/src/net/osmand/plus/track/TrackDisplayHelper.java new file mode 100644 index 0000000000..5ab0a6740f --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/track/TrackDisplayHelper.java @@ -0,0 +1,179 @@ +package net.osmand.plus.track; + +import android.app.Activity; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.GPXUtilities.GPXFile; +import net.osmand.GPXUtilities.WptPt; +import net.osmand.data.LatLon; +import net.osmand.data.PointDescription; +import net.osmand.data.QuadRect; +import net.osmand.plus.GPXDatabase.GpxDataItem; +import net.osmand.plus.GpxSelectionHelper; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType; +import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.measurementtool.GpxData; +import net.osmand.plus.settings.backend.OsmandSettings; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class TrackDisplayHelper { + + private final OsmandApplication app; + + private File file; + private GPXFile gpxFile; + private GpxDataItem gpxDataItem; + + private long modifiedTime = -1; + private List displayGroups; + private List originalGroups = new ArrayList<>(); + + public TrackDisplayHelper(OsmandApplication app) { + this.app = app; + } + + @Nullable + public GPXFile getGpx() { + return gpxFile; + } + + @Nullable + public GpxDataItem getGpxDataItem() { + return gpxDataItem; + } + + public File getFile() { + return file; + } + + public void setFile(@Nullable File file) { + this.file = file; + } + + public void setGpx(@NonNull GPXFile result) { + this.gpxFile = result; + if (file == null) { + this.gpxFile = app.getSavingTrackHelper().getCurrentGpx(); + } + } + + public void setGpxDataItem(GpxDataItem gpxDataItem) { + this.gpxDataItem = gpxDataItem; + } + + public QuadRect getRect() { + if (getGpx() != null) { + return getGpx().getRect(); + } else { + return new QuadRect(0, 0, 0, 0); + } + } + + public boolean setJoinSegments(boolean joinSegments) { + if (gpxDataItem != null) { + boolean updated = app.getGpxDbHelper().updateJoinSegments(gpxDataItem, joinSegments); + + SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(gpxFile.path); + if (updated && selectedGpxFile != null) { + selectedGpxFile.setJoinSegments(joinSegments); + } + return updated; + } + return false; + } + + public boolean isJoinSegments() { + return gpxDataItem != null && gpxDataItem.isJoinSegments(); + } + + public List getGpxFile(boolean useDisplayGroups) { + if (gpxFile == null) { + return new ArrayList<>(); + } + if (gpxFile.modifiedTime != modifiedTime) { + modifiedTime = gpxFile.modifiedTime; + GpxSelectionHelper selectedGpxHelper = app.getSelectedGpxHelper(); + displayGroups = selectedGpxHelper.collectDisplayGroups(gpxFile); + originalGroups.clear(); + for (GpxSelectionHelper.GpxDisplayGroup g : displayGroups) { + originalGroups.add(g.cloneInstance()); + } + if (file != null) { + SelectedGpxFile sf = selectedGpxHelper.getSelectedFileByPath(gpxFile.path); + if (sf != null && file != null && sf.getDisplayGroups(app) != null) { + displayGroups = sf.getDisplayGroups(app); + } + } + } + if (useDisplayGroups) { + return displayGroups; + } else { + return originalGroups; + } + } + + @NonNull + public List getOriginalGroups(GpxDisplayItemType[] filterTypes) { + return filterGroups(false, filterTypes); + } + + @NonNull + public List getDisplayGroups(GpxDisplayItemType[] filterTypes) { + return filterGroups(true, filterTypes); + } + + private boolean hasFilterType(GpxDisplayItemType filterType, GpxDisplayItemType[] filterTypes) { + for (GpxDisplayItemType type : filterTypes) { + if (type == filterType) { + return true; + } + } + return false; + } + + @NonNull + private List filterGroups(boolean useDisplayGroups, GpxDisplayItemType[] filterTypes) { + List groups = new ArrayList<>(); + for (GpxDisplayGroup group : getGpxFile(useDisplayGroups)) { + if (hasFilterType(group.getType(), filterTypes)) { + groups.add(group); + } + } + return groups; + } + + public static List flatten(List groups) { + ArrayList list = new ArrayList<>(); + for (GpxDisplayGroup g : groups) { + list.addAll(g.getModifiableList()); + } + return list; + } + + public void addNewGpxData(Activity activity) { + GPXFile gpxFile = getGpx(); + GpxData gpxData = new GpxData(gpxFile); + WptPt pointToShow = gpxFile != null ? gpxFile.findPointToShow() : null; + if (pointToShow != null) { + LatLon location = new LatLon(pointToShow.getLatitude(), pointToShow.getLongitude()); + final OsmandSettings settings = app.getSettings(); + settings.setMapLocationToShow(location.getLatitude(), location.getLongitude(), + settings.getLastKnownMapZoom(), + new PointDescription(PointDescription.POINT_TYPE_WPT, activity.getString(R.string.add_line)), + false, + gpxData + ); + MapActivity.launchMapActivityMoveToTop(activity); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/track/TrackMenuFragment.java b/OsmAnd/src/net/osmand/plus/track/TrackMenuFragment.java new file mode 100644 index 0000000000..595c456858 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/track/TrackMenuFragment.java @@ -0,0 +1,596 @@ +package net.osmand.plus.track; + +import android.content.DialogInterface; +import android.content.res.ColorStateList; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +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.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; +import com.google.android.material.bottomnavigation.BottomNavigationView; + +import net.osmand.AndroidUtils; +import net.osmand.GPXUtilities.GPXFile; +import net.osmand.GPXUtilities.Track; +import net.osmand.GPXUtilities.TrkSegment; +import net.osmand.GPXUtilities.WptPt; +import net.osmand.PlatformUtil; +import net.osmand.data.QuadRect; +import net.osmand.data.RotatedTileBox; +import net.osmand.plus.GPXDatabase.GpxDataItem; +import net.osmand.plus.GpxDbHelper; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType; +import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.ContextMenuFragment; +import net.osmand.plus.base.ContextMenuScrollFragment; +import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.helpers.GpxUiHelper; +import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType; +import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet; +import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu; +import net.osmand.plus.measurementtool.GpxData; +import net.osmand.plus.measurementtool.MeasurementEditingContext; +import net.osmand.plus.measurementtool.MeasurementToolFragment; +import net.osmand.plus.myplaces.GPXTabItemType; +import net.osmand.plus.myplaces.SegmentActionsListener; +import net.osmand.plus.myplaces.SplitSegmentDialogFragment; +import net.osmand.plus.myplaces.TrackActivityFragmentAdapter; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; +import net.osmand.plus.routepreparationmenu.cards.BaseCard.CardListener; +import net.osmand.plus.track.SaveGpxAsyncTask.SaveGpxListener; +import net.osmand.plus.widgets.IconPopupMenu; +import net.osmand.util.Algorithms; + +import org.apache.commons.logging.Log; + +import java.io.File; +import java.util.List; + +import static net.osmand.plus.activities.TrackActivity.CURRENT_RECORDING; +import static net.osmand.plus.activities.TrackActivity.TRACK_FILE_NAME; + +public class TrackMenuFragment extends ContextMenuScrollFragment implements CardListener, SegmentActionsListener { + + public static final String TAG = TrackMenuFragment.class.getName(); + private static final Log log = PlatformUtil.getLog(TrackMenuFragment.class); + + private OsmandApplication app; + private TrackDisplayHelper displayHelper; + + private GpxDataItem gpxDataItem; + private SelectedGpxFile selectedGpxFile; + + private View routeMenuTopShadowAll; + private BottomNavigationView bottomNav; + private TrackMenuType menuType = TrackMenuType.TRACK; + private SegmentsCard segmentsCard; + + private int menuTitleHeight; + + public enum TrackMenuType { + TRACK(R.id.action_track, R.string.shared_string_gpx_tracks), + POINTS(R.id.action_points, R.string.shared_string_gpx_points); + + TrackMenuType(@DrawableRes int iconId, @StringRes int titleId) { + this.iconId = iconId; + this.titleId = titleId; + } + + public final int iconId; + public final int titleId; + } + + @Override + public int getMainLayoutId() { + return R.layout.track_menu; + } + + @Override + public int getHeaderViewHeight() { + return menuTitleHeight; + } + + @Override + public boolean isHeaderViewDetached() { + return false; + } + + @Override + public int getToolbarHeight() { + return 0; + } + + public float getMiddleStateKoef() { + return 0.5f; + } + + @Override + public int getSupportedMenuStatesPortrait() { + return MenuState.HEADER_ONLY | MenuState.HALF_SCREEN | MenuState.FULL_SCREEN; + } + + public TrackDisplayHelper getDisplayHelper() { + return displayHelper; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + app = requireMyApplication(); + GpxDbHelper gpxDbHelper = app.getGpxDbHelper(); + displayHelper = new TrackDisplayHelper(app); + + String gpxFilePath = ""; + boolean currentRecording = false; + Bundle arguments = getArguments(); + if (savedInstanceState != null) { + gpxFilePath = savedInstanceState.getString(TRACK_FILE_NAME); + currentRecording = savedInstanceState.getBoolean(CURRENT_RECORDING, false); + } else if (arguments != null) { + gpxFilePath = arguments.getString(TRACK_FILE_NAME); + currentRecording = arguments.getBoolean(CURRENT_RECORDING, false); + } + if (currentRecording) { + selectedGpxFile = app.getSavingTrackHelper().getCurrentTrack(); + } else { + File file = new File(gpxFilePath); + displayHelper.setFile(file); + gpxDataItem = gpxDbHelper.getItem(file); + selectedGpxFile = app.getSelectedGpxHelper().getSelectedFileByPath(gpxFilePath); + } + displayHelper.setGpxDataItem(gpxDataItem); + displayHelper.setGpx(selectedGpxFile.getGpxFile()); + } + + public GPXFile getGpx() { + return displayHelper.getGpx(); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + if (view != null) { + bottomNav = view.findViewById(R.id.bottom_navigation); + routeMenuTopShadowAll = view.findViewById(R.id.route_menu_top_shadow_all); + TextView title = view.findViewById(R.id.title); + String fileName = Algorithms.getFileWithoutDirs(getGpx().path); + title.setText(GpxUiHelper.getGpxTitle(fileName)); + + if (isPortrait()) { + updateCardsLayout(); + } + setupCards(); + setupButtons(view); + enterTrackAppearanceMode(); + runLayoutListener(); + } + return view; + } + + private void setupCards() { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + ViewGroup cardsContainer = getCardsContainer(); + cardsContainer.removeAllViews(); + if (menuType == TrackMenuType.TRACK) { + if (segmentsCard != null) { + cardsContainer.addView(segmentsCard.getView()); + } else { + segmentsCard = new SegmentsCard(mapActivity, displayHelper, this); + cardsContainer.addView(segmentsCard.build(mapActivity)); + } + } + } + } + + @Override + protected void calculateLayout(View view, boolean initLayout) { + menuTitleHeight = routeMenuTopShadowAll.getHeight() + + bottomNav.getHeight(); + super.calculateLayout(view, initLayout); + } + + @Override + protected void setViewY(int y, boolean animated, boolean adjustMapPos) { + super.setViewY(y, animated, adjustMapPos); + updateStatusBarColor(); + } + + @Override + protected void updateMainViewLayout(int posY) { + super.updateMainViewLayout(posY); + updateStatusBarColor(); + } + + @Override + public boolean shouldShowMapControls(int menuState) { + return menuState == MenuState.HEADER_ONLY || menuState == MenuState.HALF_SCREEN; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + adjustMapPosition(getHeight()); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + exitTrackAppearanceMode(); + } + + private void enterTrackAppearanceMode() { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + boolean portrait = AndroidUiHelper.isOrientationPortrait(mapActivity); + AndroidUiHelper.setVisibility(mapActivity, portrait ? View.INVISIBLE : View.GONE, + R.id.map_left_widgets_panel, + R.id.map_right_widgets_panel, + R.id.map_center_info); + } + } + + private void exitTrackAppearanceMode() { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + AndroidUiHelper.setVisibility(mapActivity, View.VISIBLE, + R.id.map_left_widgets_panel, + R.id.map_right_widgets_panel, + R.id.map_center_info, + R.id.map_search_button); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putString(TRACK_FILE_NAME, getGpx().path); + outState.putBoolean(CURRENT_RECORDING, selectedGpxFile.isShowCurrentTrack()); + super.onSaveInstanceState(outState); + } + + @Override + public int getStatusBarColorId() { + View view = getView(); + if (view != null) { + boolean nightMode = isNightMode(); + if (getViewY() <= getFullScreenTopPosY() || !isPortrait()) { + if (Build.VERSION.SDK_INT >= 23 && !nightMode) { + view.setSystemUiVisibility(view.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + return nightMode ? R.color.divider_color_dark : R.color.divider_color_light; + } else { + if (Build.VERSION.SDK_INT >= 23 && !nightMode) { + view.setSystemUiVisibility(view.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + } + } + return -1; + } + + private void updateStatusBarColor() { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + mapActivity.updateStatusBarColor(); + } + } + + @Override + protected String getThemeInfoProviderTag() { + return TAG; + } + + @Override + public void onCardLayoutNeeded(@NonNull BaseCard card) { + + } + + @Override + public void onCardPressed(@NonNull BaseCard card) { + + } + + @Override + public void onCardButtonPressed(@NonNull BaseCard card, int buttonIndex) { + + } + + @Override + protected int applyPosY(int currentY, boolean needCloseMenu, boolean needMapAdjust, int previousMenuState, int newMenuState, int dZoom, boolean animated) { + int y = super.applyPosY(currentY, needCloseMenu, needMapAdjust, previousMenuState, newMenuState, dZoom, animated); + if (needMapAdjust) { + adjustMapPosition(y); + } + return y; + } + + @Override + protected void onHeaderClick() { + adjustMapPosition(getViewY()); + } + + private void adjustMapPosition(int y) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null && mapActivity.getMapView() != null) { + GPXFile gpxFile = getGpx(); + QuadRect r = gpxFile.getRect(); + + RotatedTileBox tb = mapActivity.getMapView().getCurrentRotatedTileBox().copy(); + int tileBoxWidthPx = 0; + int tileBoxHeightPx = 0; + + if (!isPortrait()) { + tileBoxWidthPx = tb.getPixWidth() - getWidth(); + } else { + int fHeight = getViewHeight() - y - AndroidUtils.getStatusBarHeight(mapActivity); + tileBoxHeightPx = tb.getPixHeight() - fHeight; + } + if (r.left != 0 && r.right != 0) { + mapActivity.getMapView().fitRectToMap(r.left, r.right, r.top, r.bottom, tileBoxWidthPx, tileBoxHeightPx, 0); + } + } + } + + private void updateCardsLayout() { + View mainView = getMainView(); + if (mainView != null) { + View topShadow = getTopShadow(); + FrameLayout bottomContainer = getBottomContainer(); + if (getCurrentMenuState() == MenuState.HEADER_ONLY) { + topShadow.setVisibility(View.INVISIBLE); + bottomContainer.setBackgroundDrawable(null); + } else { + topShadow.setVisibility(View.VISIBLE); + AndroidUtils.setBackground(mainView.getContext(), bottomContainer, isNightMode(), R.color.list_background_color_light, R.color.list_background_color_dark); + } + } + } + + private void setupButtons(View view) { + ColorStateList navColorStateList = AndroidUtils.createBottomNavColorStateList(getContext(), isNightMode()); + BottomNavigationView bottomNav = view.findViewById(R.id.bottom_navigation); + bottomNav.setItemIconTintList(navColorStateList); + bottomNav.setItemTextColor(navColorStateList); + bottomNav.setSelectedItemId(R.id.action_track); + bottomNav.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + for (TrackMenuType type : TrackMenuType.values()) { + if (type.iconId == item.getItemId()) { + menuType = type; + setupCards(); + break; + } + } + return true; + } + }); + } + + @Override + public void updateContent() { + if (segmentsCard != null) { + segmentsCard.updateContent(); + } + setupCards(); + } + + @Override + public void onChartTouch() { + getBottomScrollView().requestDisallowInterceptTouchEvent(true); + } + + @Override + public void scrollBy(int px) { + + } + + @Override + public void onPointSelected(double lat, double lon) { + + } + + @Override + public void openSplitInterval(GpxDisplayItem gpxItem, TrkSegment trkSegment) { + FragmentManager fragmentManager = getFragmentManager(); + if (fragmentManager != null) { + SplitSegmentDialogFragment.showInstance(fragmentManager, displayHelper, gpxItem, trkSegment); + } + } + + @Override + public void openAnalyzeOnMap(GpxDisplayItem gpxItem, List dataSets, GPXTabItemType tabType) { + WptPt wpt = null; + gpxItem.chartTypes = null; + if (dataSets != null && dataSets.size() > 0) { + gpxItem.chartTypes = new GPXDataSetType[dataSets.size()]; + for (int i = 0; i < dataSets.size(); i++) { + OrderedLineDataSet orderedDataSet = (OrderedLineDataSet) dataSets.get(i); + gpxItem.chartTypes[i] = orderedDataSet.getDataSetType(); + } + if (gpxItem.chartHighlightPos != -1) { + TrkSegment segment = null; + for (Track t : gpxItem.group.getGpx().tracks) { + for (TrkSegment s : t.segments) { + if (s.points.size() > 0 && s.points.get(0).equals(gpxItem.analysis.locationStart)) { + segment = s; + break; + } + } + if (segment != null) { + break; + } + } + if (segment != null) { + OrderedLineDataSet dataSet = (OrderedLineDataSet) dataSets.get(0); + float distance = gpxItem.chartHighlightPos * dataSet.getDivX(); + for (WptPt p : segment.points) { + if (p.distance >= distance) { + wpt = p; + break; + } + } + } + } + } + if (wpt != null) { + gpxItem.locationOnMap = wpt; + } else { + gpxItem.locationOnMap = gpxItem.locationStart; + } + + TrackDetailsMenu trackDetailsMenu = getMapActivity().getTrackDetailsMenu(); + trackDetailsMenu.setGpxItem(gpxItem); + trackDetailsMenu.show(); + close(); + } + + @Override + public void showOptionsPopupMenu(View view, final TrkSegment segment, final boolean confirmDeletion) { + FragmentActivity activity = getActivity(); + if (activity != null) { + IconPopupMenu optionsPopupMenu = new IconPopupMenu(activity, view.findViewById(R.id.overflow_menu)); + Menu menu = optionsPopupMenu.getMenu(); + optionsPopupMenu.getMenuInflater().inflate(R.menu.track_segment_menu, menu); + menu.findItem(R.id.action_edit).setIcon(app.getUIUtilities().getThemedIcon(R.drawable.ic_action_edit_dark)); + menu.findItem(R.id.action_delete).setIcon(app.getUIUtilities().getThemedIcon(R.drawable.ic_action_remove_dark)); + optionsPopupMenu.setOnMenuItemClickListener(new IconPopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + int i = item.getItemId(); + if (i == R.id.action_edit) { + editSegment(segment); + return true; + } else if (i == R.id.action_delete) { + FragmentActivity activity = getActivity(); + if (!confirmDeletion) { + deleteAndSaveSegment(segment); + } else if (activity != null) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setMessage(R.string.recording_delete_confirm); + builder.setPositiveButton(R.string.shared_string_yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteAndSaveSegment(segment); + } + }); + builder.setNegativeButton(R.string.shared_string_cancel, null); + builder.show(); + } + return true; + } + return false; + } + }); + optionsPopupMenu.show(); + } + } + + private void editSegment(TrkSegment segment) { + GPXFile gpxFile = getGpx(); + openPlanRoute(new GpxData(gpxFile)); + close(); + } + + public void openPlanRoute(GpxData gpxData) { + QuadRect qr = gpxData.getRect(); + getMapActivity().getMapView().fitRectToMap(qr.left, qr.right, qr.top, qr.bottom, (int) qr.width(), (int) qr.height(), 0); + MeasurementEditingContext editingContext = new MeasurementEditingContext(); + editingContext.setGpxData(gpxData); + MeasurementToolFragment.showInstance(getFragmentManager(), editingContext); + } + + private void deleteAndSaveSegment(TrkSegment segment) { + if (deleteSegment(segment)) { + GPXFile gpx = displayHelper.getGpx(); + if (gpx != null) { + boolean showOnMap = TrackActivityFragmentAdapter.isGpxFileSelected(app, gpx); + SelectedGpxFile selectedGpxFile = app.getSelectedGpxHelper().selectGpxFile(gpx, showOnMap, false); + saveGpx(showOnMap ? selectedGpxFile : null, gpx); + } + } + } + + private boolean deleteSegment(TrkSegment segment) { + if (segment != null) { + GPXFile gpx = displayHelper.getGpx(); + if (gpx != null) { + return gpx.removeTrkSegment(segment); + } + } + return false; + } + + private void saveGpx(final SelectedGpxFile selectedGpxFile, GPXFile gpxFile) { + new SaveGpxAsyncTask(new File(gpxFile.path), gpxFile, new SaveGpxListener() { + @Override + public void gpxSavingStarted() { + + } + + @Override + public void gpxSavingFinished(Exception errorMessage) { + if (selectedGpxFile != null) { + List groups = displayHelper.getDisplayGroups(new GpxDisplayItemType[] {GpxDisplayItemType.TRACK_SEGMENT}); + if (groups != null) { + selectedGpxFile.setDisplayGroups(groups, app); + selectedGpxFile.processPoints(app); + } + } + updateContent(); + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void close() { + try { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + FragmentManager fragmentManager = mapActivity.getSupportFragmentManager(); + fragmentManager.beginTransaction().remove(this).commitAllowingStateLoss(); + } + } catch (Exception e) { + log.error(e); + } + } + + public static boolean showInstance(@NonNull MapActivity mapActivity, String path, boolean showCurrentTrack) { + try { + Bundle args = new Bundle(); + args.putString(TRACK_FILE_NAME, path); + args.putBoolean(CURRENT_RECORDING, showCurrentTrack); + args.putInt(ContextMenuFragment.MENU_STATE_KEY, MenuState.HALF_SCREEN); + + TrackMenuFragment fragment = new TrackMenuFragment(); + fragment.setArguments(args); + + mapActivity.getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragmentContainer, fragment, fragment.getFragmentTag()) + .addToBackStack(fragment.getFragmentTag()) + .commitAllowingStateLoss(); + return true; + } catch (RuntimeException e) { + return false; + } + } +} \ No newline at end of file From ad11fee0c3a693ba2f93100d6e7fbc0a65bc3ea0 Mon Sep 17 00:00:00 2001 From: androiddevkkotlin Date: Tue, 29 Dec 2020 18:39:12 +0200 Subject: [PATCH 03/28] After add to track: show "track is saved" bottom sheet. --- .../osmand/plus/measurementtool/MeasurementToolFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java index fe12a9212b..6fb7aa7cab 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolFragment.java @@ -1211,7 +1211,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route SelectedGpxFile selectedGpxFile = mapActivity.getMyApplication().getSelectedGpxHelper() .getSelectedFileByPath(gpxFile.path); boolean showOnMap = selectedGpxFile != null; - saveExistingGpx(gpxFile, showOnMap, false, true, FinalSaveAction.SHOW_TOAST); + saveExistingGpx(gpxFile, showOnMap, false, true, FinalSaveAction.SHOW_IS_SAVED_FRAGMENT); } } From d3d79b25aaf99d4c753fec4f6d706dcd6b49f422 Mon Sep 17 00:00:00 2001 From: xmd5a Date: Tue, 29 Dec 2020 22:03:31 +0300 Subject: [PATCH 04/28] Add synonyms --- OsmAnd/res/values/phrases.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index 28c17c4a5d..c69f7860f3 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -384,7 +384,7 @@ Electricity Aircraft fuel station Gas station for boats - Charging station + Charging station;Electric vehicle charging station;EV charging station;Electric recharging point;Charge point;Electronic charging station;Electric vehicle supply equipment Fuel: wood Fuel: charcoal Fuel: coal @@ -4326,5 +4326,8 @@ Lavoir + Swimming area + + From c1ee9dec982ce6aafce9b836a3c3aa83cfcb10ce Mon Sep 17 00:00:00 2001 From: Tobias Henricsson Date: Tue, 29 Dec 2020 19:25:43 +0000 Subject: [PATCH 05/28] Translated using Weblate (Swedish) Currently translated at 60.7% (2181 of 3590 strings) --- OsmAnd/res/values-sv/strings.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/OsmAnd/res/values-sv/strings.xml b/OsmAnd/res/values-sv/strings.xml index c6cd3aca26..80d752ea56 100644 --- a/OsmAnd/res/values-sv/strings.xml +++ b/OsmAnd/res/values-sv/strings.xml @@ -2915,4 +2915,22 @@ Vänligen tillhandahåll fullständig kod Språk Språk Alla språk + • La till inställning för att exportera och importera all data, inklusive inställningar, resurser, mina platser +\n +\n • Planera rutt: Grafer för seggraphs for track segments with route, and added the ability to create and edit multiple track segments +\n +\n • Added OAuth authentication method for OpenStreetMap, improved UI of OSM dialogs +\n +\n • Support custom colors for favorites and track waypoints +\n +\n + Vänd alla punkter + Välj den profil som ska användas när appen startas. + Senast använd + Föredra vandringsleder + Föredra vandringsleder + Tillåt strömmar och torrläggningar + Tillåt strömmar och torrläggningar + Tillåt vägar över vatten + Tillåt vägar över vatten \ No newline at end of file From 32e55e62af80ad3e4605f97a954e819b6ac0e616 Mon Sep 17 00:00:00 2001 From: "J. Lavoie" Date: Mon, 28 Dec 2020 20:36:05 +0000 Subject: [PATCH 06/28] Translated using Weblate (French) Currently translated at 100.0% (3590 of 3590 strings) --- OsmAnd/res/values-fr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-fr/strings.xml b/OsmAnd/res/values-fr/strings.xml index b52ca23ec8..3cd132f82a 100644 --- a/OsmAnd/res/values-fr/strings.xml +++ b/OsmAnd/res/values-fr/strings.xml @@ -4004,4 +4004,6 @@ Pirivilégier les itinéraires de randonnée Autoriser les voies navigables intermittentes Autoriser les voies navigables intermittentes + Autoriser les cours d’eau et les drains + Autoriser les cours d’eau et les drains \ No newline at end of file From 599c99023a0fa7ae53537cf35075859deab5da23 Mon Sep 17 00:00:00 2001 From: Artem Date: Tue, 29 Dec 2020 14:45:42 +0000 Subject: [PATCH 07/28] Translated using Weblate (Russian) Currently translated at 99.6% (3576 of 3590 strings) --- OsmAnd/res/values-ru/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml index c7dbab21d9..59bc5514c9 100644 --- a/OsmAnd/res/values-ru/strings.xml +++ b/OsmAnd/res/values-ru/strings.xml @@ -4001,4 +4001,8 @@ Разделить после Профиль пользователя Профиль OsmAnd + Выберите профиль, который будет использоваться при запуске приложения. + Последний раз использовалось + Разрешить прерывистые водные пути + Разрешить прерывистые водные пути \ No newline at end of file From c9a36c274e3a0e8f17bd50c5c7fa0105fcbaca76 Mon Sep 17 00:00:00 2001 From: WaldiS Date: Mon, 28 Dec 2020 17:16:43 +0000 Subject: [PATCH 08/28] Translated using Weblate (Polish) Currently translated at 99.6% (3577 of 3590 strings) --- OsmAnd/res/values-pl/strings.xml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/OsmAnd/res/values-pl/strings.xml b/OsmAnd/res/values-pl/strings.xml index c6616b7b1e..2fcf1a61c4 100644 --- a/OsmAnd/res/values-pl/strings.xml +++ b/OsmAnd/res/values-pl/strings.xml @@ -3793,7 +3793,7 @@ Podaj nazwę punktu Usuwa następny cel na trasie. Jeśli jest to miejsce docelowe, nawigacja zostanie zatrzymana. Pobierz mapy Wikipedii - Uzyskaj informacje o ciekawych miejscach z Wikipedii, kieszonkowego przewodnika offline zawierającego artykuły o miejscach i celach. + Uzyskaj informacje o ciekawych miejscach z Wikipedii, kieszonkowego przewodnika offline zawierającego artykuły o miejscach i celach podróży. Motocykl Enduro Skuter Określ długość pojazdu dozwoloną na trasach. @@ -3913,11 +3913,11 @@ Ikony startu i końca Dziękujemy za zakup \"Linii konturowych\" Subskrypcja jest naliczana za wybrany okres. Anuluj go w AppGallery w dowolnym momencie. - Płatność zostanie pobrana z konta AppGallery po potwierdzeniu zakupu. + Twoje konto AppGallery jest obciążane po potwierdzeniu zakupu. \n -\nSubskrypcja jest automatycznie odnawiana, chyba że zostanie anulowana przed datą odnowienia. Twoje konto zostanie obciążone opłatą za okres odnowienia (miesiąc/trzy miesiące/rok) tylko w dniu odnowienia. +\nSubskrypcja przedłuża się automatycznie, chyba że zostanie anulowana przed datą odnowienia. Twoje konto zostanie obciążone za okres odnowienia (miesiąc/trzy miesiące/rok) tylko w dniu odnowienia. \n -\nMożesz zarządzać subskrypcjami i anulować je, przechodząc do ustawień Galerii aplikacji. +\nMożesz zarządzać swoimi subskrypcjami i anulować je w ustawieniach AppGallery. Unikaj chodników Unikaj chodników Rozwój @@ -4024,4 +4024,12 @@ Profil OsmAnd Profil użytkownika Odwróć wszystkie punkty + Wybierz profil, który będzie używany przy uruchomieniu aplikacji. + Ostatnio używane + Preferowane szlaki turystyczne + Preferuj szlaki turystyczne + Zezwalaj na strumienie i dreny + Zezwalaj na strumienie i dreny + Zezwalaj na przerywane drogi wodne + Zezwalaj na przerywane drogi wodne \ No newline at end of file From 54c0270cbee37150eb6c939d9e6dbdaa4a6ce260 Mon Sep 17 00:00:00 2001 From: abdullah abdulrhman Date: Tue, 29 Dec 2020 08:24:38 +0000 Subject: [PATCH 09/28] Translated using Weblate (Arabic) Currently translated at 92.7% (3597 of 3877 strings) --- OsmAnd/res/values-ar/phrases.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-ar/phrases.xml b/OsmAnd/res/values-ar/phrases.xml index da2bfb09a0..19039d722a 100644 --- a/OsmAnd/res/values-ar/phrases.xml +++ b/OsmAnd/res/values-ar/phrases.xml @@ -2374,7 +2374,7 @@ نوع الكبينة/الخزانة: خدمة بريدية نوع الكبينة/الخزانة: غاز نوع الكبينة/الخزانة: اتصالات - نوع الكبينة/الخزانة: طاقة + نوع الكبينة/الخزانة: كهرباء في الخدمة: نعم صهريج مجرى From d2b0b3895b6cc2079a45830932cc59f0b020896f Mon Sep 17 00:00:00 2001 From: Victor Shcherb Date: Wed, 30 Dec 2020 12:57:53 +0100 Subject: [PATCH 10/28] Fix #9704 --- OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java b/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java index 5c4c24ee68..280d75cebe 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java @@ -764,6 +764,7 @@ public class CommonWords { addFrequentlyUsed("martiri"); addFrequentlyUsed("verdi"); addFrequentlyUsed("augusta"); + addFrequentlyUsed("neuberger"); From 72a67ed8a560f075c954bb998edf9db9b98a7730 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Wed, 30 Dec 2020 14:06:06 +0200 Subject: [PATCH 11/28] Fix #9946 Current road ref not showing during navigation --- OsmAnd/src/net/osmand/plus/routing/CurrentStreetName.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/routing/CurrentStreetName.java b/OsmAnd/src/net/osmand/plus/routing/CurrentStreetName.java index c984fb9119..c21679ead3 100644 --- a/OsmAnd/src/net/osmand/plus/routing/CurrentStreetName.java +++ b/OsmAnd/src/net/osmand/plus/routing/CurrentStreetName.java @@ -43,7 +43,7 @@ public class CurrentStreetName { String rf = n.directionInfo.getRef(); String dn = n.directionInfo.getDestinationName(); isSet = !(Algorithms.isEmpty(nm) && Algorithms.isEmpty(rf) && Algorithms.isEmpty(dn)); - streetName.text = RoutingHelperUtils.formatStreetName(nm, null, dn, "»"); + streetName.text = RoutingHelperUtils.formatStreetName(nm, rf, dn, "»"); streetName.turnType = n.directionInfo.getTurnType(); streetName.shieldObject = n.directionInfo.getRouteDataObject(); if (streetName.turnType == null) { @@ -62,7 +62,8 @@ public class CurrentStreetName { if (rs != null) { streetName.text = getRouteSegmentStreetName(routingHelper, rs, false); if (Algorithms.isEmpty(streetName.text)) { - isSet = !Algorithms.isEmpty(getRouteSegmentStreetName(routingHelper, rs, true)); + streetName.text = getRouteSegmentStreetName(routingHelper, rs, true); + isSet = !Algorithms.isEmpty(streetName.text); } else { isSet = true; } From e32591c99e08d7c59a58e56c5e0e04b158f903d9 Mon Sep 17 00:00:00 2001 From: Kseniia Date: Wed, 30 Dec 2020 14:19:28 +0200 Subject: [PATCH 12/28] add frequantly word --- OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java b/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java index 280d75cebe..74571665df 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java @@ -764,7 +764,7 @@ public class CommonWords { addFrequentlyUsed("martiri"); addFrequentlyUsed("verdi"); addFrequentlyUsed("augusta"); - addFrequentlyUsed("neuberger"); + addFrequentlyUsed("neuburger"); From 6236da3a0274500788d52f3e2631169c22079099 Mon Sep 17 00:00:00 2001 From: max-klaus Date: Wed, 30 Dec 2020 15:53:50 +0300 Subject: [PATCH 13/28] Refactoring travel helpers --- .../osmand/binary/BinaryMapIndexReader.java | 13 +- .../main/java/net/osmand/data/Amenity.java | 1 + .../main/java/net/osmand/data/MapObject.java | 2 + .../main/java/net/osmand/util/Algorithms.java | 8 + .../adapters/MapMarkersGroupsAdapter.java | 2 +- .../TrackActivityFragmentAdapter.java | 16 +- .../wikivoyage/WikivoyageWebViewClient.java | 12 +- .../WikivoyageArticleDialogFragment.java | 61 +-- .../WikivoyageArticleNavigationFragment.java | 42 +- .../plus/wikivoyage/data/TravelArticle.java | 116 ++++- .../plus/wikivoyage/data/TravelDbHelper.java | 111 +++-- .../plus/wikivoyage/data/TravelHelper.java | 22 +- .../plus/wikivoyage/data/TravelObfHelper.java | 442 ++++++++++++------ .../data/WikivoyageSearchResult.java | 51 +- .../explore/SavedArticlesTabFragment.java | 2 +- .../explore/WikivoyageExploreActivity.java | 15 +- .../travelcards/ArticleTravelCard.java | 3 +- .../menu/WikivoyageWptPtMenuController.java | 8 +- .../search/SearchRecyclerViewAdapter.java | 4 +- .../WikivoyageSearchDialogFragment.java | 3 +- 20 files changed, 639 insertions(+), 295 deletions(-) diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java index f0b26d09e8..7486576b61 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java @@ -1727,13 +1727,12 @@ public class BinaryMapIndexReader { double half16t = MapUtils.getDistance(lat, MapUtils.getLongitudeFromTile(16, ((int) dx) + 0.5), lat, MapUtils.getLongitudeFromTile(16, (int) dx)); double cf31 = ((double) radiusMeters / (half16t * 2)) * (1 << 15); - int y31 = MapUtils.get31TileNumberY(lat); - int x31 = MapUtils.get31TileNumberX(lon); - left = (int) (x31 - cf31); - right = (int) (x31 + cf31); - top = (int) (y31 - cf31); - bottom = (int) (y31 + cf31); - + y = MapUtils.get31TileNumberY(lat); + x = MapUtils.get31TileNumberX(lon); + left = (int) (x - cf31); + right = (int) (x + cf31); + top = (int) (y - cf31); + bottom = (int) (y + cf31); } public boolean publish(T obj) { 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 1759e79034..e51f04df16 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/Amenity.java @@ -44,6 +44,7 @@ public class Amenity extends MapObject { public static final String IS_AGGR_PART = "is_aggr_part"; public static final String CONTENT_JSON = "content_json"; public static final String ROUTE_ID = "route_id"; + public static final String ROUTE_SOURCE = "route_source"; private String subType; diff --git a/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java b/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java index 5fb13d62ac..c3205431cd 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java @@ -16,11 +16,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.zip.GZIPInputStream; diff --git a/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java b/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java index 2c0c056ab3..a9d85ce118 100644 --- a/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java +++ b/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java @@ -78,6 +78,10 @@ public class Algorithms { return map == null || map.size() == 0; } + public static String emptyIfNull(String s) { + return s == null ? "" : s; + } + public static boolean isEmpty(CharSequence s) { return s == null || s.length() == 0; } @@ -86,6 +90,10 @@ public class Algorithms { return s == null || s.trim().length() == 0; } + public static int hash(Object... values) { + return Arrays.hashCode(values); + } + public static boolean stringsEqual(String s1, String s2) { if (s1 == null && s2 == null) { return true; diff --git a/OsmAnd/src/net/osmand/plus/mapmarkers/adapters/MapMarkersGroupsAdapter.java b/OsmAnd/src/net/osmand/plus/mapmarkers/adapters/MapMarkersGroupsAdapter.java index 2277dded7c..fb74956b16 100644 --- a/OsmAnd/src/net/osmand/plus/mapmarkers/adapters/MapMarkersGroupsAdapter.java +++ b/OsmAnd/src/net/osmand/plus/mapmarkers/adapters/MapMarkersGroupsAdapter.java @@ -427,7 +427,7 @@ public class MapMarkersGroupsAdapter extends RecyclerView.Adapter extensions = metadata.getExtensionsToRead(); @@ -466,7 +467,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { } @Nullable - private String getMetadataImageLink(@NonNull GPXUtilities.Metadata metadata) { + private String getMetadataImageLink(@NonNull Metadata metadata) { String link = metadata.link; if (!TextUtils.isEmpty(link)) { String lowerCaseLink = link.toLowerCase(); @@ -482,11 +483,12 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { } @Nullable - private TravelArticle getTravelArticle(@NonNull GPXUtilities.Metadata metadata) { + private TravelArticle getTravelArticle(@NonNull GPXFile gpx) { + Metadata metadata = gpx.metadata; String title = metadata.getArticleTitle(); String lang = metadata.getArticleLang(); if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(lang)) { - return app.getTravelHelper().getArticleByTitle(title, lang); + return app.getTravelHelper().getArticleByTitle(title, gpx.getRect(), lang); } return null; } @@ -506,8 +508,8 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener { public void onClick(View v) { TrackActivity activity = getTrackActivity(); if (activity != null) { - WikivoyageArticleDialogFragment.showInstance(app, - activity.getSupportFragmentManager(), article.getRouteId(), article.getLang()); + WikivoyageArticleDialogFragment.showInstance(app, activity.getSupportFragmentManager(), + article.generateIdentifier(), article.getLang()); } } }; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/WikivoyageWebViewClient.java b/OsmAnd/src/net/osmand/plus/wikivoyage/WikivoyageWebViewClient.java index d5e678c61d..c2c42551e2 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/WikivoyageWebViewClient.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/WikivoyageWebViewClient.java @@ -14,6 +14,7 @@ import androidx.fragment.app.FragmentManager; import net.osmand.AndroidUtils; import net.osmand.GPXUtilities; +import net.osmand.GPXUtilities.WptPt; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.plus.OsmandApplication; @@ -22,6 +23,7 @@ import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.wikipedia.WikiArticleHelper; import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment; import net.osmand.plus.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.plus.wikivoyage.explore.WikivoyageExploreActivity; import java.io.File; @@ -67,8 +69,8 @@ public class WikivoyageWebViewClient extends WebViewClient { if (url.contains(WIKIVOYAGE_DOMAIN) && isWebPage) { String lang = WikiArticleHelper.getLang(url); String articleName = WikiArticleHelper.getArticleNameFromUrl(url, lang); - String articleId = app.getTravelHelper().getArticleId(articleName, lang); - if (!articleId.isEmpty()) { + TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(articleName, lang); + if (articleId != null) { WikivoyageArticleDialogFragment.showInstance(app, fragmentManager, articleId, lang); } else { WikiArticleHelper.warnAboutExternalLoad(url, activity, nightMode); @@ -83,8 +85,8 @@ public class WikivoyageWebViewClient extends WebViewClient { WikiArticleHelper.warnAboutExternalLoad(url, activity, nightMode); } else if (url.startsWith(PREFIX_GEO)) { if (article != null) { - List points = article.getGpxFile().getPoints(); - GPXUtilities.WptPt gpxPoint = null; + List points = article.getGpxFile().getPoints(); + WptPt gpxPoint = null; String coordinates = url.replace(PREFIX_GEO, ""); double lat; double lon; @@ -96,7 +98,7 @@ public class WikivoyageWebViewClient extends WebViewClient { Log.w(TAG, e.getMessage(), e); return true; } - for (GPXUtilities.WptPt point : points) { + for (WptPt point : points) { if (point.getLatitude() == lat && point.getLongitude() == lon) { gpxPoint = point; break; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java index 784c1ee8d4..9303fb7b84 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleDialogFragment.java @@ -30,17 +30,18 @@ import net.osmand.AndroidUtils; import net.osmand.IndexConstants; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; import net.osmand.plus.activities.TrackActivity; import net.osmand.plus.development.OsmandDevelopmentPlugin; import net.osmand.plus.helpers.FileNameTranslationHelper; +import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.wikipedia.WikiArticleBaseDialogFragment; import net.osmand.plus.wikipedia.WikiArticleHelper; import net.osmand.plus.wikivoyage.WikivoyageShowPicturesDialogFragment; import net.osmand.plus.wikivoyage.WikivoyageWebViewClient; import net.osmand.plus.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.plus.wikivoyage.data.TravelHelper; import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper; import net.osmand.util.Algorithms; @@ -58,15 +59,15 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme public static final String TAG = "WikivoyageArticleDialogFragment"; - private static final String ROUTE_ID_KEY = "route_id_key"; - private static final String LANGS_KEY = "langs_key"; - private static final String SELECTED_LANG_KEY = "selected_lang_key"; + private static final String ARTICLE_ID_KEY = "article_id"; + private static final String LANGS_KEY = "langs"; + private static final String SELECTED_LANG_KEY = "selected_lang"; private static final String EMPTY_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d4//"; private static final int MENU_ITEM_SHARE = 0; - private String routeId = ""; + private TravelArticleIdentifier articleId; private ArrayList langs; private String selectedLang; private TravelArticle article; @@ -192,15 +193,17 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme if (requestCode == WikivoyageArticleContentsFragment.SHOW_CONTENT_ITEM_REQUEST_CODE) { String link = data.getStringExtra(WikivoyageArticleContentsFragment.CONTENT_ITEM_LINK_KEY); String title = data.getStringExtra(WikivoyageArticleContentsFragment.CONTENT_ITEM_TITLE_KEY); - moveToAnchor(link, title); + if (title != null) { + moveToAnchor(link, title); + } } else if (requestCode == WikivoyageShowPicturesDialogFragment.SHOW_PICTURES_CHANGED_REQUEST_CODE) { updateWebSettings(); populateArticle(); } else if (requestCode == WikivoyageArticleNavigationFragment.OPEN_ARTICLE_REQUEST_CODE) { - String tripId = data.getStringExtra(WikivoyageArticleNavigationFragment.ROUTE_ID_KEY); + TravelArticleIdentifier articleId = data.getParcelableExtra(WikivoyageArticleNavigationFragment.ARTICLE_ID_KEY); String selectedLang = data.getStringExtra(WikivoyageArticleNavigationFragment.SELECTED_LANG_KEY); - if (!Algorithms.isEmpty(tripId) && !TextUtils.isEmpty(selectedLang)) { - this.routeId = tripId; + if (articleId != null && !TextUtils.isEmpty(selectedLang)) { + this.articleId = articleId; this.selectedLang = selectedLang; populateArticle(); } @@ -286,21 +289,21 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme @Override protected void populateArticle() { - if (Algorithms.isEmpty(routeId) || langs == null) { + if (articleId == null || langs == null) { Bundle args = getArguments(); if (args != null) { - routeId = args.getString(ROUTE_ID_KEY); + articleId = args.getParcelable(ARTICLE_ID_KEY); langs = args.getStringArrayList(LANGS_KEY); } } - if (Algorithms.isEmpty(routeId) || langs == null || langs.isEmpty()) { + if (articleId == null || langs == null || langs.isEmpty()) { return; } if (selectedLang == null) { selectedLang = langs.get(0); } articleToolbarText.setText(""); - article = getMyApplication().getTravelHelper().getArticleById(routeId, selectedLang); + article = getMyApplication().getTravelHelper().getArticleById(articleId, selectedLang); if (article == null) { return; } @@ -366,34 +369,34 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme } public static boolean showInstanceByTitle(@NonNull OsmandApplication app, - @NonNull FragmentManager fm, - @NonNull String title, - @NonNull String lang) { - String articleId = app.getTravelHelper().getArticleId(title, lang); + @NonNull FragmentManager fm, + @NonNull String title, + @NonNull String lang) { + TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(title, lang); return showInstance(app, fm, articleId, lang); } public static boolean showInstance(@NonNull OsmandApplication app, @NonNull FragmentManager fm, - @NonNull String routeId, + @NonNull TravelArticleIdentifier articleId, @Nullable String selectedLang) { - ArrayList langs = app.getTravelHelper().getArticleLangs(routeId); - return showInstance(fm, routeId, langs, selectedLang); + ArrayList langs = app.getTravelHelper().getArticleLangs(articleId); + return showInstance(fm, articleId, langs, selectedLang); } public static boolean showInstance(@NonNull FragmentManager fm, - String routeId, + @NonNull TravelArticleIdentifier articleId, @NonNull ArrayList langs) { - return showInstance(fm, routeId, langs, null); + return showInstance(fm, articleId, langs, null); } - public static boolean showInstance(@NonNull FragmentManager fm, - String routeId, - @NonNull ArrayList langs, - @Nullable String selectedLang) { + private static boolean showInstance(@NonNull FragmentManager fm, + @NonNull TravelArticleIdentifier articleId, + @NonNull ArrayList langs, + @Nullable String selectedLang) { try { Bundle args = new Bundle(); - args.putString(ROUTE_ID_KEY, routeId); + args.putParcelable(ARTICLE_ID_KEY, articleId); args.putStringArrayList(LANGS_KEY, langs); if (langs.contains(selectedLang)) { args.putString(SELECTED_LANG_KEY, selectedLang); @@ -416,7 +419,7 @@ public class WikivoyageArticleDialogFragment extends WikiArticleBaseDialogFragme return; } WikivoyageArticleNavigationFragment.showInstance(fm, - WikivoyageArticleDialogFragment.this, routeId, selectedLang); + WikivoyageArticleDialogFragment.this, articleId, selectedLang); } } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java index 7f0d9e8c0e..960608a784 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/article/WikivoyageArticleNavigationFragment.java @@ -26,6 +26,7 @@ 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.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.plus.wikivoyage.data.WikivoyageSearchResult; import net.osmand.util.Algorithms; @@ -38,14 +39,14 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr public static final String TAG = WikivoyageArticleNavigationFragment.class.getSimpleName(); - public static final String ROUTE_ID_KEY = "route_id_key"; - public static final String SELECTED_LANG_KEY = "selected_lang_key"; + public static final String ARTICLE_ID_KEY = "article_id"; + public static final String SELECTED_LANG_KEY = "selected_lang"; public static final int OPEN_ARTICLE_REQUEST_CODE = 2; private static final long UNDEFINED = -1; - private String routeId = ""; + private TravelArticleIdentifier articleId; private String selectedLang; private TravelArticle article; private List parentsList; @@ -61,26 +62,27 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr if (savedInstanceState != null) { selectedLang = savedInstanceState.getString(SELECTED_LANG_KEY); - routeId = savedInstanceState.getString(ROUTE_ID_KEY); + articleId = savedInstanceState.getParcelable(ARTICLE_ID_KEY); } else { Bundle args = getArguments(); if (args != null) { selectedLang = args.getString(SELECTED_LANG_KEY); - routeId = args.getString(ROUTE_ID_KEY); + articleId = args.getParcelable(ARTICLE_ID_KEY); } } - if (Algorithms.isEmpty(routeId) || TextUtils.isEmpty(selectedLang)) { + if (articleId == null || TextUtils.isEmpty(selectedLang)) { return; } - article = getMyApplication().getTravelHelper().getArticleById(routeId, selectedLang); + article = getMyApplication().getTravelHelper().getArticleById(articleId, selectedLang); if (article == null) { return; } parentsList = new ArrayList<>(Arrays.asList(article.getAggregatedPartOf().split(","))); - Map> navigationMap = getMyApplication().getTravelHelper().getNavigationMap(article); + Map> navigationMap + = getMyApplication().getTravelHelper().getNavigationMap(article); items.add(new TitleItem(getString(R.string.shared_string_navigation))); @@ -102,7 +104,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { WikivoyageSearchResult articleItem = listAdapter.getArticleItem(groupPosition, childPosition); - sendResults(articleItem.getRouteId()); + sendResults(articleItem.getArticleId()); dismiss(); return true; } @@ -111,10 +113,10 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { WikivoyageSearchResult articleItem = (WikivoyageSearchResult) listAdapter.getGroup(groupPosition); - if (Algorithms.isEmpty(articleItem.getRouteId())) { + if (Algorithms.isEmpty(articleItem.getArticleRouteId())) { Toast.makeText(getContext(), R.string.wiki_article_not_found, Toast.LENGTH_LONG).show(); } else { - sendResults(articleItem.getRouteId()); + sendResults(articleItem.getArticleId()); dismiss(); } return true; @@ -134,7 +136,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putString(ROUTE_ID_KEY, routeId); + outState.putParcelable(ARTICLE_ID_KEY, articleId); outState.putString(SELECTED_LANG_KEY, selectedLang); } @@ -148,17 +150,17 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr return nightMode ? R.color.wikivoyage_bottom_bar_bg_dark : R.color.list_background_color_light; } - private void sendResults(String routeId) { - WikivoyageArticleDialogFragment.showInstance(getMyApplication(), getFragmentManager(), routeId, selectedLang); + private void sendResults(TravelArticleIdentifier articleId) { + WikivoyageArticleDialogFragment.showInstance(getMyApplication(), getFragmentManager(), articleId, selectedLang); } public static boolean showInstance(@NonNull FragmentManager fm, @Nullable Fragment targetFragment, - String routeId, + @NonNull TravelArticleIdentifier articleId, @NonNull String selectedLang) { try { Bundle args = new Bundle(); - args.putString(ROUTE_ID_KEY, routeId); + args.putParcelable(ARTICLE_ID_KEY, articleId); args.putString(SELECTED_LANG_KEY, selectedLang); WikivoyageArticleNavigationFragment fragment = new WikivoyageArticleNavigationFragment(); if (targetFragment != null) { @@ -204,7 +206,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr @Override public Object getChild(int groupPosition, int childPosititon) { - return getArticleItem(groupPosition, childPosititon).getArticleTitles().get(0); + return getArticleItem(groupPosition, childPosititon).getArticleTitle(); } @Override @@ -236,8 +238,8 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { WikivoyageSearchResult articleItem = getArticleItem(groupPosition, childPosition); - String childTitle = articleItem.getArticleTitles().get(0); - boolean selected = Algorithms.stringsEqual(routeId, articleItem.getRouteId()) || parentsList.contains(childTitle); + String childTitle = articleItem.getArticleTitle(); + boolean selected = articleItem.getArticleId().equals(articleId) || parentsList.contains(childTitle); if (convertView == null) { convertView = LayoutInflater.from(context) @@ -263,7 +265,7 @@ public class WikivoyageArticleNavigationFragment extends MenuBottomSheetDialogFr @Override public View getGroupView(final int groupPosition, final boolean isExpanded, View convertView, ViewGroup parent) { - String groupTitle = ((WikivoyageSearchResult) getGroup(groupPosition)).getArticleTitles().get(0); + String groupTitle = ((WikivoyageSearchResult) getGroup(groupPosition)).getArticleTitle(); boolean selected = parentsList.contains(groupTitle) || article.getTitle().equals(groupTitle); if (convertView == null) { convertView = LayoutInflater.from(context) diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java index a559f3cf0f..ed62fac337 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelArticle.java @@ -1,5 +1,7 @@ package net.osmand.plus.wikivoyage.data; +import android.os.Parcel; +import android.os.Parcelable; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -7,13 +9,21 @@ import androidx.annotation.Nullable; import androidx.annotation.Size; import net.osmand.GPXUtilities.GPXFile; +import net.osmand.Location; +import net.osmand.aidl.search.SearchResult; +import net.osmand.data.LatLon; +import net.osmand.util.Algorithms; +import net.osmand.util.MapUtils; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; +import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Objects; public class TravelArticle { @@ -21,19 +31,29 @@ public class TravelArticle { private static final String THUMB_PREFIX = "320px-"; private static final String REGULAR_PREFIX = "1280px-";//1280, 1024, 800 + File file; String title; String content; String isPartOf; - double lat; - double lon; + double lat = Double.NaN; + double lon = Double.NaN; String imageTitle; GPXFile gpxFile; String routeId; + String routeSource; long originalId; String lang; String contentsJson; String aggregatedPartOf; + @NonNull + public TravelArticleIdentifier generateIdentifier() { + return new TravelArticleIdentifier(this); + } + + public File getFile() { + return file; + } public String getTitle() { return title; @@ -67,6 +87,10 @@ public class TravelArticle { return routeId; } + public String getRouteSource() { + return routeSource; + } + public long getOriginalId() { return originalId; } @@ -126,4 +150,92 @@ public class TravelArticle { String md5 = new String(Hex.encodeHex(DigestUtils.md5(s))); return new String[]{md5.substring(0, 1), md5.substring(0, 2)}; } + + public static class TravelArticleIdentifier implements Parcelable { + @Nullable File file; + double lat; + double lon; + @Nullable String title; + @Nullable String routeId; + @Nullable String routeSource; + + public static final Creator CREATOR = new Creator() { + @Override + public TravelArticleIdentifier createFromParcel(Parcel in) { + return new TravelArticleIdentifier(in); + } + + @Override + public TravelArticleIdentifier[] newArray(int size) { + return new TravelArticleIdentifier[size]; + } + }; + + private TravelArticleIdentifier(@NonNull Parcel in) { + readFromParcel(in); + } + + private TravelArticleIdentifier(@NonNull TravelArticle article) { + file = article.file; + lat = article.lat; + lon = article.lon; + title = article.title; + routeId = article.routeId; + routeSource = article.routeSource; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeDouble(lat); + out.writeDouble(lon); + out.writeString(title); + out.writeString(routeId); + out.writeString(routeSource); + out.writeString(file != null ? file.getAbsolutePath() : null); + } + + private void readFromParcel(Parcel in) { + lat = in.readDouble(); + lon = in.readDouble(); + title = in.readString(); + routeId = in.readString(); + routeSource = in.readString(); + String filePath = in.readString(); + if (!Algorithms.isEmpty(filePath)) { + file = new File(filePath); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TravelArticleIdentifier that = (TravelArticleIdentifier) o; + return areLatLonEqual(that.lat, that.lon, lat, lon) && + Algorithms.objectEquals(file, that.file) && + Algorithms.stringsEqual(title, that.title) && + Algorithms.stringsEqual(routeId, that.routeId) && + Algorithms.stringsEqual(routeSource, that.routeSource); + } + + @Override + public int hashCode() { + return Algorithms.hash(file, lat, lon, title, routeId, routeSource); + } + + private static boolean areLatLonEqual(double lat1, double lon1, double lat2, double lon2) { + boolean latEqual = (Double.isNaN(lat1) && Double.isNaN(lat2)) || Math.abs(lat1 - lat2) < 0.00001; + boolean lonEqual = (Double.isNaN(lon1) && Double.isNaN(lon2)) || Math.abs(lon1 - lon2) < 0.00001; + return latEqual && lonEqual; + } + } } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java index d50d05e42b..e9e392a9e3 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelDbHelper.java @@ -16,10 +16,12 @@ import net.osmand.OsmAndCollator; import net.osmand.PlatformUtil; import net.osmand.data.Amenity; import net.osmand.data.LatLon; +import net.osmand.data.QuadRect; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.api.SQLiteAPI.SQLiteConnection; import net.osmand.plus.api.SQLiteAPI.SQLiteCursor; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; @@ -245,12 +247,15 @@ public class TravelDbHelper implements TravelHelper { if (cursor != null) { if (cursor.moveToFirst()) { do { - WikivoyageSearchResult rs = new WikivoyageSearchResult(); - rs.routeId = cursor.getLong(0) + ""; - rs.articleTitles.add(cursor.getString(1)); - rs.langs.add(cursor.getString(2)); - rs.isPartOf.add(cursor.getString(3)); - rs.imageTitle = cursor.getString(4); + String routeId = cursor.getLong(0) + ""; + String articleTitle = cursor.getString(1); + String lang = cursor.getString(2); + String isPartOf = cursor.getString(3); + String imageTitle = cursor.getString(4); + List langs = new ArrayList<>(); + langs.add(lang); + WikivoyageSearchResult rs = new WikivoyageSearchResult(routeId, articleTitle, + isPartOf, imageTitle, langs); res.add(rs); } while (cursor.moveToNext()); } @@ -386,12 +391,12 @@ public class TravelDbHelper implements TravelHelper { Collections.sort(list, new Comparator() { @Override public int compare(WikivoyageSearchResult o1, WikivoyageSearchResult o2) { - boolean c1 = CollatorStringMatcher.cmatches(collator, searchQuery, o1.articleTitles.get(0), + boolean c1 = CollatorStringMatcher.cmatches(collator, searchQuery, o1.getArticleTitle(), StringMatcherMode.CHECK_ONLY_STARTS_WITH); - boolean c2 = CollatorStringMatcher.cmatches(collator, searchQuery, o2.articleTitles.get(0), + boolean c2 = CollatorStringMatcher.cmatches(collator, searchQuery, o2.getArticleTitle(), StringMatcherMode.CHECK_ONLY_STARTS_WITH); if (c1 == c2) { - return collator.compare(o1.articleTitles.get(0), o2.articleTitles.get(0)); + return collator.compare(o1.getArticleTitle(), o2.getArticleTitle()); } else if (c1) { return -1; } else if (c2) { @@ -421,29 +426,28 @@ public class TravelDbHelper implements TravelHelper { }); } } - private Collection groupSearchResultsByRouteId(List res) { String baseLng = application.getLanguage(); Map wikivoyage = new HashMap<>(); for (WikivoyageSearchResult rs : res) { - WikivoyageSearchResult prev = wikivoyage.get(rs.routeId); + WikivoyageSearchResult prev = wikivoyage.get(rs.getArticleRouteId()); if (prev != null) { - int insInd = prev.langs.size(); + boolean matchLang = false; if (rs.langs.get(0).equals(baseLng)) { - insInd = 0; + matchLang = true; } else if (rs.langs.get(0).equals("en")) { if (!prev.langs.get(0).equals(baseLng)) { - insInd = 0; - } else { - insInd = 1; + matchLang = true; } } - prev.articleTitles.add(insInd, rs.articleTitles.get(0)); - prev.langs.add(insInd, rs.langs.get(0)); - prev.isPartOf.add(insInd, rs.isPartOf.get(0)); + if (matchLang) { + prev.articleId.title = rs.getArticleTitle(); + prev.isPartOf = rs.getIsPartOf(); + } + prev.langs.add(matchLang ? 0 : 1, rs.langs.get(0)); } else { - wikivoyage.put(rs.routeId, rs); + wikivoyage.put(rs.getArticleRouteId(), rs); } } return wikivoyage.values(); @@ -451,12 +455,11 @@ public class TravelDbHelper implements TravelHelper { @NonNull @Override - public LinkedHashMap> getNavigationMap( - @NonNull final TravelArticle article) { + public Map> getNavigationMap(@NonNull final TravelArticle article) { String lang = article.getLang(); String title = article.getTitle(); if (TextUtils.isEmpty(lang) || TextUtils.isEmpty(title)) { - return new LinkedHashMap<>(); + return Collections.emptyMap(); } String[] parts = null; if (!TextUtils.isEmpty(article.getAggregatedPartOf())) { @@ -498,20 +501,20 @@ public class TravelDbHelper implements TravelHelper { SQLiteCursor cursor = conn.rawQuery(query.toString(), params.toArray(new String[0])); if (cursor != null && cursor.moveToFirst()) { do { - WikivoyageSearchResult rs = new WikivoyageSearchResult(); - rs.routeId = cursor.getLong(0) + ""; - rs.articleTitles.add(cursor.getString(1)); - rs.langs.add(cursor.getString(2)); - rs.isPartOf.add(cursor.getString(3)); - List l = navMap.get(rs.isPartOf.get(0)); + String routeId = cursor.getLong(0) + ""; + String articleTitle = cursor.getString(1); + String articleLang = cursor.getString(2); + String isPartOf = cursor.getString(3); + WikivoyageSearchResult rs = new WikivoyageSearchResult(routeId, articleTitle, + isPartOf, null, Collections.singletonList(articleLang)); + List l = navMap.get(rs.isPartOf); if (l == null) { l = new ArrayList<>(); - navMap.put(rs.isPartOf.get(0), l); + navMap.put(rs.isPartOf, l); } l.add(rs); - String key = rs.getArticleTitles().get(0); - if (headers != null && headers.contains(key)) { - headerObjs.put(key, rs); + if (headers != null && headers.contains(articleTitle)) { + headerObjs.put(articleTitle, rs); } } while (cursor.moveToNext()); } @@ -527,12 +530,10 @@ public class TravelDbHelper implements TravelHelper { Collections.sort(results, new Comparator() { @Override public int compare(WikivoyageSearchResult o1, WikivoyageSearchResult o2) { - return collator.compare(o1.articleTitles.get(0), o2.articleTitles.get(0)); + return collator.compare(o1.getArticleTitle(), o2.getArticleTitle()); } }); - WikivoyageSearchResult emptyResult = new WikivoyageSearchResult(); - emptyResult.articleTitles.add(header); - emptyResult.routeId = ""; + WikivoyageSearchResult emptyResult = new WikivoyageSearchResult("", header, null, null, null); searchResult = searchResult != null ? searchResult : emptyResult; res.put(searchResult, results); } @@ -542,9 +543,10 @@ public class TravelDbHelper implements TravelHelper { @Override @Nullable - public TravelArticle getArticleById(@NonNull String routeId, @NonNull String lang) { + public TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) { TravelArticle res = null; SQLiteConnection conn = openConnection(); + String routeId = articleId.routeId; if (conn != null && !Algorithms.isEmpty(routeId)) { SQLiteCursor cursor = conn.rawQuery(ARTICLES_TABLE_SELECT + " WHERE " + ARTICLES_COL_TRIP_ID + " = ? AND " + ARTICLES_COL_LANG + " = ?", new String[] { routeId, lang }); @@ -558,9 +560,21 @@ public class TravelDbHelper implements TravelHelper { return res; } - @Override @Nullable - public TravelArticle getArticleByTitle(@NonNull String title, @NonNull String lang) { + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang) { + return getArticleByTitle(title, new QuadRect(), lang); + } + + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull LatLon latLon, @NonNull final String lang) { + return getArticleByTitle(title, new QuadRect(), lang); + } + + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect, @NonNull final String lang) { TravelArticle res = null; SQLiteConnection conn = openConnection(); if (conn != null) { @@ -576,33 +590,32 @@ public class TravelDbHelper implements TravelHelper { return res; } - @NonNull + @Nullable @Override - public String getArticleId(@NonNull String title, @NonNull String lang) { - String res = ""; + public TravelArticleIdentifier getArticleId(@NonNull String title, @NonNull String lang) { + TravelArticle article = null; SQLiteConnection conn = openConnection(); if (conn != null) { - SQLiteCursor cursor = conn.rawQuery("SELECT " + ARTICLES_COL_TRIP_ID + " FROM " - + ARTICLES_TABLE_NAME + " WHERE " + ARTICLES_COL_TITLE + " = ? AND " + SQLiteCursor cursor = conn.rawQuery(ARTICLES_TABLE_SELECT + " WHERE " + ARTICLES_COL_TITLE + " = ? AND " + ARTICLES_COL_LANG + " = ?", new String[]{title, lang}); if (cursor != null) { if (cursor.moveToFirst()) { - res = cursor.getLong(0) + ""; + article = readArticle(cursor); } cursor.close(); } } - return res; + return article != null ? article.generateIdentifier() : null; } @NonNull @Override - public ArrayList getArticleLangs(@NonNull String routeId) { + public ArrayList getArticleLangs(@NonNull TravelArticleIdentifier articleId) { ArrayList res = new ArrayList<>(); SQLiteConnection conn = openConnection(); if (conn != null) { SQLiteCursor cursor = conn.rawQuery("SELECT " + ARTICLES_COL_LANG + " FROM " + ARTICLES_TABLE_NAME - + " WHERE " + ARTICLES_COL_TRIP_ID + " = ?", new String[]{routeId}); + + " WHERE " + ARTICLES_COL_TRIP_ID + " = ?", new String[]{articleId.routeId}); if (cursor != null) { if (cursor.moveToFirst()) { String baseLang = application.getLanguage(); diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java index b55c398817..af60514e73 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelHelper.java @@ -3,6 +3,10 @@ package net.osmand.plus.wikivoyage.data; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import net.osmand.data.LatLon; +import net.osmand.data.QuadRect; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; + import java.io.File; import java.util.ArrayList; import java.util.List; @@ -28,16 +32,22 @@ public interface TravelHelper { Map> getNavigationMap(@NonNull final TravelArticle article); @Nullable - TravelArticle getArticleById(@NonNull String routeId, @NonNull String lang); + TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang); @Nullable - TravelArticle getArticleByTitle(@NonNull String title, @NonNull String lang); + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang); + + @Nullable + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull LatLon latLon, @NonNull final String lang); + + @Nullable + TravelArticle getArticleByTitle(@NonNull String title, @NonNull QuadRect rect, @NonNull String lang); + + @Nullable + TravelArticleIdentifier getArticleId(@NonNull String title, @NonNull String lang); @NonNull - String getArticleId(@NonNull String title, @NonNull String lang); - - @NonNull - ArrayList getArticleLangs(@NonNull String routeId); + ArrayList getArticleLangs(@NonNull TravelArticleIdentifier articleId); @NonNull String getGPXName(@NonNull final TravelArticle article); diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java index e1dc65ebfd..842c2058e3 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/TravelObfHelper.java @@ -1,10 +1,11 @@ package net.osmand.plus.wikivoyage.data; +import android.text.TextUtils; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import net.osmand.Collator; -import net.osmand.CollatorStringMatcher; import net.osmand.GPXUtilities; import net.osmand.GPXUtilities.GPXFile; import net.osmand.IndexConstants; @@ -13,10 +14,13 @@ import net.osmand.PlatformUtil; import net.osmand.ResultMatcher; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.binary.BinaryMapIndexReader.SearchPoiTypeFilter; +import net.osmand.binary.BinaryMapIndexReader.SearchRequest; import net.osmand.data.Amenity; import net.osmand.data.LatLon; +import net.osmand.data.QuadRect; import net.osmand.osm.PoiCategory; import net.osmand.plus.OsmandApplication; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; @@ -25,33 +29,39 @@ import org.apache.commons.logging.Log; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; - -import static net.osmand.CollatorStringMatcher.StringMatcherMode.CHECK_EQUALS_FROM_SPACE; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; public class TravelObfHelper implements TravelHelper { private static final Log LOG = PlatformUtil.getLog(TravelObfHelper.class); public static final String ROUTE_ARTICLE = "route_article"; - public static final int SEARCH_RADIUS = 100000; + public static final int POPULAR_ARTICLES_SEARCH_RADIUS = 100000; + public static final int ARTICLE_SEARCH_RADIUS = 50000; + public static final int MAX_POPULAR_ARTICLES_COUNT = 100; private final OsmandApplication app; private final Collator collator; private List popularArticles = new ArrayList<>(); - private final Map cachedArticles; + private Map> cachedArticles = new ConcurrentHashMap<>(); private final TravelLocalDataHelper localDataHelper; public TravelObfHelper(OsmandApplication app) { this.app = app; collator = OsmAndCollator.primaryCollator(); localDataHelper = new TravelLocalDataHelper(app); - cachedArticles = new HashMap<>(); } @Override @@ -71,22 +81,23 @@ public class TravelObfHelper implements TravelHelper { @NonNull public List loadPopularArticles() { - String language = app.getLanguage(); + String lang = app.getLanguage(); List popularArticles = new ArrayList<>(); - for (BinaryMapIndexReader travelBookReader : getTravelBookReaders()) { + for (BinaryMapIndexReader reader : getReaders()) { try { final LatLon location = app.getMapViewTrackingUtilities().getMapLocation(); - BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( - location, SEARCH_RADIUS, -1, getSearchRouteArticleFilter(), null); - List amenities = travelBookReader.searchPoi(req); + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( + location, POPULAR_ARTICLES_SEARCH_RADIUS, -1, getSearchRouteArticleFilter(), null); + List amenities = reader.searchPoi(req); if (amenities.size() > 0) { - for (Amenity a : amenities) { - if (!Algorithms.isEmpty(a.getName(language))) { - TravelArticle article = readArticle(a, language); - popularArticles.add(article); - cachedArticles.put(article.routeId, article); - if (popularArticles.size() >= 100) { - break; + for (Amenity amenity : amenities) { + if (!Algorithms.isEmpty(amenity.getName(lang))) { + TravelArticle article = cacheTravelArticles(reader.getFile(), amenity, lang); + if (article != null) { + popularArticles.add(article); + if (popularArticles.size() >= MAX_POPULAR_ARTICLES_COUNT) { + break; + } } } } @@ -102,13 +113,25 @@ public class TravelObfHelper implements TravelHelper { }); } } catch (Exception e) { - LOG.error(e.getMessage()); + LOG.error(e.getMessage(), e); } } this.popularArticles = popularArticles; return popularArticles; } + @Nullable + private TravelArticle cacheTravelArticles(File file, Amenity amenity, String lang) { + TravelArticle article = null; + Map articles = readArticles(file, amenity); + if (!Algorithms.isEmpty(articles)) { + TravelArticleIdentifier newArticleId = articles.values().iterator().next().generateIdentifier(); + cachedArticles.put(newArticleId, articles); + article = getCachedArticle(newArticleId, lang); + } + return article; + } + SearchPoiTypeFilter getSearchRouteArticleFilter() { return new SearchPoiTypeFilter() { @Override @@ -123,23 +146,29 @@ public class TravelObfHelper implements TravelHelper { }; } - private TravelArticle readArticle(@NonNull Amenity amenity, @Nullable String lang) { - TravelArticle res = new TravelArticle(); - String title = Algorithms.isEmpty(amenity.getName(lang)) ? amenity.getName() : amenity.getName(lang); - if (Algorithms.isEmpty(title)) { - Map namesMap = amenity.getNamesMap(true); - if (!namesMap.isEmpty()) { - lang = namesMap.keySet().iterator().next(); - title = amenity.getName(lang); - } + @NonNull + private Map readArticles(@NonNull File file, @NonNull Amenity amenity) { + Map articles = new HashMap<>(); + Set langs = getLanguages(amenity); + for (String lang : langs) { + articles.put(lang, readArticle(file, amenity, lang)); } - res.title = title; + return articles; + } + + @NonNull + private TravelArticle readArticle(@NonNull File file, @NonNull Amenity amenity, @Nullable String lang) { + TravelArticle res = new TravelArticle(); + res.file = file; + String title = amenity.getName(lang); + res.title = Algorithms.isEmpty(title) ? amenity.getName() : title; res.content = amenity.getDescription(lang); res.isPartOf = emptyIfNull(amenity.getTagContent(Amenity.IS_PART, lang)); res.lat = amenity.getLocation().getLatitude(); res.lon = amenity.getLocation().getLongitude(); - res.imageTitle = emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, lang)); - res.routeId = getRouteId(amenity); + res.imageTitle = emptyIfNull(amenity.getTagContent(Amenity.IMAGE_TITLE, null)); + res.routeId = emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null)); + res.routeSource = emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null)); res.originalId = 0; res.lang = lang; res.contentsJson = emptyIfNull(amenity.getTagContent(Amenity.CONTENT_JSON, lang)); @@ -151,85 +180,72 @@ public class TravelObfHelper implements TravelHelper { return text == null ? "" : text; } - private String getRouteId(Amenity amenity) { - return amenity.getTagContent(Amenity.ROUTE_ID, null); - } - @Override public boolean isAnyTravelBookPresent() { - return !Algorithms.isEmpty(getTravelBookReaders()); + return !Algorithms.isEmpty(getReaders()); } @NonNull @Override public List search(@NonNull String searchQuery) { List res = new ArrayList<>(); - List searchObjects = null; - for (BinaryMapIndexReader reader : app.getResourceManager().getTravelRepositories()) { + Map> amenityMap = new HashMap<>(); + for (BinaryMapIndexReader reader : getReaders()) { try { - BinaryMapIndexReader.SearchRequest searchRequest = BinaryMapIndexReader. - buildSearchPoiRequest(0, 0, searchQuery, + SearchRequest searchRequest = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, searchQuery, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchRouteArticleFilter(), null, null); - searchObjects = reader.searchPoiByName(searchRequest); + List amenities = reader.searchPoiByName(searchRequest); + if (!Algorithms.isEmpty(amenities)) { + amenityMap.put(reader.getFile(), amenities); + } } catch (IOException e) { - LOG.error(e); + LOG.error(e.getMessage(), e); } } - if (!Algorithms.isEmpty(searchObjects)) { - String baseLng = app.getLanguage(); - for (Amenity obj : searchObjects) { - WikivoyageSearchResult r = new WikivoyageSearchResult(); - TravelArticle article = readArticle(obj, baseLng); - r.articleTitles = new ArrayList<>(Collections.singletonList(article.title)); - r.imageTitle = article.imageTitle; - r.routeId = article.routeId; - r.isPartOf = new ArrayList<>(Collections.singletonList(article.isPartOf)); - r.langs = new ArrayList<>(Collections.singletonList(baseLng)); - res.add(r); - cachedArticles.put(article.routeId, article); + if (!Algorithms.isEmpty(amenityMap)) { + String appLang = app.getLanguage(); + for (Entry> entry : amenityMap.entrySet()) { + File file = entry.getKey(); + for (Amenity amenity : entry.getValue()) { + Set nameLangs = getLanguages(amenity); + if (nameLangs.contains(appLang)) { + TravelArticle article = readArticle(file, amenity, appLang); + WikivoyageSearchResult r = new WikivoyageSearchResult(article, new ArrayList<>(nameLangs)); + res.add(r); + } + } } - res = new ArrayList<>(groupSearchResultsByRouteId(res)); sortSearchResults(res); } return res; } - private void sortSearchResults(@NonNull List list) { - Collections.sort(list, new Comparator() { - - @Override - public int compare(WikivoyageSearchResult res1, WikivoyageSearchResult res2) { - return collator.compare(res1.articleTitles.get(0), res2.articleTitles.get(0)); - } - }); - } - - @NonNull - private Collection groupSearchResultsByRouteId(@NonNull List res) { - String baseLng = app.getLanguage(); - Map wikivoyage = new HashMap<>(); - for (WikivoyageSearchResult rs : res) { - WikivoyageSearchResult prev = wikivoyage.get(rs.routeId); - if (prev != null) { - int insInd = prev.langs.size(); - if (rs.langs.get(0).equals(baseLng)) { - insInd = 0; - } else if (rs.langs.get(0).equals("en")) { - if (!prev.langs.get(0).equals(baseLng)) { - insInd = 0; - } else { - insInd = 1; - } + private Set getLanguages(@NonNull Amenity amenity) { + Set langs = new HashSet<>(); + String descrStart = Amenity.DESCRIPTION + ":"; + String partStart = Amenity.IS_PART + ":"; + for (String infoTag : amenity.getAdditionalInfoKeys()) { + if (infoTag.startsWith(descrStart)) { + if (infoTag.length() > descrStart.length()) { + langs.add(infoTag.substring(descrStart.length())); + } + } else if (infoTag.startsWith(partStart)) { + if (infoTag.length() > partStart.length()) { + langs.add(infoTag.substring(partStart.length())); } - prev.articleTitles.add(insInd, rs.articleTitles.get(0)); - prev.langs.add(insInd, rs.langs.get(0)); - prev.isPartOf.add(insInd, rs.isPartOf.get(0)); - } else { - wikivoyage.put(rs.routeId, rs); } } - return wikivoyage.values(); + return langs; + } + + private void sortSearchResults(@NonNull List list) { + Collections.sort(list, new Comparator() { + @Override + public int compare(WikivoyageSearchResult res1, WikivoyageSearchResult res2) { + return collator.compare(res1.articleId.title, res2.articleId.title); + } + }); } @NonNull @@ -241,71 +257,153 @@ public class TravelObfHelper implements TravelHelper { @NonNull @Override public Map> getNavigationMap(@NonNull final TravelArticle article) { - return Collections.emptyMap(); - } - - @Override - public TravelArticle getArticleById(@NonNull String routeId, @NonNull String lang) { - TravelArticle article = cachedArticles.get(routeId); - if (article != null) { - return article; - } else { - return getArticleByIdFromTravelBooks(routeId, lang); + final String lang = article.getLang(); + final String title = article.getTitle(); + if (TextUtils.isEmpty(lang) || TextUtils.isEmpty(title)) { + return Collections.emptyMap(); } - } - - private TravelArticle getArticleByIdFromTravelBooks(final String routeId, final String lang) { - TravelArticle article = null; - final List amenities = new ArrayList<>(); - for (BinaryMapIndexReader travelBookReader : getTravelBookReaders()) { + final String[] parts; + if (!TextUtils.isEmpty(article.getAggregatedPartOf())) { + String[] originalParts = article.getAggregatedPartOf().split(","); + if (originalParts.length > 1) { + parts = new String[originalParts.length]; + for (int i = 0; i < originalParts.length; i++) { + parts[i] = originalParts[originalParts.length - i - 1]; + } + } else { + parts = originalParts; + } + } else { + parts = null; + } + Map> navMap = new HashMap<>(); + Set headers = new LinkedHashSet(); + Map headerObjs = new HashMap<>(); + Map> amenityMap = new HashMap<>(); + for (BinaryMapIndexReader reader : getReaders()) { try { - BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( - 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, -1, getSearchRouteArticleFilter(), - new ResultMatcher() { - boolean done = false; + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, + Integer.MAX_VALUE, 0, Integer.MAX_VALUE, -1, getSearchRouteArticleFilter(), new ResultMatcher() { @Override public boolean publish(Amenity amenity) { - if (getRouteId(amenity).equals(routeId)) { - amenities.add(amenity); - done = true; + String isPartOf = amenity.getTagContent(Amenity.IS_PART, lang); + if (Algorithms.stringsEqual(title, isPartOf)) { + return true; + } else if (parts != null && parts.length > 0) { + String title = amenity.getName(lang); + title = Algorithms.isEmpty(title) ? amenity.getName() : title; + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if (i == 0 && Algorithms.stringsEqual(part, title) || Algorithms.stringsEqual(part, isPartOf)) { + return true; + } + } } return false; } @Override public boolean isCancelled() { - return done; + return false; } }); - - travelBookReader.searchPoi(req); - } catch (IOException e) { - LOG.error(e.getMessage()); - } - if (!amenities.isEmpty()) { - article = readArticle(amenities.get(0), lang); - cachedArticles.put(article.routeId, article); + List amenities = reader.searchPoi(req); + if (!Algorithms.isEmpty(amenities)) { + amenityMap.put(reader.getFile(), amenities); + } + } catch (Exception e) { + LOG.error(e.getMessage(), e); } } - return article; + if (parts != null && parts.length > 0) { + headers.addAll(Arrays.asList(parts)); + headers.add(title); + } + if (!Algorithms.isEmpty(amenityMap)) { + for (Entry> entry : amenityMap.entrySet()) { + File file = entry.getKey(); + for (Amenity amenity : entry.getValue()) { + Set nameLangs = getLanguages(amenity); + if (nameLangs.contains(lang)) { + TravelArticle a = readArticle(file, amenity, lang); + WikivoyageSearchResult rs = new WikivoyageSearchResult(a, new ArrayList<>(nameLangs)); + List l = navMap.get(rs.isPartOf); + if (l == null) { + l = new ArrayList<>(); + navMap.put(rs.isPartOf, l); + } + l.add(rs); + if (headers != null && headers.contains(a.getTitle())) { + headerObjs.put(a.getTitle(), rs); + } + } + } + } + } + + LinkedHashMap> res = new LinkedHashMap<>(); + for (String header : headers) { + WikivoyageSearchResult searchResult = headerObjs.get(header); + List results = navMap.get(header); + if (results != null) { + Collections.sort(results, new Comparator() { + @Override + public int compare(WikivoyageSearchResult o1, WikivoyageSearchResult o2) { + return collator.compare(o1.getArticleTitle(), o2.getArticleTitle()); + } + }); + WikivoyageSearchResult emptyResult = new WikivoyageSearchResult("", header, null, null, null); + searchResult = searchResult != null ? searchResult : emptyResult; + res.put(searchResult, results); + } + } + return res; + } + + @Override + public TravelArticle getArticleById(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) { + TravelArticle article = getCachedArticle(articleId, lang); + return article == null ? findArticleById(articleId, lang) : article; } @Nullable - @Override - public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang) { + private TravelArticle getCachedArticle(@NonNull TravelArticleIdentifier articleId, @NonNull String lang) { + TravelArticle article = null; + Map articles = cachedArticles.get(articleId); + if (articles != null) { + if (Algorithms.isEmpty(lang)) { + Collection ac = articles.values(); + if (!ac.isEmpty()) { + article = ac.iterator().next(); + } + } else { + article = articles.get(lang); + if (article == null) { + article = articles.get(""); + } + } + } + return article == null ? findArticleById(articleId, lang) : article; + } + + private TravelArticle findArticleById(@NonNull final TravelArticleIdentifier articleId, final String lang) { TravelArticle article = null; final List amenities = new ArrayList<>(); - for (BinaryMapIndexReader travelBookReader : getTravelBookReaders()) { + for (BinaryMapIndexReader reader : getReaders()) { try { - BinaryMapIndexReader.SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( - 0, 0, title, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, getSearchRouteArticleFilter(), - new ResultMatcher() { + if (articleId.file != null && !articleId.file.equals(reader.getFile())) { + continue; + } + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest(0, 0, + Algorithms.emptyIfNull(articleId.title), 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, + getSearchRouteArticleFilter(), new ResultMatcher() { boolean done = false; @Override public boolean publish(Amenity amenity) { - if (CollatorStringMatcher.cmatches(collator, title, amenity.getName(lang), CHECK_EQUALS_FROM_SPACE)) { + if (Algorithms.stringsEqual(articleId.routeId, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_ID, null))) + && Algorithms.stringsEqual(articleId.routeSource, Algorithms.emptyIfNull(amenity.getTagContent(Amenity.ROUTE_SOURCE, null)))) { amenities.add(amenity); done = true; } @@ -318,19 +416,74 @@ public class TravelObfHelper implements TravelHelper { } }, null); - travelBookReader.searchPoiByName(req); + if (!Double.isNaN(articleId.lat)) { + req.setBBoxRadius(articleId.lat, articleId.lon, ARTICLE_SEARCH_RADIUS); + if (!Algorithms.isEmpty(articleId.title)) { + reader.searchPoiByName(req); + } else { + reader.searchPoi(req); + } + } else { + reader.searchPoi(req); + } } catch (IOException e) { LOG.error(e.getMessage()); } if (!amenities.isEmpty()) { - article = readArticle(amenities.get(0), lang); - cachedArticles.put(article.routeId, article); + article = cacheTravelArticles(reader.getFile(), amenities.get(0), lang); } } return article; } - private List getTravelBookReaders() { + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull final String lang) { + return getArticleByTitle(title, new QuadRect(), lang); + } + + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull LatLon latLon, @NonNull final String lang) { + QuadRect rect = latLon != null ? MapUtils.calculateLatLonBbox(latLon.getLatitude(), latLon.getLongitude(), ARTICLE_SEARCH_RADIUS) : new QuadRect(); + return getArticleByTitle(title, rect, lang); + } + + @Nullable + @Override + public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect, @NonNull final String lang) { + TravelArticle article = null; + List amenities = null; + int x = 0; + int y = 0; + int left = 0; + int right = Integer.MAX_VALUE; + int top = 0; + int bottom = Integer.MAX_VALUE; + if (rect.height() > 0 && rect.width() > 0) { + x = (int) rect.centerX(); + y = (int) rect.centerY(); + left = (int) rect.left; + right = (int) rect.right; + top = (int) rect.top; + bottom = (int) rect.bottom; + } + for (BinaryMapIndexReader reader : getReaders()) { + try { + SearchRequest req = BinaryMapIndexReader.buildSearchPoiRequest( + x, y, title, left, right, top, bottom, getSearchRouteArticleFilter(), null, null); + amenities = reader.searchPoiByName(req); + } catch (IOException e) { + LOG.error(e.getMessage()); + } + if (!Algorithms.isEmpty(amenities)) { + article = cacheTravelArticles(reader.getFile(), amenities.get(0), lang); + } + } + return article; + } + + private List getReaders() { if (!app.isApplicationInitializing()) { return app.getResourceManager().getTravelRepositories(); } else { @@ -338,14 +491,16 @@ public class TravelObfHelper implements TravelHelper { } } - @NonNull + @Nullable @Override - public String getArticleId(@NonNull String title, @NonNull String lang) { + public TravelArticleIdentifier getArticleId(@NonNull String title, @NonNull String lang) { TravelArticle a = null; - for (TravelArticle article : cachedArticles.values()) { - if (article.getTitle().equals(title)) { - a = article; - break; + for (Map articles : cachedArticles.values()) { + for (TravelArticle article : articles.values()) { + if (article.getTitle().equals(title)) { + a = article; + break; + } } } if (a == null) { @@ -354,17 +509,18 @@ public class TravelObfHelper implements TravelHelper { a = article; } } - return a != null && a.getRouteId() != null ? a.getRouteId() : ""; + return a != null ? a.generateIdentifier() : null; } @NonNull @Override - public ArrayList getArticleLangs(@NonNull String routeId) { + public ArrayList getArticleLangs(@NonNull TravelArticleIdentifier articleId) { ArrayList res = new ArrayList<>(); - res.add("en"); - for (TravelArticle article : popularArticles) { - if (article.getRouteId().equals(routeId)) { - res.add(article.getLang()); + TravelArticle article = getArticleById(articleId, ""); + if (article != null) { + Map articles = cachedArticles.get(articleId); + if (articles != null) { + res.addAll(articles.keySet()); } } return res; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchResult.java b/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchResult.java index 7f3b7bfd49..5482bfee6c 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchResult.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/data/WikivoyageSearchResult.java @@ -1,5 +1,9 @@ package net.osmand.plus.wikivoyage.data; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.util.Algorithms; import java.util.ArrayList; @@ -9,25 +13,52 @@ public class WikivoyageSearchResult { private static final int SHOW_LANGS = 3; - String routeId; - List articleTitles = new ArrayList<>(); - List langs = new ArrayList<>(); - List isPartOf = new ArrayList<>(); - String imageTitle; + TravelArticleIdentifier articleId; - public String getRouteId() { - return routeId; + String imageTitle; + String isPartOf; + + List langs = new ArrayList<>(); + + public WikivoyageSearchResult(@NonNull TravelArticle article, @Nullable List langs) { + articleId = article.generateIdentifier(); + imageTitle = article.imageTitle; + isPartOf = article.isPartOf; + if (langs != null) { + this.langs = langs; + } } - public List getArticleTitles() { - return articleTitles; + public WikivoyageSearchResult(String routeId, String articleTitle, String isPartOf, String imageTitle, @Nullable List langs) { + TravelArticle article = new TravelArticle(); + article.routeId = routeId; + article.title = articleTitle; + + this.articleId = article.generateIdentifier(); + this.imageTitle = imageTitle; + this.isPartOf = isPartOf; + if (langs != null) { + this.langs = langs; + } + } + + public TravelArticleIdentifier getArticleId() { + return articleId; + } + + public String getArticleTitle() { + return articleId.title; + } + + public String getArticleRouteId() { + return articleId.routeId; } public List getLangs() { return langs; } - public List getIsPartOf() { + public String getIsPartOf() { return isPartOf; } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesTabFragment.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesTabFragment.java index 3bcb969f47..617ecf2d46 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesTabFragment.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/SavedArticlesTabFragment.java @@ -50,7 +50,7 @@ public class SavedArticlesTabFragment extends BaseOsmAndFragment implements Trav public void openArticle(TravelArticle article) { FragmentManager fm = getFragmentManager(); if (fm != null) { - WikivoyageArticleDialogFragment.showInstanceByTitle(app, fm, article.getTitle(), article.getLang()); + WikivoyageArticleDialogFragment.showInstance(app, fm, article.generateIdentifier(), article.getLang()); } } }); diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/WikivoyageExploreActivity.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/WikivoyageExploreActivity.java index 45047e2e1c..202c834d8d 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/WikivoyageExploreActivity.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/WikivoyageExploreActivity.java @@ -37,6 +37,7 @@ import net.osmand.plus.download.DownloadIndexesThread.DownloadEvents; import net.osmand.plus.wikipedia.WikiArticleHelper; import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment; import net.osmand.plus.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; import net.osmand.plus.wikivoyage.data.TravelHelper; import net.osmand.plus.wikivoyage.data.TravelLocalDataHelper; import net.osmand.plus.wikivoyage.search.WikivoyageSearchDialogFragment; @@ -50,8 +51,8 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv TravelLocalDataHelper.Listener { private static final String TAB_SELECTED = "tab_selected"; - private static final String ROUTE_ID_KEY = "route_id_key"; - private static final String SELECTED_LANG_KEY = "selected_lang_key"; + private static final String ARTICLE_ID_KEY = "article_id"; + private static final String SELECTED_LANG_KEY = "selected_lang"; private static final int EXPLORE_POSITION = 0; private static final int SAVED_ARTICLES_POSITION = 1; @@ -182,9 +183,9 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv BottomNavigationView bottomNav = (BottomNavigationView) findViewById(R.id.bottom_navigation); bottomNav.setSelectedItemId(R.id.action_saved_articles); } - String articleId = intent.getStringExtra(ROUTE_ID_KEY); + TravelArticleIdentifier articleId = intent.getParcelableExtra(ARTICLE_ID_KEY); String selectedLang = intent.getStringExtra(SELECTED_LANG_KEY); - if (!Algorithms.isEmpty(articleId)) { + if (articleId != null) { WikivoyageArticleDialogFragment.showInstance(app, getSupportFragmentManager(), articleId, selectedLang); } } @@ -201,8 +202,8 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv String title = WikiArticleHelper.decodeTitleFromTravelUrl(data.getQueryParameter("title")); String selectedLang = data.getQueryParameter("lang"); if (!Algorithms.isEmpty(title) && !Algorithms.isEmpty(selectedLang)) { - String articleId = app.getTravelHelper().getArticleId(title, selectedLang); - if (!articleId.isEmpty()) { + TravelArticleIdentifier articleId = app.getTravelHelper().getArticleId(title, selectedLang); + if (articleId != null) { WikivoyageArticleDialogFragment.showInstance(app, getSupportFragmentManager(), articleId, selectedLang); } } @@ -279,7 +280,7 @@ public class WikivoyageExploreActivity extends TabActivity implements DownloadEv private void applyIntentParameters(Intent intent, TravelArticle article) { intent.putExtra(TAB_SELECTED, viewPager.getCurrentItem()); - intent.putExtra(ROUTE_ID_KEY, article.getRouteId()); + intent.putExtra(ARTICLE_ID_KEY, article.generateIdentifier()); intent.putExtra(SELECTED_LANG_KEY, article.getLang()); } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/ArticleTravelCard.java b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/ArticleTravelCard.java index b9c5dbeb11..b955273d01 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/ArticleTravelCard.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/explore/travelcards/ArticleTravelCard.java @@ -76,7 +76,8 @@ public class ArticleTravelCard extends BaseTravelCard { @Override public void onClick(View v) { if (fragmentManager != null) { - WikivoyageArticleDialogFragment.showInstance(app, fragmentManager, article.getRouteId(), article.getLang()); + WikivoyageArticleDialogFragment.showInstance(app, fragmentManager, + article.generateIdentifier(), article.getLang()); } } }; diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/menu/WikivoyageWptPtMenuController.java b/OsmAnd/src/net/osmand/plus/wikivoyage/menu/WikivoyageWptPtMenuController.java index 947af0897a..6f3ee2fd43 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/menu/WikivoyageWptPtMenuController.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/menu/WikivoyageWptPtMenuController.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import net.osmand.GPXUtilities.GPXFile; import net.osmand.GPXUtilities.Metadata; import net.osmand.GPXUtilities.WptPt; +import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; import net.osmand.plus.R; @@ -14,12 +15,13 @@ import net.osmand.plus.activities.MapActivity; import net.osmand.plus.mapcontextmenu.controllers.WptPtMenuController; import net.osmand.plus.wikivoyage.article.WikivoyageArticleDialogFragment; import net.osmand.plus.wikivoyage.data.TravelArticle; +import net.osmand.plus.wikivoyage.data.TravelArticle.TravelArticleIdentifier; public class WikivoyageWptPtMenuController extends WptPtMenuController { private WikivoyageWptPtMenuController(@NonNull MapActivity mapActivity, @NonNull PointDescription pointDescription, @NonNull WptPt wpt, @NonNull TravelArticle article) { super(new WikivoyageWptPtMenuBuilder(mapActivity, wpt), mapActivity, pointDescription, wpt); - final String tripId = article.getRouteId(); + final TravelArticleIdentifier articleId = article.generateIdentifier(); final String lang = article.getLang(); leftTitleButtonController = new TitleButtonController() { @Override @@ -27,7 +29,7 @@ public class WikivoyageWptPtMenuController extends WptPtMenuController { MapActivity mapActivity = getMapActivity(); if (mapActivity != null) { WikivoyageArticleDialogFragment.showInstance(mapActivity.getMyApplication(), - mapActivity.getSupportFragmentManager(), tripId, lang); + mapActivity.getSupportFragmentManager(), articleId, lang); } } }; @@ -42,7 +44,7 @@ public class WikivoyageWptPtMenuController extends WptPtMenuController { String title = metadata != null ? metadata.getArticleTitle() : null; String lang = metadata != null ? metadata.getArticleLang() : null; if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(lang)) { - return mapActivity.getMyApplication().getTravelHelper().getArticleByTitle(title, lang); + return mapActivity.getMyApplication().getTravelHelper().getArticleByTitle(title, new LatLon(wpt.lat, wpt.lon), lang); } return null; } diff --git a/OsmAnd/src/net/osmand/plus/wikivoyage/search/SearchRecyclerViewAdapter.java b/OsmAnd/src/net/osmand/plus/wikivoyage/search/SearchRecyclerViewAdapter.java index de9793d267..34e369a3f2 100644 --- a/OsmAnd/src/net/osmand/plus/wikivoyage/search/SearchRecyclerViewAdapter.java +++ b/OsmAnd/src/net/osmand/plus/wikivoyage/search/SearchRecyclerViewAdapter.java @@ -83,8 +83,8 @@ public class SearchRecyclerViewAdapter extends RecyclerView.Adapter(res.getLangs())); + WikivoyageArticleDialogFragment.showInstance(fm, res.getArticleId(), new ArrayList<>(res.getLangs())); } else if (item instanceof WikivoyageSearchHistoryItem) { WikivoyageSearchHistoryItem historyItem = (WikivoyageSearchHistoryItem) item; WikivoyageArticleDialogFragment From c9e69e8e340f2cac0eeae80e3a76f2b015d9cd10 Mon Sep 17 00:00:00 2001 From: xmd5a Date: Wed, 30 Dec 2020 19:47:37 +0300 Subject: [PATCH 14/28] Add phrases --- OsmAnd/res/values/phrases.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OsmAnd/res/values/phrases.xml b/OsmAnd/res/values/phrases.xml index c69f7860f3..3b5feac342 100644 --- a/OsmAnd/res/values/phrases.xml +++ b/OsmAnd/res/values/phrases.xml @@ -4328,6 +4328,9 @@ Swimming area + Wildlife crossing + Bat bridge + Bat tunnel From d5eacb97954163a9a86255400e4f5cdfc1212013 Mon Sep 17 00:00:00 2001 From: Thomas jensen Date: Wed, 30 Dec 2020 16:52:09 +0000 Subject: [PATCH 15/28] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 58.6% (2105 of 3590 strings) --- OsmAnd/res/values-nb/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OsmAnd/res/values-nb/strings.xml b/OsmAnd/res/values-nb/strings.xml index a23068442d..9be06f04f9 100644 --- a/OsmAnd/res/values-nb/strings.xml +++ b/OsmAnd/res/values-nb/strings.xml @@ -3943,4 +3943,6 @@ Ressurser Velg profilen som skal brukes når programmet starter. Sist brukt + Tillat bekker og avløp + Tillat intermitterende vannveier \ No newline at end of file From 31b6d7d86162cb1a23c1ed5f190fe2e83f4613b7 Mon Sep 17 00:00:00 2001 From: josep constanti Date: Wed, 30 Dec 2020 11:01:12 +0000 Subject: [PATCH 16/28] Translated using Weblate (Catalan) Currently translated at 97.3% (3496 of 3590 strings) --- OsmAnd/res/values-ca/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/OsmAnd/res/values-ca/strings.xml b/OsmAnd/res/values-ca/strings.xml index 1b1794113a..9a28473286 100644 --- a/OsmAnd/res/values-ca/strings.xml +++ b/OsmAnd/res/values-ca/strings.xml @@ -3925,4 +3925,12 @@ \n \n Revertir tots els punts + Seleccioneu el perfil que s\'utilitzarà en iniciar l\'aplicació. + Darrera utilització + Prioritza les rutes de senderisme + Prioritza les rutes de senderisme + Permet rierols i torrents + Permet rierols i torrents + Permet les vies navegables no permanents + Permet les vies navegables no permanents \ No newline at end of file From 1e9e59114d0ab400f8e5f3c8179e5807b9a9cca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20H=C3=A5ll?= Date: Wed, 30 Dec 2020 22:00:09 +0000 Subject: [PATCH 17/28] Translated using Weblate (Swedish) Currently translated at 61.7% (2218 of 3590 strings) --- OsmAnd/res/values-sv/strings.xml | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/OsmAnd/res/values-sv/strings.xml b/OsmAnd/res/values-sv/strings.xml index 80d752ea56..fa8bc69b01 100644 --- a/OsmAnd/res/values-sv/strings.xml +++ b/OsmAnd/res/values-sv/strings.xml @@ -2933,4 +2933,45 @@ Vänligen tillhandahåll fullständig kod Tillåt strömmar och torrläggningar Tillåt vägar över vatten Tillåt vägar över vatten + \"Privat\" betyder att spåret inte visas i några offentliga listor, men spårpunkter från det i okronologisk ordning är tillgängliga via det offentliga GPS-API utan tidsstämplar. + \"Identifierbart\" betyder att spårningen kommer att visas offentligt i dina GPS-spår och i offentliga GPS-spårningar, dvs. andra användare kommer att kunna komma åt grundata kring spårningen och associera den med ditt användarnamn. Offentliga tidsstämplade tracepoint-data från GPS-API som serveras via trackpoints API refererar till din ursprungliga spårningssida. + \"Spårbar\" betyder att spåret inte visas i några offentliga listor, men bearbetade spårpunkter tillsammans med respektive tidsstämplar (som inte kan kopplas direkt till dig) görs genom nedladdningar från det offentliga GPS-API: et. + Stäng OSM Anteckningar + Kommentera OSM Anteckningar + Du kan logga in antingen med OAuth metoden (högre säkerhet) eller genom att använda ditt användarnamn och lösenord. + Lägg till bild + Registrera dig hos +\nOpenPlaceReviews.org + Bilder tillhandahålls av OpenPlaceReviews.org\'s öppen data projekt. För att kunna ladda upp dina bilder behöver du registrera dig på deras webbsida. + Registrera ett nytt konto + Jag har redan ett konto + Sökhistorik + Kajak + Motorbåt + Uppladdning av bild misslyckades, vänligen försök igen senare + Välj bild + Resurser + Uppskattad filstorlek + Vänligen välj vilken data som önskas exporteras till filen. + Erfordras för importering + Din enhet har bara %1$s ledigt lagringsutrymme. Vänligen frigör utrymme alternativt avmarkera några föremål som skall exporteras. + Det finns inte tillräckligt med lagringsutrymme + Välj vilka föremål som skall importeras. + Välj vilka grupper som skall importeras. + Lägg till Mapillary + Lägg till ÖppnaPlattsRecensioner (OPR) + Byt till att använda dev.openstreetmap.org istället för openstreetmap.org för att testa uppladdning av OSM Anteckningar / Sevärdheter / GPX. + Använd dev.openstreetmap.org + Mindre flyplan + OsmAnd visar bilder från flertalet källor: +\nOpenPlaceReviews - Foton av sevärdheter; +\nMapillary - Bilder på gatunivå; +\nWeb/Wikimedia - Foton av sevärdheter enligt OpenStreetMap-data. + Du kan använda höjddata för att ta hänsyn till upp- / nedstigning på din resa + Sammanslå segment + Dela innan + Dela efter + Lägg till ett nytt segment + OsmAnd profil + Användarprofil \ No newline at end of file From 6da8554512acc2d3cf4206daad99f75b5c36e1d6 Mon Sep 17 00:00:00 2001 From: Ldm Public Date: Wed, 30 Dec 2020 20:53:07 +0000 Subject: [PATCH 18/28] Translated using Weblate (French) Currently translated at 100.0% (3590 of 3590 strings) --- OsmAnd/res/values-fr/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OsmAnd/res/values-fr/strings.xml b/OsmAnd/res/values-fr/strings.xml index 3cd132f82a..137cfe1597 100644 --- a/OsmAnd/res/values-fr/strings.xml +++ b/OsmAnd/res/values-fr/strings.xml @@ -3543,18 +3543,18 @@ Min %1$s de %2$s Terrain - Carte ombrée utilisant des nuances sombres pour visualiser les pistes, les sommets et les plaines. - La piste est colorée selon sa pente. + Carte ombrée utilisant des nuances sombres pour visualiser les pentes, les sommets et les plaines. + La pente est colorée selon l\'inclinaison du terrain. Définissez les niveaux de zoom minimum et maximum pour lesquels la couche sera affichée. Des cartes supplémentaires sont nécessaires pour afficher l\'ombrage du relief sur la carte. - Des cartes supplémentaires sont nécessaires pour afficher les pistes sur la carte. - Vous pouvez en apprendre plus sur les pistes dans %1$s. + Des cartes supplémentaires sont nécessaires pour afficher les pentes sur la carte. + Vous pouvez en savoir plus sur les pentes dans %1$s. Transparence Niveaux de zoom Légende - Permet d\'afficher l\'ombrage du relief ou les pistes. Vous pouvez en apprendre plus sur ces types de cartes sur notre site. + Active l\'affichage de l\'ombrage du relief et l\'inclinaison. Vous pouvez en savoir plus sur ces types de cartes sur notre site. Ombrage du relief - Pistes + Pentes Affiche ou masque le terrain Masquer le terrain Afficher le terrain From 551be5f7e8d7027cede79a954e4e68fd1e55ddc6 Mon Sep 17 00:00:00 2001 From: Ahmad Alfrhood Date: Wed, 30 Dec 2020 18:49:30 +0000 Subject: [PATCH 19/28] Translated using Weblate (Arabic) Currently translated at 100.0% (3590 of 3590 strings) --- OsmAnd/res/values-ar/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index ebc6a7b115..80095419cd 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -740,7 +740,7 @@ الخريطة العلوية… بدون الخريطة العلوية - اختيار خريطة التراكب + اختيار خريطة علوية أو سفلية يتم الاحتفاظ بكافة العلامات الأخرى سيمارك متغير POI @@ -2379,12 +2379,12 @@ \n• يدعم نقاط وسيطة خلال مسارك \n• إعادة تلقائية للتوجيه كلما انحرفت عن الطريق \n• البحث عن الأماكن حسب العنوان، النوع (مثل: مطعم، فندق، محطة وقود، متحف)،أو حسب الإحداثيات الجغرافية - عرض الخريطة -\n• عرض موقعك والتوجيه -\n• محاذاة اختيارية للصورة وفق البوصلة أو توجيه الحركة -\n• حفظ أهم أماكنك المفضلة -\n• عرض النقاط المهمة من حولك (POI) -\n• عرض متخصص لبيانات خرائط على الإنترنت، الرؤية من الأقمار الصناعية (من بينج) وتراكب طبقات خرائط مختلفة كالسياحة ومسارات GPX للملاحة وطبقات إضافية مع شفافية قابلة للتعديل + عرض الخريطة +\n• عرض موقعك والتوجيه +\n• محاذاة اختيارية للصورة وفق البوصلة أو توجيه الحركة +\n• حفظ أهم أماكنك المفضلة +\n• عرض نقاط الاهتمام من حولك +\n• عرض متخصص لبيانات خرائط على الإنترنت، الرؤية من الأقمار الصناعية (من بينج) وتراكب طبقات خرائط مختلفة كالسياحة ومسارات GPX للملاحة وطبقات إضافية مع شفافية قابلة للتعديل \n• عرض اختياري لأسماء الأماكن باللغة الإنكليزية، اللغة المحلية، أو عبر الإملاء الصوتي \n استخدام OSM وبيانات ويكيبيديا From ceea05e81bb1709bbd831e142f70b2ee88c8d1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 31 Dec 2020 03:51:32 +0000 Subject: [PATCH 20/28] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 58.6% (2105 of 3590 strings) --- OsmAnd/res/values-nb/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OsmAnd/res/values-nb/strings.xml b/OsmAnd/res/values-nb/strings.xml index 9be06f04f9..a0bfa47166 100644 --- a/OsmAnd/res/values-nb/strings.xml +++ b/OsmAnd/res/values-nb/strings.xml @@ -3944,5 +3944,5 @@ Velg profilen som skal brukes når programmet starter. Sist brukt Tillat bekker og avløp - Tillat intermitterende vannveier + Tillat periodiske vannveier \ No newline at end of file From 1a1f0fe6e6031bb0f7796ff43621d16680e7a350 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 30 Dec 2020 06:10:08 +0000 Subject: [PATCH 21/28] Translated using Weblate (German) Currently translated at 99.9% (3877 of 3878 strings) --- OsmAnd/res/values-de/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-de/phrases.xml b/OsmAnd/res/values-de/phrases.xml index 216951a62c..b20f6b4092 100644 --- a/OsmAnd/res/values-de/phrases.xml +++ b/OsmAnd/res/values-de/phrases.xml @@ -3901,4 +3901,5 @@ Müllumladestation Fahrzeugwaage Rangerstation + Schwimmbereich \ No newline at end of file From 94d82f4752a165465d7f8f706b83ec35a363d7b9 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 29 Dec 2020 23:05:00 +0000 Subject: [PATCH 22/28] Translated using Weblate (Ukrainian) Currently translated at 100.0% (3878 of 3878 strings) --- OsmAnd/res/values-uk/phrases.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values-uk/phrases.xml b/OsmAnd/res/values-uk/phrases.xml index 6991f87786..82e842588b 100644 --- a/OsmAnd/res/values-uk/phrases.xml +++ b/OsmAnd/res/values-uk/phrases.xml @@ -198,7 +198,7 @@ 85% Етанолу (Е85) Рідкий водень Електроенергія - Станція зарядки + Зарядна станція;Зарядна станція для електромобіля;Зарядна станція для електромобілів;точка підзарядки електроенергією;Точка зарядки; Електронна зарядна станція;Обладнання для електромобілів Рампа для огляду/перевірки машин Стиснене повітря Стоянка @@ -3886,4 +3886,5 @@ Автомобільні ваги Громадська пральня Станція перевезення відходів + Плавальний майданчик \ No newline at end of file From 62b32ed17a4067b5a5af2cc27c5ee0617e675c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Babos=20G=C3=A1bor?= Date: Wed, 30 Dec 2020 06:22:38 +0000 Subject: [PATCH 23/28] Translated using Weblate (Hungarian) Currently translated at 99.9% (3875 of 3878 strings) --- OsmAnd/res/values-hu/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-hu/phrases.xml b/OsmAnd/res/values-hu/phrases.xml index 7c65c9a561..312ac3e5ee 100644 --- a/OsmAnd/res/values-hu/phrases.xml +++ b/OsmAnd/res/values-hu/phrases.xml @@ -3890,4 +3890,5 @@ Csap Vízmű Csöves kút + Fürdésre kijelölt terület \ No newline at end of file From 370cc3ee6d70e18f297796ff31ed93f2446aecca Mon Sep 17 00:00:00 2001 From: josep constanti Date: Wed, 30 Dec 2020 11:03:40 +0000 Subject: [PATCH 24/28] Translated using Weblate (Catalan) Currently translated at 73.9% (2868 of 3878 strings) --- OsmAnd/res/values-ca/phrases.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OsmAnd/res/values-ca/phrases.xml b/OsmAnd/res/values-ca/phrases.xml index 8949b96590..ead5d67e74 100644 --- a/OsmAnd/res/values-ca/phrases.xml +++ b/OsmAnd/res/values-ca/phrases.xml @@ -2888,4 +2888,9 @@ Cajun Burritos Gofres + Roba de motorista: no + Roba de motorista + Pneumàtics: no + Pneumàtics + Recanvis: no \ No newline at end of file From 0d16e7625d35087c068a82cbea48f8f652c434a1 Mon Sep 17 00:00:00 2001 From: Eduardo Addad de Oliveira Date: Wed, 30 Dec 2020 02:14:22 +0000 Subject: [PATCH 25/28] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (3878 of 3878 strings) --- OsmAnd/res/values-pt-rBR/phrases.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OsmAnd/res/values-pt-rBR/phrases.xml b/OsmAnd/res/values-pt-rBR/phrases.xml index d675321fc8..53d09cabf8 100644 --- a/OsmAnd/res/values-pt-rBR/phrases.xml +++ b/OsmAnd/res/values-pt-rBR/phrases.xml @@ -194,7 +194,7 @@ Biogás Hidrogênio líquido Eletricidade - Eletroposto + Eletroposto;Estação de carregamento de veículos elétricos; Estação de carregamento de VE; Ponto de recarga elétrica; Ponto de carga; Estação de recarga eletrônica; Equipamento de abastecimento de veículos elétricos Rampa de veículo Ar comprimido Estacionamento @@ -3898,4 +3898,5 @@ Posto de guarda florestal Estação de transferência de resíduos Lavandaria pública + Área de natação \ No newline at end of file From 6abe4efa3b1c056d77d2a428dd1455c90ed9a0bd Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 30 Dec 2020 01:18:05 +0000 Subject: [PATCH 26/28] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3878 of 3878 strings) --- OsmAnd/res/values-zh-rTW/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-zh-rTW/phrases.xml b/OsmAnd/res/values-zh-rTW/phrases.xml index e675bb2d0b..8778b0b0b5 100644 --- a/OsmAnd/res/values-zh-rTW/phrases.xml +++ b/OsmAnd/res/values-zh-rTW/phrases.xml @@ -3897,4 +3897,5 @@ 護林員站 公共洗衣區 垃圾站 + 游泳區 \ No newline at end of file From 8877496504e878d2953ddbdae74db8f60a90eaa4 Mon Sep 17 00:00:00 2001 From: solokot Date: Wed, 30 Dec 2020 07:30:11 +0000 Subject: [PATCH 27/28] Translated using Weblate (Russian) Currently translated at 99.9% (3877 of 3878 strings) --- OsmAnd/res/values-ru/phrases.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-ru/phrases.xml b/OsmAnd/res/values-ru/phrases.xml index 1cdda217e1..18e2cfe80c 100644 --- a/OsmAnd/res/values-ru/phrases.xml +++ b/OsmAnd/res/values-ru/phrases.xml @@ -3886,4 +3886,5 @@ Умывальник Станция перекачки отходов Станция рейнджеров + Место для купания \ No newline at end of file From de324fe0da6fec6f90bcb2df08e67be42952c44c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 31 Dec 2020 03:51:42 +0000 Subject: [PATCH 28/28] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 58.6% (2105 of 3590 strings) --- OsmAnd/res/values-nb/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/OsmAnd/res/values-nb/strings.xml b/OsmAnd/res/values-nb/strings.xml index a0bfa47166..6397b5c7d1 100644 --- a/OsmAnd/res/values-nb/strings.xml +++ b/OsmAnd/res/values-nb/strings.xml @@ -3945,4 +3945,5 @@ Sist brukt Tillat bekker og avløp Tillat periodiske vannveier + Tillat periodiske vannveier \ No newline at end of file