diff --git a/android/app/src/main/java/app/organicmaps/ChartController.java b/android/app/src/main/java/app/organicmaps/ChartController.java index acfbb704f..c6fad3ad8 100644 --- a/android/app/src/main/java/app/organicmaps/ChartController.java +++ b/android/app/src/main/java/app/organicmaps/ChartController.java @@ -10,6 +10,8 @@ import androidx.core.content.ContextCompat; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.bookmarks.data.BookmarkManager; import app.organicmaps.sdk.bookmarks.data.ElevationInfo; +import app.organicmaps.sdk.bookmarks.data.Track; +import app.organicmaps.sdk.bookmarks.data.TrackStatistics; import app.organicmaps.util.ThemeUtils; import app.organicmaps.util.Utils; import app.organicmaps.widget.placepage.AxisValueFormatter; @@ -31,9 +33,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -public class ChartController implements OnChartValueSelectedListener, - BookmarkManager.OnElevationActivePointChangedListener, - BookmarkManager.OnElevationCurrentPositionChangedListener +public class ChartController implements OnChartValueSelectedListener { private static final int CHART_Y_LABEL_COUNT = 3; private static final int CHART_X_LABEL_COUNT = 6; @@ -42,6 +42,7 @@ public class ChartController implements OnChartValueSelectedListener, private static final int CHART_AXIS_GRANULARITY = 100; private static final float CUBIC_INTENSITY = 0.2f; private static final int CURRENT_POSITION_OUT_OF_TRACK = -1; + private static final String ELEVATION_PROFILE_POINTS = "ELEVATION_PROFILE_POINTS"; @SuppressWarnings("NullableProblems") @NonNull @@ -62,6 +63,7 @@ public class ChartController implements OnChartValueSelectedListener, private final Context mContext; private long mTrackId = Utils.INVALID_ID; private boolean mCurrentPositionOutOfTrack = true; + private boolean mInformSelectedActivePointToCore = true; public ChartController(@NonNull Context context) { @@ -70,8 +72,6 @@ public class ChartController implements OnChartValueSelectedListener, public void initialize(@NonNull View view) { - BookmarkManager.INSTANCE.setElevationActivePointChangedListener(this); - BookmarkManager.INSTANCE.setElevationCurrentPositionChangedListener(this); final Resources resources = mContext.getResources(); mChart = view.findViewById(R.id.elevation_profile_chart); @@ -101,13 +101,6 @@ public class ChartController implements OnChartValueSelectedListener, initAxises(); } - @SuppressWarnings("unused") - public void destroy() - { - BookmarkManager.INSTANCE.setElevationActivePointChangedListener(null); - BookmarkManager.INSTANCE.setElevationCurrentPositionChangedListener(null); - } - private void highlightChartCurrentLocation() { mChart.highlightValues(Collections.singletonList(getCurrentPosHighlight()), @@ -142,15 +135,17 @@ public class ChartController implements OnChartValueSelectedListener, mChart.getAxisRight().setEnabled(false); } - public void setData(@NonNull ElevationInfo info) + public void setData(Track track) { - mTrackId = info.getId(); + mTrackId = track.getTrackId(); + ElevationInfo info = track.getElevationInfo(); + TrackStatistics stats = track.getTrackStatistics(); List values = new ArrayList<>(); for (ElevationInfo.Point point : info.getPoints()) - values.add(new Entry((float) point.getDistance(), point.getAltitude())); + values.add(new Entry((float) point.getDistance(), point.getAltitude(), point)); - LineDataSet set = new LineDataSet(values, "Elevation_profile_points"); + LineDataSet set = new LineDataSet(values, ELEVATION_PROFILE_POINTS); set.setMode(LineDataSet.Mode.CUBIC_BEZIER); set.setCubicIntensity(CUBIC_INTENSITY); set.setDrawFilled(true); @@ -173,8 +168,8 @@ public class ChartController implements OnChartValueSelectedListener, mChart.setData(data); mChart.animateX(CHART_ANIMATION_DURATION); - mMinAltitude.setText(Framework.nativeFormatAltitude(info.getMinAltitude())); - mMaxAltitude.setText(Framework.nativeFormatAltitude(info.getMaxAltitude())); + mMinAltitude.setText(Framework.nativeFormatAltitude(stats.getMinElevation())); + mMaxAltitude.setText(Framework.nativeFormatAltitude(stats.getMaxElevation())); highlightActivePointManually(); } @@ -192,7 +187,9 @@ public class ChartController implements OnChartValueSelectedListener, if (mTrackId == Utils.INVALID_ID) return; - BookmarkManager.INSTANCE.setElevationActivePoint(mTrackId, e.getX()); + if (mInformSelectedActivePointToCore) + BookmarkManager.INSTANCE.setElevationActivePoint(mTrackId, e.getX(), (ElevationInfo.Point) e.getData()); + mInformSelectedActivePointToCore = true; } @NonNull @@ -211,7 +208,6 @@ public class ChartController implements OnChartValueSelectedListener, highlightChartCurrentLocation(); } - @Override public void onCurrentPositionChanged() { if (mTrackId == Utils.INVALID_ID) @@ -222,7 +218,6 @@ public class ChartController implements OnChartValueSelectedListener, highlightActivePointManually(); } - @Override public void onElevationActivePointChanged() { if (mTrackId == Utils.INVALID_ID) @@ -234,6 +229,7 @@ public class ChartController implements OnChartValueSelectedListener, private void highlightActivePointManually() { Highlight highlight = getActivePoint(); + mInformSelectedActivePointToCore = false; mChart.highlightValue(highlight, true); } diff --git a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Bookmark.java b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Bookmark.java index 8b8a4705a..93f55e7e9 100644 --- a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Bookmark.java +++ b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Bookmark.java @@ -2,6 +2,7 @@ package app.organicmaps.sdk.bookmarks.data; import android.annotation.SuppressLint; import android.os.Parcel; +import androidx.annotation.ColorInt; import androidx.annotation.IntRange; import androidx.annotation.Keep; import androidx.annotation.NonNull; @@ -110,6 +111,14 @@ public class Bookmark extends MapObject mCategoryId = catId; } + public void setIconColor(@ColorInt int color) + { + Icon icon = new Icon(PredefinedColors.getPredefinedColorIndex(color), + BookmarkManager.INSTANCE.getBookmarkIcon(mBookmarkId)); + BookmarkManager.INSTANCE.notifyParametersUpdating(this, getName(), icon, getBookmarkDescription()); + mIcon = icon; + } + public void setParams(@NonNull String title, @Nullable Icon icon, @NonNull String description) { BookmarkManager.INSTANCE.notifyParametersUpdating(this, title, icon, description); diff --git a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/BookmarkManager.java b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/BookmarkManager.java index e683d4a56..128a90ef2 100644 --- a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/BookmarkManager.java +++ b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/BookmarkManager.java @@ -263,6 +263,12 @@ public enum BookmarkManager { return nativeUpdateBookmarkPlacePage(bmkId); } + @Nullable + public void updateTrackPlacePage(long trackId) + { + nativeUpdateTrackPlacePage(trackId); + } + @Nullable public BookmarkInfo getBookmarkInfo(long bmkId) { @@ -729,9 +735,9 @@ public enum BookmarkManager { return nativeGetElevationCurPositionDistance(trackId); } - public void setElevationActivePoint(long trackId, double distance) + public void setElevationActivePoint(long trackId, double distance, ElevationInfo.Point point) { - nativeSetElevationActivePoint(trackId, distance); + nativeSetElevationActivePoint(trackId, distance, point.getLatitude(), point.getLongitude()); } public double getElevationActivePointDistance(long trackId) @@ -739,9 +745,14 @@ public enum BookmarkManager { return nativeGetElevationActivePointDistance(trackId); } + private static native ElevationInfo.Point nativeGetElevationActivePointCoordinates(long trackId); + @Nullable private native Bookmark nativeUpdateBookmarkPlacePage(long bmkId); + @Nullable + private native void nativeUpdateTrackPlacePage(long trackId); + @Nullable private native BookmarkInfo nativeGetBookmarkInfo(long bmkId); @@ -892,14 +903,25 @@ public enum BookmarkManager { public static native void nativeRemoveElevationCurrentPositionChangedListener(); - private static native void nativeSetElevationActivePoint(long trackId, double distanceInMeters); + private static native void nativeSetElevationActivePoint(long trackId, double distanceInMeters, double latitude, + double longitude); private static native double nativeGetElevationActivePointDistance(long trackId); + public ElevationInfo.Point getElevationActivePointCoordinates(long trackId) + { + return nativeGetElevationActivePointCoordinates(trackId); + } + private static native void nativeSetElevationActiveChangedListener(); public static native void nativeRemoveElevationActiveChangedListener(); + public static native ElevationInfo nativeGetTrackElevationInfo(long trackId); + + public static native boolean nativeIsElevationInfoHasValue(long trackId); + + public static native TrackStatistics nativeGetTrackStatistics(long trackId); public interface BookmarksLoadingListener { default void onBookmarksLoadingStarted() {} diff --git a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/ElevationInfo.java b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/ElevationInfo.java index c16b78df3..f501c25f5 100644 --- a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/ElevationInfo.java +++ b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/ElevationInfo.java @@ -15,42 +15,19 @@ import java.util.List; @SuppressWarnings("unused") public class ElevationInfo implements PlacePageData { - private final long mId; - @NonNull - private final String mName; @NonNull private final List mPoints; - private final int mAscent; - private final int mDescent; - private final int mMinAltitude; - private final int mMaxAltitude; private final int mDifficulty; - private final long mDuration; - public ElevationInfo(long trackId, @NonNull String name, @NonNull Point[] points, int ascent, int descent, - int minAltitude, int maxAltitude, int difficulty, long duration) + public ElevationInfo(@NonNull Point[] points, int difficulty) { - mId = trackId; - mName = name; mPoints = Arrays.asList(points); - mAscent = ascent; - mDescent = descent; - mMinAltitude = minAltitude; - mMaxAltitude = maxAltitude; mDifficulty = difficulty; - mDuration = duration; } protected ElevationInfo(Parcel in) { - mId = in.readLong(); - mName = in.readString(); - mAscent = in.readInt(); - mDescent = in.readInt(); - mMinAltitude = in.readInt(); - mMaxAltitude = in.readInt(); mDifficulty = in.readInt(); - mDuration = in.readLong(); mPoints = readPoints(in); } @@ -62,53 +39,17 @@ public class ElevationInfo implements PlacePageData return points; } - public long getId() - { - return mId; - } - - @NonNull - public String getName() - { - return mName; - } - @NonNull public List getPoints() { return Collections.unmodifiableList(mPoints); } - public int getAscent() - { - return mAscent; - } - - public int getDescent() - { - return mDescent; - } - - public int getMinAltitude() - { - return mMinAltitude; - } - - public int getMaxAltitude() - { - return mMaxAltitude; - } - public int getDifficulty() { return mDifficulty; } - public long getDuration() - { - return mDuration; - } - @Override public int describeContents() { @@ -118,14 +59,7 @@ public class ElevationInfo implements PlacePageData @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(mId); - dest.writeString(mName); - dest.writeInt(mAscent); - dest.writeInt(mDescent); - dest.writeInt(mMinAltitude); - dest.writeInt(mMaxAltitude); dest.writeInt(mDifficulty); - dest.writeLong(mDuration); // All collections are deserialized AFTER non-collection and primitive type objects, // so collections must be always serialized at the end. dest.writeTypedList(mPoints); @@ -138,17 +72,23 @@ public class ElevationInfo implements PlacePageData { private final double mDistance; private final int mAltitude; + private final double mLatitude; + private final double mLongitude; - public Point(double distance, int altitude) + public Point(double distance, int altitude, double latitude, double longitude) { mDistance = distance; mAltitude = altitude; + mLatitude = latitude; + mLongitude = longitude; } protected Point(Parcel in) { mDistance = in.readDouble(); mAltitude = in.readInt(); + mLatitude = in.readDouble(); + mLongitude = in.readDouble(); } public static final Creator CREATOR = new Creator<>() { @@ -175,6 +115,16 @@ public class ElevationInfo implements PlacePageData return mAltitude; } + public double getLatitude() + { + return mLatitude; + } + + public double getLongitude() + { + return mLongitude; + } + @Override public int describeContents() { diff --git a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/MapObject.java b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/MapObject.java index 70e12cef7..fc097fe23 100644 --- a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/MapObject.java +++ b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/MapObject.java @@ -25,7 +25,7 @@ import java.util.Objects; public class MapObject implements PlacePageData { @Retention(RetentionPolicy.SOURCE) - @IntDef({POI, API_POINT, BOOKMARK, MY_POSITION, SEARCH}) + @IntDef({POI, API_POINT, BOOKMARK, MY_POSITION, SEARCH, TRACK}) public @interface MapObjectType {} @@ -34,6 +34,7 @@ public class MapObject implements PlacePageData public static final int BOOKMARK = 2; public static final int MY_POSITION = 3; public static final int SEARCH = 4; + public static final int TRACK = 5; @Retention(RetentionPolicy.SOURCE) @IntDef({OPENING_MODE_PREVIEW, OPENING_MODE_PREVIEW_PLUS, OPENING_MODE_DETAILS, OPENING_MODE_FULL}) @@ -315,6 +316,11 @@ public class MapObject implements PlacePageData return mMapObjectType == BOOKMARK; } + public final boolean isTrack() + { + return mMapObjectType == TRACK; + } + @Nullable public RoutePointInfo getRoutePointInfo() { diff --git a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Track.java b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Track.java index 1e713ad20..db73f6440 100644 --- a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Track.java +++ b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/Track.java @@ -1,21 +1,32 @@ package app.organicmaps.sdk.bookmarks.data; +import androidx.annotation.IntRange; import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import app.organicmaps.sdk.routing.RoutePointInfo; +import app.organicmaps.sdk.search.Popularity; import app.organicmaps.sdk.util.Distance; // Called from JNI. @Keep @SuppressWarnings("unused") -public class Track +public class Track extends MapObject { private final long mTrackId; - private final long mCategoryId; + private long mCategoryId; private final String mName; private final Distance mLength; - private final int mColor; + private int mColor; + @Nullable + private ElevationInfo mElevationInfo; + @Nullable + private TrackStatistics mTrackStatistics; Track(long trackId, long categoryId, String name, Distance length, int color) { + super(FeatureId.fromFeatureIdString("1:2:3"), TRACK, name, "", "", "", 0, 0, "", null, OPENING_MODE_PREVIEW_PLUS, + null, "", RoadWarningMarkType.UNKNOWN.ordinal(), null); mTrackId = trackId; mCategoryId = categoryId; mName = name; @@ -23,6 +34,34 @@ public class Track mColor = color; } + // used by JNI + Track(@NonNull FeatureId featureId, @IntRange(from = 0) long categoryId, @IntRange(from = 0) long trackId, + String title, @Nullable String secondaryTitle, @Nullable String subtitle, @Nullable String address, + @Nullable RoutePointInfo routePointInfo, @OpeningMode int openingMode, @NonNull Popularity popularity, + @NonNull String description, @Nullable String[] rawTypes, int color, Distance length, double lat, double lon) + { + super(featureId, TRACK, title, secondaryTitle, subtitle, address, lat, lon, "", routePointInfo, openingMode, + popularity, description, RoadWarningMarkType.UNKNOWN.ordinal(), rawTypes); + mTrackId = trackId; + mCategoryId = categoryId; + mColor = color; + mName = title; + mLength = length; + } + + // modifying this categoryId will not change the core data + // its just for temporary changes + public void setCategoryId(@NonNull long categoryId) + { + mCategoryId = categoryId; + } + + public void setColor(@NonNull int color) + { + mColor = color; + BookmarkManager.INSTANCE.changeTrackColor(getTrackId(), color); + } + public String getName() { return mName; @@ -52,4 +91,25 @@ public class Track { return BookmarkManager.INSTANCE.getTrackDescription(mTrackId); } + + public ElevationInfo getElevationInfo() + { + if (mElevationInfo != null) + return mElevationInfo; + mElevationInfo = BookmarkManager.nativeGetTrackElevationInfo(mTrackId); + return mElevationInfo; + } + + public boolean isElevationInfoHasValue() + { + return BookmarkManager.nativeIsElevationInfoHasValue(mTrackId); + } + + public TrackStatistics getTrackStatistics() + { + if (mTrackStatistics != null) + return mTrackStatistics; + mTrackStatistics = BookmarkManager.nativeGetTrackStatistics(mTrackId); + return mTrackStatistics; + } } diff --git a/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/TrackStatistics.java b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/TrackStatistics.java new file mode 100644 index 000000000..bfee37e89 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/sdk/bookmarks/data/TrackStatistics.java @@ -0,0 +1,57 @@ +package app.organicmaps.sdk.bookmarks.data; + +import androidx.annotation.Keep; + +// Used by JNI +@Keep +public class TrackStatistics +{ + private final double m_length; + private final double m_duration; + private final double m_ascent; + private final double m_descent; + private final int m_minElevation; + private final int m_maxElevation; + + @Keep + public TrackStatistics(double length, double duration, double ascent, double descent, int minElevation, + int maxElevation) + { + m_length = length; + m_duration = duration; + m_ascent = ascent; + m_descent = descent; + m_minElevation = minElevation; + m_maxElevation = maxElevation; + } + + public double getLength() + { + return m_length; + } + + public double getDuration() + { + return m_duration; + } + + public double getAscent() + { + return m_ascent; + } + + public double getDescent() + { + return m_descent; + } + + public int getMinElevation() + { + return m_minElevation; + } + + public int getMaxElevation() + { + return m_maxElevation; + } +} diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/AxisValueFormatter.java b/android/app/src/main/java/app/organicmaps/widget/placepage/AxisValueFormatter.java index d3542f370..0543be12b 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/AxisValueFormatter.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/AxisValueFormatter.java @@ -1,7 +1,7 @@ package app.organicmaps.widget.placepage; import androidx.annotation.NonNull; -import app.organicmaps.sdk.Framework; +import app.organicmaps.sdk.util.StringUtils; import com.github.mikephil.charting.charts.BarLineChartBase; import com.github.mikephil.charting.formatter.DefaultValueFormatter; @@ -20,6 +20,6 @@ public class AxisValueFormatter extends DefaultValueFormatter @Override public String getFormattedValue(float value) { - return Framework.nativeFormatAltitude(value); + return StringUtils.nativeFormatDistance(value).toString(mChart.getContext()); } } diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/ElevationProfileViewRenderer.java b/android/app/src/main/java/app/organicmaps/widget/placepage/ElevationProfileViewRenderer.java index d6a808555..bce8db144 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/ElevationProfileViewRenderer.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/ElevationProfileViewRenderer.java @@ -1,7 +1,6 @@ package app.organicmaps.widget.placepage; import android.content.Context; -import android.os.Bundle; import android.view.View; import android.widget.RelativeLayout; import android.widget.TextView; @@ -12,18 +11,19 @@ import app.organicmaps.ChartController; import app.organicmaps.R; import app.organicmaps.sdk.Framework; import app.organicmaps.sdk.bookmarks.data.ElevationInfo; +import app.organicmaps.sdk.bookmarks.data.Track; +import app.organicmaps.sdk.bookmarks.data.TrackStatistics; import app.organicmaps.sdk.util.UiUtils; -import app.organicmaps.sdk.widget.placepage.PlacePageData; import app.organicmaps.util.Utils; import java.util.Objects; -@SuppressWarnings("unused") // https://github.com/organicmaps/organicmaps/issues/2829 public class ElevationProfileViewRenderer implements PlacePageStateListener { // Must be correspond to map/elevation_info.hpp constants. private static final int MAX_DIFFICULTY_LEVEL = 3; private static final int UNKNOWN_DIFFICULTY = 0; - + @NonNull + private final View[] mDifficultyLevels = new View[MAX_DIFFICULTY_LEVEL]; @SuppressWarnings("NullableProblems") @NonNull private NestedScrollView mScrollView; @@ -45,8 +45,6 @@ public class ElevationProfileViewRenderer implements PlacePageStateListener @SuppressWarnings("NullableProblems") @NonNull private TextView mTime; - @NonNull - private final View[] mDifficultyLevels = new View[MAX_DIFFICULTY_LEVEL]; @SuppressWarnings("NullableProblems") @NonNull private ChartController mChartController; @@ -58,23 +56,7 @@ public class ElevationProfileViewRenderer implements PlacePageStateListener @SuppressWarnings("NullableProblems") @NonNull private View mTimeContainer; - - public void render(@NonNull PlacePageData data) - { - final Context context = mAscent.getContext(); - - mElevationInfo = (ElevationInfo) data; - mChartController.setData(mElevationInfo); - mTitle.setText(mElevationInfo.getName()); - setDifficulty(mElevationInfo.getDifficulty()); - mAscent.setText(formatDistance(context, mElevationInfo.getAscent())); - mDescent.setText(formatDistance(context, mElevationInfo.getDescent())); - mMaxAltitude.setText(formatDistance(context, mElevationInfo.getMaxAltitude())); - mMinAltitude.setText(formatDistance(context, mElevationInfo.getMinAltitude())); - UiUtils.hideIf(mElevationInfo.getDuration() == 0, mTimeContainer); - mTime.setText( - Utils.formatRoutingTime(mTitle.getContext(), (int) mElevationInfo.getDuration(), R.dimen.text_size_body_2)); - } + private View mTitleContainer; @NonNull private static String formatDistance(final Context context, int distance) @@ -82,6 +64,24 @@ public class ElevationProfileViewRenderer implements PlacePageStateListener return Framework.nativeFormatAltitude(distance); } + public void render(@NonNull Track track) + { + final Context context = mAscent.getContext(); + TrackStatistics stats = track.getTrackStatistics(); + + mElevationInfo = track.getElevationInfo(); + mChartController.setData(track); + UiUtils.hide(mTitleContainer); + mTitle.setText(track.getName()); + setDifficulty(mElevationInfo.getDifficulty()); + mAscent.setText(formatDistance(context, (int) stats.getAscent())); + mDescent.setText(formatDistance(context, (int) stats.getDescent())); + mMaxAltitude.setText(formatDistance(context, stats.getMaxElevation())); + mMinAltitude.setText(formatDistance(context, stats.getMinElevation())); + UiUtils.hide(mTimeContainer); + mTime.setText(Utils.formatRoutingTime(mAscent.getContext(), (int) stats.getDuration(), R.dimen.text_size_body_2)); + } + public void initialize(@Nullable View view) { Objects.requireNonNull(view); @@ -89,6 +89,7 @@ public class ElevationProfileViewRenderer implements PlacePageStateListener mChartController.initialize(view); mScrollView = (NestedScrollView) view; mTitle = view.findViewById(R.id.title); + mTitleContainer = view.findViewById(R.id.title_container); mAscent = view.findViewById(R.id.ascent); mDescent = view.findViewById(R.id.descent); mMaxAltitude = view.findViewById(R.id.max_altitude); @@ -123,21 +124,13 @@ public class ElevationProfileViewRenderer implements PlacePageStateListener mDifficultyLevels[i].setEnabled(true); } - public void onSave(@NonNull Bundle outState) + public void onChartElevationActivePointChanged() { - // outState.putParcelable(PlacePageUtils.EXTRA_PLACE_PAGE_DATA, mElevationInfo); + mChartController.onElevationActivePointChanged(); } - public void onRestore(@NonNull Bundle inState) + public void onChartCurrentPositionChanged() { - // mElevationInfo = BundleCompat.getParcelable(inState, PlacePageUtils.EXTRA_PLACE_PAGE_DATA, - // ElevationInfo.class); if (mElevationInfo != null) - // render(mElevationInfo); - } - - public void onHide() - { - mScrollView.scrollTo(0, 0); - mChartController.onHide(); + mChartController.onCurrentPositionChanged(); } } diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/FloatingMarkerView.java b/android/app/src/main/java/app/organicmaps/widget/placepage/FloatingMarkerView.java index a58ab35b1..d31e70030 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/FloatingMarkerView.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/FloatingMarkerView.java @@ -37,9 +37,6 @@ public class FloatingMarkerView extends RelativeLayout implements IMarker private TextView mAltitudeView; @SuppressWarnings("NullableProblems") @NonNull - private TextView mDistanceTextView; - @SuppressWarnings("NullableProblems") - @NonNull private TextView mDistanceValueView; @SuppressWarnings("NullableProblems") @NonNull @@ -93,7 +90,6 @@ public class FloatingMarkerView extends RelativeLayout implements IMarker mTextContentContainer = findViewById(R.id.floating_text_container); mFloatingTriangle = findViewById(R.id.floating_triangle); mImage = findViewById(R.id.image); - mDistanceTextView = findViewById(R.id.distance_text); mAltitudeView = findViewById(R.id.altitude); mDistanceValueView = findViewById(R.id.distance_value); } @@ -125,7 +121,7 @@ public class FloatingMarkerView extends RelativeLayout implements IMarker public void updateOffsets(@NonNull Entry entry, @NonNull Highlight highlight) { updateVertical(entry); - final float halfImg = Math.abs(mImage.getWidth()) / 2f; + final float halfImg = mImage.getResources().getDimensionPixelSize(R.dimen.elevation_profile_marker_width) / 2f; boolean isLeftToRightDirection = isInvertedOrder(highlight); mOffset = isLeftToRightDirection ? -getWidth() + halfImg : -halfImg; updateHorizontal(highlight); @@ -184,7 +180,6 @@ public class FloatingMarkerView extends RelativeLayout implements IMarker private void updatePointValues(@NonNull Entry entry) { - mDistanceTextView.setText(R.string.elevation_profile_distance); mDistanceValueView.setText( StringUtils.nativeFormatDistance(entry.getX()).toString(mDistanceValueView.getContext())); mAltitudeView.setText(Framework.nativeFormatAltitude(entry.getY())); diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtonFactory.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtonFactory.java index 5f3978a0a..8d4898b3c 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtonFactory.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtonFactory.java @@ -34,6 +34,11 @@ public class PlacePageButtonFactory titleId = R.string.delete; yield R.drawable.ic_bookmarks_on; } + case TRACK_DELETE -> + { + titleId = R.string.delete; + yield R.drawable.ic_delete; + } case ROUTE_FROM -> { titleId = R.string.p2p_from_here; diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java index cc98ca6c4..5664f5933 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageButtons.java @@ -136,6 +136,7 @@ public final class PlacePageButtons extends Fragment implements Observer mPlacePageDistanceToTopObserver = new Observer<>() { private float mPlacePageCornerRadius; @@ -120,7 +124,6 @@ public class PlacePageController bg.setCornerSize(mPlacePageCornerRadius); } }; - private final BottomSheetBehavior.BottomSheetCallback mDefaultBottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { @Override @@ -145,6 +148,18 @@ public class PlacePageController } }; + @NonNull + private static PlacePageButtons.ButtonType toPlacePageButton(@NonNull RoadWarningMarkType type) + { + return switch (type) + { + case DIRTY -> PlacePageButtons.ButtonType.ROUTE_AVOID_UNPAVED; + case FERRY -> PlacePageButtons.ButtonType.ROUTE_AVOID_FERRY; + case TOLL -> PlacePageButtons.ButtonType.ROUTE_AVOID_TOLL; + default -> throw new AssertionError("Unsupported road warning type: " + type); + }; + } + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @@ -202,18 +217,6 @@ public class PlacePageController ViewCompat.requestApplyInsets(mPlacePage); } - @NonNull - private static PlacePageButtons.ButtonType toPlacePageButton(@NonNull RoadWarningMarkType type) - { - return switch (type) - { - case DIRTY -> PlacePageButtons.ButtonType.ROUTE_AVOID_UNPAVED; - case FERRY -> PlacePageButtons.ButtonType.ROUTE_AVOID_FERRY; - case TOLL -> PlacePageButtons.ButtonType.ROUTE_AVOID_TOLL; - default -> throw new AssertionError("Unsupported road warning type: " + type); - }; - } - private void stopCustomPeekHeightAnimation() { if (mCustomPeekHeightAnimator != null && mCustomPeekHeightAnimator.isStarted()) @@ -415,6 +418,7 @@ public class PlacePageController switch (item) { case BOOKMARK_SAVE, BOOKMARK_DELETE -> onBookmarkBtnClicked(); + case TRACK_DELETE -> onTrackRemoveClicked(); case BACK -> onBackBtnClicked(); case ROUTE_FROM -> onRouteFromBtnClicked(); case ROUTE_TO -> onRouteToBtnClicked(); @@ -439,6 +443,48 @@ public class PlacePageController BookmarkManager.INSTANCE.addNewBookmark(mMapObject.getLat(), mMapObject.getLon()); } + private void onTrackRemoveClicked() + { + // mMapObject is set to null when the place page closes + // We don't want users to interact with the buttons when the PP is closing + if (mMapObject == null) + return; + showTrackDeleteAlertDialog(); + } + + void showTrackDeleteAlertDialog() + { + if (mMapObject == null) + return; + if (mAlertDialog != null) + { + mAlertDialog.dismiss(); + mAlertDialog.show(); + mViewModel.isAlertDialogShowing = true; + return; + } + mViewModel.isAlertDialogShowing = true; + mAlertDialog = new MaterialAlertDialogBuilder(requireContext(), R.style.MwmTheme_AlertDialog) + .setTitle("Would you like to delete " + mMapObject.getTitle() + "?") + .setCancelable(true) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton("delete", + (dialog, which) -> { + BookmarkManager.INSTANCE.deleteTrack(((Track) mMapObject).getTrackId()); + close(); + }) + .setOnDismissListener(dialog -> dismissAlertDialog()) + .show(); + } + + void dismissAlertDialog() + { + if (mAlertDialog == null) + return; + mAlertDialog.dismiss(); + mViewModel.isAlertDialogShowing = false; + } + private void onBackBtnClicked() { if (mMapObject == null) @@ -577,8 +623,12 @@ public class PlacePageController if (needToShowRoutingButtons && RoutingController.get().isStopPointAllowed()) buttons.add(PlacePageButtons.ButtonType.ROUTE_ADD); else + { buttons.add(mapObject.isBookmark() ? PlacePageButtons.ButtonType.BOOKMARK_DELETE : PlacePageButtons.ButtonType.BOOKMARK_SAVE); + if (mapObject.isTrack()) + buttons.add(PlacePageButtons.ButtonType.TRACK_DELETE); + } if (needToShowRoutingButtons) { @@ -610,6 +660,8 @@ public class PlacePageController // Place page will automatically open when the bottom sheet content is loaded so we can compute the peek height createPlacePageFragments(); updateButtons(mapObject, showBackButton, !mMapObject.isMyPosition()); + if (mViewModel.isAlertDialogShowing) + showTrackDeleteAlertDialog(); } else close(); diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java index 6d0091334..edc150685 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageView.java @@ -6,6 +6,7 @@ import static app.organicmaps.sdk.util.Utils.getLocalizedFeatureType; import static app.organicmaps.sdk.util.Utils.getTagValueLocalized; import android.content.Context; +import android.graphics.drawable.Drawable; import android.location.Location; import android.net.Uri; import android.os.Bundle; @@ -16,25 +17,39 @@ import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentFactory; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import app.organicmaps.MwmActivity; import app.organicmaps.MwmApplication; import app.organicmaps.R; +import app.organicmaps.bookmarks.BookmarksSharingHelper; +import app.organicmaps.bookmarks.ChooseBookmarkCategoryFragment; import app.organicmaps.downloader.DownloaderStatusIcon; import app.organicmaps.editor.OhState; import app.organicmaps.sdk.Framework; +import app.organicmaps.sdk.bookmarks.data.Bookmark; +import app.organicmaps.sdk.bookmarks.data.BookmarkCategory; +import app.organicmaps.sdk.bookmarks.data.BookmarkManager; +import app.organicmaps.sdk.bookmarks.data.BookmarkSharingResult; import app.organicmaps.sdk.bookmarks.data.DistanceAndAzimut; +import app.organicmaps.sdk.bookmarks.data.Icon; +import app.organicmaps.sdk.bookmarks.data.KmlFileType; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.bookmarks.data.Metadata; +import app.organicmaps.sdk.bookmarks.data.PredefinedColors; +import app.organicmaps.sdk.bookmarks.data.Track; import app.organicmaps.sdk.downloader.CountryItem; import app.organicmaps.sdk.downloader.MapManager; import app.organicmaps.sdk.editor.Editor; @@ -49,13 +64,17 @@ import app.organicmaps.sdk.util.StringUtils; import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.sdk.util.concurrency.UiThread; import app.organicmaps.sdk.widget.placepage.CoordinatesFormat; +import app.organicmaps.util.Graphics; import app.organicmaps.util.SharingUtils; import app.organicmaps.util.Utils; +import app.organicmaps.util.bottomsheet.MenuBottomSheetFragment; +import app.organicmaps.util.bottomsheet.MenuBottomSheetItem; import app.organicmaps.widget.ArrowView; import app.organicmaps.widget.placepage.sections.PlacePageBookmarkFragment; import app.organicmaps.widget.placepage.sections.PlacePageLinksFragment; import app.organicmaps.widget.placepage.sections.PlacePageOpeningHoursFragment; import app.organicmaps.widget.placepage.sections.PlacePagePhoneFragment; +import app.organicmaps.widget.placepage.sections.PlacePageTrackFragment; import app.organicmaps.widget.placepage.sections.PlacePageWikipediaFragment; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.button.MaterialButton; @@ -68,15 +87,19 @@ import java.util.Arrays; import java.util.List; public class PlacePageView extends Fragment - implements View.OnClickListener, View.OnLongClickListener, LocationListener, SensorListener, Observer + implements View.OnClickListener, View.OnLongClickListener, LocationListener, SensorListener, Observer, + ChooseBookmarkCategoryFragment.Listener, EditBookmarkFragment.EditBookmarkListener, + MenuBottomSheetFragment.MenuBottomSheetInterface, BookmarkManager.BookmarksSharingListener { private static final String PREF_COORDINATES_FORMAT = "coordinates_format"; private static final String BOOKMARK_FRAGMENT_TAG = "BOOKMARK_FRAGMENT_TAG"; + private static final String TRACK_FRAGMENT_TAG = "TRACK_FRAGMENT_TAG"; private static final String WIKIPEDIA_FRAGMENT_TAG = "WIKIPEDIA_FRAGMENT_TAG"; private static final String PHONE_FRAGMENT_TAG = "PHONE_FRAGMENT_TAG"; private static final String OPENING_HOURS_FRAGMENT_TAG = "OPENING_HOURS_FRAGMENT_TAG"; private static final String LINKS_FRAGMENT_TAG = "LINKS_FRAGMENT_TAG"; + private static final String TRACK_SHARE_MENU_ID = "TRACK_SHARE_MENU_ID"; private static final List visibleCoordsFormat = Arrays.asList(CoordinatesFormat.LatLonDMS, CoordinatesFormat.LatLonDecimal, CoordinatesFormat.OLCFull, @@ -124,12 +147,16 @@ public class PlacePageView extends Fragment private View mAddOrganisation; private View mAddPlace; private View mEditTopSpace; + private ImageView mColorIcon; + private TextView mTvCategory; + private ImageView mEditBookmark; // Data private CoordinatesFormat mCoordsFormat = CoordinatesFormat.LatLonDecimal; // Downloader`s stuff private DownloaderStatusIcon mDownloaderIcon; private MaterialTextView mDownloaderInfo; + private ActivityResultLauncher shareLauncher; private int mStorageCallbackSlot; @Nullable private CountryItem mCurrentCountry; @@ -185,6 +212,7 @@ public class PlacePageView extends Fragment @Nullable Bundle savedInstanceState) { mViewModel = new ViewModelProvider(requireActivity()).get(PlacePageViewModel.class); + shareLauncher = SharingUtils.RegisterLauncher(this); return inflater.inflate(R.layout.place_page, container, false); } @@ -232,8 +260,12 @@ public class PlacePageView extends Fragment mTvAddress.setOnLongClickListener(this); mTvAddress.setOnClickListener(this); + mColorIcon = mFrame.findViewById(R.id.item_icon); + mTvCategory = mFrame.findViewById(R.id.tv__category); + mEditBookmark = mFrame.findViewById(R.id.edit_Bookmark); + MaterialButton shareButton = mPreview.findViewById(R.id.share_button); - shareButton.setOnClickListener((v) -> SharingUtils.shareMapObject(requireContext(), mMapObject)); + shareButton.setOnClickListener(this::shareClickListener); final MaterialButton closeButton = mPreview.findViewById(R.id.close_button); closeButton.setOnClickListener((v) -> mPlacePageViewListener.onPlacePageRequestClose()); @@ -301,6 +333,7 @@ public class PlacePageView extends Fragment { super.onStart(); mViewModel.getMapObject().observe(requireActivity(), this); + BookmarkManager.INSTANCE.addSharingListener(this); MwmApplication.from(requireContext()).getLocationHelper().addListener(this); MwmApplication.from(requireContext()).getSensorHelper().addListener(this); } @@ -310,6 +343,7 @@ public class PlacePageView extends Fragment { super.onStop(); mViewModel.getMapObject().removeObserver(this); + BookmarkManager.INSTANCE.removeSharingListener(this); MwmApplication.from(requireContext()).getLocationHelper().removeListener(this); MwmApplication.from(requireContext()).getSensorHelper().removeListener(this); UiThread.cancelDelayedTasks(updateOpenState); @@ -334,6 +368,10 @@ public class PlacePageView extends Fragment refreshMyPosition(loc); else refreshDistanceToObject(loc); + UiUtils.hideIf(mMapObject.isTrack(), mFrame.findViewById(R.id.ll__place_latlon), + mFrame.findViewById(R.id.ll__place_open_in), mFrame.findViewById(R.id.ll__place_add), mEditTopSpace, + mTvAzimuth, mTvDistance, mAvDirection); + UiUtils.hideIf(MapObjectType.getMapObjectType(mMapObject) != MapObjectType.OTHER, mTvSubtitle); } private void updateViewFragment(Class controllerClass, String fragmentTag, @@ -375,6 +413,12 @@ public class PlacePageView extends Fragment mMapObject.isBookmark()); } + private void updateTrackView() + { + updateViewFragment(PlacePageTrackFragment.class, TRACK_FRAGMENT_TAG, R.id.place_page_track_fragment, + mMapObject.isTrack()); + } + private boolean hasWikipediaEntry() { final String wikipediaLink = mMapObject.getMetadata(Metadata.MetadataType.FMD_WIKIPEDIA); @@ -416,6 +460,180 @@ public class PlacePageView extends Fragment mToolbar.setTitle(mMapObject.getTitle()); setTextAndColorizeSubtitle(); UiUtils.setTextAndHideIfEmpty(mTvAddress, mMapObject.getAddress()); + refreshCategoryPreview(); + } + + void refreshCategoryPreview() + { + View categoryContainer = mFrame.findViewById(R.id.category_container); + switch (MapObjectType.getMapObjectType(mMapObject)) + { + case TRACK -> + { + Track track = (Track) mMapObject; + Drawable circle = + Graphics.drawCircle(track.getColor(), R.dimen.place_page_icon_size, requireContext().getResources()); + mColorIcon.setImageDrawable(circle); + mTvCategory.setText(BookmarkManager.INSTANCE.getCategoryById(track.getCategoryId()).getName()); + UiUtils.show(mColorIcon, mTvCategory, categoryContainer); + } + case BOOKMARK -> + { + Bookmark bookmark = (Bookmark) mMapObject; + Icon icon = bookmark.getIcon(); + if (icon != null) + { + Drawable circle = Graphics.drawCircleAndImage(icon.argb(), R.dimen.place_page_icon_size, + app.organicmaps.sdk.R.drawable.ic_bookmark_none, + R.dimen.place_page_icon_mark_size, requireContext()); + mColorIcon.setImageDrawable(circle); + mTvCategory.setText(BookmarkManager.INSTANCE.getCategoryById(bookmark.getCategoryId()).getName()); + UiUtils.show(mColorIcon, mTvCategory, categoryContainer); + } + } + case OTHER -> UiUtils.hide(mColorIcon, mTvCategory, categoryContainer); + } + mColorIcon.setOnClickListener(this::onClick); + mTvCategory.setOnClickListener(this::onClick); + mEditBookmark.setOnClickListener(this::onClick); + } + + void showColorDialog() + { + final Bundle args = new Bundle(); + final FragmentManager manager = getChildFragmentManager(); + String className = BookmarkColorDialogFragment.class.getName(); + final FragmentFactory factory = manager.getFragmentFactory(); + final BookmarkColorDialogFragment dialogFragment = + (BookmarkColorDialogFragment) factory.instantiate(getContext().getClassLoader(), className); + dialogFragment.setArguments(args); + + switch (MapObjectType.getMapObjectType(mMapObject)) + { + case TRACK -> + { + final Track mTrack = (Track) mMapObject; + args.putInt(BookmarkColorDialogFragment.ICON_COLOR, PredefinedColors.getPredefinedColorIndex(mTrack.getColor())); + dialogFragment.setOnColorSetListener((colorPos) -> { + int from = mTrack.getColor(); + int to = PredefinedColors.getColor(colorPos); + if (from == to) + return; + mViewModel.modifyMapObjectColorSilently(to); + Drawable circle = Graphics.drawCircle(to, R.dimen.place_page_icon_size, requireContext().getResources()); + mColorIcon.setImageDrawable(circle); + }); + dialogFragment.show(requireActivity().getSupportFragmentManager(), null); + } + case BOOKMARK -> + { + final Bookmark bookmark = (Bookmark) mMapObject; + args.putInt(BookmarkColorDialogFragment.ICON_COLOR, bookmark.getIcon().getColor()); + args.putInt(BookmarkColorDialogFragment.ICON_RES, bookmark.getIcon().getResId()); + dialogFragment.setOnColorSetListener((colorPos) -> { + int from = bookmark.getIcon().argb(); + int to = PredefinedColors.getColor(colorPos); + if (from == to) + return; + mViewModel.modifyMapObjectColorSilently(to); + Drawable circle = Graphics.drawCircleAndImage(to, R.dimen.place_page_icon_size, + app.organicmaps.sdk.R.drawable.ic_bookmark_none, + R.dimen.place_page_icon_mark_size, requireContext()); + mColorIcon.setImageDrawable(circle); + }); + dialogFragment.show(requireActivity().getSupportFragmentManager(), null); + } + } + } + + private void showCategoryList() + { + final Bundle args = new Bundle(); + final List categories = BookmarkManager.INSTANCE.getCategories(); + final FragmentManager manager = getChildFragmentManager(); + String className = ChooseBookmarkCategoryFragment.class.getName(); + final FragmentFactory factory = manager.getFragmentFactory(); + final ChooseBookmarkCategoryFragment frag = + (ChooseBookmarkCategoryFragment) factory.instantiate(getContext().getClassLoader(), className); + switch (MapObjectType.getMapObjectType(mMapObject)) + { + case TRACK -> + { + Track track = (Track) mMapObject; + BookmarkCategory currentCategory = BookmarkManager.INSTANCE.getCategoryById(track.getCategoryId()); + final int index = categories.indexOf(currentCategory); + args.putInt(ChooseBookmarkCategoryFragment.CATEGORY_POSITION, index); + frag.setArguments(args); + frag.show(manager, null); + } + case BOOKMARK -> + { + Bookmark bookmark = (Bookmark) mMapObject; + BookmarkCategory currentCategory = BookmarkManager.INSTANCE.getCategoryById(bookmark.getCategoryId()); + final int index = categories.indexOf(currentCategory); + args.putInt(ChooseBookmarkCategoryFragment.CATEGORY_POSITION, index); + frag.setArguments(args); + frag.show(manager, null); + } + } + } + + @Override + public void onCategoryChanged(@NonNull BookmarkCategory newCategory) + { + switch (MapObjectType.getMapObjectType(mMapObject)) + { + case TRACK -> + { + Track track = (Track) mMapObject; + BookmarkCategory previousCategory = BookmarkManager.INSTANCE.getCategoryById(track.getCategoryId()); + if (previousCategory == newCategory) + return; + BookmarkManager.INSTANCE.notifyCategoryChanging(track, newCategory.getId()); + mTvCategory.setText(newCategory.getName()); + mViewModel.modifyMapObjectCategoryIdSilently(newCategory.getId()); + } + case BOOKMARK -> + { + Bookmark bookmark = (Bookmark) mMapObject; + BookmarkCategory previousCategory = BookmarkManager.INSTANCE.getCategoryById(bookmark.getCategoryId()); + if (previousCategory == newCategory) + return; + mTvCategory.setText(newCategory.getName()); + mViewModel.modifyMapObjectCategoryIdSilently(newCategory.getId()); + } + } + } + + void showBookmarkEditFragment() + { + switch (MapObjectType.getMapObjectType(mMapObject)) + { + case TRACK -> + { + Track track = (Track) mMapObject; + final FragmentActivity activity = requireActivity(); + EditBookmarkFragment.editTrack(track.getCategoryId(), track.getTrackId(), activity, getChildFragmentManager(), + PlacePageView.this); + } + case BOOKMARK -> + { + Bookmark bookmark = (Bookmark) mMapObject; + final FragmentActivity activity = requireActivity(); + EditBookmarkFragment.editBookmark(bookmark.getCategoryId(), bookmark.getBookmarkId(), activity, + getChildFragmentManager(), PlacePageView.this); + } + } + } + + @Override + public void onBookmarkSaved(long bookmarkId, boolean movedFromCategory) + { + switch (MapObjectType.getMapObjectType(mMapObject)) + { + case TRACK -> BookmarkManager.INSTANCE.updateTrackPlacePage(bookmarkId); + case BOOKMARK -> BookmarkManager.INSTANCE.updateBookmarkPlacePage(bookmarkId); + } } private void refreshDetails() @@ -494,6 +712,7 @@ public class PlacePageView extends Fragment updateWikipediaView(); updateBookmarkView(); updatePhoneView(); + updateTrackView(); } private void refreshWiFi() @@ -532,6 +751,8 @@ public class PlacePageView extends Fragment private void refreshDistanceToObject(Location l) { + if (mMapObject.isTrack()) + return; UiUtils.showIf(l != null, mTvDistance); if (l == null) return; @@ -660,6 +881,12 @@ public class PlacePageView extends Fragment } else if (id == R.id.direction_frame) showBigDirection(); + else if (id == R.id.item_icon) + showColorDialog(); + else if (id == R.id.edit_Bookmark) + showBookmarkEditFragment(); + else if (id == R.id.tv__category) + showCategoryList(); } private void showBigDirection() @@ -818,7 +1045,7 @@ public class PlacePageView extends Fragment @Override public void onCompassUpdated(double north) { - if (mMapObject == null || mMapObject.isMyPosition()) + if (mMapObject == null || mMapObject.isMyPosition() || mMapObject.isTrack()) return; final Location location = MwmApplication.from(requireContext()).getLocationHelper().getSavedLocation(); @@ -840,6 +1067,50 @@ public class PlacePageView extends Fragment } } + void shareClickListener(View v) + { + if (mMapObject.isTrack()) + { + MenuBottomSheetFragment.newInstance(TRACK_SHARE_MENU_ID, getString(R.string.PP_track_bottom_sheet_title)) + .show(getChildFragmentManager(), TRACK_SHARE_MENU_ID); + } + else + SharingUtils.shareMapObject(requireContext(), mMapObject); + } + + private void onShareTrackSelected(long trackId, KmlFileType kmlFileType) + { + BookmarksSharingHelper.INSTANCE.prepareTrackForSharing(requireActivity(), trackId, kmlFileType); + } + + @Nullable + @Override + public ArrayList getMenuBottomSheetItems(String id) + { + return switch (id) + { + case TRACK_SHARE_MENU_ID -> getTrackShareMenuItems(); + default -> null; + }; + } + + public ArrayList getTrackShareMenuItems() + { + Track track = (Track) mMapObject; + ArrayList items = new ArrayList<>(); + items.add(new MenuBottomSheetItem(R.string.export_file, R.drawable.ic_file_kmz, + () -> onShareTrackSelected(track.getTrackId(), KmlFileType.Text))); + items.add(new MenuBottomSheetItem(R.string.export_file_gpx, R.drawable.ic_file_gpx, + () -> onShareTrackSelected(track.getTrackId(), KmlFileType.Gpx))); + return items; + } + + @Override + public void onPreparedFileForSharing(@NonNull BookmarkSharingResult result) + { + BookmarksSharingHelper.INSTANCE.onPreparedFileForSharing(requireActivity(), shareLauncher, result); + } + public interface PlacePageViewListener { // Called when the content has actually changed and we are ready to compute the peek height @@ -848,4 +1119,21 @@ public class PlacePageView extends Fragment void onPlacePageRequestToggleState(); void onPlacePageRequestClose(); } + + public enum MapObjectType + { + TRACK, + BOOKMARK, + OTHER; + + public static MapObjectType getMapObjectType(MapObject mapObject) + { + if (mapObject.isTrack()) + return TRACK; + else if (mapObject.isBookmark()) + return BOOKMARK; + else + return OTHER; + } + } } diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageViewModel.java b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageViewModel.java index 57ca04eb3..a8f4b852d 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageViewModel.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/PlacePageViewModel.java @@ -3,7 +3,10 @@ package app.organicmaps.widget.placepage; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import app.organicmaps.sdk.bookmarks.data.Bookmark; +import app.organicmaps.sdk.bookmarks.data.ElevationInfo; import app.organicmaps.sdk.bookmarks.data.MapObject; +import app.organicmaps.sdk.bookmarks.data.Track; import java.util.List; public class PlacePageViewModel extends ViewModel @@ -12,6 +15,7 @@ public class PlacePageViewModel extends ViewModel private final MutableLiveData mMapObject = new MutableLiveData<>(); private final MutableLiveData mPlacePageWidth = new MutableLiveData<>(); private final MutableLiveData mPlacePageDistanceToTop = new MutableLiveData<>(); + public boolean isAlertDialogShowing = false; public LiveData> getCurrentButtons() { @@ -33,6 +37,38 @@ public class PlacePageViewModel extends ViewModel mMapObject.setValue(mapObject); } + // These silent method are used to update data of map object silently without triggering + // the observer which refreshes everything and puts extra load on device in calculation + // which is not required + public void modifyMapObjectPointSilently(ElevationInfo.Point point) + { + if (mMapObject.getValue() == null) + return; + mMapObject.getValue().setLat(point.getLatitude()); + mMapObject.getValue().setLon(point.getLongitude()); + } + + public void modifyMapObjectCategoryIdSilently(long categoryId) + { + if (mMapObject.getValue() == null) + return; + switch (PlacePageView.MapObjectType.getMapObjectType(mMapObject.getValue())) + { + case TRACK -> ((Track) mMapObject.getValue()).setCategoryId(categoryId); + case BOOKMARK -> ((Bookmark) mMapObject.getValue()).setCategoryId(categoryId); + } + } + + public void modifyMapObjectColorSilently(int color) + { + if (mMapObject.getValue() == null) + return; + switch (PlacePageView.MapObjectType.getMapObjectType(mMapObject.getValue())) + { + case TRACK -> ((Track) mMapObject.getValue()).setColor(color); + case BOOKMARK -> ((Bookmark) mMapObject.getValue()).setIconColor(color); + } + } public MutableLiveData getPlacePageWidth() { return mPlacePageWidth; diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageBookmarkFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageBookmarkFragment.java index f200c0621..4411201a2 100644 --- a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageBookmarkFragment.java +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageBookmarkFragment.java @@ -16,23 +16,18 @@ import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import app.organicmaps.R; import app.organicmaps.sdk.bookmarks.data.Bookmark; -import app.organicmaps.sdk.bookmarks.data.BookmarkManager; import app.organicmaps.sdk.bookmarks.data.MapObject; import app.organicmaps.sdk.util.StringUtils; import app.organicmaps.sdk.util.UiUtils; import app.organicmaps.util.Utils; -import app.organicmaps.widget.placepage.EditBookmarkFragment; import app.organicmaps.widget.placepage.PlacePageViewModel; import com.google.android.material.textview.MaterialTextView; -public class PlacePageBookmarkFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener, - Observer, - EditBookmarkFragment.EditBookmarkListener +public class PlacePageBookmarkFragment extends Fragment implements View.OnLongClickListener, Observer { private View mFrame; private MaterialTextView mTvBookmarkNote; @@ -60,8 +55,6 @@ public class PlacePageBookmarkFragment extends Fragment implements View.OnClickL mFrame = view; mTvBookmarkNote = mFrame.findViewById(R.id.tv__bookmark_notes); mTvBookmarkNote.setOnLongClickListener(this); - final View editBookmarkBtn = mFrame.findViewById(R.id.tv__bookmark_edit); - editBookmarkBtn.setOnClickListener(this); } private void initWebView() @@ -119,14 +112,6 @@ public class PlacePageBookmarkFragment extends Fragment implements View.OnClickL } } - @Override - public void onClick(View v) - { - final FragmentActivity activity = requireActivity(); - EditBookmarkFragment.editBookmark(currentBookmark.getCategoryId(), currentBookmark.getBookmarkId(), activity, - getChildFragmentManager(), PlacePageBookmarkFragment.this); - } - @Override public boolean onLongClick(View v) { @@ -156,13 +141,4 @@ public class PlacePageBookmarkFragment extends Fragment implements View.OnClickL updateBookmarkDetails(); } } - - @Override - public void onBookmarkSaved(long bookmarkId, boolean movedFromCategory) - { - Bookmark updatedBookmark = BookmarkManager.INSTANCE.updateBookmarkPlacePage(bookmarkId); - if (updatedBookmark == null) - return; - mViewModel.setMapObject(updatedBookmark); - } } diff --git a/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageTrackFragment.java b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageTrackFragment.java new file mode 100644 index 000000000..708110553 --- /dev/null +++ b/android/app/src/main/java/app/organicmaps/widget/placepage/sections/PlacePageTrackFragment.java @@ -0,0 +1,115 @@ +package app.organicmaps.widget.placepage.sections; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import app.organicmaps.R; +import app.organicmaps.sdk.bookmarks.data.BookmarkManager; +import app.organicmaps.sdk.bookmarks.data.ElevationInfo; +import app.organicmaps.sdk.bookmarks.data.MapObject; +import app.organicmaps.sdk.bookmarks.data.Track; +import app.organicmaps.sdk.util.UiUtils; +import app.organicmaps.widget.placepage.ElevationProfileViewRenderer; +import app.organicmaps.widget.placepage.PlacePageStateListener; +import app.organicmaps.widget.placepage.PlacePageViewModel; + +public class PlacePageTrackFragment extends Fragment + implements PlacePageStateListener, Observer, BookmarkManager.OnElevationActivePointChangedListener, + BookmarkManager.OnElevationCurrentPositionChangedListener +{ + private PlacePageViewModel mViewModel; + @Nullable + private Track mTrack; + private ElevationProfileViewRenderer mElevationProfileViewRenderer; + private View mFrame; + private View mElevationProfileView; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) + { + mViewModel = new ViewModelProvider(requireActivity()).get(PlacePageViewModel.class); + return inflater.inflate(R.layout.placepage_track_fragment, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) + { + super.onViewCreated(view, savedInstanceState); + mElevationProfileViewRenderer = new ElevationProfileViewRenderer(); + + mFrame = view; + mElevationProfileView = mFrame.findViewById(R.id.elevation_profile); + mElevationProfileViewRenderer.initialize(mElevationProfileView); + } + + @Override + public void onStart() + { + super.onStart(); + BookmarkManager.INSTANCE.setElevationActivePointChangedListener(this); + BookmarkManager.INSTANCE.setElevationCurrentPositionChangedListener(this); + mViewModel.getMapObject().observe(requireActivity(), this); + } + + @Override + public void onStop() + { + super.onStop(); + mViewModel.getMapObject().removeObserver(this); + } + + @Override + public void onDestroy() + { + BookmarkManager.INSTANCE.setElevationActivePointChangedListener(null); + BookmarkManager.INSTANCE.setElevationCurrentPositionChangedListener(null); + super.onDestroy(); + } + + @Override + public void onChanged(@Nullable MapObject mapObject) + { + // MapObject could be something else than a Track if the user already has the place page + // opened and clicks on a non-Track POI. + // This callback would be called before the fragment had time to be destroyed + if (mapObject != null && mapObject.isTrack()) + { + Track track = (Track) mapObject; + if (track.isElevationInfoHasValue()) + { + if (mTrack == null || mTrack.getTrackId() != track.getTrackId()) + { + mElevationProfileViewRenderer.render(track); + UiUtils.show(mElevationProfileView); + } + } + else + UiUtils.hide(mElevationProfileView); + mTrack = track; + } + } + + @Override + public void onElevationActivePointChanged() + { + if (mTrack == null) + return; + mElevationProfileViewRenderer.onChartElevationActivePointChanged(); + ElevationInfo.Point point = BookmarkManager.INSTANCE.getElevationActivePointCoordinates(mTrack.getTrackId()); + mViewModel.modifyMapObjectPointSilently(point); + } + + @Override + public void onCurrentPositionChanged() + { + mElevationProfileViewRenderer.onChartCurrentPositionChanged(); + } +} diff --git a/android/app/src/main/res/drawable/ic_distance_travelled.xml b/android/app/src/main/res/drawable/ic_distance_travelled.xml new file mode 100644 index 000000000..286e357bf --- /dev/null +++ b/android/app/src/main/res/drawable/ic_distance_travelled.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/layout-land/elevation_profile_bottom_sheet.xml b/android/app/src/main/res/layout-land/elevation_profile_bottom_sheet.xml index 1c522066f..561143c09 100644 --- a/android/app/src/main/res/layout-land/elevation_profile_bottom_sheet.xml +++ b/android/app/src/main/res/layout-land/elevation_profile_bottom_sheet.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/elevation_profile" - android:layout_width="@dimen/place_page_width" + android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/margin_base" android:paddingBottom="@dimen/margin_base" diff --git a/android/app/src/main/res/layout/elevation_profile.xml b/android/app/src/main/res/layout/elevation_profile.xml index e8caa071e..a8f7b67cf 100644 --- a/android/app/src/main/res/layout/elevation_profile.xml +++ b/android/app/src/main/res/layout/elevation_profile.xml @@ -8,7 +8,9 @@ + android:layout_height="match_parent" + android:layout_alignParentStart="true" + android:layout_alignParentEnd="true" /> - - - diff --git a/android/app/src/main/res/layout/elevation_profile_internal.xml b/android/app/src/main/res/layout/elevation_profile_internal.xml index e093d172c..174fbbcd4 100644 --- a/android/app/src/main/res/layout/elevation_profile_internal.xml +++ b/android/app/src/main/res/layout/elevation_profile_internal.xml @@ -4,22 +4,188 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:showIn="@layout/elevation_profile_bottom_sheet"> - - + + + + + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/margin_half" + android:layout_marginTop="@dimen/margin_quarter" + android:layout_marginEnd="@dimen/margin_half" + android:layout_marginBottom="@dimen/margin_half_plus_eight" + android:baselineAligned="false" + android:orientation="horizontal"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + android:text="@string/elevation_profile_difficulty" + android:textAppearance="?android:textAppearance" /> + android:layout_marginStart="@dimen/margin_half_plus_eight" + android:layout_marginBottom="@dimen/elevation_profile_dot_levels_margin" + android:orientation="horizontal"> + android:enabled="false" /> + android:enabled="false" /> + android:enabled="false" /> + android:text="@string/elevation_profile_time" + android:textAppearance="?android:textAppearance" /> diff --git a/android/app/src/main/res/layout/floating_marker_view.xml b/android/app/src/main/res/layout/floating_marker_view.xml index 6cee6ebd6..aac3f4477 100644 --- a/android/app/src/main/res/layout/floating_marker_view.xml +++ b/android/app/src/main/res/layout/floating_marker_view.xml @@ -9,8 +9,8 @@ tools:ignore="Overdraw"> - + + android:textSize="@dimen/margin_half_plus_eight" + tools:text="10 km" /> - - diff --git a/android/app/src/main/res/layout/place_page_details.xml b/android/app/src/main/res/layout/place_page_details.xml index ddbe48dd1..ee6a0fa9c 100644 --- a/android/app/src/main/res/layout/place_page_details.xml +++ b/android/app/src/main/res/layout/place_page_details.xml @@ -21,7 +21,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" tools:layout="@layout/place_page_bookmark_fragment" /> - + android:orientation="vertical"> @@ -180,4 +179,62 @@ - + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/place_page_shadow_gap.xml b/android/app/src/main/res/layout/place_page_shadow_gap.xml new file mode 100644 index 000000000..188fbfbd2 --- /dev/null +++ b/android/app/src/main/res/layout/place_page_shadow_gap.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/placepage_track_fragment.xml b/android/app/src/main/res/layout/placepage_track_fragment.xml new file mode 100644 index 000000000..0eb214575 --- /dev/null +++ b/android/app/src/main/res/layout/placepage_track_fragment.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml index 8ee79ed8d..492beeae5 100644 --- a/android/app/src/main/res/values/dimens.xml +++ b/android/app/src/main/res/values/dimens.xml @@ -46,6 +46,8 @@ 320dp 56dp 40dp + 32dp + 20dp 40dp 32dp @@ -75,12 +77,13 @@ 300dp 260dp 144dp - 68dp + 54dp 10dp 144dp - 108dp - 54dp + 80dp + 40dp 3dp + 25dp 48dp 28dp 34dp diff --git a/android/app/src/main/res/values/font_sizes.xml b/android/app/src/main/res/values/font_sizes.xml index cce2c58ee..3277b1530 100644 --- a/android/app/src/main/res/values/font_sizes.xml +++ b/android/app/src/main/res/values/font_sizes.xml @@ -16,6 +16,7 @@ 14sp 12sp 10sp + 8sp 14sp 10sp 16sp diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 98a93b066..e8e4a24d8 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -971,4 +971,5 @@ Closed now at %s + Save Track As diff --git a/android/app/src/main/res/values/styles-text.xml b/android/app/src/main/res/values/styles-text.xml index 07b1336da..2fb94e160 100644 --- a/android/app/src/main/res/values/styles-text.xml +++ b/android/app/src/main/res/values/styles-text.xml @@ -93,6 +93,11 @@ ?android:textColorSecondary + +