diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle
index 714b85eae8..e33b1ca75f 100644
--- a/OsmAnd/build.gradle
+++ b/OsmAnd/build.gradle
@@ -563,6 +563,7 @@ dependencies {
implementation ("com.github.HITGIF:TextFieldBoxes:1.4.5"){
exclude group: 'com.android.support'
}
+ implementation 'com.jaredrummler:colorpicker:1.1.0'
huaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar')
freehuaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar')
diff --git a/OsmAnd/res/layout/custom_color_picker.xml b/OsmAnd/res/layout/custom_color_picker.xml
new file mode 100644
index 0000000000..db6c3a254c
--- /dev/null
+++ b/OsmAnd/res/layout/custom_color_picker.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OsmAnd/res/layout/track_coloring_card.xml b/OsmAnd/res/layout/track_coloring_card.xml
index 47326a9ad7..5775a98c7d 100644
--- a/OsmAnd/res/layout/track_coloring_card.xml
+++ b/OsmAnd/res/layout/track_coloring_card.xml
@@ -40,7 +40,7 @@
- CURRENT_TRACK_WIDTH = new StringPreference("current_track_width", "").makeGlobal().cache();
public final CommonPreference CURRENT_TRACK_SHOW_ARROWS = new BooleanPreference("current_track_show_arrows", false).makeGlobal().cache();
public final CommonPreference CURRENT_TRACK_SHOW_START_FINISH = new BooleanPreference("current_track_show_start_finish", true).makeGlobal().cache();
+ public final ListStringPreference CUSTOM_TRACK_COLORS = (ListStringPreference) new ListStringPreference("custom_track_colors", null, ",").makeGlobal();
// this value string is synchronized with settings_pref.xml preference name
public final CommonPreference SAVE_TRACK_INTERVAL = new IntPreference("save_track_interval", 5000).makeProfile();
diff --git a/OsmAnd/src/net/osmand/plus/track/CustomColorBottomSheet.java b/OsmAnd/src/net/osmand/plus/track/CustomColorBottomSheet.java
new file mode 100644
index 0000000000..66029119d5
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/track/CustomColorBottomSheet.java
@@ -0,0 +1,228 @@
+package net.osmand.plus.track;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import com.jaredrummler.android.colorpicker.ColorPanelView;
+import com.jaredrummler.android.colorpicker.ColorPickerView;
+import com.jaredrummler.android.colorpicker.ColorPickerView.OnColorChangedListener;
+
+import net.osmand.AndroidUtils;
+import net.osmand.PlatformUtil;
+import net.osmand.plus.R;
+import net.osmand.plus.UiUtilities;
+import net.osmand.plus.base.MenuBottomSheetDialogFragment;
+import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
+import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem;
+import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem;
+
+import org.apache.commons.logging.Log;
+
+public class CustomColorBottomSheet extends MenuBottomSheetDialogFragment implements OnColorChangedListener {
+
+ private static final String TAG = CustomColorBottomSheet.class.getSimpleName();
+
+ private static final Log log = PlatformUtil.getLog(CustomColorBottomSheet.class);
+
+ private static final String NEW_SELECTED_COLOR = "new_selected_color";
+ private static final String PREV_SELECTED_COLOR = "prev_selected_color";
+
+ private ColorPickerView colorPicker;
+ private ColorPanelView newColorPanel;
+
+ private EditText hexEditText;
+ private boolean fromEditText;
+
+ @ColorInt
+ private int prevColor;
+ @ColorInt
+ private int newColor;
+
+ @Override
+ public void createMenuItems(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ newColor = savedInstanceState.getInt(NEW_SELECTED_COLOR);
+ prevColor = savedInstanceState.getInt(PREV_SELECTED_COLOR);
+ } else {
+ Bundle args = getArguments();
+ if (args != null) {
+ prevColor = args.getInt(PREV_SELECTED_COLOR);
+ newColor = prevColor != -1 ? prevColor : Color.RED;
+ }
+ }
+
+ items.add(new TitleItem(getString(R.string.select_color)));
+
+ BaseBottomSheetItem item = new SimpleBottomSheetItem.Builder()
+ .setCustomView(createPickerView())
+ .create();
+ items.add(item);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putInt(NEW_SELECTED_COLOR, newColor);
+ outState.putInt(PREV_SELECTED_COLOR, prevColor);
+ super.onSaveInstanceState(outState);
+ }
+
+ private View createPickerView() {
+ LayoutInflater themedInflater = UiUtilities.getMaterialInflater(getActivity(), nightMode);
+ View colorView = themedInflater.inflate(R.layout.custom_color_picker, null);
+ colorPicker = colorView.findViewById(R.id.color_picker_view);
+ newColorPanel = colorView.findViewById(R.id.color_panel_new);
+ hexEditText = colorView.findViewById(R.id.color_hex_edit_text);
+
+ setHex(newColor);
+ newColorPanel.setColor(newColor);
+ colorPicker.setColor(newColor, true);
+ colorPicker.setOnColorChangedListener(this);
+ hexEditText.addTextChangedListener(new TextWatcher() {
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (hexEditText.isFocused()) {
+ int color = parseColorString(s.toString());
+ if (color != colorPicker.getColor()) {
+ fromEditText = true;
+ colorPicker.setColor(color, true);
+ }
+ }
+ }
+ });
+
+ return colorView;
+ }
+
+ @Override
+ public void onColorChanged(int newColor) {
+ this.newColor = newColor;
+ if (newColorPanel != null) {
+ newColorPanel.setColor(newColor);
+ }
+ if (!fromEditText && hexEditText != null) {
+ setHex(newColor);
+ Activity activity = getActivity();
+ if (activity != null && hexEditText.hasFocus()) {
+ AndroidUtils.hideSoftKeyboard(activity, hexEditText);
+ hexEditText.clearFocus();
+ }
+ }
+ fromEditText = false;
+ }
+
+ private void setHex(int color) {
+ hexEditText.setText(String.format("%08X", color));
+ }
+
+ @Override
+ protected int getRightBottomButtonTextId() {
+ return R.string.shared_string_apply;
+ }
+
+ @Override
+ protected void onRightBottomButtonClick() {
+ Fragment target = getTargetFragment();
+ if (target instanceof ColorPickerListener) {
+ ((ColorPickerListener) target).onColorSelected(prevColor, newColor);
+ }
+ dismiss();
+ }
+
+ public static void showInstance(@NonNull FragmentManager fragmentManager, Fragment target, int prevColor) {
+ try {
+ if (!fragmentManager.isStateSaved() && fragmentManager.findFragmentByTag(CustomColorBottomSheet.TAG) == null) {
+ Bundle args = new Bundle();
+ args.putInt(PREV_SELECTED_COLOR, prevColor);
+
+ CustomColorBottomSheet customColorBottomSheet = new CustomColorBottomSheet();
+ customColorBottomSheet.setArguments(args);
+ customColorBottomSheet.setTargetFragment(target, 0);
+ customColorBottomSheet.show(fragmentManager, CustomColorBottomSheet.TAG);
+ }
+ } catch (RuntimeException e) {
+ log.error(e);
+ }
+ }
+
+ private static int parseColorString(String colorString) throws NumberFormatException {
+ int a, r, g, b = 0;
+ if (colorString.startsWith("#")) {
+ colorString = colorString.substring(1);
+ }
+ if (colorString.length() == 0) {
+ r = 0;
+ a = 255;
+ g = 0;
+ } else if (colorString.length() <= 2) {
+ a = 255;
+ r = 0;
+ b = Integer.parseInt(colorString, 16);
+ g = 0;
+ } else if (colorString.length() == 3) {
+ a = 255;
+ r = Integer.parseInt(colorString.substring(0, 1), 16);
+ g = Integer.parseInt(colorString.substring(1, 2), 16);
+ b = Integer.parseInt(colorString.substring(2, 3), 16);
+ } else if (colorString.length() == 4) {
+ a = 255;
+ r = Integer.parseInt(colorString.substring(0, 2), 16);
+ g = r;
+ r = 0;
+ b = Integer.parseInt(colorString.substring(2, 4), 16);
+ } else if (colorString.length() == 5) {
+ a = 255;
+ r = Integer.parseInt(colorString.substring(0, 1), 16);
+ g = Integer.parseInt(colorString.substring(1, 3), 16);
+ b = Integer.parseInt(colorString.substring(3, 5), 16);
+ } else if (colorString.length() == 6) {
+ a = 255;
+ r = Integer.parseInt(colorString.substring(0, 2), 16);
+ g = Integer.parseInt(colorString.substring(2, 4), 16);
+ b = Integer.parseInt(colorString.substring(4, 6), 16);
+ } else if (colorString.length() == 7) {
+ a = Integer.parseInt(colorString.substring(0, 1), 16);
+ r = Integer.parseInt(colorString.substring(1, 3), 16);
+ g = Integer.parseInt(colorString.substring(3, 5), 16);
+ b = Integer.parseInt(colorString.substring(5, 7), 16);
+ } else if (colorString.length() == 8) {
+ a = Integer.parseInt(colorString.substring(0, 2), 16);
+ r = Integer.parseInt(colorString.substring(2, 4), 16);
+ g = Integer.parseInt(colorString.substring(4, 6), 16);
+ b = Integer.parseInt(colorString.substring(6, 8), 16);
+ } else {
+ b = -1;
+ g = -1;
+ r = -1;
+ a = -1;
+ }
+ return Color.argb(a, r, g, b);
+ }
+
+ public interface ColorPickerListener {
+
+ void onColorSelected(@ColorInt int prevColor, @ColorInt int newColor);
+
+ }
+}
\ 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 0c2176687c..4834a43b0e 100644
--- a/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java
+++ b/OsmAnd/src/net/osmand/plus/track/TrackAppearanceFragment.java
@@ -41,6 +41,7 @@ import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.routepreparationmenu.cards.BaseCard.CardListener;
import net.osmand.plus.settings.backend.OsmandSettings;
+import net.osmand.plus.track.CustomColorBottomSheet.ColorPickerListener;
import net.osmand.plus.track.SplitTrackAsyncTask.SplitTrackListener;
import net.osmand.render.RenderingRulesStorage;
import net.osmand.util.Algorithms;
@@ -57,9 +58,9 @@ import static net.osmand.plus.dialogs.ConfigureMapMenu.CURRENT_TRACK_COLOR_ATTR;
import static net.osmand.plus.dialogs.GpxAppearanceAdapter.TRACK_WIDTH_BOLD;
import static net.osmand.plus.dialogs.GpxAppearanceAdapter.TRACK_WIDTH_MEDIUM;
-public class TrackAppearanceFragment extends ContextMenuScrollFragment implements CardListener {
+public class TrackAppearanceFragment extends ContextMenuScrollFragment implements CardListener, ColorPickerListener {
- public static final String TAG = TrackAppearanceFragment.class.getSimpleName();
+ public static final String TAG = TrackAppearanceFragment.class.getName();
private static final Log log = PlatformUtil.getLog(TrackAppearanceFragment.class);
@@ -76,6 +77,7 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
private TrackWidthCard trackWidthCard;
private SplitIntervalCard splitIntervalCard;
+ private TrackColoringCard trackColoringCard;
private ImageView trackIcon;
@@ -324,6 +326,11 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
}
+ @Override
+ public void onColorSelected(int prevColor, int newColor) {
+ trackColoringCard.onColorSelected(prevColor, newColor);
+ }
+
@Override
protected int applyPosY(int currentY, boolean needCloseMenu, boolean needMapAdjust, int previousMenuState, int newMenuState, int dZoom, boolean animated) {
int y = super.applyPosY(currentY, needCloseMenu, needMapAdjust, previousMenuState, newMenuState, dZoom, animated);
@@ -555,7 +562,7 @@ public class TrackAppearanceFragment extends ContextMenuScrollFragment implement
directionArrowsCard.setListener(this);
cardsContainer.addView(directionArrowsCard.build(mapActivity));
- TrackColoringCard trackColoringCard = new TrackColoringCard(mapActivity, trackDrawInfo);
+ trackColoringCard = new TrackColoringCard(mapActivity, trackDrawInfo, this);
trackColoringCard.setListener(this);
cardsContainer.addView(trackColoringCard.build(mapActivity));
diff --git a/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java b/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java
index fbc9eebcdc..7d102c44fa 100644
--- a/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java
+++ b/OsmAnd/src/net/osmand/plus/track/TrackColoringCard.java
@@ -1,12 +1,13 @@
package net.osmand.plus.track;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
@@ -14,10 +15,14 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.google.android.material.internal.FlowLayout;
+
import net.osmand.AndroidUtils;
+import net.osmand.PlatformUtil;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.MapActivity;
@@ -25,16 +30,22 @@ import net.osmand.plus.dialogs.GpxAppearanceAdapter.AppearanceListItem;
import net.osmand.plus.dialogs.GpxAppearanceAdapter.GpxAppearanceAdapterType;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.routepreparationmenu.cards.BaseCard;
-import net.osmand.plus.widgets.FlowLayout;
+import net.osmand.plus.track.CustomColorBottomSheet.ColorPickerListener;
+import net.osmand.util.Algorithms;
+
+import org.apache.commons.logging.Log;
import java.util.ArrayList;
import java.util.List;
import static net.osmand.plus.dialogs.GpxAppearanceAdapter.getAppearanceItems;
-public class TrackColoringCard extends BaseCard {
+public class TrackColoringCard extends BaseCard implements ColorPickerListener {
+
+ public static final int INVALID_VALUE = -1;
private final static String SOLID_COLOR = "solid_color";
+ private static final Log log = PlatformUtil.getLog(TrackColoringCard.class);
private TrackDrawInfo trackDrawInfo;
@@ -42,10 +53,15 @@ public class TrackColoringCard extends BaseCard {
private TrackAppearanceItem selectedAppearanceItem;
private List appearanceItems;
- public TrackColoringCard(MapActivity mapActivity, TrackDrawInfo trackDrawInfo) {
+ private List customColors;
+ private Fragment target;
+
+ public TrackColoringCard(MapActivity mapActivity, TrackDrawInfo trackDrawInfo, Fragment target) {
super(mapActivity);
+ this.target = target;
this.trackDrawInfo = trackDrawInfo;
appearanceItems = getGradientAppearanceItems();
+ customColors = getCustomColors();
}
@Override
@@ -67,6 +83,25 @@ public class TrackColoringCard extends BaseCard {
AndroidUiHelper.updateVisibility(view.findViewById(R.id.top_divider), isShowDivider());
}
+ private List getCustomColors() {
+ List colors = new ArrayList<>();
+ List colorNames = app.getSettings().CUSTOM_TRACK_COLORS.getStringsList();
+ if (colorNames != null) {
+ for (String colorHex : colorNames) {
+ try {
+ if (!Algorithms.isEmpty(colorHex)) {
+ int color = Algorithms.parseColor(colorHex);
+ colors.add(color);
+ }
+ } catch (IllegalArgumentException e) {
+ log.error(e);
+ }
+ }
+ }
+
+ return colors;
+ }
+
private List getGradientAppearanceItems() {
List items = new ArrayList<>();
items.add(new TrackAppearanceItem(SOLID_COLOR, app.getString(R.string.track_coloring_solid), R.drawable.ic_action_circle));
@@ -80,6 +115,16 @@ public class TrackColoringCard extends BaseCard {
private void createColorSelector() {
FlowLayout selectColor = view.findViewById(R.id.select_color);
+ selectColor.removeAllViews();
+
+ for (int color : customColors) {
+ selectColor.addView(createColorItemView(color, selectColor, true));
+ }
+ if (customColors.size() < 6) {
+ selectColor.addView(createAddCustomColorItemView(selectColor));
+ }
+ selectColor.addView(createDividerView(selectColor));
+
List colors = new ArrayList<>();
for (AppearanceListItem appearanceListItem : getAppearanceItems(app, GpxAppearanceAdapterType.TRACK_COLOR)) {
if (!colors.contains(appearanceListItem.getColor())) {
@@ -87,20 +132,13 @@ public class TrackColoringCard extends BaseCard {
}
}
for (int color : colors) {
- selectColor.addView(createColorItemView(color, selectColor), new FlowLayout.LayoutParams(0, 0));
+ selectColor.addView(createColorItemView(color, selectColor, false));
}
updateColorSelector(trackDrawInfo.getColor(), selectColor);
}
- private View createColorItemView(@ColorInt final int color, final FlowLayout rootView) {
- FrameLayout colorItemView = (FrameLayout) UiUtilities.getInflater(rootView.getContext(), nightMode)
- .inflate(R.layout.point_editor_button, rootView, false);
- ImageView outline = colorItemView.findViewById(R.id.outline);
- outline.setImageDrawable(
- UiUtilities.tintDrawable(AppCompatResources.getDrawable(app, R.drawable.bg_point_circle_contour),
- ContextCompat.getColor(app,
- nightMode ? R.color.stroked_buttons_and_links_outline_dark
- : R.color.stroked_buttons_and_links_outline_light)));
+ private View createColorItemView(@ColorInt final int color, final FlowLayout rootView, boolean customColor) {
+ View colorItemView = createCircleView(rootView);
ImageView backgroundCircle = colorItemView.findViewById(R.id.background);
backgroundCircle.setImageDrawable(UiUtilities.tintDrawable(AppCompatResources.getDrawable(app, R.drawable.bg_point_circle), color));
backgroundCircle.setOnClickListener(new View.OnClickListener() {
@@ -116,10 +154,68 @@ public class TrackColoringCard extends BaseCard {
}
}
});
+ if (customColor) {
+ backgroundCircle.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ MapActivity mapActivity = getMapActivity();
+ if (mapActivity != null) {
+ CustomColorBottomSheet.showInstance(mapActivity.getSupportFragmentManager(), target, color);
+ }
+ return false;
+ }
+ });
+ }
colorItemView.setTag(color);
return colorItemView;
}
+ private View createAddCustomColorItemView(FlowLayout rootView) {
+ View colorItemView = createCircleView(rootView);
+ ImageView backgroundCircle = colorItemView.findViewById(R.id.background);
+
+ int bgColorId = nightMode ? R.color.activity_background_color_dark : R.color.activity_background_color_light;
+ Drawable backgroundIcon = app.getUIUtilities().getIcon(R.drawable.bg_point_circle, bgColorId);
+
+ ImageView icon = colorItemView.findViewById(R.id.icon);
+ icon.setVisibility(View.VISIBLE);
+ int activeColorResId = nightMode ? R.color.icon_color_active_dark : R.color.icon_color_active_light;
+ icon.setImageDrawable(app.getUIUtilities().getIcon(R.drawable.ic_action_add, activeColorResId));
+
+ backgroundCircle.setImageDrawable(backgroundIcon);
+ backgroundCircle.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MapActivity mapActivity = getMapActivity();
+ if (mapActivity != null) {
+ CustomColorBottomSheet.showInstance(mapActivity.getSupportFragmentManager(), target, TrackColoringCard.INVALID_VALUE);
+ }
+ }
+ });
+ return colorItemView;
+ }
+
+ private View createDividerView(FlowLayout rootView) {
+ LayoutInflater themedInflater = UiUtilities.getInflater(view.getContext(), nightMode);
+ View divider = themedInflater.inflate(R.layout.simple_divider_item, rootView, false);
+
+ LinearLayout dividerContainer = new LinearLayout(view.getContext());
+ dividerContainer.addView(divider);
+ dividerContainer.setPadding(0, AndroidUtils.dpToPx(app, 1), 0, AndroidUtils.dpToPx(app, 5));
+
+ return dividerContainer;
+ }
+
+ private View createCircleView(ViewGroup rootView) {
+ LayoutInflater themedInflater = UiUtilities.getInflater(view.getContext(), nightMode);
+ View circleView = themedInflater.inflate(R.layout.point_editor_button, rootView, false);
+ ImageView outline = circleView.findViewById(R.id.outline);
+ int colorId = nightMode ? R.color.stroked_buttons_and_links_outline_dark : R.color.stroked_buttons_and_links_outline_light;
+ Drawable contourIcon = app.getUIUtilities().getIcon(R.drawable.bg_point_circle_contour, colorId);
+ outline.setImageDrawable(contourIcon);
+ return circleView;
+ }
+
private void updateColorSelector(int color, View rootView) {
View oldColor = rootView.findViewWithTag(trackDrawInfo.getColor());
if (oldColor != null) {
@@ -175,6 +271,29 @@ public class TrackColoringCard extends BaseCard {
updateColorSelector();
}
+ @Override
+ public void onColorSelected(int prevColor, int newColor) {
+ if (prevColor == INVALID_VALUE && customColors.size() < 6) {
+ customColors.add(newColor);
+ } else if (!Algorithms.isEmpty(customColors)) {
+ int index = customColors.indexOf(prevColor);
+ if (index != INVALID_VALUE) {
+ customColors.set(index, newColor);
+ }
+ }
+ saveCustomColors();
+ updateContent();
+ }
+
+ private void saveCustomColors() {
+ List colorNames = new ArrayList<>();
+ for (Integer color : customColors) {
+ String colorHex = Algorithms.colorToString(color);
+ colorNames.add(colorHex);
+ }
+ app.getSettings().CUSTOM_TRACK_COLORS.setStringsList(colorNames);
+ }
+
private class TrackColoringAdapter extends RecyclerView.Adapter {
private List items;