ThemeUtils.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Tobias Kaminsky
  5. * @author Andy Scherzinger
  6. * Copyright (C) 2017 Tobias Kaminsky
  7. * Copyright (C) 2017 Nextcloud GmbH
  8. * Copyright (C) 2018 Andy Scherzinger
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. */
  23. package com.owncloud.android.utils;
  24. import android.accounts.Account;
  25. import android.app.Activity;
  26. import android.content.Context;
  27. import android.content.res.ColorStateList;
  28. import android.content.res.Configuration;
  29. import android.graphics.Color;
  30. import android.graphics.PorterDuff;
  31. import android.graphics.drawable.Drawable;
  32. import android.os.Build;
  33. import android.text.Spannable;
  34. import android.text.SpannableString;
  35. import android.text.Spanned;
  36. import android.text.style.ForegroundColorSpan;
  37. import android.view.MenuItem;
  38. import android.view.View;
  39. import android.view.Window;
  40. import android.widget.Button;
  41. import android.widget.EditText;
  42. import android.widget.ImageButton;
  43. import android.widget.ImageView;
  44. import android.widget.ProgressBar;
  45. import android.widget.SeekBar;
  46. import android.widget.TextView;
  47. import com.google.android.material.button.MaterialButton;
  48. import com.google.android.material.floatingactionbutton.FloatingActionButton;
  49. import com.google.android.material.snackbar.Snackbar;
  50. import com.google.android.material.textfield.TextInputLayout;
  51. import com.nextcloud.client.account.UserAccountManagerImpl;
  52. import com.owncloud.android.MainApp;
  53. import com.owncloud.android.R;
  54. import com.owncloud.android.datamodel.FileDataStorageManager;
  55. import com.owncloud.android.lib.common.utils.Log_OC;
  56. import com.owncloud.android.lib.resources.status.OCCapability;
  57. import java.lang.reflect.Field;
  58. import androidx.annotation.ColorInt;
  59. import androidx.annotation.DrawableRes;
  60. import androidx.annotation.Nullable;
  61. import androidx.appcompat.app.ActionBar;
  62. import androidx.appcompat.app.AppCompatDelegate;
  63. import androidx.appcompat.widget.AppCompatCheckBox;
  64. import androidx.appcompat.widget.SearchView;
  65. import androidx.appcompat.widget.SwitchCompat;
  66. import androidx.core.content.ContextCompat;
  67. import androidx.core.content.res.ResourcesCompat;
  68. import androidx.core.graphics.ColorUtils;
  69. import androidx.core.graphics.drawable.DrawableCompat;
  70. import androidx.core.widget.CompoundButtonCompat;
  71. import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
  72. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  73. /**
  74. * Utility class with methods for client side theming.
  75. */
  76. public final class ThemeUtils {
  77. private static final String TAG = ThemeUtils.class.getSimpleName();
  78. private static final int INDEX_LUMINATION = 2;
  79. private static final double MAX_LIGHTNESS = 0.92;
  80. public static final double LUMINATION_THRESHOLD = 0.8;
  81. private ThemeUtils() {
  82. // utility class -> private constructor
  83. }
  84. public static int primaryAccentColor(Context context) {
  85. OCCapability capability = getCapability(context);
  86. try {
  87. float adjust;
  88. if (darkTheme(context)) {
  89. if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
  90. adjust = +0.5f;
  91. // return adjustLightness(adjust, Color.parseColor(capability.getServerColor()), -1);
  92. } else {
  93. adjust = +0.1f;
  94. }
  95. } else {
  96. adjust = -0.1f;
  97. }
  98. return adjustLightness(adjust, Color.parseColor(capability.getServerColor()), 0.35f);
  99. } catch (Exception e) {
  100. return context.getResources().getColor(R.color.color_accent);
  101. }
  102. }
  103. public static int primaryDarkColor(Context context) {
  104. return primaryDarkColor(null, context);
  105. }
  106. public static int primaryDarkColor(Account account, Context context) {
  107. OCCapability capability = getCapability(account, context);
  108. try {
  109. return adjustLightness(-0.2f, Color.parseColor(capability.getServerColor()), -1f);
  110. } catch (Exception e) {
  111. return context.getResources().getColor(R.color.primary_dark);
  112. }
  113. }
  114. public static int primaryColor(Context context) {
  115. return primaryColor(context, false);
  116. }
  117. public static int primaryColor(Context context, boolean replaceEdgeColors) {
  118. return primaryColor(null, replaceEdgeColors, context);
  119. }
  120. public static int primaryColor(Account account, boolean replaceEdgeColors, Context context) {
  121. return primaryColor(account, replaceEdgeColors, false, context);
  122. }
  123. /**
  124. * return the primary color defined in the server-side theming respecting Android dark/light theming and edge case
  125. * scenarios including drawer menu.
  126. *
  127. * @param account the Nextcloud user
  128. * @param replaceEdgeColors flag if edge case color scenarios should be handled
  129. * @param replaceEdgeColorsByInvertedColor flag in edge case handling should be done via color inversion
  130. * (black/white)
  131. * @param context the context (needed to load client-side colors)
  132. * @return the color
  133. */
  134. public static int primaryColor(Account account,
  135. boolean replaceEdgeColors,
  136. boolean replaceEdgeColorsByInvertedColor,
  137. Context context) {
  138. if (context == null) {
  139. return Color.GRAY;
  140. }
  141. try {
  142. int color = Color.parseColor(getCapability(account, context).getServerColor());
  143. if (replaceEdgeColors) {
  144. if (isDarkModeActive(context)) {
  145. if (Color.BLACK == color) {
  146. if (replaceEdgeColorsByInvertedColor) {
  147. return Color.WHITE;
  148. } else {
  149. return getNeutralGrey(context);
  150. }
  151. } else {
  152. return color;
  153. }
  154. } else {
  155. if (Color.WHITE == color) {
  156. if (replaceEdgeColorsByInvertedColor) {
  157. return Color.BLACK;
  158. } else {
  159. return getNeutralGrey(context);
  160. }
  161. } else {
  162. return color;
  163. }
  164. }
  165. } else {
  166. return color;
  167. }
  168. } catch (Exception e) {
  169. return context.getResources().getColor(R.color.primary);
  170. }
  171. }
  172. public static int getNeutralGrey(Context context) {
  173. return darkTheme(context) ? context.getResources().getColor(R.color.fg_contrast) : Color.GRAY;
  174. }
  175. public static int elementColor(Context context) {
  176. return elementColor(null, context, false);
  177. }
  178. @NextcloudServer(max = 12)
  179. public static int elementColor(Account account, Context context, boolean replaceEdgeColors) {
  180. OCCapability capability = getCapability(account, context);
  181. try {
  182. return Color.parseColor(capability.getServerElementColor());
  183. } catch (Exception e) {
  184. int color;
  185. try {
  186. color = Color.parseColor(capability.getServerColor());
  187. } catch (Exception e1) {
  188. color = context.getResources().getColor(R.color.primary);
  189. }
  190. if (replaceEdgeColors) {
  191. if (isDarkModeActive(context)) {
  192. if (Color.BLACK == color) {
  193. return getNeutralGrey(context);
  194. } else {
  195. return color;
  196. }
  197. } else {
  198. if (Color.WHITE == color) {
  199. return getNeutralGrey(context);
  200. } else {
  201. return color;
  202. }
  203. }
  204. } else {
  205. float[] hsl = colorToHSL(color);
  206. if (hsl[INDEX_LUMINATION] > LUMINATION_THRESHOLD) {
  207. return context.getResources().getColor(R.color.element_fallback_color);
  208. } else {
  209. return color;
  210. }
  211. }
  212. }
  213. }
  214. public static boolean themingEnabled(Context context) {
  215. return getCapability(context).getServerColor() != null && !getCapability(context).getServerColor().isEmpty();
  216. }
  217. /**
  218. * returns the font color based on the server side theming and uses black/white as a fallback based on
  219. * replaceWhite.
  220. *
  221. * @param context the context
  222. * @param replaceWhite FLAG to return white/black if server side color isn't available
  223. * @return int font color to use
  224. */
  225. public static int fontColor(Context context, boolean replaceWhite) {
  226. if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
  227. if (replaceWhite) {
  228. return Color.BLACK;
  229. } else {
  230. return Color.WHITE;
  231. }
  232. }
  233. try {
  234. return Color.parseColor(getCapability(context).getServerTextColor());
  235. } catch (Exception e) {
  236. if (darkTheme(context)) {
  237. return Color.WHITE;
  238. } else {
  239. return Color.BLACK;
  240. }
  241. }
  242. }
  243. public static int fontColor(Context context) {
  244. return fontColor(context, false);
  245. }
  246. /**
  247. * Tests if light color is set
  248. *
  249. * @param color the color
  250. * @return true if primaryColor is lighter than MAX_LIGHTNESS
  251. */
  252. public static boolean lightTheme(int color) {
  253. float[] hsl = colorToHSL(color);
  254. return hsl[INDEX_LUMINATION] >= MAX_LIGHTNESS;
  255. }
  256. /**
  257. * Tests if dark color is set
  258. *
  259. * @return true if dark theme -> e.g.use light font color, darker accent color
  260. */
  261. public static boolean darkTheme(Context context) {
  262. int primaryColor = primaryColor(context);
  263. float[] hsl = colorToHSL(primaryColor);
  264. return hsl[INDEX_LUMINATION] <= 0.55;
  265. }
  266. public static int primaryAppbarColor(Context context) {
  267. return ContextCompat.getColor(context, R.color.appbar);
  268. }
  269. public static int appBarPrimaryFontColor(Context context) {
  270. return ContextCompat.getColor(context, R.color.fontAppbar);
  271. }
  272. public static int appBarSecondaryFontColor(Context context) {
  273. return ContextCompat.getColor(context, R.color.fontSecondaryAppbar);
  274. }
  275. public static int actionModeColor(Context context) {
  276. return ContextCompat.getColor(context, R.color.action_mode_background);
  277. }
  278. /**
  279. * Set color of title to white/black depending on background color
  280. *
  281. * @param actionBar actionBar to be used
  282. * @param title title to be shown
  283. */
  284. public static void setColoredTitle(@Nullable ActionBar actionBar, String title, Context context) {
  285. if (actionBar != null) {
  286. if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
  287. actionBar.setTitle(title);
  288. } else {
  289. Spannable text = new SpannableString(title);
  290. text.setSpan(new ForegroundColorSpan(appBarPrimaryFontColor(context)),
  291. 0,
  292. text.length(),
  293. Spannable.SPAN_INCLUSIVE_INCLUSIVE);
  294. actionBar.setTitle(text);
  295. }
  296. }
  297. }
  298. public static void setColoredTitle(@Nullable ActionBar actionBar, int titleId, Context context) {
  299. setColoredTitle(actionBar, context.getString(titleId), context);
  300. }
  301. /**
  302. * Set color of subtitle to white/black depending on background color
  303. *
  304. * @param actionBar actionBar to be used
  305. * @param title title to be shown
  306. */
  307. public static void setColoredSubtitle(@Nullable ActionBar actionBar, String title, Context context) {
  308. if (actionBar != null) {
  309. if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
  310. actionBar.setSubtitle(title);
  311. } else {
  312. Spannable text = new SpannableString(title);
  313. text.setSpan(new ForegroundColorSpan(appBarSecondaryFontColor(context)),
  314. 0,
  315. text.length(),
  316. Spannable.SPAN_INCLUSIVE_INCLUSIVE);
  317. actionBar.setSubtitle(text);
  318. }
  319. }
  320. }
  321. /**
  322. * For activities that do not use drawer, e.g. Settings, this can be used to correctly tint back button based on
  323. * theme
  324. *
  325. * @param supportActionBar
  326. */
  327. public static void tintBackButton(@Nullable ActionBar supportActionBar, Context context) {
  328. tintBackButton(supportActionBar, context, ThemeUtils.appBarPrimaryFontColor(context));
  329. }
  330. public static void tintBackButton(@Nullable ActionBar supportActionBar, Context context, @ColorInt int color) {
  331. if (supportActionBar == null) {
  332. return;
  333. }
  334. Drawable backArrow = ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_arrow_back, null);
  335. supportActionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, color));
  336. }
  337. public static Spanned getColoredTitle(String title, int color) {
  338. Spannable text = new SpannableString(title);
  339. text.setSpan(new ForegroundColorSpan(color),
  340. 0,
  341. text.length(),
  342. Spannable.SPAN_INCLUSIVE_INCLUSIVE);
  343. return text;
  344. }
  345. public static String getDefaultDisplayNameForRootFolder(Context context) {
  346. OCCapability capability = getCapability(context);
  347. if (MainApp.isOnlyOnDevice()) {
  348. return MainApp.getAppContext().getString(R.string.drawer_item_on_device);
  349. } else {
  350. if (capability.getServerName() == null || capability.getServerName().isEmpty()) {
  351. return MainApp.getAppContext().getResources().getString(R.string.default_display_name_for_root_folder);
  352. } else {
  353. return capability.getServerName();
  354. }
  355. }
  356. }
  357. public static void setStatusBarColor(Activity activity, @ColorInt int color) {
  358. if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  359. activity.getWindow().setStatusBarColor(color);
  360. }
  361. }
  362. /**
  363. * Adjust lightness of given color
  364. *
  365. * @param lightnessDelta values -1..+1
  366. * @param color original color
  367. * @param threshold 0..1 as maximum value, -1 to disable
  368. * @return color adjusted by lightness
  369. */
  370. public static int adjustLightness(float lightnessDelta, int color, float threshold) {
  371. float[] hsl = colorToHSL(color);
  372. if (threshold == -1f) {
  373. hsl[INDEX_LUMINATION] += lightnessDelta;
  374. } else {
  375. hsl[INDEX_LUMINATION] = Math.min(hsl[INDEX_LUMINATION] + lightnessDelta, threshold);
  376. }
  377. return ColorUtils.HSLToColor(hsl);
  378. }
  379. private static float[] colorToHSL(int color) {
  380. float[] hsl = new float[3];
  381. ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl);
  382. return hsl;
  383. }
  384. public static void colorPrimaryButton(Button button, Context context) {
  385. int primaryColor = ThemeUtils.primaryColor(null, true, false, context);
  386. int fontColor = ThemeUtils.fontColor(context, false);
  387. button.setBackgroundColor(primaryColor);
  388. if (Color.BLACK == primaryColor) {
  389. button.setTextColor(Color.WHITE);
  390. } else if (Color.WHITE == primaryColor) {
  391. button.setTextColor(Color.BLACK);
  392. } else {
  393. button.setTextColor(fontColor);
  394. }
  395. }
  396. /**
  397. * sets the tinting of the given ImageButton's icon to color_accent.
  398. *
  399. * @param imageButton the image button who's icon should be colored
  400. */
  401. public static void colorImageButton(ImageButton imageButton, @ColorInt int color) {
  402. if (imageButton != null) {
  403. imageButton.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
  404. }
  405. }
  406. public static void colorEditText(EditText editText, int elementColor) {
  407. if (editText != null) {
  408. editText.setTextColor(elementColor);
  409. editText.getBackground().setColorFilter(elementColor, PorterDuff.Mode.SRC_ATOP);
  410. }
  411. }
  412. /**
  413. * sets the coloring of the given progress bar to given color.
  414. *
  415. * @param progressBar the progress bar to be colored
  416. * @param color the color to be used
  417. */
  418. public static void colorHorizontalProgressBar(ProgressBar progressBar, @ColorInt int color) {
  419. if (progressBar != null) {
  420. progressBar.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
  421. progressBar.getProgressDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
  422. }
  423. }
  424. /**
  425. * sets the coloring of the given progress bar's progress to given color.
  426. *
  427. * @param progressBar the progress bar to be colored
  428. * @param color the color to be used
  429. */
  430. public static void colorProgressBar(ProgressBar progressBar, @ColorInt int color) {
  431. if (progressBar != null) {
  432. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  433. progressBar.setProgressTintList(ColorStateList.valueOf(color));
  434. } else {
  435. ThemeUtils.colorHorizontalProgressBar(progressBar, color);
  436. }
  437. }
  438. }
  439. /**
  440. * sets the coloring of the given seek bar to color_accent.
  441. *
  442. * @param seekBar the seek bar to be colored
  443. */
  444. public static void colorHorizontalSeekBar(SeekBar seekBar, Context context) {
  445. int color = ThemeUtils.primaryAccentColor(context);
  446. colorHorizontalProgressBar(seekBar, color);
  447. seekBar.getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN);
  448. }
  449. public static void colorSwipeRefreshLayout(Context context, SwipeRefreshLayout swipeRefreshLayout) {
  450. int primaryColor = ThemeUtils.primaryColor(context);
  451. int darkColor = ThemeUtils.primaryDarkColor(context);
  452. int accentColor = ThemeUtils.primaryAccentColor(context);
  453. swipeRefreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
  454. swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.bg_elevation_one);
  455. }
  456. /**
  457. * set the Nextcloud standard colors for the snackbar.
  458. *
  459. * @param context the context relevant for setting the color according to the context's theme
  460. * @param snackbar the snackbar to be colored
  461. */
  462. public static void colorSnackbar(Context context, Snackbar snackbar) {
  463. // Changing action button text color
  464. snackbar.setActionTextColor(ContextCompat.getColor(context, R.color.fg_inverse));
  465. }
  466. /**
  467. * Sets the color of the status bar to {@code color} on devices with OS version lollipop or higher.
  468. *
  469. * @param fragmentActivity fragment activity
  470. * @param color the color
  471. */
  472. public static void colorStatusBar(Activity fragmentActivity, @ColorInt int color) {
  473. Window window = fragmentActivity.getWindow();
  474. boolean isLightTheme = lightTheme(color);
  475. if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  476. window.setStatusBarColor(color);
  477. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  478. View decor = window.getDecorView();
  479. if (isLightTheme) {
  480. int systemUiFlagLightStatusBar;
  481. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  482. systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
  483. } else {
  484. systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
  485. }
  486. decor.setSystemUiVisibility(systemUiFlagLightStatusBar);
  487. } else {
  488. decor.setSystemUiVisibility(0);
  489. }
  490. } else if (isLightTheme) {
  491. window.setStatusBarColor(Color.BLACK);
  492. }
  493. }
  494. }
  495. public static void colorStatusBar(Activity fragmentActivity) {
  496. colorStatusBar(fragmentActivity, primaryAppbarColor(fragmentActivity));
  497. }
  498. /**
  499. * Sets the color of the TextInputLayout to {@code color} for hint text and box stroke.
  500. *
  501. * @param textInputLayout the TextInputLayout instance
  502. * @param color the color to be used for the hint text and box stroke
  503. */
  504. public static void colorTextInputLayout(TextInputLayout textInputLayout, int color) {
  505. textInputLayout.setBoxStrokeColor(color);
  506. textInputLayout.setDefaultHintTextColor(new ColorStateList(
  507. new int[][]{
  508. new int[]{-android.R.attr.state_focused},
  509. new int[]{android.R.attr.state_focused},
  510. },
  511. new int[]{
  512. Color.GRAY,
  513. color
  514. }
  515. ));
  516. }
  517. public static void themeDialogActionButton(MaterialButton button) {
  518. if (button == null) {
  519. return;
  520. }
  521. Context context = button.getContext();
  522. int accentColor = ThemeUtils.primaryAccentColor(button.getContext());
  523. int disabledColor = ContextCompat.getColor(context, R.color.disabled_text);
  524. button.setTextColor(new ColorStateList(
  525. new int[][]{
  526. new int[]{android.R.attr.state_enabled}, // enabled
  527. new int[]{-android.R.attr.state_enabled}, // disabled
  528. },
  529. new int[]{
  530. accentColor,
  531. disabledColor
  532. }
  533. ));
  534. }
  535. public static void themeEditText(Context context, EditText editText, boolean themedBackground) {
  536. if (editText == null) {
  537. return;
  538. }
  539. int color = ContextCompat.getColor(context, R.color.text_color);
  540. if (themedBackground) {
  541. if (darkTheme(context)) {
  542. color = ContextCompat.getColor(context, R.color.themed_fg);
  543. } else {
  544. color = ContextCompat.getColor(context, R.color.themed_fg_inverse);
  545. }
  546. }
  547. setEditTextColor(context, editText, color);
  548. }
  549. private static void setEditTextColor(Context context, EditText editText, int color) {
  550. editText.setTextColor(color);
  551. editText.setHighlightColor(context.getResources().getColor(R.color.fg_contrast));
  552. setEditTextCursorColor(editText, color);
  553. setTextViewHandlesColor(context, editText, color);
  554. }
  555. /**
  556. * Theme search view
  557. *
  558. * @param searchView searchView to be changed
  559. * @param context the app's context
  560. */
  561. public static void themeSearchView(SearchView searchView, Context context) {
  562. // hacky as no default way is provided
  563. int fontColor = appBarPrimaryFontColor(context);
  564. SearchView.SearchAutoComplete editText = searchView.findViewById(R.id.search_src_text);
  565. setEditTextColor(context, editText, fontColor);
  566. editText.setHintTextColor(appBarSecondaryFontColor(context));
  567. ImageView closeButton = searchView.findViewById(androidx.appcompat.R.id.search_close_btn);
  568. closeButton.setColorFilter(fontColor);
  569. ImageView searchButton = searchView.findViewById(androidx.appcompat.R.id.search_button);
  570. searchButton.setColorFilter(fontColor);
  571. }
  572. public static void themeProgressBar(Context context, ProgressBar progressBar) {
  573. int color = ThemeUtils.primaryAccentColor(context);
  574. progressBar.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
  575. }
  576. public static void tintCheckbox(AppCompatCheckBox checkBox, int color) {
  577. CompoundButtonCompat.setButtonTintList(checkBox, new ColorStateList(
  578. new int[][]{
  579. new int[]{-android.R.attr.state_checked},
  580. new int[]{android.R.attr.state_checked},
  581. },
  582. new int[]{
  583. Color.GRAY,
  584. color
  585. }
  586. ));
  587. }
  588. public static void tintSwitch(SwitchCompat switchView, int color) {
  589. tintSwitch(switchView, color, false);
  590. }
  591. public static void tintSwitch(SwitchCompat switchView, int color, boolean colorText) {
  592. if (colorText) {
  593. switchView.setTextColor(color);
  594. }
  595. int trackColor = Color.argb(77, Color.red(color), Color.green(color), Color.blue(color));
  596. // setting the thumb color
  597. DrawableCompat.setTintList(switchView.getThumbDrawable(), new ColorStateList(
  598. new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
  599. new int[]{color, Color.WHITE}));
  600. // setting the track color
  601. DrawableCompat.setTintList(switchView.getTrackDrawable(), new ColorStateList(
  602. new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
  603. new int[]{trackColor, MainApp.getAppContext().getResources().getColor(R.color.switch_track_color_unchecked)}));
  604. }
  605. public static Drawable tintDrawable(@DrawableRes int id, int color) {
  606. Drawable drawable = ResourcesCompat.getDrawable(MainApp.getAppContext().getResources(), id, null);
  607. return tintDrawable(drawable, color);
  608. }
  609. @Nullable
  610. public static Drawable tintDrawable(Drawable drawable, int color) {
  611. if (drawable != null) {
  612. Drawable wrap = DrawableCompat.wrap(drawable);
  613. wrap.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
  614. return wrap;
  615. }
  616. return null;
  617. }
  618. /**
  619. * Will change a menu item text tint
  620. * @param item the menu item object
  621. * @param color the wanted color (as resource or color)
  622. */
  623. public static void tintMenuItemText(MenuItem item, int color) {
  624. SpannableString newItemTitle = new SpannableString(item.getTitle());
  625. newItemTitle.setSpan(new ForegroundColorSpan(color), 0, newItemTitle.length(),
  626. Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  627. item.setTitle(newItemTitle);
  628. }
  629. public static String colorToHexString(int color) {
  630. return String.format("#%06X", 0xFFFFFF & color);
  631. }
  632. public static void tintFloatingActionButton(FloatingActionButton button, Context context) {
  633. button.setBackgroundTintList(ColorStateList.valueOf(ThemeUtils.primaryColor(context)));
  634. button.setRippleColor(ThemeUtils.primaryDarkColor(context));
  635. }
  636. public static void drawableFloatingActionButton(FloatingActionButton button, @DrawableRes int
  637. drawable, Context context) {
  638. button.setImageDrawable(ThemeUtils.tintDrawable(drawable, ThemeUtils.fontColor(context)));
  639. }
  640. private static OCCapability getCapability(Context context) {
  641. return getCapability(null, context);
  642. }
  643. private static OCCapability getCapability(Account acc, Context context) {
  644. Account account = null;
  645. if (acc != null) {
  646. account = acc;
  647. } else if (context != null) {
  648. // TODO: refactor when dark theme work is completed
  649. account = UserAccountManagerImpl.fromContext(context).getCurrentAccount();
  650. }
  651. if (account != null) {
  652. FileDataStorageManager storageManager = new FileDataStorageManager(account, context.getContentResolver());
  653. return storageManager.getCapability(account.name);
  654. } else {
  655. return new OCCapability();
  656. }
  657. }
  658. public static Drawable setIconColor(Drawable drawable) {
  659. int color;
  660. if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
  661. color = Color.WHITE;
  662. } else {
  663. color = Color.BLACK;
  664. }
  665. return tintDrawable(drawable, color);
  666. }
  667. /**
  668. * Lifted from SO. FindBugs surpressed because of lack of public API to alter the cursor color.
  669. *
  670. * @param editText TextView to be styled
  671. * @param color The desired cursor colour
  672. * @see <a href="https://stackoverflow.com/a/52564925">StackOverflow url</a>
  673. */
  674. @SuppressFBWarnings
  675. public static void setEditTextCursorColor(EditText editText, int color) {
  676. try {
  677. // Get the cursor resource id
  678. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {//set differently in Android P (API 28)
  679. Field field = TextView.class.getDeclaredField("mCursorDrawableRes");
  680. field.setAccessible(true);
  681. int drawableResId = field.getInt(editText);
  682. // Get the editor
  683. field = TextView.class.getDeclaredField("mEditor");
  684. field.setAccessible(true);
  685. Object editor = field.get(editText);
  686. // Get the drawable and set a color filter
  687. Drawable drawable = ContextCompat.getDrawable(editText.getContext(), drawableResId);
  688. drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
  689. // Set the drawables
  690. field = editor.getClass().getDeclaredField("mDrawableForCursor");
  691. field.setAccessible(true);
  692. field.set(editor, drawable);
  693. } else {
  694. Field field = TextView.class.getDeclaredField("mCursorDrawableRes");
  695. field.setAccessible(true);
  696. int drawableResId = field.getInt(editText);
  697. // Get the editor
  698. field = TextView.class.getDeclaredField("mEditor");
  699. field.setAccessible(true);
  700. Object editor = field.get(editText);
  701. // Get the drawable and set a color filter
  702. Drawable drawable = ContextCompat.getDrawable(editText.getContext(), drawableResId);
  703. drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
  704. Drawable[] drawables = {drawable, drawable};
  705. // Set the drawables
  706. field = editor.getClass().getDeclaredField("mCursorDrawable");
  707. field.setAccessible(true);
  708. field.set(editor, drawables);
  709. }
  710. } catch (Exception exception) {
  711. // we do not log this
  712. }
  713. }
  714. /**
  715. * Set the color of the handles when you select text in a {@link android.widget.EditText} or other view that extends
  716. * {@link TextView}. FindBugs surpressed because of lack of public API to alter the {@link TextView} handles color.
  717. *
  718. * @param view The {@link TextView} or a {@link View} that extends {@link TextView}.
  719. * @param color The color to set for the text handles
  720. * @see <a href="https://gist.github.com/jaredrummler/2317620559d10ac39b8218a1152ec9d4">External reference</a>
  721. */
  722. @SuppressFBWarnings
  723. private static void setTextViewHandlesColor(Context context, TextView view, int color) {
  724. try {
  725. Field editorField = TextView.class.getDeclaredField("mEditor");
  726. if (!editorField.isAccessible()) {
  727. editorField.setAccessible(true);
  728. }
  729. Object editor = editorField.get(view);
  730. Class<?> editorClass = editor.getClass();
  731. String[] handleNames = {"mSelectHandleLeft", "mSelectHandleRight", "mSelectHandleCenter"};
  732. String[] resNames = {"mTextSelectHandleLeftRes", "mTextSelectHandleRightRes", "mTextSelectHandleRes"};
  733. for (int i = 0; i < handleNames.length; i++) {
  734. Field handleField = editorClass.getDeclaredField(handleNames[i]);
  735. if (!handleField.isAccessible()) {
  736. handleField.setAccessible(true);
  737. }
  738. Drawable handleDrawable = (Drawable) handleField.get(editor);
  739. if (handleDrawable == null) {
  740. Field resField = TextView.class.getDeclaredField(resNames[i]);
  741. if (!resField.isAccessible()) {
  742. resField.setAccessible(true);
  743. }
  744. int resId = resField.getInt(view);
  745. handleDrawable = ContextCompat.getDrawable(context, resId);
  746. }
  747. if (handleDrawable != null) {
  748. Drawable drawable = handleDrawable.mutate();
  749. drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
  750. handleField.set(editor, drawable);
  751. }
  752. }
  753. } catch (Exception e) {
  754. Log_OC.e(TAG, "Error setting TextView handles color", e);
  755. }
  756. }
  757. public static boolean isDarkModeActive(Context context) {
  758. int nightModeFlag = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
  759. return Configuration.UI_MODE_NIGHT_YES == nightModeFlag;
  760. }
  761. }