From 3026bd5c51660e7255a067d5b4775cff3dbf5c38 Mon Sep 17 00:00:00 2001 From: Grishka Date: Fri, 25 Oct 2024 02:40:33 +0300 Subject: [PATCH] Support for Android 15's color contrast setting WIP --- .../android/fragments/SplashFragment.java | 2 +- .../settings/SettingsDebugFragment.java | 71 +++++++++++++++- .../android/ui/ColorContrastMode.java | 15 ++++ .../android/ui/text/BaseMonospaceSpan.java | 7 +- .../android/ui/text/BlockQuoteSpan.java | 11 +-- .../android/ui/utils/UiUtils.java | 26 +++++- .../ui/viewholders/LinkCardHolder.java | 6 +- .../ui/views/CheckIconSelectableTextView.java | 6 +- .../android/ui/views/FilterChipView.java | 4 + .../main/res/color-v31/m3_sys_accent3_750.xml | 4 + .../main/res/color-v31/m3_sys_accent3_850.xml | 4 + mastodon/src/main/res/values-night/styles.xml | 2 + mastodon/src/main/res/values-v31/colors.xml | 2 +- mastodon/src/main/res/values-v34/colors.xml | 59 +++++++++++++ mastodon/src/main/res/values/attrs.xml | 10 +++ mastodon/src/main/res/values/colors.xml | 14 ++++ mastodon/src/main/res/values/styles.xml | 84 +++++++++++++++++-- 17 files changed, 296 insertions(+), 31 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/ColorContrastMode.java create mode 100644 mastodon/src/main/res/color-v31/m3_sys_accent3_750.xml create mode 100644 mastodon/src/main/res/color-v31/m3_sys_accent3_850.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java index 30f03caca..a021f3231 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java @@ -243,7 +243,7 @@ public class SplashFragment extends AppKitFragment{ @Override public boolean wantsLightNavigationBar(){ - return true; + return false; } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDebugFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDebugFragment.java index 5035bbf3e..39ff1a30a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDebugFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDebugFragment.java @@ -2,10 +2,16 @@ package org.joinmastodon.android.fragments.settings; import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Color; import android.os.Bundle; +import android.util.Pair; +import android.view.ViewGroup; +import android.widget.ScrollView; +import android.widget.TextView; import android.widget.Toast; import org.joinmastodon.android.GlobalUserPreferences; +import org.joinmastodon.android.R; import org.joinmastodon.android.api.PushSubscriptionManager; import org.joinmastodon.android.api.session.AccountActivationInfo; import org.joinmastodon.android.api.session.AccountSession; @@ -14,12 +20,22 @@ import org.joinmastodon.android.fragments.HomeFragment; import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment; import org.joinmastodon.android.model.viewmodel.CheckableListItem; import org.joinmastodon.android.model.viewmodel.ListItem; +import org.joinmastodon.android.ui.M3AlertDialogBuilder; +import org.joinmastodon.android.ui.SimpleViewHolder; import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper; +import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.updater.GithubSelfUpdater; +import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.List; +import androidx.annotation.NonNull; +import androidx.palette.graphics.Palette; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import me.grishka.appkit.Nav; +import me.grishka.appkit.utils.V; public class SettingsDebugFragment extends BaseSettingsFragment{ private CheckableListItem donationsStagingItem; @@ -38,7 +54,8 @@ public class SettingsDebugFragment extends BaseSettingsFragment{ new ListItem<>("Reset pre-reply sheets", null, this::onResetPreReplySheetsClick), new ListItem<>("Clear dismissed donation campaigns", null, this::onClearDismissedCampaignsClick), donationsStagingItem=new CheckableListItem<>("Use staging environment for donations", "Restart app to apply", CheckableListItem.Style.SWITCH, getPrefs().getBoolean("donationsStaging", false), this::toggleCheckableItem), - new ListItem<>("Delete cached instance info", null, this::onDeleteInstanceInfoClick) + new ListItem<>("Delete cached instance info", null, this::onDeleteInstanceInfoClick), + new ListItem<>("View dynamic color values", null, this::onViewColorsClick) )); if(!GithubSelfUpdater.needSelfUpdating()){ resetUpdateItem.isEnabled=selfUpdateItem.isEnabled=false; @@ -101,6 +118,58 @@ public class SettingsDebugFragment extends BaseSettingsFragment{ Toast.makeText(getActivity(), "Instances removed from database", Toast.LENGTH_LONG).show(); } + private void onViewColorsClick(ListItem item){ + ArrayList> attrs=new ArrayList<>(); + Field[] fields=R.attr.class.getFields(); + try{ + for(Field fld:fields){ + if(fld.getName().startsWith("color") && fld.getType().equals(int.class)){ + attrs.add(new Pair<>((Integer)fld.get(null), fld.getName())); + } + } + }catch(IllegalAccessException x){ + Toast.makeText(getActivity(), x.toString(), Toast.LENGTH_SHORT).show(); + return; + } + + class ColorsAdapter extends RecyclerView.Adapter{ + @NonNull + @Override + public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + TextView view=new TextView(getActivity()); + int pad=V.dp(16); + view.setPadding(pad, pad, pad, pad); + view.setTextSize(14); + view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return new SimpleViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){ + Pair attr=attrs.get(position); + TextView view=(TextView) holder.itemView; + int color=UiUtils.getThemeColor(getActivity(), attr.first); + view.setBackgroundColor(color); + view.setText(String.format("%s\n#%06X", attr.second, (color & 0xFF000000) != 0xFF000000 ? color : (color & 0xFFFFFF))); + view.setTextColor(new Palette.Swatch(color | 0xFF000000, 1).getBodyTextColor()); + } + + @Override + public int getItemCount(){ + return attrs.size(); + } + } + + RecyclerView rv=new RecyclerView(getActivity()); + rv.setLayoutManager(new LinearLayoutManager(getActivity())); + rv.setAdapter(new ColorsAdapter()); + new M3AlertDialogBuilder(getActivity()) + .setTitle("Dynamic colors") + .setView(rv) + .setPositiveButton(R.string.ok, null) + .show(); + } + private void restartUI(){ Bundle args=new Bundle(); args.putString("account", accountID); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/ColorContrastMode.java b/mastodon/src/main/java/org/joinmastodon/android/ui/ColorContrastMode.java new file mode 100644 index 000000000..b94ef282f --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/ColorContrastMode.java @@ -0,0 +1,15 @@ +package org.joinmastodon.android.ui; + +public enum ColorContrastMode{ + DEFAULT, + MEDIUM, + HIGH; + + public static ColorContrastMode fromContrastValue(float value){ + if(value>0.75f) + return HIGH; + if(value>0.25f) + return MEDIUM; + return DEFAULT; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/BaseMonospaceSpan.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/BaseMonospaceSpan.java index 8425cc441..91790478a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/BaseMonospaceSpan.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/BaseMonospaceSpan.java @@ -5,6 +5,7 @@ import android.text.TextPaint; import android.text.style.TypefaceSpan; import org.joinmastodon.android.R; +import org.joinmastodon.android.ui.ColorContrastMode; import org.joinmastodon.android.ui.utils.UiUtils; import androidx.annotation.NonNull; @@ -21,7 +22,11 @@ public abstract class BaseMonospaceSpan extends TypefaceSpan{ @Override public void updateDrawState(@NonNull TextPaint paint){ super.updateDrawState(paint); - paint.setColor(UiUtils.getThemeColor(context, R.attr.colorRichTextText)); + if(!UiUtils.isDarkTheme() && UiUtils.getColorContrastMode(context)==ColorContrastMode.HIGH){ + + }else{ + paint.setColor(UiUtils.getThemeColor(context, R.attr.colorRichTextText)); + } paint.setTextSize(paint.getTextSize()*0.9375f); paint.baselineShift=V.dp(-1); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/BlockQuoteSpan.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/BlockQuoteSpan.java index 434beac81..a5f4384cc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/BlockQuoteSpan.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/BlockQuoteSpan.java @@ -41,16 +41,7 @@ public class BlockQuoteSpan extends CharacterStyle implements LeadingMarginSpan{ @Override public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir, int top, int baseline, int bottom, @NonNull CharSequence text, int start, int end, boolean first, @NonNull Layout layout){ if(text instanceof Spanned s && s.getSpanStart(this)==start){ - int color; - if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S && UiUtils.isDarkTheme()){ - color=UiUtils.alphaBlendColors( - context.getColor(android.R.color.system_accent3_700), - context.getColor(android.R.color.system_accent3_800), - 0.5f - ); - }else{ - color=UiUtils.getThemeColor(context, R.attr.colorRichTextDecorations); - } + int color=UiUtils.getThemeColor(context, R.attr.colorRichTextDecorations); int level=s.getSpans(start, end, LeadingMarginSpan.class).length-1; if(dir<0){ // RTL if(level==0){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index 77e0fd5c5..c04c0ea48 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -2,6 +2,7 @@ package org.joinmastodon.android.ui.utils; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.UiModeManager; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ComponentName; @@ -75,6 +76,7 @@ import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.SearchResults; import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.ColorContrastMode; import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.Snackbar; import org.joinmastodon.android.ui.sheets.BlockAccountConfirmationSheet; @@ -721,9 +723,21 @@ public class UiUtils{ public static void setUserPreferredTheme(Context context){ context.setTheme(switch(GlobalUserPreferences.theme){ - case AUTO -> R.style.Theme_Mastodon_AutoLightDark; - case LIGHT -> R.style.Theme_Mastodon_Light; - case DARK -> R.style.Theme_Mastodon_Dark; + case AUTO -> switch(getColorContrastMode(context)){ + case DEFAULT -> R.style.Theme_Mastodon_AutoLightDark; + case MEDIUM -> R.style.Theme_Mastodon_AutoLightDark_MediumContrast; + case HIGH -> R.style.Theme_Mastodon_AutoLightDark_HighContrast; + }; + case LIGHT -> switch(getColorContrastMode(context)){ + case DEFAULT -> R.style.Theme_Mastodon_Light; + case MEDIUM -> R.style.Theme_Mastodon_Light_MediumContrast; + case HIGH -> R.style.Theme_Mastodon_Light_HighContrast; + }; + case DARK -> switch(getColorContrastMode(context)){ + case DEFAULT -> R.style.Theme_Mastodon_Dark; + case MEDIUM -> R.style.Theme_Mastodon_Dark_MediumContrast; + case HIGH -> R.style.Theme_Mastodon_Dark_HighContrast; + }; }); } @@ -1086,4 +1100,10 @@ public class UiUtils{ rv.scrollBy(0, -topItemOffset); } } + + public static ColorContrastMode getColorContrastMode(Context context){ + if(Build.VERSION.SDK_INT extends S String[] authorParts=itemView.getContext().getString(R.string.article_by_author, "{author}").split("\\{author\\}"); String before=authorParts.length>0 ? authorParts[0].trim() : ""; String after=authorParts.length>1 ? authorParts[1].trim() : ""; - if(!TextUtils.isEmpty(before)){ - authorBefore.setText(before); - }else{ - authorBefore.setText(""); - } + authorBefore.setText(before); if(TextUtils.isEmpty(after)){ authorAfter.setVisibility(View.GONE); }else{ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckIconSelectableTextView.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckIconSelectableTextView.java index 7b21933b9..7eff03bc5 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckIconSelectableTextView.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckIconSelectableTextView.java @@ -24,6 +24,10 @@ public class CheckIconSelectableTextView extends TextView{ super(context, attrs, defStyle); } + protected int getCheckmarkColorAttribute(){ + return R.attr.colorM3OnSurface; + } + @Override protected void drawableStateChanged(){ super.drawableStateChanged(); @@ -32,7 +36,7 @@ public class CheckIconSelectableTextView extends TextView{ currentlySelected=isSelected(); Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()).mutate() : null; if(start!=null) - start.setTint(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurface)); + start.setTint(UiUtils.getThemeColor(getContext(), getCheckmarkColorAttribute())); Drawable end=getCompoundDrawablesRelative()[2]; setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java index 7f79095e8..1d112b7f2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/FilterChipView.java @@ -35,6 +35,10 @@ public class FilterChipView extends CheckIconSelectableTextView{ updatePadding(); } + protected int getCheckmarkColorAttribute(){ + return R.attr.colorM3OnSecondaryContainer; + } + private void updatePadding(){ int vertical=V.dp(6); Drawable[] drawables=getCompoundDrawablesRelative(); diff --git a/mastodon/src/main/res/color-v31/m3_sys_accent3_750.xml b/mastodon/src/main/res/color-v31/m3_sys_accent3_750.xml new file mode 100644 index 000000000..efeb8f5dc --- /dev/null +++ b/mastodon/src/main/res/color-v31/m3_sys_accent3_750.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color-v31/m3_sys_accent3_850.xml b/mastodon/src/main/res/color-v31/m3_sys_accent3_850.xml new file mode 100644 index 000000000..322ddd92f --- /dev/null +++ b/mastodon/src/main/res/color-v31/m3_sys_accent3_850.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/values-night/styles.xml b/mastodon/src/main/res/values-night/styles.xml index 00190b478..80b9884fc 100644 --- a/mastodon/src/main/res/values-night/styles.xml +++ b/mastodon/src/main/res/values-night/styles.xml @@ -1,4 +1,6 @@ + + + + + + + +