From 5e52fc46b8c04c68a1de0d6ce78277e054d4611c Mon Sep 17 00:00:00 2001 From: Nikit Bhandari Date: Tue, 30 Jul 2019 20:47:41 +0530 Subject: [PATCH] For #3443 - convert textPercentageSeekBar to Kotlin (#4081) * convert textPercentageSeekBar to Kotlin * fix test * fix test again * fix test again * conflicts resolved * merge multiple kdocs into one --- .../TextPercentageSeekBarPreference.java | 527 ------------------ .../TextPercentageSeekBarPreference.kt | 438 +++++++++++++++ 2 files changed, 438 insertions(+), 527 deletions(-) delete mode 100644 app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.java create mode 100644 app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.kt diff --git a/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.java b/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.java deleted file mode 100644 index 249898749..000000000 --- a/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.java +++ /dev/null @@ -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. - * - *

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. - * - *

The {@link SeekBar} value view can be shown or disabled by setting {@code showSeekBarValue} - * attribute to true or false, respectively. - * - *

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. - * - *

It is important to always call through to super methods. - */ - private static class SavedState extends BaseSavedState { - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @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); - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.kt b/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.kt new file mode 100644 index 000000000..59ad52c06 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.kt @@ -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 = object : Parcelable.Creator { + override fun createFromParcel(`in`: Parcel): SavedState { + return SavedState(`in`) + } + + override fun newArray(size: Int): Array { + 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 + } +}