Import ColorMatrixListPreference from AICP

Original commit history from frameworks/opt/aicpgear:

commit adf33979bdd1da0a3fd96e7276dd567e559db780
Author: SpiritCroc <dev@spiritcroc.de>
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 <dev@spiritcroc.de>
Date:   Sun Oct 20 16:43:10 2019 +0200

    Migrate AicpGear to blueprint

    Change-Id: I6fecdb3c18a80b6fc9fda52bd730c20e4568f54b

commit 26931c7a31ee4aa1bb88b6de1f2f7c9fee7c68e5
Author: SpiritCroc <dev@spiritcroc.de>
Date:   Sat Sep 7 11:08:01 2019 +0200

    AICP Q Theming [1/2]

    Change-Id: Ic947806f618d9e9a81def568781f409d3a428dba

commit 31b5eb51bd4ff3378eb4b8b5cac010c1b9e47932
Author: SpiritCroc <dev@spiritcroc.de>
Date:   Fri Sep 6 17:29:44 2019 +0200

    androidx: PreferenceFragment is deprecated, use PreferenceFragmentCompat

    Change-Id: I000587f1e775900d92e9b3cf859f145995afa08b

commit 58e09c3dfaad470e9eaea5f11299c5504db9366d
Author: Michele Bono <bono.michele94@gmail.com>
Date:   Sat Aug 17 14:21:34 2019 +0200

    aicpgear: Migrate to androidx

    Change-Id: I3c63fe017128785bb541a7668bbb388a8cb2fed0

commit 4636b50f0c6788f9945f64427a8a541623f2b48d
Author: SpiritCroc <dev@spiritcroc.de>
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 <dev@spiritcroc.de>
Date:   Fri Aug 9 13:09:14 2019 +0200

    Add ColorMatrixListPreference

    Change-Id: I3c03d95fa56aed2ee5754e58f0394f3ff55fae39

Change-Id: I41098d5f5255dc8efa515e8b68191dd89d94233f
This commit is contained in:
SpiritCroc 2021-07-24 10:19:42 +02:00
parent 4c9b304135
commit 8b7cae31a9
7 changed files with 584 additions and 2 deletions

View File

@ -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;
}
}

View File

@ -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<ColorListEntry> {
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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -20,7 +20,7 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.preference.PreferenceFragmentCompat import de.spiritcroc.preference.ScPreferenceFragment
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.DaggerScreenComponent import im.vector.app.core.di.DaggerScreenComponent
import im.vector.app.core.di.HasScreenInjector 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 org.matrix.android.sdk.api.session.Session
import timber.log.Timber import timber.log.Timber
abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), HasScreenInjector { abstract class VectorSettingsBaseFragment : ScPreferenceFragment(), HasScreenInjector {
val vectorActivity: VectorBaseActivity<*> by lazy { val vectorActivity: VectorBaseActivity<*> by lazy {
activity as VectorBaseActivity<*> activity as VectorBaseActivity<*>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 Android Ice Cold Project
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<declare-styleable name="ColorListPreference">
<attr name="entryPreviews" format="reference" />
<attr name="entryPreviewsLight" format="reference" />
</declare-styleable>
</resources>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 Android Ice Cold Project
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<dimen name="color_matrix_list_preview_size">72dp</dimen>
<dimen name="color_matrix_list_selection_padding">8dp</dimen>
</resources>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 Android Ice Cold Project
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="color_matrix_list_expert_mode">Expert</string>
<string name="color_matrix_list_complainer_mode">Easy</string>
</resources>