Merge pull request #9999 from osmandapp/AddGraphs

Plan Route Add Graphs initial commit
This commit is contained in:
Vitaliy 2020-10-13 14:31:22 +03:00 committed by GitHub
commit be042ec0bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 944 additions and 194 deletions

View file

@ -130,33 +130,25 @@
tools:text="@string/add_point_after"/>
</RelativeLayout>
<View
<LinearLayout
android:id="@+id/additional_info_container"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/dashboard_divider"/>
<FrameLayout
android:id="@+id/points_list_container"
android:layout_width="match_parent"
android:layout_height="@dimen/measurement_tool_points_list_container_height"
android:background="@color/activity_background_color_dark"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone" >
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/measure_points_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include layout="@layout/custom_radio_buttons" />
<include layout="@layout/card_bottom_divider"/>
<androidx.appcompat.widget.AppCompatImageView
<View
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_gravity="bottom"
android:alpha="0.5"
android:scaleType="fitXY"
osmand:srcCompat="@drawable/bg_shadow_onmap"/>
</FrameLayout>
android:layout_height="@dimen/content_padding_small" />
<FrameLayout
android:id="@+id/cards_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/measure_mode_controls"

View file

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/graph_types_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/content_padding_small"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
tools:itemCount="3"
tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/point_editor_icon_category_item"
tools:orientation="horizontal" />
<LinearLayout
android:id="@+id/common_graphs_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:orientation="vertical">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/line_chart"
android:layout_width="match_parent"
android:layout_height="@dimen/route_info_line_chart_height"
android:layout_gravity="center_vertical" />
</LinearLayout>
<LinearLayout
android:id="@+id/custom_graphs_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:orientation="vertical">
<com.github.mikephil.charting.charts.HorizontalBarChart
android:id="@+id/horizontal_chart"
android:layout_width="match_parent"
android:layout_height="@dimen/route_info_chart_height" />
<LinearLayout
android:id="@+id/route_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="@dimen/route_info_legend_padding"
android:paddingBottom="@dimen/route_info_legend_padding" />
</LinearLayout>
<LinearLayout
android:id="@+id/message_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/content_padding"
android:layout_marginLeft="@dimen/content_padding"
android:layout_marginTop="@dimen/content_padding_small"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding_small"
android:visibility="gone"
tools:visibility="visible"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/message_icon"
android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:tint="?attr/default_icon_color"
tools:src="@drawable/ic_action_info_dark" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="@dimen/card_button_progress_size"
android:layout_height="@dimen/card_button_progress_size"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/content_padding"
android:layout_marginRight="@dimen/content_padding"
android:indeterminate="true"
android:visibility="gone" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/message_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:letterSpacing="@dimen/description_letter_spacing"
android:lineSpacingMultiplier="@dimen/bottom_sheet_text_spacing_multiplier"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
tools:text="Altitude data available only on the roads, you need to calculate a route using “Route between points” to get it." />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_gravity="bottom"
android:alpha="0.5"
android:scaleType="fitXY"
osmand:srcCompat="@drawable/bg_shadow_onmap" />
</LinearLayout>

View file

@ -0,0 +1,24 @@
<?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"
android:id="@+id/points_list_container"
android:layout_width="match_parent"
android:layout_height="@dimen/measurement_tool_points_list_container_height"
android:background="?attr/activity_background_color">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/measure_points_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include layout="@layout/card_bottom_divider" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_gravity="bottom"
android:alpha="0.5"
android:scaleType="fitXY"
osmand:srcCompat="@drawable/bg_shadow_onmap" />
</FrameLayout>

View file

@ -324,6 +324,7 @@
<dimen name="route_info_legend_padding">8dp</dimen>
<dimen name="route_info_warning_padding">18dp</dimen>
<dimen name="route_info_chart_height">71dp</dimen>
<dimen name="route_info_line_chart_height">120dp</dimen>
<dimen name="route_info_toolbar_button_size">40dp</dimen>
<dimen name="route_info_control_buttons_height">48dp</dimen>
<dimen name="route_info_button_bg_line_radius">18dp</dimen>

View file

@ -11,6 +11,9 @@
Thx - Hardy
-->
<string name="message_graph_will_be_available_after_recalculation">Wait for the route recalculation.\nGraph will be available after recalculation.</string>
<string name="message_need_calculate_route_before_show_graph">%1$s data available only on the roads, you need to calculate a route using “Route between points” to get it.</string>
<string name="shared_string_graph">Graph</string>
<string name="use_two_phase_routing">Use 2-phase A* routing algorithm</string>
<string name="file_already_imported">File is already imported in OsmAnd</string>
<string name="osm_edit_logout_success">Logout successful</string>

View file

@ -618,12 +618,12 @@ public abstract class PointEditorFragmentNew extends BaseOsmAndFragment {
}
}
HorizontalSelectionAdapter horizontalSelectionAdapter = new HorizontalSelectionAdapter(app, nightMode);
horizontalSelectionAdapter.setItems(new ArrayList<>(iconCategories.keySet()));
horizontalSelectionAdapter.setSelectedItem(selectedIconCategory);
horizontalSelectionAdapter.setTitledItems(new ArrayList<>(iconCategories.keySet()));
horizontalSelectionAdapter.setSelectedItemByTitle(selectedIconCategory);
horizontalSelectionAdapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() {
@Override
public void onItemSelected(String item) {
selectedIconCategory = item;
public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) {
selectedIconCategory = item.getTitle();
createIconForCategory();
updateIconSelector(selectedIcon, PointEditorFragmentNew.this.view);
}

View file

@ -17,6 +17,7 @@ import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import java.util.ArrayList;
import java.util.List;
import static net.osmand.util.Algorithms.capitalizeFirstLetter;
@ -24,19 +25,28 @@ import static net.osmand.util.Algorithms.capitalizeFirstLetter;
public class HorizontalSelectionAdapter extends RecyclerView.Adapter<HorizontalSelectionAdapter.ItemViewHolder> {
private List<String> items;
public static int INVALID_ID = -1;
private List<HorizontalSelectionItem> items;
private OsmandApplication app;
private boolean nightMode;
private HorizontalSelectionAdapterListener listener;
private String selectedItem = "";
private HorizontalSelectionItem selectedItem = null;
public HorizontalSelectionAdapter(OsmandApplication app, boolean nightMode) {
this.app = app;
this.nightMode = nightMode;
}
public void setItems(List<String> items) {
public void setTitledItems(List<String> titles) {
List<HorizontalSelectionItem> items = new ArrayList<>();
for (String title : titles) {
items.add(new HorizontalSelectionItem(title));
}
setItems(items);
}
public void setItems(List<HorizontalSelectionItem> items) {
this.items = items;
}
@ -44,23 +54,30 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter<HorizontalS
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
view = UiUtilities.getInflater(parent.getContext(), nightMode).inflate(R.layout.point_editor_icon_category_item,
parent, false);
view = UiUtilities.getInflater(parent.getContext(), nightMode)
.inflate(R.layout.point_editor_icon_category_item, parent, false);
return new ItemViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, final int position) {
final String item = items.get(holder.getAdapterPosition());
final HorizontalSelectionItem item = items.get(holder.getAdapterPosition());
TextView textView = holder.buttonText;
int activeColorResId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
if (item.equals(selectedItem)) {
if (item.equals(selectedItem) && item.isEnabled()) {
AndroidUtils.setBackground(holder.button, app.getUIUtilities().getPaintedIcon(
R.drawable.bg_select_icon_group_button, ContextCompat.getColor(app, activeColorResId)));
textView.setTextColor(ContextCompat.getColor(app, R.color.color_white));
} else {
if (!item.isEnabled()) {
int inactiveColorId = nightMode ?
R.color.icon_color_default_dark : R.color.icon_color_secondary_light;
textView.setTextColor(ContextCompat.getColor(app, inactiveColorId));
} else {
int defaultTitleColorId = nightMode ? R.color.active_color_primary_dark : R.color.preference_category_title;
textView.setTextColor(ContextCompat.getColor(app,
nightMode ? R.color.active_color_primary_dark : R.color.preference_category_title));
item.getTitleColorId() != INVALID_ID ? item.getTitleColorId() : defaultTitleColorId));
}
GradientDrawable buttonBackground = (GradientDrawable) AppCompatResources.getDrawable(app,
R.drawable.bg_select_icon_group_button).mutate();
buttonBackground.setStroke(AndroidUtils.dpToPx(app, 1), ContextCompat.getColor(app,
@ -69,8 +86,9 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter<HorizontalS
buttonBackground.setColor(ContextCompat.getColor(app, R.color.color_transparent));
AndroidUtils.setBackground(holder.button, buttonBackground);
}
textView.setText(capitalizeFirstLetter(item));
textView.setText(capitalizeFirstLetter(item.title));
textView.requestLayout();
holder.button.setEnabled(item.isEnabled());
holder.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -96,18 +114,32 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter<HorizontalS
return items.indexOf(name);
}
public void setSelectedItem(String selectedItem) {
public void setSelectedItemByTitle(String selectedItemTitle) {
HorizontalSelectionItem selectedItem = getItemByTitle(selectedItemTitle);
setSelectedItem(selectedItem);
}
public void setSelectedItem(HorizontalSelectionItem selectedItem) {
this.selectedItem = selectedItem;
notifyDataSetChanged();
}
public HorizontalSelectionItem getItemByTitle(String title) {
for (HorizontalSelectionItem item : items) {
if (title.equals(item.getTitle())) {
return item;
}
}
return null;
}
public void setListener(HorizontalSelectionAdapterListener listener) {
this.listener = listener;
}
public interface HorizontalSelectionAdapterListener {
void onItemSelected(String item);
void onItemSelected(HorizontalSelectionItem item);
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
@ -120,4 +152,44 @@ public class HorizontalSelectionAdapter extends RecyclerView.Adapter<HorizontalS
button = itemView.findViewById(R.id.button);
}
}
public static class HorizontalSelectionItem {
private String title;
private boolean enabled = true;
private int titleColorId = INVALID_ID;
private Object object;
public HorizontalSelectionItem(String title) {
this.title = title;
}
public HorizontalSelectionItem(String title, Object object) {
this.title = title;
this.object = object;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setTitleColorId(int titleColorId) {
this.titleColorId = titleColorId;
}
public String getTitle() {
return title;
}
public boolean isEnabled() {
return enabled;
}
public int getTitleColorId() {
return titleColorId;
}
public Object getObject() {
return object;
}
}
}

View file

@ -0,0 +1,396 @@
package net.osmand.plus.measurementtool;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.github.mikephil.charting.charts.HorizontalBarChart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.SettingsBaseActivity;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.OrderedLineDataSet;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter;
import net.osmand.plus.measurementtool.MeasurementToolFragment.OnUpdateAdditionalInfoListener;
import net.osmand.plus.routepreparationmenu.RouteDetailsFragment;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.router.RouteSegmentResult;
import net.osmand.util.Algorithms;
import static net.osmand.router.RouteStatisticsHelper.RouteStatistics;
import static net.osmand.GPXUtilities.GPXTrackAnalysis;
import static net.osmand.GPXUtilities.GPXFile;
import static net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class GraphsCard extends BaseCard implements OnUpdateAdditionalInfoListener {
private static String GRAPH_DATA_GPX_FILE_NAME = "graph_data_tmp";
private MeasurementEditingContext editingCtx;
private MeasurementToolFragment fragment;
private GraphType visibleGraphType;
private List<GraphType> graphTypes = new ArrayList<>();
private View commonGraphContainer;
private View customGraphContainer;
private View messageContainer;
private LineChart commonGraphChart;
private HorizontalBarChart customGraphChart;
private RecyclerView graphTypesMenu;
private enum CommonGraphType {
OVERVIEW(R.string.shared_string_overview, false),
ALTITUDE(R.string.altitude, true),
SLOPE(R.string.shared_string_slope, true),
SPEED(R.string.map_widget_speed, false);
CommonGraphType(int titleId, boolean canBeCalculated) {
this.titleId = titleId;
this.canBeCalculated = canBeCalculated;
}
final int titleId;
final boolean canBeCalculated;
}
public GraphsCard(@NonNull MapActivity mapActivity, MeasurementToolFragment fragment) {
super(mapActivity);
this.fragment = fragment;
}
@Override
protected void updateContent() {
if (mapActivity == null || fragment == null) return;
editingCtx = fragment.getEditingCtx();
commonGraphContainer = view.findViewById(R.id.common_graphs_container);
customGraphContainer = view.findViewById(R.id.custom_graphs_container);
messageContainer = view.findViewById(R.id.message_container);
commonGraphChart = (LineChart) view.findViewById(R.id.line_chart);
customGraphChart = (HorizontalBarChart) view.findViewById(R.id.horizontal_chart);
updateGraphData();
graphTypesMenu = view.findViewById(R.id.graph_types_recycler_view);
graphTypesMenu.setLayoutManager(
new LinearLayoutManager(mapActivity, RecyclerView.HORIZONTAL, false));
refreshGraphTypesSelectionMenu();
updateDataView();
}
@Override
public int getCardLayoutId() {
return R.layout.fragment_measurement_tool_graph;
}
@Override
public void onUpdateAdditionalInfo() {
if (!isRouteCalculating()) {
updateGraphData();
refreshGraphTypesSelectionMenu();
}
updateDataView();
}
private void refreshGraphTypesSelectionMenu() {
graphTypesMenu.removeAllViews();
OsmandApplication app = getMyApplication();
int activeColorId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
final HorizontalSelectionAdapter adapter = new HorizontalSelectionAdapter(app, nightMode);
final ArrayList<HorizontalSelectionItem> items = new ArrayList<>();
for (GraphType type : graphTypes) {
HorizontalSelectionItem item = new HorizontalSelectionItem(type.getTitle(), type);
if (type.isCustom()) {
item.setTitleColorId(activeColorId);
}
if (type.isAvailable()) {
items.add(item);
}
}
adapter.setItems(items);
String selectedItemKey = visibleGraphType.getTitle();
adapter.setSelectedItemByTitle(selectedItemKey);
adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() {
@Override
public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) {
adapter.setItems(items);
adapter.setSelectedItem(item);
GraphType chosenGraphType = (GraphType) item.getObject();
if (!isCurrentVisibleType(chosenGraphType)) {
setupVisibleGraphType(chosenGraphType);
}
}
});
graphTypesMenu.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
private void setupVisibleGraphType(GraphType type) {
visibleGraphType = type;
updateDataView();
}
private boolean isCurrentVisibleType(GraphType type) {
if (visibleGraphType != null && type != null) {
return Algorithms.objectEquals(visibleGraphType.getTitle(), type.getTitle());
}
return false;
}
private GraphType getFirstAvailableGraphType() {
for (GraphType type : graphTypes) {
if (type.isAvailable()) {
return type;
}
}
return null;
}
private void updateDataView() {
if (isRouteCalculating()) {
showProgressMessage();
} else if (visibleGraphType.hasData()) {
showGraph();
} else if (visibleGraphType.canBeCalculated()) {
showMessage();
}
}
private void showProgressMessage() {
commonGraphContainer.setVisibility(View.GONE);
customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.VISIBLE);
TextView tvMessage = messageContainer.findViewById(R.id.message_text);
ImageView icon = messageContainer.findViewById(R.id.message_icon);
ProgressBar pb = messageContainer.findViewById(R.id.progress_bar);
pb.setVisibility(View.VISIBLE);
icon.setVisibility(View.GONE);
tvMessage.setText(R.string.message_graph_will_be_available_after_recalculation);
}
private void showGraph() {
if (visibleGraphType.isCustom()) {
customGraphChart.clear();
commonGraphContainer.setVisibility(View.GONE);
customGraphContainer.setVisibility(View.VISIBLE);
messageContainer.setVisibility(View.GONE);
prepareCustomGraphView((BarData) visibleGraphType.getGraphData());
} else {
commonGraphChart.clear();
commonGraphContainer.setVisibility(View.VISIBLE);
customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.GONE);
prepareCommonGraphView((LineData) visibleGraphType.getGraphData());
}
}
private void showMessage() {
commonGraphContainer.setVisibility(View.GONE);
customGraphContainer.setVisibility(View.GONE);
messageContainer.setVisibility(View.VISIBLE);
TextView tvMessage = messageContainer.findViewById(R.id.message_text);
ImageView icon = messageContainer.findViewById(R.id.message_icon);
ProgressBar pb = messageContainer.findViewById(R.id.progress_bar);
pb.setVisibility(View.GONE);
icon.setVisibility(View.VISIBLE);
tvMessage.setText(app.getString(
R.string.message_need_calculate_route_before_show_graph,
visibleGraphType.getTitle()));
icon.setImageResource(R.drawable.ic_action_altitude_average);
}
private void prepareCommonGraphView(LineData data) {
GpxUiHelper.setupGPXChart(commonGraphChart, 4, 24f, 16f, !nightMode, true);
commonGraphChart.setData(data);
}
private void prepareCustomGraphView(BarData data) {
OsmandApplication app = getMyApplication();
if (app == null) return;
GpxUiHelper.setupHorizontalGPXChart(app, customGraphChart, 5, 9, 24, true, nightMode);
customGraphChart.setExtraRightOffset(16);
customGraphChart.setExtraLeftOffset(16);
customGraphChart.setData(data);
}
private void updateGraphData() {
graphTypes.clear();
OsmandApplication app = getMyApplication();
GPXTrackAnalysis analysis = createGpxTrackAnalysis();
// update common graph data
for (CommonGraphType commonType : CommonGraphType.values()) {
List<ILineDataSet> dataSets = getDataSets(commonType, commonGraphChart, analysis);
LineData data = null;
if (!Algorithms.isEmpty(dataSets)) {
data = new LineData(dataSets);
}
String title = app.getString(commonType.titleId);
graphTypes.add(new GraphType(title, false, commonType.canBeCalculated, data));
}
// update custom graph data
List<RouteSegmentResult> routeSegments = editingCtx.getAllRouteSegments();
List<RouteStatistics> routeStatistics = calculateRouteStatistics(routeSegments);
if (analysis != null && routeStatistics != null) {
for (RouteStatistics statistics : routeStatistics) {
String title = SettingsBaseActivity.getStringRouteInfoPropertyValue(app, statistics.name);
BarData data = null;
if (!Algorithms.isEmpty(statistics.elements)) {
data = GpxUiHelper.buildStatisticChart(
app, customGraphChart, statistics, analysis, true, nightMode);
}
graphTypes.add(new GraphType(title, true, false, data));
}
}
// update current visible graph type
if (visibleGraphType == null) {
visibleGraphType = getFirstAvailableGraphType();
} else {
for (GraphType type : graphTypes) {
if (isCurrentVisibleType(type)) {
visibleGraphType = type.isAvailable() ? type : getFirstAvailableGraphType();
break;
}
}
}
}
private List<ILineDataSet> getDataSets(CommonGraphType type, LineChart chart, GPXTrackAnalysis analysis) {
List<ILineDataSet> dataSets = new ArrayList<>();
if (chart != null && analysis != null) {
OsmandApplication app = getMyApplication();
switch (type) {
case OVERVIEW: {
List<OrderedLineDataSet> dataList = new ArrayList<>();
if (analysis.hasSpeedData) {
OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, true, true, false);
dataList.add(speedDataSet);
}
if (analysis.hasElevationData) {
OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, false);
dataList.add(elevationDataSet);
OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, null, true, true, false);
dataList.add(slopeDataSet);
}
if (dataList.size() > 0) {
Collections.sort(dataList, new Comparator<OrderedLineDataSet>() {
@Override
public int compare(OrderedLineDataSet o1, OrderedLineDataSet o2) {
return Float.compare(o1.getPriority(), o2.getPriority());
}
});
}
dataSets.addAll(dataList);
break;
}
case ALTITUDE: {
if (analysis.hasElevationData) {
OrderedLineDataSet elevationDataSet = GpxUiHelper.createGPXElevationDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps);
dataSets.add(elevationDataSet);
}
break;
}
case SLOPE:
if (analysis.hasElevationData) {
OrderedLineDataSet slopeDataSet = GpxUiHelper.createGPXSlopeDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, null, true, true, false);
dataSets.add(slopeDataSet);
}
break;
case SPEED: {
if (analysis.hasSpeedData) {
OrderedLineDataSet speedDataSet = GpxUiHelper.createGPXSpeedDataSet(app, chart,
analysis, GPXDataSetAxisType.DISTANCE, false, true, false);//calcWithoutGaps);
dataSets.add(speedDataSet);
}
break;
}
}
}
return dataSets;
}
private GPXTrackAnalysis createGpxTrackAnalysis() {
GPXFile gpx;
if (editingCtx.getGpxData() != null) {
gpx = editingCtx.getGpxData().getGpxFile();
} else {
gpx = editingCtx.exportRouteAsGpx(GRAPH_DATA_GPX_FILE_NAME);
}
return gpx != null ? gpx.getAnalysis(0) : null;
}
private List<RouteStatistics> calculateRouteStatistics(List<RouteSegmentResult> route) {
OsmandApplication app = getMyApplication();
if (route == null || app == null) return null;
return RouteDetailsFragment.calculateRouteStatistics(app, route, nightMode);
}
private boolean isRouteCalculating() {
return fragment.isProgressBarVisible();
}
private static class GraphType {
private String title;
private boolean isCustom;
private boolean canBeCalculated;
private ChartData graphData;
public GraphType(String title, boolean isCustom, boolean canBeCalculated, ChartData graphData) {
this.title = title;
this.isCustom = isCustom;
this.canBeCalculated = canBeCalculated;
this.graphData = graphData;
}
public String getTitle() {
return title;
}
public boolean isCustom() {
return isCustom;
}
public boolean isAvailable() {
return hasData() || canBeCalculated();
}
public boolean canBeCalculated() {
return canBeCalculated;
}
public boolean hasData() {
return getGraphData() != null;
}
public ChartData getGraphData() {
return graphData;
}
}
}

View file

@ -313,6 +313,74 @@ public class MeasurementEditingContext {
return before.points.size();
}
public List<RouteSegmentResult> getAllRouteSegments() {
// prepare data for sorting
List<TmpRouteSegmentData> fullList = new ArrayList<>();
for (Map.Entry<Pair<WptPt, WptPt>, RoadSegmentData> entry : roadSegmentData.entrySet()) {
fullList.add(new TmpRouteSegmentData(
entry.getKey().first,
entry.getKey().second,
entry.getValue().getSegments()));
}
// sorting data by connecting together
while (fullList.size() > 1) {
TmpRouteSegmentData firstInList = fullList.get(0);
for (int i = 1; i < fullList.size(); i++) {
TmpRouteSegmentData other = fullList.get(i);
boolean isMatched = false;
if (firstInList.isAfterOf(other)) {
isMatched = true;
firstInList.joinBefore(other);
} else if (firstInList.isBeforeOf(other)) {
isMatched = true;
firstInList.joinAfter(other);
}
if (isMatched) {
fullList.remove(other);
break;
}
}
}
return fullList.size() > 0 ? fullList.get(0).getRouteSegments() : null;
}
private static class TmpRouteSegmentData {
private WptPt start;
private WptPt end;
private List<RouteSegmentResult> routeSegments;
public TmpRouteSegmentData(WptPt start, WptPt end,
List<RouteSegmentResult> routeSegments) {
this.start = start;
this.end = end;
this.routeSegments = new ArrayList<>(routeSegments);
}
boolean isAfterOf(TmpRouteSegmentData other) {
return Algorithms.objectEquals(this.start, other.end);
}
boolean isBeforeOf(TmpRouteSegmentData other) {
return Algorithms.objectEquals(this.end, other.start);
}
void joinAfter(TmpRouteSegmentData other) {
end = other.end;
routeSegments.addAll(other.routeSegments);
}
void joinBefore(TmpRouteSegmentData other) {
start = other.start;
routeSegments.addAll(0, other.routeSegments);
}
public List<RouteSegmentResult> getRouteSegments() {
return routeSegments;
}
}
void splitSegments(int position) {
List<WptPt> points = new ArrayList<>();
points.addAll(before.points);

View file

@ -5,7 +5,6 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
@ -14,6 +13,7 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
@ -24,10 +24,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.widget.TextViewCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
@ -62,7 +60,6 @@ import net.osmand.plus.measurementtool.RouteBetweenPointsBottomSheetDialogFragme
import net.osmand.plus.measurementtool.RouteBetweenPointsBottomSheetDialogFragment.RouteBetweenPointsFragmentListener;
import net.osmand.plus.measurementtool.SaveGpxRouteAsyncTask.SaveGpxRouteListener;
import net.osmand.plus.measurementtool.SelectedPointBottomSheetDialogFragment.SelectedPointFragmentListener;
import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter;
import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter.MeasurementAdapterListener;
import net.osmand.plus.measurementtool.command.AddPointCommand;
import net.osmand.plus.measurementtool.command.ApplyGpxApproximationCommand;
@ -73,9 +70,9 @@ import net.osmand.plus.measurementtool.command.MovePointCommand;
import net.osmand.plus.measurementtool.command.RemovePointCommand;
import net.osmand.plus.measurementtool.command.ReorderPointCommand;
import net.osmand.plus.measurementtool.command.ReversePointsCommand;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback;
import net.osmand.plus.views.layers.MapControlsLayer;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType;
@ -93,6 +90,8 @@ import java.util.Locale;
import static net.osmand.IndexConstants.GPX_FILE_EXT;
import static net.osmand.IndexConstants.GPX_INDEX_DIR;
import static net.osmand.plus.UiUtilities.CustomRadioButtonType.END;
import static net.osmand.plus.UiUtilities.CustomRadioButtonType.START;
import static net.osmand.plus.measurementtool.MeasurementEditingContext.CalculationMode;
import static net.osmand.plus.measurementtool.MeasurementEditingContext.SnapToRoadProgressListener;
import static net.osmand.plus.measurementtool.SaveAsNewTrackBottomSheetDialogFragment.SaveAsNewTrackFragmentListener;
@ -110,18 +109,16 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
public static final String TAG = MeasurementToolFragment.class.getSimpleName();
public static final String TAPS_DISABLED_KEY = "taps_disabled_key";
private RecyclerView pointsRv;
private String previousToolBarTitle = "";
private MeasurementToolBarController toolBarController;
private MeasurementToolAdapter adapter;
private TextView distanceTv;
private TextView pointsTv;
private TextView distanceToCenterTv;
private String pointsSt;
private Drawable upIcon;
private Drawable downIcon;
private View pointsListContainer;
private View upDownRow;
private View additionalInfoContainer;
private ViewGroup additionalInfoCardsContainer;
private BaseCard visibleAdditionalInfoCard;
private LinearLayout customRadioButton;
private View mainView;
private ImageView upDownBtn;
private ImageView undoBtn;
@ -130,9 +127,11 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
private Snackbar snackbar;
private String fileName;
private AdditionalInfoType currentAdditionalInfoType;
private boolean wasCollapseButtonVisible;
private boolean progressBarVisible;
private boolean pointsListOpened;
private boolean additionalInfoExpanded;
private static final int PLAN_ROUTE_MODE = 0x1;
private static final int DIRECTION_MODE = 0x2;
@ -160,8 +159,9 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
SHOW_IS_SAVED_FRAGMENT
}
protected MeasurementEditingContext getEditingCtx() {
return editingCtx;
private enum AdditionalInfoType {
POINTS,
GRAPH
}
private void setEditingCtx(MeasurementEditingContext editingCtx) {
@ -223,6 +223,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
@Override
public void showProgressBar() {
MeasurementToolFragment.this.showProgressBar();
updateAdditionalInfoView();
}
@Override
@ -234,6 +235,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
public void hideProgressBar() {
((ProgressBar) mainView.findViewById(R.id.snap_to_road_progress_bar)).setVisibility(View.GONE);
progressBarVisible = false;
updateAdditionalInfoView();
}
@Override
@ -245,17 +247,9 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
measurementLayer.setEditingCtx(editingCtx);
// If rotate the screen from landscape to portrait when the list of points is displayed then
// the RecyclerViewFragment will exist without view. This is necessary to remove it.
if (!portrait) {
hidePointsListFragment();
}
nightMode = mapActivity.getMyApplication().getDaynightHelper().isNightModeForMapControls();
portrait = AndroidUiHelper.isOrientationPortrait(mapActivity);
upIcon = getContentIcon(R.drawable.ic_action_arrow_up);
downIcon = getContentIcon(R.drawable.ic_action_arrow_down);
pointsSt = getString(R.string.shared_string_gpx_points).toLowerCase();
View view = UiUtilities.getInflater(getContext(), nightMode)
@ -263,12 +257,30 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
mainView = view.findViewById(R.id.main_view);
AndroidUtils.setBackground(mapActivity, mainView, nightMode, R.drawable.bg_bottom_menu_light, R.drawable.bg_bottom_menu_dark);
pointsListContainer = view.findViewById(R.id.points_list_container);
if (portrait && pointsListContainer != null) {
final int backgroundColor = ContextCompat.getColor(mapActivity, nightMode
? R.color.activity_background_color_dark
: R.color.activity_background_color_light);
pointsListContainer.setBackgroundColor(backgroundColor);
additionalInfoContainer = mainView.findViewById(R.id.additional_info_container);
additionalInfoCardsContainer = mainView.findViewById(R.id.cards_container);
if (portrait) {
customRadioButton = mainView.findViewById(R.id.custom_radio_buttons);
View pointListBtn = customRadioButton.findViewById(R.id.left_button_container);
TextView tvPointListBtn = customRadioButton.findViewById(R.id.left_button);
tvPointListBtn.setText(R.string.shared_string_gpx_points);
pointListBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
changeAdditionalInfoType(AdditionalInfoType.POINTS);
}
});
View graphBtn = customRadioButton.findViewById(R.id.right_button_container);
TextView tvGraphBtn = customRadioButton.findViewById(R.id.right_button);
tvGraphBtn.setText(R.string.shared_string_graph);
graphBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
changeAdditionalInfoType(AdditionalInfoType.GRAPH);
}
});
}
if (progressBarVisible) {
@ -281,7 +293,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
mainIcon = (ImageView) mainView.findViewById(R.id.main_icon);
upDownBtn = (ImageView) mainView.findViewById(R.id.up_down_button);
upDownBtn.setImageDrawable(upIcon);
updateUpDownBtn();
mainView.findViewById(R.id.cancel_move_point_button).setOnClickListener(new OnClickListener() {
@Override
@ -297,14 +309,14 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
}
});
upDownRow = mainView.findViewById(R.id.up_down_row);
View upDownRow = mainView.findViewById(R.id.up_down_row);
upDownRow.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (!pointsListOpened && editingCtx.getPointsCount() > 0 && editingCtx.getSelectedPointPosition() == -1) {
showPointsList();
if (!additionalInfoExpanded && editingCtx.getPointsCount() > 0 && editingCtx.getSelectedPointPosition() == -1) {
expandAdditionalInfoView();
} else {
hidePointsList();
collapseAdditionalInfoView();
}
}
});
@ -397,8 +409,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
@Override
public void onSelectPoint(int selectedPointPos) {
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
if (selectedPointPos != -1) {
openSelectedPointMenu(mapActivity);
@ -421,8 +433,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
measurementLayer.setOnEnterMovePointModeListener(new MeasurementToolLayer.OnEnterMovePointModeListener() {
@Override
public void onEnterMovePointMode() {
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
switchMovePointMode(true);
}
@ -475,18 +487,6 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
updateToolbar();
final GpxData gpxData = editingCtx.getGpxData();
adapter = new MeasurementToolAdapter(getMapActivity(), editingCtx.getPoints(),
gpxData != null ? gpxData.getActionType() : null);
if (portrait) {
pointsRv = mainView.findViewById(R.id.measure_points_recycler_view);
} else {
pointsRv = new RecyclerView(getActivity());
}
ItemTouchHelper touchHelper = new ItemTouchHelper(new ReorderItemTouchHelperCallback(adapter));
touchHelper.attachToRecyclerView(pointsRv);
adapter.setAdapterListener(createMeasurementAdapterListener(touchHelper));
pointsRv.setLayoutManager(new LinearLayoutManager(getContext()));
pointsRv.setAdapter(adapter);
ImageButton snapToRoadBtn = (ImageButton) mapActivity.findViewById(R.id.snap_to_road_image_button);
snapToRoadBtn.setBackgroundResource(nightMode ? R.drawable.btn_circle_night : R.drawable.btn_circle);
@ -513,6 +513,36 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
return view;
}
private void changeAdditionalInfoType(@NonNull AdditionalInfoType type) {
if (!additionalInfoExpanded || !isCurrentAdditionalInfoType(type)) {
MapActivity ma = getMapActivity();
if (ma == null) return;
currentAdditionalInfoType = type;
updateUpDownBtn();
OsmandApplication app = ma.getMyApplication();
BaseCard additionalInfoCard = null;
if (AdditionalInfoType.POINTS == type) {
additionalInfoCard = new PointsCard(ma, this);
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, START);
} else if (AdditionalInfoType.GRAPH == type) {
additionalInfoCard = new GraphsCard(ma, this);
UiUtilities.updateCustomRadioButtons(app, customRadioButton, nightMode, END);
}
if (additionalInfoCard != null) {
visibleAdditionalInfoCard = additionalInfoCard;
additionalInfoCardsContainer.removeAllViews();
additionalInfoCardsContainer.addView(additionalInfoCard.build(ma));
additionalInfoExpanded = true;
}
}
}
private void updateAdditionalInfoView() {
if (visibleAdditionalInfoCard instanceof OnUpdateAdditionalInfoListener) {
((OnUpdateAdditionalInfoListener) visibleAdditionalInfoCard).onUpdateAdditionalInfo();
}
}
public boolean isInEditMode() {
return !isPlanRouteMode() && !editingCtx.isNewData() && !isDirectionMode() && !isFollowTrackMode();
}
@ -521,12 +551,16 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
this.fileName = fileName;
}
public MeasurementEditingContext getEditingCtx() {
return editingCtx;
}
private void updateUndoRedoCommonStuff() {
hidePointsListIfNoPoints();
collapseAdditionalInfoIfNoPointsEnough();
if (editingCtx.getPointsCount() > 0) {
enable(upDownBtn);
}
adapter.notifyDataSetChanged();
updateAdditionalInfoView();
updateDistancePointsText();
updateSnapToRoadControls();
}
@ -586,9 +620,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
super.onDestroyView();
cancelModes();
exitMeasurementMode();
adapter.setAdapterListener(null);
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
MeasurementToolLayer layer = getMeasurementLayer();
if (layer != null) {
@ -636,6 +669,10 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
progressBarVisible = true;
}
public boolean isProgressBarVisible() {
return progressBarVisible;
}
private void updateMainIcon() {
GpxData gpxData = editingCtx.getGpxData();
if (gpxData != null) {
@ -839,8 +876,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
MeasurementToolLayer measurementLayer = getMeasurementLayer();
editingCtx.getCommandManager().execute(new ClearPointsCommand(measurementLayer, ALL));
editingCtx.cancelSnapToRoad();
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
updateUndoRedoButton(false, redoBtn);
disable(upDownBtn);
@ -855,8 +892,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
if (points.size() > 1) {
MeasurementToolLayer measurementLayer = getMeasurementLayer();
editingCtx.getCommandManager().execute(new ReversePointsCommand(measurementLayer));
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
updateUndoRedoButton(false, redoBtn);
updateUndoRedoButton(true, undoBtn);
@ -924,8 +961,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
private void trimRoute(ClearCommandMode before) {
MeasurementToolLayer measurementLayer = getMeasurementLayer();
editingCtx.getCommandManager().execute(new ClearPointsCommand(measurementLayer, before));
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
editingCtx.setSelectedPointPosition(-1);
editingCtx.splitSegments(editingCtx.getBeforePoints().size() + editingCtx.getAfterPoints().size());
@ -1082,11 +1119,11 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
private void removePoint(MeasurementToolLayer measurementLayer, int position) {
if (measurementLayer != null) {
editingCtx.getCommandManager().execute(new RemovePointCommand(measurementLayer, position));
adapter.notifyDataSetChanged();
updateAdditionalInfoView();
updateUndoRedoButton(true, undoBtn);
updateUndoRedoButton(false, redoBtn);
updateDistancePointsText();
hidePointsListIfNoPoints();
collapseAdditionalInfoIfNoPointsEnough();
}
}
@ -1109,7 +1146,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
}
}
private MeasurementAdapterListener createMeasurementAdapterListener(final ItemTouchHelper touchHelper) {
MeasurementAdapterListener createMeasurementAdapterListener(final ItemTouchHelper touchHelper) {
return new MeasurementAdapterListener() {
final MapActivity mapActivity = getMapActivity();
@ -1125,8 +1162,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
@Override
public void onItemClick(int position) {
if (mapActivity != null && measurementLayer != null) {
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
if (portrait) {
setMapPosition(OsmandSettings.MIDDLE_TOP_CONSTANT);
@ -1148,7 +1185,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
toPosition = holder.getAdapterPosition();
if (toPosition >= 0 && fromPosition >= 0 && toPosition != fromPosition) {
editingCtx.getCommandManager().execute(new ReorderPointCommand(measurementLayer, fromPosition, toPosition));
adapter.notifyDataSetChanged();
updateAdditionalInfoView();
updateUndoRedoButton(false, redoBtn);
updateDistancePointsText();
mapActivity.refreshMap();
@ -1214,7 +1251,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
if (!isUndoMode()) {
editingCtx.addPoints(points);
}
adapter.notifyDataSetChanged();
updateAdditionalInfoView();
updateDistancePointsText();
}
}
@ -1226,7 +1263,7 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
if (!isUndoMode()) {
editingCtx.addPoints();
}
adapter.notifyDataSetChanged();
updateAdditionalInfoView();
updateDistancePointsText();
}
}
@ -1422,80 +1459,48 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
updateUndoRedoButton(true, undoBtn);
updateUndoRedoButton(false, redoBtn);
updateDistancePointsText();
adapter.notifyDataSetChanged();
updateAdditionalInfoView();
}
private void showPointsList() {
pointsListOpened = true;
upDownBtn.setImageDrawable(downIcon);
private void expandAdditionalInfoView() {
if (portrait) {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
if (portrait && pointsListContainer != null) {
pointsListContainer.setVisibility(View.VISIBLE);
} else {
showPointsListFragment();
}
additionalInfoContainer.setVisibility(View.VISIBLE);
AdditionalInfoType typeToShow = currentAdditionalInfoType == null
? AdditionalInfoType.POINTS : currentAdditionalInfoType;
changeAdditionalInfoType(typeToShow);
setMapPosition(portrait
? OsmandSettings.MIDDLE_TOP_CONSTANT
: OsmandSettings.LANDSCAPE_MIDDLE_RIGHT_CONSTANT);
}
}
private void hidePointsList() {
pointsListOpened = false;
upDownBtn.setImageDrawable(upIcon);
if (portrait && pointsListContainer != null) {
pointsListContainer.setVisibility(View.GONE);
} else {
hidePointsListFragment();
}
private void collapseAdditionalInfoView() {
if (portrait) {
additionalInfoExpanded = false;
updateUpDownBtn();
additionalInfoContainer.setVisibility(View.GONE);
setDefaultMapPosition();
}
}
private void hidePointsListIfNoPoints() {
private void collapseAdditionalInfoIfNoPointsEnough() {
MeasurementToolLayer measurementLayer = getMeasurementLayer();
if (measurementLayer != null) {
if (editingCtx.getPointsCount() < 1) {
int pointsCount = editingCtx.getPointsCount();
if (isCurrentAdditionalInfoType(AdditionalInfoType.GRAPH) && pointsCount < 2) {
collapseAdditionalInfoView();
} else if (pointsCount < 1) {
disable(upDownBtn);
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
}
}
}
private void showPointsListFragment() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
boolean transparentStatusBar = Build.VERSION.SDK_INT >= 21;
int statusBarHeight = transparentStatusBar ? 0 : AndroidUtils.getStatusBarHeight(mapActivity);
int screenHeight = AndroidUtils.getScreenHeight(mapActivity) - statusBarHeight;
RecyclerViewFragment fragment = new RecyclerViewFragment();
fragment.setRecyclerView(pointsRv);
fragment.setWidth(upDownRow.getWidth());
fragment.setHeight(screenHeight - upDownRow.getHeight());
fragment.setTransparentStatusBar(transparentStatusBar);
mapActivity.getSupportFragmentManager().beginTransaction()
.add(R.id.fragmentContainer, fragment, RecyclerViewFragment.TAG)
.commitAllowingStateLoss();
}
}
private void hidePointsListFragment() {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
try {
FragmentManager manager = mapActivity.getSupportFragmentManager();
Fragment fragment = manager.findFragmentByTag(RecyclerViewFragment.TAG);
if (fragment != null) {
manager.beginTransaction().remove(fragment).commitAllowingStateLoss();
}
} catch (Exception e) {
// ignore
}
}
}
private void setDefaultMapPosition() {
setMapPosition(OsmandSettings.CENTER_CONSTANT);
}
@ -1520,6 +1525,16 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
}
}
private void updateUpDownBtn() {
Drawable icon = getContentIcon(additionalInfoExpanded
? R.drawable.ic_action_arrow_down : R.drawable.ic_action_arrow_up);
upDownBtn.setImageDrawable(icon);
}
private boolean isCurrentAdditionalInfoType(@NonNull AdditionalInfoType type) {
return type.equals(currentAdditionalInfoType);
}
private String getSuggestedFileName() {
GpxData gpxData = editingCtx.getGpxData();
String displayedName = null;
@ -1803,8 +1818,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
final MapActivity mapActivity = getMapActivity();
MeasurementToolLayer measurementLayer = getMeasurementLayer();
if (mapActivity != null && measurementLayer != null) {
if (pointsListOpened && hidePointsListFirst) {
hidePointsList();
if (additionalInfoExpanded && hidePointsListFirst) {
collapseAdditionalInfoView();
return;
}
if (!editingCtx.hasChanges()) {
@ -1824,8 +1839,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
if (clearContext) {
editingCtx.clearSegments();
}
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
resetAppMode();
hideSnapToRoadIcon();
@ -1962,8 +1977,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
if (!editingCtx.getCommandManager().update(command)) {
editingCtx.getCommandManager().execute(command);
}
if (pointsListOpened) {
hidePointsList();
if (additionalInfoExpanded) {
collapseAdditionalInfoView();
}
updateSnapToRoadControls();
}
@ -2037,4 +2052,8 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
public boolean isNightModeForMapControls() {
return nightMode;
}
public interface OnUpdateAdditionalInfoListener {
void onUpdateAdditionalInfo();
}
}

View file

@ -0,0 +1,48 @@
package net.osmand.plus.measurementtool;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.measurementtool.MeasurementToolFragment.OnUpdateAdditionalInfoListener;
import net.osmand.plus.measurementtool.adapter.MeasurementToolAdapter;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.views.controls.ReorderItemTouchHelperCallback;
public class PointsCard extends BaseCard implements OnUpdateAdditionalInfoListener {
private MeasurementToolAdapter adapter;
private MeasurementToolFragment fragment;
public PointsCard(@NonNull MapActivity mapActivity, MeasurementToolFragment fragment) {
super(mapActivity);
this.fragment = fragment;
}
@Override
public void onUpdateAdditionalInfo() {
adapter.notifyDataSetChanged();
}
@Override
public int getCardLayoutId() {
return R.layout.fragment_measurement_tool_points_list;
}
@Override
protected void updateContent() {
MeasurementEditingContext editingCtx = fragment.getEditingCtx();
final GpxData gpxData = editingCtx.getGpxData();
adapter = new MeasurementToolAdapter(mapActivity, editingCtx.getPoints(),
gpxData != null ? gpxData.getActionType() : null);
RecyclerView pointsRv = view.findViewById(R.id.measure_points_recycler_view);
ItemTouchHelper touchHelper = new ItemTouchHelper(new ReorderItemTouchHelperCallback(adapter));
touchHelper.attachToRecyclerView(pointsRv);
adapter.setAdapterListener(fragment.createMeasurementAdapterListener(touchHelper));
pointsRv.setLayoutManager(new LinearLayoutManager(app));
pointsRv.setAdapter(adapter);
}
}

View file

@ -28,6 +28,7 @@ import net.osmand.plus.helpers.GpxUiHelper.GPXInfo;
import net.osmand.plus.helpers.enums.TracksSortByMode;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionAdapterListener;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem;
import java.io.File;
import java.util.ArrayList;
@ -128,7 +129,7 @@ public class SelectFileBottomSheet extends BottomSheetBehaviourDialogFragment {
sortButton.setImageResource(mode.getIconId());
updateDescription(descriptionView);
sortFolderList();
folderAdapter.setItems(getFolderNames());
folderAdapter.setTitledItems(getFolderNames());
folderAdapter.notifyDataSetChanged();
sortFileList();
adapter.notifyDataSetChanged();
@ -191,13 +192,13 @@ public class SelectFileBottomSheet extends BottomSheetBehaviourDialogFragment {
folders = new ArrayList<>();
collectDirs(gpxDir, folders);
sortFolderList();
folderAdapter.setItems(getFolderNames());
folderAdapter.setSelectedItem(selectedFolder);
folderAdapter.setTitledItems(getFolderNames());
folderAdapter.setSelectedItemByTitle(selectedFolder);
foldersRecyclerView.setAdapter(folderAdapter);
folderAdapter.setListener(new HorizontalSelectionAdapterListener() {
@Override
public void onItemSelected(String item) {
selectedFolder = item;
public void onItemSelected(HorizontalSelectionItem item) {
selectedFolder = item.getTitle();
updateFileList(folderAdapter);
}
});

View file

@ -345,18 +345,9 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
List<RouteSegmentResult> route = app.getRoutingHelper().getRoute().getOriginalRoute();
if (route != null) {
RenderingRulesStorage currentRenderer = app.getRendererRegistry().getCurrentSelectedRenderer();
RenderingRulesStorage defaultRender = app.getRendererRegistry().defaultRender();
MapRenderRepositories maps = app.getResourceManager().getRenderer();
RenderingRuleSearchRequest currentSearchRequest = maps.getSearchRequestWithAppliedCustomRules(currentRenderer, isNightMode());
RenderingRuleSearchRequest defaultSearchRequest = maps.getSearchRequestWithAppliedCustomRules(defaultRender, isNightMode());
List<RouteStatistics> routeStatistics = RouteStatisticsHelper.calculateRouteStatistic(route,
currentRenderer, defaultRender, currentSearchRequest, defaultSearchRequest);
List<RouteStatistics> routeStatistics = calculateRouteStatistics(app, route, isNightMode());
GPXTrackAnalysis analysis = gpx.getAnalysis(0);
for (RouteStatistics statistic : routeStatistics) {
RouteInfoCard routeClassCard = new RouteInfoCard(mapActivity, statistic, analysis);
addRouteCard(cardsContainer, routeClassCard);
@ -375,6 +366,20 @@ public class RouteDetailsFragment extends ContextMenuFragment implements PublicT
}
}
public static List<RouteStatistics> calculateRouteStatistics(OsmandApplication app,
List<RouteSegmentResult> route,
boolean nightMode) {
RenderingRulesStorage currentRenderer = app.getRendererRegistry().getCurrentSelectedRenderer();
RenderingRulesStorage defaultRender = app.getRendererRegistry().defaultRender();
MapRenderRepositories maps = app.getResourceManager().getRenderer();
RenderingRuleSearchRequest currentSearchRequest =
maps.getSearchRequestWithAppliedCustomRules(currentRenderer, nightMode);
RenderingRuleSearchRequest defaultSearchRequest =
maps.getSearchRequestWithAppliedCustomRules(defaultRender, nightMode);
return RouteStatisticsHelper.calculateRouteStatistic(route, currentRenderer,
defaultRender, currentSearchRequest, defaultSearchRequest);
}
@Override
protected void calculateLayout(View view, boolean initLayout) {
super.calculateLayout(view, initLayout);

View file

@ -85,13 +85,13 @@ public class TracksToFollowCard extends BaseCard {
private void setupCategoriesRow() {
final HorizontalSelectionAdapter selectionAdapter = new HorizontalSelectionAdapter(app, nightMode);
selectionAdapter.setItems(new ArrayList<>(gpxInfoCategories.keySet()));
selectionAdapter.setSelectedItem(selectedCategory);
selectionAdapter.setTitledItems(new ArrayList<>(gpxInfoCategories.keySet()));
selectionAdapter.setSelectedItemByTitle(selectedCategory);
selectionAdapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() {
@Override
public void onItemSelected(String item) {
selectedCategory = item;
List<GPXInfo> items = gpxInfoCategories.get(item);
public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) {
selectedCategory = item.getTitle();
List<GPXInfo> items = gpxInfoCategories.get(selectedCategory);
tracksAdapter.setShowFolderName(showFoldersName());
tracksAdapter.setGpxInfoList(items != null ? items : new ArrayList<GPXInfo>());
tracksAdapter.notifyDataSetChanged();

View file

@ -121,7 +121,7 @@ public class VehicleParametersBottomSheet extends BasePreferenceBottomSheet {
currentValue = 0.0f;
}
selectedItem = preference.getEntryFromValue(String.valueOf(currentValue));
adapter.setSelectedItem(selectedItem);
adapter.setSelectedItemByTitle(selectedItem);
int itemPosition = adapter.getItemPosition(selectedItem);
if (itemPosition >= 0) {
recyclerView.smoothScrollToPosition(itemPosition);
@ -129,11 +129,11 @@ public class VehicleParametersBottomSheet extends BasePreferenceBottomSheet {
}
});
adapter.setItems(Arrays.asList(preference.getEntries()));
adapter.setTitledItems(Arrays.asList(preference.getEntries()));
adapter.setListener(new HorizontalSelectionAdapter.HorizontalSelectionAdapterListener() {
@Override
public void onItemSelected(String item) {
selectedItem = item;
public void onItemSelected(HorizontalSelectionAdapter.HorizontalSelectionItem item) {
selectedItem = item.getTitle();
currentValue = preference.getValueFromEntries(selectedItem);
String currentValueStr = currentValue == 0.0f
? "" : df.format(currentValue + 0.01f);
@ -145,7 +145,7 @@ public class VehicleParametersBottomSheet extends BasePreferenceBottomSheet {
}
});
recyclerView.setAdapter(adapter);
adapter.setSelectedItem(selectedItem);
adapter.setSelectedItemByTitle(selectedItem);
return new BaseBottomSheetItem.Builder()
.setCustomView(mainView)
.create();