Evolver: CustomSeekbarPreference changes [SQUASH]
* Update CustomSeekbarPreference to use material expressive layout * Remove tinting reset icon in CustomSeekbarPreference * Add slider increment guard for CustomSeekBarPreference * Add callChangeListener() for all changes in CustomSeekBarPreference * Fix reset icon in CustomSeekBarPreference in RTL layout Fixes: https://github.com/crdroidandroid/issue_tracker/issues/778 Fixes: https://github.com/crdroidandroid/issue_tracker/issues/803 Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
This commit is contained in:
@@ -1,138 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2017-2022 crDroid Android Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:background="?android:attr/activatedBackgroundIndicator"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/icon_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="0dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp">
|
||||
|
||||
<androidx.preference.internal.PreferenceImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:maxWidth="48dp"
|
||||
app:maxHeight="48dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:ellipsize="marquee" />
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@android:id/title"
|
||||
android:layout_alignStart="@android:id/title"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:maxLines="10"
|
||||
android:ellipsize="end" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/value_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@android:id/summary"
|
||||
android:layout_alignStart="@android:id/title" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/reset"
|
||||
android:src="@drawable/ic_custom_seekbar_reset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_toEndOf="@id/value"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/seekbar_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/value_frame"
|
||||
android:layout_alignStart="@android:id/title" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/minus"
|
||||
android:src="@drawable/ic_custom_seekbar_minus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/plus"
|
||||
android:src="@drawable/ic_custom_seekbar_plus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/seekbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_toEndOf="@id/minus"
|
||||
android:layout_toStartOf="@id/plus"
|
||||
android:layout_centerVertical="true" />
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Preference should place its actual preference widget here. -->
|
||||
<LinearLayout android:id="@android:id/widget_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="end|center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
@@ -1,359 +1,376 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2023 crDroid Android Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2016-2025 crDroid Android Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.evolution.settings.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.PorterDuff;
|
||||
import androidx.core.content.res.TypedArrayUtils;
|
||||
import androidx.preference.*;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settingslib.widget.SliderPreference;
|
||||
|
||||
import com.google.android.material.slider.LabelFormatter;
|
||||
import com.google.android.material.slider.Slider;
|
||||
|
||||
public class CustomSeekBarPreference extends SliderPreference {
|
||||
|
||||
public class CustomSeekBarPreference extends Preference implements SeekBar.OnSeekBarChangeListener {
|
||||
protected final String TAG = getClass().getName();
|
||||
private static final String SETTINGS_NS = "http://schemas.android.com/apk/res/com.android.settings";
|
||||
protected static final String ANDROIDNS = "http://schemas.android.com/apk/res/android";
|
||||
private static final String ANDROIDNS = "http://schemas.android.com/apk/res/android";
|
||||
|
||||
protected int mInterval = 1;
|
||||
protected boolean mShowSign = false;
|
||||
protected String mUnits = "";
|
||||
protected boolean mContinuousUpdates = false;
|
||||
private boolean mShowSign;
|
||||
@Nullable
|
||||
private String mUnits = "";
|
||||
@Nullable
|
||||
private String mDefaultValueText;
|
||||
private boolean mDefaultValueTextExists;
|
||||
private boolean mDefaultValueExists;
|
||||
private int mDefaultValue;
|
||||
|
||||
protected int mMinValue = 0;
|
||||
protected int mMaxValue = 100;
|
||||
protected boolean mDefaultValueExists = false;
|
||||
protected int mDefaultValue;
|
||||
protected boolean mDefaultValueTextExists = false;
|
||||
protected String mDefaultValueText;
|
||||
private CharSequence mUserSummary;
|
||||
|
||||
protected int mValue;
|
||||
|
||||
protected TextView mValueTextView;
|
||||
protected ImageView mResetImageView;
|
||||
protected ImageView mMinusImageView;
|
||||
protected ImageView mPlusImageView;
|
||||
protected SeekBar mSeekBar;
|
||||
|
||||
protected boolean mTrackingTouch = false;
|
||||
protected int mTrackingValue;
|
||||
|
||||
public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomSeekBarPreference);
|
||||
try {
|
||||
mShowSign = a.getBoolean(R.styleable.CustomSeekBarPreference_showSign, mShowSign);
|
||||
String units = a.getString(R.styleable.CustomSeekBarPreference_units);
|
||||
if (units != null)
|
||||
mUnits = " " + units;
|
||||
mContinuousUpdates = a.getBoolean(R.styleable.CustomSeekBarPreference_continuousUpdates, mContinuousUpdates);
|
||||
String defaultValueText = a.getString(R.styleable.CustomSeekBarPreference_defaultValueText);
|
||||
mDefaultValueTextExists = defaultValueText != null && !defaultValueText.isEmpty();
|
||||
if (mDefaultValueTextExists) {
|
||||
mDefaultValueText = defaultValueText;
|
||||
}
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
try {
|
||||
String newInterval = attrs.getAttributeValue(SETTINGS_NS, "interval");
|
||||
if (newInterval != null)
|
||||
mInterval = Integer.parseInt(newInterval);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Invalid interval value", e);
|
||||
}
|
||||
mMinValue = attrs.getAttributeIntValue(SETTINGS_NS, "min", mMinValue);
|
||||
mMaxValue = attrs.getAttributeIntValue(ANDROIDNS, "max", mMaxValue);
|
||||
if (mMaxValue < mMinValue)
|
||||
mMaxValue = mMinValue;
|
||||
String defaultValue = attrs.getAttributeValue(ANDROIDNS, "defaultValue");
|
||||
mDefaultValueExists = defaultValue != null && !defaultValue.isEmpty();
|
||||
if (mDefaultValueExists) {
|
||||
mDefaultValue = getLimitedValue(Integer.parseInt(defaultValue));
|
||||
mValue = mDefaultValue;
|
||||
} else {
|
||||
mValue = mMinValue;
|
||||
}
|
||||
|
||||
mSeekBar = new SeekBar(context, attrs);
|
||||
setLayoutResource(R.layout.preference_custom_seekbar);
|
||||
}
|
||||
|
||||
public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
private boolean mInUserDrag = false;
|
||||
|
||||
public CustomSeekBarPreference(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, TypedArrayUtils.getAttr(context,
|
||||
androidx.preference.R.attr.seekBarPreferenceStyle,
|
||||
com.android.internal.R.attr.seekBarPreferenceStyle));
|
||||
super(context, attrs);
|
||||
readLegacyAttrs(context, attrs);
|
||||
initDefaults();
|
||||
mUserSummary = super.getSummary();
|
||||
updateSummaryNow();
|
||||
}
|
||||
|
||||
public CustomSeekBarPreference(Context context) {
|
||||
this(context, null);
|
||||
super(context, null);
|
||||
initDefaults();
|
||||
mUserSummary = super.getSummary();
|
||||
updateSummaryNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDependencyChanged(Preference dependency, boolean disableDependent) {
|
||||
super.onDependencyChanged(dependency, disableDependent);
|
||||
this.setShouldDisableView(true);
|
||||
if (mSeekBar != null)
|
||||
mSeekBar.setEnabled(!disableDependent);
|
||||
if (mResetImageView != null)
|
||||
mResetImageView.setEnabled(!disableDependent);
|
||||
if (mPlusImageView != null)
|
||||
mPlusImageView.setEnabled(!disableDependent);
|
||||
if (mMinusImageView != null)
|
||||
mMinusImageView.setEnabled(!disableDependent);
|
||||
private void initDefaults() {
|
||||
setShowSliderValue(true);
|
||||
setHapticFeedbackMode(HAPTIC_FEEDBACK_MODE_ON_TICKS);
|
||||
setLabelFormater(new LabelFormatter() {
|
||||
@Override public String getFormattedValue(float value) {
|
||||
return formatValueForSummary((int) value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
try
|
||||
{
|
||||
// move our seekbar to the new view we've been given
|
||||
ViewParent oldContainer = mSeekBar.getParent();
|
||||
ViewGroup newContainer = (ViewGroup) holder.findViewById(R.id.seekbar);
|
||||
if (oldContainer != newContainer) {
|
||||
// remove the seekbar from the old view
|
||||
if (oldContainer != null) {
|
||||
((ViewGroup) oldContainer).removeView(mSeekBar);
|
||||
private void readLegacyAttrs(Context c, AttributeSet attrs) {
|
||||
if (attrs == null) return;
|
||||
final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CustomSeekBarPreference);
|
||||
try {
|
||||
mShowSign = a.getBoolean(R.styleable.CustomSeekBarPreference_showSign, false);
|
||||
final String units = a.getString(R.styleable.CustomSeekBarPreference_units);
|
||||
if (units != null) mUnits = units;
|
||||
|
||||
final boolean continuous = a.getBoolean(
|
||||
R.styleable.CustomSeekBarPreference_continuousUpdates, false);
|
||||
setUpdatesContinuously(continuous);
|
||||
|
||||
mDefaultValueText = a.getString(
|
||||
R.styleable.CustomSeekBarPreference_defaultValueText);
|
||||
mDefaultValueTextExists = mDefaultValueText != null && !mDefaultValueText.isEmpty();
|
||||
|
||||
String defaultValue = attrs.getAttributeValue(ANDROIDNS, "defaultValue");
|
||||
if (defaultValue == null) {
|
||||
defaultValue = attrs.getAttributeValue(SETTINGS_NS, "defaultValue");
|
||||
}
|
||||
if (defaultValue != null && !defaultValue.isEmpty()) {
|
||||
try {
|
||||
mDefaultValue = Integer.parseInt(defaultValue);
|
||||
mDefaultValueExists = true;
|
||||
} catch (NumberFormatException ignored) {
|
||||
mDefaultValueExists = false;
|
||||
}
|
||||
// remove the existing seekbar (there may not be one) and add ours
|
||||
newContainer.removeAllViews();
|
||||
newContainer.addView(mSeekBar, ViewGroup.LayoutParams.FILL_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "Error binding view: " + ex.toString());
|
||||
|
||||
int interval = attrs.getAttributeIntValue(SETTINGS_NS, "interval", 0);
|
||||
if (interval == 0) {
|
||||
interval = attrs.getAttributeIntValue(ANDROIDNS, "interval", 0);
|
||||
}
|
||||
if (interval > 0) setSliderIncrement(interval);
|
||||
|
||||
// Guard against improper slider increment
|
||||
int min = getMin();
|
||||
int max = getMax();
|
||||
int span = Math.max(0, max - min);
|
||||
|
||||
int step = getSliderIncrement();
|
||||
if (step <= 0 || span == 0) {
|
||||
setSliderIncrement(1); // Always use discrete steps for CustomSeekBarPreference
|
||||
} else if ((span % step) != 0) {
|
||||
int gcd = gcd(span, step);
|
||||
if (gcd <= 0) gcd = 1;
|
||||
setSliderIncrement(gcd);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
// keep safe defaults
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
mSeekBar.setMax(getSeekValue(mMaxValue));
|
||||
mSeekBar.setProgress(getSeekValue(mValue));
|
||||
mSeekBar.setEnabled(isEnabled());
|
||||
|
||||
mValueTextView = (TextView) holder.findViewById(R.id.value);
|
||||
mResetImageView = (ImageView) holder.findViewById(R.id.reset);
|
||||
mMinusImageView = (ImageView) holder.findViewById(R.id.minus);
|
||||
mPlusImageView = (ImageView) holder.findViewById(R.id.plus);
|
||||
|
||||
updateValueViews();
|
||||
|
||||
mSeekBar.setOnSeekBarChangeListener(this);
|
||||
mResetImageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Toast.makeText(getContext(), getContext().getString(R.string.custom_seekbar_default_value_to_set, getTextValue(mDefaultValue)),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
mResetImageView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
setValue(mDefaultValue, true);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
mMinusImageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
setValue(mValue - mInterval, true);
|
||||
}
|
||||
});
|
||||
mMinusImageView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue < mValue * 2 ? Math.floorDiv(mMaxValue + mMinValue, 2) : mMinValue, true);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
mPlusImageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
setValue(mValue + mInterval, true);
|
||||
}
|
||||
});
|
||||
mPlusImageView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue > mValue * 2 ? -1 * Math.floorDiv(-1 * (mMaxValue + mMinValue), 2) : mMaxValue, true);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected int getLimitedValue(int v) {
|
||||
return v < mMinValue ? mMinValue : (v > mMaxValue ? mMaxValue : v);
|
||||
@Override
|
||||
public void setSummary(CharSequence summary) {
|
||||
mUserSummary = summary;
|
||||
updateSummaryNow();
|
||||
}
|
||||
|
||||
protected int getSeekValue(int v) {
|
||||
return 0 - Math.floorDiv(mMinValue - v, mInterval);
|
||||
@Override
|
||||
public void setValue(int sliderValue) {
|
||||
super.setValue(sliderValue);
|
||||
if (!mInUserDrag) updateSummaryNow();
|
||||
}
|
||||
|
||||
protected String getTextValue(int v) {
|
||||
if (mDefaultValueTextExists && mDefaultValueExists && v == mDefaultValue) {
|
||||
private void updateSummaryNow() {
|
||||
CharSequence composed = composeSummary(mUserSummary, getValue());
|
||||
super.setSummary(composed);
|
||||
}
|
||||
|
||||
private String formatValueForSummary(int v) {
|
||||
if (mDefaultValueExists && mDefaultValueTextExists && v == mDefaultValue) {
|
||||
return mDefaultValueText;
|
||||
}
|
||||
return (mShowSign && v > 0 ? "+" : "") + String.valueOf(v) + mUnits;
|
||||
String s = String.valueOf(v);
|
||||
if (mShowSign && v > 0) s = "+" + s;
|
||||
if (mUnits != null && !mUnits.isEmpty()) s = s + " " + mUnits;
|
||||
return s;
|
||||
}
|
||||
|
||||
protected void updateValueViews() {
|
||||
if (mValueTextView != null) {
|
||||
if (!mTrackingTouch || mContinuousUpdates) {
|
||||
if (mDefaultValueTextExists && mDefaultValueExists && mValue == mDefaultValue) {
|
||||
mValueTextView.setText(mDefaultValueText + " (" +
|
||||
getContext().getString(R.string.custom_seekbar_default_value) + ")");
|
||||
} else {
|
||||
mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, getTextValue(mValue)) +
|
||||
(mDefaultValueExists && mValue == mDefaultValue ? " (" +
|
||||
getContext().getString(R.string.custom_seekbar_default_value) + ")" : ""));
|
||||
}
|
||||
} else {
|
||||
if (mDefaultValueTextExists && mDefaultValueExists && mTrackingValue == mDefaultValue) {
|
||||
mValueTextView.setText("[" + mDefaultValueText + "]");
|
||||
} else {
|
||||
mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, "[" + getTextValue(mTrackingValue) + "]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mResetImageView != null) {
|
||||
if (!mDefaultValueExists || mValue == mDefaultValue || mTrackingTouch)
|
||||
mResetImageView.setVisibility(View.INVISIBLE);
|
||||
else
|
||||
mResetImageView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (mMinusImageView != null) {
|
||||
if (mValue == mMinValue || mTrackingTouch) {
|
||||
mMinusImageView.setClickable(false);
|
||||
mMinusImageView.setColorFilter(Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorTertiary),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
} else {
|
||||
mMinusImageView.setClickable(true);
|
||||
mMinusImageView.clearColorFilter();
|
||||
}
|
||||
}
|
||||
if (mPlusImageView != null) {
|
||||
if (mValue == mMaxValue || mTrackingTouch) {
|
||||
mPlusImageView.setClickable(false);
|
||||
mPlusImageView.setColorFilter(Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorTertiary),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
} else {
|
||||
mPlusImageView.setClickable(true);
|
||||
mPlusImageView.clearColorFilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void changeValue(int newValue) {
|
||||
// for subclasses
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
int newValue = getLimitedValue(mMinValue + (progress * mInterval));
|
||||
if (mTrackingTouch && !mContinuousUpdates) {
|
||||
mTrackingValue = newValue;
|
||||
updateValueViews();
|
||||
} else if (mValue != newValue) {
|
||||
// change rejected, revert to the previous value
|
||||
if (!callChangeListener(newValue)) {
|
||||
mSeekBar.setProgress(getSeekValue(mValue));
|
||||
return;
|
||||
}
|
||||
// change accepted, store it
|
||||
changeValue(newValue);
|
||||
persistInt(newValue);
|
||||
|
||||
mValue = newValue;
|
||||
updateValueViews();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
mTrackingValue = mValue;
|
||||
mTrackingTouch = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
mTrackingTouch = false;
|
||||
if (!mContinuousUpdates)
|
||||
onProgressChanged(mSeekBar, getSeekValue(mTrackingValue), false);
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
|
||||
if (restoreValue)
|
||||
mValue = getPersistedInt(mValue);
|
||||
private CharSequence composeSummary(CharSequence userSummary, int v) {
|
||||
final String valueText = formatValueForSummary(v);
|
||||
if (userSummary == null || userSummary.length() == 0) return valueText;
|
||||
return valueText + " \u2022 " + userSummary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultValue(Object defaultValue) {
|
||||
if (defaultValue instanceof Integer)
|
||||
setDefaultValue((Integer) defaultValue, mSeekBar != null);
|
||||
else
|
||||
setDefaultValue(defaultValue == null ? (String) null : defaultValue.toString(), mSeekBar != null);
|
||||
}
|
||||
|
||||
public void setDefaultValue(int newValue, boolean update) {
|
||||
newValue = getLimitedValue(newValue);
|
||||
if (!mDefaultValueExists || mDefaultValue != newValue) {
|
||||
if (defaultValue instanceof Integer) {
|
||||
mDefaultValueExists = true;
|
||||
mDefaultValue = newValue;
|
||||
if (update)
|
||||
updateValueViews();
|
||||
mDefaultValue = (Integer) defaultValue;
|
||||
}
|
||||
super.setDefaultValue(defaultValue);
|
||||
updateSummaryNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
|
||||
final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
|
||||
if (summaryView != null) {
|
||||
summaryView.setText(composeSummary(mUserSummary, getValue()));
|
||||
}
|
||||
|
||||
final View labelFrame = holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.label_frame);
|
||||
final TextView startText = (TextView) holder.findViewById(android.R.id.text1);
|
||||
final TextView endText = (TextView) holder.findViewById(android.R.id.text2);
|
||||
|
||||
if (labelFrame != null) {
|
||||
boolean hasStart = startText != null && startText.getText() != null
|
||||
&& startText.getText().length() > 0;
|
||||
boolean hasEnd = endText != null && endText.getText() != null
|
||||
&& endText.getText().length() > 0;
|
||||
boolean parentWantsLabels = hasStart || hasEnd;
|
||||
|
||||
labelFrame.setVisibility((parentWantsLabels || mDefaultValueExists) ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
if (endText != null) {
|
||||
attachResetIcon(endText);
|
||||
}
|
||||
|
||||
ViewGroup minusFrame = (ViewGroup) holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.icon_start_frame);
|
||||
ImageView minusIcon = (ImageView) holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.icon_start);
|
||||
|
||||
ViewGroup plusFrame = (ViewGroup) holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.icon_end_frame);
|
||||
ImageView plusIcon = (ImageView) holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.icon_end);
|
||||
|
||||
final Slider slider = (Slider) holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.slider);
|
||||
|
||||
int stepForClicks = Math.max(1, getSliderIncrement());
|
||||
|
||||
if (minusFrame != null && minusIcon != null) {
|
||||
minusFrame.setVisibility(View.VISIBLE);
|
||||
minusIcon.setImageResource(R.drawable.ic_custom_seekbar_minus);
|
||||
minusFrame.setOnClickListener(v -> {
|
||||
if (!isEnabled()) return;
|
||||
int base = slider != null ? Math.round(slider.getValue()) : getValue();
|
||||
int newVal = Math.max(getMin(), base - stepForClicks);
|
||||
applyUserValue(newVal, slider);
|
||||
updatePlusMinusEnabledStates(holder);
|
||||
});
|
||||
}
|
||||
|
||||
if (plusFrame != null && plusIcon != null) {
|
||||
plusFrame.setVisibility(View.VISIBLE);
|
||||
plusIcon.setImageResource(R.drawable.ic_custom_seekbar_plus);
|
||||
plusFrame.setOnClickListener(v -> {
|
||||
if (!isEnabled()) return;
|
||||
int base = slider != null ? Math.round(slider.getValue()) : getValue();
|
||||
int newVal = Math.min(getMax(), base + stepForClicks);
|
||||
applyUserValue(newVal, slider);
|
||||
updatePlusMinusEnabledStates(holder);
|
||||
});
|
||||
}
|
||||
|
||||
updatePlusMinusEnabledStates(holder);
|
||||
|
||||
if (slider != null && summaryView != null) {
|
||||
slider.addOnChangeListener((s, value, fromUser) -> {
|
||||
if (fromUser) {
|
||||
summaryView.setText(composeSummary(mUserSummary, (int) value));
|
||||
updatePlusMinusEnabledStates(holder);
|
||||
}
|
||||
});
|
||||
slider.addOnSliderTouchListener(new Slider.OnSliderTouchListener() {
|
||||
@Override
|
||||
public void onStartTrackingTouch(@NonNull Slider s) {
|
||||
mInUserDrag = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(@NonNull Slider s) {
|
||||
mInUserDrag = false;
|
||||
applyUserValue(Math.round(s.getValue()), s);
|
||||
updatePlusMinusEnabledStates(holder);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setDefaultValue(String newValue, boolean update) {
|
||||
if (mDefaultValueExists && (newValue == null || newValue.isEmpty())) {
|
||||
mDefaultValueExists = false;
|
||||
if (update)
|
||||
updateValueViews();
|
||||
} else if (newValue != null && !newValue.isEmpty()) {
|
||||
setDefaultValue(Integer.parseInt(newValue), update);
|
||||
@Override
|
||||
public void onDependencyChanged(@NonNull Preference dependency, boolean disableDependent) {
|
||||
super.onDependencyChanged(dependency, disableDependent);
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
private void applyUserValue(int newVal, @Nullable Slider slider) {
|
||||
if (newVal == getValue()) return;
|
||||
if (!callChangeListener(newVal)) {
|
||||
if (slider != null) slider.setValue(getValue());
|
||||
return;
|
||||
}
|
||||
setValue(newVal);
|
||||
updateSummaryNow();
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
private static int gcd(int a, int b) {
|
||||
a = Math.abs(a); b = Math.abs(b);
|
||||
if (a == 0) return b;
|
||||
if (b == 0) return a;
|
||||
while (b != 0) {
|
||||
int t = b; b = a % b; a = t;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
private void updatePlusMinusEnabledStates(PreferenceViewHolder holder) {
|
||||
View minusFrame = holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.icon_start_frame);
|
||||
ImageView minusIcon = (ImageView) holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.icon_start);
|
||||
View plusFrame = holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.icon_end_frame);
|
||||
ImageView plusIcon = (ImageView) holder.findViewById(
|
||||
com.android.settingslib.widget.preference.slider.R.id.icon_end);
|
||||
boolean enabled = isEnabled();
|
||||
int value = getValue();
|
||||
|
||||
if (minusFrame != null && minusIcon != null) {
|
||||
int min = getMin();
|
||||
minusFrame.setEnabled(enabled && (value > min));
|
||||
minusIcon.setEnabled(enabled && (value > min));
|
||||
}
|
||||
if (plusFrame != null && plusIcon != null) {
|
||||
int max = getMax();
|
||||
plusFrame.setEnabled(enabled && (value < max));
|
||||
plusIcon.setEnabled(enabled && (value < max));
|
||||
}
|
||||
}
|
||||
|
||||
public void setValue(int newValue) {
|
||||
mValue = getLimitedValue(newValue);
|
||||
if (mSeekBar != null) mSeekBar.setProgress(getSeekValue(mValue));
|
||||
private void attachResetIcon(TextView tv) {
|
||||
if (!mDefaultValueExists) {
|
||||
tv.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null);
|
||||
tv.setOnTouchListener(null);
|
||||
tv.setClickable(false);
|
||||
return;
|
||||
}
|
||||
|
||||
final Drawable icon = ResourcesCompat.getDrawable(
|
||||
tv.getResources(), R.drawable.ic_custom_seekbar_reset, tv.getContext().getTheme());
|
||||
if (icon == null) return;
|
||||
|
||||
tv.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, icon, null);
|
||||
tv.setCompoundDrawablePadding(dp(tv, 6));
|
||||
tv.setClickable(isEnabled());
|
||||
tv.setFocusable(isEnabled());
|
||||
|
||||
final int tapSlop = dp(tv, 8);
|
||||
|
||||
tv.setOnTouchListener((v, ev) -> {
|
||||
if (!isEnabled() || ev.getAction() != MotionEvent.ACTION_UP) return false;
|
||||
|
||||
final boolean isRtl = ViewCompat.getLayoutDirection(tv) == ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
final Drawable[] drs = tv.getCompoundDrawablesRelative();
|
||||
final Drawable end = drs[2];
|
||||
if (end == null) return false;
|
||||
|
||||
final int iconW = end.getIntrinsicWidth();
|
||||
final int x = (int) ev.getX();
|
||||
|
||||
if (!isRtl) {
|
||||
final int left = tv.getWidth() - ViewCompat.getPaddingEnd(tv) - iconW - tapSlop;
|
||||
if (x >= left) { performReset(); return true; }
|
||||
} else {
|
||||
final int right = ViewCompat.getPaddingStart(tv) + iconW + tapSlop;
|
||||
if (x <= right) { performReset(); return true; }
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public void setValue(int newValue, boolean update) {
|
||||
newValue = getLimitedValue(newValue);
|
||||
if (mValue != newValue) {
|
||||
if (update)
|
||||
mSeekBar.setProgress(getSeekValue(newValue));
|
||||
else
|
||||
mValue = newValue;
|
||||
private void performReset() {
|
||||
if (mDefaultValueExists) {
|
||||
applyUserValue(mDefaultValue, null);
|
||||
}
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public void refresh(int newValue) {
|
||||
// this will ...
|
||||
setValue(newValue, mSeekBar != null);
|
||||
private static int dp(TextView v, int dp) {
|
||||
return Math.round(dp * v.getResources().getDisplayMetrics().density);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 crDroid Android Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2016-2025 crDroid Android Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.evolution.settings.preferences;
|
||||
@@ -10,11 +21,6 @@ import android.util.AttributeSet;
|
||||
|
||||
public class GlobalSettingSeekBarPreference extends CustomSeekBarPreference {
|
||||
|
||||
public GlobalSettingSeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setPreferenceDataStore(new GlobalSettingsStore(context.getContentResolver()));
|
||||
}
|
||||
|
||||
public GlobalSettingSeekBarPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setPreferenceDataStore(new GlobalSettingsStore(context.getContentResolver()));
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2022 crDroid Android Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2016-2025 crDroid Android Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.evolution.settings.preferences;
|
||||
@@ -10,11 +21,6 @@ import android.util.AttributeSet;
|
||||
|
||||
public class LineageSecureSettingSeekBarPreference extends CustomSeekBarPreference {
|
||||
|
||||
public LineageSecureSettingSeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setPreferenceDataStore(new LineageSecureSettingsStore(context.getContentResolver()));
|
||||
}
|
||||
|
||||
public LineageSecureSettingSeekBarPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setPreferenceDataStore(new LineageSecureSettingsStore(context.getContentResolver()));
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 crDroid Android Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2016-2025 crDroid Android Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.evolution.settings.preferences;
|
||||
@@ -10,11 +21,6 @@ import android.util.AttributeSet;
|
||||
|
||||
public class LineageSystemSettingSeekBarPreference extends CustomSeekBarPreference {
|
||||
|
||||
public LineageSystemSettingSeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setPreferenceDataStore(new LineageSystemSettingsStore(context.getContentResolver()));
|
||||
}
|
||||
|
||||
public LineageSystemSettingSeekBarPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setPreferenceDataStore(new LineageSystemSettingsStore(context.getContentResolver()));
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 crDroid Android Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2016-2025 crDroid Android Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.evolution.settings.preferences;
|
||||
@@ -10,11 +21,6 @@ import android.util.AttributeSet;
|
||||
|
||||
public class SecureSettingSeekBarPreference extends CustomSeekBarPreference {
|
||||
|
||||
public SecureSettingSeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setPreferenceDataStore(new SecureSettingsStore(context.getContentResolver()));
|
||||
}
|
||||
|
||||
public SecureSettingSeekBarPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setPreferenceDataStore(new SecureSettingsStore(context.getContentResolver()));
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2019 crDroid Android Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2016-2025 crDroid Android Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.evolution.settings.preferences;
|
||||
@@ -10,11 +21,6 @@ import android.util.AttributeSet;
|
||||
|
||||
public class SystemSettingSeekBarPreference extends CustomSeekBarPreference {
|
||||
|
||||
public SystemSettingSeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
|
||||
}
|
||||
|
||||
public SystemSettingSeekBarPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
|
||||
|
||||
Reference in New Issue
Block a user