From 4ec42791365d754d7c72eb7617002cb7ea047eaa Mon Sep 17 00:00:00 2001 From: Skalii Date: Mon, 8 Feb 2021 17:31:14 +0200 Subject: [PATCH] add block statistics; show current recording status; many fixes; the basis is prepared for the implementation of the functionality --- .../btn_background_active_dark.xml | 6 + .../btn_background_inactive_dark.xml | 6 + .../btn_background_inactive_light.xml | 6 + .../btn_background_stroked_active_dark.xml | 6 + .../btn_background_stroked_active_light.xml | 6 + .../btn_background_stroked_inactive_dark.xml | 6 + .../btn_background_stroked_inactive_light.xml | 6 + .../drawable/btn_background_active_light.xml | 6 + .../layout/bottom_sheet_button_with_icon.xml | 104 ++-- .../layout/trip_recording_active_fragment.xml | 10 +- .../monitoring/OsmandMonitoringPlugin.java | 35 +- .../TripRecordingActiveBottomSheet.java | 444 +++++++++++++----- .../plus/track/GpxBlockStatisticsBuilder.java | 297 ++++++++++++ 13 files changed, 750 insertions(+), 188 deletions(-) create mode 100644 OsmAnd/res/drawable-mdpi/btn_background_active_dark.xml create mode 100644 OsmAnd/res/drawable-mdpi/btn_background_inactive_dark.xml create mode 100644 OsmAnd/res/drawable-mdpi/btn_background_inactive_light.xml create mode 100644 OsmAnd/res/drawable-mdpi/btn_background_stroked_active_dark.xml create mode 100644 OsmAnd/res/drawable-mdpi/btn_background_stroked_active_light.xml create mode 100644 OsmAnd/res/drawable-mdpi/btn_background_stroked_inactive_dark.xml create mode 100644 OsmAnd/res/drawable-mdpi/btn_background_stroked_inactive_light.xml create mode 100644 OsmAnd/res/drawable/btn_background_active_light.xml create mode 100644 OsmAnd/src/net/osmand/plus/track/GpxBlockStatisticsBuilder.java diff --git a/OsmAnd/res/drawable-mdpi/btn_background_active_dark.xml b/OsmAnd/res/drawable-mdpi/btn_background_active_dark.xml new file mode 100644 index 0000000000..88873ec3b4 --- /dev/null +++ b/OsmAnd/res/drawable-mdpi/btn_background_active_dark.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable-mdpi/btn_background_inactive_dark.xml b/OsmAnd/res/drawable-mdpi/btn_background_inactive_dark.xml new file mode 100644 index 0000000000..07c3aed3e9 --- /dev/null +++ b/OsmAnd/res/drawable-mdpi/btn_background_inactive_dark.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable-mdpi/btn_background_inactive_light.xml b/OsmAnd/res/drawable-mdpi/btn_background_inactive_light.xml new file mode 100644 index 0000000000..03ca0abfe5 --- /dev/null +++ b/OsmAnd/res/drawable-mdpi/btn_background_inactive_light.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable-mdpi/btn_background_stroked_active_dark.xml b/OsmAnd/res/drawable-mdpi/btn_background_stroked_active_dark.xml new file mode 100644 index 0000000000..1cdfa50a92 --- /dev/null +++ b/OsmAnd/res/drawable-mdpi/btn_background_stroked_active_dark.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable-mdpi/btn_background_stroked_active_light.xml b/OsmAnd/res/drawable-mdpi/btn_background_stroked_active_light.xml new file mode 100644 index 0000000000..543dd7ef1f --- /dev/null +++ b/OsmAnd/res/drawable-mdpi/btn_background_stroked_active_light.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable-mdpi/btn_background_stroked_inactive_dark.xml b/OsmAnd/res/drawable-mdpi/btn_background_stroked_inactive_dark.xml new file mode 100644 index 0000000000..f669c9d34f --- /dev/null +++ b/OsmAnd/res/drawable-mdpi/btn_background_stroked_inactive_dark.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable-mdpi/btn_background_stroked_inactive_light.xml b/OsmAnd/res/drawable-mdpi/btn_background_stroked_inactive_light.xml new file mode 100644 index 0000000000..feacf12888 --- /dev/null +++ b/OsmAnd/res/drawable-mdpi/btn_background_stroked_inactive_light.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_background_active_light.xml b/OsmAnd/res/drawable/btn_background_active_light.xml new file mode 100644 index 0000000000..4d8d8e82a5 --- /dev/null +++ b/OsmAnd/res/drawable/btn_background_active_light.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_button_with_icon.xml b/OsmAnd/res/layout/bottom_sheet_button_with_icon.xml index ba24cc289c..79b89d1213 100644 --- a/OsmAnd/res/layout/bottom_sheet_button_with_icon.xml +++ b/OsmAnd/res/layout/bottom_sheet_button_with_icon.xml @@ -1,59 +1,69 @@ - + android:layout_height="wrap_content"> + android:id="@+id/button_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clickable="true" + android:focusable="true" + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingStart="@dimen/content_padding_small" + android:paddingLeft="@dimen/content_padding_small" + android:paddingTop="@dimen/text_margin_small" + android:paddingEnd="@dimen/content_padding_small" + android:paddingRight="@dimen/content_padding_small" + android:paddingBottom="@dimen/text_margin_small" + tools:ignore="UselessParent"> - + android:layout_weight="1" + android:duplicateParentState="true" + android:orientation="vertical"> - + + + + + + + - - - \ No newline at end of file + \ No newline at end of file diff --git a/OsmAnd/res/layout/trip_recording_active_fragment.xml b/OsmAnd/res/layout/trip_recording_active_fragment.xml index 4f45d49fc9..df687a1bc5 100644 --- a/OsmAnd/res/layout/trip_recording_active_fragment.xml +++ b/OsmAnd/res/layout/trip_recording_active_fragment.xml @@ -30,14 +30,17 @@ osmand:typeface="@string/font_roboto_medium" /> + android:layout_marginRight="@dimen/content_padding" + android:layout_marginBottom="@dimen/content_padding" /> choice, final ValueHolder v, @@ -569,11 +580,11 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) { String s; int progress = (int) value; - if(progress == 0) { + if (progress == 0) { s = uiCtx.getString(R.string.int_continuosly); v.value = 0; } else { - if(progress < secondsLength) { + if (progress < secondsLength) { s = seconds[progress] + " " + uiCtx.getString(R.string.int_seconds); v.value = seconds[progress] * 1000; } else { @@ -598,7 +609,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { } } } - + ll.setOrientation(LinearLayout.VERTICAL); ll.addView(tv); ll.addView(sliderContainer); diff --git a/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingActiveBottomSheet.java b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingActiveBottomSheet.java index 6234d58b18..5f5c662882 100644 --- a/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingActiveBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingActiveBottomSheet.java @@ -1,14 +1,18 @@ package net.osmand.plus.monitoring; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; import android.content.Context; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.util.TypedValue; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -22,8 +26,12 @@ import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.SwitchCompat; import androidx.core.content.ContextCompat; import androidx.fragment.app.FragmentManager; +import androidx.recyclerview.widget.RecyclerView; -import net.osmand.plus.GpxSelectionHelper; +import net.osmand.AndroidUtils; +import net.osmand.Location; +import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; +import net.osmand.plus.OsmAndLocationProvider; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; @@ -31,7 +39,9 @@ import net.osmand.plus.activities.MapActivity; import net.osmand.plus.base.MenuBottomSheetDialogFragment; import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithDescription; import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.helpers.FontCache; import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.track.GpxBlockStatisticsBuilder; import net.osmand.plus.track.TrackAppearanceFragment; import net.osmand.plus.widgets.TextViewEx; import net.osmand.util.Algorithms; @@ -44,179 +54,205 @@ public class TripRecordingActiveBottomSheet extends MenuBottomSheetDialogFragmen private OsmandApplication app; private OsmandSettings settings; + private SelectedGpxFile selectedGpxFile; + private GpxBlockStatisticsBuilder blockStatisticsBuilder; + private boolean wasTrackMonitored = false; + private boolean hasDataToSave = false; + private boolean searchingGPS = false; + + private View statusContainer; + + private final Handler handler = new Handler(); + private Runnable updatingGPS; + + public void setSelectedGpxFile(SelectedGpxFile selectedGpxFile) { + this.selectedGpxFile = selectedGpxFile; + } + + public void setWasTrackMonitored(boolean wasTrackMonitored) { + this.wasTrackMonitored = wasTrackMonitored; + } + + public void setHasDataToSave(boolean hasDataToSave) { + this.hasDataToSave = hasDataToSave; + } + + public void setSearchingGPS(boolean searchingGPS) { + this.searchingGPS = searchingGPS; + } @Override public void createMenuItems(Bundle savedInstanceState) { app = requiredMyApplication(); settings = app.getSettings(); - Context context = requireContext(); + LayoutInflater inflater = UiUtilities.getInflater(getContext(), nightMode); - LayoutInflater inflater = UiUtilities.getInflater(context, nightMode); View itemView = inflater.inflate(R.layout.trip_recording_active_fragment, null, false); items.add(new BottomSheetItemWithDescription.Builder() .setCustomView(itemView) .create()); - TextView statusTitle = itemView.findViewById(R.id.status); - statusTitle.setText(ItemType.SEARCHING_GPS.titleId); - statusTitle.setTextColor(ContextCompat.getColor(app, getSecondaryTextColorId())); - ImageView statusIcon = itemView.findViewById(R.id.icon_status); - Drawable statusDrawable = UiUtilities.tintDrawable( - AppCompatResources.getDrawable(app, ItemType.SEARCHING_GPS.iconId), - ContextCompat.getColor(app, nightMode ? R.color.icon_color_default_dark : R.color.icon_color_default_light) - ); - statusIcon.setImageDrawable(statusDrawable); - View buttonClear = itemView.findViewById(R.id.button_clear); View buttonStart = itemView.findViewById(R.id.button_start); View buttonSave = itemView.findViewById(R.id.button_save); - View buttonPause = itemView.findViewById(R.id.button_pause); + final View buttonPause = itemView.findViewById(R.id.button_pause); View buttonStop = itemView.findViewById(R.id.button_stop); - createButton(buttonClear, ItemType.CLEAR_DATA, true, null); - createButton(buttonStart, ItemType.START_SEGMENT, true, null); - createButton(buttonSave, ItemType.SAVE, true, "17 min. ago"); - createButton(buttonPause, ItemType.PAUSE, true, null); - createButton(buttonStop, ItemType.STOP, true, null); + createItem(buttonClear, ItemType.CLEAR_DATA, hasDataToSave, null); + createItem(buttonStart, ItemType.START_SEGMENT, wasTrackMonitored, null); + createItem(buttonSave, ItemType.SAVE, hasDataToSave, "..."); + createItem(buttonPause, wasTrackMonitored ? ItemType.PAUSE : ItemType.RESUME, true, null); + createItem(buttonStop, ItemType.STOP, true, null); - LinearLayout showTrackOnMapView = itemView.findViewById(R.id.show_track_on_map); - TextView showTrackOnMapTitle = showTrackOnMapView.findViewById(R.id.title); - showTrackOnMapTitle.setText(R.string.show_track_on_map); + statusContainer = itemView.findViewById(R.id.status_container); + updateStatus(); - ImageView trackAppearanceIcon = showTrackOnMapView.findViewById(R.id.icon_after_divider); - - int color = settings.CURRENT_TRACK_COLOR.get(); - String width = settings.CURRENT_TRACK_WIDTH.get(); - boolean showArrows = settings.CURRENT_TRACK_SHOW_ARROWS.get(); - Drawable drawable = TrackAppearanceFragment.getTrackIcon(app, width, showArrows, color); - - trackAppearanceIcon.setImageDrawable(drawable); - trackAppearanceIcon.setOnClickListener(new View.OnClickListener() { + // todo example, need to check + buttonPause.findViewById(R.id.button_container).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - hide(); - GpxSelectionHelper.SelectedGpxFile selectedGpxFile = app.getSavingTrackHelper().getCurrentTrack(); - TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile, TripRecordingActiveBottomSheet.this); - } + boolean wasTrackMonitored = !settings.SAVE_GLOBAL_TRACK_TO_GPX.get(); + createItem(buttonPause, wasTrackMonitored ? ItemType.PAUSE : ItemType.RESUME, true, null); + TripRecordingActiveBottomSheet.this.wasTrackMonitored = wasTrackMonitored; + settings.SAVE_GLOBAL_TRACK_TO_GPX.set(wasTrackMonitored); + updateStatus(); } }); - final SwitchCompat showTrackOnMapButton = showTrackOnMapView.findViewById(R.id.switch_button); + RecyclerView statBlocks = itemView.findViewById(R.id.block_statistics); + blockStatisticsBuilder = new GpxBlockStatisticsBuilder(app, selectedGpxFile, null); + blockStatisticsBuilder.setBlocksView(statBlocks); + blockStatisticsBuilder.initStatBlocks(null, ContextCompat.getColor(app, getActiveTextColorId(nightMode)), nightMode); + + LinearLayout showTrackOnMapView = itemView.findViewById(R.id.show_track_on_map); + final LinearLayout basicItemBody = showTrackOnMapView.findViewById(R.id.basic_item_body); + + TextView showTrackTitle = basicItemBody.findViewById(R.id.title); + showTrackTitle.setText(ItemType.SHOW_TRACK.getTitleId()); + showTrackTitle.setTextColor(ContextCompat.getColor(app, getActiveIconColorId(nightMode))); + showTrackTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize(R.dimen.default_desc_text_size)); + Typeface typeface = FontCache.getFont(app, app.getResources().getString(R.string.font_roboto_medium)); + showTrackTitle.setTypeface(typeface); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + float letterSpacing = AndroidUtils.getFloatValueFromRes(app, R.dimen.description_letter_spacing); + showTrackTitle.setLetterSpacing(letterSpacing); + } + final SwitchCompat showTrackOnMapButton = basicItemBody.findViewById(R.id.switch_button); showTrackOnMapButton.setChecked(app.getSelectedGpxHelper().getSelectedCurrentRecordingTrack() != null); - View basicItem = itemView.findViewById(R.id.basic_item_body); - basicItem.setOnClickListener(new View.OnClickListener() { + UiUtilities.setupCompoundButton(showTrackOnMapButton, nightMode, PROFILE_DEPENDENT); + + final LinearLayout additionalButton = showTrackOnMapView.findViewById(R.id.additional_button); + View divider = additionalButton.getChildAt(0); + AndroidUiHelper.setVisibility(View.GONE, divider); + int marginS = app.getResources().getDimensionPixelSize(R.dimen.context_menu_padding_margin_small); + UiUtilities.setMargins(additionalButton, marginS, 0, 0, 0); + String width = settings.CURRENT_TRACK_WIDTH.get(); + boolean showArrows = settings.CURRENT_TRACK_SHOW_ARROWS.get(); + int color = settings.CURRENT_TRACK_COLOR.get(); + Drawable appearanceDrawable = TrackAppearanceFragment.getTrackIcon(app, width, showArrows, color); + AppCompatImageView appearanceIcon = additionalButton.findViewById(R.id.icon_after_divider); + int marginTrackIconH = app.getResources().getDimensionPixelSize(R.dimen.content_padding_small); + UiUtilities.setMargins(appearanceIcon, marginTrackIconH, 0, marginTrackIconH, 0); + appearanceIcon.setImageDrawable(appearanceDrawable); + additionalButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (showTrackOnMapButton.isChecked()) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + hide(); + SelectedGpxFile selectedGpxFile = app.getSavingTrackHelper().getCurrentTrack(); + TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile, TripRecordingActiveBottomSheet.this); + } + } + } + }); + createItem(additionalButton, ItemType.APPEARANCE, showTrackOnMapButton.isChecked(), null); + setShowOnMapBackgroundInactive(basicItemBody, app, showTrackOnMapButton.isChecked(), nightMode); + basicItemBody.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { boolean checked = !showTrackOnMapButton.isChecked(); showTrackOnMapButton.setChecked(checked); app.getSelectedGpxHelper().selectGpxFile(app.getSavingTrackHelper().getCurrentGpx(), checked, false); + setShowOnMapBackgroundInactive(basicItemBody, app, checked, nightMode); + createItem(additionalButton, ItemType.APPEARANCE, checked, null); } }); - UiUtilities.setupCompoundButton(showTrackOnMapButton, nightMode, PROFILE_DEPENDENT); - } - private void createButton(View view, ItemType type, boolean enabled, @Nullable String description) { + private void updateStatus() { + TextView statusTitle = statusContainer.findViewById(R.id.text_status); + AppCompatImageView statusIcon = statusContainer.findViewById(R.id.icon_status); + ItemType status = searchingGPS ? ItemType.SEARCHING_GPS : !wasTrackMonitored ? ItemType.ON_PAUSE : ItemType.RECORDING; + statusTitle.setText(status.getTitleId()); + int colorText = status.equals(ItemType.SEARCHING_GPS) ? getSecondaryTextColorId(nightMode) : getOsmandIconColorId(nightMode); + statusTitle.setTextColor(ContextCompat.getColor(app, colorText)); + int colorDrawable = ContextCompat.getColor(app, + status.equals(ItemType.SEARCHING_GPS) ? getSecondaryIconColorId(nightMode) : getOsmandIconColorId(nightMode)); + Drawable statusDrawable = UiUtilities.tintDrawable(AppCompatResources.getDrawable(app, status.getIconId()), colorDrawable); + statusIcon.setImageDrawable(statusDrawable); + } - Context ctx = view.getContext(); + private void createItem(View view, ItemType type, boolean enabled, @Nullable String description) { + view.setTag(type); + LinearLayout button = view.findViewById(R.id.button_container); - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { - view.setBackground(AppCompatResources.getDrawable(ctx, nightMode ? R.drawable.dlg_btn_secondary_dark : R.drawable.dlg_btn_secondary_light)); - } else { - view.setBackgroundDrawable(AppCompatResources.getDrawable(ctx, nightMode ? R.drawable.dlg_btn_secondary_dark : R.drawable.dlg_btn_secondary_light)); - } - - TextViewEx title = view.findViewById(R.id.title); - TextViewEx desc = view.findViewById(R.id.desc); AppCompatImageView icon = view.findViewById(R.id.icon); - - title.setText(type.getTitleId()); - title.setTextColor(ContextCompat.getColor(ctx, type == ItemType.CLEAR_DATA ? R.color.color_osm_edit_delete - : enabled ? getActiveTextColorId() : getSecondaryTextColorId())); - - Drawable tintDrawable = UiUtilities.tintDrawable( - AppCompatResources.getDrawable(ctx, type.iconId), - ContextCompat.getColor(ctx, type == ItemType.CLEAR_DATA ? R.color.color_osm_edit_delete - : enabled ? getActiveIconColorId() : getSecondaryIconColorId()) - ); - icon.setBackgroundDrawable(tintDrawable); - - boolean isShowDesc = !Algorithms.isBlank(description); - int marginSingle = app.getResources().getDimensionPixelSize(R.dimen.context_menu_padding_margin_medium); - AndroidUiHelper.updateVisibility(desc, isShowDesc); - UiUtilities.setMargins(title, 0, isShowDesc ? 0 : marginSingle, 0, isShowDesc ? 0 : marginSingle); - desc.setText(description); - } - - enum ItemType { - SEARCHING_GPS(R.string.searching_gps, R.drawable.ic_action_gps_info), - RECORDING(R.string.recording_default_name, R.drawable.ic_action_track_recordable), - ON_PAUSE(R.string.on_pause, R.drawable.ic_pause), - CLEAR_DATA(R.string.clear_recorded_data, R.drawable.ic_action_delete_dark), - START_SEGMENT(R.string.gpx_start_new_segment, R.drawable.ic_action_new_segment), - SAVE(R.string.shared_string_save, R.drawable.ic_action_save_to_file), - PAUSE(R.string.shared_string_pause, R.drawable.ic_pause), - STOP(R.string.shared_string_control_stop, R.drawable.ic_action_rec_stop); - - @StringRes - private int titleId; - @DrawableRes - private int iconId; - - ItemType(@StringRes int titleId, @DrawableRes int iconId) { - this.titleId = titleId; - this.iconId = iconId; + if (icon != null) { + type.setTintedIcon(icon, app, enabled, false, nightMode); } - public int getTitleId() { - return titleId; + TextView title = view.findViewById(R.id.title); + if (title != null) { + title.setText(type.getTitleId()); + type.setTextColor(title, app, enabled, false, nightMode); } - public int getIconId() { - return iconId; + setItemBackgroundInactive(button != null ? button : (LinearLayout) view, app, nightMode); + type.changeOnTouch(button != null ? button : (LinearLayout) view, icon, title, app, enabled, nightMode); + + TextViewEx desc = view.findViewById(R.id.desc); + if (desc != null) { + boolean isShowDesc = !Algorithms.isBlank(description); + int marginDesc = isShowDesc ? 0 : app.getResources().getDimensionPixelSize(R.dimen.context_menu_padding_margin_medium); + AndroidUiHelper.updateVisibility(desc, isShowDesc); + UiUtilities.setMargins(title, 0, marginDesc, 0, marginDesc); + desc.setText(description); } } - @ColorRes - protected int getActiveTextColorId() { - return nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light; - } - - @ColorRes - protected int getSecondaryTextColorId() { - return nightMode ? R.color.text_color_secondary_dark : R.color.text_color_secondary_light; - } - - @ColorRes - protected int getActiveIconColorId() { - return nightMode ? R.color.icon_color_active_dark : R.color.icon_color_active_light; - } - - @ColorRes - protected int getSecondaryIconColorId() { - return nightMode ? R.color.icon_color_secondary_dark : R.color.icon_color_secondary_light; - } - - @ColorRes - protected int getOsmandIconColorId() { - return nightMode ? R.color.icon_color_osmand_dark : R.color.icon_color_osmand_light; + @Override + public void onResume() { + super.onResume(); + blockStatisticsBuilder.runUpdatingStatBlocks(); + runUpdatingGPS(); } @Override - protected int getDismissButtonHeight() { - return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_cancel_button_height); + public void onPause() { + super.onPause(); + blockStatisticsBuilder.stopUpdatingStatBlocks(); + stopUpdatingGPS(); } - @Override - protected int getDismissButtonTextId() { - return R.string.shared_string_close; + public void stopUpdatingGPS() { + handler.removeCallbacks(updatingGPS); } - @Override - protected boolean useVerticalButtons() { - return true; + public void runUpdatingGPS() { + updatingGPS = new Runnable() { + @Override + public void run() { + int interval = app.getSettings().SAVE_GLOBAL_TRACK_INTERVAL.get(); + OsmAndLocationProvider locationProvider = app.getLocationProvider(); + Location lastKnownLocation = locationProvider.getLastKnownLocation(); + searchingGPS = lastKnownLocation == null; + updateStatus(); + handler.postDelayed(this, Math.max(1000, interval)); + } + }; + handler.post(updatingGPS); } @Nullable @@ -235,9 +271,165 @@ public class TripRecordingActiveBottomSheet extends MenuBottomSheetDialogFragmen } } - public static void showInstance(@NonNull FragmentManager fragmentManager) { + private static void setItemBackgroundActive(LinearLayout view, Context context, boolean nightMode) { + Drawable background = AppCompatResources.getDrawable(context, + nightMode ? R.drawable.btn_background_active_dark : R.drawable.btn_background_active_light); + view.setBackgroundDrawable(background); + } + + private static void setItemBackgroundInactive(LinearLayout view, Context context, boolean nightMode) { + Drawable background = AppCompatResources.getDrawable(context, + nightMode ? R.drawable.btn_background_inactive_dark : R.drawable.btn_background_inactive_light); + view.setBackgroundDrawable(background); + } + + private static void setShowOnMapBackgroundActive(LinearLayout view, Context context, boolean checked, boolean nightMode) { + Drawable background = AppCompatResources.getDrawable(context, + nightMode ? checked ? R.drawable.btn_background_active_dark : R.drawable.btn_background_stroked_active_dark + : checked ? R.drawable.btn_background_active_light : R.drawable.btn_background_stroked_active_light); + view.setBackgroundDrawable(background); + } + + private static void setShowOnMapBackgroundInactive(LinearLayout view, Context context, boolean checked, boolean nightMode) { + Drawable background = AppCompatResources.getDrawable(context, + nightMode ? checked ? R.drawable.btn_background_inactive_dark : R.drawable.btn_background_stroked_inactive_dark + : checked ? R.drawable.btn_background_inactive_light : R.drawable.btn_background_stroked_inactive_light); + view.setBackgroundDrawable(background); + } + + enum ItemType { + SHOW_TRACK(R.string.shared_string_show_on_map, null), + APPEARANCE(null, null), + SEARCHING_GPS(R.string.searching_gps, R.drawable.ic_action_gps_info), + RECORDING(R.string.recording_default_name, R.drawable.ic_action_track_recordable), + ON_PAUSE(R.string.on_pause, R.drawable.ic_pause), + CLEAR_DATA(R.string.clear_recorded_data, R.drawable.ic_action_delete_dark), + START_SEGMENT(R.string.gpx_start_new_segment, R.drawable.ic_action_new_segment), + SAVE(R.string.shared_string_save, R.drawable.ic_action_save_to_file), + PAUSE(R.string.shared_string_pause, R.drawable.ic_pause), + RESUME(R.string.shared_string_resume, R.drawable.ic_play_dark), + STOP(R.string.shared_string_control_stop, R.drawable.ic_action_rec_stop); + + @StringRes + private final Integer titleId; + @DrawableRes + private final Integer iconId; + + ItemType(@Nullable @StringRes Integer titleId, @Nullable @DrawableRes Integer iconId) { + this.titleId = titleId; + this.iconId = iconId; + } + + public Integer getTitleId() { + return titleId; + } + + public Integer getIconId() { + return iconId; + } + + public void setTextColor(TextView tv, Context context, boolean enabled, boolean pressed, boolean nightMode) { + if (tv != null) { + tv.setTextColor(ContextCompat.getColor(context, + enabled ? pressed ? getPressedColorId(nightMode) + : equals(ItemType.CLEAR_DATA) ? R.color.color_osm_edit_delete + : getActiveTextColorId(nightMode) : getSecondaryTextColorId(nightMode))); + } + } + + public void setTintedIcon(AppCompatImageView iv, Context context, boolean enabled, boolean pressed, boolean nightMode) { + if (iv != null) { + int iconColor = ContextCompat.getColor(context, + enabled ? pressed ? getPressedColorId(nightMode) + : equals(ItemType.CLEAR_DATA) ? R.color.color_osm_edit_delete + : getActiveIconColorId(nightMode) : getSecondaryIconColorId(nightMode)); + Drawable icon = UiUtilities.createTintedDrawable(context, iconId, iconColor); + iv.setImageDrawable(icon); + } + } + + @SuppressLint("ClickableViewAccessibility") + private void changeOnTouch(final LinearLayout button, @Nullable final AppCompatImageView iv, @Nullable final TextView tv, + final Context context, final boolean enabled, final boolean nightMode) { + button.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (enabled) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + setItemBackgroundActive(button, context, nightMode); + setTintedIcon(iv, context, enabled, true, nightMode); + setTextColor(tv, context, enabled, true, nightMode); + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + setItemBackgroundInactive(button, context, nightMode); + setTintedIcon(iv, context, enabled, false, nightMode); + setTextColor(tv, context, enabled, false, nightMode); + break; + } + } + } + return false; + } + }); + } + } + + @ColorRes + private static int getActiveTextColorId(boolean nightMode) { + return nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light; + } + + @ColorRes + private static int getSecondaryTextColorId(boolean nightMode) { + return nightMode ? R.color.text_color_secondary_dark : R.color.text_color_secondary_light; + } + + @ColorRes + private static int getActiveIconColorId(boolean nightMode) { + return nightMode ? R.color.icon_color_active_dark : R.color.icon_color_active_light; + } + + @ColorRes + private static int getSecondaryIconColorId(boolean nightMode) { + return nightMode ? R.color.icon_color_secondary_dark : R.color.icon_color_secondary_light; + } + + @ColorRes + private static int getOsmandIconColorId(boolean nightMode) { + return nightMode ? R.color.icon_color_osmand_dark : R.color.icon_color_osmand_light; + } + + @ColorRes + private static int getPressedColorId(boolean nightMode) { + return nightMode ? R.color.active_buttons_and_links_text_dark : R.color.active_buttons_and_links_text_light; + } + + @Override + protected int getDismissButtonHeight() { + return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_cancel_button_height); + } + + @Override + protected int getDismissButtonTextId() { + return R.string.shared_string_close; + } + + @Override + protected boolean useVerticalButtons() { + return true; + } + + public static void showInstance(@NonNull FragmentManager fragmentManager, SelectedGpxFile selectedGpxFile, + boolean wasTrackMonitored, boolean hasDataToSave, boolean searchingGPS) { if (!fragmentManager.isStateSaved()) { TripRecordingActiveBottomSheet fragment = new TripRecordingActiveBottomSheet(); + fragment.setSelectedGpxFile(selectedGpxFile); + fragment.setWasTrackMonitored(wasTrackMonitored); + fragment.setHasDataToSave(hasDataToSave); + fragment.setSearchingGPS(searchingGPS); fragment.show(fragmentManager, TAG); } } diff --git a/OsmAnd/src/net/osmand/plus/track/GpxBlockStatisticsBuilder.java b/OsmAnd/src/net/osmand/plus/track/GpxBlockStatisticsBuilder.java new file mode 100644 index 0000000000..d132de4ede --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/track/GpxBlockStatisticsBuilder.java @@ -0,0 +1,297 @@ +package net.osmand.plus.track; + +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import net.osmand.AndroidUtils; +import net.osmand.GPXUtilities.GPXTrackAnalysis; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayGroup; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; +import net.osmand.plus.GpxSelectionHelper.GpxDisplayItemType; +import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; +import net.osmand.plus.OsmAndFormatter; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType; +import net.osmand.plus.myplaces.SegmentActionsListener; +import net.osmand.plus.widgets.TextViewEx; +import net.osmand.util.Algorithms; + +import java.util.ArrayList; +import java.util.List; + +import android.os.Handler; + +public class GpxBlockStatisticsBuilder { + + private final OsmandApplication app; + private RecyclerView blocksView; + private final SelectedGpxFile selectedGpxFile; + private final TrackDisplayHelper displayHelper; + private final GpxDisplayItemType[] filterTypes = {GpxDisplayItemType.TRACK_SEGMENT}; + + private BlockStatisticsAdapter bsAdapter; + private final List items = new ArrayList<>(); + + private final Handler handler = new Handler(); + private Runnable updatingStats; + + public GpxBlockStatisticsBuilder(OsmandApplication app, SelectedGpxFile selectedGpxFile, TrackDisplayHelper displayHelper) { + this.app = app; + this.selectedGpxFile = selectedGpxFile; + this.displayHelper = displayHelper; + } + + public void setBlocksView(RecyclerView blocksView) { + this.blocksView = blocksView; + } + + private GPXTrackAnalysis getAnalysis() { + return selectedGpxFile.getTrackAnalysis(app); + } + + public void initStatBlocks(@Nullable SegmentActionsListener actionsListener, @ColorInt int activeColor, boolean nightMode) { + initItems(); + if (Algorithms.isEmpty(items)) { + AndroidUiHelper.updateVisibility(blocksView, false); + } else { + bsAdapter = new BlockStatisticsAdapter(actionsListener, activeColor, nightMode); + bsAdapter.setItems(items); + blocksView.setLayoutManager(new LinearLayoutManager(app, LinearLayoutManager.HORIZONTAL, false)); + blocksView.setAdapter(bsAdapter); + } + } + + public void stopUpdatingStatBlocks() { + handler.removeCallbacks(updatingStats); + } + + public void runUpdatingStatBlocks() { + updatingStats = new Runnable() { + @Override + public void run() { + Log.d("BlockStatisticsBuilder", "run: working"); + if (bsAdapter != null) { + initItems(); + bsAdapter.setItems(items); + } + int interval = app.getSettings().SAVE_GLOBAL_TRACK_INTERVAL.get(); + handler.postDelayed(this, Math.max(1000, interval)); + } + }; + handler.post(updatingStats); + } + + public void initItems() { + GPXTrackAnalysis analysis = getAnalysis(); + float totalDistance = analysis.totalDistance; + float timeSpan = analysis.timeSpan; + String asc = OsmAndFormatter.getFormattedAlt(analysis.diffElevationUp, app); + String desc = OsmAndFormatter.getFormattedAlt(analysis.diffElevationDown, app); + String avg = OsmAndFormatter.getFormattedSpeed(analysis.avgSpeed, app); + String max = OsmAndFormatter.getFormattedSpeed(analysis.maxSpeed, app); + + items.clear(); + prepareData(analysis, app.getString(R.string.distance), OsmAndFormatter.getFormattedDistance(totalDistance, app), + R.drawable.ic_action_track_16, R.color.icon_color_default_light, GPXDataSetType.ALTITUDE, GPXDataSetType.SPEED, ItemType.ITEM_DISTANCE); + prepareData(analysis, app.getString(R.string.altitude_ascent), asc, + R.drawable.ic_action_arrow_up_16, R.color.gpx_chart_red, GPXDataSetType.SLOPE, null, ItemType.ITEM_ALTITUDE); + prepareData(analysis, app.getString(R.string.altitude_descent), desc, + R.drawable.ic_action_arrow_down_16, R.color.gpx_pale_green, GPXDataSetType.ALTITUDE, GPXDataSetType.SLOPE, ItemType.ITEM_ALTITUDE); + prepareData(analysis, app.getString(R.string.average_speed), avg, + R.drawable.ic_action_speed_16, R.color.icon_color_default_light, GPXDataSetType.SPEED, null, ItemType.ITEM_SPEED); + prepareData(analysis, app.getString(R.string.max_speed), max, + R.drawable.ic_action_max_speed_16, R.color.icon_color_default_light, GPXDataSetType.SPEED, null, ItemType.ITEM_SPEED); + prepareData(analysis, app.getString(R.string.shared_string_time_span), + Algorithms.formatDuration((int) (timeSpan / 1000), app.accessibilityEnabled()), + R.drawable.ic_action_time_span_16, R.color.icon_color_default_light, GPXDataSetType.SPEED, null, ItemType.ITEM_TIME); + } + + public void prepareData(GPXTrackAnalysis analysis, String title, String value, + @DrawableRes int imageResId, @ColorRes int imageColorId, + GPXDataSetType firstType, GPXDataSetType secondType, ItemType itemType) { + StatBlock statBlock = new StatBlock(title, value, imageResId, imageColorId, firstType, secondType, itemType); + switch (statBlock.itemType) { + case ITEM_DISTANCE: { + if (analysis.totalDistance != 0f) { + items.add(statBlock); + } + break; + } + case ITEM_ALTITUDE: { + if (analysis.hasElevationData) { + items.add(statBlock); + } + break; + } + case ITEM_SPEED: { + if (analysis.isSpeedSpecified()) { + items.add(statBlock); + } + break; + } + case ITEM_TIME: { + if (analysis.hasSpeedData) { + items.add(statBlock); + } + break; + } + } + } + + private void setImageDrawable(ImageView iv, @DrawableRes Integer resId, @ColorRes int color) { + Drawable icon = resId != null ? app.getUIUtilities().getIcon(resId, color) + : UiUtilities.tintDrawable(iv.getDrawable(), getResolvedColor(color)); + iv.setImageDrawable(icon); + } + + @ColorInt + protected int getResolvedColor(@ColorRes int colorId) { + return ContextCompat.getColor(app, colorId); + } + + public class StatBlock { + + private final String title; + private final String value; + private final int imageResId; + private final int imageColorId; + private final GPXDataSetType firstType; + private final GPXDataSetType secondType; + private final ItemType itemType; + + public StatBlock(String title, String value, @DrawableRes int imageResId, @ColorRes int imageColorId, + GPXDataSetType firstType, GPXDataSetType secondType, ItemType itemType) { + this.title = title; + this.value = value; + this.imageResId = imageResId; + this.imageColorId = imageColorId; + this.firstType = firstType; + this.secondType = secondType; + this.itemType = itemType; + } + } + + public enum ItemType { + ITEM_DISTANCE, + ITEM_ALTITUDE, + ITEM_SPEED, + ITEM_TIME; + } + + private class BlockStatisticsAdapter extends RecyclerView.Adapter { + + @ColorInt + private final int activeColor; + private final List statBlocks = new ArrayList<>(); + private final boolean nightMode; + private final SegmentActionsListener actionsListener; + + public BlockStatisticsAdapter(@Nullable SegmentActionsListener actionsListener, @ColorInt int activeColor, boolean nightMode) { + this.actionsListener = actionsListener; + this.activeColor = activeColor; + this.nightMode = nightMode; + } + + @Override + public int getItemCount() { + return statBlocks.size(); + } + + @NonNull + @Override + public BlockStatisticsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_gpx_stat_block, parent, false); + return new BlockStatisticsViewHolder(itemView); + } + + @Override + public void onBindViewHolder(final BlockStatisticsViewHolder holder, int position) { + final StatBlock item = statBlocks.get(position); + holder.valueText.setText(item.value); + holder.titleText.setText(item.title); + if (handler.hasCallbacks(updatingStats)) { + holder.titleText.setWidth(app.getResources().getDimensionPixelSize(R.dimen.map_route_buttons_width)); + } + holder.valueText.setTextColor(activeColor); + holder.titleText.setTextColor(app.getResources().getColor(R.color.text_color_secondary_light)); + if (actionsListener != null && displayHelper != null) { + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + List groups = displayHelper.getDisplayGroups(filterTypes); + GpxDisplayGroup group = null; + for (GpxDisplayGroup g : groups) { + if (g.isGeneralTrack()) { + group = g; + } + } + if (group == null && !groups.isEmpty()) { + group = groups.get(0); + } + if (group != null) { + GpxDisplayItem displayItem = group.getModifiableList().get(0); + if (displayItem != null && displayItem.analysis != null) { + ArrayList list = new ArrayList<>(); + if (displayItem.analysis.hasElevationData || displayItem.analysis.isSpeedSpecified() || displayItem.analysis.hasSpeedData) { + if (item.firstType != null) { + list.add(item.firstType); + } + if (item.secondType != null) { + list.add(item.secondType); + } + } + displayItem.chartTypes = list.size() > 0 ? list.toArray(new GPXDataSetType[0]) : null; + displayItem.locationOnMap = displayItem.locationStart; + actionsListener.openAnalyzeOnMap(displayItem); + } + } + } + }); + } + setImageDrawable(holder.imageView, item.imageResId, item.imageColorId); + AndroidUtils.setBackgroundColor(app, holder.divider, nightMode, R.color.divider_color_light, R.color.divider_color_dark); + AndroidUiHelper.updateVisibility(holder.divider, position != statBlocks.size() - 1); + } + + public void setItems(List statBlocks) { + this.statBlocks.clear(); + this.statBlocks.addAll(statBlocks); + notifyItemRangeChanged(0, getItemCount()); + } + } + + private class BlockStatisticsViewHolder extends RecyclerView.ViewHolder { + + private final TextViewEx valueText; + private final TextView titleText; + private final AppCompatImageView imageView; + private final View divider; + + public BlockStatisticsViewHolder(View view) { + super(view); + valueText = view.findViewById(R.id.value); + titleText = view.findViewById(R.id.title); + imageView = view.findViewById(R.id.image); + divider = view.findViewById(R.id.divider); + } + } +}