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:
Pranav Vashi
2025-08-31 20:50:19 +05:30
committed by Joey
parent 6646a051f0
commit 689d09cb69
7 changed files with 382 additions and 473 deletions

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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()));

View File

@@ -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()));

View File

@@ -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()));

View File

@@ -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()));

View File

@@ -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()));