Merge pull request #10487 from osmandapp/gpx_context_menu

Track context menu first part
This commit is contained in:
alex-osm 2020-12-30 16:30:41 +03:00 committed by GitHub
commit b5bdfd7cb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 2217 additions and 1627 deletions

View file

@ -109,61 +109,6 @@
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginStart="@dimen/content_padding" />
<LinearLayout
android:id="@+id/split_color_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<LinearLayout
android:id="@+id/split_interval_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.6"
android:minHeight="@dimen/card_row_min_height"
android:paddingLeft="@dimen/content_padding"
android:paddingStart="@dimen/content_padding"
android:paddingRight="0dp"
android:paddingEnd="0dp"
android:paddingTop="@dimen/content_padding_half"
android:paddingBottom="@dimen/content_padding_half"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/split_interval_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
android:text="@string/gpx_split_interval"/>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/split_interval_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding_half"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:typeface="@string/font_roboto_medium"
tools:text="10 km"
android:paddingEnd="@dimen/content_padding_half"
android:paddingStart="@dimen/content_padding_half" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/split_interval_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
osmand:srcCompat="@drawable/ic_action_arrow_drop_down"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/appearance_view"
android:layout_width="match_parent"

View file

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_transparent">
<LinearLayout
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/context_menu_top_shadow" />
<LinearLayout
android:id="@+id/route_menu_top_shadow_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/list_background_color"
android:orientation="vertical">
<View
android:layout_width="@dimen/content_padding"
android:layout_height="2dp"
android:layout_gravity="center"
android:layout_marginTop="@dimen/context_menu_padding_margin_tiny"
android:layout_marginBottom="@dimen/list_item_button_padding"
android:background="?attr/bg_dash_line" />
<LinearLayout
android:id="@+id/context_menu_top_shadow_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/context_menu_top_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="@dimen/context_menu_padding_margin_default"
android:paddingLeft="@dimen/context_menu_padding_margin_default"
android:paddingEnd="@dimen/context_menu_padding_margin_default"
android:paddingRight="@dimen/context_menu_padding_margin_default">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/context_menu_first_line_top_margin"
android:layout_marginEnd="@dimen/context_menu_padding_margin_default"
android:layout_marginRight="@dimen/context_menu_padding_margin_default"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/title"
style="@style/TextAppearance.ContextMenuTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/search_address_building" />
<TextView
android:id="@+id/description"
style="@style/TextAppearance.ContextMenuSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
tools:text="@string/amenity_type_finance" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/context_menu_icon_view"
android:layout_width="@dimen/map_widget_icon"
android:layout_height="@dimen/map_widget_icon"
android:layout_marginTop="@dimen/context_menu_padding_margin_default"
android:tint="?attr/default_icon_color"
osmand:srcCompat="@drawable/ic_action_polygom_dark" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/bottom_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/activity_background_basic"
android:foreground="@drawable/bg_contextmenu_shadow"
android:foregroundGravity="top|fill_horizontal">
<net.osmand.plus.LockableScrollView
android:id="@+id/route_menu_bottom_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:id="@+id/route_menu_cards_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/context_menu_action_buttons_height" />
</net.osmand.plus.LockableScrollView>
</FrameLayout>
</LinearLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="@dimen/context_menu_action_buttons_height"
android:layout_gravity="bottom"
android:background="?attr/wikivoyage_card_bg_color"
osmand:itemBackground="?attr/wikivoyage_card_bg_color"
osmand:itemIconTint="@color/bottom_navigation_color_selector_light"
osmand:itemTextColor="@color/bottom_navigation_color_selector_light"
osmand:labelVisibilityMode="labeled"
osmand:menu="@menu/track_menu_bottom_navigation" />
</FrameLayout>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- <item-->
<!-- android:id="@+id/action_overview"-->
<!-- android:icon="@drawable/ic_action_trail_overview"-->
<!-- android:title="@string/shared_string_overview" />-->
<item
android:id="@+id/action_track"
android:icon="@drawable/ic_action_polygom_dark"
android:title="@string/shared_string_gpx_track" />
<item
android:id="@+id/action_points"
android:icon="@drawable/ic_action_waypoint"
android:title="@string/shared_string_gpx_points" />
<!-- <item-->
<!-- android:id="@+id/action_options"-->
<!-- android:icon="@drawable/ic_overflow_menu_white"-->
<!-- android:title="@string/shared_string_options" />-->
</menu>

View file

@ -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<WeakReference<Fragment>> 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<WeakReference<Fragment>> fragList = new ArrayList<>();
private List<GpxDisplayGroup> displayGroups;
private List<GpxDisplayGroup> 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<GpxDisplayGroup> 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);
@ -266,15 +239,6 @@ public class TrackActivity extends TabActivity {
return false;
}
public void updateSplitView() {
for (WeakReference<Fragment> f : fragList) {
Fragment frag = f.get();
if (frag instanceof TrackSegmentFragment) {
((TrackSegmentFragment) frag).updateSplitView();
}
}
}
public void updateHeader(Fragment sender) {
for (WeakReference<Fragment> f : fragList) {
Fragment frag = f.get();
@ -323,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<Fragment> f : fragList) {
@ -406,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<Void, Void, GPXFile> {
private OsmandApplication app;
@ -437,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() {
@ -486,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);
}

View file

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

View file

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

View file

@ -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<Void, Void, Exception> {
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);
}

View file

@ -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<Void, Void, Void> {
private OsmandApplication app;
private GPXFile gpx;
private Set<GpxDisplayItem> selectedItems;
private WeakReference<OnPointsDeleteListener> listenerRef;
DeletePointsTask(OsmandApplication app, GPXFile gpxFile, Set<GpxDisplayItem> 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();
}
}

View file

@ -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<GPXTabItemType, List<ILineDataSet>> dataSetsMap = new HashMap<>();
private WptPt selectedWpt;
private TrkSegment segment;
private GpxDisplayItem gpxItem;
private GPXTabItemType[] tabTypes;
private PagerSlidingTabStrip tabs;
private SparseArray<View> 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<GPXTabItemType> 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<ILineDataSet> getDataSets(LineChart chart, GPXTabItemType tabType,
LineGraphType firstType, LineGraphType secondType) {
List<ILineDataSet> 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<ILineDataSet> 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<ILineDataSet> 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<ILineDataSet> ds = getDataSets(null, tabType, null, null);
actionsListener.openAnalyzeOnMap(gpxItem, ds, tabType);
}
private void openSplitIntervalScreen() {
actionsListener.openSplitInterval(gpxItem, getTrkSegment());
}
}

View file

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

View file

@ -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<ILineDataSet> dataSets, GPXTabItemType tabType);
}

View file

@ -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<GpxDisplayItem> {
private OsmandApplication app;
private TrackDisplayHelper displayHelper;
private SegmentActionsListener listener;
private boolean nightMode;
public SegmentGPXAdapter(@NonNull Context context, @NonNull List<GpxDisplayItem> 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;
}
}

View file

@ -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;
@ -24,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;
@ -40,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;
@ -55,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;
@ -79,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;
@ -90,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));
}
@ -104,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);
@ -251,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);
@ -298,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() {
@ -347,7 +340,7 @@ public class SplitSegmentDialogFragment extends DialogFragment {
@NonNull
private List<GpxDisplayGroup> getDisplayGroups() {
return filterGroups(true);
return displayHelper.getDisplayGroups(filterTypes);
}
private void addOptionSplit(int value, boolean distance, List<GpxDisplayGroup> model) {
@ -375,32 +368,13 @@ public class SplitSegmentDialogFragment extends DialogFragment {
}
}
@NonNull
private List<GpxDisplayGroup> filterGroups(boolean useDisplayGroups) {
List<GpxDisplayGroup> groups = new ArrayList<>();
if (getTrackActivity() != null) {
List<GpxDisplayGroup> result = getTrackActivity().getGpxFile(useDisplayGroups);
for (GpxDisplayGroup group : result) {
boolean add = hasFilterType(group.getType());
if (add) {
groups.add(group);
}
}
}
return groups;
}
@NonNull
private List<GpxDisplayItem> getSplitSegments() {
TrackActivity trackActivity = getTrackActivity();
List<GpxDisplayItem> splitSegments = new ArrayList<>();
if (trackActivity != null) {
List<GpxDisplayGroup> result = trackActivity.getGpxFile(true);
if (result != null && result.size() > 0 && trkSegment.points.size() > 0) {
for (GpxDisplayGroup group : result) {
splitSegments.addAll(collectDisplayItemsFromGroup(group));
}
List<GpxDisplayGroup> result = displayHelper.getGpxFile(true);
if (result != null && result.size() > 0 && trkSegment.points.size() > 0) {
for (GpxDisplayGroup group : result) {
splitSegments.addAll(collectDisplayItemsFromGroup(group));
}
}
return splitSegments;
@ -434,24 +408,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<GpxDisplayItem> {
SplitSegmentsAdapter(List<GpxDisplayItem> items) {
@ -464,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);
}
@ -750,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;

View file

@ -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;
@ -43,13 +40,8 @@ import net.osmand.GPXUtilities.Metadata;
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.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
@ -58,9 +50,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.GpxSplitType;
import net.osmand.plus.track.SplitTrackAsyncTask;
import net.osmand.plus.track.SplitTrackAsyncTask.SplitTrackListener;
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;
@ -68,12 +58,8 @@ 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 gnu.trove.list.array.TIntArrayList;
import static net.osmand.plus.dialogs.ConfigureMapMenu.CURRENT_TRACK_COLOR_ATTR;
public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
@ -82,11 +68,8 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
private Fragment fragment;
private ListView listView;
private GpxDisplayItemType[] filterTypes;
private TrackDisplayHelper displayHelper;
private List<String> options = new ArrayList<>();
private List<Double> distanceSplit = new ArrayList<>();
private TIntArrayList timeSplit = new TIntArrayList();
private int selectedSplitInterval;
private boolean updateEnable;
private View headerView;
private SwitchCompat vis;
@ -109,17 +92,18 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
private View lineTextLayout;
private View overlayView;
ListPopupWindow splitListPopupWindow;
ListPopupWindow colorListPopupWindow;
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) {
@ -207,14 +191,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() {
@ -266,17 +248,15 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showTemporaryObjectOnMap(getRect());
showTemporaryObjectOnMap(displayHelper.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();
boolean gpxFileSelected = isGpxFileSelected(gpxFile);
boolean gpxFileSelected = isGpxFileSelected(app, gpxFile);
boolean hasPath = gpxFile != null && (gpxFile.tracks.size() > 0 || gpxFile.routes.size() > 0);
TrackActivity activity = getTrackActivity();
@ -298,13 +278,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);
@ -313,45 +287,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) {
@ -364,6 +305,7 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
appearanceView.setVisibility(View.GONE);
divider.setVisibility(View.GONE);
}
bottomDivider.setVisibility(View.GONE);
}
updateTrackColor();
}
@ -374,7 +316,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) {
@ -587,7 +529,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));
@ -596,26 +538,10 @@ 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<GpxDisplayGroup> groups = getDisplayGroups();
if (groups.size() > 0) {
updateSplit(groups, visible ? sf : null);
if (getGpxDataItem() != null) {
updateSplitInDatabase();
}
}
}
app.getSelectedGpxHelper().selectGpxFile(gpxFile, visible, false);
}
}
@Nullable
private QuadRect getRect() {
TrackActivity activity = getTrackActivity();
return activity != null ? activity.getRect() : null;
}
public boolean isShowOnMap() {
return vis != null && vis.isChecked();
}
@ -631,88 +557,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
menuFab.setVisibility(visible ? View.VISIBLE : View.GONE);
}
@NonNull
public List<GpxDisplayGroup> getOriginalGroups() {
return filterGroups(false);
}
@NonNull
public List<GpxDisplayGroup> getDisplayGroups() {
return filterGroups(true);
}
private boolean hasFilterType(GpxDisplayItemType filterType) {
for (GpxDisplayItemType type : filterTypes) {
if (type == filterType) {
return true;
}
}
return false;
}
@NonNull
private List<GpxDisplayGroup> filterGroups(boolean useDisplayGroups) {
List<GpxDisplayGroup> groups = new ArrayList<>();
TrackActivity activity = getTrackActivity();
if (activity != null) {
List<GpxDisplayGroup> result = activity.getGpxFile(useDisplayGroups);
for (GpxDisplayGroup group : result) {
boolean add = hasFilterType(group.getType());
if (add) {
groups.add(group);
}
}
}
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<GpxDisplayGroup> 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();
@ -734,154 +578,6 @@ public class TrackActivityFragmentAdapter implements TrackBitmapDrawerListener {
}
}
public List<GpxSelectionHelper.GpxDisplayItem> flatten(List<GpxDisplayGroup> groups) {
ArrayList<GpxSelectionHelper.GpxDisplayItem> list = new ArrayList<>();
for (GpxDisplayGroup g : groups) {
list.addAll(g.getModifiableList());
}
return list;
}
private void prepareSplitIntervalAdapterData() {
final List<GpxDisplayGroup> 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<GpxDisplayGroup> 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<GpxDisplayGroup> 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<GpxDisplayGroup> 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<GpxDisplayGroup> 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);
}

View file

@ -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<GpxDisplayItemType, Set<GpxDisplayItem>> 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<GpxDisplayItem> 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<GpxDisplayGroup> 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<GpxDisplayGroup> groups = getOriginalGroups();
if (groups != null) {
adapter.synchronizeGroups(groups);
}
}
class PointGPXAdapter extends OsmandBaseExpandableListAdapter implements Filterable {
Map<GpxDisplayGroup, List<GpxDisplayItem>> 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<Void, Void, Void> {
private OsmandApplication app;
private WeakReference<TrackPointFragment> fragmentRef;
private GPXFile gpx;
private Set<GpxDisplayItem> 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<GpxDisplayGroup> 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);
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

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

View file

@ -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<GpxDisplayGroup> displayGroups;
private List<GpxDisplayGroup> 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<GpxDisplayGroup> 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<GpxDisplayGroup> getOriginalGroups(GpxDisplayItemType[] filterTypes) {
return filterGroups(false, filterTypes);
}
@NonNull
public List<GpxDisplayGroup> 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<GpxDisplayGroup> filterGroups(boolean useDisplayGroups, GpxDisplayItemType[] filterTypes) {
List<GpxDisplayGroup> groups = new ArrayList<>();
for (GpxDisplayGroup group : getGpxFile(useDisplayGroups)) {
if (hasFilterType(group.getType(), filterTypes)) {
groups.add(group);
}
}
return groups;
}
public static List<GpxDisplayItem> flatten(List<GpxDisplayGroup> groups) {
ArrayList<GpxDisplayItem> 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);
}
}
}

View file

@ -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<ILineDataSet> 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<GpxDisplayGroup> 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;
}
}
}