/* * Nextcloud Android client application * * @author Tobias Kaminsky * @author Andy Scherzinger * Copyright (C) 2017 Tobias Kaminsky * Copyright (C) 2017 Andy Scherzinger * Copyright (C) 2017 Nextcloud GmbH. * * 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 . */ 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.support.annotation.ColorInt; import android.support.annotation.DrawableRes; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; import android.support.v4.content.res.ResourcesCompat; import android.support.v4.graphics.ColorUtils; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.widget.CompoundButtonCompat; import android.support.v7.app.ActionBar; import android.support.v7.widget.AppCompatCheckBox; import android.support.v7.widget.SwitchCompat; import android.text.Html; import android.text.Spanned; import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.SeekBar; 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.resources.status.OCCapability; import com.owncloud.android.ui.activity.ToolbarActivity; /** * Utility class with methods for client side theming. */ public class ThemeUtils { 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(null, context); } public static int primaryColor(Account account, Context context) { OCCapability capability = getCapability(account, context); try { return Color.parseColor(capability.getServerColor()); } 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[2] > 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 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[2] <= 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(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("" + title + "")); } } } public static Spanned getColoredTitle(String title, int color) { String colorHex = colorToHexString(color); return Html.fromHtml("" + title + ""); } /** * 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(ActionBar actionBar, int titleId, Context context) { 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("" + title + "")); } } public static String getDefaultDisplayNameForRootFolder(Context context) { OCCapability capability = getCapability(context); 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 * @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[2] += lightnessDelta; } else { hsl[2] = Math.min(hsl[2] + 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); } } /** * sets the coloring of the given progress bar to color_accent. * * @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 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.white)); } /** * 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { fragmentActivity.getWindow().setStatusBarColor(color); } } /** * 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); } } 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); } 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; } else { return drawable; } } 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; if (acc != null) { account = acc; } else { account = AccountUtils.getCurrentOwnCloudAccount(context); } if (account != null) { FileDataStorageManager storageManager = new FileDataStorageManager(account, context.getContentResolver()); return storageManager.getCapability(account.name); } else { return new OCCapability(); } } }