From 8b7cae31a9a324856e5c149a300ddc0f6eafe001 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Sat, 24 Jul 2021 10:19:42 +0200 Subject: [PATCH] Import ColorMatrixListPreference from AICP Original commit history from frameworks/opt/aicpgear: commit adf33979bdd1da0a3fd96e7276dd567e559db780 Author: SpiritCroc Date: Thu Oct 31 11:59:04 2019 +0100 Use proper androidx attrs again Have been removed in the blueprint bringup commit Change-Id: I25f7781db9b39916680feec6fe75e1e39240e78a commit 9de1e6ae734b3acc686c57d2b7984ffd2f3346ba Author: SpiritCroc Date: Sun Oct 20 16:43:10 2019 +0200 Migrate AicpGear to blueprint Change-Id: I6fecdb3c18a80b6fc9fda52bd730c20e4568f54b commit 26931c7a31ee4aa1bb88b6de1f2f7c9fee7c68e5 Author: SpiritCroc Date: Sat Sep 7 11:08:01 2019 +0200 AICP Q Theming [1/2] Change-Id: Ic947806f618d9e9a81def568781f409d3a428dba commit 31b5eb51bd4ff3378eb4b8b5cac010c1b9e47932 Author: SpiritCroc Date: Fri Sep 6 17:29:44 2019 +0200 androidx: PreferenceFragment is deprecated, use PreferenceFragmentCompat Change-Id: I000587f1e775900d92e9b3cf859f145995afa08b commit 58e09c3dfaad470e9eaea5f11299c5504db9366d Author: Michele Bono Date: Sat Aug 17 14:21:34 2019 +0200 aicpgear: Migrate to androidx Change-Id: I3c63fe017128785bb541a7668bbb388a8cb2fed0 commit 4636b50f0c6788f9945f64427a8a541623f2b48d Author: SpiritCroc Date: Mon Sep 2 18:48:51 2019 +0200 ColorMatrixListPreferenceDialogFragment: It's a feature When previewing two colors for light and dark theme at the same time, this is considered as "glitchy" by some users. Enlighten them (hopefully) by hiding the second color with a toggle button. Change-Id: I90a70af5188687edd184e5937e55686843fdaba0 commit e1e6b7b44c2a801f787ee8e42aed9af24464d4b2 Author: SpiritCroc Date: Fri Aug 9 13:09:14 2019 +0200 Add ColorMatrixListPreference Change-Id: I3c03d95fa56aed2ee5754e58f0394f3ff55fae39 Change-Id: I41098d5f5255dc8efa515e8b68191dd89d94233f --- .../preference/ColorMatrixListPreference.java | 75 ++++ ...lorMatrixListPreferenceDialogFragment.java | 389 ++++++++++++++++++ .../preference/ScPreferenceFragment.java | 43 ++ .../settings/VectorSettingsBaseFragment.kt | 4 +- .../src/main/res/values/color_list_attrs.xml | 26 ++ .../res/values/color_matrix_list_dimens.xml | 25 ++ .../res/values/color_matrix_list_strings.xml | 24 ++ 7 files changed, 584 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/de/spiritcroc/preference/ColorMatrixListPreference.java create mode 100644 vector/src/main/java/de/spiritcroc/preference/ColorMatrixListPreferenceDialogFragment.java create mode 100644 vector/src/main/java/de/spiritcroc/preference/ScPreferenceFragment.java create mode 100644 vector/src/main/res/values/color_list_attrs.xml create mode 100644 vector/src/main/res/values/color_matrix_list_dimens.xml create mode 100644 vector/src/main/res/values/color_matrix_list_strings.xml diff --git a/vector/src/main/java/de/spiritcroc/preference/ColorMatrixListPreference.java b/vector/src/main/java/de/spiritcroc/preference/ColorMatrixListPreference.java new file mode 100644 index 0000000000..42b8574083 --- /dev/null +++ b/vector/src/main/java/de/spiritcroc/preference/ColorMatrixListPreference.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 Android Ice Cold 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.spiritcroc.preference; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +import androidx.preference.ListPreference; + +import im.vector.app.R; + +public class ColorMatrixListPreference extends ListPreference { + + public static final int COLOR_MAGIC_TEXT = 0x1000000; + + private CharSequence[] mEntryPreviews; + private CharSequence[] mEntryPreviewsLight; + + public ColorMatrixListPreference(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.ColorListPreference, defStyleAttr, defStyleRes); + + mEntryPreviews = a.getTextArray(R.styleable.ColorListPreference_entryPreviews); + mEntryPreviewsLight = a.getTextArray(R.styleable.ColorListPreference_entryPreviewsLight); + + a.recycle(); + } + + public ColorMatrixListPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ColorMatrixListPreference(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.dialogPreferenceStyle); + } + + public ColorMatrixListPreference(Context context) { + this(context, null); + } + + public void setEntryPreviews(CharSequence[] entryPreviews) { + mEntryPreviews = entryPreviews; + } + + public void setEntryPreviewsLight(CharSequence[] entryPreviewsLight) { + mEntryPreviewsLight = entryPreviewsLight; + } + + public CharSequence[] getEntryPreviews() { + return mEntryPreviews; + } + + public CharSequence[] getEntryPreviewsLight() { + return mEntryPreviewsLight; + } + +} diff --git a/vector/src/main/java/de/spiritcroc/preference/ColorMatrixListPreferenceDialogFragment.java b/vector/src/main/java/de/spiritcroc/preference/ColorMatrixListPreferenceDialogFragment.java new file mode 100644 index 0000000000..30c82fc2c4 --- /dev/null +++ b/vector/src/main/java/de/spiritcroc/preference/ColorMatrixListPreferenceDialogFragment.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 Android Ice Cold 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.spiritcroc.preference; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.os.Bundle; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceDialogFragmentCompat; + +import java.util.Arrays; + +import im.vector.app.R; + +import static de.spiritcroc.preference.ColorMatrixListPreference.COLOR_MAGIC_TEXT; + + +public class ColorMatrixListPreferenceDialogFragment extends PreferenceDialogFragmentCompat { + + private static final String SAVE_STATE_VALUE = "ColorMatrixListPreferenceDialogFragment.value"; + private static final String SAVE_STATE_ENTRIES = + "ColorMatrixListPreferenceDialogFragment.entries"; + private static final String SAVE_STATE_ENTRY_VALUES = + "ColorMatrixListPreferenceDialogFragment.entryValues"; + private static final String SAVE_STATE_ENTRY_PREVIEWS = + "ColorMatrixListPreferenceDialogFragment.entryPreviews"; + private static final String SAVE_STATE_ENTRY_PREVIEWS_LIGHT = + "ColorMatrixListPreferenceDialogFragment.entryPreviewsLight"; + + // TODO config? + private int mColumnCount = 5; + + private String mValue; + private boolean mPositiveResult = false; + private CharSequence[] mEntries; + private CharSequence[] mEntryValues; + private CharSequence[] mEntryPreviews; + private CharSequence[] mEntryPreviewsLight; + private ColorListEntry[] mColorListEntries; + + private boolean mExpertModePossible = false; + private boolean mExpertMode = false; + + private LinearLayout mBaseLayout; + + public static ColorMatrixListPreferenceDialogFragment newInstance(String key) { + final ColorMatrixListPreferenceDialogFragment fragment = new ColorMatrixListPreferenceDialogFragment(); + final Bundle b = new Bundle(1); + b.putString(ARG_KEY, key); + fragment.setArguments(b); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + final ColorMatrixListPreference preference = (ColorMatrixListPreference) getPreference(); + + if (preference.getEntryPreviews() == null) { + throw new IllegalStateException( + "ColorMatrixListPreference requires an entryPreviews array."); + } + + mValue = preference.getValue(); + mEntries = preference.getEntries(); + mEntryValues = preference.getEntryValues(); + mEntryPreviews = preference.getEntryPreviews(); + mEntryPreviewsLight = preference.getEntryPreviewsLight(); + } else { + mValue = savedInstanceState.getString(SAVE_STATE_VALUE); + mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES); + mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES); + mEntryPreviews = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_PREVIEWS); + mEntryPreviewsLight = + savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_PREVIEWS_LIGHT); + } + if (mEntryPreviewsLight == null) { + mEntryPreviewsLight = mEntryPreviews; + } + buildColorArray(); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putString(SAVE_STATE_VALUE, mValue); + outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries); + outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues); + outState.putCharSequenceArray(SAVE_STATE_ENTRY_PREVIEWS, mEntryPreviews); + outState.putCharSequenceArray(SAVE_STATE_ENTRY_PREVIEWS_LIGHT, mEntryPreviewsLight); + } + + private boolean isDarkTheme() { + TypedValue tv = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, tv, true); + int bgColor = tv.data; + getContext().getTheme().resolveAttribute(android.R.attr.colorForeground, tv, true); + int fgColor = tv.data; + return Color.luminance(fgColor) > Color.luminance(bgColor); + } + + private int getAccentColor() { + TypedValue tv = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.colorAccent, tv, true); + return tv.data; + } + + private int parseColor(CharSequence s) { + int result = Integer.decode(s.toString()); + if ((result & 0xff000000) == 0) { + // Add alpha channel + result |= 0xff000000; + } + return result; + } + + private void buildColorArray() { + mColorListEntries = new ColorListEntry[mEntries.length]; + boolean isDark = isDarkTheme(); + for (int i = 0; i < mEntries.length; i++) { + mColorListEntries[i] = new ColorListEntry(isDark, mEntries[i], mEntryValues[i], + parseColor(mEntryPreviews[i]), parseColor(mEntryPreviewsLight[i])); + if (mColorListEntries[i].entryPreview != mColorListEntries[i].entryPreviewLight) { + mExpertModePossible = true; + } + } + Arrays.sort(mColorListEntries); + } + + @Override + protected View onCreateDialogView(Context context) { + mBaseLayout = new LinearLayout(context); + mBaseLayout.setOrientation(LinearLayout.VERTICAL); + + populateView(context); + + ViewGroup.LayoutParams layoutParams = + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + ScrollView view = new ScrollView(context); + view.setLayoutParams(layoutParams); + view.addView(mBaseLayout); + return view; + } + + private void rebuildView(Context context) { + mBaseLayout.removeAllViews(); + populateView(context); + } + + private void populateView(Context context) { + int previewSize = getResources() + .getDimensionPixelSize(R.dimen.color_matrix_list_preview_size); + + ViewGroup.LayoutParams layoutParams = + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + LinearLayout.LayoutParams childLayoutParams = + new LinearLayout.LayoutParams(0, previewSize); + childLayoutParams.weight = 1; + + LinearLayout.LayoutParams subChild1LayoutParams = + new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); + subChild1LayoutParams.weight = 1; + + LinearLayout.LayoutParams subChild2LayoutParams = + new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, previewSize/4); + + LinearLayout.LayoutParams firstTextChildLayoutParams = + new LinearLayout.LayoutParams(0, previewSize); + firstTextChildLayoutParams.weight = 2; + + for (int i = 0; i < mColorListEntries.length;) { + LinearLayout rowLayout = new LinearLayout(context); + rowLayout.setLayoutParams(layoutParams); + rowLayout.setOrientation(LinearLayout.HORIZONTAL); + mBaseLayout.addView(rowLayout); + for (int j = 0; j < mColumnCount && i < mEntries.length; j++) { + boolean selected = mColorListEntries[i].entryValue.toString().equals(mValue); + View child; + int color = mColorListEntries[i].getPreview(); + int color2 = mColorListEntries[i].getPreview2(); + if (color == COLOR_MAGIC_TEXT) { + TextView tv = new TextView(context); + tv.setText(mEntries[i]); + tv.setGravity(Gravity.CENTER); + child = tv; + if (i == 0) { + // If text view on first position: give it twice as much space + child.setLayoutParams(firstTextChildLayoutParams); + j++; + } else { + child.setLayoutParams(childLayoutParams); + } + if (selected) { + tv.setTextColor(getAccentColor()); + } + } else { + if (mExpertMode && color2 != color) { + // Two previews, with weight on the one chosen from the theme + LinearLayout childLayout = new LinearLayout(context); + childLayout.setOrientation(LinearLayout.VERTICAL); + View child1 = new View(context); + child1.setBackgroundColor(color); + child1.setLayoutParams(subChild1LayoutParams); + childLayout.addView(child1); + View child2 = new View(context); + child2.setBackgroundColor(color2); + child2.setLayoutParams(subChild2LayoutParams); + childLayout.addView(child2); + child = childLayout; + } else { + child = new View(context); + child.setBackgroundColor(color); + } + + if (selected) { + int selectionPadding = getResources() + .getDimensionPixelSize(R.dimen.color_matrix_list_selection_padding); + FrameLayout.LayoutParams frameChildParams = + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + frameChildParams.setMargins(selectionPadding, selectionPadding, + selectionPadding, selectionPadding); + child.setLayoutParams(frameChildParams); + FrameLayout frame = new FrameLayout(context); + frame.addView(child); + child = frame; + } + child.setLayoutParams(childLayoutParams); + } + child.setOnClickListener(mChildClickListener); + child.setTag(mColorListEntries[i]); + rowLayout.addView(child); + i++; + } + } + } + + private View.OnClickListener mChildClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + mValue = ((ColorListEntry) view.getTag()).entryValue.toString(); + mPositiveResult = true; + dismiss(); + } + }; + + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + + if (mEntries == null || mEntryValues == null || mEntryPreviews == null) { + throw new IllegalStateException( + "ListPreference requires an entries array, an entryValues array, and an entryPreviews array."); + } + + /* + * 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.setPositiveButton(null, null); + + // Toggle to expert mode + if (mExpertModePossible) { + builder.setNeutralButton(R.string.color_matrix_list_expert_mode, null); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final AlertDialog dialog = (AlertDialog) super.onCreateDialog(savedInstanceState); + dialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialogInterface) { + if (mExpertModePossible) { + final Button neutralButton = dialog.getButton(AlertDialog.BUTTON_NEUTRAL); + neutralButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mExpertMode = !mExpertMode; + neutralButton.setText(mExpertMode + ? R.string.color_matrix_list_complainer_mode + : R.string.color_matrix_list_expert_mode); + rebuildView(getContext()); + } + }); + } + } + }); + return dialog; + } + + @Override + public void onDialogClosed(boolean positiveResult) { + final ColorMatrixListPreference preference = (ColorMatrixListPreference) getPreference(); + if ((positiveResult||mPositiveResult) && mValue != null) { + if (preference.callChangeListener(mValue)) { + preference.setValue(mValue); + } + mPositiveResult = false; + } + } + + private class ColorListEntry implements Comparable { + private CharSequence entry; + private CharSequence entryValue; + private int entryPreview; + private int entryPreviewLight; + private boolean useDark; + private ColorListEntry(boolean useDark, CharSequence entry, CharSequence entryValue, + int entryPreview, int entryPreviewLight) { + this.useDark = useDark; + this.entry = entry; + this.entryValue = entryValue; + this.entryPreview = entryPreview; + this.entryPreviewLight = entryPreviewLight; + } + @Override + public int compareTo(ColorListEntry other) { + int result = compareColor(getPreview(), other.getPreview()); + if (result != 0) { + return result; + } + return compareColor(getPreview2(), getPreview2()); + } + private int compareColor(int a, int b) { + float[] hsv = new float[3], otherHsv = new float[3]; + if (a == COLOR_MAGIC_TEXT) { + // Magic value for text, add to beginning + return -1; + } else if (b == COLOR_MAGIC_TEXT) { + // Magic value for text, add to beginning + return 1; + } + Color.colorToHSV(a, hsv); + Color.colorToHSV(b, otherHsv); + if (hsv[0] == otherHsv[0]) { + if (hsv[1] == otherHsv[1]) { + if (hsv[2] == otherHsv[2]) { + return 0; + } + return hsv[2] > otherHsv[2] ? 1 : -1; + } + return hsv[1] > otherHsv[1] ? 1 : -1; + } + return hsv[0] > otherHsv[0] ? 1 : -1; + } + private int getPreview() { + return useDark ? entryPreview : entryPreviewLight; + } + private int getPreview2() { + return useDark ? entryPreviewLight : entryPreview; + } + } +} diff --git a/vector/src/main/java/de/spiritcroc/preference/ScPreferenceFragment.java b/vector/src/main/java/de/spiritcroc/preference/ScPreferenceFragment.java new file mode 100644 index 0000000000..f41b3dffcc --- /dev/null +++ b/vector/src/main/java/de/spiritcroc/preference/ScPreferenceFragment.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 Android Ice Cold 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.spiritcroc.preference; + +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.Preference; + +public abstract class ScPreferenceFragment extends PreferenceFragmentCompat { + + private static final String DIALOG_FRAGMENT_TAG = + "de.spiritcroc.preference.ScPreferenceFragment.DIALOG"; + + @Override + public void onDisplayPreferenceDialog(Preference preference) { + // check if dialog is already showing + if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { + return; + } + if (preference instanceof ColorMatrixListPreference) { + ColorMatrixListPreferenceDialogFragment dialogFragment = + ColorMatrixListPreferenceDialogFragment.newInstance(preference.getKey()); + dialogFragment.setTargetFragment(this, 0); + dialogFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); + } else { + super.onDisplayPreferenceDialog(preference); + } + } + +} diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt index 069216aaae..95f700488b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt @@ -20,7 +20,7 @@ import android.content.Context import android.os.Bundle import android.view.View import androidx.annotation.CallSuper -import androidx.preference.PreferenceFragmentCompat +import de.spiritcroc.preference.ScPreferenceFragment import im.vector.app.R import im.vector.app.core.di.DaggerScreenComponent import im.vector.app.core.di.HasScreenInjector @@ -33,7 +33,7 @@ import io.reactivex.disposables.Disposable import org.matrix.android.sdk.api.session.Session import timber.log.Timber -abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), HasScreenInjector { +abstract class VectorSettingsBaseFragment : ScPreferenceFragment(), HasScreenInjector { val vectorActivity: VectorBaseActivity<*> by lazy { activity as VectorBaseActivity<*> diff --git a/vector/src/main/res/values/color_list_attrs.xml b/vector/src/main/res/values/color_list_attrs.xml new file mode 100644 index 0000000000..28a325ce97 --- /dev/null +++ b/vector/src/main/res/values/color_list_attrs.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/values/color_matrix_list_dimens.xml b/vector/src/main/res/values/color_matrix_list_dimens.xml new file mode 100644 index 0000000000..f0758a239c --- /dev/null +++ b/vector/src/main/res/values/color_matrix_list_dimens.xml @@ -0,0 +1,25 @@ + + + + + + + 72dp + 8dp + + + diff --git a/vector/src/main/res/values/color_matrix_list_strings.xml b/vector/src/main/res/values/color_matrix_list_strings.xml new file mode 100644 index 0000000000..74fe6df5e7 --- /dev/null +++ b/vector/src/main/res/values/color_matrix_list_strings.xml @@ -0,0 +1,24 @@ + + + + + + + Expert + Easy + +