From 7ab5eb4cafdea695c4c53ce2a737f6302afd6380 Mon Sep 17 00:00:00 2001 From: Joshua Gross Date: Wed, 27 Nov 2019 12:53:14 -0800 Subject: [PATCH] AndroidTextInput: support using commands instead of setNativeProps (native change) Summary: In AndroidTextInput, support codegen'd ViewCommands in native and add three commands that will eventually replace usage of setNativeProps on Android. TextInput will use these commands in a future diff. Changelog: [Internal] Reviewed By: TheSavior Differential Revision: D18612150 fbshipit-source-id: 5d427040686e8c5ab504dd845bc8ef863f558c35 --- .../AndroidTextInputNativeComponent.js | 31 ++++++++++- .../views/text/ReactTextViewManager.java | 2 +- .../textinput/ReactTextInputManager.java | 45 +++++++++++++++- .../AndroidTextInputShadowNode.cpp | 51 +++++++++++-------- 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js index d08573f119d3a8..3a381d1bdb64cd 100644 --- a/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js +++ b/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js @@ -18,11 +18,13 @@ import type { Float, Int32, WithDefault, -} from 'react-native/Libraries/Types/CodegenTypes'; +} from '../../Types/CodegenTypes'; import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; import type {TextStyleProp, ViewStyleProp} from '../../StyleSheet/StyleSheet'; import type {ColorValue} from '../../StyleSheet/StyleSheetTypes'; import {requireNativeComponent} from 'react-native'; +import codegenNativeCommands from '../../Utilities/codegenNativeCommands'; +import * as React from 'react'; export type KeyboardType = // Cross Platform @@ -534,6 +536,33 @@ export type NativeProps = $ReadOnly<{| text?: ?string, |}>; +type NativeType = HostComponent; + +interface NativeCommands { + +focus: (viewRef: React.ElementRef) => void; + +blur: (viewRef: React.ElementRef) => void; + +setMostRecentEventCount: ( + viewRef: React.ElementRef, + eventCount: Int32, + ) => void; + +setTextAndSelection: ( + viewRef: React.ElementRef, + mostRecentEventCount: Int32, + value: ?string, // in theory this is nullable + start: Int32, + end: Int32, + ) => void; +} + +export const Commands: NativeCommands = codegenNativeCommands({ + supportedCommands: [ + 'focus', + 'blur', + 'setMostRecentEventCount', + 'setTextAndSelection', + ], +}); + const AndroidTextInputNativeComponent: HostComponent = requireNativeComponent( 'AndroidTextInput', ); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java index 8ac470bf0a58d5..26239ca438009d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java @@ -96,7 +96,7 @@ public Object updateState( return new ReactTextUpdate( spanned, - -1, // TODO add this into local Data? + state.hasKey("mostRecentEventCount") ? state.getInt("mostRecentEventCount") : -1, false, // TODO add this into local Data textViewProps.getTextAlign(), textBreakStrategy, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index 0f7994195fe6db..72165e54271b04 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -15,6 +15,7 @@ import android.text.InputType; import android.text.Layout; import android.text.Spannable; +import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.view.Gravity; import android.view.KeyEvent; @@ -55,6 +56,7 @@ import com.facebook.react.views.text.TextAttributeProps; import com.facebook.react.views.text.TextInlineImageSpan; import com.facebook.react.views.text.TextLayoutManager; +import com.facebook.react.views.text.TextTransform; import com.facebook.yoga.YogaConstants; import java.lang.reflect.Field; import java.util.LinkedList; @@ -72,6 +74,8 @@ public class ReactTextInputManager extends BaseViewManagertextAttributes); // Use BaseTextShadowNode to get attributed string from children - { - auto const &attributedString = - BaseTextShadowNode::getAttributedString(textAttributes, *this); - if (!attributedString.isEmpty() || !usePlaceholders) { - return attributedString; + auto const &attributedString = + BaseTextShadowNode::getAttributedString(textAttributes, *this); + if (!attributedString.isEmpty()) { + return attributedString; + } + if (!getProps()->text.empty() || usePlaceholders) { + // If the BaseTextShadowNode didn't detect any child Text nodes, we + // may actually just have a `text` attribute. + // TODO: figure out why BaseTextShadowNode doesn't pick this up, this + // is a bug. A minimal Playground example that triggers this: P122991121 + auto textAttributedString = AttributedString{}; + auto fragment = AttributedString::Fragment{}; + fragment.string = getProps()->text; + + if (usePlaceholders) { + // Return placeholder text instead, if text was empty. + if (fragment.string.empty()) { + fragment.string = getProps()->placeholder; + } + // For measurement purposes, we want to make sure that there's at least a + // single character in the string so that the measured height is greater + // than zero. Otherwise, empty TextInputs with no placeholder don't + // display at all. + if (fragment.string.empty()) { + fragment.string = " "; + } } + fragment.textAttributes = textAttributes; + fragment.parentShadowView = ShadowView(*this); + textAttributedString.appendFragment(fragment); + return textAttributedString; } - // Return placeholder text instead, if text was empty. - auto placeholderAttributedString = AttributedString{}; - auto fragment = AttributedString::Fragment{}; - fragment.string = getProps()->placeholder; - - // For measurement purposes, we want to make sure that there's at least a - // single character in the string so that the measured height is greater than - // zero. Otherwise, empty TextInputs with no placeholder don't display at all. - if (fragment.string == "") { - fragment.string = " "; - } - fragment.textAttributes = textAttributes; - fragment.parentShadowView = ShadowView(*this); - placeholderAttributedString.appendFragment(fragment); - return placeholderAttributedString; + return attributedString; } void AndroidTextInputShadowNode::setTextLayoutManager(