/* * AndFHEM - Open Source Android application to control a FHEM home automation * server. * * Copyright (c) 2011, Matthias Klass or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU GENERAL PUBLIC LICENSE, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU GENERAL PUBLIC LICENSE * for more details. * * You should have received a copy of the GNU GENERAL PUBLIC LICENSE * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ /* The following code was written by Matthew Wiggins * and is released under the APACHE 2.0 license * * http://www.apache.org/licenses/LICENSE-2.0 */ package li.klass.fhem.widget.preference; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.preference.DialogPreference; import android.preference.PreferenceManager; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.SeekBar; import android.widget.TextView; import org.jetbrains.annotations.NotNull; import li.klass.fhem.R; import static java.lang.Integer.parseInt; import static li.klass.fhem.util.NumberUtil.isDecimalNumber; /** * Preference showing a seek bar as dialog. The minimum, default and maximum values can * be configured using the respective getters and (partly) the xml configuration. * * The main layout is overtaken by * <a href="http://android.hlidskialf.com/blog/code/android-seekbar-preference">Hlidskialf Codes</a>. * However, the source code was heavily refactored and changed to fit the needs of andFHEM. * * As Android's seek bars always handle minimum values to be 0, we recalculate each value * to fit Android's needs. Each value is calculated to be <i>value - minimumValue</i>. That * way we can handle non 0 minimum values properly. * * This is also why internal values are stored in this recalculated format and not in the original * one provided by the using class. This concerns fields such as {@link #defaultValue}, * {@link #maximumValue}, {@link #minimumValue} and {@link #internalValue}. */ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener { private static final String ANDROID_NS = "http://schemas.android.com/apk/res/android"; private Context context; /** * The seek bar users can use to change values. */ private SeekBar seekBar; /** * A text field showing the current progress including a suffix value. */ private TextView valueText; /** * Some message to show to the user. The message is shown above * the seek bar! */ private String dialogMessageTop; /** * Text field for showing dialog messages, */ private TextView dialogMessageTopTextView; /** * Some message to show to the user. The message is shown below * the seek bar. */ private String dialogMessageBottom; /** * Text field for showing dialog messages below the text field, */ private TextView dialogMessageBottomTextView; /** * A suffix shown after the value within the {@link #valueText} field. */ private String suffix; /** * A default value which is used whenever no persisted value can be found. */ private int defaultValue; /** * A maximum value. * Internal note: Make sure that a progress which is set to the seek bar is always within bounds. * If a value is bigger than the maximum value, the maximum value is used. */ private int maximumValue; /** * A minimum value. This is used to recalculate all other values to the internal format. */ private int minimumValue; /** * The current progress (internal format). */ private int internalValue = 0; public SeekBarPreference(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; dialogMessageTop = attrs.getAttributeValue(ANDROID_NS, "dialogMessage"); suffix = attrs.getAttributeValue(ANDROID_NS, "text"); if (suffix.startsWith("@") && isDecimalNumber(suffix.substring(1))) { suffix = context.getString(parseInt(suffix.substring(1))); } setDefaultValue(attrs.getAttributeIntValue(ANDROID_NS, "defaultValue", 0)); setMaximumValue(attrs.getAttributeIntValue(ANDROID_NS, "max", 100)); String key = attrs.getAttributeValue(ANDROID_NS, "key"); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); if (! preferences.contains(key)) { preferences.edit().putInt(key, defaultValue).apply(); } } @Override public void setDefaultValue(Object newDefaultValue) { if (!(newDefaultValue instanceof Integer)) { return; } int newDefaultValueExternal = (Integer) newDefaultValue; int newDefaultValueInternal = toInternalValue(newDefaultValueExternal, minimumValue); // Overwrite the current default. if (defaultValue == internalValue) { setValue(newDefaultValueExternal); } this.defaultValue = newDefaultValueInternal; super.setDefaultValue(newDefaultValueInternal); } public void setMaximumValue(int maximumValue) { this.maximumValue = toInternalValue(maximumValue, minimumValue); if (internalValue > maximumValue) internalValue = maximumValue; if (seekBar != null) { seekBar.setMax(maximumValue); } } private static int toInternalValue(int value, int minimumValue) { return value - minimumValue; } @Override protected View onCreateDialogView() { @SuppressLint("InflateParams") View view = LayoutInflater.from(context).inflate(R.layout.seekbar_preference_dialog, null); assert view != null; dialogMessageTopTextView = (TextView) view.findViewById(R.id.dialogMessageTop); setDialogMessageTop(dialogMessageTop); dialogMessageBottomTextView = (TextView) view.findViewById(R.id.dialogMessageBottom); setDialogMessageBottom(dialogMessageBottom); valueText = (TextView) view.findViewById(R.id.value); seekBar = (SeekBar) view.findViewById(R.id.seekBar); seekBar.setOnSeekBarChangeListener(this); seekBar.setMax(maximumValue); if (shouldPersist()) { int externalDefault = toExternalValue(defaultValue, minimumValue); int loadValue = getPersistedInt(externalDefault); setValue(loadValue); } return view; } public void setDialogMessageTop(String dialogMessageTop) { this.dialogMessageTop = dialogMessageTop; if (dialogMessageTop != null) { dialogMessageTopTextView.setText(dialogMessageTop); } } public void setDialogMessageBottom(String dialogMessageBottom) { this.dialogMessageBottom = dialogMessageBottom; if (dialogMessageBottom != null) { dialogMessageBottomTextView.setText(dialogMessageBottom); } } private static int toExternalValue(int value, int minimumValue) { return value + minimumValue; } @Override protected void onBindDialogView(@NotNull View v) { super.onBindDialogView(v); seekBar.setMax(maximumValue); seekBar.setProgress(internalValue); } @Override protected void onSetInitialValue(boolean restore, Object def) { super.onSetInitialValue(restore, defaultValue); if (restore) { internalValue = getPersistedInt(defaultValue); } else { internalValue = defaultValue; } internalValue -= minimumValue; } public void onProgressChanged(SeekBar seek, int newValue, boolean fromTouch) { this.internalValue = newValue; updateValueText(); } /** * Update the {@link #valueText} field to match the current progress. */ private void updateValueText() { int persistValue = getValue(); String text = String.valueOf(persistValue); if (valueText != null) { valueText.setText(suffix == null ? text : text + " " + suffix); } callChangeListener(persistValue); } public int getValue() { return toExternalValue(internalValue, minimumValue); } public void setValue(int value) { internalValue = toInternalValue(value, minimumValue); if (seekBar != null) { seekBar.setProgress(internalValue); } } @Override protected void onDialogClosed(boolean positiveResult) { super.onDialogClosed(positiveResult); if (!positiveResult) return; if (shouldPersist()) { persistInt(toExternalValue(internalValue, minimumValue)); } } public void onStartTrackingTouch(SeekBar seek) { } public void onStopTrackingTouch(SeekBar seek) { } /** * Sets the minimum value and recalculates all internal values to match the new minimum value. * (We do not want to loose state). * * @param newMinimumValue minimum to set. */ public void setMinimumValue(int newMinimumValue) { int maximumValueExternal = toExternalValue(maximumValue, minimumValue); int defaultValueExternal = toExternalValue(defaultValue, minimumValue); int currentValue = toExternalValue(internalValue, minimumValue); this.minimumValue = newMinimumValue; setMaximumValue(maximumValueExternal); setDefaultValue(defaultValueExternal); setValue(currentValue); } }