ThemeUtils.java 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  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.graphics.Color;
  29. import android.graphics.PorterDuff;
  30. import android.graphics.drawable.Drawable;
  31. import android.os.Build;
  32. import android.text.Html;
  33. import android.text.Spanned;
  34. import android.view.View;
  35. import android.view.Window;
  36. import android.widget.EditText;
  37. import android.widget.ImageButton;
  38. import android.widget.ProgressBar;
  39. import android.widget.SeekBar;
  40. import android.widget.TextView;
  41. import com.google.android.material.button.MaterialButton;
  42. import com.google.android.material.floatingactionbutton.FloatingActionButton;
  43. import com.google.android.material.snackbar.Snackbar;
  44. import com.google.android.material.textfield.TextInputLayout;
  45. import com.owncloud.android.MainApp;
  46. import com.owncloud.android.R;
  47. import com.owncloud.android.authentication.AccountUtils;
  48. import com.owncloud.android.datamodel.FileDataStorageManager;
  49. import com.owncloud.android.lib.common.utils.Log_OC;
  50. import com.owncloud.android.lib.resources.status.OCCapability;
  51. import com.owncloud.android.ui.activity.ToolbarActivity;
  52. import java.lang.reflect.Field;
  53. import androidx.annotation.ColorInt;
  54. import androidx.annotation.DrawableRes;
  55. import androidx.annotation.Nullable;
  56. import androidx.appcompat.app.ActionBar;
  57. import androidx.appcompat.widget.AppCompatCheckBox;
  58. import androidx.appcompat.widget.SearchView;
  59. import androidx.appcompat.widget.SwitchCompat;
  60. import androidx.core.content.ContextCompat;
  61. import androidx.core.content.res.ResourcesCompat;
  62. import androidx.core.graphics.ColorUtils;
  63. import androidx.core.graphics.drawable.DrawableCompat;
  64. import androidx.core.widget.CompoundButtonCompat;
  65. import androidx.fragment.app.FragmentActivity;
  66. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  67. /**
  68. * Utility class with methods for client side theming.
  69. */
  70. public final class ThemeUtils {
  71. private static final String TAG = ThemeUtils.class.getSimpleName();
  72. private static final int INDEX_LUMINATION = 2;
  73. private static final double MAX_LIGHTNESS = 0.92;
  74. private ThemeUtils() {
  75. // utility class -> private constructor
  76. }
  77. public static int primaryAccentColor(Context context) {
  78. OCCapability capability = getCapability(context);
  79. try {
  80. float adjust;
  81. if (darkTheme(context)) {
  82. adjust = +0.1f;
  83. } else {
  84. adjust = -0.1f;
  85. }
  86. return adjustLightness(adjust, Color.parseColor(capability.getServerColor()), 0.35f);
  87. } catch (Exception e) {
  88. return context.getResources().getColor(R.color.color_accent);
  89. }
  90. }
  91. public static int primaryDarkColor(Context context) {
  92. return primaryDarkColor(null, context);
  93. }
  94. public static int primaryDarkColor(Account account, Context context) {
  95. OCCapability capability = getCapability(account, context);
  96. try {
  97. return adjustLightness(-0.2f, Color.parseColor(capability.getServerColor()), -1f);
  98. } catch (Exception e) {
  99. return context.getResources().getColor(R.color.primary_dark);
  100. }
  101. }
  102. public static int primaryColor(Context context) {
  103. return primaryColor(context, false);
  104. }
  105. public static int primaryColor(Context context, boolean replaceWhite) {
  106. return primaryColor(null, replaceWhite, context);
  107. }
  108. public static int primaryColor(Account account, boolean replaceWhite, Context context) {
  109. OCCapability capability = getCapability(account, context);
  110. try {
  111. int color = Color.parseColor(capability.getServerColor());
  112. if (replaceWhite && Color.WHITE == color) {
  113. return Color.GRAY;
  114. } else {
  115. return color;
  116. }
  117. } catch (Exception e) {
  118. return context.getResources().getColor(R.color.primary);
  119. }
  120. }
  121. public static int elementColor(Context context) {
  122. return elementColor(null, context);
  123. }
  124. @NextcloudServer(max = 12)
  125. public static int elementColor(Account account, Context context) {
  126. OCCapability capability = getCapability(account, context);
  127. try {
  128. return Color.parseColor(capability.getServerElementColor());
  129. } catch (Exception e) {
  130. int primaryColor;
  131. try {
  132. primaryColor = Color.parseColor(capability.getServerColor());
  133. } catch (Exception e1) {
  134. primaryColor = context.getResources().getColor(R.color.primary);
  135. }
  136. float[] hsl = colorToHSL(primaryColor);
  137. if (hsl[INDEX_LUMINATION] > 0.8) {
  138. return context.getResources().getColor(R.color.elementFallbackColor);
  139. } else {
  140. return primaryColor;
  141. }
  142. }
  143. }
  144. public static boolean themingEnabled(Context context) {
  145. return getCapability(context).getServerColor() != null && !getCapability(context).getServerColor().isEmpty();
  146. }
  147. /**
  148. * @return int font color to use
  149. * adapted from https://github.com/nextcloud/server/blob/master/apps/theming/lib/Util.php#L90-L102
  150. */
  151. public static int fontColor(Context context) {
  152. try {
  153. return Color.parseColor(getCapability(context).getServerTextColor());
  154. } catch (Exception e) {
  155. if (darkTheme(context)) {
  156. return Color.WHITE;
  157. } else {
  158. return Color.BLACK;
  159. }
  160. }
  161. }
  162. /**
  163. * Tests if light color is set
  164. * @return true if primaryColor is lighter than MAX_LIGHTNESS
  165. */
  166. public static boolean lightTheme(Context context) {
  167. int primaryColor = primaryColor(context);
  168. float[] hsl = colorToHSL(primaryColor);
  169. return hsl[INDEX_LUMINATION] >= MAX_LIGHTNESS;
  170. }
  171. /**
  172. * Tests if dark color is set
  173. * @return true if dark theme -> e.g.use light font color, darker accent color
  174. */
  175. public static boolean darkTheme(Context context) {
  176. int primaryColor = primaryColor(context);
  177. float[] hsl = colorToHSL(primaryColor);
  178. return hsl[INDEX_LUMINATION] <= 0.55;
  179. }
  180. /**
  181. * Set color of title to white/black depending on background color
  182. *
  183. * @param actionBar actionBar to be used
  184. * @param title title to be shown
  185. */
  186. public static void setColoredTitle(@Nullable ActionBar actionBar, String title, Context context) {
  187. if (actionBar != null) {
  188. if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
  189. actionBar.setTitle(title);
  190. } else {
  191. String colorHex = colorToHexString(fontColor(context));
  192. actionBar.setTitle(Html.fromHtml("<font color='" + colorHex + "'>" + title + "</font>"));
  193. }
  194. }
  195. }
  196. public static Spanned getColoredTitle(String title, int color) {
  197. String colorHex = colorToHexString(color);
  198. return Html.fromHtml("<font color='" + colorHex + "'>" + title + "</font>");
  199. }
  200. /**
  201. * Set color of title to white/black depending on background color
  202. *
  203. * @param actionBar actionBar to be used
  204. * @param titleId title to be shown
  205. */
  206. public static void setColoredTitle(@Nullable ActionBar actionBar, int titleId, Context context) {
  207. if (actionBar != null) {
  208. if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
  209. actionBar.setTitle(titleId);
  210. } else {
  211. String colorHex = colorToHexString(fontColor(context));
  212. String title = context.getString(titleId);
  213. actionBar.setTitle(Html.fromHtml("<font color='" + colorHex + "'>" + title + "</font>"));
  214. }
  215. }
  216. }
  217. public static String getDefaultDisplayNameForRootFolder(Context context) {
  218. OCCapability capability = getCapability(context);
  219. if (MainApp.isOnlyOnDevice()) {
  220. return MainApp.getAppContext().getString(R.string.drawer_item_on_device);
  221. } else {
  222. if (capability.getServerName() == null || capability.getServerName().isEmpty()) {
  223. return MainApp.getAppContext().getResources().getString(R.string.default_display_name_for_root_folder);
  224. } else {
  225. return capability.getServerName();
  226. }
  227. }
  228. }
  229. public static void setStatusBarColor(Activity activity, @ColorInt int color) {
  230. if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  231. activity.getWindow().setStatusBarColor(color);
  232. }
  233. }
  234. /**
  235. * Adjust lightness of given color
  236. *
  237. * @param lightnessDelta values -1..+1
  238. * @param color original color
  239. * @param threshold 0..1 as maximum value, -1 to disable
  240. * @return color adjusted by lightness
  241. */
  242. public static int adjustLightness(float lightnessDelta, int color, float threshold) {
  243. float[] hsl = colorToHSL(color);
  244. if (threshold == -1f) {
  245. hsl[INDEX_LUMINATION] += lightnessDelta;
  246. } else {
  247. hsl[INDEX_LUMINATION] = Math.min(hsl[INDEX_LUMINATION] + lightnessDelta, threshold);
  248. }
  249. return ColorUtils.HSLToColor(hsl);
  250. }
  251. private static float[] colorToHSL(int color) {
  252. float[] hsl = new float[3];
  253. ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl);
  254. return hsl;
  255. }
  256. /**
  257. * sets the tinting of the given ImageButton's icon to color_accent.
  258. *
  259. * @param imageButton the image button who's icon should be colored
  260. */
  261. public static void colorImageButton(ImageButton imageButton, @ColorInt int color) {
  262. if (imageButton != null) {
  263. imageButton.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
  264. }
  265. }
  266. public static void colorEditText(EditText editText, int elementColor) {
  267. if (editText != null) {
  268. editText.setTextColor(elementColor);
  269. editText.getBackground().setColorFilter(elementColor, PorterDuff.Mode.SRC_ATOP);
  270. }
  271. }
  272. /**
  273. * sets the coloring of the given progress bar to given color.
  274. *
  275. * @param progressBar the progress bar to be colored
  276. * @param color the color to be used
  277. */
  278. public static void colorHorizontalProgressBar(ProgressBar progressBar, @ColorInt int color) {
  279. if (progressBar != null) {
  280. progressBar.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
  281. progressBar.getProgressDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
  282. }
  283. }
  284. /**
  285. * sets the coloring of the given progress bar's progress to given color.
  286. *
  287. * @param progressBar the progress bar to be colored
  288. * @param color the color to be used
  289. */
  290. public static void colorProgressBar(ProgressBar progressBar, @ColorInt int color) {
  291. if (progressBar != null) {
  292. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  293. progressBar.setProgressTintList(ColorStateList.valueOf(color));
  294. } else {
  295. ThemeUtils.colorHorizontalProgressBar(progressBar, color);
  296. }
  297. }
  298. }
  299. /**
  300. * sets the coloring of the given seek bar to color_accent.
  301. *
  302. * @param seekBar the seek bar to be colored
  303. */
  304. public static void colorHorizontalSeekBar(SeekBar seekBar, Context context) {
  305. int color = ThemeUtils.primaryAccentColor(context);
  306. colorHorizontalProgressBar(seekBar, color);
  307. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  308. seekBar.getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN);
  309. }
  310. }
  311. /**
  312. * set the Nextcloud standard colors for the snackbar.
  313. *
  314. * @param context the context relevant for setting the color according to the context's theme
  315. * @param snackbar the snackbar to be colored
  316. */
  317. public static void colorSnackbar(Context context, Snackbar snackbar) {
  318. // Changing action button text color
  319. snackbar.setActionTextColor(ContextCompat.getColor(context, R.color.fg_inverse));
  320. }
  321. /**
  322. * Sets the color of the status bar to {@code color} on devices with OS version lollipop or higher.
  323. *
  324. * @param fragmentActivity fragment activity
  325. * @param color the color
  326. */
  327. public static void colorStatusBar(FragmentActivity fragmentActivity, @ColorInt int color) {
  328. Window window = fragmentActivity.getWindow();
  329. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && window != null) {
  330. window.setStatusBarColor(color);
  331. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  332. View decor = window.getDecorView();
  333. if (lightTheme(fragmentActivity.getApplicationContext())) {
  334. decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
  335. } else {
  336. decor.setSystemUiVisibility(0);
  337. }
  338. }
  339. }
  340. }
  341. /**
  342. * Sets the color of the progressbar to {@code color} within the given toolbar.
  343. *
  344. * @param activity the toolbar activity instance
  345. * @param progressBarColor the color to be used for the toolbar's progress bar
  346. */
  347. public static void colorToolbarProgressBar(FragmentActivity activity, int progressBarColor) {
  348. if (activity instanceof ToolbarActivity) {
  349. ((ToolbarActivity) activity).setProgressBarBackgroundColor(progressBarColor);
  350. }
  351. }
  352. /**
  353. * Sets the color of the TextInputLayout to {@code color} for hint text and box stroke.
  354. *
  355. * @param textInputLayout the TextInputLayout instance
  356. * @param color the color to be used for the hint text and box stroke
  357. */
  358. public static void colorTextInputLayout(TextInputLayout textInputLayout, int color) {
  359. textInputLayout.setBoxStrokeColor(color);
  360. textInputLayout.setDefaultHintTextColor(new ColorStateList(
  361. new int[][]{
  362. new int[]{-android.R.attr.state_focused},
  363. new int[]{android.R.attr.state_focused},
  364. },
  365. new int[]{
  366. Color.GRAY,
  367. color
  368. }
  369. ));
  370. }
  371. public static void themeDialogActionButton(MaterialButton button) {
  372. if (button == null ) {
  373. return;
  374. }
  375. Context context = button.getContext();
  376. int accentColor = ThemeUtils.primaryAccentColor(button.getContext());
  377. int disabledColor = ContextCompat.getColor(context, R.color.disabled_text);
  378. button.setTextColor(new ColorStateList(
  379. new int[][]{
  380. new int[] { android.R.attr.state_enabled}, // enabled
  381. new int[] {-android.R.attr.state_enabled}, // disabled
  382. },
  383. new int[]{
  384. accentColor,
  385. disabledColor
  386. }
  387. ));
  388. }
  389. public static void themeEditText(Context context, EditText editText, boolean themedBackground) {
  390. if (editText == null) { return; }
  391. int color = primaryColor(context);
  392. // Don't theme the view when it is already on a theme'd background
  393. if (themedBackground) {
  394. if (darkTheme(context)) {
  395. color = ContextCompat.getColor(context, R.color.themed_fg);
  396. } else {
  397. color = ContextCompat.getColor(context, R.color.themed_fg_inverse);
  398. }
  399. } else {
  400. if (lightTheme(context)) {
  401. color = ContextCompat.getColor(context, R.color.fg_default);
  402. }
  403. }
  404. editText.setHighlightColor(context.getResources().getColor(R.color.fg_contrast));
  405. setTextViewCursorColor(editText, color);
  406. setTextViewHandlesColor(context, editText, color);
  407. }
  408. public static void themeSearchView(Context context, SearchView searchView, boolean themedBackground) {
  409. if (searchView == null) { return; }
  410. SearchView.SearchAutoComplete editText = searchView.findViewById(R.id.search_src_text);
  411. themeEditText(context, editText, themedBackground);
  412. }
  413. public static void tintCheckbox(AppCompatCheckBox checkBox, int color) {
  414. CompoundButtonCompat.setButtonTintList(checkBox, new ColorStateList(
  415. new int[][]{
  416. new int[]{-android.R.attr.state_checked},
  417. new int[]{android.R.attr.state_checked},
  418. },
  419. new int[]{
  420. Color.GRAY,
  421. color
  422. }
  423. ));
  424. }
  425. public static void tintSwitch(SwitchCompat switchView, int color) {
  426. tintSwitch(switchView, color, false);
  427. }
  428. public static void tintSwitch(SwitchCompat switchView, int color, boolean colorText) {
  429. if (colorText) {
  430. switchView.setTextColor(color);
  431. }
  432. int trackColor = Color.argb(77, Color.red(color), Color.green(color), Color.blue(color));
  433. // setting the thumb color
  434. DrawableCompat.setTintList(switchView.getThumbDrawable(), new ColorStateList(
  435. new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
  436. new int[]{color, Color.WHITE}));
  437. // setting the track color
  438. DrawableCompat.setTintList(switchView.getTrackDrawable(), new ColorStateList(
  439. new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
  440. new int[]{trackColor, Color.parseColor("#4D000000")}));
  441. }
  442. public static Drawable tintDrawable(@DrawableRes int id, int color) {
  443. Drawable drawable = ResourcesCompat.getDrawable(MainApp.getAppContext().getResources(), id, null);
  444. return tintDrawable(drawable, color);
  445. }
  446. @Nullable
  447. public static Drawable tintDrawable(Drawable drawable, int color) {
  448. if (drawable != null) {
  449. Drawable wrap = DrawableCompat.wrap(drawable);
  450. wrap.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
  451. return wrap;
  452. }
  453. return null;
  454. }
  455. public static String colorToHexString(int color) {
  456. return String.format("#%06X", 0xFFFFFF & color);
  457. }
  458. public static void tintFloatingActionButton(FloatingActionButton button, @DrawableRes int
  459. drawable, Context context) {
  460. button.setBackgroundTintList(ColorStateList.valueOf(ThemeUtils.primaryColor(context)));
  461. button.setRippleColor(ThemeUtils.primaryDarkColor(context));
  462. button.setImageDrawable(ThemeUtils.tintDrawable(drawable, ThemeUtils.fontColor(context)));
  463. }
  464. private static OCCapability getCapability(Context context) {
  465. return getCapability(null, context);
  466. }
  467. private static OCCapability getCapability(Account acc, Context context) {
  468. Account account = null;
  469. if (acc != null) {
  470. account = acc;
  471. } else if (context != null) {
  472. account = AccountUtils.getCurrentOwnCloudAccount(context);
  473. }
  474. if (account != null) {
  475. FileDataStorageManager storageManager = new FileDataStorageManager(account, context.getContentResolver());
  476. return storageManager.getCapability(account.name);
  477. } else {
  478. return new OCCapability();
  479. }
  480. }
  481. /**
  482. * Lifted from SO.
  483. * FindBugs surpressed because of lack of public API to alter the cursor color.
  484. *
  485. * @param view TextView to be styled
  486. * @param color The desired cursor colour
  487. * @see <a href="https://stackoverflow.com/questions/25996032/how-to-change-programmatically-edittext-cursor-color-in-android#26543290">StackOverflow url</a>
  488. */
  489. @SuppressFBWarnings
  490. private static void setTextViewCursorColor(EditText view, @ColorInt int color) {
  491. try {
  492. // Get the cursor resource id
  493. Field field = TextView.class.getDeclaredField("mCursorDrawableRes");
  494. field.setAccessible(true);
  495. int drawableResId = field.getInt(view);
  496. // Get the editor
  497. field = TextView.class.getDeclaredField("mEditor");
  498. field.setAccessible(true);
  499. Object editor = field.get(view);
  500. // Get the drawable and set a color filter
  501. Drawable drawable = ContextCompat.getDrawable(view.getContext(), drawableResId);
  502. drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
  503. Drawable[] drawables = {drawable, drawable};
  504. // Set the drawables
  505. field = editor.getClass().getDeclaredField("mCursorDrawable");
  506. field.setAccessible(true);
  507. field.set(editor, drawables);
  508. } catch (Exception e) {
  509. Log_OC.e(TAG, "setTextViewCursorColor", e);
  510. }
  511. }
  512. /**
  513. * Set the color of the handles when you select text in a
  514. * {@link android.widget.EditText} or other view that extends {@link TextView}.
  515. * FindBugs surpressed because of lack of public API to alter the {@link TextView} handles color.
  516. *
  517. * @param view
  518. * The {@link TextView} or a {@link View} that extends {@link TextView}.
  519. * @param color
  520. * The color to set for the text handles
  521. *
  522. * @see <a href="https://gist.github.com/jaredrummler/2317620559d10ac39b8218a1152ec9d4">External reference</a>
  523. */
  524. @SuppressFBWarnings
  525. private static void setTextViewHandlesColor(Context context, TextView view, int color) {
  526. try {
  527. Field editorField = TextView.class.getDeclaredField("mEditor");
  528. if (!editorField.isAccessible()) {
  529. editorField.setAccessible(true);
  530. }
  531. Object editor = editorField.get(view);
  532. Class<?> editorClass = editor.getClass();
  533. String[] handleNames = {"mSelectHandleLeft", "mSelectHandleRight", "mSelectHandleCenter"};
  534. String[] resNames = {"mTextSelectHandleLeftRes", "mTextSelectHandleRightRes", "mTextSelectHandleRes"};
  535. for (int i = 0; i < handleNames.length; i++) {
  536. Field handleField = editorClass.getDeclaredField(handleNames[i]);
  537. if (!handleField.isAccessible()) {
  538. handleField.setAccessible(true);
  539. }
  540. Drawable handleDrawable = (Drawable) handleField.get(editor);
  541. if (handleDrawable == null) {
  542. Field resField = TextView.class.getDeclaredField(resNames[i]);
  543. if (!resField.isAccessible()) {
  544. resField.setAccessible(true);
  545. }
  546. int resId = resField.getInt(view);
  547. handleDrawable = ContextCompat.getDrawable(context, resId);
  548. }
  549. if (handleDrawable != null) {
  550. Drawable drawable = handleDrawable.mutate();
  551. drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
  552. handleField.set(editor, drawable);
  553. }
  554. }
  555. } catch (Exception e) {
  556. Log_OC.e(TAG, "Error setting TextView handles color", e);
  557. }
  558. }
  559. }