From 95d3d8905484929a1369470d77e90f080ea4f7ea Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Thu, 4 Feb 2016 22:39:16 +0800 Subject: [PATCH 1/3] migrate movies to es6 --- Examples/Movies/MovieCell.js | 20 ++--- Examples/Movies/MovieScreen.js | 43 +++++----- Examples/Movies/MoviesApp.android.js | 20 ++--- Examples/Movies/MoviesApp.ios.js | 16 ++-- Examples/Movies/SearchBar.android.js | 21 +++-- Examples/Movies/SearchBar.ios.js | 14 ++-- Examples/Movies/SearchScreen.js | 121 +++++++++++++++------------ Examples/Movies/getImageSource.js | 4 +- Examples/Movies/getStyleFromScore.js | 13 ++- Examples/Movies/getTextFromScore.js | 4 +- 10 files changed, 135 insertions(+), 141 deletions(-) diff --git a/Examples/Movies/MovieCell.js b/Examples/Movies/MovieCell.js index c90931c08768ee..6114e4e0e91db7 100644 --- a/Examples/Movies/MovieCell.js +++ b/Examples/Movies/MovieCell.js @@ -15,8 +15,8 @@ */ 'use strict'; -var React = require('react-native'); -var { +import React, { + Component, Image, Platform, StyleSheet, @@ -24,14 +24,14 @@ var { TouchableHighlight, TouchableNativeFeedback, View -} = React; +} from 'react-native'; -var getStyleFromScore = require('./getStyleFromScore'); -var getImageSource = require('./getImageSource'); -var getTextFromScore = require('./getTextFromScore'); +import getStyleFromScore from './getStyleFromScore'; +import getImageSource from './getImageSource'; +import getTextFromScore from './getTextFromScore'; -var MovieCell = React.createClass({ - render: function() { +export default class MovieCell extends Component { + render() { var criticsScore = this.props.movie.ratings.critics_score; var TouchableElement = TouchableHighlight; if (Platform.OS === 'android') { @@ -68,7 +68,7 @@ var MovieCell = React.createClass({ ); } -}); +} var styles = StyleSheet.create({ textContainer: { @@ -102,5 +102,3 @@ var styles = StyleSheet.create({ marginLeft: 4, }, }); - -module.exports = MovieCell; diff --git a/Examples/Movies/MovieScreen.js b/Examples/Movies/MovieScreen.js index ff228a8a3e7c57..766199fb5a63f0 100644 --- a/Examples/Movies/MovieScreen.js +++ b/Examples/Movies/MovieScreen.js @@ -15,21 +15,21 @@ */ 'use strict'; -var React = require('react-native'); -var { +import React, { + Component, Image, ScrollView, StyleSheet, Text, View, -} = React; +} from 'react-native'; -var getImageSource = require('./getImageSource'); -var getStyleFromScore = require('./getStyleFromScore'); -var getTextFromScore = require('./getTextFromScore'); +import getImageSource from './getImageSource'; +import getStyleFromScore from './getStyleFromScore'; +import getTextFromScore from './getTextFromScore'; -var MovieScreen = React.createClass({ - render: function() { +export default class MovieScreen extends Component { + render() { return ( @@ -59,11 +59,11 @@ var MovieScreen = React.createClass({ ); - }, -}); + } +} -var Ratings = React.createClass({ - render: function() { +class Ratings extends Component { + render() { var criticsScore = this.props.ratings.critics_score; var audienceScore = this.props.ratings.audience_score; @@ -83,27 +83,28 @@ var Ratings = React.createClass({ ); - }, -}); + } +} -var Cast = React.createClass({ - render: function() { - if (!this.props.actors) { +class Cast extends Component { + render() { + const { actors } = this.props; + if (!actors) { return null; } return ( Actors - {this.props.actors.map(actor => + {actors.map(actor => • {actor.name} )} ); - }, -}); + } +} var styles = StyleSheet.create({ contentContainer: { @@ -162,5 +163,3 @@ var styles = StyleSheet.create({ marginLeft: 2, }, }); - -module.exports = MovieScreen; diff --git a/Examples/Movies/MoviesApp.android.js b/Examples/Movies/MoviesApp.android.js index 5dfb57fd2fd2ba..1eb05e507c005c 100644 --- a/Examples/Movies/MoviesApp.android.js +++ b/Examples/Movies/MoviesApp.android.js @@ -16,18 +16,18 @@ */ 'use strict'; -var React = require('react-native'); -var { +import React, { AppRegistry, BackAndroid, + Component, Navigator, StyleSheet, ToolbarAndroid, View, -} = React; +} from 'react-native'; -var MovieScreen = require('./MovieScreen'); -var SearchScreen = require('./SearchScreen'); +import MovieScreen from './MovieScreen'; +import SearchScreen from './SearchScreen'; var _navigator; BackAndroid.addEventListener('hardwareBackPress', () => { @@ -38,7 +38,7 @@ BackAndroid.addEventListener('hardwareBackPress', () => { return false; }); -var RouteMapper = function(route, navigationOperations, onComponentRef) { +var RouteMapper = (route, navigationOperations, onComponentRef) => { _navigator = navigationOperations; if (route.name === 'search') { return ( @@ -64,8 +64,8 @@ var RouteMapper = function(route, navigationOperations, onComponentRef) { } }; -var MoviesApp = React.createClass({ - render: function() { +export default class MoviesApp extends Component { + render() { var initialRoute = {name: 'search'}; return ( ); } -}); +} var styles = StyleSheet.create({ container: { @@ -90,5 +90,3 @@ var styles = StyleSheet.create({ }); AppRegistry.registerComponent('MoviesApp', () => MoviesApp); - -module.exports = MoviesApp; diff --git a/Examples/Movies/MoviesApp.ios.js b/Examples/Movies/MoviesApp.ios.js index 1c6fc4b4fb451b..5f86e386760786 100644 --- a/Examples/Movies/MoviesApp.ios.js +++ b/Examples/Movies/MoviesApp.ios.js @@ -16,17 +16,17 @@ */ 'use strict'; -var React = require('react-native'); -var { +import React, { AppRegistry, + Component, NavigatorIOS, StyleSheet, -} = React; +} from 'react-native'; -var SearchScreen = require('./SearchScreen'); +import SearchScreen from './SearchScreen'; -var MoviesApp = React.createClass({ - render: function() { +export default class MoviesApp extends Component { + render() { return ( ); } -}); +} var styles = StyleSheet.create({ container: { @@ -47,5 +47,3 @@ var styles = StyleSheet.create({ }); AppRegistry.registerComponent('MoviesApp', () => MoviesApp); - -module.exports = MoviesApp; diff --git a/Examples/Movies/SearchBar.android.js b/Examples/Movies/SearchBar.android.js index 2e12ff48800779..8339c4309af8c0 100644 --- a/Examples/Movies/SearchBar.android.js +++ b/Examples/Movies/SearchBar.android.js @@ -16,8 +16,8 @@ */ 'use strict'; -var React = require('react-native'); -var { +import React, { + Component, Image, Platform, ProgressBarAndroid, @@ -25,14 +25,15 @@ var { StyleSheet, TouchableNativeFeedback, View, -} = React; +} from 'react-native'; var IS_RIPPLE_EFFECT_SUPPORTED = Platform.Version >= 21; -var SearchBar = React.createClass({ - render: function() { +export default class SearchBar extends Component { + render() { + const { isLoading, onSearchChange, onFocus } = this.props; var loadingView; - if (this.props.isLoading) { + if (isLoading) { loadingView = ( {loadingView} ); } -}); +} var styles = StyleSheet.create({ searchBar: { @@ -100,5 +101,3 @@ var styles = StyleSheet.create({ marginHorizontal: 8, }, }); - -module.exports = SearchBar; diff --git a/Examples/Movies/SearchBar.ios.js b/Examples/Movies/SearchBar.ios.js index f4a2354ef939c3..80faf5c4ada911 100644 --- a/Examples/Movies/SearchBar.ios.js +++ b/Examples/Movies/SearchBar.ios.js @@ -16,16 +16,16 @@ */ 'use strict'; -var React = require('react-native'); -var { +import React, { ActivityIndicatorIOS, + Component, TextInput, StyleSheet, View, -} = React; +} from 'react-native'; -var SearchBar = React.createClass({ - render: function() { +export default class SearchBar extends Component { + render() { return ( ); } -}); +} var styles = StyleSheet.create({ searchBar: { @@ -62,5 +62,3 @@ var styles = StyleSheet.create({ width: 30, }, }); - -module.exports = SearchBar; diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index 1bc6bb906ceaad..537aad4b614a3e 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -15,32 +15,31 @@ */ 'use strict'; -var React = require('react-native'); -var { +import React, { ActivityIndicatorIOS, + Component, ListView, Platform, ProgressBarAndroid, StyleSheet, Text, View, -} = React; -var TimerMixin = require('react-timer-mixin'); +} from 'react-native'; -var invariant = require('invariant'); -var dismissKeyboard = require('dismissKeyboard'); +import invariant from 'invariant'; +import dismissKeyboard from 'dismissKeyboard'; -var MovieCell = require('./MovieCell'); -var MovieScreen = require('./MovieScreen'); -var SearchBar = require('SearchBar'); +import MovieCell from './MovieCell'; +import MovieScreen from './MovieScreen'; +import SearchBar from 'SearchBar'; /** * This is for demo purposes only, and rate limited. * In case you want to use the Rotten Tomatoes' API on a real app you should * create an account at http://developer.rottentomatoes.com/ */ -var API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/'; -var API_KEYS = [ +const API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/'; +const API_KEYS = [ '7waqfqbprs7pajbz28mqf6vz', // 'y4vwv8m33hed9ety83jmv52f', Fallback api_key ]; @@ -57,13 +56,21 @@ var resultsCache = { var LOADING = {}; -var SearchScreen = React.createClass({ - mixins: [TimerMixin], +export default class SearchScreen extends Component { + constructor(props: any) { + super(props); - timeoutID: (null: any), + this.timeoutID = null; + + // There is no autobind in ES6. + // You need to bind functions manually. + this.onSearchChange = this.onSearchChange.bind(this); + this.onEndReached = this.onEndReached.bind(this); + this.renderFooter = this.renderFooter.bind(this); + this.renderRow = this.renderRow.bind(this); + this.renderSeparator = this.renderSeparator.bind(this); - getInitialState: function() { - return { + this.state = { isLoading: false, isLoadingTail: false, dataSource: new ListView.DataSource({ @@ -72,13 +79,17 @@ var SearchScreen = React.createClass({ filter: '', queryNumber: 0, }; - }, + } - componentDidMount: function() { + componentDidMount() { this.searchMovies(''); - }, + } + + componentWillUnMount() { + this.timeoutID && clearTimeout(this.timeoutID); + } - _urlForQueryAndPage: function(query: string, pageNumber: number): string { + _urlForQueryAndPage(query: string, pageNumber: number): string { var apiKey = API_KEYS[this.state.queryNumber % API_KEYS.length]; if (query) { return ( @@ -92,9 +103,9 @@ var SearchScreen = React.createClass({ '&page_limit=20&page=' + pageNumber ); } - }, + } - searchMovies: function(query: string) { + searchMovies(query: string) { this.timeoutID = null; this.setState({filter: query}); @@ -148,9 +159,9 @@ var SearchScreen = React.createClass({ }); }) .done(); - }, + } - hasMore: function(): boolean { + hasMore(): boolean { var query = this.state.filter; if (!resultsCache.dataForQuery[query]) { return true; @@ -159,9 +170,9 @@ var SearchScreen = React.createClass({ resultsCache.totalForQuery[query] !== resultsCache.dataForQuery[query].length ); - }, + } - onEndReached: function() { + onEndReached() { var query = this.state.filter; if (!this.hasMore() || this.state.isLoadingTail) { // We're already fetching or have all the elements so noop @@ -215,37 +226,38 @@ var SearchScreen = React.createClass({ }); }) .done(); - }, + } - getDataSource: function(movies: Array): ListView.DataSource { + getDataSource(movies: Array): ListView.DataSource { return this.state.dataSource.cloneWithRows(movies); - }, + } - selectMovie: function(movie: Object) { + selectMovie(movie: Object) { + const { navigator } = this.props; if (Platform.OS === 'ios') { - this.props.navigator.push({ + navigator.push({ title: movie.title, component: MovieScreen, passProps: {movie}, }); } else { dismissKeyboard(); - this.props.navigator.push({ + navigator.push({ title: movie.title, name: 'movie', movie: movie, }); } - }, + } - onSearchChange: function(event: Object) { + onSearchChange(event: Object) { var filter = event.nativeEvent.text.toLowerCase(); - this.clearTimeout(this.timeoutID); - this.timeoutID = this.setTimeout(() => this.searchMovies(filter), 100); - }, + clearTimeout(this.timeoutID); + this.timeoutID = setTimeout(() => this.searchMovies(filter), 100); + } - renderFooter: function() { + renderFooter() { if (!this.hasMore() || !this.state.isLoadingTail) { return ; } @@ -258,9 +270,9 @@ var SearchScreen = React.createClass({ ); } - }, + } - renderSeparator: function( + renderSeparator( sectionID: number | string, rowID: number | string, adjacentRowHighlighted: boolean @@ -272,9 +284,9 @@ var SearchScreen = React.createClass({ return ( ); - }, + } - renderRow: function( + renderRow( movie: Object, sectionID: number | string, rowID: number | string, @@ -289,9 +301,9 @@ var SearchScreen = React.createClass({ movie={movie} /> ); - }, + }; - render: function() { + render() { var content = this.state.dataSource.getRowCount() === 0 ? - this.refs.listview && this.refs.listview.getScrollResponder().scrollTo(0, 0)} + this.refs.listview && this.refs.listview.getScrollResponder().scrollTo({ x: 0, y: 0 })} /> {content} ); - }, -}); + } +} -var NoMovies = React.createClass({ - render: function() { +class NoMovies extends Component { + render() { var text = ''; - if (this.props.filter) { - text = `No results for "${this.props.filter}"`; - } else if (!this.props.isLoading) { + const { filter, isLoading } = this.props; + if (filter) { + text = `No results for "${filter}"`; + } else if (!isLoading) { // If we're looking at the latest movies, aren't currently loading, and // still have no results, show a message text = 'No movies found'; @@ -342,7 +355,7 @@ var NoMovies = React.createClass({ ); } -}); +} var styles = StyleSheet.create({ container: { @@ -372,5 +385,3 @@ var styles = StyleSheet.create({ opacity: 0.0, }, }); - -module.exports = SearchScreen; diff --git a/Examples/Movies/getImageSource.js b/Examples/Movies/getImageSource.js index 29ec4549ef1609..4623c1ad91c413 100644 --- a/Examples/Movies/getImageSource.js +++ b/Examples/Movies/getImageSource.js @@ -15,12 +15,10 @@ */ 'use strict'; -function getImageSource(movie: Object, kind: ?string): {uri: ?string} { +export default function getImageSource(movie: Object, kind: ?string): {uri: ?string} { var uri = movie && movie.posters ? movie.posters.thumbnail : null; if (uri && kind) { uri = uri.replace('tmb', kind); } return { uri }; } - -module.exports = getImageSource; diff --git a/Examples/Movies/getStyleFromScore.js b/Examples/Movies/getStyleFromScore.js index 1d5b599b62bc1c..02b8de2b56fd91 100644 --- a/Examples/Movies/getStyleFromScore.js +++ b/Examples/Movies/getStyleFromScore.js @@ -15,17 +15,16 @@ */ 'use strict'; -var React = require('react-native'); -var { +import React, { StyleSheet, -} = React; +} from 'react-native'; -var MAX_VALUE = 200; +const MAX_VALUE = 200; import type { StyleObj } from 'StyleSheetTypes'; -function getStyleFromScore(score: number): StyleObj { - if (score < 0) { +export default function getStyleFromScore(score: number): StyleObj { + if (!score || score < 0) { return styles.noScore; } @@ -44,5 +43,3 @@ var styles = StyleSheet.create({ color: '#999999', }, }); - -module.exports = getStyleFromScore; diff --git a/Examples/Movies/getTextFromScore.js b/Examples/Movies/getTextFromScore.js index 9593461b2efefd..96f81df3dd95c6 100644 --- a/Examples/Movies/getTextFromScore.js +++ b/Examples/Movies/getTextFromScore.js @@ -15,8 +15,6 @@ */ 'use strict'; -function getTextFromScore(score: number): string { +export default function getTextFromScore(score: number): string { return score > 0 ? score + '%' : 'N/A'; } - -module.exports = getTextFromScore; From 16fda9f933e54b375a70caa46ab91debc1a1577c Mon Sep 17 00:00:00 2001 From: "sunny.luo" Date: Sun, 2 Dec 2018 22:48:55 +0800 Subject: [PATCH 2/3] Add textAlign justify --- RNTester/js/TextExample.android.js | 8 ++++- .../views/text/ReactBaseTextShadowNode.java | 33 ++++++++++++------- .../react/views/text/ReactTextShadowNode.java | 21 +++++++----- .../react/views/text/ReactTextUpdate.java | 12 +++++-- .../react/views/text/ReactTextView.java | 5 +++ .../views/text/ReactTextViewManager.java | 6 +++- .../react/views/text/TextAttributeProps.java | 33 ++++++++++++------- .../textinput/ReactTextInputManager.java | 33 ++++++++++++------- .../textinput/ReactTextInputShadowNode.java | 3 +- .../react/views/text/ReactTextTest.java | 16 +++++++++ .../textinput/ReactTextInputPropertyTest.java | 6 ++++ 11 files changed, 130 insertions(+), 46 deletions(-) diff --git a/RNTester/js/TextExample.android.js b/RNTester/js/TextExample.android.js index a8b23ad87b2cba..ba87256b35fdf7 100644 --- a/RNTester/js/TextExample.android.js +++ b/RNTester/js/TextExample.android.js @@ -322,11 +322,17 @@ class TextExample extends React.Component<{}> { center center center center center center center center center center center - + right right right right right right right right right right right right right + + justify (works when api level >= 26 otherwise fallbacks to "left"): + this text component{"'"}s contents are laid out with "textAlign: + justify" and as you can see all of the lines except the last one + span the available width of the parent container. + diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index 05091cc328954f..c75929c844f3d0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -270,6 +270,8 @@ private static int parseNumericFontWeight(String fontWeightString) { protected int mTextAlign = Gravity.NO_GRAVITY; protected int mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY; + protected int mJustificationMode = + (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE; protected TextTransform mTextTransform = TextTransform.UNSET; protected float mTextShadowOffsetDx = 0; @@ -377,19 +379,28 @@ public void setAllowFontScaling(boolean allowFontScaling) { @ReactProp(name = ViewProps.TEXT_ALIGN) public void setTextAlign(@Nullable String textAlign) { - if (textAlign == null || "auto".equals(textAlign)) { - mTextAlign = Gravity.NO_GRAVITY; - } else if ("left".equals(textAlign)) { - mTextAlign = Gravity.LEFT; - } else if ("right".equals(textAlign)) { - mTextAlign = Gravity.RIGHT; - } else if ("center".equals(textAlign)) { - mTextAlign = Gravity.CENTER_HORIZONTAL; - } else if ("justify".equals(textAlign)) { - // Fallback gracefully for cross-platform compat instead of error + if ("justify".equals(textAlign)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mJustificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD; + } mTextAlign = Gravity.LEFT; } else { - throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; + } + + if (textAlign == null || "auto".equals(textAlign)) { + mTextAlign = Gravity.NO_GRAVITY; + } else if ("left".equals(textAlign)) { + mTextAlign = Gravity.LEFT; + } else if ("right".equals(textAlign)) { + mTextAlign = Gravity.RIGHT; + } else if ("center".equals(textAlign)) { + mTextAlign = Gravity.CENTER_HORIZONTAL; + } else { + throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + } + } markUpdated(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index 98d4e02e187be6..d4e01b09159446 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -97,14 +97,18 @@ public long measure( new StaticLayout( text, textPaint, hintWidth, alignment, 1.f, 0.f, mIncludeFontPadding); } else { - layout = + StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, hintWidth) - .setAlignment(alignment) - .setLineSpacing(0.f, 1.f) - .setIncludePad(mIncludeFontPadding) - .setBreakStrategy(mTextBreakStrategy) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) - .build(); + .setAlignment(alignment) + .setLineSpacing(0.f, 1.f) + .setIncludePad(mIncludeFontPadding) + .setBreakStrategy(mTextBreakStrategy) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder.setJustificationMode(mJustificationMode); + } + layout = builder.build(); } } else if (boring != null && (unconstrainedWidth || boring.width <= width)) { @@ -215,7 +219,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { getPadding(Spacing.END), getPadding(Spacing.BOTTOM), getTextAlign(), - mTextBreakStrategy); + mTextBreakStrategy, + mJustificationMode); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java index c31bb3c55ea52d..fd1344f0fb714d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextUpdate.java @@ -26,6 +26,7 @@ public class ReactTextUpdate { private final float mPaddingBottom; private final int mTextAlign; private final int mTextBreakStrategy; + private final int mJustificationMode; /** * @deprecated Use a non-deprecated constructor for ReactTextUpdate instead. This one remains @@ -49,7 +50,8 @@ public ReactTextUpdate( paddingEnd, paddingBottom, textAlign, - Layout.BREAK_STRATEGY_HIGH_QUALITY); + Layout.BREAK_STRATEGY_HIGH_QUALITY, + Layout.JUSTIFICATION_MODE_NONE); } public ReactTextUpdate( @@ -61,7 +63,8 @@ public ReactTextUpdate( float paddingEnd, float paddingBottom, int textAlign, - int textBreakStrategy) { + int textBreakStrategy, + int justificationMode) { mText = text; mJsEventCounter = jsEventCounter; mContainsImages = containsImages; @@ -71,6 +74,7 @@ public ReactTextUpdate( mPaddingBottom = paddingBottom; mTextAlign = textAlign; mTextBreakStrategy = textBreakStrategy; + mJustificationMode = justificationMode; } public Spannable getText() { @@ -108,4 +112,8 @@ public int getTextAlign() { public int getTextBreakStrategy() { return mTextBreakStrategy; } + + public int getJustificationMode() { + return mJustificationMode; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 4c7023f07ea490..d0fceead37abec 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -72,6 +72,11 @@ public void setText(ReactTextUpdate update) { setBreakStrategy(update.getTextBreakStrategy()); } } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (getJustificationMode() != update.getJustificationMode()) { + setJustificationMode(update.getJustificationMode()); + } + } } @Override 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 226776b7dd6d0d..380ab0d9eac511 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 @@ -82,6 +82,9 @@ public Object updateLocalData(ReactTextView view, ReactStylesDiffMap props, Reac // TODO add textBreakStrategy prop into local Data int textBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY; + // TODO add justificationMode prop into local Data + int justificationMode = Layout.JUSTIFICATION_MODE_NONE; + return new ReactTextUpdate( spanned, @@ -92,7 +95,8 @@ public Object updateLocalData(ReactTextView view, ReactStylesDiffMap props, Reac textViewProps.getEndPadding(), textViewProps.getBottomPadding(), textViewProps.getTextAlign(), - textBreakStrategy + textBreakStrategy, + justificationMode ); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java index cbf49e34dc0e84..e23961d75d8b44 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java @@ -50,6 +50,8 @@ public class TextAttributeProps { protected int mTextAlign = Gravity.NO_GRAVITY; protected int mTextBreakStrategy = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY; + protected int mJustificationMode = + (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) ? 0 : Layout.JUSTIFICATION_MODE_NONE; protected TextTransform mTextTransform = TextTransform.UNSET; protected float mTextShadowOffsetDx = 0; @@ -204,19 +206,28 @@ public void setAllowFontScaling(boolean allowFontScaling) { } public void setTextAlign(@Nullable String textAlign) { - if (textAlign == null || "auto".equals(textAlign)) { - mTextAlign = Gravity.NO_GRAVITY; - } else if ("left".equals(textAlign)) { - mTextAlign = Gravity.LEFT; - } else if ("right".equals(textAlign)) { - mTextAlign = Gravity.RIGHT; - } else if ("center".equals(textAlign)) { - mTextAlign = Gravity.CENTER_HORIZONTAL; - } else if ("justify".equals(textAlign)) { - // Fallback gracefully for cross-platform compat instead of error + if ("justify".equals(textAlign)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mJustificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD; + } mTextAlign = Gravity.LEFT; } else { - throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; + } + + if (textAlign == null || "auto".equals(textAlign)) { + mTextAlign = Gravity.NO_GRAVITY; + } else if ("left".equals(textAlign)) { + mTextAlign = Gravity.LEFT; + } else if ("right".equals(textAlign)) { + mTextAlign = Gravity.RIGHT; + } else if ("center".equals(textAlign)) { + mTextAlign = Gravity.CENTER_HORIZONTAL; + } else { + throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + } + } } 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 1b6d1e83767d44..d868054506c7f5 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 @@ -10,10 +10,12 @@ import android.graphics.PorterDuff; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.os.Build; import android.support.v4.content.ContextCompat; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; +import android.text.Layout; import android.text.Spannable; import android.text.TextWatcher; import android.util.TypedValue; @@ -430,19 +432,28 @@ public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineCol @ReactProp(name = ViewProps.TEXT_ALIGN) public void setTextAlign(ReactEditText view, @Nullable String textAlign) { - if (textAlign == null || "auto".equals(textAlign)) { - view.setGravityHorizontal(Gravity.NO_GRAVITY); - } else if ("left".equals(textAlign)) { - view.setGravityHorizontal(Gravity.LEFT); - } else if ("right".equals(textAlign)) { - view.setGravityHorizontal(Gravity.RIGHT); - } else if ("center".equals(textAlign)) { - view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL); - } else if ("justify".equals(textAlign)) { - // Fallback gracefully for cross-platform compat instead of error + if ("justify".equals(textAlign)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + view.setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD); + } view.setGravityHorizontal(Gravity.LEFT); } else { - throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + view.setJustificationMode(Layout.JUSTIFICATION_MODE_NONE); + } + + if (textAlign == null || "auto".equals(textAlign)) { + view.setGravityHorizontal(Gravity.NO_GRAVITY); + } else if ("left".equals(textAlign)) { + view.setGravityHorizontal(Gravity.LEFT); + } else if ("right".equals(textAlign)) { + view.setGravityHorizontal(Gravity.RIGHT); + } else if ("center".equals(textAlign)) { + view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL); + } else { + throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign); + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java index c009dfbcc65bf9..8d5c1b5b5224cb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java @@ -204,7 +204,8 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { getPadding(Spacing.RIGHT), getPadding(Spacing.BOTTOM), mTextAlign, - mTextBreakStrategy); + mTextBreakStrategy, + mJustificationMode); uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate); } } diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java index d4843406e088a7..441908c60e964c 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java @@ -17,6 +17,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; +import android.text.Layout; import android.text.Spanned; import android.text.TextUtils; import android.text.style.AbsoluteSizeSpan; @@ -424,6 +425,21 @@ public void testMaxLinesApplied() { assertThat(textView.getEllipsize()).isEqualTo(TextUtils.TruncateAt.END); } + @TargetApi(Build.VERSION_CODES.O) + @Test + public void testTextAlignJustifyApplied() { + UIManagerModule uiManager = getUIManagerModule(); + + ReactRootView rootView = createText( + uiManager, + JavaOnlyMap.of("textAlign", "justify"), + JavaOnlyMap.of(ReactRawTextShadowNode.PROP_TEXT, "test text")); + + TextView textView = (TextView) rootView.getChildAt(0); + assertThat(textView.getText().toString()).isEqualTo("test text"); + assertThat(textView.getJustificationMode()).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD); + } + /** * Make sure TextView has exactly one span and that span has given type. */ diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java index 50fc92e218d3ed..3bf3865b6e298d 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java @@ -9,8 +9,10 @@ import android.content.res.ColorStateList; import android.graphics.Color; +import android.os.Build; import android.text.InputType; import android.text.InputFilter; +import android.text.Layout; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.inputmethod.EditorInfo; @@ -344,6 +346,10 @@ public void testTextAlign() { assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_HORIZONTAL); mManager.updateProperties(view, buildStyles("textAlign", null)); assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(defaultHorizontalGravity); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mManager.updateProperties(view, buildStyles("textAlign", "justify")); + assertThat(view.getJustificationMode()).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD); + } // TextAlignVertical mManager.updateProperties(view, buildStyles("textAlignVertical", "top")); From 8d7121a661abf969867713a08aca9935e08b187a Mon Sep 17 00:00:00 2001 From: "sunny.luo" Date: Sun, 2 Dec 2018 22:49:49 +0800 Subject: [PATCH 3/3] Prettier --- RNTester/js/TextExample.android.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RNTester/js/TextExample.android.js b/RNTester/js/TextExample.android.js index ba87256b35fdf7..a1009301c3f592 100644 --- a/RNTester/js/TextExample.android.js +++ b/RNTester/js/TextExample.android.js @@ -322,7 +322,7 @@ class TextExample extends React.Component<{}> { center center center center center center center center center center center - + right right right right right right right right right right right right right