[fenix] For https://github.com/mozilla-mobile/fenix/issues/3443 - convert textPercentageSeekBar to Kotlin (https://github.com/mozilla-mobile/fenix/pull/4081)
* convert textPercentageSeekBar to Kotlin * fix test * fix test again * fix test again * conflicts resolved * merge multiple kdocs into onepull/600/head
parent
4401093b9a
commit
20273fa5ec
@ -1,527 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
package org.mozilla.fenix.settings;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright 2018 The Android Open Source 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.SeekBar;
|
|
||||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceViewHolder;
|
|
||||||
import org.mozilla.fenix.R;
|
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preference based on android.preference.SeekBarPreference but uses support preference as a base
|
|
||||||
* . It contains a title and a {@link SeekBar} and a SeekBar value {@link TextView} and an Example {@link TextView}.
|
|
||||||
* The actual preference layout is customizable by setting {@code android:layout} on the
|
|
||||||
* preference widget layout or {@code seekBarPreferenceStyle} attribute.
|
|
||||||
*
|
|
||||||
* <p>The {@link SeekBar} within the preference can be defined adjustable or not by setting {@code
|
|
||||||
* adjustable} attribute. If adjustable, the preference will be responsive to DPAD left/right keys.
|
|
||||||
* Otherwise, it skips those keys.
|
|
||||||
*
|
|
||||||
* <p>The {@link SeekBar} value view can be shown or disabled by setting {@code showSeekBarValue}
|
|
||||||
* attribute to true or false, respectively.
|
|
||||||
*
|
|
||||||
* <p>Other {@link SeekBar} specific attributes (e.g. {@code title, summary, defaultValue, min,
|
|
||||||
* max})
|
|
||||||
* can be set directly on the preference widget layout.
|
|
||||||
*/
|
|
||||||
public class TextPercentageSeekBarPreference extends Preference {
|
|
||||||
private static final String TAG = "SeekBarPreference";
|
|
||||||
private static final int STEP_SIZE = 5;
|
|
||||||
private static final int MIN_VALUE = 50;
|
|
||||||
private static final float DECIMAL_CONVERSION = 100f;
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
|
||||||
int mSeekBarValue;
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
|
||||||
int mMin;
|
|
||||||
private int mMax;
|
|
||||||
private int mSeekBarIncrement;
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
|
||||||
boolean mTrackingTouch;
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
|
||||||
SeekBar mSeekBar;
|
|
||||||
private TextView mSeekBarValueTextView;
|
|
||||||
private TextView mExampleTextTextView;
|
|
||||||
// Whether the SeekBar should respond to the left/right keys
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
|
||||||
boolean mAdjustable;
|
|
||||||
// Whether to show the SeekBar value TextView next to the bar
|
|
||||||
private boolean mShowSeekBarValue;
|
|
||||||
// Whether the SeekBarPreference should continuously save the Seekbar value while it is being
|
|
||||||
// dragged.
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
|
||||||
boolean mUpdatesContinuously;
|
|
||||||
/**
|
|
||||||
* Listener reacting to the {@link SeekBar} changing value by the user
|
|
||||||
*/
|
|
||||||
private OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
|
||||||
if (fromUser && (mUpdatesContinuously || !mTrackingTouch)) {
|
|
||||||
syncValueInternal(seekBar);
|
|
||||||
} else {
|
|
||||||
// We always want to update the text while the seekbar is being dragged
|
|
||||||
updateLabelValue(progress + mMin);
|
|
||||||
updateExampleTextValue(progress + mMin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
|
||||||
mTrackingTouch = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
|
||||||
mTrackingTouch = false;
|
|
||||||
if (seekBar.getProgress() + mMin != mSeekBarValue) {
|
|
||||||
syncValueInternal(seekBar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener reacting to the user pressing DPAD left/right keys if {@code
|
|
||||||
* adjustable} attribute is set to true; it transfers the key presses to the {@link SeekBar}
|
|
||||||
* to be handled accordingly.
|
|
||||||
*/
|
|
||||||
private View.OnKeyListener mSeekBarKeyListener = new View.OnKeyListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
|
||||||
if (event.getAction() != KeyEvent.ACTION_DOWN) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mAdjustable && (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
|
|
||||||
|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) {
|
|
||||||
// Right or left keys are pressed when in non-adjustable mode; Skip the keys.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't want to propagate the click keys down to the SeekBar view since it will
|
|
||||||
// create the ripple effect for the thumb.
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mSeekBar == null) {
|
|
||||||
Log.e(TAG, "SeekBar view is null and hence cannot be adjusted.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return mSeekBar.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public TextPercentageSeekBarPreference(
|
|
||||||
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
|
|
||||||
TypedArray a = context.obtainStyledAttributes(
|
|
||||||
attrs, R.styleable.SeekBarPreference, defStyleAttr, defStyleRes);
|
|
||||||
|
|
||||||
// The ordering of these two statements are important. If we want to set max first, we need
|
|
||||||
// to perform the same steps by changing min/max to max/min as following:
|
|
||||||
// mMax = a.getInt(...) and setMin(...).
|
|
||||||
mMin = a.getInt(R.styleable.SeekBarPreference_min, 0);
|
|
||||||
setMax(a.getInt(R.styleable.SeekBarPreference_android_max, 100));
|
|
||||||
setSeekBarIncrement(a.getInt(R.styleable.SeekBarPreference_seekBarIncrement, 0));
|
|
||||||
mAdjustable = a.getBoolean(R.styleable.SeekBarPreference_adjustable, true);
|
|
||||||
mShowSeekBarValue = a.getBoolean(R.styleable.SeekBarPreference_showSeekBarValue, false);
|
|
||||||
mUpdatesContinuously = a.getBoolean(R.styleable.SeekBarPreference_updatesContinuously,
|
|
||||||
false);
|
|
||||||
a.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextPercentageSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
this(context, attrs, defStyleAttr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextPercentageSeekBarPreference(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, R.attr.seekBarPreferenceStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextPercentageSeekBarPreference(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
|
||||||
super.onBindViewHolder(view);
|
|
||||||
view.itemView.setOnKeyListener(mSeekBarKeyListener);
|
|
||||||
mSeekBar = (SeekBar) view.findViewById(R.id.seekbar);
|
|
||||||
mExampleTextTextView = (TextView) view.findViewById(R.id.sampleText);
|
|
||||||
mSeekBarValueTextView = (TextView) view.findViewById(R.id.seekbar_value);
|
|
||||||
if (mShowSeekBarValue) {
|
|
||||||
mSeekBarValueTextView.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
mSeekBarValueTextView.setVisibility(View.GONE);
|
|
||||||
mSeekBarValueTextView = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mSeekBar == null) {
|
|
||||||
Log.e(TAG, "SeekBar view is null in onBindViewHolder.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
|
|
||||||
mSeekBar.setMax(mMax - mMin);
|
|
||||||
// If the increment is not zero, use that. Otherwise, use the default mKeyProgressIncrement
|
|
||||||
// in AbsSeekBar when it's zero. This default increment value is set by AbsSeekBar
|
|
||||||
// after calling setMax. That's why it's important to call setKeyProgressIncrement after
|
|
||||||
// calling setMax() since setMax() can change the increment value.
|
|
||||||
if (mSeekBarIncrement != 0) {
|
|
||||||
mSeekBar.setKeyProgressIncrement(mSeekBarIncrement);
|
|
||||||
} else {
|
|
||||||
mSeekBarIncrement = mSeekBar.getKeyProgressIncrement();
|
|
||||||
}
|
|
||||||
|
|
||||||
mSeekBar.setProgress(mSeekBarValue - mMin);
|
|
||||||
updateExampleTextValue(mSeekBarValue);
|
|
||||||
updateLabelValue(mSeekBarValue);
|
|
||||||
mSeekBar.setEnabled(isEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSetInitialValue(Object defaultValue) {
|
|
||||||
if (defaultValue == null) {
|
|
||||||
defaultValue = 0;
|
|
||||||
}
|
|
||||||
setValue(getPersistedInt((Integer) defaultValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object onGetDefaultValue(TypedArray a, int index) {
|
|
||||||
return a.getInt(index, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the lower bound set on the {@link SeekBar}.
|
|
||||||
*
|
|
||||||
* @return The lower bound set
|
|
||||||
*/
|
|
||||||
public int getMin() {
|
|
||||||
return mMin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the lower bound on the {@link SeekBar}.
|
|
||||||
*
|
|
||||||
* @param min The lower bound to set
|
|
||||||
*/
|
|
||||||
public void setMin(int min) {
|
|
||||||
if (min > mMax) {
|
|
||||||
min = mMax;
|
|
||||||
}
|
|
||||||
if (min != mMin) {
|
|
||||||
mMin = min;
|
|
||||||
notifyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the amount of increment change via each arrow key click. This value is derived from
|
|
||||||
* user's specified increment value if it's not zero. Otherwise, the default value is picked
|
|
||||||
* from the default mKeyProgressIncrement value in {@link android.widget.AbsSeekBar}.
|
|
||||||
*
|
|
||||||
* @return The amount of increment on the {@link SeekBar} performed after each user's arrow
|
|
||||||
* key press
|
|
||||||
*/
|
|
||||||
public final int getSeekBarIncrement() {
|
|
||||||
return mSeekBarIncrement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the increment amount on the {@link SeekBar} for each arrow key press.
|
|
||||||
*
|
|
||||||
* @param seekBarIncrement The amount to increment or decrement when the user presses an
|
|
||||||
* arrow key.
|
|
||||||
*/
|
|
||||||
public final void setSeekBarIncrement(int seekBarIncrement) {
|
|
||||||
if (seekBarIncrement != mSeekBarIncrement) {
|
|
||||||
mSeekBarIncrement = Math.min(mMax - mMin, Math.abs(seekBarIncrement));
|
|
||||||
notifyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the upper bound set on the {@link SeekBar}.
|
|
||||||
*
|
|
||||||
* @return The upper bound set
|
|
||||||
*/
|
|
||||||
public int getMax() {
|
|
||||||
return mMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the upper bound on the {@link SeekBar}.
|
|
||||||
*
|
|
||||||
* @param max The upper bound to set
|
|
||||||
*/
|
|
||||||
public final void setMax(int max) {
|
|
||||||
if (max < mMin) {
|
|
||||||
max = mMin;
|
|
||||||
}
|
|
||||||
if (max != mMax) {
|
|
||||||
mMax = max;
|
|
||||||
notifyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets whether the {@link SeekBar} should respond to the left/right keys.
|
|
||||||
*
|
|
||||||
* @return Whether the {@link SeekBar} should respond to the left/right keys
|
|
||||||
*/
|
|
||||||
public boolean isAdjustable() {
|
|
||||||
return mAdjustable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the {@link SeekBar} should respond to the left/right keys.
|
|
||||||
*
|
|
||||||
* @param adjustable Whether the {@link SeekBar} should respond to the left/right keys
|
|
||||||
*/
|
|
||||||
public void setAdjustable(boolean adjustable) {
|
|
||||||
mAdjustable = adjustable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets whether the {@link androidx.preference.SeekBarPreference} should continuously save the {@link SeekBar} value
|
|
||||||
* while it is being dragged. Note that when the value is true,
|
|
||||||
* {@link Preference.OnPreferenceChangeListener} will be called continuously as well.
|
|
||||||
*
|
|
||||||
* @return Whether the {@link androidx.preference.SeekBarPreference} should continuously save the {@link SeekBar}
|
|
||||||
* value while it is being dragged
|
|
||||||
* @see #setUpdatesContinuously(boolean)
|
|
||||||
*/
|
|
||||||
public boolean getUpdatesContinuously() {
|
|
||||||
return mUpdatesContinuously;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the {@link androidx.preference.SeekBarPreference} should continuously save the {@link SeekBar} value
|
|
||||||
* while it is being dragged.
|
|
||||||
*
|
|
||||||
* @param updatesContinuously Whether the {@link androidx.preference.SeekBarPreference} should continuously save
|
|
||||||
* the {@link SeekBar} value while it is being dragged
|
|
||||||
* @see #getUpdatesContinuously()
|
|
||||||
*/
|
|
||||||
public void setUpdatesContinuously(boolean updatesContinuously) {
|
|
||||||
mUpdatesContinuously = updatesContinuously;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets whether the current {@link SeekBar} value is displayed to the user.
|
|
||||||
*
|
|
||||||
* @return Whether the current {@link SeekBar} value is displayed to the user
|
|
||||||
* @see #setShowSeekBarValue(boolean)
|
|
||||||
*/
|
|
||||||
public boolean getShowSeekBarValue() {
|
|
||||||
return mShowSeekBarValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the current {@link SeekBar} value is displayed to the user.
|
|
||||||
*
|
|
||||||
* @param showSeekBarValue Whether the current {@link SeekBar} value is displayed to the user
|
|
||||||
* @see #getShowSeekBarValue()
|
|
||||||
*/
|
|
||||||
public void setShowSeekBarValue(boolean showSeekBarValue) {
|
|
||||||
mShowSeekBarValue = showSeekBarValue;
|
|
||||||
notifyChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setValueInternal(int seekBarValue, boolean notifyChanged) {
|
|
||||||
if (seekBarValue < mMin) {
|
|
||||||
seekBarValue = mMin;
|
|
||||||
}
|
|
||||||
if (seekBarValue > mMax) {
|
|
||||||
seekBarValue = mMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seekBarValue != mSeekBarValue) {
|
|
||||||
mSeekBarValue = seekBarValue;
|
|
||||||
updateLabelValue(mSeekBarValue);
|
|
||||||
updateExampleTextValue(mSeekBarValue);
|
|
||||||
persistInt(seekBarValue);
|
|
||||||
if (notifyChanged) {
|
|
||||||
notifyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current progress of the {@link SeekBar}.
|
|
||||||
*
|
|
||||||
* @return The current progress of the {@link SeekBar}
|
|
||||||
*/
|
|
||||||
public int getValue() {
|
|
||||||
return mSeekBarValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current progress of the {@link SeekBar}.
|
|
||||||
*
|
|
||||||
* @param seekBarValue The current progress of the {@link SeekBar}
|
|
||||||
*/
|
|
||||||
public void setValue(int seekBarValue) {
|
|
||||||
setValueInternal(seekBarValue, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persist the {@link SeekBar}'s SeekBar value if callChangeListener returns true, otherwise
|
|
||||||
* set the {@link SeekBar}'s value to the stored value.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
|
||||||
void syncValueInternal(SeekBar seekBar) {
|
|
||||||
int seekBarValue = mMin + seekBar.getProgress();
|
|
||||||
if (seekBarValue != mSeekBarValue) {
|
|
||||||
if (callChangeListener(seekBarValue)) {
|
|
||||||
setValueInternal(seekBarValue, false);
|
|
||||||
} else {
|
|
||||||
seekBar.setProgress(mSeekBarValue - mMin);
|
|
||||||
updateLabelValue(mSeekBarValue);
|
|
||||||
updateExampleTextValue(mSeekBarValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to update the TextView label that displays the current value.
|
|
||||||
*
|
|
||||||
* @param value the value to display next to the {@link SeekBar}
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
|
||||||
void updateLabelValue(int value) {
|
|
||||||
if (mSeekBarValueTextView != null) {
|
|
||||||
value = (value * STEP_SIZE) + MIN_VALUE;
|
|
||||||
final double decimalValue = value / DECIMAL_CONVERSION;
|
|
||||||
final String percentage = NumberFormat.getPercentInstance().format(decimalValue);
|
|
||||||
mSeekBarValueTextView.setText(percentage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to update the example TextView text with text scale size.
|
|
||||||
*
|
|
||||||
* @param value the value of text size
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic access */
|
|
||||||
void updateExampleTextValue(int value) {
|
|
||||||
if (mExampleTextTextView != null) {
|
|
||||||
value = (value * STEP_SIZE) + MIN_VALUE;
|
|
||||||
final float decimal = value / DECIMAL_CONVERSION;
|
|
||||||
final float textSize = 16f * decimal;
|
|
||||||
mExampleTextTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Parcelable onSaveInstanceState() {
|
|
||||||
final Parcelable superState = super.onSaveInstanceState();
|
|
||||||
if (isPersistent()) {
|
|
||||||
// No need to save instance state since it's persistent
|
|
||||||
return superState;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the instance state
|
|
||||||
final SavedState myState = new SavedState(superState);
|
|
||||||
myState.mSeekBarValue = mSeekBarValue;
|
|
||||||
myState.mMin = mMin;
|
|
||||||
myState.mMax = mMax;
|
|
||||||
return myState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRestoreInstanceState(Parcelable state) {
|
|
||||||
if (!state.getClass().equals(SavedState.class)) {
|
|
||||||
// Didn't save state for us in onSaveInstanceState
|
|
||||||
super.onRestoreInstanceState(state);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the instance state
|
|
||||||
SavedState myState = (SavedState) state;
|
|
||||||
super.onRestoreInstanceState(myState.getSuperState());
|
|
||||||
mSeekBarValue = myState.mSeekBarValue;
|
|
||||||
mMin = myState.mMin;
|
|
||||||
mMax = myState.mMax;
|
|
||||||
notifyChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SavedState, a subclass of {@link BaseSavedState}, will store the state of this preference.
|
|
||||||
*
|
|
||||||
* <p>It is important to always call through to super methods.
|
|
||||||
*/
|
|
||||||
private static class SavedState extends BaseSavedState {
|
|
||||||
public static final Parcelable.Creator<SavedState> CREATOR =
|
|
||||||
new Parcelable.Creator<SavedState>() {
|
|
||||||
@Override
|
|
||||||
public SavedState createFromParcel(Parcel in) {
|
|
||||||
return new SavedState(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SavedState[] newArray(int size) {
|
|
||||||
return new SavedState[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int mSeekBarValue;
|
|
||||||
int mMin;
|
|
||||||
int mMax;
|
|
||||||
|
|
||||||
SavedState(Parcel source) {
|
|
||||||
super(source);
|
|
||||||
|
|
||||||
// Restore the click counter
|
|
||||||
mSeekBarValue = source.readInt();
|
|
||||||
mMin = source.readInt();
|
|
||||||
mMax = source.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
SavedState(Parcelable superState) {
|
|
||||||
super(superState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
super.writeToParcel(dest, flags);
|
|
||||||
|
|
||||||
// Save the click counter
|
|
||||||
dest.writeInt(mSeekBarValue);
|
|
||||||
dest.writeInt(mMin);
|
|
||||||
dest.writeInt(mMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,438 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.settings
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2018 The Android Open Source 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.TypedArray
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.SeekBar
|
||||||
|
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceViewHolder
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
|
import java.text.NumberFormat
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference based on android.preference.SeekBarPreference but uses support preference as a base
|
||||||
|
* . It contains a title and a [SeekBar] and a SeekBar value [TextView] and an Example [TextView].
|
||||||
|
* The actual preference layout is customizable by setting `android:layout` on the
|
||||||
|
* preference widget layout or `seekBarPreferenceStyle` attribute.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The [SeekBar] within the preference can be defined adjustable or not by setting `adjustable` attribute.
|
||||||
|
* If adjustable, the preference will be responsive to DPAD left/right keys.
|
||||||
|
* Otherwise, it skips those keys.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The [SeekBar] value view can be shown or disabled by setting `showSeekBarValue`
|
||||||
|
* attribute to true or false, respectively.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Other [SeekBar] specific attributes (e.g. `title, summary, defaultValue, min,
|
||||||
|
* max`)
|
||||||
|
* can be set directly on the preference widget layout.
|
||||||
|
*/
|
||||||
|
class TextPercentageSeekBarPreference @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = R.attr.seekBarPreferenceStyle,
|
||||||
|
defStyleRes: Int = 0
|
||||||
|
) : Preference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
internal /* synthetic access */ var mSeekBarValue: Int = 0
|
||||||
|
internal /* synthetic access */ var mMin: Int = 0
|
||||||
|
private var mMax: Int = 0
|
||||||
|
private var mSeekBarIncrement: Int = 0
|
||||||
|
internal /* synthetic access */ var mTrackingTouch: Boolean = false
|
||||||
|
internal /* synthetic access */ var mSeekBar: SeekBar? = null
|
||||||
|
private var mSeekBarValueTextView: TextView? = null
|
||||||
|
private var mExampleTextTextView: TextView? = null
|
||||||
|
// Whether the SeekBar should respond to the left/right keys
|
||||||
|
/* synthetic access */ var isAdjustable: Boolean = false
|
||||||
|
// Whether to show the SeekBar value TextView next to the bar
|
||||||
|
private var mShowSeekBarValue: Boolean = false
|
||||||
|
// Whether the SeekBarPreference should continuously save the Seekbar value while it is being
|
||||||
|
// dragged.
|
||||||
|
/* synthetic access */ var updatesContinuously: Boolean = false
|
||||||
|
/**
|
||||||
|
* Listener reacting to the [SeekBar] changing value by the user
|
||||||
|
*/
|
||||||
|
private val mSeekBarChangeListener = object : OnSeekBarChangeListener {
|
||||||
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
|
if (fromUser && (updatesContinuously || !mTrackingTouch)) {
|
||||||
|
syncValueInternal(seekBar)
|
||||||
|
} else {
|
||||||
|
// We always want to update the text while the seekbar is being dragged
|
||||||
|
updateLabelValue(progress + mMin)
|
||||||
|
updateExampleTextValue(progress + mMin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||||
|
mTrackingTouch = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||||
|
mTrackingTouch = false
|
||||||
|
if (seekBar.progress + mMin != mSeekBarValue) {
|
||||||
|
syncValueInternal(seekBar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener reacting to the user pressing DPAD left/right keys if `adjustable` attribute is
|
||||||
|
* set to true; it transfers the key presses to the [SeekBar]
|
||||||
|
* to be handled accordingly.
|
||||||
|
*/
|
||||||
|
private val mSeekBarKeyListener = View.OnKeyListener { v, keyCode, event ->
|
||||||
|
if (event.action != KeyEvent.ACTION_DOWN) {
|
||||||
|
return@OnKeyListener false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAdjustable && (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) {
|
||||||
|
// Right or left keys are pressed when in non-adjustable mode; Skip the keys.
|
||||||
|
return@OnKeyListener false
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want to propagate the click keys down to the SeekBar view since it will
|
||||||
|
// create the ripple effect for the thumb.
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||||
|
return@OnKeyListener false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSeekBar == null) {
|
||||||
|
Log.e(TAG, "SeekBar view is null and hence cannot be adjusted.")
|
||||||
|
return@OnKeyListener false
|
||||||
|
}
|
||||||
|
mSeekBar!!.onKeyDown(keyCode, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the lower bound set on the [SeekBar].
|
||||||
|
* @return The lower bound set
|
||||||
|
* Sets the lower bound on the [SeekBar].
|
||||||
|
* @param min The lower bound to set
|
||||||
|
*/
|
||||||
|
var min: Int
|
||||||
|
get() = mMin
|
||||||
|
set(min) {
|
||||||
|
var min = min
|
||||||
|
if (min > mMax) {
|
||||||
|
min = mMax
|
||||||
|
}
|
||||||
|
if (min != mMin) {
|
||||||
|
mMin = min
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the amount of increment change via each arrow key click. This value is derived from
|
||||||
|
* user's specified increment value if it's not zero. Otherwise, the default value is picked
|
||||||
|
* from the default mKeyProgressIncrement value in [android.widget.AbsSeekBar].
|
||||||
|
* @return The amount of increment on the [SeekBar] performed after each user's arrow
|
||||||
|
* key press
|
||||||
|
*
|
||||||
|
* Sets the increment amount on the [SeekBar] for each arrow key press.
|
||||||
|
* @param seekBarIncrement The amount to increment or decrement when the user presses an
|
||||||
|
* arrow key.
|
||||||
|
*/
|
||||||
|
var seekBarIncrement: Int
|
||||||
|
get() = mSeekBarIncrement
|
||||||
|
set(seekBarIncrement) {
|
||||||
|
if (seekBarIncrement != mSeekBarIncrement) {
|
||||||
|
mSeekBarIncrement = Math.min(mMax - mMin, Math.abs(seekBarIncrement))
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the upper bound set on the [SeekBar].
|
||||||
|
* @return The upper bound set
|
||||||
|
* Sets the upper bound on the [SeekBar].
|
||||||
|
* @param max The upper bound to set
|
||||||
|
*/
|
||||||
|
var max: Int
|
||||||
|
get() = mMax
|
||||||
|
set(max) {
|
||||||
|
var max = max
|
||||||
|
if (max < mMin) {
|
||||||
|
max = mMin
|
||||||
|
}
|
||||||
|
if (max != mMax) {
|
||||||
|
mMax = max
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether the current [SeekBar] value is displayed to the user.
|
||||||
|
* @return Whether the current [SeekBar] value is displayed to the user
|
||||||
|
* @see .setShowSeekBarValue
|
||||||
|
* Sets whether the current [SeekBar] value is displayed to the user.
|
||||||
|
* @param showSeekBarValue Whether the current [SeekBar] value is displayed to the user
|
||||||
|
* @see .getShowSeekBarValue
|
||||||
|
*/
|
||||||
|
var showSeekBarValue: Boolean
|
||||||
|
get() = mShowSeekBarValue
|
||||||
|
set(showSeekBarValue) {
|
||||||
|
mShowSeekBarValue = showSeekBarValue
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current progress of the [SeekBar].
|
||||||
|
* @return The current progress of the [SeekBar]
|
||||||
|
* Sets the current progress of the [SeekBar].
|
||||||
|
* @param seekBarValue The current progress of the [SeekBar]
|
||||||
|
*/
|
||||||
|
var value: Int
|
||||||
|
get() = mSeekBarValue
|
||||||
|
set(seekBarValue) = setValueInternal(seekBarValue, true)
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
val a = context.obtainStyledAttributes(
|
||||||
|
attrs, R.styleable.SeekBarPreference, defStyleAttr, defStyleRes
|
||||||
|
)
|
||||||
|
|
||||||
|
// The ordering of these two statements are important. If we want to set max first, we need
|
||||||
|
// to perform the same steps by changing min/max to max/min as following:
|
||||||
|
// mMax = a.getInt(...) and setMin(...).
|
||||||
|
mMin = a.getInt(R.styleable.SeekBarPreference_min, 0)
|
||||||
|
max = a.getInt(R.styleable.SeekBarPreference_android_max, SEEK_BAR_MAX)
|
||||||
|
seekBarIncrement = a.getInt(R.styleable.SeekBarPreference_seekBarIncrement, 0)
|
||||||
|
isAdjustable = a.getBoolean(R.styleable.SeekBarPreference_adjustable, true)
|
||||||
|
mShowSeekBarValue = a.getBoolean(R.styleable.SeekBarPreference_showSeekBarValue, false)
|
||||||
|
updatesContinuously = a.getBoolean(
|
||||||
|
R.styleable.SeekBarPreference_updatesContinuously,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
a.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(view: PreferenceViewHolder) {
|
||||||
|
super.onBindViewHolder(view)
|
||||||
|
view.itemView.setOnKeyListener(mSeekBarKeyListener)
|
||||||
|
mSeekBar = view.findViewById(R.id.seekbar) as SeekBar
|
||||||
|
mExampleTextTextView = view.findViewById(R.id.sampleText) as TextView
|
||||||
|
mSeekBarValueTextView = view.findViewById(R.id.seekbar_value) as TextView
|
||||||
|
if (mShowSeekBarValue) {
|
||||||
|
mSeekBarValueTextView?.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
mSeekBarValueTextView?.visibility = View.GONE
|
||||||
|
mSeekBarValueTextView = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSeekBar == null) {
|
||||||
|
Log.e(TAG, "SeekBar view is null in onBindViewHolder.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mSeekBar?.setOnSeekBarChangeListener(mSeekBarChangeListener)
|
||||||
|
mSeekBar?.max = mMax - mMin
|
||||||
|
// If the increment is not zero, use that. Otherwise, use the default mKeyProgressIncrement
|
||||||
|
// in AbsSeekBar when it's zero. This default increment value is set by AbsSeekBar
|
||||||
|
// after calling setMax. That's why it's important to call setKeyProgressIncrement after
|
||||||
|
// calling setMax() since setMax() can change the increment value.
|
||||||
|
if (mSeekBarIncrement != 0) {
|
||||||
|
mSeekBar?.keyProgressIncrement = mSeekBarIncrement
|
||||||
|
} else {
|
||||||
|
mSeekBarIncrement = mSeekBar!!.keyProgressIncrement
|
||||||
|
}
|
||||||
|
|
||||||
|
mSeekBar?.progress = mSeekBarValue - mMin
|
||||||
|
updateExampleTextValue(mSeekBarValue)
|
||||||
|
updateLabelValue(mSeekBarValue)
|
||||||
|
mSeekBar?.isEnabled = isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSetInitialValue(defaultValue: Any?) {
|
||||||
|
var defaultValue = defaultValue
|
||||||
|
if (defaultValue == null) {
|
||||||
|
defaultValue = 0
|
||||||
|
}
|
||||||
|
value = getPersistedInt((defaultValue as Int?)!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetDefaultValue(a: TypedArray?, index: Int): Any {
|
||||||
|
return a!!.getInt(index, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setValueInternal(seekBarValue: Int, notifyChanged: Boolean) {
|
||||||
|
var seekBarValue = seekBarValue
|
||||||
|
if (seekBarValue < mMin) {
|
||||||
|
seekBarValue = mMin
|
||||||
|
}
|
||||||
|
if (seekBarValue > mMax) {
|
||||||
|
seekBarValue = mMax
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seekBarValue != mSeekBarValue) {
|
||||||
|
mSeekBarValue = seekBarValue
|
||||||
|
updateLabelValue(mSeekBarValue)
|
||||||
|
updateExampleTextValue(mSeekBarValue)
|
||||||
|
persistInt(seekBarValue)
|
||||||
|
if (notifyChanged) {
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the [SeekBar]'s SeekBar value if callChangeListener returns true, otherwise
|
||||||
|
* set the [SeekBar]'s value to the stored value.
|
||||||
|
*/
|
||||||
|
internal /* synthetic access */ fun syncValueInternal(seekBar: SeekBar) {
|
||||||
|
val seekBarValue = mMin + seekBar.progress
|
||||||
|
if (seekBarValue != mSeekBarValue) {
|
||||||
|
if (callChangeListener(seekBarValue)) {
|
||||||
|
setValueInternal(seekBarValue, false)
|
||||||
|
} else {
|
||||||
|
seekBar.progress = mSeekBarValue - mMin
|
||||||
|
updateLabelValue(mSeekBarValue)
|
||||||
|
updateExampleTextValue(mSeekBarValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to update the TextView label that displays the current value.
|
||||||
|
*
|
||||||
|
* @param value the value to display next to the [SeekBar]
|
||||||
|
*/
|
||||||
|
internal /* synthetic access */ fun updateLabelValue(value: Int) {
|
||||||
|
var value = value
|
||||||
|
if (mSeekBarValueTextView != null) {
|
||||||
|
value = value * STEP_SIZE + MIN_VALUE
|
||||||
|
val decimalValue = (value / DECIMAL_CONVERSION).toDouble()
|
||||||
|
val percentage = NumberFormat.getPercentInstance().format(decimalValue)
|
||||||
|
mSeekBarValueTextView?.text = percentage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to update the example TextView text with text scale size.
|
||||||
|
*
|
||||||
|
* @param value the value of text size
|
||||||
|
*/
|
||||||
|
internal /* synthetic access */ fun updateExampleTextValue(value: Int) {
|
||||||
|
var value = value
|
||||||
|
if (mExampleTextTextView != null) {
|
||||||
|
value = value * STEP_SIZE + MIN_VALUE
|
||||||
|
val decimal = value / DECIMAL_CONVERSION
|
||||||
|
val textSize = TEXT_SIZE * decimal
|
||||||
|
mExampleTextTextView?.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(): Parcelable {
|
||||||
|
val superState = super.onSaveInstanceState()
|
||||||
|
if (isPersistent) {
|
||||||
|
// No need to save instance state since it's persistent
|
||||||
|
return superState
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the instance state
|
||||||
|
val myState = SavedState(superState)
|
||||||
|
myState.mSeekBarValue = mSeekBarValue
|
||||||
|
myState.mMin = mMin
|
||||||
|
myState.mMax = mMax
|
||||||
|
return myState
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRestoreInstanceState(state: Parcelable?) {
|
||||||
|
if (state?.javaClass != SavedState::class.java) {
|
||||||
|
// Didn't save state for us in onSaveInstanceState
|
||||||
|
super.onRestoreInstanceState(state)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the instance state
|
||||||
|
val myState = state as SavedState?
|
||||||
|
super.onRestoreInstanceState(myState!!.superState)
|
||||||
|
mSeekBarValue = myState.mSeekBarValue
|
||||||
|
mMin = myState.mMin
|
||||||
|
mMax = myState.mMax
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SavedState, a subclass of [BaseSavedState], will store the state of this preference.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* It is important to always call through to super methods.
|
||||||
|
*/
|
||||||
|
private class SavedState : BaseSavedState {
|
||||||
|
|
||||||
|
internal var mSeekBarValue: Int = 0
|
||||||
|
internal var mMin: Int = 0
|
||||||
|
internal var mMax: Int = 0
|
||||||
|
|
||||||
|
internal constructor(source: Parcel) : super(source) {
|
||||||
|
|
||||||
|
// Restore the click counter
|
||||||
|
mSeekBarValue = source.readInt()
|
||||||
|
mMin = source.readInt()
|
||||||
|
mMax = source.readInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal constructor(superState: Parcelable) : super(superState)
|
||||||
|
|
||||||
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
|
super.writeToParcel(dest, flags)
|
||||||
|
|
||||||
|
// Save the click counter
|
||||||
|
dest.writeInt(mSeekBarValue)
|
||||||
|
dest.writeInt(mMin)
|
||||||
|
dest.writeInt(mMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
|
||||||
|
override fun createFromParcel(`in`: Parcel): SavedState {
|
||||||
|
return SavedState(`in`)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<SavedState?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "SeekBarPreference"
|
||||||
|
private const val STEP_SIZE = 5
|
||||||
|
private const val MIN_VALUE = 50
|
||||||
|
private const val DECIMAL_CONVERSION = 100f
|
||||||
|
private const val TEXT_SIZE = 16f
|
||||||
|
private const val SEEK_BAR_MAX = 100
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue