Java tutorial
/* * Twidere - Twitter client for Android * * Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com> * * 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/>. */ package org.mariotaku.twidere.util; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PorterDuff.Mode; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; import android.support.v4.view.ViewCompat; import android.support.v7.app.ActionBar; import android.support.v7.internal.app.WindowDecorActionBar; import android.support.v7.internal.app.WindowDecorActionBar.ActionModeImpl; import android.support.v7.internal.view.StandaloneActionMode; import android.support.v7.internal.view.menu.ActionMenuItemView; import android.support.v7.internal.widget.ActionBarContainer; import android.support.v7.internal.widget.ActionBarContextView; import android.support.v7.internal.widget.ActionBarOverlayLayout; import android.support.v7.internal.widget.ContentFrameLayout; import android.support.v7.widget.ActionMenuPresenter; import android.support.v7.widget.ActionMenuView; import android.support.v7.widget.Toolbar; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.TextUtils; import android.util.TypedValue; import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextThemeWrapper; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnLongClickListener; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; import org.apache.commons.lang3.ArrayUtils; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.R; import org.mariotaku.twidere.activity.iface.IThemedActivity; import org.mariotaku.twidere.graphic.ActionBarColorDrawable; import org.mariotaku.twidere.graphic.ActionIconDrawable; import org.mariotaku.twidere.preference.ThemeBackgroundPreference; import org.mariotaku.twidere.text.ParagraphSpacingSpan; import org.mariotaku.twidere.util.menu.TwidereMenuInfo; import org.mariotaku.twidere.util.support.ViewSupport; import org.mariotaku.twidere.view.TabPagerIndicator; import org.mariotaku.twidere.view.TwidereToolbar; import java.lang.reflect.Field; public class ThemeUtils implements Constants { public static final int ACCENT_COLOR_THRESHOLD = 192; private static final int[] ANIM_OPEN_STYLE_ATTRS = { android.R.attr.activityOpenEnterAnimation, android.R.attr.activityOpenExitAnimation }; private static final int[] ANIM_CLOSE_STYLE_ATTRS = { android.R.attr.activityCloseEnterAnimation, android.R.attr.activityCloseExitAnimation }; public static final int[] ATTRS_TEXT_COLOR_PRIMARY = { android.R.attr.textColorPrimary }; public static final int[] ATTRS_TEXT_COLOR_PRIMARY_AND_INVERSE = { android.R.attr.textColorPrimary, android.R.attr.textColorPrimaryInverse }; public static final int[] ATTRS_COLOR_FOREGROUND_AND_INVERSE = { android.R.attr.colorForeground, android.R.attr.colorForegroundInverse }; private ThemeUtils() { throw new AssertionError(); } public static void applyActionBarBackground(final ActionBar actionBar, final Context context, final int themeRes, final int accentColor, String backgroundOption, boolean outlineEnabled) { if (actionBar == null || context == null) return; actionBar.setBackgroundDrawable( getActionBarBackground(context, themeRes, accentColor, backgroundOption, outlineEnabled)); actionBar.setSplitBackgroundDrawable(getActionBarSplitBackground(context, themeRes)); actionBar.setStackedBackgroundDrawable( getActionBarStackedBackground(context, themeRes, accentColor, backgroundOption, outlineEnabled)); } public static void applyActionBarBackground(final ActionBarContainer actionBar, final Context context, final int themeRes, final int accentColor, String backgroundOption, boolean outlineEnabled) { if (actionBar == null || context == null) return; actionBar.setPrimaryBackground( getActionBarBackground(context, themeRes, accentColor, backgroundOption, outlineEnabled)); actionBar.setSplitBackground(getActionBarSplitBackground(context, themeRes)); actionBar.setStackedBackground( getActionBarStackedBackground(context, themeRes, accentColor, backgroundOption, outlineEnabled)); } public static void applyColorFilterToMenuIcon(final Menu menu, final int color, final int popupColor, final int highlightColor, final Mode mode, final int... excludedGroups) { for (int i = 0, j = menu.size(); i < j; i++) { final MenuItem item = menu.getItem(i); final Drawable icon = item.getIcon(); final ContextMenuInfo info = item.getMenuInfo(); if (icon != null && !ArrayUtils.contains(excludedGroups, item.getGroupId())) { icon.mutate(); if (info instanceof TwidereMenuInfo) { final TwidereMenuInfo sInfo = (TwidereMenuInfo) info; icon.setColorFilter(sInfo.isHighlight() ? sInfo.getHighlightColor(highlightColor) : color, mode); } else { icon.setColorFilter(color, mode); } } if (item.hasSubMenu()) { // SubMenu item is always in popup applyColorFilterToMenuIcon(item.getSubMenu(), popupColor, popupColor, highlightColor, mode, excludedGroups); } } } public static void applyParagraphSpacing(TextView textView, float multiplier) { final SpannableStringBuilder builder = SpannableStringBuilder.valueOf(textView.getText()); int prevLineBreak, currLineBreak = 0; for (int i = 0, j = builder.length(); i < j; i++) { if (builder.charAt(i) == '\n') { prevLineBreak = currLineBreak; currLineBreak = i; if (currLineBreak > 0) { builder.setSpan(new ParagraphSpacingSpan(multiplier), prevLineBreak, currLineBreak, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } } } textView.setText(builder); } public static void applySupportActionModeColor(final android.support.v7.view.ActionMode modeCompat, Activity activity, int themeRes, int accentColor, String backgroundOption, boolean outlineEnabled) { // Very dirty implementation // This call ensures TitleView created modeCompat.setTitle(modeCompat.getTitle()); View contextView = null; if (modeCompat instanceof ActionModeImpl) { WindowDecorActionBar actionBar = (WindowDecorActionBar) Utils.findFieldOfTypes(modeCompat, ActionModeImpl.class, WindowDecorActionBar.class); if (actionBar == null) return; contextView = (View) Utils.findFieldOfTypes(actionBar, WindowDecorActionBar.class, ActionBarContextView.class); } else if (modeCompat instanceof StandaloneActionMode) { contextView = (View) Utils.findFieldOfTypes(modeCompat, StandaloneActionMode.class, ActionBarContextView.class); } if (!(contextView instanceof ActionBarContextView)) return; setActionBarContextViewBackground((ActionBarContextView) contextView, themeRes, accentColor, backgroundOption, outlineEnabled); } public static void setActionBarContextViewBackground(@NonNull ActionBarContextView contextView, int themeRes, int accentColor, String backgroundOption, boolean outlineEnabled) { ViewSupport.setBackground(contextView, getActionBarBackground(contextView.getContext(), themeRes, accentColor, backgroundOption, outlineEnabled)); } public static void setActionBarContextViewColor(@NonNull ActionBarContextView contextView, int itemColor) { contextView.setTitle(contextView.getTitle()); contextView.setSubtitle(contextView.getSubtitle()); final ImageView actionModeCloseButton = (ImageView) contextView .findViewById(android.support.v7.appcompat.R.id.action_mode_close_button); final ActionMenuView menuView = ViewSupport.findViewByType(contextView, ActionMenuView.class); if (actionModeCloseButton != null) { actionModeCloseButton.setColorFilter(itemColor, Mode.SRC_ATOP); } if (menuView != null) { setActionBarOverflowColor(menuView, itemColor); ThemeUtils.wrapToolbarMenuIcon(menuView, itemColor, itemColor); } } public static void applyWindowBackground(@NonNull Context context, @NonNull Window window, int theme, String option, int alpha) { if (isWindowFloating(context, theme)) return; final int normalTheme = getThemeResource(theme); if (VALUE_THEME_BACKGROUND_TRANSPARENT.equals(option)) { window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER); window.setBackgroundDrawable(getWindowBackgroundFromThemeApplyAlpha(context, normalTheme, alpha)); } else if (VALUE_THEME_BACKGROUND_SOLID.equals(option)) { window.setBackgroundDrawable(new ColorDrawable(isDarkTheme(normalTheme) ? Color.BLACK : Color.WHITE)); } else { window.setBackgroundDrawable(getWindowBackgroundFromTheme(context, normalTheme)); } } public static void applyWindowBackground(Context context, View window, int theme, String option, int alpha) { if (isWindowFloating(context, theme)) return; final int normalTheme = getThemeResource(theme); if (VALUE_THEME_BACKGROUND_TRANSPARENT.equals(option)) { ViewSupport.setBackground(window, getWindowBackgroundFromThemeApplyAlpha(context, normalTheme, alpha)); } else if (VALUE_THEME_BACKGROUND_SOLID.equals(option)) { ViewSupport.setBackground(window, new ColorDrawable(isDarkTheme(normalTheme) ? Color.BLACK : Color.WHITE)); } else { ViewSupport.setBackground(window, getWindowBackgroundFromTheme(context, normalTheme)); } } @NonNull public static Drawable getActionBarBackground(final Context context, final int themeRes, final int accentColor, final String backgroundOption, final boolean outlineEnabled) { final int actionBarColor; if (!isDarkTheme(themeRes)) { actionBarColor = accentColor; } else if (isSolidBackground(backgroundOption)) { actionBarColor = Color.BLACK; } else { actionBarColor = context.getResources().getColor(R.color.background_color_action_bar_dark); } return ActionBarColorDrawable.create(actionBarColor, outlineEnabled); } public static int getActionBarPopupThemeRes(final Context context, int def) { final TypedValue outValue = new TypedValue(); if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, outValue, true)) { return outValue.resourceId; } return def; } public static Drawable getActionBarSplitBackground(final Context context, final int themeRes) { @SuppressWarnings("ConstantConditions") final TypedArray a = context.obtainStyledAttributes(null, new int[] { android.R.attr.backgroundSplit }, android.R.attr.actionBarStyle, themeRes); try { return a.getDrawable(0); } finally { a.recycle(); } } @NonNull public static Drawable getActionBarStackedBackground(final Context context, final int themeRes, final int accentColor, String backgroundOption, boolean outlineEnabled) { return getActionBarBackground(context, themeRes, accentColor, backgroundOption, outlineEnabled); } public static int getCardBackgroundColor(final Context context, String backgroundOption, int themeAlpha) { final TypedArray a = context.obtainStyledAttributes(new int[] { R.attr.cardItemBackgroundColor }); final int color = a.getColor(0, Color.TRANSPARENT); a.recycle(); if (isTransparentBackground(backgroundOption)) { return themeAlpha << 24 | (0x00FFFFFF & color); } else if (isSolidBackground(backgroundOption)) { return TwidereColorUtils.getContrastYIQ(color, Color.WHITE, Color.BLACK); } else { return color; } } public static Drawable getCompatToolbarOverlay(Activity activity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return null; final Window window = activity.getWindow(); final View view = window.findViewById(android.support.v7.appcompat.R.id.decor_content_parent); if (!(view instanceof ActionBarOverlayLayout)) { final View contentLayout = window .findViewById(android.support.v7.appcompat.R.id.action_bar_activity_content); if (contentLayout instanceof ContentFrameLayout) { return ((ContentFrameLayout) contentLayout).getForeground(); } return null; } try { final Field field = ActionBarOverlayLayout.class.getDeclaredField("mWindowContentOverlay"); field.setAccessible(true); return (Drawable) field.get(view); } catch (Exception ignore) { } return null; } public static int getComposeThemeResource(final Context context) { return getComposeThemeResource(getThemeNameOption(context)); } public static int getComposeThemeResource(final String name) { if (VALUE_THEME_NAME_DARK.equals(name)) return R.style.Theme_Twidere_Dark_Compose; return R.style.Theme_Twidere_Light_Compose; } public static int getContrastForegroundColor(Context context, int theme, int color) { final int[] colors = new int[2]; getDarkLightForegroundColors(context, theme, colors); if (isDarkTheme(theme) || TwidereColorUtils.getYIQLuminance(color) <= ACCENT_COLOR_THRESHOLD) { //return light text color return colors[1]; } //return dark text color return colors[0]; } public static int getContrastActionBarItemColor(Context context) { return getColorFromAttribute(context, android.R.attr.colorForeground, 0); } public static int getDialogThemeResource(final Context context) { return getDialogThemeResource(getThemeNameOption(context)); } public static int getDialogThemeResource(final String name) { if (VALUE_THEME_NAME_DARK.equals(name)) return R.style.Theme_Twidere_Dark_Dialog; return R.style.Theme_Twidere_Light_Dialog; } public static Context getDialogThemedContext(final Context context) { return new ContextThemeWrapper(context, getDialogThemeResource(context)); } public static int getDialogWhenLargeThemeResource(final Context context) { return getDialogWhenLargeThemeResource(getThemeNameOption(context)); } public static int getDialogWhenLargeThemeResource(final String name) { if (VALUE_THEME_NAME_DARK.equals(name)) { return R.style.Theme_Twidere_Dark_DialogWhenLarge_NoActionBar; } return R.style.Theme_Twidere_Light_DialogWhenLarge_NoActionBar; } public static int getDrawerThemeResource(final Context context) { return getDrawerThemeResource(getNoActionBarThemeResource(context)); } public static int getDrawerThemeResource(final int themeRes) { return R.style.Theme_Twidere_Drawer_Dark; } public static Drawable getImageHighlightDrawable(final Context context) { final Drawable d = getSelectableItemBackgroundDrawable(context); if (d != null) { d.setAlpha(0x80); } return d; } public static int getNoDisplayThemeResource(final Context context) { if (context == null) return R.style.Theme_Twidere_Dark_NoDisplay; final SharedPreferencesWrapper pref = getSharedPreferencesWrapper(context); final String theme = pref.getString(KEY_THEME, VALUE_THEME_NAME_TWIDERE); if (VALUE_THEME_NAME_DARK.equals(theme)) return R.style.Theme_Twidere_Dark_NoDisplay; return R.style.Theme_Twidere_Light_NoDisplay; } public static int getOptimalLinkColor(int linkColor, int color) { final int[] yiq = new int[3]; TwidereColorUtils.colorToYIQ(color, yiq); final int y = yiq[0]; TwidereColorUtils.colorToYIQ(linkColor, yiq); if (y < 32 && yiq[0] <= ACCENT_COLOR_THRESHOLD) { return linkColor; } else if (y > ACCENT_COLOR_THRESHOLD && yiq[0] > 32) { return linkColor; } yiq[0] = yiq[0] + (y - yiq[0]) / 2; return TwidereColorUtils.YIQToColor(Color.alpha(linkColor), yiq); } public static int getQuickSearchBarThemeResource(final Context context) { return getQuickSearchBarThemeResource(getThemeNameOption(context)); } public static int getQuickSearchBarThemeResource(final String name) { if (VALUE_THEME_NAME_DARK.equals(name)) return R.style.Theme_Twidere_Dark_QuickSearchBar; return R.style.Theme_Twidere_Light_QuickSearchBar; } public static Resources getResources(final Context context) { return context.getResources(); } public static Drawable getSelectableItemBackgroundDrawable(final Context context) { final TypedArray a = context.obtainStyledAttributes(new int[] { android.R.attr.selectableItemBackground }); try { return a.getDrawable(0); } finally { a.recycle(); } } public static float getSupportActionBarElevation(final Context context) { @SuppressWarnings("ConstantConditions") final TypedArray a = context.obtainStyledAttributes(null, new int[] { R.attr.elevation }, R.attr.actionBarStyle, 0); try { return a.getDimension(0, 0); } finally { a.recycle(); } } public static int getColorFromAttribute(Context context, int attr, int def) { final TypedValue outValue = new TypedValue(); if (!context.getTheme().resolveAttribute(attr, outValue, true)) return def; if (outValue.type == TypedValue.TYPE_REFERENCE) return context.getResources().getColor(attr); return outValue.data; } public static int getColorFromAttribute(Context context, int themeId, int attr, int def) { final TypedValue outValue = new TypedValue(); final Resources.Theme theme = context.getResources().newTheme(); theme.applyStyle(themeId, true); if (!theme.resolveAttribute(attr, outValue, true)) return def; if (outValue.type == TypedValue.TYPE_REFERENCE) return context.getResources().getColor(attr); return outValue.data; } public static int getTextColorPrimary(final Context context) { final TypedArray a = context.obtainStyledAttributes(ATTRS_TEXT_COLOR_PRIMARY); try { return a.getColor(0, Color.TRANSPARENT); } finally { a.recycle(); } } public static void getTextColorPrimaryAndInverse(final Context context, int[] colors) { final TypedArray a = context.obtainStyledAttributes(ATTRS_TEXT_COLOR_PRIMARY_AND_INVERSE); try { colors[0] = a.getColor(0, Color.TRANSPARENT); colors[1] = a.getColor(1, Color.TRANSPARENT); } finally { a.recycle(); } } public static void getDarkLightForegroundColors(final Context context, int themeRes, int[] colors) { final TypedArray a = context.obtainStyledAttributes(ATTRS_COLOR_FOREGROUND_AND_INVERSE); try { if (isDarkTheme(themeRes)) { colors[0] = a.getColor(1, Color.WHITE); colors[1] = a.getColor(0, Color.BLACK); } else { colors[0] = a.getColor(0, Color.WHITE); colors[1] = a.getColor(1, Color.BLACK); } } finally { a.recycle(); } } public static void getColorForegroundAndInverse(final Context context, int[] colors) { final TypedArray a = context.obtainStyledAttributes(ATTRS_COLOR_FOREGROUND_AND_INVERSE); try { colors[0] = a.getColor(0, Color.WHITE); colors[1] = a.getColor(1, Color.BLACK); } finally { a.recycle(); } } public static int getThemeBackgroundColor(final Context context) { final TypedArray a = context.obtainStyledAttributes(new int[] { android.R.attr.colorBackground }); try { return a.getColor(0, 0); } finally { a.recycle(); } } public static int getThemeBackgroundColor(final Context context, int themeRes) { if (themeRes == 0) { return getThemeBackgroundColor(context); } @SuppressWarnings("ConstantConditions") final TypedArray a = context.obtainStyledAttributes(null, new int[] { android.R.attr.colorBackground }, 0, themeRes); try { return a.getColor(0, 0); } finally { a.recycle(); } } public static String getThemeBackgroundOption(final Context context) { if (context == null) return VALUE_THEME_BACKGROUND_DEFAULT; final SharedPreferencesWrapper pref = getSharedPreferencesWrapper(context); return pref.getString(KEY_THEME_BACKGROUND, VALUE_THEME_BACKGROUND_DEFAULT); } public static int getThemeColor(final Context context) { final TypedArray a = context.obtainStyledAttributes(new int[] { android.R.attr.colorActivatedHighlight }); try { final Resources resources = context.getResources(); return a.getColor(0, resources.getColor(R.color.material_light_blue)); } finally { a.recycle(); } } public static int getThemeColor(Context context, int themeResourceId) { final Context appContext = context.getApplicationContext(); final Resources res = appContext.getResources(); @SuppressWarnings("ConstantConditions") final TypedArray a = appContext.obtainStyledAttributes(null, new int[] { android.R.attr.colorActivatedHighlight }, 0, themeResourceId); try { return a.getColor(0, res.getColor(R.color.material_light_blue)); } finally { a.recycle(); } } public static String getThemeFontFamily(final Context context) { if (context == null) return VALUE_THEME_FONT_FAMILY_REGULAR; final SharedPreferencesWrapper pref = getSharedPreferencesWrapper(context); final String fontFamily = pref.getString(KEY_THEME_FONT_FAMILY, VALUE_THEME_FONT_FAMILY_REGULAR); if (!TextUtils.isEmpty(fontFamily)) return fontFamily; return VALUE_THEME_FONT_FAMILY_REGULAR; } public static int getThemeForegroundColor(final Context context) { return getThemeForegroundColor(context, 0); } public static int getThemeForegroundColor(final Context context, int themeRes) { @SuppressWarnings("ConstantConditions") final TypedValue value = new TypedValue(); final Resources.Theme theme; if (themeRes != 0) { theme = context.getResources().newTheme(); theme.applyStyle(themeRes, false); } else { theme = context.getTheme(); } if (!theme.resolveAttribute(android.R.attr.colorForeground, value, true)) { return 0; } if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { // windowBackground is a color return value.data; } else if (value.type == TypedValue.TYPE_REFERENCE) { return theme.getResources().getColor(value.resourceId); } return 0; } public static String getThemeNameOption(final Context context) { if (context == null) return VALUE_THEME_NAME_TWIDERE; final SharedPreferencesWrapper pref = getSharedPreferencesWrapper(context); return pref.getString(KEY_THEME, VALUE_THEME_NAME_TWIDERE); } public static int getThemeResource(final int otherTheme) { if (isDarkTheme(otherTheme)) { return R.style.Theme_Twidere_Dark; } return R.style.Theme_Twidere_Light; } public static int getNoActionBarThemeResource(final Context context) { return getNoActionBarThemeResource(getThemeNameOption(context)); } public static int getNoActionBarThemeResource(final String name) { if (VALUE_THEME_NAME_DARK.equals(name)) { return R.style.Theme_Twidere_Dark_NoActionBar; } return R.style.Theme_Twidere_Light_NoActionBar; } public static int getTitleTextAppearance(final Context context) { @SuppressWarnings("ConstantConditions") final TypedArray a = context.obtainStyledAttributes(null, new int[] { android.R.attr.titleTextStyle }, android.R.attr.actionBarStyle, android.R.style.Widget_Holo_ActionBar); final int textAppearance = a.getResourceId(0, android.R.style.TextAppearance_Holo); a.recycle(); return textAppearance; } public static int getUserAccentColor(final Context context) { if (context == null) return Color.TRANSPARENT; final SharedPreferencesWrapper pref = getSharedPreferencesWrapper(context); final Resources res = context.getResources(); final int def = res.getColor(R.color.branding_color); return pref.getInt(KEY_THEME_COLOR, def); } public static int getOptimalAccentColor(final Context context, boolean isActionBarContext, int themeResId) { final int userAccentColor = getUserAccentColor(context); final int backgroundColorApprox; final boolean isDarkTheme = isDarkTheme(themeResId); if (!isActionBarContext) { backgroundColorApprox = isDarkTheme ? Color.BLACK : Color.WHITE; } else if (isDarkTheme) { // View context is derived from ActionBar but is currently dark theme, so we should show // light backgroundColorApprox = Color.BLACK; } else { // View context is derived from ActionBar and it's light theme, so we use contrast color backgroundColorApprox = Color.WHITE; } if (Math.abs(TwidereColorUtils.getYIQContrast(backgroundColorApprox, userAccentColor)) > 64) return userAccentColor; return getColorFromAttribute(context, R.attr.colorAccent, context.getResources().getColor(R.color.branding_color)); } public static int getUserHighlightColor(final Context context) { final int color = getUserLinkTextColor(context); final int red = Color.red(color), green = Color.green(color), blue = Color.blue(color); return Color.argb(0x66, red, green, blue); } public static int getUserLinkTextColor(final Context context) { if (context == null) return new TextPaint().linkColor; final int themeColor = getUserAccentColor(context); final float[] hsv = new float[3]; Color.colorToHSV(themeColor, hsv); if (isDarkTheme(context)) { hsv[2] = MathUtils.clamp(hsv[2], 1, 0.5f); } else { hsv[2] = MathUtils.clamp(hsv[2], 0.1f, 0.75f); } return Color.HSVToColor(hsv); } public static int getUserThemeBackgroundAlpha(final Context context) { if (context == null) return DEFAULT_THEME_BACKGROUND_ALPHA; final SharedPreferencesWrapper pref = getSharedPreferencesWrapper(context); return MathUtils.clamp(pref.getInt(KEY_THEME_BACKGROUND_ALPHA, DEFAULT_THEME_BACKGROUND_ALPHA), ThemeBackgroundPreference.MIN_ALPHA, ThemeBackgroundPreference.MAX_ALPHA); } public static int getActionBarAlpha(final int alpha) { final int normalizedAlpha = MathUtils.clamp(alpha, 0, 0xFF); final int delta = (ThemeBackgroundPreference.MAX_ALPHA - normalizedAlpha); return MathUtils.clamp(ThemeBackgroundPreference.MAX_ALPHA - delta / 2, ThemeBackgroundPreference.MIN_ALPHA, ThemeBackgroundPreference.MAX_ALPHA); } public static Typeface getUserTypeface(final Context context, final Typeface defTypeface) { if (context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return Typeface.DEFAULT; final int fontStyle = defTypeface != null ? defTypeface.getStyle() : Typeface.NORMAL; final String fontFamily = getThemeFontFamily(context); final Typeface tf = Typeface.create(fontFamily, fontStyle); if (tf != null) return tf; return Typeface.create(Typeface.DEFAULT, fontStyle); } public static Typeface getUserTypeface(final Context context, final String fontFamily, final Typeface defTypeface) { if (context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return Typeface.DEFAULT; final int fontStyle = defTypeface != null ? defTypeface.getStyle() : Typeface.NORMAL; final Typeface tf = Typeface.create(fontFamily, fontStyle); if (tf != null) return tf; return Typeface.create(Typeface.DEFAULT, fontStyle); } public static int getViewerThemeResource(final Context context) { return R.style.Theme_Twidere_Viewer; } public static Drawable getWindowBackground(final Context context) { final TypedArray a = context.obtainStyledAttributes(new int[] { android.R.attr.windowBackground }); try { return a.getDrawable(0); } finally { a.recycle(); } } public static Drawable getWindowBackgroundFromTheme(final Context context, int theme) { @SuppressWarnings("ConstantConditions") final TypedArray a = context.obtainStyledAttributes(null, new int[] { android.R.attr.windowBackground }, 0, theme); try { return a.getDrawable(0); } finally { a.recycle(); } } public static Drawable getWindowBackgroundFromThemeApplyAlpha(final Context context, final int theme, final int alpha) { @SuppressWarnings("ConstantConditions") final TypedArray a = context.obtainStyledAttributes(null, new int[] { android.R.attr.windowBackground }, 0, theme); final Drawable d = a.getDrawable(0); a.recycle(); if (d != null) { d.setAlpha(MathUtils.clamp(alpha, ThemeBackgroundPreference.MIN_ALPHA, ThemeBackgroundPreference.MAX_ALPHA)); } return d; } public static Drawable getWindowContentOverlay(final Context context) { final TypedArray a = context.obtainStyledAttributes(new int[] { android.R.attr.windowContentOverlay }); try { return a.getDrawable(0); } finally { a.recycle(); } } public static Drawable getWindowContentOverlay(final Context context, int themeRes) { @SuppressWarnings("ConstantConditions") final TypedArray a = context.obtainStyledAttributes(null, new int[] { android.R.attr.windowContentOverlay }, 0, themeRes); try { return a.getDrawable(0); } finally { a.recycle(); } } public static Drawable getNormalWindowContentOverlay(final Context context, int themeRes) { final int normalThemeResId = ThemeUtils.getThemeResource(themeRes); return getWindowContentOverlay(context, normalThemeResId); } public static void initPagerIndicatorAsActionBarTab(FragmentActivity activity, TabPagerIndicator indicator, @Nullable View pagerOverlay) { final float supportActionBarElevation = getSupportActionBarElevation(activity); ViewCompat.setElevation(indicator, supportActionBarElevation); if (!(activity instanceof IThemedActivity)) return; final int themeRes = ((IThemedActivity) activity).getCurrentThemeResourceId(); final int themeColor = ((IThemedActivity) activity).getCurrentThemeColor(); final String backgroundOption = ((IThemedActivity) activity).getCurrentThemeBackgroundOption(); final int colorDark, colorLight; final int[] textColors = new int[2]; getTextColorPrimaryAndInverse(activity, textColors); if (isDarkTheme(themeRes)) { colorDark = textColors[1]; colorLight = textColors[0]; } else { colorDark = textColors[0]; colorLight = textColors[1]; } final int contrastColor = TwidereColorUtils.getContrastYIQ(themeColor, ACCENT_COLOR_THRESHOLD, colorDark, colorLight); ViewSupport.setBackground(indicator, getActionBarStackedBackground(activity, themeRes, themeColor, backgroundOption, true)); if (isDarkTheme(themeRes)) { final int foregroundColor = getThemeForegroundColor(activity); indicator.setIconColor(foregroundColor); indicator.setLabelColor(foregroundColor); indicator.setStripColor(themeColor); } else { indicator.setIconColor(contrastColor); indicator.setLabelColor(contrastColor); indicator.setStripColor(contrastColor); } indicator.updateAppearance(); if (pagerOverlay != null) { ViewSupport.setBackground(pagerOverlay, getNormalWindowContentOverlay(activity, themeRes)); } } public static boolean isColoredActionBar(int themeRes) { return !isDarkTheme(themeRes); } public static boolean isDarkTheme(final Context context) { return isDarkTheme(getNoActionBarThemeResource(context)); } public static boolean isDarkTheme(final int themeRes) { switch (themeRes) { case R.style.Theme_Twidere_Dark: case R.style.Theme_Twidere_Dark_NoActionBar: case R.style.Theme_Twidere_Dark_Dialog: case R.style.Theme_Twidere_Dark_DialogWhenLarge_NoActionBar: case R.style.Theme_Twidere_Dark_Compose: return true; } return false; } public static boolean isSolidBackground(final String option) { return VALUE_THEME_BACKGROUND_SOLID.equals(option); } public static boolean isTransparentBackground(final Context context) { return isTransparentBackground(getThemeBackgroundOption(context)); } public static boolean isTransparentBackground(final String option) { return VALUE_THEME_BACKGROUND_TRANSPARENT.equals(option); } public static boolean isWindowFloating(Context context, int theme) { final TypedArray a; if (theme != 0) { //noinspection ConstantConditions a = context.obtainStyledAttributes(null, new int[] { android.R.attr.windowIsFloating }, 0, theme); } else { a = context.obtainStyledAttributes(new int[] { android.R.attr.windowIsFloating }); } try { return a.getBoolean(0, false); } finally { a.recycle(); } } public static void overrideActivityCloseAnimation(final Activity activity) { TypedArray a = activity.obtainStyledAttributes(new int[] { android.R.attr.windowAnimationStyle }); final int windowAnimationStyleResId = a.getResourceId(0, 0); a.recycle(); // Now retrieve the resource ids of the actual animations used in the // animation style pointed to by // the window animation resource id. a = activity.obtainStyledAttributes(windowAnimationStyleResId, ANIM_CLOSE_STYLE_ATTRS); final int activityCloseEnterAnimation = a.getResourceId(0, 0); final int activityCloseExitAnimation = a.getResourceId(1, 0); a.recycle(); activity.overridePendingTransition(activityCloseEnterAnimation, activityCloseExitAnimation); } public static void overrideActivityOpenAnimation(final Activity activity) { TypedArray a = activity.obtainStyledAttributes(new int[] { android.R.attr.windowAnimationStyle }); final int windowAnimationStyleResId = a.getResourceId(0, 0); a.recycle(); // Now retrieve the resource ids of the actual animations used in the // animation style pointed to by // the window animation resource id. a = activity.obtainStyledAttributes(windowAnimationStyleResId, ANIM_OPEN_STYLE_ATTRS); final int activityOpenEnterAnimation = a.getResourceId(0, 0); final int activityOpenExitAnimation = a.getResourceId(1, 0); a.recycle(); activity.overridePendingTransition(activityOpenEnterAnimation, activityOpenExitAnimation); } public static void overrideNormalActivityCloseAnimation(final Activity activity) { @SuppressWarnings("ConstantConditions") final TypedArray a = activity.obtainStyledAttributes(null, ANIM_CLOSE_STYLE_ATTRS, 0, android.R.style.Animation_Activity); final int activityCloseEnterAnimation = a.getResourceId(0, 0); final int activityCloseExitAnimation = a.getResourceId(1, 0); a.recycle(); activity.overridePendingTransition(activityCloseEnterAnimation, activityCloseExitAnimation); } public static void resetCheatSheet(ActionMenuView menuView) { final OnLongClickListener listener = new OnLongClickListener() { @Override public boolean onLongClick(View v) { if (((ActionMenuItemView) v).hasText()) return false; final MenuItem menuItem = ((ActionMenuItemView) v).getItemData(); Utils.showMenuItemToast(v, menuItem.getTitle(), true); return true; } }; for (int i = 0, j = menuView.getChildCount(); i < j; i++) { final View child = menuView.getChildAt(i); if (!(child instanceof ActionMenuItemView)) continue; final ActionMenuItemView itemView = (ActionMenuItemView) child; final MenuItem menuItem = itemView.getItemData(); if (menuItem.hasSubMenu()) continue; itemView.setOnLongClickListener(listener); } } public static void setActionBarOverflowColor(Toolbar toolbar, int itemColor) { if (toolbar == null) return; if (toolbar instanceof TwidereToolbar) { ((TwidereToolbar) toolbar).setItemColor(itemColor); } final ActionMenuView actionMenuView = ViewSupport.findViewByType(toolbar, ActionMenuView.class); if (actionMenuView == null) return; View overflowView = null; for (int i = 0, j = actionMenuView.getChildCount(); i < j; i++) { final View child = actionMenuView.getChildAt(i); final ActionMenuView.LayoutParams lp = (ActionMenuView.LayoutParams) child.getLayoutParams(); if (lp.isOverflowButton) { overflowView = child; break; } } if (!(overflowView instanceof ImageView)) return; ((ImageView) overflowView).setColorFilter(itemColor, Mode.SRC_ATOP); } public static void setActionBarMenuItemsColor(Toolbar toolbar, int itemColor) { if (toolbar == null) return; if (toolbar instanceof TwidereToolbar) { ((TwidereToolbar) toolbar).setItemColor(itemColor); } final ActionMenuView actionMenuView = ViewSupport.findViewByType(toolbar, ActionMenuView.class); if (actionMenuView == null) return; for (int i = 0, j = actionMenuView.getChildCount(); i < j; i++) { final View child = actionMenuView.getChildAt(i); if (child instanceof ActionMenuView.ActionMenuChildView && child instanceof ImageView) { ((ImageView) child).setColorFilter(itemColor, Mode.SRC_ATOP); } } } public static void setActionBarOverflowColor(ActionMenuPresenter presenter, int itemColor) { if (presenter == null) return; final View view = (View) Utils.findFieldOfTypes(presenter, ActionMenuPresenter.class, ActionMenuView.ActionMenuChildView.class, View.class); if (!(view instanceof ImageView)) return; ((ImageView) view).setColorFilter(itemColor, Mode.SRC_ATOP); } public static void setActionBarOverflowColor(ActionMenuView actionMenuView, int itemColor) { if (actionMenuView == null) return; View overflowView = null; for (int i = 0, j = actionMenuView.getChildCount(); i < j; i++) { final View child = actionMenuView.getChildAt(i); final ActionMenuView.LayoutParams lp = (ActionMenuView.LayoutParams) child.getLayoutParams(); if (lp.isOverflowButton) { overflowView = child; break; } } if (!(overflowView instanceof ImageView)) return; final Drawable drawable = ((ImageView) overflowView).getDrawable(); if (drawable == null) return; drawable.setColorFilter(itemColor, Mode.SRC_ATOP); } public static void setCompatToolbarOverlay(Activity activity, Drawable overlay) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return; final Window window = activity.getWindow(); final View view = window.findViewById(android.support.v7.appcompat.R.id.decor_content_parent); if (!(view instanceof ActionBarOverlayLayout)) return; try { final Field field = ActionBarOverlayLayout.class.getDeclaredField("mWindowContentOverlay"); field.setAccessible(true); field.set(view, overlay); } catch (Exception ignore) { } } public static void setCompatContentViewOverlay(Activity activity, Drawable overlay) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return; final Window window = activity.getWindow(); View contentLayout = window.findViewById(android.support.v7.appcompat.R.id.action_bar_activity_content); if (contentLayout == null) { contentLayout = window.findViewById(android.R.id.content); } if (contentLayout instanceof ContentFrameLayout) { ((ContentFrameLayout) contentLayout).setForeground(overlay); } } public static void setWindowOverlayViewOverlay(Activity activity, Drawable overlay) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return; final Window window = activity.getWindow(); final View windowOverlay = window.findViewById(R.id.window_overlay); if (windowOverlay == null) { return; } ViewSupport.setBackground(windowOverlay, overlay); } public static void setupDrawerBackground(Context context, View view) { if (!(context instanceof IThemedActivity)) return; final int themeRes = ((IThemedActivity) context).getCurrentThemeResourceId(); final int drawerThemeRes = getDrawerThemeResource(themeRes); final String backgroundOption = ((IThemedActivity) context).getThemeBackgroundOption(); final int alpha = ((IThemedActivity) context).getCurrentThemeBackgroundAlpha(); final Drawable d; if (isSolidBackground(backgroundOption)) { d = new ColorDrawable(Color.BLACK); } else { d = getWindowBackgroundFromTheme(context, drawerThemeRes); } if (d == null) throw new NullPointerException(); if (isTransparentBackground(backgroundOption)) { d.setAlpha(alpha); } ViewSupport.setBackground(view, d); } public static void wrapMenuIcon(@NonNull Menu menu, int itemColor, int subItemColor, int... excludeGroups) { for (int i = 0, j = menu.size(); i < j; i++) { final MenuItem item = menu.getItem(i); wrapMenuItemIcon(item, itemColor, excludeGroups); if (item.hasSubMenu()) { wrapMenuIcon(item.getSubMenu(), subItemColor, subItemColor, excludeGroups); } } } public static void wrapMenuIcon(ActionMenuView view, int... excludeGroups) { final Resources resources = view.getResources(); final int colorDark = resources.getColor(R.color.action_icon_dark); final int colorLight = resources.getColor(R.color.action_icon_light); wrapMenuIcon(view, colorDark, colorLight, excludeGroups); } public static void wrapMenuIcon(ActionMenuView view, int colorDark, int colorLight, int... excludeGroups) { final int itemBackgroundColor = ThemeUtils.getThemeBackgroundColor(view.getContext()); final int popupItemBackgroundColor = ThemeUtils.getThemeBackgroundColor(view.getContext(), view.getPopupTheme()); final int itemColor = TwidereColorUtils.getContrastYIQ(itemBackgroundColor, colorDark, colorLight); final int popupItemColor = TwidereColorUtils.getContrastYIQ(popupItemBackgroundColor, colorDark, colorLight); final Menu menu = view.getMenu(); final int childCount = view.getChildCount(); for (int i = 0, j = menu.size(), k = 0; i < j; i++) { final MenuItem item = menu.getItem(i); wrapMenuItemIcon(item, itemColor, excludeGroups); if (item.hasSubMenu()) { wrapMenuIcon(item.getSubMenu(), popupItemColor, popupItemColor, excludeGroups); } if (item.isVisible()) { k++; } } } public static void wrapMenuItemIcon(@NonNull MenuItem item, int itemColor, int... excludeGroups) { if (ArrayUtils.contains(excludeGroups, item.getGroupId())) return; final Drawable icon = item.getIcon(); if (icon == null) return; if (icon instanceof ActionIconDrawable) { ((ActionIconDrawable) icon).setDefaultColor(itemColor); item.setIcon(icon); return; } icon.mutate(); final Drawable.Callback callback = icon.getCallback(); final ActionIconDrawable newIcon = new ActionIconDrawable(icon, itemColor); newIcon.setCallback(callback); item.setIcon(newIcon); } public static void wrapToolbarMenuIcon(@Nullable ActionMenuView view, int itemColor, int popupItemColor, int... excludeGroups) { if (view == null) return; final Menu menu = view.getMenu(); final int childCount = view.getChildCount(); for (int i = 0, j = menu.size(), k = 0; i < j; i++) { final MenuItem item = menu.getItem(i); wrapMenuItemIcon(item, itemColor, excludeGroups); if (item.hasSubMenu()) { wrapMenuIcon(item.getSubMenu(), popupItemColor, popupItemColor, excludeGroups); } if (item.isVisible()) { k++; } } } @NonNull private static SharedPreferencesWrapper getSharedPreferencesWrapper(Context context) { final Context appContext = context.getApplicationContext(); return SharedPreferencesWrapper.getInstance(appContext, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); } public static int getActionBarHeight(Context context) { final TypedValue tv = new TypedValue(); final Resources.Theme theme = context.getTheme(); final int attr = R.attr.actionBarSize; if (theme.resolveAttribute(attr, tv, true)) { return TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics()); } return 0; } public static Context getActionBarThemedContext(Context base) { final TypedValue outValue = new TypedValue(); final Resources.Theme baseTheme = base.getTheme(); baseTheme.resolveAttribute(android.support.v7.appcompat.R.attr.actionBarTheme, outValue, true); if (outValue.resourceId != 0) { final Resources.Theme actionBarTheme = base.getResources().newTheme(); actionBarTheme.setTo(baseTheme); actionBarTheme.applyStyle(outValue.resourceId, true); final ActionBarContextThemeWrapper actionBarContext = new ActionBarContextThemeWrapper(base, outValue.resourceId); actionBarContext.getTheme().setTo(actionBarTheme); return actionBarContext; } else { return base; } } public static Context getActionBarThemedContext(Context base, int themeId, int accentColor) { final int actionBarThemeId; if (isDarkTheme(themeId) || TwidereColorUtils.getYIQLuminance(accentColor) <= ACCENT_COLOR_THRESHOLD) { actionBarThemeId = R.style.Theme_Twidere_Dark; } else { actionBarThemeId = R.style.Theme_Twidere_Light; } final Resources.Theme baseTheme = base.getTheme(); final Resources.Theme actionBarTheme = base.getResources().newTheme(); actionBarTheme.setTo(baseTheme); actionBarTheme.applyStyle(actionBarThemeId, true); final ActionBarContextThemeWrapper actionBarContext = new ActionBarContextThemeWrapper(base, actionBarThemeId); actionBarContext.getTheme().setTo(actionBarTheme); return actionBarContext; } public static int getActionBarColor(Context context, int themeColor, int themeResId, String backgroundOption) { if (!isDarkTheme(themeResId)) { return themeColor; } else if (isSolidBackground(backgroundOption)) { return Color.BLACK; } return context.getResources().getColor(R.color.background_color_action_bar_dark); } public static boolean isDarkTheme(final String name) { return VALUE_THEME_NAME_DARK.equals(name); } public static final class ActionBarContextThemeWrapper extends android.support.v7.internal.view.ContextThemeWrapper { public ActionBarContextThemeWrapper(Context base, int themeres) { super(base, themeres); } } public static int getActionBarThemeResource(int themeId, int accentColor) { if (isDarkTheme(themeId) || TwidereColorUtils.getYIQLuminance(accentColor) <= ACCENT_COLOR_THRESHOLD) { return R.style.Theme_Twidere_Dark; } else { return R.style.Theme_Twidere_Light; } } }