/*
* Copyright 2016 Hippo Seven
*
* 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.hippo.preference;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.annotation.StringRes;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import com.hippo.ehviewer.R;
/**
* A base class for {@link Preference} objects that are
* dialog-based. These preferences will, when clicked, open a dialog showing the
* actual preference controls.
*/
public abstract class DialogPreference extends Preference implements
DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
PreferenceManager.OnActivityDestroyListener {
private AlertDialog.Builder mBuilder;
private CharSequence mDialogTitle;
private Drawable mDialogIcon;
private CharSequence mPositiveButtonText;
private CharSequence mNegativeButtonText;
private int mDialogLayoutResId;
/** The dialog, if it is showing. */
private AlertDialog mDialog;
/** Which button was clicked. */
private int mWhichButtonClicked;
public DialogPreference(Context context) {
super(context);
init(context, null, 0, 0);
}
public DialogPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0, 0);
}
public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, 0);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DialogPreference, defStyleAttr, defStyleRes);
mDialogTitle = a.getString(R.styleable.DialogPreference_dialogTitle);
if (mDialogTitle == null) {
// Fallback on the regular title of the preference
// (the one that is seen in the list)
mDialogTitle = getTitle();
}
mDialogIcon = a.getDrawable(R.styleable.DialogPreference_dialogIcon);
mPositiveButtonText = a.getString(R.styleable.DialogPreference_positiveButtonText);
mNegativeButtonText = a.getString(R.styleable.DialogPreference_negativeButtonText);
mDialogLayoutResId = a.getResourceId(R.styleable.DialogPreference_dialogLayout, mDialogLayoutResId);
a.recycle();
}
/**
* Sets the title of the dialog. This will be shown on subsequent dialogs.
*
* @param dialogTitle The title.
*/
public void setDialogTitle(CharSequence dialogTitle) {
mDialogTitle = dialogTitle;
}
/**
* @see #setDialogTitle(CharSequence)
* @param dialogTitleResId The dialog title as a resource.
*/
public void setDialogTitle(int dialogTitleResId) {
setDialogTitle(getContext().getString(dialogTitleResId));
}
/**
* Returns the title to be shown on subsequent dialogs.
* @return The title.
*/
public CharSequence getDialogTitle() {
return mDialogTitle;
}
/**
* Sets the icon of the dialog. This will be shown on subsequent dialogs.
*
* @param dialogIcon The icon, as a {@link Drawable}.
*/
public void setDialogIcon(Drawable dialogIcon) {
mDialogIcon = dialogIcon;
}
/**
* Sets the icon (resource ID) of the dialog. This will be shown on
* subsequent dialogs.
*
* @param dialogIconRes The icon, as a resource ID.
*/
public void setDialogIcon(@DrawableRes int dialogIconRes) {
mDialogIcon = ContextCompat.getDrawable(getContext(), dialogIconRes);
}
/**
* Returns the icon to be shown on subsequent dialogs.
* @return The icon, as a {@link Drawable}.
*/
public Drawable getDialogIcon() {
return mDialogIcon;
}
/**
* Sets the text of the positive button of the dialog. This will be shown on
* subsequent dialogs.
*
* @param positiveButtonText The text of the positive button.
*/
public void setPositiveButtonText(CharSequence positiveButtonText) {
mPositiveButtonText = positiveButtonText;
}
/**
* @see #setPositiveButtonText(CharSequence)
* @param positiveButtonTextResId The positive button text as a resource.
*/
public void setPositiveButtonText(@StringRes int positiveButtonTextResId) {
setPositiveButtonText(getContext().getString(positiveButtonTextResId));
}
/**
* Returns the text of the positive button to be shown on subsequent
* dialogs.
*
* @return The text of the positive button.
*/
public CharSequence getPositiveButtonText() {
return mPositiveButtonText;
}
/**
* Sets the text of the negative button of the dialog. This will be shown on
* subsequent dialogs.
*
* @param negativeButtonText The text of the negative button.
*/
public void setNegativeButtonText(CharSequence negativeButtonText) {
mNegativeButtonText = negativeButtonText;
}
/**
* @see #setNegativeButtonText(CharSequence)
* @param negativeButtonTextResId The negative button text as a resource.
*/
public void setNegativeButtonText(@StringRes int negativeButtonTextResId) {
setNegativeButtonText(getContext().getString(negativeButtonTextResId));
}
/**
* Returns the text of the negative button to be shown on subsequent
* dialogs.
*
* @return The text of the negative button.
*/
public CharSequence getNegativeButtonText() {
return mNegativeButtonText;
}
/**
* Sets the layout resource that is inflated as the {@link View} to be shown
* as the content View of subsequent dialogs.
*
* @param dialogLayoutResId The layout resource ID to be inflated.
*/
public void setDialogLayoutResource(int dialogLayoutResId) {
mDialogLayoutResId = dialogLayoutResId;
}
/**
* Returns the layout resource that is used as the content View for
* subsequent dialogs.
*
* @return The layout resource.
*/
public int getDialogLayoutResource() {
return mDialogLayoutResId;
}
/**
* Prepares the dialog builder to be shown when the preference is clicked.
* Use this to set custom properties on the dialog.
* <p>
* Do not {@link AlertDialog.Builder#create()} or
* {@link AlertDialog.Builder#show()}.
*/
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
}
@Override
protected void onClick() {
if (mDialog != null && mDialog.isShowing()) return;
showDialog(null);
}
/**
* Shows the dialog associated with this Preference. This is normally initiated
* automatically on clicking on the preference. Call this method if you need to
* show the dialog on some other event.
*
* @param state Optional instance state to restore on the dialog
*/
protected void showDialog(Bundle state) {
Context context = getContext();
mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
mBuilder = new AlertDialog.Builder(context)
.setTitle(mDialogTitle)
.setIcon(mDialogIcon)
.setPositiveButton(mPositiveButtonText, this)
.setNegativeButton(mNegativeButtonText, this);
View contentView = onCreateDialogView();
if (contentView != null) {
onBindDialogView(contentView);
mBuilder.setView(contentView);
}
onPrepareDialogBuilder(mBuilder);
PreferenceUtils.registerOnActivityDestroyListener(this, this);
// Create the dialog
final AlertDialog dialog = mDialog = mBuilder.create();
if (state != null) {
dialog.onRestoreInstanceState(state);
}
if (needInputMethod()) {
requestInputMethod(dialog);
}
dialog.setOnDismissListener(this);
dialog.show();
onDialogCreated(dialog);
}
/**
* Returns whether the preference needs to display a soft input method when the dialog
* is displayed. Default is false. Subclasses should override this method if they need
* the soft input method brought up automatically.
*/
protected boolean needInputMethod() {
return false;
}
/**
* Sets the required flags on the dialog window to enable input method window to show up.
*/
private void requestInputMethod(Dialog dialog) {
Window window = dialog.getWindow();
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
/**
* Creates the content view for the dialog (if a custom content view is
* required). By default, it inflates the dialog layout resource if it is
* set.
*
* @return The content View for the dialog.
* @see #setLayoutResource(int)
*/
protected View onCreateDialogView() {
if (mDialogLayoutResId == 0) {
return null;
}
LayoutInflater inflater = LayoutInflater.from(mBuilder.getContext());
return inflater.inflate(mDialogLayoutResId, null);
}
/**
* Binds views in the content View of the dialog to data.
*
* @param view The content View of the dialog, if it is custom.
*/
protected void onBindDialogView(View view) {}
protected void onDialogCreated(AlertDialog dialog) {}
@Override
public void onClick(DialogInterface dialog, int which) {
mWhichButtonClicked = which;
}
@Override
public void onDismiss(DialogInterface dialog) {
PreferenceUtils.unregisterOnActivityDestroyListener(this, this);
mDialog = null;
onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
}
/**
* Called when the dialog is dismissed and should be used to save data to
* the {@link SharedPreferences}.
*
* @param positiveResult Whether the positive button was clicked (true), or
* the negative button was clicked or the dialog was canceled (false).
*/
protected void onDialogClosed(boolean positiveResult) {
}
/**
* Gets the dialog that is shown by this preference.
*
* @return The dialog, or null if a dialog is not being shown.
*/
public Dialog getDialog() {
return mDialog;
}
@Override
public void onActivityDestroy() {
if (mDialog == null || !mDialog.isShowing()) {
return;
}
mDialog.dismiss();
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
if (mDialog == null || !mDialog.isShowing()) {
return superState;
}
final SavedState myState = new SavedState(superState);
myState.isDialogShowing = true;
myState.dialogBundle = mDialog.onSaveInstanceState();
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save state for us in onSaveInstanceState
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
if (myState.isDialogShowing) {
showDialog(myState.dialogBundle);
}
}
private static class SavedState extends BaseSavedState {
boolean isDialogShowing;
Bundle dialogBundle;
public SavedState(Parcel source) {
super(source);
isDialogShowing = source.readInt() == 1;
dialogBundle = source.readBundle(DialogPreference.class.getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(isDialogShowing ? 1 : 0);
dest.writeBundle(dialogBundle);
}
public SavedState(Parcelable superState) {
super(superState);
}
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];
}
};
}
}