From 976f4be4578bd5ce8556b40f98dd4407b5f1ea48 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Thu, 21 Feb 2019 20:11:58 -0800 Subject: [PATCH] support checkbox tinting (#18300) Summary: RN offers checkbox component on android: https://facebook.github.io/react-native/docs/checkbox.html The Checkbox colors for checked and unchecked states cannot be controlled from JS at the moment; this PR adds support for that. The essence of changing colors for the states is this: ``` ColorStateList cls = new ColorStateList( new int[][] { new int[] { -android.R.attr.state_checked }, // unchecked new int[] { android.R.attr.state_checked } // checked }, new int[] { uncheckedColor, checkedColor } ); checkBox.setSupportButtonTintList(cls); ``` Because of this, I did it so that both colors have to provided together in an object. This is similar to [switch](https://facebook.github.io/react-native/docs/switch#trackcolor) Pull Request resolved: https://github.com/facebook/react-native/pull/18300 Differential Revision: D14180218 Pulled By: cpojer fbshipit-source-id: 88a9d1faf061c0651e3e28950f697535b90fbfd4 --- .../AndroidCheckBoxNativeComponent.js | 1 + .../Components/CheckBox/CheckBox.android.js | 28 ++++++++++- RNTester/js/CheckBoxExample.js | 5 +- .../views/checkbox/ReactCheckBoxManager.java | 47 +++++++++++++++++++ 4 files changed, 78 insertions(+), 3 deletions(-) diff --git a/Libraries/Components/CheckBox/AndroidCheckBoxNativeComponent.js b/Libraries/Components/CheckBox/AndroidCheckBoxNativeComponent.js index 14c84b069f..e8aaf3c68d 100644 --- a/Libraries/Components/CheckBox/AndroidCheckBoxNativeComponent.js +++ b/Libraries/Components/CheckBox/AndroidCheckBoxNativeComponent.js @@ -42,6 +42,7 @@ type NativeProps = $ReadOnly<{| on?: ?boolean, enabled?: boolean, + tintColors: {|true: ?number, false: ?number|} | typeof undefined, |}>; type CheckBoxNativeType = Class>; diff --git a/Libraries/Components/CheckBox/CheckBox.android.js b/Libraries/Components/CheckBox/CheckBox.android.js index e288562a0b..1e06f857b5 100644 --- a/Libraries/Components/CheckBox/CheckBox.android.js +++ b/Libraries/Components/CheckBox/CheckBox.android.js @@ -11,6 +11,7 @@ const React = require('React'); const StyleSheet = require('StyleSheet'); +const processColor = require('processColor'); const AndroidCheckBoxNativeComponent = require('AndroidCheckBoxNativeComponent'); const nullthrows = require('nullthrows'); @@ -19,6 +20,7 @@ const setAndForwardRef = require('setAndForwardRef'); import type {ViewProps} from 'ViewPropTypes'; import type {SyntheticEvent} from 'CoreEventTypes'; import type {NativeComponent} from 'ReactNative'; +import type {ColorValue} from 'StyleSheetTypes'; type CheckBoxEvent = SyntheticEvent< $ReadOnly<{| @@ -51,6 +53,7 @@ type NativeProps = $ReadOnly<{| on?: ?boolean, enabled?: boolean, + tintColors: {|true: ?number, false: ?number|} | typeof undefined, |}>; type CheckBoxNativeType = Class>; @@ -74,6 +77,11 @@ type Props = $ReadOnly<{| * Used to get the ref for the native checkbox */ forwardedRef?: ?React.Ref, + + /** + * Controls the colors the checkbox has in checked and unchecked states. + */ + tintColors?: {|true?: ?ColorValue, false?: ?ColorValue|}, |}>; /** @@ -150,8 +158,24 @@ class CheckBox extends React.Component { this.props.onValueChange(event.nativeEvent.value); }; + getTintColors(tintColors) { + return tintColors + ? { + true: processColor(tintColors.true), + false: processColor(tintColors.false), + } + : undefined; + } + render() { - const {disabled: _, value: __, style, forwardedRef, ...props} = this.props; + const { + disabled: _, + value: __, + tintColors, + style, + forwardedRef, + ...props + } = this.props; const disabled = this.props.disabled ?? false; const value = this.props.value ?? false; @@ -161,9 +185,9 @@ class CheckBox extends React.Component { onResponderTerminationRequest: () => false, enabled: !disabled, on: value, + tintColors: this.getTintColors(tintColors), style: [styles.rctCheckBox, style], }; - return ( { onValueChange={value => this.setState({falseCheckBoxIsOn: value})} style={styles.checkbox} value={this.state.falseCheckBoxIsOn} + tintColors={{false: 'red'}} /> this.setState({trueCheckBoxIsOn: value})} value={this.state.trueCheckBoxIsOn} + tintColors={{true: 'green'}} /> ); @@ -120,7 +122,8 @@ exports.displayName = 'CheckBoxExample'; exports.description = 'Native boolean input'; exports.examples = [ { - title: 'CheckBoxes can be set to true or false', + title: + 'CheckBoxes can be set to true or false, and the color of both states can be specified.', render(): React.Element { return ; }, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/checkbox/ReactCheckBoxManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/checkbox/ReactCheckBoxManager.java index f139e96744..ac55c91b7c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/checkbox/ReactCheckBoxManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/checkbox/ReactCheckBoxManager.java @@ -7,15 +7,22 @@ package com.facebook.react.views.checkbox; import android.content.Context; +import android.content.res.ColorStateList; +import android.support.v4.widget.CompoundButtonCompat; import android.support.v7.widget.TintContextWrapper; +import android.util.TypedValue; import android.widget.CompoundButton; + import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; +import javax.annotation.Nullable; + /** View manager for {@link ReactCheckBox} components. */ public class ReactCheckBoxManager extends SimpleViewManager { @@ -72,4 +79,44 @@ public class ReactCheckBoxManager extends SimpleViewManager { view.setOn(on); view.setOnCheckedChangeListener(ON_CHECKED_CHANGE_LISTENER); } + + private static int getThemeColor(final Context context, String colorId) { + final TypedValue value = new TypedValue(); + context.getTheme().resolveAttribute(getIdentifier(context, colorId), value, true); + return value.data; + } + + /** + * The appcompat-v7 BUCK dep is listed as a provided_dep, which complains that + * com.facebook.react.R doesn't exist. Since the attributes are provided from a parent, we can access + * those attributes dynamically. + */ + private static int getIdentifier(Context context, String name) { + return context.getResources().getIdentifier(name, "attr", context.getPackageName()); + } + + @ReactProp(name = "tintColors") + public void setTintColors(ReactCheckBox view, @Nullable ReadableMap colorsMap) { + String defaultColorIdOfCheckedState = "colorAccent"; + int trueColor = colorsMap == null || !colorsMap.hasKey("true") ? + getThemeColor(view.getContext(), defaultColorIdOfCheckedState) : colorsMap.getInt("true"); + + String defaultColorIdOfUncheckedState = "colorPrimaryDark"; + int falseColor = colorsMap == null || !colorsMap.hasKey("false") ? + getThemeColor(view.getContext(), defaultColorIdOfUncheckedState) : colorsMap.getInt("false"); + + ColorStateList csl = new ColorStateList( + new int[][]{ + new int[]{android.R.attr.state_checked}, + new int[]{-android.R.attr.state_checked} + }, + new int[]{ + trueColor, + falseColor, + } + ); + + CompoundButtonCompat.setButtonTintList(view, csl); + } + }