diff --git a/OsmAnd/res/layout/bottom_sheet_with_switch_divider_and_additional_button.xml b/OsmAnd/res/layout/bottom_sheet_with_switch_divider_and_additional_button.xml new file mode 100644 index 0000000000..f0ff17170d --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_with_switch_divider_and_additional_button.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/trip_recording_fragment.xml b/OsmAnd/res/layout/trip_recording_fragment.xml new file mode 100644 index 0000000000..a460193cd5 --- /dev/null +++ b/OsmAnd/res/layout/trip_recording_fragment.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index bbb71f001a..299e7aa5b2 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -12,6 +12,8 @@ --> + Show track on map + Start recording Announcement time Announcement time of different voice prompts depends on prompt type, current navigation speed and default navigation speed. Time and distance intervals diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 0f27fc6e26..007280144e 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -118,6 +118,7 @@ import net.osmand.plus.measurementtool.LoginBottomSheetFragment; import net.osmand.plus.measurementtool.MeasurementEditingContext; import net.osmand.plus.measurementtool.MeasurementToolFragment; import net.osmand.plus.measurementtool.SnapTrackWarningFragment; +import net.osmand.plus.monitoring.TripRecordingBottomSheet; import net.osmand.plus.render.RendererRegistry; import net.osmand.plus.resources.ResourceManager; import net.osmand.plus.routepreparationmenu.ChooseRouteFragment; @@ -2205,6 +2206,10 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven return getFragment(MeasurementToolFragment.TAG); } + public TripRecordingBottomSheet getTripRecordingBottomSheet() { + return getFragment(TripRecordingBottomSheet.TAG); + } + public ChooseRouteFragment getChooseRouteFragment() { return getFragment(ChooseRouteFragment.TAG); } @@ -2221,7 +2226,6 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven return getFragment(SnapTrackWarningFragment.TAG); } - @NonNull public TrackMenuFragment getTrackMenuFragment() { return getFragment(TrackMenuFragment.TAG); } diff --git a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java index 16c60911df..42bae2ab62 100644 --- a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java +++ b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java @@ -100,12 +100,12 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { public void updateLocation(Location location) { liveMonitoringHelper.updateLocation(location); } - + @Override public int getLogoResourceId() { return R.drawable.ic_action_gps_info; } - + @Override public Drawable getAssetResourceImage() { return app.getUIUtilities().getIcon(R.drawable.trip_recording); @@ -140,7 +140,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { private void registerWidget(MapActivity activity) { MapInfoLayer layer = activity.getMapLayers().getMapInfoLayer(); monitoringControl = createMonitoringControl(activity); - + layer.registerSideWidget(monitoringControl, R.drawable.ic_action_play_dark, R.string.map_widget_monitoring, "monitoring", false, 30); layer.recreateControls(); @@ -161,7 +161,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { } } } - + public static final int[] SECONDS = new int[] {0, 1, 2, 3, 5, 10, 15, 20, 30, 60, 90}; public static final int[] MINUTES = new int[] {2, 3, 5}; public static final int[] MAX_INTERVAL_TO_SEND_MINUTES = new int[] {1, 2, 5, 10, 15, 20, 30, 60, 90, 2 * 60, 3 * 60, 4 * 60, 6 * 60, 12 * 60, 24 * 60}; @@ -287,7 +287,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { controlDialog(map, true); } - + }); return monitoringControl; } @@ -422,7 +422,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { public void saveCurrentTrack() { saveCurrentTrack(null, null); } - + public void saveCurrentTrack(@Nullable final Runnable onComplete) { saveCurrentTrack(onComplete, null); } @@ -464,7 +464,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { SaveGPXBottomSheetFragment.showInstance(((FragmentActivity) a).getSupportFragmentManager(), result.getFilenames()); } } - + if (onComplete != null) { onComplete.run(); } @@ -505,15 +505,9 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { }; if (choice.value || map == null) { runnable.run(); - } else { - showIntervalChooseDialog(map, app.getString(R.string.save_track_interval_globally) + " : %s", - app.getString(R.string.save_track_to_gpx_globally), SECONDS, MINUTES, choice, vs, showTrackSelection, - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - runnable.run(); - } - }); + } else if (map instanceof FragmentActivity) { + FragmentActivity activity = (FragmentActivity) map; + TripRecordingBottomSheet.showInstance(activity.getSupportFragmentManager()); } } @@ -588,7 +582,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { tv.setText(String.format(patternMsg, s)); } }); - + for (int i = 0; i < secondsLength + minutesLength - 1; i++) { if (i < secondsLength) { if (v.value <= seconds[i] * 1000) { diff --git a/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java new file mode 100644 index 0000000000..f33f7dfa38 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java @@ -0,0 +1,273 @@ +package net.osmand.plus.monitoring; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.SpannableString; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.SwitchCompat; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.slider.RangeSlider; + +import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; +import net.osmand.plus.NavigationService; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.UiUtilities.DialogButtonType; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.MenuBottomSheetDialogFragment; +import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithDescription; +import net.osmand.plus.base.bottomsheetmenu.simpleitems.DividerSpaceItem; +import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.helpers.FontCache; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.track.TrackAppearanceFragment; + +import static net.osmand.plus.UiUtilities.CompoundButtonType.PROFILE_DEPENDENT; +import static net.osmand.plus.monitoring.OsmandMonitoringPlugin.MINUTES; +import static net.osmand.plus.monitoring.OsmandMonitoringPlugin.SECONDS; + +public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment { + + public static final String TAG = TripRecordingBottomSheet.class.getSimpleName(); + + private OsmandApplication app; + private OsmandSettings settings; + + private ImageView upDownBtn; + private SwitchCompat confirmEveryRun; + private TextView intervalValueView; + + private boolean infoExpanded; + + @Override + public void createMenuItems(Bundle savedInstanceState) { + app = requiredMyApplication(); + settings = app.getSettings(); + Context context = requireContext(); + + LayoutInflater inflater = UiUtilities.getInflater(context, nightMode); + View itemView = inflater.inflate(R.layout.trip_recording_fragment, null, false); + items.add(new BottomSheetItemWithDescription.Builder() + .setCustomView(itemView) + .create()); + + int padding = getResources().getDimensionPixelSize(R.dimen.content_padding_small); + final int paddingSmall = getResources().getDimensionPixelSize(R.dimen.content_padding_small); + + items.add(new DividerSpaceItem(context, padding)); + + 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); + + 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() { + @Override + public void onClick(View v) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + hide(); + SelectedGpxFile selectedGpxFile = app.getSavingTrackHelper().getCurrentTrack(); + TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile); + } + } + }); + + upDownBtn = itemView.findViewById(R.id.up_down_button); + upDownBtn.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + toggleInfoView(); + } + }); + + final int secondsLength = SECONDS.length; + final int minutesLength = MINUTES.length; + + intervalValueView = itemView.findViewById(R.id.interval_value); + updateIntervalLegend(); + + RangeSlider intervalSlider = itemView.findViewById(R.id.interval_slider); + intervalSlider.setValueTo(secondsLength + minutesLength - 1); + intervalSlider.addOnChangeListener(new RangeSlider.OnChangeListener() { + + @Override + public void onValueChange(@NonNull RangeSlider slider, float value, boolean fromUser) { + int progress = (int) value; + if (progress == 0) { + settings.SAVE_GLOBAL_TRACK_INTERVAL.set(0); + } else if (progress < secondsLength) { + settings.SAVE_GLOBAL_TRACK_INTERVAL.set(SECONDS[progress] * 1000); + } else { + settings.SAVE_GLOBAL_TRACK_INTERVAL.set(MINUTES[progress - secondsLength] * 60 * 1000); + } + updateIntervalLegend(); + } + }); + for (int i = 0; i < secondsLength + minutesLength; i++) { + if (i < secondsLength) { + if (settings.SAVE_GLOBAL_TRACK_INTERVAL.get() <= SECONDS[i] * 1000) { + intervalSlider.setValues((float) i); + break; + } + } else { + if (settings.SAVE_GLOBAL_TRACK_INTERVAL.get() <= MINUTES[i - secondsLength] * 1000 * 60) { + intervalSlider.setValues((float) i); + break; + } + } + } + boolean checked = !settings.SAVE_GLOBAL_TRACK_REMEMBER.get(); + confirmEveryRun = itemView.findViewById(R.id.confirm_every_run); + confirmEveryRun.setChecked(checked); + setBackgroundAndPadding(checked, paddingSmall); + confirmEveryRun.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setBackgroundAndPadding(isChecked, paddingSmall); + settings.SAVE_GLOBAL_TRACK_REMEMBER.set(!isChecked); + } + }); + + SwitchCompat showTrackOnMapButton = showTrackOnMapView.findViewById(R.id.switch_button); + showTrackOnMapButton.setChecked(app.getSelectedGpxHelper().getSelectedCurrentRecordingTrack() != null); + showTrackOnMapButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + app.getSelectedGpxHelper().selectGpxFile(app.getSavingTrackHelper().getCurrentGpx(), isChecked, false); + } + }); + UiUtilities.setupCompoundButton(showTrackOnMapButton, nightMode, PROFILE_DEPENDENT); + + updateUpDownBtn(); + } + + private void updateIntervalLegend() { + String text = getString(R.string.save_track_interval_globally); + String textValue; + int interval = settings.SAVE_GLOBAL_TRACK_INTERVAL.get(); + if (interval == 0) { + textValue = getString(R.string.int_continuosly); + } else { + int seconds = interval / 1000; + if (seconds <= SECONDS[SECONDS.length - 1]) { + textValue = seconds + " " + getString(R.string.int_seconds); + } else { + textValue = (seconds / 60) + " " + getString(R.string.int_min); + } + } + String textAll = getString(R.string.ltr_or_rtl_combine_via_colon, text, textValue); + Typeface typeface = FontCache.getRobotoMedium(app); + SpannableString spannableString = UiUtilities.createCustomFontSpannable(typeface, textAll, textValue); + intervalValueView.setText(spannableString); + } + + public void show() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.show(); + } + } + + public void hide() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.hide(); + } + } + + private void setBackgroundAndPadding(boolean isChecked, int paddingSmall) { + if (nightMode) { + confirmEveryRun.setBackgroundResource( + isChecked ? R.drawable.layout_bg_dark_solid : R.drawable.layout_bg_dark); + } else { + confirmEveryRun.setBackgroundResource( + isChecked ? R.drawable.layout_bg_solid : R.drawable.layout_bg); + } + confirmEveryRun.setPadding(paddingSmall, 0, paddingSmall, 0); + } + + private void updateUpDownBtn() { + int iconId = infoExpanded ? R.drawable.ic_action_arrow_down : R.drawable.ic_action_arrow_up; + upDownBtn.setImageDrawable(getContentIcon(iconId)); + } + + private void toggleInfoView() { + infoExpanded = !infoExpanded; + AndroidUiHelper.updateVisibility(confirmEveryRun, infoExpanded); + updateUpDownBtn(); + } + + @Override + protected boolean useVerticalButtons() { + return true; + } + + @Override + protected int getRightBottomButtonTextId() { + return R.string.start_recording; + } + + @Override + protected int getDismissButtonTextId() { + return R.string.shared_string_cancel; + } + + @Override + protected DialogButtonType getRightBottomButtonType() { + return DialogButtonType.PRIMARY; + } + + @Override + public int getSecondDividerHeight() { + return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_icon_margin); + } + + @Override + protected void onRightBottomButtonClick() { + app.getSavingTrackHelper().startNewSegment(); + settings.SAVE_GLOBAL_TRACK_TO_GPX.set(true); + app.startNavigationService(NavigationService.USED_BY_GPX); + dismiss(); + } + + @Nullable + public MapActivity getMapActivity() { + Activity activity = getActivity(); + if (activity instanceof MapActivity) { + return (MapActivity) activity; + } + return null; + } + + public static void showInstance(@NonNull FragmentManager fragmentManager) { + if (!fragmentManager.isStateSaved()) { + TripRecordingBottomSheet fragment = new TripRecordingBottomSheet(); + fragment.show(fragmentManager, TAG); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java b/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java index 43642bfe9c..9a89f67017 100644 --- a/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java +++ b/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java @@ -42,6 +42,7 @@ import net.osmand.plus.dialogs.GpxAppearanceAdapter; import net.osmand.plus.dialogs.GpxAppearanceAdapter.AppearanceListItem; import net.osmand.plus.dialogs.GpxAppearanceAdapter.GpxAppearanceAdapterType; import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.monitoring.TripRecordingBottomSheet; import net.osmand.plus.routepreparationmenu.cards.BaseCard; import net.osmand.plus.routepreparationmenu.cards.BaseCard.CardListener; import net.osmand.plus.settings.backend.CommonPreference; @@ -160,8 +161,13 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement public void handleOnBackPressed() { MapActivity mapActivity = getMapActivity(); if (mapActivity != null) { + TripRecordingBottomSheet fragment = mapActivity.getTripRecordingBottomSheet(); + if (fragment != null) { + fragment.show(); + } else { + mapActivity.launchPrevActivityIntent(); + } dismissImmediate(); - mapActivity.launchPrevActivityIntent(); } } }); @@ -405,7 +411,7 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement } } - public Drawable getTrackIcon(OsmandApplication app, String widthAttr, boolean showArrows, @ColorInt int color) { + public static Drawable getTrackIcon(OsmandApplication app, String widthAttr, boolean showArrows, @ColorInt int color) { int widthIconId = getWidthIconId(widthAttr); Drawable widthIcon = app.getUIUtilities().getPaintedIcon(widthIconId, color); @@ -423,7 +429,7 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement return UiUtilities.getLayeredIcon(transparencyIcon, widthIcon, strokeIcon); } - private Drawable getTransparencyIcon(OsmandApplication app, String widthAttr, @ColorInt int color) { + private static Drawable getTransparencyIcon(OsmandApplication app, String widthAttr, @ColorInt int color) { int transparencyIconId = getTransparencyIconId(widthAttr); int colorWithoutAlpha = UiUtilities.removeAlpha(color); int transparencyColor = UiUtilities.getColorWithAlpha(colorWithoutAlpha, 0.8f);