diff --git a/app/src/main/java/io/legado/app/lib/theme/ATH.java b/app/src/main/java/io/legado/app/lib/theme/ATH.java new file mode 100644 index 000000000..d7ee194d1 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ATH.java @@ -0,0 +1,117 @@ +package io.legado.app.lib.theme; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.ColorStateList; +import android.os.Build; +import android.view.View; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class ATH { + + @SuppressLint("CommitPrefEdits") + public static boolean didThemeValuesChange(@NonNull Context context, long since) { + return ThemeStore.isConfigured(context) && ThemeStore.prefs(context).getLong(ThemeStore.VALUES_CHANGED, -1) > since; + } + + public static void setStatusbarColorAuto(Activity activity) { + setStatusbarColor(activity, ThemeStore.statusBarColor(activity)); + } + + public static void setStatusbarColor(Activity activity, int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().setStatusBarColor(color); + setLightStatusbarAuto(activity, color); + } + } + + public static void setLightStatusbarAuto(Activity activity, int bgColor) { + setLightStatusbar(activity, ColorUtil.isColorLight(bgColor)); + } + + public static void setLightStatusbar(Activity activity, boolean enabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + final View decorView = activity.getWindow().getDecorView(); + final int systemUiVisibility = decorView.getSystemUiVisibility(); + if (enabled) { + decorView.setSystemUiVisibility(systemUiVisibility | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else { + decorView.setSystemUiVisibility(systemUiVisibility & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + } + } + + public static void setLightNavigationbar(Activity activity, boolean enabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final View decorView = activity.getWindow().getDecorView(); + int systemUiVisibility = decorView.getSystemUiVisibility(); + if (enabled) { + systemUiVisibility |= SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } else { + systemUiVisibility &= ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } + decorView.setSystemUiVisibility(systemUiVisibility); + } + } + + public static void setLightNavigationbarAuto(Activity activity, int bgColor) { + setLightNavigationbar(activity, ColorUtil.isColorLight(bgColor)); + } + + public static void setNavigationbarColorAuto(Activity activity) { + setNavigationbarColor(activity, ThemeStore.navigationBarColor(activity)); + } + + public static void setNavigationbarColor(Activity activity, int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().setNavigationBarColor(color); + setLightNavigationbarAuto(activity, color); + } + } + + public static void setTaskDescriptionColorAuto(@NonNull Activity activity) { + setTaskDescriptionColor(activity, ThemeStore.primaryColor(activity)); + } + + public static void setTaskDescriptionColor(@NonNull Activity activity, @ColorInt int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // Task description requires fully opaque color + color = ColorUtil.stripAlpha(color); + // Sets color of entry in the system recents page + activity.setTaskDescription(new ActivityManager.TaskDescription((String) activity.getTitle(), null, color)); + } + } + + public static void setTint(@NonNull View view, @ColorInt int color) { + TintHelper.setTintAuto(view, color, false); + } + + public static void setBackgroundTint(@NonNull View view, @ColorInt int color) { + TintHelper.setTintAuto(view, color, true); + } + + public static void setAlertDialogTint(@NonNull AlertDialog dialog) { + ColorStateList colorStateList = Selector.colorBuild() + .setDefaultColor(ThemeStore.accentColor(dialog.getContext())) + .setPressedColor(ColorUtil.darkenColor(ThemeStore.accentColor(dialog.getContext()))) + .create(); + if (dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE) != null) { + dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE).setTextColor(colorStateList); + } + if (dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) != null) { + dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setTextColor(colorStateList); + } + } + + private ATH() { + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/ATHUtil.java b/app/src/main/java/io/legado/app/lib/theme/ATHUtil.java new file mode 100644 index 000000000..eb84da3e5 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ATHUtil.java @@ -0,0 +1,31 @@ +package io.legado.app.lib.theme; + +import android.content.Context; +import android.content.res.TypedArray; +import androidx.annotation.AttrRes; + +/** + * @author Aidan Follestad (afollestad) + */ +public final class ATHUtil { + + public static boolean isWindowBackgroundDark(Context context) { + return !ColorUtil.isColorLight(ATHUtil.resolveColor(context, android.R.attr.windowBackground)); + } + + public static int resolveColor(Context context, @AttrRes int attr) { + return resolveColor(context, attr, 0); + } + + public static int resolveColor(Context context, @AttrRes int attr, int fallback) { + TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr}); + try { + return a.getColor(0, fallback); + } finally { + a.recycle(); + } + } + + private ATHUtil() { + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/ColorUtil.java b/app/src/main/java/io/legado/app/lib/theme/ColorUtil.java new file mode 100644 index 000000000..4fc255d07 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ColorUtil.java @@ -0,0 +1,80 @@ +package io.legado.app.lib.theme; + +import android.graphics.Color; +import androidx.annotation.ColorInt; +import androidx.annotation.FloatRange; + +@SuppressWarnings({"unused", "WeakerAccess"}) +public class ColorUtil { + + public static String intToString(int intColor) { + return String.format("#%06X", 0xFFFFFF & intColor); + } + + + public static int stripAlpha(@ColorInt int color) { + return 0xff000000 | color; + } + + @ColorInt + public static int shiftColor(@ColorInt int color, @FloatRange(from = 0.0f, to = 2.0f) float by) { + if (by == 1f) return color; + int alpha = Color.alpha(color); + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + hsv[2] *= by; // value component + return (alpha << 24) + (0x00ffffff & Color.HSVToColor(hsv)); + } + + @ColorInt + public static int darkenColor(@ColorInt int color) { + return shiftColor(color, 0.9f); + } + + @ColorInt + public static int lightenColor(@ColorInt int color) { + return shiftColor(color, 1.1f); + } + + public static boolean isColorLight(@ColorInt int color) { + final double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255; + return darkness < 0.4; + } + + @ColorInt + public static int invertColor(@ColorInt int color) { + final int r = 255 - Color.red(color); + final int g = 255 - Color.green(color); + final int b = 255 - Color.blue(color); + return Color.argb(Color.alpha(color), r, g, b); + } + + @ColorInt + public static int adjustAlpha(@ColorInt int color, @FloatRange(from = 0.0, to = 1.0) float factor) { + int alpha = Math.round(Color.alpha(color) * factor); + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + return Color.argb(alpha, red, green, blue); + } + + @ColorInt + public static int withAlpha(@ColorInt int baseColor, @FloatRange(from = 0.0, to = 1.0) float alpha) { + int a = Math.min(255, Math.max(0, (int) (alpha * 255))) << 24; + int rgb = 0x00ffffff & baseColor; + return a + rgb; + } + + /** + * Taken from CollapsingToolbarLayout's CollapsingTextHelper class. + */ + public static int blendColors(int color1, int color2, @FloatRange(from = 0.0, to = 1.0) float ratio) { + final float inverseRatio = 1f - ratio; + float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio); + float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio); + float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio); + float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio); + return Color.argb((int) a, (int) r, (int) g, (int) b); + } + +} diff --git a/app/src/main/java/io/legado/app/lib/theme/DrawableUtil.java b/app/src/main/java/io/legado/app/lib/theme/DrawableUtil.java new file mode 100644 index 000000000..2254798f5 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/DrawableUtil.java @@ -0,0 +1,28 @@ +package io.legado.app.lib.theme; + +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import androidx.annotation.ColorInt; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class DrawableUtil { + + public static TransitionDrawable createTransitionDrawable(@ColorInt int startColor, @ColorInt int endColor) { + return createTransitionDrawable(new ColorDrawable(startColor), new ColorDrawable(endColor)); + } + + public static TransitionDrawable createTransitionDrawable(Drawable start, Drawable end) { + final Drawable[] drawables = new Drawable[2]; + + drawables[0] = start; + drawables[1] = end; + + return new TransitionDrawable(drawables); + } + + private DrawableUtil() { + } +} diff --git a/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.java b/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.java new file mode 100644 index 000000000..51f9031b4 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.java @@ -0,0 +1,52 @@ +package io.legado.app.lib.theme; + +import android.annotation.SuppressLint; +import android.content.Context; +import androidx.annotation.ColorInt; +import androidx.core.content.ContextCompat; +import io.legado.app.R; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class MaterialValueHelper { + + @SuppressLint("PrivateResource") + @ColorInt + public static int getPrimaryTextColor(final Context context, boolean dark) { + if (dark) { + return ContextCompat.getColor(context, R.color.primary_text_default_material_light); + } + return ContextCompat.getColor(context, R.color.primary_text_default_material_dark); + } + + @SuppressLint("PrivateResource") + @ColorInt + public static int getSecondaryTextColor(final Context context, boolean dark) { + if (dark) { + return ContextCompat.getColor(context, R.color.secondary_text_default_material_light); + } + return ContextCompat.getColor(context, R.color.secondary_text_default_material_dark); + } + + @SuppressLint("PrivateResource") + @ColorInt + public static int getPrimaryDisabledTextColor(final Context context, boolean dark) { + if (dark) { + return ContextCompat.getColor(context, R.color.primary_text_disabled_material_light); + } + return ContextCompat.getColor(context, R.color.primary_text_disabled_material_dark); + } + + @SuppressLint("PrivateResource") + @ColorInt + public static int getSecondaryDisabledTextColor(final Context context, boolean dark) { + if (dark) { + return ContextCompat.getColor(context, R.color.secondary_text_disabled_material_light); + } + return ContextCompat.getColor(context, R.color.secondary_text_disabled_material_dark); + } + + private MaterialValueHelper() { + } +} diff --git a/app/src/main/java/io/legado/app/lib/theme/NavigationViewUtil.java b/app/src/main/java/io/legado/app/lib/theme/NavigationViewUtil.java new file mode 100644 index 000000000..0707cf132 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/NavigationViewUtil.java @@ -0,0 +1,55 @@ +package io.legado.app.lib.theme; + +import android.content.res.ColorStateList; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import com.google.android.material.internal.NavigationMenuView; +import com.google.android.material.navigation.NavigationView; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class NavigationViewUtil { + + public static void setItemIconColors(@NonNull NavigationView navigationView, @ColorInt int normalColor, @ColorInt int selectedColor) { + final ColorStateList iconSl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_checked}, + new int[]{android.R.attr.state_checked} + }, + new int[]{ + normalColor, + selectedColor + }); + navigationView.setItemIconTintList(iconSl); + } + + public static void setItemTextColors(@NonNull NavigationView navigationView, @ColorInt int normalColor, @ColorInt int selectedColor) { + final ColorStateList textSl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_checked}, + new int[]{android.R.attr.state_checked} + }, + new int[]{ + normalColor, + selectedColor + }); + navigationView.setItemTextColor(textSl); + } + + /** + * 去掉navigationView的滚动条 + * @param navigationView NavigationView + */ + public static void disableScrollbar(NavigationView navigationView) { + if (navigationView != null) { + NavigationMenuView navigationMenuView = (NavigationMenuView) navigationView.getChildAt(0); + if (navigationMenuView != null) { + navigationMenuView.setVerticalScrollBarEnabled(false); + } + } + } + + private NavigationViewUtil() { + } +} diff --git a/app/src/main/java/io/legado/app/lib/theme/Selector.java b/app/src/main/java/io/legado/app/lib/theme/Selector.java new file mode 100644 index 000000000..df4e44b59 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/Selector.java @@ -0,0 +1,430 @@ +package io.legado.app.lib.theme; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.StateListDrawable; +import androidx.annotation.ColorInt; +import androidx.annotation.Dimension; +import androidx.annotation.DrawableRes; +import androidx.annotation.IntDef; +import androidx.core.content.ContextCompat; + +public class Selector { + public static ShapeSelector shapeBuild() { + return new ShapeSelector(); + } + + public static ColorSelector colorBuild() { + return new ColorSelector(); + } + + public static DrawableSelector drawableBuild() { + return new DrawableSelector(); + } + + /** + * 形状ShapeSelector + * + * @author hjy + * created at 2017/12/11 22:26 + */ + public static final class ShapeSelector { + @IntDef({GradientDrawable.RECTANGLE, GradientDrawable.OVAL, + GradientDrawable.LINE, GradientDrawable.RING}) + private @interface Shape { + } + + private int mShape; //the shape of background + private int mDefaultBgColor; //default background color + private int mDisabledBgColor; //state_enabled = false + private int mPressedBgColor; //state_pressed = true + private int mSelectedBgColor; //state_selected = true + private int mFocusedBgColor; //state_focused = true + private int mCheckedBgColor; //state_checked = true + private int mStrokeWidth; //stroke width in pixel + private int mDefaultStrokeColor; //default stroke color + private int mDisabledStrokeColor; //state_enabled = false + private int mPressedStrokeColor; //state_pressed = true + private int mSelectedStrokeColor; //state_selected = true + private int mFocusedStrokeColor; //state_focused = true + private int mCheckedStrokeColor; //state_checked = true + private int mCornerRadius; //corner radius + + private boolean hasSetDisabledBgColor = false; + private boolean hasSetPressedBgColor = false; + private boolean hasSetSelectedBgColor = false; + private boolean hasSetFocusedBgColor = false; + private boolean hasSetCheckedBgColor = false; + + private boolean hasSetDisabledStrokeColor = false; + private boolean hasSetPressedStrokeColor = false; + private boolean hasSetSelectedStrokeColor = false; + private boolean hasSetFocusedStrokeColor = false; + private boolean hasSetCheckedStrokeColor = false; + + public ShapeSelector() { + //initialize default values + mShape = GradientDrawable.RECTANGLE; + mDefaultBgColor = Color.TRANSPARENT; + mDisabledBgColor = Color.TRANSPARENT; + mPressedBgColor = Color.TRANSPARENT; + mSelectedBgColor = Color.TRANSPARENT; + mFocusedBgColor = Color.TRANSPARENT; + mStrokeWidth = 0; + mDefaultStrokeColor = Color.TRANSPARENT; + mDisabledStrokeColor = Color.TRANSPARENT; + mPressedStrokeColor = Color.TRANSPARENT; + mSelectedStrokeColor = Color.TRANSPARENT; + mFocusedStrokeColor = Color.TRANSPARENT; + mCornerRadius = 0; + } + + public ShapeSelector setShape(@Shape int shape) { + mShape = shape; + return this; + } + + public ShapeSelector setDefaultBgColor(@ColorInt int color) { + mDefaultBgColor = color; + if (!hasSetDisabledBgColor) + mDisabledBgColor = color; + if (!hasSetPressedBgColor) + mPressedBgColor = color; + if (!hasSetSelectedBgColor) + mSelectedBgColor = color; + if (!hasSetFocusedBgColor) + mFocusedBgColor = color; + return this; + } + + public ShapeSelector setDisabledBgColor(@ColorInt int color) { + mDisabledBgColor = color; + hasSetDisabledBgColor = true; + return this; + } + + public ShapeSelector setPressedBgColor(@ColorInt int color) { + mPressedBgColor = color; + hasSetPressedBgColor = true; + return this; + } + + public ShapeSelector setSelectedBgColor(@ColorInt int color) { + mSelectedBgColor = color; + hasSetSelectedBgColor = true; + return this; + } + + public ShapeSelector setFocusedBgColor(@ColorInt int color) { + mFocusedBgColor = color; + hasSetPressedBgColor = true; + return this; + } + + public ShapeSelector setCheckedBgColor(@ColorInt int color) { + mCheckedBgColor = color; + hasSetCheckedBgColor = true; + return this; + } + + public ShapeSelector setStrokeWidth(@Dimension int width) { + mStrokeWidth = width; + return this; + } + + public ShapeSelector setDefaultStrokeColor(@ColorInt int color) { + mDefaultStrokeColor = color; + if (!hasSetDisabledStrokeColor) + mDisabledStrokeColor = color; + if (!hasSetPressedStrokeColor) + mPressedStrokeColor = color; + if (!hasSetSelectedStrokeColor) + mSelectedStrokeColor = color; + if (!hasSetFocusedStrokeColor) + mFocusedStrokeColor = color; + return this; + } + + public ShapeSelector setDisabledStrokeColor(@ColorInt int color) { + mDisabledStrokeColor = color; + hasSetDisabledStrokeColor = true; + return this; + } + + public ShapeSelector setPressedStrokeColor(@ColorInt int color) { + mPressedStrokeColor = color; + hasSetPressedStrokeColor = true; + return this; + } + + public ShapeSelector setSelectedStrokeColor(@ColorInt int color) { + mSelectedStrokeColor = color; + hasSetSelectedStrokeColor = true; + return this; + } + + public ShapeSelector setCheckedStrokeColor(@ColorInt int color) { + mCheckedStrokeColor = color; + hasSetCheckedStrokeColor = true; + return this; + } + + public ShapeSelector setFocusedStrokeColor(@ColorInt int color) { + mFocusedStrokeColor = color; + hasSetFocusedStrokeColor = true; + return this; + } + + public ShapeSelector setCornerRadius(@Dimension int radius) { + mCornerRadius = radius; + return this; + } + + public StateListDrawable create() { + StateListDrawable selector = new StateListDrawable(); + + //enabled = false + if (hasSetDisabledBgColor || hasSetDisabledStrokeColor) { + GradientDrawable disabledShape = getItemShape(mShape, mCornerRadius, + mDisabledBgColor, mStrokeWidth, mDisabledStrokeColor); + selector.addState(new int[]{-android.R.attr.state_enabled}, disabledShape); + } + + //pressed = true + if (hasSetPressedBgColor || hasSetPressedStrokeColor) { + GradientDrawable pressedShape = getItemShape(mShape, mCornerRadius, + mPressedBgColor, mStrokeWidth, mPressedStrokeColor); + selector.addState(new int[]{android.R.attr.state_pressed}, pressedShape); + } + + //selected = true + if (hasSetSelectedBgColor || hasSetSelectedStrokeColor) { + GradientDrawable selectedShape = getItemShape(mShape, mCornerRadius, + mSelectedBgColor, mStrokeWidth, mSelectedStrokeColor); + selector.addState(new int[]{android.R.attr.state_selected}, selectedShape); + } + + //focused = true + if (hasSetFocusedBgColor || hasSetFocusedStrokeColor) { + GradientDrawable focusedShape = getItemShape(mShape, mCornerRadius, + mFocusedBgColor, mStrokeWidth, mFocusedStrokeColor); + selector.addState(new int[]{android.R.attr.state_focused}, focusedShape); + } + + //checked = true + if (hasSetCheckedBgColor || hasSetCheckedStrokeColor) { + GradientDrawable checkedShape = getItemShape(mShape, mCornerRadius, + mCheckedBgColor, mStrokeWidth, mCheckedStrokeColor); + selector.addState(new int[]{android.R.attr.state_checked}, checkedShape); + } + + //default + GradientDrawable defaultShape = getItemShape(mShape, mCornerRadius, + mDefaultBgColor, mStrokeWidth, mDefaultStrokeColor); + selector.addState(new int[]{}, defaultShape); + + return selector; + } + + private GradientDrawable getItemShape(int shape, int cornerRadius, + int solidColor, int strokeWidth, int strokeColor) { + GradientDrawable drawable = new GradientDrawable(); + drawable.setShape(shape); + drawable.setStroke(strokeWidth, strokeColor); + drawable.setCornerRadius(cornerRadius); + drawable.setColor(solidColor); + return drawable; + } + } + + /** + * 资源DrawableSelector + * + * @author hjy + * created at 2017/12/11 22:34 + */ + public static final class DrawableSelector { + + private Drawable mDefaultDrawable; + private Drawable mDisabledDrawable; + private Drawable mPressedDrawable; + private Drawable mSelectedDrawable; + private Drawable mFocusedDrawable; + + private boolean hasSetDisabledDrawable = false; + private boolean hasSetPressedDrawable = false; + private boolean hasSetSelectedDrawable = false; + private boolean hasSetFocusedDrawable = false; + + private DrawableSelector() { + mDefaultDrawable = new ColorDrawable(Color.TRANSPARENT); + } + + public DrawableSelector setDefaultDrawable(Drawable drawable) { + mDefaultDrawable = drawable; + if (!hasSetDisabledDrawable) + mDisabledDrawable = drawable; + if (!hasSetPressedDrawable) + mPressedDrawable = drawable; + if (!hasSetSelectedDrawable) + mSelectedDrawable = drawable; + if (!hasSetFocusedDrawable) + mFocusedDrawable = drawable; + return this; + } + + public DrawableSelector setDisabledDrawable(Drawable drawable) { + mDisabledDrawable = drawable; + hasSetDisabledDrawable = true; + return this; + } + + public DrawableSelector setPressedDrawable(Drawable drawable) { + mPressedDrawable = drawable; + hasSetPressedDrawable = true; + return this; + } + + public DrawableSelector setSelectedDrawable(Drawable drawable) { + mSelectedDrawable = drawable; + hasSetSelectedDrawable = true; + return this; + } + + public DrawableSelector setFocusedDrawable(Drawable drawable) { + mFocusedDrawable = drawable; + hasSetFocusedDrawable = true; + return this; + } + + public StateListDrawable create() { + StateListDrawable selector = new StateListDrawable(); + if (hasSetDisabledDrawable) + selector.addState(new int[]{-android.R.attr.state_enabled}, mDisabledDrawable); + if (hasSetPressedDrawable) + selector.addState(new int[]{android.R.attr.state_pressed}, mPressedDrawable); + if (hasSetSelectedDrawable) + selector.addState(new int[]{android.R.attr.state_selected}, mSelectedDrawable); + if (hasSetFocusedDrawable) + selector.addState(new int[]{android.R.attr.state_focused}, mFocusedDrawable); + selector.addState(new int[]{}, mDefaultDrawable); + return selector; + } + + public DrawableSelector setDefaultDrawable(Context context, @DrawableRes int drawableRes) { + return setDefaultDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + + public DrawableSelector setDisabledDrawable(Context context, @DrawableRes int drawableRes) { + return setDisabledDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + + public DrawableSelector setPressedDrawable(Context context, @DrawableRes int drawableRes) { + return setPressedDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + + public DrawableSelector setSelectedDrawable(Context context, @DrawableRes int drawableRes) { + return setSelectedDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + + public DrawableSelector setFocusedDrawable(Context context, @DrawableRes int drawableRes) { + return setFocusedDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + } + + /** + * 颜色ColorSelector + * + * @author hjy + * created at 2017/12/11 22:26 + */ + public static final class ColorSelector { + + private int mDefaultColor; + private int mDisabledColor; + private int mPressedColor; + private int mSelectedColor; + private int mFocusedColor; + private int mCheckedColor; + + private boolean hasSetDisabledColor = false; + private boolean hasSetPressedColor = false; + private boolean hasSetSelectedColor = false; + private boolean hasSetFocusedColor = false; + private boolean hasSetCheckedColor = false; + + private ColorSelector() { + mDefaultColor = Color.BLACK; + mDisabledColor = Color.GRAY; + mPressedColor = Color.BLACK; + mSelectedColor = Color.BLACK; + mFocusedColor = Color.BLACK; + } + + public ColorSelector setDefaultColor(@ColorInt int color) { + mDefaultColor = color; + if (!hasSetDisabledColor) + mDisabledColor = color; + if (!hasSetPressedColor) + mPressedColor = color; + if (!hasSetSelectedColor) + mSelectedColor = color; + if (!hasSetFocusedColor) + mFocusedColor = color; + return this; + } + + public ColorSelector setDisabledColor(@ColorInt int color) { + mDisabledColor = color; + hasSetDisabledColor = true; + return this; + } + + public ColorSelector setPressedColor(@ColorInt int color) { + mPressedColor = color; + hasSetPressedColor = true; + return this; + } + + public ColorSelector setSelectedColor(@ColorInt int color) { + mSelectedColor = color; + hasSetSelectedColor = true; + return this; + } + + public ColorSelector setFocusedColor(@ColorInt int color) { + mFocusedColor = color; + hasSetFocusedColor = true; + return this; + } + + public ColorSelector setCheckedColor(@ColorInt int color) { + mCheckedColor = color; + hasSetCheckedColor = true; + return this; + } + + public ColorStateList create() { + int[] colors = new int[]{ + hasSetDisabledColor ? mDisabledColor : mDefaultColor, + hasSetPressedColor ? mPressedColor : mDefaultColor, + hasSetSelectedColor ? mSelectedColor : mDefaultColor, + hasSetFocusedColor ? mFocusedColor : mDefaultColor, + hasSetCheckedColor ? mCheckedColor : mDefaultColor, + mDefaultColor + }; + int[][] states = new int[6][]; + states[0] = new int[]{-android.R.attr.state_enabled}; + states[1] = new int[]{android.R.attr.state_pressed}; + states[2] = new int[]{android.R.attr.state_selected}; + states[3] = new int[]{android.R.attr.state_focused}; + states[4] = new int[]{android.R.attr.state_checked}; + states[5] = new int[]{}; + return new ColorStateList(states, colors); + } + } +} diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.java b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.java new file mode 100644 index 000000000..52e4544ea --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.java @@ -0,0 +1,318 @@ +package io.legado.app.lib.theme; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Color; +import androidx.annotation.*; +import androidx.core.content.ContextCompat; +import io.legado.app.R; + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +public final class ThemeStore implements ThemeStorePrefKeys, ThemeStoreInterface { + + private final Context mContext; + private final SharedPreferences.Editor mEditor; + + public static ThemeStore editTheme(@NonNull Context context) { + return new ThemeStore(context); + } + + @SuppressLint("CommitPrefEdits") + private ThemeStore(@NonNull Context context) { + mContext = context; + mEditor = prefs(context).edit(); + } + + + @Override + public ThemeStore primaryColor(@ColorInt int color) { + mEditor.putInt(KEY_PRIMARY_COLOR, color); + if (autoGeneratePrimaryDark(mContext)) + primaryColorDark(ColorUtil.darkenColor(color)); + return this; + } + + @Override + public ThemeStore primaryColorRes(@ColorRes int colorRes) { + return primaryColor(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore primaryColorAttr(@AttrRes int colorAttr) { + return primaryColor(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore primaryColorDark(@ColorInt int color) { + mEditor.putInt(KEY_PRIMARY_COLOR_DARK, color); + return this; + } + + @Override + public ThemeStore primaryColorDarkRes(@ColorRes int colorRes) { + return primaryColorDark(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore primaryColorDarkAttr(@AttrRes int colorAttr) { + return primaryColorDark(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore accentColor(@ColorInt int color) { + mEditor.putInt(KEY_ACCENT_COLOR, color); + return this; + } + + @Override + public ThemeStore accentColorRes(@ColorRes int colorRes) { + return accentColor(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore accentColorAttr(@AttrRes int colorAttr) { + return accentColor(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore statusBarColor(@ColorInt int color) { + mEditor.putInt(KEY_STATUS_BAR_COLOR, color); + return this; + } + + @Override + public ThemeStore statusBarColorRes(@ColorRes int colorRes) { + return statusBarColor(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore statusBarColorAttr(@AttrRes int colorAttr) { + return statusBarColor(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore navigationBarColor(@ColorInt int color) { + mEditor.putInt(KEY_NAVIGATION_BAR_COLOR, color); + return this; + } + + @Override + public ThemeStore navigationBarColorRes(@ColorRes int colorRes) { + return navigationBarColor(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore navigationBarColorAttr(@AttrRes int colorAttr) { + return navigationBarColor(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore textColorPrimary(@ColorInt int color) { + mEditor.putInt(KEY_TEXT_COLOR_PRIMARY, color); + return this; + } + + @Override + public ThemeStore textColorPrimaryRes(@ColorRes int colorRes) { + return textColorPrimary(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore textColorPrimaryAttr(@AttrRes int colorAttr) { + return textColorPrimary(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore textColorPrimaryInverse(@ColorInt int color) { + mEditor.putInt(KEY_TEXT_COLOR_PRIMARY_INVERSE, color); + return this; + } + + @Override + public ThemeStore textColorPrimaryInverseRes(@ColorRes int colorRes) { + return textColorPrimaryInverse(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore textColorPrimaryInverseAttr(@AttrRes int colorAttr) { + return textColorPrimaryInverse(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore textColorSecondary(@ColorInt int color) { + mEditor.putInt(KEY_TEXT_COLOR_SECONDARY, color); + return this; + } + + @Override + public ThemeStore textColorSecondaryRes(@ColorRes int colorRes) { + return textColorSecondary(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore textColorSecondaryAttr(@AttrRes int colorAttr) { + return textColorSecondary(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore textColorSecondaryInverse(@ColorInt int color) { + mEditor.putInt(KEY_TEXT_COLOR_SECONDARY_INVERSE, color); + return this; + } + + @Override + public ThemeStore textColorSecondaryInverseRes(@ColorRes int colorRes) { + return textColorSecondaryInverse(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore textColorSecondaryInverseAttr(@AttrRes int colorAttr) { + return textColorSecondaryInverse(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore backgroundColor(int color) { + mEditor.putInt(KEY_BACKGROUND_COLOR, color); + return this; + } + + @Override + public ThemeStore coloredStatusBar(boolean colored) { + mEditor.putBoolean(KEY_APPLY_PRIMARYDARK_STATUSBAR, colored); + return this; + } + + @Override + public ThemeStore coloredNavigationBar(boolean applyToNavBar) { + mEditor.putBoolean(KEY_APPLY_PRIMARY_NAVBAR, applyToNavBar); + return this; + } + + @Override + public ThemeStore autoGeneratePrimaryDark(boolean autoGenerate) { + mEditor.putBoolean(KEY_AUTO_GENERATE_PRIMARYDARK, autoGenerate); + return this; + } + + // Commit method + + @SuppressWarnings("unchecked") + @Override + public void apply() { + mEditor.putLong(VALUES_CHANGED, System.currentTimeMillis()) + .putBoolean(IS_CONFIGURED_KEY, true) + .apply(); + } + + // Static getters + + @CheckResult + @NonNull + protected static SharedPreferences prefs(@NonNull Context context) { + return context.getSharedPreferences(CONFIG_PREFS_KEY_DEFAULT, Context.MODE_PRIVATE); + } + + public static void markChanged(@NonNull Context context) { + new ThemeStore(context).apply(); + } + + @CheckResult + @ColorInt + public static int primaryColor(@NonNull Context context) { + return prefs(context).getInt(KEY_PRIMARY_COLOR, ATHUtil.resolveColor(context, R.attr.colorPrimary, Color.parseColor("#455A64"))); + } + + @CheckResult + @ColorInt + public static int primaryColorDark(@NonNull Context context) { + return prefs(context).getInt(KEY_PRIMARY_COLOR_DARK, ATHUtil.resolveColor(context, R.attr.colorPrimaryDark, Color.parseColor("#37474F"))); + } + + @CheckResult + @ColorInt + public static int accentColor(@NonNull Context context) { + return prefs(context).getInt(KEY_ACCENT_COLOR, ATHUtil.resolveColor(context, R.attr.colorAccent, Color.parseColor("#263238"))); + } + + @CheckResult + @ColorInt + public static int statusBarColor(@NonNull Context context) { + if (!coloredStatusBar(context)) { + return Color.BLACK; + } + return prefs(context).getInt(KEY_STATUS_BAR_COLOR, primaryColorDark(context)); + } + + @CheckResult + @ColorInt + public static int navigationBarColor(@NonNull Context context) { + if (!coloredNavigationBar(context)) { + return Color.BLACK; + } + return prefs(context).getInt(KEY_NAVIGATION_BAR_COLOR, primaryColor(context)); + } + + @CheckResult + @ColorInt + public static int textColorPrimary(@NonNull Context context) { + return prefs(context).getInt(KEY_TEXT_COLOR_PRIMARY, ATHUtil.resolveColor(context, android.R.attr.textColorPrimary)); + } + + @CheckResult + @ColorInt + public static int textColorPrimaryInverse(@NonNull Context context) { + return prefs(context).getInt(KEY_TEXT_COLOR_PRIMARY_INVERSE, ATHUtil.resolveColor(context, android.R.attr.textColorPrimaryInverse)); + } + + @CheckResult + @ColorInt + public static int textColorSecondary(@NonNull Context context) { + return prefs(context).getInt(KEY_TEXT_COLOR_SECONDARY, ATHUtil.resolveColor(context, android.R.attr.textColorSecondary)); + } + + @CheckResult + @ColorInt + public static int textColorSecondaryInverse(@NonNull Context context) { + return prefs(context).getInt(KEY_TEXT_COLOR_SECONDARY_INVERSE, ATHUtil.resolveColor(context, android.R.attr.textColorSecondaryInverse)); + } + + @CheckResult + @ColorInt + public static int backgroundColor(@NonNull Context context) { + return prefs(context).getInt(KEY_BACKGROUND_COLOR, ATHUtil.resolveColor(context, android.R.attr.colorBackground)); + } + + @CheckResult + public static boolean coloredStatusBar(@NonNull Context context) { + return prefs(context).getBoolean(KEY_APPLY_PRIMARYDARK_STATUSBAR, true); + } + + @CheckResult + public static boolean coloredNavigationBar(@NonNull Context context) { + return prefs(context).getBoolean(KEY_APPLY_PRIMARY_NAVBAR, false); + } + + @CheckResult + public static boolean autoGeneratePrimaryDark(@NonNull Context context) { + return prefs(context).getBoolean(KEY_AUTO_GENERATE_PRIMARYDARK, true); + } + + @CheckResult + public static boolean isConfigured(Context context) { + return prefs(context).getBoolean(IS_CONFIGURED_KEY, false); + } + + @SuppressLint("CommitPrefEdits") + public static boolean isConfigured(Context context, @IntRange(from = 0, to = Integer.MAX_VALUE) int version) { + final SharedPreferences prefs = prefs(context); + final int lastVersion = prefs.getInt(IS_CONFIGURED_VERSION_KEY, -1); + if (version > lastVersion) { + prefs.edit().putInt(IS_CONFIGURED_VERSION_KEY, version).apply(); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.java b/app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.java new file mode 100644 index 000000000..4c3214396 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.java @@ -0,0 +1,92 @@ +package io.legado.app.lib.theme; + + +import androidx.annotation.AttrRes; +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +interface ThemeStoreInterface { + + // Primary colors + + ThemeStore primaryColor(@ColorInt int color); + + ThemeStore primaryColorRes(@ColorRes int colorRes); + + ThemeStore primaryColorAttr(@AttrRes int colorAttr); + + ThemeStore autoGeneratePrimaryDark(boolean autoGenerate); + + ThemeStore primaryColorDark(@ColorInt int color); + + ThemeStore primaryColorDarkRes(@ColorRes int colorRes); + + ThemeStore primaryColorDarkAttr(@AttrRes int colorAttr); + + // Accent colors + + ThemeStore accentColor(@ColorInt int color); + + ThemeStore accentColorRes(@ColorRes int colorRes); + + ThemeStore accentColorAttr(@AttrRes int colorAttr); + + // Status bar color + + ThemeStore statusBarColor(@ColorInt int color); + + ThemeStore statusBarColorRes(@ColorRes int colorRes); + + ThemeStore statusBarColorAttr(@AttrRes int colorAttr); + + // Navigation bar color + + ThemeStore navigationBarColor(@ColorInt int color); + + ThemeStore navigationBarColorRes(@ColorRes int colorRes); + + ThemeStore navigationBarColorAttr(@AttrRes int colorAttr); + + // Primary text color + + ThemeStore textColorPrimary(@ColorInt int color); + + ThemeStore textColorPrimaryRes(@ColorRes int colorRes); + + ThemeStore textColorPrimaryAttr(@AttrRes int colorAttr); + + ThemeStore textColorPrimaryInverse(@ColorInt int color); + + ThemeStore textColorPrimaryInverseRes(@ColorRes int colorRes); + + ThemeStore textColorPrimaryInverseAttr(@AttrRes int colorAttr); + + // Secondary text color + + ThemeStore textColorSecondary(@ColorInt int color); + + ThemeStore textColorSecondaryRes(@ColorRes int colorRes); + + ThemeStore textColorSecondaryAttr(@AttrRes int colorAttr); + + ThemeStore textColorSecondaryInverse(@ColorInt int color); + + ThemeStore textColorSecondaryInverseRes(@ColorRes int colorRes); + + ThemeStore textColorSecondaryInverseAttr(@AttrRes int colorAttr); + + ThemeStore backgroundColor(@ColorInt int color); + + // Toggle configurations + + ThemeStore coloredStatusBar(boolean colored); + + ThemeStore coloredNavigationBar(boolean applyToNavBar); + + // Commit/apply + + void apply(); +} diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.java b/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.java new file mode 100644 index 000000000..9a8286f9e --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.java @@ -0,0 +1,29 @@ +package io.legado.app.lib.theme; + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +interface ThemeStorePrefKeys { + + String CONFIG_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]"; + String IS_CONFIGURED_KEY = "is_configured"; + String IS_CONFIGURED_VERSION_KEY = "is_configured_version"; + String VALUES_CHANGED = "values_changed"; + + String KEY_PRIMARY_COLOR = "primary_color"; + String KEY_PRIMARY_COLOR_DARK = "primary_color_dark"; + String KEY_ACCENT_COLOR = "accent_color"; + String KEY_STATUS_BAR_COLOR = "status_bar_color"; + String KEY_NAVIGATION_BAR_COLOR = "navigation_bar_color"; + + String KEY_TEXT_COLOR_PRIMARY = "text_color_primary"; + String KEY_TEXT_COLOR_PRIMARY_INVERSE = "text_color_primary_inverse"; + String KEY_TEXT_COLOR_SECONDARY = "text_color_secondary"; + String KEY_TEXT_COLOR_SECONDARY_INVERSE = "text_color_secondary_inverse"; + + String KEY_BACKGROUND_COLOR = "backgroundColor"; + + String KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar"; + String KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar"; + String KEY_AUTO_GENERATE_PRIMARYDARK = "auto_generate_primarydark"; +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/TintHelper.java b/app/src/main/java/io/legado/app/lib/theme/TintHelper.java new file mode 100644 index 000000000..439190147 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/TintHelper.java @@ -0,0 +1,384 @@ +package io.legado.app.lib.theme; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.RippleDrawable; +import android.os.Build; +import android.view.View; +import android.widget.*; +import androidx.annotation.CheckResult; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatEditText; +import androidx.appcompat.widget.SearchView; +import androidx.appcompat.widget.SwitchCompat; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import io.legado.app.R; + +import java.lang.reflect.Field; + +/** + * @author afollestad, plusCubed + */ +public final class TintHelper { + + @SuppressLint("PrivateResource") + @ColorInt + private static int getDefaultRippleColor(@NonNull Context context, boolean useDarkRipple) { + // Light ripple is actually translucent black, and vice versa + return ContextCompat.getColor(context, useDarkRipple ? + R.color.ripple_material_light : R.color.ripple_material_dark); + } + + @NonNull + private static ColorStateList getDisabledColorStateList(@ColorInt int normal, @ColorInt int disabled) { + return new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled} + }, new int[]{ + disabled, + normal + }); + } + + @SuppressWarnings("deprecation") + public static void setTintSelector(@NonNull View view, @ColorInt final int color, final boolean darker, final boolean useDarkTheme) { + final boolean isColorLight = ColorUtil.isColorLight(color); + final int disabled = ContextCompat.getColor(view.getContext(), useDarkTheme ? R.color.ate_button_disabled_dark : R.color.ate_button_disabled_light); + final int pressed = ColorUtil.shiftColor(color, darker ? 0.9f : 1.1f); + final int activated = ColorUtil.shiftColor(color, darker ? 1.1f : 0.9f); + final int rippleColor = getDefaultRippleColor(view.getContext(), isColorLight); + final int textColor = ContextCompat.getColor(view.getContext(), isColorLight ? R.color.ate_primary_text_light : R.color.ate_primary_text_dark); + + final ColorStateList sl; + if (view instanceof Button) { + sl = getDisabledColorStateList(color, disabled); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + view.getBackground() instanceof RippleDrawable) { + RippleDrawable rd = (RippleDrawable) view.getBackground(); + rd.setColor(ColorStateList.valueOf(rippleColor)); + } + + // Disabled text color state for buttons, may get overridden later by ATE tags + final Button button = (Button) view; + button.setTextColor(getDisabledColorStateList(textColor, ContextCompat.getColor(view.getContext(), useDarkTheme ? R.color.ate_button_text_disabled_dark : R.color.ate_button_text_disabled_light))); + } else if (view instanceof FloatingActionButton) { + // FloatingActionButton doesn't support disabled state? + sl = new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_pressed}, + new int[]{android.R.attr.state_pressed} + }, new int[]{ + color, + pressed + }); + + final FloatingActionButton fab = (FloatingActionButton) view; + fab.setRippleColor(rippleColor); + fab.setBackgroundTintList(sl); + if (fab.getDrawable() != null) + fab.setImageDrawable(createTintedDrawable(fab.getDrawable(), textColor)); + return; + } else { + sl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_activated}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_checked} + }, + new int[]{ + disabled, + color, + pressed, + activated, + activated + } + ); + } + + Drawable drawable = view.getBackground(); + if (drawable != null) { + drawable = createTintedDrawable(drawable, sl); + ViewUtil.setBackgroundCompat(view, drawable); + } + + if (view instanceof TextView && !(view instanceof Button)) { + final TextView tv = (TextView) view; + tv.setTextColor(getDisabledColorStateList(textColor, ContextCompat.getColor(view.getContext(), isColorLight ? R.color.ate_text_disabled_light : R.color.ate_text_disabled_dark))); + } + } + + public static void setTintAuto(final @NonNull View view, final @ColorInt int color, + boolean background) { + setTintAuto(view, color, background, ATHUtil.isWindowBackgroundDark(view.getContext())); + } + + @SuppressWarnings("deprecation") + public static void setTintAuto(final @NonNull View view, final @ColorInt int color, + boolean background, final boolean isDark) { + if (!background) { + if (view instanceof RadioButton) + setTint((RadioButton) view, color, isDark); + else if (view instanceof SeekBar) + setTint((SeekBar) view, color, isDark); + else if (view instanceof ProgressBar) + setTint((ProgressBar) view, color); + else if (view instanceof AppCompatEditText) + setTint((AppCompatEditText) view, color, isDark); + else if (view instanceof CheckBox) + setTint((CheckBox) view, color, isDark); + else if (view instanceof ImageView) + setTint((ImageView) view, color); + else if (view instanceof Switch) + setTint((Switch) view, color, isDark); + else if (view instanceof SwitchCompat) + setTint((SwitchCompat) view, color, isDark); + else if (view instanceof SearchView) { + int iconIdS[] = new int[]{androidx.appcompat.R.id.search_button, androidx.appcompat.R.id.search_close_btn,}; + for (int iconId : iconIdS) { + ImageView icon = view.findViewById(iconId); + if (icon != null) { + setTint(icon, color); + } + } + + } else { + background = true; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + !background && view.getBackground() instanceof RippleDrawable) { + // Ripples for the above views (e.g. when you tap and hold a switch or checkbox) + RippleDrawable rd = (RippleDrawable) view.getBackground(); + @SuppressLint("PrivateResource") final int unchecked = ContextCompat.getColor(view.getContext(), + isDark ? R.color.ripple_material_dark : R.color.ripple_material_light); + final int checked = ColorUtil.adjustAlpha(color, 0.4f); + final ColorStateList sl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_activated, -android.R.attr.state_checked}, + new int[]{android.R.attr.state_activated}, + new int[]{android.R.attr.state_checked} + }, + new int[]{ + unchecked, + checked, + checked + } + ); + rd.setColor(sl); + } + } + if (background) { + // Need to tint the background of a view + if (view instanceof FloatingActionButton || view instanceof Button) { + setTintSelector(view, color, false, isDark); + } else if (view.getBackground() != null) { + Drawable drawable = view.getBackground(); + if (drawable != null) { + drawable = createTintedDrawable(drawable, color); + ViewUtil.setBackgroundCompat(view, drawable); + } + } + } + } + + public static void setTint(@NonNull RadioButton radioButton, @ColorInt int color, boolean useDarker) { + ColorStateList sl = new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, -android.R.attr.state_checked}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_checked} + }, new int[]{ + // Rdio button includes own alpha for disabled state + ColorUtil.stripAlpha(ContextCompat.getColor(radioButton.getContext(), useDarker ? R.color.ate_control_disabled_dark : R.color.ate_control_disabled_light)), + ContextCompat.getColor(radioButton.getContext(), useDarker ? R.color.ate_control_normal_dark : R.color.ate_control_normal_light), + color + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + radioButton.setButtonTintList(sl); + } else { + Drawable d = createTintedDrawable(ContextCompat.getDrawable(radioButton.getContext(), R.drawable.abc_btn_radio_material), sl); + radioButton.setButtonDrawable(d); + } + } + + public static void setTint(@NonNull SeekBar seekBar, @ColorInt int color, boolean useDarker) { + final ColorStateList s1 = getDisabledColorStateList(color, + ContextCompat.getColor(seekBar.getContext(), useDarker ? R.color.ate_control_disabled_dark : R.color.ate_control_disabled_light)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + seekBar.setThumbTintList(s1); + seekBar.setProgressTintList(s1); + } else { + Drawable progressDrawable = createTintedDrawable(seekBar.getProgressDrawable(), s1); + seekBar.setProgressDrawable(progressDrawable); + Drawable thumbDrawable = createTintedDrawable(seekBar.getThumb(), s1); + seekBar.setThumb(thumbDrawable); + } + } + + public static void setTint(@NonNull ProgressBar progressBar, @ColorInt int color) { + setTint(progressBar, color, false); + } + + public static void setTint(@NonNull ProgressBar progressBar, @ColorInt int color, boolean skipIndeterminate) { + ColorStateList sl = ColorStateList.valueOf(color); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + progressBar.setProgressTintList(sl); + progressBar.setSecondaryProgressTintList(sl); + if (!skipIndeterminate) + progressBar.setIndeterminateTintList(sl); + } else { + PorterDuff.Mode mode = PorterDuff.Mode.SRC_IN; + if (!skipIndeterminate && progressBar.getIndeterminateDrawable() != null) + progressBar.getIndeterminateDrawable().setColorFilter(color, mode); + if (progressBar.getProgressDrawable() != null) + progressBar.getProgressDrawable().setColorFilter(color, mode); + } + } + + + @SuppressLint("RestrictedApi") + public static void setTint(@NonNull AppCompatEditText editText, @ColorInt int color, boolean useDarker) { + final ColorStateList editTextColorStateList = new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, -android.R.attr.state_pressed, -android.R.attr.state_focused}, + new int[]{} + }, new int[]{ + ContextCompat.getColor(editText.getContext(), useDarker ? R.color.ate_text_disabled_dark : R.color.ate_text_disabled_light), + ContextCompat.getColor(editText.getContext(), useDarker ? R.color.ate_control_normal_dark : R.color.ate_control_normal_light), + color + }); + editText.setSupportBackgroundTintList(editTextColorStateList); + setCursorTint(editText, color); + } + + public static void setTint(@NonNull CheckBox box, @ColorInt int color, boolean useDarker) { + ColorStateList sl = new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, -android.R.attr.state_checked}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_checked} + }, new int[]{ + ContextCompat.getColor(box.getContext(), useDarker ? R.color.ate_control_disabled_dark : R.color.ate_control_disabled_light), + ContextCompat.getColor(box.getContext(), useDarker ? R.color.ate_control_normal_dark : R.color.ate_control_normal_light), + color + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + box.setButtonTintList(sl); + } else { + Drawable drawable = createTintedDrawable(ContextCompat.getDrawable(box.getContext(), R.drawable.abc_btn_check_material), sl); + box.setButtonDrawable(drawable); + } + } + + public static void setTint(@NonNull ImageView image, @ColorInt int color) { + image.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + } + + private static Drawable modifySwitchDrawable(@NonNull Context context, @NonNull Drawable from, @ColorInt int tint, boolean thumb, boolean compatSwitch, boolean useDarker) { + if (useDarker) { + tint = ColorUtil.shiftColor(tint, 1.1f); + } + tint = ColorUtil.adjustAlpha(tint, (compatSwitch && !thumb) ? 0.5f : 1.0f); + int disabled; + int normal; + if (thumb) { + disabled = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_thumb_disabled_dark : R.color.ate_switch_thumb_disabled_light); + normal = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_thumb_normal_dark : R.color.ate_switch_thumb_normal_light); + } else { + disabled = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_track_disabled_dark : R.color.ate_switch_track_disabled_light); + normal = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_track_normal_dark : R.color.ate_switch_track_normal_light); + } + + // Stock switch includes its own alpha + if (!compatSwitch) { + normal = ColorUtil.stripAlpha(normal); + } + + final ColorStateList sl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, -android.R.attr.state_activated, -android.R.attr.state_checked}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_activated}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_checked} + }, + new int[]{ + disabled, + normal, + tint, + tint + } + ); + return createTintedDrawable(from, sl); + } + + public static void setTint(@NonNull Switch switchView, @ColorInt int color, boolean useDarker) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return; + if (switchView.getTrackDrawable() != null) { + switchView.setTrackDrawable(modifySwitchDrawable(switchView.getContext(), + switchView.getTrackDrawable(), color, false, false, useDarker)); + } + if (switchView.getThumbDrawable() != null) { + switchView.setThumbDrawable(modifySwitchDrawable(switchView.getContext(), + switchView.getThumbDrawable(), color, true, false, useDarker)); + } + } + + public static void setTint(@NonNull SwitchCompat switchView, @ColorInt int color, boolean useDarker) { + if (switchView.getTrackDrawable() != null) { + switchView.setTrackDrawable(modifySwitchDrawable(switchView.getContext(), + switchView.getTrackDrawable(), color, false, true, useDarker)); + } + if (switchView.getThumbDrawable() != null) { + switchView.setThumbDrawable(modifySwitchDrawable(switchView.getContext(), + switchView.getThumbDrawable(), color, true, true, useDarker)); + } + } + + // This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise. + @CheckResult + @Nullable + public static Drawable createTintedDrawable(@Nullable Drawable drawable, @ColorInt int color) { + if (drawable == null) return null; + drawable = DrawableCompat.wrap(drawable.mutate()); + DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_IN); + DrawableCompat.setTint(drawable, color); + return drawable; + } + + // This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise. + @CheckResult + @Nullable + public static Drawable createTintedDrawable(@Nullable Drawable drawable, @NonNull ColorStateList sl) { + if (drawable == null) return null; + drawable = DrawableCompat.wrap(drawable.mutate()); + DrawableCompat.setTintList(drawable, sl); + return drawable; + } + + public static void setCursorTint(@NonNull EditText editText, @ColorInt int color) { + try { + Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes"); + fCursorDrawableRes.setAccessible(true); + int mCursorDrawableRes = fCursorDrawableRes.getInt(editText); + Field fEditor = TextView.class.getDeclaredField("mEditor"); + fEditor.setAccessible(true); + Object editor = fEditor.get(editText); + Class clazz = editor.getClass(); + Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable"); + fCursorDrawable.setAccessible(true); + Drawable[] drawables = new Drawable[2]; + drawables[0] = ContextCompat.getDrawable(editText.getContext(), mCursorDrawableRes); + drawables[0] = createTintedDrawable(drawables[0], color); + drawables[1] = ContextCompat.getDrawable(editText.getContext(), mCursorDrawableRes); + drawables[1] = createTintedDrawable(drawables[1], color); + fCursorDrawable.set(editor, drawables); + } catch (Exception ignored) { + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/ViewUtil.java b/app/src/main/java/io/legado/app/lib/theme/ViewUtil.java new file mode 100644 index 000000000..7fd0f65e3 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ViewUtil.java @@ -0,0 +1,48 @@ +package io.legado.app.lib.theme; + +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.view.View; +import android.view.ViewTreeObserver; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class ViewUtil { + + @SuppressWarnings("deprecation") + public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener) { + v.getViewTreeObserver().removeOnGlobalLayoutListener(listener); + } + + @SuppressWarnings("deprecation") + public static void setBackgroundCompat(@NonNull View view, @Nullable Drawable drawable) { + view.setBackground(drawable); + } + + public static TransitionDrawable setBackgroundTransition(@NonNull View view, @NonNull Drawable newDrawable) { + TransitionDrawable transition = DrawableUtil.createTransitionDrawable(view.getBackground(), newDrawable); + setBackgroundCompat(view, transition); + return transition; + } + + public static TransitionDrawable setBackgroundColorTransition(@NonNull View view, @ColorInt int newColor) { + final Drawable oldColor = view.getBackground(); + + Drawable start = oldColor != null ? oldColor : new ColorDrawable(view.getSolidColor()); + Drawable end = new ColorDrawable(newColor); + + TransitionDrawable transition = DrawableUtil.createTransitionDrawable(start, end); + + setBackgroundCompat(view, transition); + + return transition; + } + + private ViewUtil() { + } +} diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 000000000..d53b8cac2 --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,34 @@ + + + @color/md_grey_800 + #353535 + #282828 + + #69000000 + + @color/md_grey_800 + @color/md_grey_900 + @color/md_grey_100 + + #30ffffff + + #363636 + #804D4D4D + #80686868 + #88111111 + #66666666 + + #737373 + #565656 + + #ffffffff + #e5ffffff + #b3ffffff + #b7b7b7 + + + #303030 + + + #222222 + diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..c6d88fbc5 --- /dev/null +++ b/app/src/main/res/values-night/styles.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8194c2ece..43af45038 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,96 @@ #66666666 + #eb4333 + #439b53 + + #00000000 + + @color/md_grey_100 + #dedede + #fcfcfc + #00000000 + + #30000000 + + #d3321b + + #80ACACAC + #80858585 + #88000000 + + #737373 + #adadad + + #de000000 + #b2000000 + #8a000000 + #dfdfdf + #383838 + + + #efefef + + #23000000 + #EEEEEE + #aaaaaaaa + + + #FFD4D4D4 + + #64000000 + #f4f4f4 + + + #99343434 + #000000 + #ffffff + + + + + + #1F000000 + #1F000000 + + #43000000 + #43000000 + #43000000 + + #61000000 + + #8A000000 + #8A000000 + #8A000000 + + #DE000000 + + #FFFAFAFA + + #FFBDBDBD + + #E8E8E8 + + + + #1AFFFFFF + + #1F000000 + + #4DFFFFFF + #4DFFFFFF + #4DFFFFFF + #4DFFFFFF + + #B3FFFFFF + #B3FFFFFF + #B3FFFFFF + + #FFFFFFFF + + #FFBDBDBD + + #FF424242 + + #202020 diff --git a/app/src/main/res/values/colors_material_design.xml b/app/src/main/res/values/colors_material_design.xml new file mode 100644 index 000000000..a00ddac9e --- /dev/null +++ b/app/src/main/res/values/colors_material_design.xml @@ -0,0 +1,338 @@ + + + + + + + @color/md_grey_50 + + #1F000000 + + #61000000 + + #8A000000 + #8A000000 + + #DE000000 + + @color/md_grey_300 + @color/md_grey_100 + @color/md_white_1000 + @color/md_white_1000 + + + @color/md_grey_850 + + #1FFFFFFF + + #4DFFFFFF + + #B3FFFFFF + #B3FFFFFF + + #FFFFFFFF + + @color/md_black_1000 + @color/md_grey_900 + @color/md_grey_800 + @color/md_grey_800 + + + #FFEBEE + #FFCDD2 + #EF9A9A + #E57373 + #EF5350 + #F44336 + #E53935 + #D32F2F + #C62828 + #B71C1C + #FF8A80 + #FF5252 + #FF1744 + #D50000 + + + #FCE4EC + #F8BBD0 + #F48FB1 + #F06292 + #EC407A + #E91E63 + #D81B60 + #C2185B + #AD1457 + #880E4F + #FF80AB + #FF4081 + #F50057 + #C51162 + + + #F3E5F5 + #E1BEE7 + #CE93D8 + #BA68C8 + #AB47BC + #9C27B0 + #8E24AA + #7B1FA2 + #6A1B9A + #4A148C + #EA80FC + #E040FB + #D500F9 + #AA00FF + + + #EDE7F6 + #D1C4E9 + #B39DDB + #9575CD + #7E57C2 + #673AB7 + #5E35B1 + #512DA8 + #4527A0 + #311B92 + #B388FF + #7C4DFF + #651FFF + #6200EA + + + #E8EAF6 + #C5CAE9 + #9FA8DA + #7986CB + #5C6BC0 + #3F51B5 + #3949AB + #303F9F + #283593 + #1A237E + #8C9EFF + #536DFE + #3D5AFE + #304FFE + + + #E3F2FD + #BBDEFB + #90CAF9 + #64B5F6 + #42A5F5 + #2196F3 + #1E88E5 + #1976D2 + #1565C0 + #0D47A1 + #82B1FF + #448AFF + #2979FF + #2962FF + + + #E1F5FE + #B3E5FC + #81D4FA + #4FC3F7 + #29B6F6 + #03A9F4 + #039BE5 + #0288D1 + #0277BD + #01579B + #80D8FF + #40C4FF + #00B0FF + #0091EA + + + #E0F7FA + #B2EBF2 + #80DEEA + #4DD0E1 + #26C6DA + #00BCD4 + #00ACC1 + #0097A7 + #00838F + #006064 + #84FFFF + #18FFFF + #00E5FF + #00B8D4 + + + #E0F2F1 + #B2DFDB + #80CBC4 + #4DB6AC + #26A69A + #009688 + #00897B + #00796B + #00695C + #004D40 + #A7FFEB + #64FFDA + #1DE9B6 + #00BFA5 + + + #E8F5E9 + #C8E6C9 + #A5D6A7 + #81C784 + #66BB6A + #4CAF50 + #43A047 + #388E3C + #2E7D32 + #1B5E20 + #B9F6CA + #69F0AE + #00E676 + #00C853 + + + #F1F8E9 + #DCEDC8 + #C5E1A5 + #AED581 + #9CCC65 + #8BC34A + #7CB342 + #689F38 + #558B2F + #33691E + #CCFF90 + #B2FF59 + #76FF03 + #64DD17 + + + #F9FBE7 + #F0F4C3 + #E6EE9C + #DCE775 + #D4E157 + #CDDC39 + #C0CA33 + #AFB42B + #9E9D24 + #827717 + #F4FF81 + #EEFF41 + #C6FF00 + #AEEA00 + + + #FFFDE7 + #FFF9C4 + #FFF59D + #FFF176 + #FFEE58 + #FFEB3B + #FDD835 + #FBC02D + #F9A825 + #F57F17 + #FFFF8D + #FFFF00 + #FFEA00 + #FFD600 + + + #FFF8E1 + #FFECB3 + #FFE082 + #FFD54F + #FFCA28 + #FFC107 + #FFB300 + #FFA000 + #FF8F00 + #FF6F00 + #FFE57F + #FFD740 + #FFC400 + #FFAB00 + + + #FFF3E0 + #FFE0B2 + #FFCC80 + #FFB74D + #FFA726 + #FF9800 + #FB8C00 + #F57C00 + #EF6C00 + #E65100 + #FFD180 + #FFAB40 + #FF9100 + #FF6D00 + + + #FBE9E7 + #FFCCBC + #FFAB91 + #FF8A65 + #FF7043 + #FF5722 + #F4511E + #E64A19 + #D84315 + #BF360C + #FF9E80 + #FF6E40 + #FF3D00 + #DD2C00 + + + #EFEBE9 + #D7CCC8 + #BCAAA4 + #A1887F + #8D6E63 + #795548 + #6D4C41 + #5D4037 + #4E342E + #3E2723 + + + #FAFAFA + #F5F5F5 + #EEEEEE + #E0E0E0 + #BDBDBD + #9E9E9E + #757575 + #616161 + #424242 + #303030 + #212121 + + + #ECEFF1 + #CFD8DC + #B0BEC5 + #90A4AE + #78909C + #607D8B + #546E7A + #455A64 + #37474F + #263238 + + + #000000 + #FFFFFF + +