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