diff --git a/app/src/main/java/com/hippo/preference/DialogPreference.java b/app/src/main/java/com/hippo/preference/DialogPreference.java new file mode 100644 index 000000000..5b757bbcd --- /dev/null +++ b/app/src/main/java/com/hippo/preference/DialogPreference.java @@ -0,0 +1,390 @@ +/* + * 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.annotation.TargetApi; +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.Build; +import android.os.Bundle; +import android.os.Parcelable; +import android.preference.Preference; +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 { + + private static final String KEY_SUPER = "super"; + private static final String KEY_IS_DIALOG_SHOWING = "is_dialog_showing"; + private static final String KEY_DIALOG_BUNDLE = "dialog_bundle"; + + 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); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context, attrs, defStyleAttr, defStyleRes); + } + + 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. + *

+ * 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); + + // 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) { + 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 + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (mDialog == null || !mDialog.isShowing()) { + return superState; + } + + final Bundle myState = new Bundle(); + myState.putParcelable(KEY_SUPER, superState); + myState.putBoolean(KEY_IS_DIALOG_SHOWING, true); + myState.putBundle(KEY_DIALOG_BUNDLE, mDialog.onSaveInstanceState()); + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(Bundle.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + Bundle myState = (Bundle) state; + super.onRestoreInstanceState(myState.getParcelable(KEY_SUPER)); + if (myState.getBoolean(KEY_IS_DIALOG_SHOWING)) { + showDialog(myState.getBundle(KEY_DIALOG_BUNDLE)); + } + } +} diff --git a/app/src/main/java/com/hippo/preference/SimpleDialogPreference.java b/app/src/main/java/com/hippo/preference/MessageDialogPreference.java similarity index 52% rename from app/src/main/java/com/hippo/preference/SimpleDialogPreference.java rename to app/src/main/java/com/hippo/preference/MessageDialogPreference.java index 9e0cecc03..214378ae2 100644 --- a/app/src/main/java/com/hippo/preference/SimpleDialogPreference.java +++ b/app/src/main/java/com/hippo/preference/MessageDialogPreference.java @@ -17,59 +17,74 @@ package com.hippo.preference; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; -import android.preference.DialogPreference; +import android.support.v7.app.AlertDialog; +import android.text.method.LinkMovementMethod; import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; import com.hippo.ehviewer.R; import com.hippo.text.Html; -// TODO Add html attr -// TODO Add url clickable -public class SimpleDialogPreference extends DialogPreference { +public class MessageDialogPreference extends DialogPreference { - private CharSequence mMessage; + private CharSequence mDialogMessage; + private boolean mDialogMessageLinkify; @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public SimpleDialogPreference(Context context) { + public MessageDialogPreference(Context context) { super(context); init(context, null, 0, 0); } - public SimpleDialogPreference(Context context, AttributeSet attrs) { + public MessageDialogPreference(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } - public SimpleDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { + public MessageDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public SimpleDialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + public MessageDialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } public void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleDialogPreference, defStyleAttr, defStyleRes); - setMessage(a.getString(R.styleable.SimpleDialogPreference_message)); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MessageDialogPreference, defStyleAttr, defStyleRes); + String message = a.getString(R.styleable.MessageDialogPreference_dialogMessage); + if (a.getBoolean(R.styleable.MessageDialogPreference_dialogMessageHtml, false)) { + mDialogMessage = Html.fromHtml(message); + } else { + mDialogMessage = message; + } + mDialogMessageLinkify = a.getBoolean(R.styleable.MessageDialogPreference_dialogMessageLinkify, false); a.recycle(); } - public void setMessage(String message) { - mMessage = Html.fromHtml(message); - } - @Override protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { super.onPrepareDialogBuilder(builder); - builder.setMessage(mMessage); + builder.setMessage(mDialogMessage); builder.setPositiveButton(android.R.string.ok, null); builder.setNegativeButton(null, null); } + + @Override + protected void onDialogCreated(AlertDialog dialog) { + super.onDialogCreated(dialog); + + if (mDialogMessageLinkify) { + final View messageView = dialog.findViewById(android.R.id.message); + if (null != messageView && messageView instanceof TextView) { + ((TextView) messageView).setMovementMethod(LinkMovementMethod.getInstance()); + } + } + } } diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index a91dda631..719381c07 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -108,10 +108,6 @@ - - - - @@ -122,4 +118,18 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/about_settings.xml b/app/src/main/res/xml/about_settings.xml index fa433cbcc..f2024c9f5 100644 --- a/app/src/main/res/xml/about_settings.xml +++ b/app/src/main/res/xml/about_settings.xml @@ -53,12 +53,13 @@ android:title="@string/settings_about_enable_analytics" android:defaultValue="true"/> - + app:dialogMessage="@string/analytics_explain" + app:dialogMessageHtml="true" + app:dialogMessageLinkify="true"/>