/* * Copyright (c) 2013, Psiphon Inc. * All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 program. If not, see <http://www.gnu.org/licenses/>. * */ /* * SeekBarPreference: * * http://robobunny.com/wp/2013/08/24/android-seekbar-preference-v2/ * License: Public Domain (http://robobunny.com/wp/2013/08/24/android-seekbar-preference-v2/#comment-7495) */ package ca.psiphon.ploggy.widgets; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.preference.Preference; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import ca.psiphon.ploggy.R; public class SeekBarPreference extends Preference implements OnSeekBarChangeListener { private final String TAG = getClass().getName(); private static final String ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"; private static final String PLOGGY_NAMESPACE = "http://ploggy.psiphon.ca"; private static final int DEFAULT_VALUE = 50; private int mMaxValue = 100; private int mMinValue = 0; private int mInterval = 1; private int mCurrentValue; private String mUnitsLeft = ""; private String mUnitsRight = ""; private SeekBar mSeekBar; private TextView mStatusText; public SeekBarPreference(Context context, AttributeSet attrs) { super(context, attrs); initPreference(context, attrs); } public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initPreference(context, attrs); } private void initPreference(Context context, AttributeSet attrs) { setValuesFromXml(attrs); mSeekBar = new SeekBar(context, attrs); mSeekBar.setMax(mMaxValue - mMinValue); mSeekBar.setOnSeekBarChangeListener(this); setWidgetLayoutResource(R.layout.seek_bar_preference); } private void setValuesFromXml(AttributeSet attrs) { mMaxValue = attrs.getAttributeIntValue(ANDROID_NAMESPACE, "max", 100); mMinValue = attrs.getAttributeIntValue(PLOGGY_NAMESPACE, "min", 0); mUnitsLeft = getAttributeStringValue(attrs, PLOGGY_NAMESPACE, "unitsLeft", ""); String units = getAttributeStringValue(attrs, PLOGGY_NAMESPACE, "units", ""); mUnitsRight = getAttributeStringValue(attrs, PLOGGY_NAMESPACE, "unitsRight", units); try { String newInterval = attrs.getAttributeValue(PLOGGY_NAMESPACE, "interval"); if (newInterval != null) { mInterval = Integer.parseInt(newInterval); } } catch(Exception e) { Log.e(TAG, "Invalid interval value", e); } } private String getAttributeStringValue(AttributeSet attrs, String namespace, String name, String defaultValue) { String value = attrs.getAttributeValue(namespace, name); if (value == null) { value = defaultValue; } if (value.length() > 1 && value.charAt(0) == '@' && value.contains("@string/")) { Context context = getContext(); Resources res = context.getResources(); final int id = res.getIdentifier(context.getPackageName() + ":" + value.substring(1), null, null); value = context.getString(id); } return value; } @Override protected View onCreateView(ViewGroup parent) { View view = super.onCreateView(parent); // TODO: ...breaks layout on tablets, where standard title/summary views are indented // The basic preference layout puts the widget frame to the right of the title and summary, // so we need to change it a bit - the seekbar should be under them. LinearLayout layout = (LinearLayout) view; layout.setOrientation(LinearLayout.VERTICAL); return view; } @Override public void onBindView(View view) { super.onBindView(view); if (view != null) { mSeekBar = (SeekBar)view.findViewById(R.id.seekBarPrefSeekBar); mSeekBar.setMax(mMaxValue - mMinValue); mSeekBar.setOnSeekBarChangeListener(this); mSeekBar.setEnabled(view.isEnabled()); } updateView(view); } /** * Update a SeekBarPreference view with our current state * @param view */ protected void updateView(View view) { try { mStatusText = (TextView) view.findViewById(R.id.seekBarPrefValue); mStatusText.setText(String.valueOf(mCurrentValue)); mStatusText.setMinimumWidth(30); mSeekBar.setProgress(mCurrentValue - mMinValue); TextView unitsRight = (TextView)view.findViewById(R.id.seekBarPrefUnitsRight); unitsRight.setText(mUnitsRight); TextView unitsLeft = (TextView)view.findViewById(R.id.seekBarPrefUnitsLeft); unitsLeft.setText(mUnitsLeft); } catch(Exception e) { Log.e(TAG, "Error updating seek bar preference", e); } } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { int newValue = progress + mMinValue; if (newValue > mMaxValue) { newValue = mMaxValue; } else if (newValue < mMinValue) { newValue = mMinValue; } else if (mInterval != 1 && newValue % mInterval != 0) { newValue = Math.round(((float)newValue)/mInterval)*mInterval; } // change rejected, revert to the previous value if (!callChangeListener(newValue)) { seekBar.setProgress(mCurrentValue - mMinValue); return; } // change accepted, store it mCurrentValue = newValue; mStatusText.setText(String.valueOf(newValue)); persistInt(newValue); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { notifyChanged(); } @Override protected Object onGetDefaultValue(TypedArray ta, int index) { int defaultValue = ta.getInt(index, DEFAULT_VALUE); return defaultValue; } @Override protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { if(restoreValue) { mCurrentValue = getPersistedInt(mCurrentValue); } else { int temp = 0; try { temp = (Integer)defaultValue; } catch(Exception ex) { Log.e(TAG, "Invalid default value: " + defaultValue.toString()); } persistInt(temp); mCurrentValue = temp; } } /** * make sure that the seekbar is disabled if the preference is disabled */ @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); mSeekBar.setEnabled(enabled); } @Override public void onDependencyChanged(Preference dependency, boolean disableDependent) { super.onDependencyChanged(dependency, disableDependent); //Disable movement of seek bar when dependency is false if (mSeekBar != null) { mSeekBar.setEnabled(!disableDependent); } } }