From 1e85b10f5ed2b095b672c2af24c3169a6c1c23ac Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Mon, 11 Jun 2018 22:29:10 +0200 Subject: [PATCH] Fix themes in preferences --- .../quasseldroid/ui/chat/ChatActivity.kt | 2 +- .../client/ClientSettingsActivity.kt | 20 +- .../ListPreferenceDialogFragmentCompat.java | 136 +++++++++ .../PreferenceDialogFragmentCompat.java | 285 ++++++++++++++++++ .../util/ui/settings/SettingsActivity.kt | 2 +- 5 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/ListPreferenceDialogFragmentCompat.java create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/PreferenceDialogFragmentCompat.java diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt index 43dd1caea..74c49ad38 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt @@ -778,7 +778,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } } .negativeColorAttr(R.attr.colorTextPrimary) - .neutralColor(R.attr.colorTextPrimary) + .neutralColorAttr(R.attr.colorTextPrimary) .backgroundColorAttr(R.attr.colorBackgroundCard) .contentColorAttr(R.attr.colorTextPrimary) .titleColorAttr(R.attr.colorTextPrimary) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsActivity.kt index 0c4b8ba57..52237e873 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsActivity.kt @@ -21,10 +21,28 @@ package de.kuschku.quasseldroid.ui.clientsettings.client import android.content.Context import android.content.Intent +import android.support.v7.preference.ListPreference +import android.support.v7.preference.Preference +import android.support.v7.preference.PreferenceFragmentCompat +import de.kuschku.quasseldroid.util.ui.settings.ListPreferenceDialogFragmentCompat import de.kuschku.quasseldroid.util.ui.settings.SettingsActivity -class ClientSettingsActivity : SettingsActivity(ClientSettingsFragment()) { +class ClientSettingsActivity : SettingsActivity(ClientSettingsFragment()), + PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback { + override fun onPreferenceDisplayDialog(caller: PreferenceFragmentCompat, pref: Preference?) = + when (pref) { + is ListPreference -> { + val f = ListPreferenceDialogFragmentCompat.newInstance(pref.getKey()) + f.setTargetFragment(fragment, 0) + f.show(supportFragmentManager!!, DIALOG_FRAGMENT_TAG) + true + } + else -> false + } + companion object { + private const val DIALOG_FRAGMENT_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG" + fun launch(context: Context) = context.startActivity(intent(context)) fun intent(context: Context) = Intent(context, ClientSettingsActivity::class.java) } diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/ListPreferenceDialogFragmentCompat.java b/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/ListPreferenceDialogFragmentCompat.java new file mode 100644 index 000000000..712a097bc --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/ListPreferenceDialogFragmentCompat.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 de.kuschku.quasseldroid.util.ui.settings; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.preference.ListPreference; +import android.view.View; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; + +import java.util.ArrayList; + +public class ListPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat { + + private static final String SAVE_STATE_INDEX = "ListPreferenceDialogFragment.index"; + private static final String SAVE_STATE_ENTRIES = "ListPreferenceDialogFragment.entries"; + private static final String SAVE_STATE_ENTRY_VALUES = + "ListPreferenceDialogFragment.entryValues"; + + private int mClickedDialogEntryIndex; + private CharSequence[] mEntries; + private CharSequence[] mEntryValues; + + public static ListPreferenceDialogFragmentCompat newInstance(String key) { + final ListPreferenceDialogFragmentCompat fragment = + new ListPreferenceDialogFragmentCompat(); + final Bundle b = new Bundle(1); + b.putString(ARG_KEY, key); + fragment.setArguments(b); + return fragment; + } + + private static void putCharSequenceArray(Bundle out, String key, CharSequence[] entries) { + final ArrayList<String> stored = new ArrayList<>(entries.length); + + for (final CharSequence cs : entries) { + stored.add(cs.toString()); + } + + out.putStringArrayList(key, stored); + } + + private static CharSequence[] getCharSequenceArray(Bundle in, String key) { + final ArrayList<String> stored = in.getStringArrayList(key); + + return stored == null ? null : stored.toArray(new CharSequence[stored.size()]); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + final ListPreference preference = getListPreference(); + + if (preference.getEntries() == null || preference.getEntryValues() == null) { + throw new IllegalStateException( + "ListPreference requires an entries array and an entryValues array."); + } + + mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue()); + mEntries = preference.getEntries(); + mEntryValues = preference.getEntryValues(); + } else { + mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0); + mEntries = getCharSequenceArray(savedInstanceState, SAVE_STATE_ENTRIES); + mEntryValues = getCharSequenceArray(savedInstanceState, SAVE_STATE_ENTRY_VALUES); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex); + putCharSequenceArray(outState, SAVE_STATE_ENTRIES, mEntries); + putCharSequenceArray(outState, SAVE_STATE_ENTRY_VALUES, mEntryValues); + } + + private ListPreference getListPreference() { + return (ListPreference) getPreference(); + } + + @Override + protected void onPrepareDialogBuilder(MaterialDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + + builder.items(mEntries).itemsCallbackSingleChoice(mClickedDialogEntryIndex, new MaterialDialog.ListCallbackSingleChoice() { + @Override + public boolean onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) { + mClickedDialogEntryIndex = which; + + /* + * Clicking on an item simulates the positive button + * click, and dismisses the dialog. + */ + ListPreferenceDialogFragmentCompat.this.onClick(dialog, DialogAction.POSITIVE); + dialog.dismiss(); + return true; + } + }); + + /* + * The typical interaction for list-based dialogs is to have + * click-on-an-item dismiss the dialog instead of the user having to + * press 'Ok'. + */ + builder.positiveText(null); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + final ListPreference preference = getListPreference(); + if (positiveResult && mClickedDialogEntryIndex >= 0) { + String value = mEntryValues[mClickedDialogEntryIndex].toString(); + if (preference.callChangeListener(value)) { + preference.setValue(value); + } + } + } + +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/PreferenceDialogFragmentCompat.java b/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/PreferenceDialogFragmentCompat.java new file mode 100644 index 000000000..e816b3e55 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/PreferenceDialogFragmentCompat.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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 de.kuschku.quasseldroid.util.ui.settings; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.preference.DialogPreference; +import android.support.v7.preference.PreferenceFragmentCompat; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.TextView; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; + +import de.kuschku.quasseldroid.R; + +import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +/** + * Abstract base class which presents a dialog associated with a + * {@link android.support.v7.preference.DialogPreference}. Since the preference object may + * not be available during fragment re-creation, the necessary information for displaying the dialog + * is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved + * instance state. Custom subclasses should also follow this pattern. + */ +public abstract class PreferenceDialogFragmentCompat extends DialogFragment implements MaterialDialog.SingleButtonCallback { + + protected static final String ARG_KEY = "key"; + + private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title"; + private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText"; + private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText"; + private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message"; + private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout"; + private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon"; + + private DialogPreference mPreference; + + private CharSequence mDialogTitle; + private CharSequence mPositiveButtonText; + private CharSequence mNegativeButtonText; + private CharSequence mDialogMessage; + private @LayoutRes + int mDialogLayoutRes; + + private BitmapDrawable mDialogIcon; + + /** + * Which button was clicked. + */ + private DialogAction mWhichButtonClicked; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Fragment rawFragment = getTargetFragment(); + if (!(rawFragment instanceof DialogPreference.TargetFragment)) { + throw new IllegalStateException("Target fragment must implement TargetFragment" + + " interface"); + } + + final DialogPreference.TargetFragment fragment = + (DialogPreference.TargetFragment) rawFragment; + + final String key = getArguments().getString(ARG_KEY); + if (savedInstanceState == null) { + mPreference = (DialogPreference) fragment.findPreference(key); + mDialogTitle = mPreference.getDialogTitle(); + mPositiveButtonText = mPreference.getPositiveButtonText(); + mNegativeButtonText = mPreference.getNegativeButtonText(); + mDialogMessage = mPreference.getDialogMessage(); + mDialogLayoutRes = mPreference.getDialogLayoutResource(); + + final Drawable icon = mPreference.getDialogIcon(); + if (icon == null || icon instanceof BitmapDrawable) { + mDialogIcon = (BitmapDrawable) icon; + } else { + final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), + icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + icon.draw(canvas); + mDialogIcon = new BitmapDrawable(getResources(), bitmap); + } + } else { + mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE); + mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT); + mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT); + mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE); + mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0); + final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON); + if (bitmap != null) { + mDialogIcon = new BitmapDrawable(getResources(), bitmap); + } + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle); + outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText); + outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText); + outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage); + outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes); + if (mDialogIcon != null) { + outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap()); + } + } + + @Override + public @NonNull + Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + mWhichButtonClicked = DialogAction.NEGATIVE; + + final MaterialDialog.Builder builder = new MaterialDialog.Builder(context) + .title(mDialogTitle) + .icon(mDialogIcon) + .positiveText(mPositiveButtonText) + .negativeText(mNegativeButtonText) + .negativeColorAttr(R.attr.colorTextPrimary) + .neutralColorAttr(R.attr.colorTextPrimary) + .backgroundColorAttr(R.attr.colorBackgroundCard) + .contentColorAttr(R.attr.colorTextPrimary) + .titleColorAttr(R.attr.colorTextPrimary) + .onPositive(this); + + View contentView = onCreateDialogView(context); + if (contentView != null) { + onBindDialogView(contentView); + builder.customView(contentView, true); + } else { + builder.content(mDialogMessage); + } + + onPrepareDialogBuilder(builder); + + // Create the dialog + final Dialog dialog = builder.build(); + if (needInputMethod()) { + requestInputMethod(dialog); + } + + return dialog; + } + + /** + * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has + * been called on the {@link PreferenceFragmentCompat} which launched this dialog. + * + * @return The {@link DialogPreference} associated with this + * dialog. + */ + public DialogPreference getPreference() { + if (mPreference == null) { + final String key = getArguments().getString(ARG_KEY); + final DialogPreference.TargetFragment fragment = + (DialogPreference.TargetFragment) getTargetFragment(); + mPreference = (DialogPreference) fragment.findPreference(key); + } + return mPreference; + } + + /** + * 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(MaterialDialog.Builder builder) { + } + + /** + * 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. + * + * @hide + */ + @RestrictTo(LIBRARY_GROUP) + 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 DialogPreference#setLayoutResource(int) + */ + protected View onCreateDialogView(Context context) { + final int resId = mDialogLayoutRes; + if (resId == 0) { + return null; + } + + LayoutInflater inflater = LayoutInflater.from(context); + return inflater.inflate(resId, null); + } + + /** + * Binds views in the content View of the dialog to data. + * <p> + * Make sure to call through to the superclass implementation. + * + * @param view The content View of the dialog, if it is custom. + */ + protected void onBindDialogView(View view) { + View dialogMessageView = view.findViewById(android.R.id.message); + + if (dialogMessageView != null) { + final CharSequence message = mDialogMessage; + int newVisibility = View.GONE; + + if (!TextUtils.isEmpty(message)) { + if (dialogMessageView instanceof TextView) { + ((TextView) dialogMessageView).setText(message); + } + + newVisibility = View.VISIBLE; + } + + if (dialogMessageView.getVisibility() != newVisibility) { + dialogMessageView.setVisibility(newVisibility); + } + } + } + + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + mWhichButtonClicked = which; + } + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + onDialogClosed(mWhichButtonClicked == DialogAction.POSITIVE); + } + + public abstract void onDialogClosed(boolean positiveResult); +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/SettingsActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/SettingsActivity.kt index 750f21d23..6e2f34226 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/SettingsActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/settings/SettingsActivity.kt @@ -30,7 +30,7 @@ import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.ui.coresettings.SettingsFragment import de.kuschku.quasseldroid.util.ui.ThemedActivity -abstract class SettingsActivity(private val fragment: Fragment? = null) : ThemedActivity() { +abstract class SettingsActivity(protected val fragment: Fragment? = null) : ThemedActivity() { protected open fun fragment(): Fragment? = null private var changeable: SettingsFragment.Changeable? = null -- GitLab