123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646 |
- /*
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * @author Andy Scherzinger
- * Copyright (C) 2017 Tobias Kaminsky
- * Copyright (C) 2017 Nextcloud GmbH
- * Copyright (C) 2018 Andy Scherzinger
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- package com.owncloud.android.utils;
- import android.accounts.Account;
- import android.app.Activity;
- import android.content.Context;
- import android.content.res.ColorStateList;
- import android.graphics.Color;
- import android.graphics.PorterDuff;
- import android.graphics.drawable.Drawable;
- import android.os.Build;
- import android.text.Html;
- import android.text.Spanned;
- import android.view.View;
- import android.view.Window;
- import android.widget.EditText;
- import android.widget.ImageButton;
- import android.widget.ProgressBar;
- import android.widget.SeekBar;
- import android.widget.TextView;
- import com.google.android.material.button.MaterialButton;
- import com.google.android.material.floatingactionbutton.FloatingActionButton;
- import com.google.android.material.snackbar.Snackbar;
- import com.google.android.material.textfield.TextInputLayout;
- import com.owncloud.android.MainApp;
- import com.owncloud.android.R;
- import com.owncloud.android.authentication.AccountUtils;
- import com.owncloud.android.datamodel.FileDataStorageManager;
- import com.owncloud.android.lib.common.utils.Log_OC;
- import com.owncloud.android.lib.resources.status.OCCapability;
- import com.owncloud.android.ui.activity.ToolbarActivity;
- import java.lang.reflect.Field;
- import androidx.annotation.ColorInt;
- import androidx.annotation.DrawableRes;
- import androidx.annotation.Nullable;
- import androidx.appcompat.app.ActionBar;
- import androidx.appcompat.widget.AppCompatCheckBox;
- import androidx.appcompat.widget.SearchView;
- import androidx.appcompat.widget.SwitchCompat;
- import androidx.core.content.ContextCompat;
- import androidx.core.content.res.ResourcesCompat;
- import androidx.core.graphics.ColorUtils;
- import androidx.core.graphics.drawable.DrawableCompat;
- import androidx.core.widget.CompoundButtonCompat;
- import androidx.fragment.app.FragmentActivity;
- import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
- /**
- * Utility class with methods for client side theming.
- */
- public final class ThemeUtils {
- private static final String TAG = ThemeUtils.class.getSimpleName();
- private static final int INDEX_LUMINATION = 2;
- private static final double MAX_LIGHTNESS = 0.92;
- private ThemeUtils() {
- // utility class -> private constructor
- }
- public static int primaryAccentColor(Context context) {
- OCCapability capability = getCapability(context);
- try {
- float adjust;
- if (darkTheme(context)) {
- adjust = +0.1f;
- } else {
- adjust = -0.1f;
- }
- return adjustLightness(adjust, Color.parseColor(capability.getServerColor()), 0.35f);
- } catch (Exception e) {
- return context.getResources().getColor(R.color.color_accent);
- }
- }
- public static int primaryDarkColor(Context context) {
- return primaryDarkColor(null, context);
- }
- public static int primaryDarkColor(Account account, Context context) {
- OCCapability capability = getCapability(account, context);
- try {
- return adjustLightness(-0.2f, Color.parseColor(capability.getServerColor()), -1f);
- } catch (Exception e) {
- return context.getResources().getColor(R.color.primary_dark);
- }
- }
- public static int primaryColor(Context context) {
- return primaryColor(context, false);
- }
- public static int primaryColor(Context context, boolean replaceWhite) {
- return primaryColor(null, replaceWhite, context);
- }
- public static int primaryColor(Account account, boolean replaceWhite, Context context) {
- OCCapability capability = getCapability(account, context);
- try {
- int color = Color.parseColor(capability.getServerColor());
- if (replaceWhite && Color.WHITE == color) {
- return Color.GRAY;
- } else {
- return color;
- }
- } catch (Exception e) {
- return context.getResources().getColor(R.color.primary);
- }
- }
- public static int elementColor(Context context) {
- return elementColor(null, context);
- }
- @NextcloudServer(max = 12)
- public static int elementColor(Account account, Context context) {
- OCCapability capability = getCapability(account, context);
- try {
- return Color.parseColor(capability.getServerElementColor());
- } catch (Exception e) {
- int primaryColor;
- try {
- primaryColor = Color.parseColor(capability.getServerColor());
- } catch (Exception e1) {
- primaryColor = context.getResources().getColor(R.color.primary);
- }
- float[] hsl = colorToHSL(primaryColor);
- if (hsl[INDEX_LUMINATION] > 0.8) {
- return context.getResources().getColor(R.color.elementFallbackColor);
- } else {
- return primaryColor;
- }
- }
- }
- public static boolean themingEnabled(Context context) {
- return getCapability(context).getServerColor() != null && !getCapability(context).getServerColor().isEmpty();
- }
- /**
- * @return int font color to use
- * adapted from https://github.com/nextcloud/server/blob/master/apps/theming/lib/Util.php#L90-L102
- */
- public static int fontColor(Context context) {
- try {
- return Color.parseColor(getCapability(context).getServerTextColor());
- } catch (Exception e) {
- if (darkTheme(context)) {
- return Color.WHITE;
- } else {
- return Color.BLACK;
- }
- }
- }
- /**
- * Tests if light color is set
- * @return true if primaryColor is lighter than MAX_LIGHTNESS
- */
- public static boolean lightTheme(Context context) {
- int primaryColor = primaryColor(context);
- float[] hsl = colorToHSL(primaryColor);
- return hsl[INDEX_LUMINATION] >= MAX_LIGHTNESS;
- }
- /**
- * Tests if dark color is set
- * @return true if dark theme -> e.g.use light font color, darker accent color
- */
- public static boolean darkTheme(Context context) {
- int primaryColor = primaryColor(context);
- float[] hsl = colorToHSL(primaryColor);
- return hsl[INDEX_LUMINATION] <= 0.55;
- }
- /**
- * Set color of title to white/black depending on background color
- *
- * @param actionBar actionBar to be used
- * @param title title to be shown
- */
- public static void setColoredTitle(@Nullable ActionBar actionBar, String title, Context context) {
- if (actionBar != null) {
- if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
- actionBar.setTitle(title);
- } else {
- String colorHex = colorToHexString(fontColor(context));
- actionBar.setTitle(Html.fromHtml("<font color='" + colorHex + "'>" + title + "</font>"));
- }
- }
- }
- public static Spanned getColoredTitle(String title, int color) {
- String colorHex = colorToHexString(color);
- return Html.fromHtml("<font color='" + colorHex + "'>" + title + "</font>");
- }
- /**
- * Set color of title to white/black depending on background color
- *
- * @param actionBar actionBar to be used
- * @param titleId title to be shown
- */
- public static void setColoredTitle(@Nullable ActionBar actionBar, int titleId, Context context) {
- if (actionBar != null) {
- if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
- actionBar.setTitle(titleId);
- } else {
- String colorHex = colorToHexString(fontColor(context));
- String title = context.getString(titleId);
- actionBar.setTitle(Html.fromHtml("<font color='" + colorHex + "'>" + title + "</font>"));
- }
- }
- }
- public static String getDefaultDisplayNameForRootFolder(Context context) {
- OCCapability capability = getCapability(context);
- if (MainApp.isOnlyOnDevice()) {
- return MainApp.getAppContext().getString(R.string.drawer_item_on_device);
- } else {
- if (capability.getServerName() == null || capability.getServerName().isEmpty()) {
- return MainApp.getAppContext().getResources().getString(R.string.default_display_name_for_root_folder);
- } else {
- return capability.getServerName();
- }
- }
- }
- public static void setStatusBarColor(Activity activity, @ColorInt int color) {
- if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- activity.getWindow().setStatusBarColor(color);
- }
- }
- /**
- * Adjust lightness of given color
- *
- * @param lightnessDelta values -1..+1
- * @param color original color
- * @param threshold 0..1 as maximum value, -1 to disable
- * @return color adjusted by lightness
- */
- public static int adjustLightness(float lightnessDelta, int color, float threshold) {
- float[] hsl = colorToHSL(color);
- if (threshold == -1f) {
- hsl[INDEX_LUMINATION] += lightnessDelta;
- } else {
- hsl[INDEX_LUMINATION] = Math.min(hsl[INDEX_LUMINATION] + lightnessDelta, threshold);
- }
- return ColorUtils.HSLToColor(hsl);
- }
- private static float[] colorToHSL(int color) {
- float[] hsl = new float[3];
- ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl);
- return hsl;
- }
- /**
- * sets the tinting of the given ImageButton's icon to color_accent.
- *
- * @param imageButton the image button who's icon should be colored
- */
- public static void colorImageButton(ImageButton imageButton, @ColorInt int color) {
- if (imageButton != null) {
- imageButton.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
- }
- }
- public static void colorEditText(EditText editText, int elementColor) {
- if (editText != null) {
- editText.setTextColor(elementColor);
- editText.getBackground().setColorFilter(elementColor, PorterDuff.Mode.SRC_ATOP);
- }
- }
- /**
- * sets the coloring of the given progress bar to given color.
- *
- * @param progressBar the progress bar to be colored
- * @param color the color to be used
- */
- public static void colorHorizontalProgressBar(ProgressBar progressBar, @ColorInt int color) {
- if (progressBar != null) {
- progressBar.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
- progressBar.getProgressDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
- }
- }
- /**
- * sets the coloring of the given progress bar's progress to given color.
- *
- * @param progressBar the progress bar to be colored
- * @param color the color to be used
- */
- public static void colorProgressBar(ProgressBar progressBar, @ColorInt int color) {
- if (progressBar != null) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- progressBar.setProgressTintList(ColorStateList.valueOf(color));
- } else {
- ThemeUtils.colorHorizontalProgressBar(progressBar, color);
- }
- }
- }
- /**
- * sets the coloring of the given seek bar to color_accent.
- *
- * @param seekBar the seek bar to be colored
- */
- public static void colorHorizontalSeekBar(SeekBar seekBar, Context context) {
- int color = ThemeUtils.primaryAccentColor(context);
- colorHorizontalProgressBar(seekBar, color);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- seekBar.getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN);
- }
- }
- /**
- * set the Nextcloud standard colors for the snackbar.
- *
- * @param context the context relevant for setting the color according to the context's theme
- * @param snackbar the snackbar to be colored
- */
- public static void colorSnackbar(Context context, Snackbar snackbar) {
- // Changing action button text color
- snackbar.setActionTextColor(ContextCompat.getColor(context, R.color.fg_inverse));
- }
- /**
- * Sets the color of the status bar to {@code color} on devices with OS version lollipop or higher.
- *
- * @param fragmentActivity fragment activity
- * @param color the color
- */
- public static void colorStatusBar(FragmentActivity fragmentActivity, @ColorInt int color) {
- Window window = fragmentActivity.getWindow();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && window != null) {
- window.setStatusBarColor(color);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- View decor = window.getDecorView();
- if (lightTheme(fragmentActivity.getApplicationContext())) {
- decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
- } else {
- decor.setSystemUiVisibility(0);
- }
- }
- }
- }
- /**
- * Sets the color of the progressbar to {@code color} within the given toolbar.
- *
- * @param activity the toolbar activity instance
- * @param progressBarColor the color to be used for the toolbar's progress bar
- */
- public static void colorToolbarProgressBar(FragmentActivity activity, int progressBarColor) {
- if (activity instanceof ToolbarActivity) {
- ((ToolbarActivity) activity).setProgressBarBackgroundColor(progressBarColor);
- }
- }
- /**
- * Sets the color of the TextInputLayout to {@code color} for hint text and box stroke.
- *
- * @param textInputLayout the TextInputLayout instance
- * @param color the color to be used for the hint text and box stroke
- */
- public static void colorTextInputLayout(TextInputLayout textInputLayout, int color) {
- textInputLayout.setBoxStrokeColor(color);
- textInputLayout.setDefaultHintTextColor(new ColorStateList(
- new int[][]{
- new int[]{-android.R.attr.state_focused},
- new int[]{android.R.attr.state_focused},
- },
- new int[]{
- Color.GRAY,
- color
- }
- ));
- }
- public static void themeDialogActionButton(MaterialButton button) {
- if (button == null ) {
- return;
- }
- Context context = button.getContext();
- int accentColor = ThemeUtils.primaryAccentColor(button.getContext());
- int disabledColor = ContextCompat.getColor(context, R.color.disabled_text);
- button.setTextColor(new ColorStateList(
- new int[][]{
- new int[] { android.R.attr.state_enabled}, // enabled
- new int[] {-android.R.attr.state_enabled}, // disabled
- },
- new int[]{
- accentColor,
- disabledColor
- }
- ));
- }
- public static void themeEditText(Context context, EditText editText, boolean themedBackground) {
- if (editText == null) { return; }
- int color = primaryColor(context);
- // Don't theme the view when it is already on a theme'd background
- if (themedBackground) {
- if (darkTheme(context)) {
- color = ContextCompat.getColor(context, R.color.themed_fg);
- } else {
- color = ContextCompat.getColor(context, R.color.themed_fg_inverse);
- }
- } else {
- if (lightTheme(context)) {
- color = ContextCompat.getColor(context, R.color.fg_default);
- }
- }
- editText.setHighlightColor(context.getResources().getColor(R.color.fg_contrast));
- setTextViewCursorColor(editText, color);
- setTextViewHandlesColor(context, editText, color);
- }
- public static void themeSearchView(Context context, SearchView searchView, boolean themedBackground) {
- if (searchView == null) { return; }
- SearchView.SearchAutoComplete editText = searchView.findViewById(R.id.search_src_text);
- themeEditText(context, editText, themedBackground);
- }
- public static void tintCheckbox(AppCompatCheckBox checkBox, int color) {
- CompoundButtonCompat.setButtonTintList(checkBox, new ColorStateList(
- new int[][]{
- new int[]{-android.R.attr.state_checked},
- new int[]{android.R.attr.state_checked},
- },
- new int[]{
- Color.GRAY,
- color
- }
- ));
- }
- public static void tintSwitch(SwitchCompat switchView, int color) {
- tintSwitch(switchView, color, false);
- }
- public static void tintSwitch(SwitchCompat switchView, int color, boolean colorText) {
- if (colorText) {
- switchView.setTextColor(color);
- }
- int trackColor = Color.argb(77, Color.red(color), Color.green(color), Color.blue(color));
- // setting the thumb color
- DrawableCompat.setTintList(switchView.getThumbDrawable(), new ColorStateList(
- new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
- new int[]{color, Color.WHITE}));
- // setting the track color
- DrawableCompat.setTintList(switchView.getTrackDrawable(), new ColorStateList(
- new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
- new int[]{trackColor, Color.parseColor("#4D000000")}));
- }
- public static Drawable tintDrawable(@DrawableRes int id, int color) {
- Drawable drawable = ResourcesCompat.getDrawable(MainApp.getAppContext().getResources(), id, null);
- return tintDrawable(drawable, color);
- }
- @Nullable
- public static Drawable tintDrawable(Drawable drawable, int color) {
- if (drawable != null) {
- Drawable wrap = DrawableCompat.wrap(drawable);
- wrap.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
- return wrap;
- }
- return null;
- }
- public static String colorToHexString(int color) {
- return String.format("#%06X", 0xFFFFFF & color);
- }
- public static void tintFloatingActionButton(FloatingActionButton button, @DrawableRes int
- drawable, Context context) {
- button.setBackgroundTintList(ColorStateList.valueOf(ThemeUtils.primaryColor(context)));
- button.setRippleColor(ThemeUtils.primaryDarkColor(context));
- button.setImageDrawable(ThemeUtils.tintDrawable(drawable, ThemeUtils.fontColor(context)));
- }
- private static OCCapability getCapability(Context context) {
- return getCapability(null, context);
- }
- private static OCCapability getCapability(Account acc, Context context) {
- Account account = null;
- if (acc != null) {
- account = acc;
- } else if (context != null) {
- account = AccountUtils.getCurrentOwnCloudAccount(context);
- }
- if (account != null) {
- FileDataStorageManager storageManager = new FileDataStorageManager(account, context.getContentResolver());
- return storageManager.getCapability(account.name);
- } else {
- return new OCCapability();
- }
- }
- /**
- * Lifted from SO.
- * FindBugs surpressed because of lack of public API to alter the cursor color.
- *
- * @param view TextView to be styled
- * @param color The desired cursor colour
- * @see <a href="https://stackoverflow.com/questions/25996032/how-to-change-programmatically-edittext-cursor-color-in-android#26543290">StackOverflow url</a>
- */
- @SuppressFBWarnings
- private static void setTextViewCursorColor(EditText view, @ColorInt int color) {
- try {
- // Get the cursor resource id
- Field field = TextView.class.getDeclaredField("mCursorDrawableRes");
- field.setAccessible(true);
- int drawableResId = field.getInt(view);
- // Get the editor
- field = TextView.class.getDeclaredField("mEditor");
- field.setAccessible(true);
- Object editor = field.get(view);
- // Get the drawable and set a color filter
- Drawable drawable = ContextCompat.getDrawable(view.getContext(), drawableResId);
- drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
- Drawable[] drawables = {drawable, drawable};
- // Set the drawables
- field = editor.getClass().getDeclaredField("mCursorDrawable");
- field.setAccessible(true);
- field.set(editor, drawables);
- } catch (Exception e) {
- Log_OC.e(TAG, "setTextViewCursorColor", e);
- }
- }
- /**
- * Set the color of the handles when you select text in a
- * {@link android.widget.EditText} or other view that extends {@link TextView}.
- * FindBugs surpressed because of lack of public API to alter the {@link TextView} handles color.
- *
- * @param view
- * The {@link TextView} or a {@link View} that extends {@link TextView}.
- * @param color
- * The color to set for the text handles
- *
- * @see <a href="https://gist.github.com/jaredrummler/2317620559d10ac39b8218a1152ec9d4">External reference</a>
- */
- @SuppressFBWarnings
- private static void setTextViewHandlesColor(Context context, TextView view, int color) {
- try {
- Field editorField = TextView.class.getDeclaredField("mEditor");
- if (!editorField.isAccessible()) {
- editorField.setAccessible(true);
- }
- Object editor = editorField.get(view);
- Class<?> editorClass = editor.getClass();
- String[] handleNames = {"mSelectHandleLeft", "mSelectHandleRight", "mSelectHandleCenter"};
- String[] resNames = {"mTextSelectHandleLeftRes", "mTextSelectHandleRightRes", "mTextSelectHandleRes"};
- for (int i = 0; i < handleNames.length; i++) {
- Field handleField = editorClass.getDeclaredField(handleNames[i]);
- if (!handleField.isAccessible()) {
- handleField.setAccessible(true);
- }
- Drawable handleDrawable = (Drawable) handleField.get(editor);
- if (handleDrawable == null) {
- Field resField = TextView.class.getDeclaredField(resNames[i]);
- if (!resField.isAccessible()) {
- resField.setAccessible(true);
- }
- int resId = resField.getInt(view);
- handleDrawable = ContextCompat.getDrawable(context, resId);
- }
- if (handleDrawable != null) {
- Drawable drawable = handleDrawable.mutate();
- drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
- handleField.set(editor, drawable);
- }
- }
- } catch (Exception e) {
- Log_OC.e(TAG, "Error setting TextView handles color", e);
- }
- }
- }
|