/*
* Copyright (C) 2011 The original author or authors.
*
* 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 com.zapta.apps.maniana.settings;
import static com.zapta.apps.maniana.util.Assertions.checkNotNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import com.zapta.apps.maniana.R;
import com.zapta.apps.maniana.annotations.ActivityScope;
import com.zapta.apps.maniana.util.Orientation;
/**
* Custom preference for selecting an integer within range.
*
* Downloaded from http://android.hlidskialf.com/blog/code/android-seekbar-preference
*
* @Author Matthew Wiggins
* @author Tal Dayan
*/
@ActivityScope
public class SeekBarPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener {
private final Context mContext;
/** Cached device density. */
private float mDensity;
/** Format string for running label in dialog. Should contain %d. Attribute: text */
private final String mValueFormat;
/** Default value. Attribute: defaultValue */
private final int mDefaultValue;
/** Min value (inclusive). Attribute: minLevel */
private final int mMinValue;
/** Max value, (inclusive). Attribute: maxLevel */
private final int mMaxValue;
/** Current preference value. Updated when the user OKs a new value. */
private int mValue;
/**
* Format string for preference summary string (when dialog is closed). Can contain a single %d
* for current value.
*/
private String mSummaryFormat;
/** The SeekBar in the dialog. */
private SeekBar mSeekBar;
/** The running value string field in the dialog. */
private TextView mValueTextView;
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mDensity = context.getResources().getDisplayMetrics().density;
// Attributes defined in res/values/attr.xml
{
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SeekBarPreference);
mValueFormat = a.getString(R.styleable.SeekBarPreference_valueFormat);
mSummaryFormat = a.getString(R.styleable.SeekBarPreference_summaryFormat);
mMinValue = a.getInt(R.styleable.SeekBarPreference_minValue, 0);
mMaxValue = a.getInt(R.styleable.SeekBarPreference_maxValue, 0);
a.recycle();
}
checkNotNull(mValueFormat, "Null label format");
checkNotNull(mSummaryFormat, "Null summary format");
mDefaultValue = attrs.getAttributeIntValue(PreferenceConstants.ANDROID_NAME_SPACE,
"defaultValue", 50);
mValue = shouldPersist() ? getPersistedInt(mDefaultValue) : mDefaultValue;
updateSummaryWithCurrentValue();
}
/** Dialog preference */
@Override
protected View onCreateDialogView() {
// NOTE: in landscape mode we use tighter vertical spacing so everything
// fits in screen.
final boolean isPortrait = Orientation.currentDeviceOrientation(mContext).isPortrait;
final LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
final int layoutHorisontalPaddingPx = px(7);
layout.setPadding(layoutHorisontalPaddingPx, 0, layoutHorisontalPaddingPx, 0);
mValueTextView = new TextView(mContext);
mValueTextView.setGravity(Gravity.CENTER_HORIZONTAL);
mValueTextView.setPadding(0, isPortrait ? px(10) : px(3), 0, 0);
// NOTE: text size is in scaled pixels (sp), no need to scale.
mValueTextView.setTextSize(isPortrait ? 64 : 50);
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(mValueTextView, params);
mSeekBar = new SeekBar(mContext);
mSeekBar.setOnSeekBarChangeListener(this);
final int seekBarVerticalPaddingPx = isPortrait ? px(26) : px(7);
final int seekBarHorisontalPaddingPx = px(20);
mSeekBar.setPadding(seekBarHorisontalPaddingPx, seekBarVerticalPaddingPx,
seekBarHorisontalPaddingPx, seekBarVerticalPaddingPx);
layout.addView(mSeekBar, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
mSeekBar.setMax(mMaxValue - mMinValue);
mSeekBar.setProgress(mValue - mMinValue);
return layout;
}
/** DialogPreference */
@Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
// NOTE: SeekBar values are zero relative so we offset them accordingly
mSeekBar.setMax(mMaxValue - mMinValue);
mSeekBar.setProgress(mValue - mMinValue);
}
/** Preference */
@Override
protected void onSetInitialValue(boolean restore, Object defaultValue) {
super.onSetInitialValue(restore, defaultValue);
if (restore) {
mValue = shouldPersist() ? getPersistedInt(mDefaultValue) : mDefaultValue;
} else {
mValue = (Integer) defaultValue;
}
updateSummaryWithCurrentValue();
}
/** Called from mSeekBar. */
@Override
public void onProgressChanged(SeekBar seek, int seekBarValue, boolean fromTouch) {
final int currentValue = seekBarValue + mMinValue;
mValueTextView.setText(String.format(mValueFormat, currentValue));
}
/** Called from mSeekBar. */
@Override
public void onStartTrackingTouch(SeekBar arg0) {
// Nothing to do here
}
/** Called from mSeekBar. */
@Override
public void onStopTrackingTouch(SeekBar arg0) {
// Nothing to do here
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
// We accept changes only if the user clicked OK
if (positiveResult) {
final int newValue = mSeekBar.getProgress() + mMinValue;
setValue(newValue);
}
}
public void setValue(int newValue) {
if (newValue != mValue) {
// Enforce range
mValue = Math.min(mMaxValue, Math.max(mMinValue, newValue));
updateSummaryWithCurrentValue();
if (shouldPersist()) {
persistInt(mValue);
}
// NOTE: we ignore the returned value and always use the value.
callChangeListener(Integer.valueOf(mValue));
}
}
private final void updateSummaryWithCurrentValue() {
super.setSummary(String.format(mSummaryFormat, mValue));
}
/** Dip to pixel converter. */
private final int px(int dip) {
return (int) (dip * mDensity + 0.5f);
}
}