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;