React Native chat bubble flexbox similar to whatsapp - react-native

I'm trying to create a chat view in React Native similar to the Whatsapp UI.
What I can't wrap my head around is - how to do the time. Here is how it looks in Whatsapp:
Notice that the time is not on a completely new line by itself, but half a line.
At the same time in the last comment you can see that if the text is long enough, the time is being pushed to a new line.
I've tried several things:
Make the bubble relative and then use absolute positioning on the time element. Unfortunately if the text is long enough my time overlaps the text.
Make the bubble flexDirection: "row", and then have the text in 1 view and the time in another view. But I can't figure out how to force a wrap when the text is "long enough".
Any ideas how to achieve this?

I managed to kind of get what I want. It's not 100% the same as whatsapp, but good enough.
I had the following code:
<View style={{
margin: 10, marginTop: 10, marginBottom: 0,
padding: 10, borderRadius: 5, paddingBottom: 10,
backgroundColor: cropType ? '#93D14C' : '#F1F0F5'
}}>
{this.renderContent(comment)}
<Text
style={[styles.muted,
{
color: constants.FJMUTEDLIGHT,
backgroundColor: 'transparent',
alignSelf: 'flex-end',
fontSize: 12,
marginBottom: -5,
marginTop: 2
}]}>{moment.utc(comment.created).local().format('HH:mm')}</Text>
</View>
The renderContentHere() function can render Text, but it also can render a complex View containing text. Initially I wanted to figure out how many lines a text has and to figure the width of the lines. This doesn't seem to be possible with React Native since the onLayout property on a Text node can only tell you the width and height of the element, but not if the Text is 2 lines or 3 lines.
So I decided to just measure the View that contains the comment and figure out if it has one or more lines. If it has one line I'm assuming that I can place the time a little higher than normally.
This is what I ended up doing:
<View style={{
margin: 10, marginTop: 10, marginBottom: 0,
padding: 10, borderRadius: 5, paddingBottom: 10,
backgroundColor: cropType ? '#93D14C' : '#F1F0F5'
}}>
<View style={this.state.oneLine ? {
// Since we don't know how long the one line is, just in case add 25px padding on the right
// this way even if we are dealing with a long line of text it won't end up over the time
paddingRight: 25
} : {}}
onLayout={(e) => {
let {height} = e.nativeEvent.layout
// if height is less than 30px, then we are dealing with a single line of content
if (height < 30) {
this.setState({'oneLine': true})
}
}}>
{this.renderContent(comment)}
</View>
<Text
style={[styles.muted,
{
color: constants.FJMUTEDLIGHT,
backgroundColor: 'transparent',
alignSelf: 'flex-end',
fontSize: 12,
marginBottom: -5,
// If one line, move the time up - there is enough space
marginTop: this.state.oneLine ? -10 : 2
}]}>{moment.utc(comment.created).local().format('HH:mm')}</Text>
</View>
If we have a single line it still possible that the text will end up behind the time, so that's why I'm applying a right padding on the Text of 25 px. This breaks long lines and we end up with a view like this:

Related

How to center oversize font inside Text's bounding box?

I'm trying to create a simple Floating Action Bar button with a plus icon in it, and have had trouble true-centering the "plus" in some edge cases. I was just using '\uFF0B' in a <Text>, but tried to switch to react-native-vector-icons, only to discover that they too were using a font and not an image to back the <Icon> instances, and that my problems seem to persist.
Things are fine on most screens and devices but in some cases users are reporting the plus icon is not perfectly centered. I have a hypothesis that it may involve users' accessibility options increasing the font size in the app beyond size of the parent View. At any rate I can reproduce something like the screenshots folks are sharing with me by setting the fontSize greater than the lineHeight. Assuming that is the issue -
How do you center a single glyph within the view area of a <Text> (or <Icon>, since that derives from <Text>), even when the fontSize may be much larger than the <Text>'s lineHeight or even overall height?
In the below example, the "+" font size is exactly double the line-height, so the plus is centered smack dab on the upper-right corner of the view area, as though it were expecting to be in a box that was 112dp x 112dp; but I want it centered dead-center of the 56dp x 56dp box instead, with the arms of the plus cropped. No combination of style attributes seems to effect it, but rather just controls where the <Icon> positions within its parent.
Currently:
Normally:
For oversized font:
Code:
<View style={s.fabStyle}>
<TouchableOpacity onPress={()=>{this.onPlus()}}>
<Icon name="plus" style={s.fabText} />
</TouchableOpacity>
</View>
...
const s = StyleSheet.create({
fabStyle: {
position: 'absolute',
right: 16,
bottom: 16,
borderRadius: 28,
width: 56,
height: 56,
backgroundColor: styleConstants.color.primary,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
},
fabText: {
position: 'relative',
left: 0,
top: 0,
right: 0,
bottom: 0,
fontSize: 112,
color: '#fff',
textAlign: 'center',
lineHeight: 56,
width: 56,
height: 56,
},
});
This isn't an answer to the question itself, which still stands, but an answer to the underlying issue, in case somebody arrives here by Google search with a similar issue. In my case it was indeed the case that accessibility settings were causing font to be bigger than it was designed to be, thus triggering the above scenario. While I still don't know how to center the text adequately in this case, in my case the issue could be circumvented by making sure allowFontScaling=false for relevant Views holding text.

Make child container occupy height of parent container [React Native]

I made a <View> object in react native containing, in a row, multiple <View> objects that contain text objects.
When the text is more than 1 line, the text containers do not expand to fill the parent component and it looks funny.
Screenshot:
This is the style code:
bigContainer: {
flex: 1,
justifyContent: 'center',
// flexDirection: 'row'
},
textContainer: {
flex: 1,
paddingLeft: 0,
paddingRight: 0,
borderRightWidth: 1,
borderRadius: 0,
borderColor: 'rbga(0, 0, 0, 0.1)',
alignItems: 'center',
justifyContent: 'center',
paddingTop: 7,
paddingBottom: 7,
overflow: 'hidden'
},
text: {
fontSize: 18,
textAlign: 'center'
},
Please help, I do not understand why this happens since I have flex: 1 and my research has not helped me. Thanks !!
UPDATE: SOLVED: I forgot that <TouchableOpacity>, the container that encloses the text container, also behaves like a flexbox
In React Native flex works a bit differently, as it only accepts a single number. This is because of the Yoga layout engine facebook uses
flex: 1 does not mean "grow as much as you need to". When you assign a group of child components that property, what you're essentially saying is that each element with the flex: 1 property should take up as much space as each other element. Think of flex: 1 as assigning a ratio. If you have 1 element with no siblings, it will attempt to fill all available space. If you have 4 elements and they are all siblings, and they are all set to flex: 1 then each will take 1/4 (25%) of the available space.
This can be manipulated as well, say you have 4 elements and 3 of them are set to flex: 1. You can set the 4th to flex: 2. That 4th element will now use twice as much space as the other components (40% of available space as opposed to 20%). Flex can also be set to a negative number, which means it will shrink to it's min height and width when needed.
This behavior is pretty much identical to how the flex-grow property works in CSS. If you want to manipulate the initial size of a flex container without respect to it's siblings, you should use the flex-basis (flexBasis in react) property. flexBasis: content will resize the container based on it's content. flexBasis also accepts a number, if you would like to manually set the initial width of your containers.

width: '100%' vs Dimension.get('window').width in react native

I'm new to react native and css styling as a whole so sorry if the question is very basic. I want a view to take 100% of the available screen width and when i use the code below my view seems to go outside the screen boundry, though when I use Dimension.get('window').width it work just fine. can someone explain how they differ from each other. any help would be really appreciated. thanks
return(
<TouchableOpacity style = {styles.food_wrapper}
onPress = {()=> this.userAction()}
>
<Text style = {styles.foodname}>
{this.name}
</Text>
<Text style = {styles.foodprice}>
Rs: {this.price}
</Text>
</TouchableOpacity>
);
food_wrapper:{
flex: 1,
flexDirection :'row',
justifyContent:'space-between',
alignItems:'flex-start',
width: '100%',//Dimensions.get('window').width,
minHeight: 50,
marginVertical: '1%',
padding: '2%',
backgroundColor: 'rgb(155,200,200)'
},
You need to understand what is the basic difference from 100% and the width you get using dimension.get('window')
100% means you want to get the container 100% width which mean if your parent container is 50px then 100% will return 50px.
the width from dimension give you a static width of your device width
so be careful to choose what to use to your component
if you want to follow the parent container then it is easier to use 100% then width from dimension but for responsive reasons without any parent container or it is the parent itself then width from dimension will work better
You need to add a wrapper view with flexDirection: 'row', then style the child view (or Touchable or whatever) with flex: 1
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1, height: 100, backgroundColor: 'grey' }} />
</View>
Or you could use alignSelf: 'stretch' inside a few with flexDirection: 'column' (which is the default for all views)
Hang in there. Learning flexbox takes some practice. Your first thought when you get stuck should be "do I need to add a wrapper view?"

React Native: Perfectly align Text in circle

I have a circle button (made with borderRadius) in React Native. The text in the component should be centered both vertically and horizonatlly.
Horyzontally it's fine, but the vertical alignment seems to fail whatever I do. Even if it looks good on large cicles with small fontSize, the small circles proof it wrong!
<View style = {{
alignItems:'center',
justifyContent:'center',
backgroundColor:'yellow',
borderColor: this.props.color,
width:size, height:size,
borderRadius:size,
borderWidth:borderWidth,
}}>
<Text style = {{
textAlign: 'center',
backgroundColor:'none',
fontSize:fontSize,
lineHeight:fontSize,
}}>
{this.props.title}
</Text>
</View>
Although already answered elsewhere, I'm unable to center text (in this case) in a circle properly.
As one can see on the image with the green background of the <Text>-Component, the text is just not centered perfectly. Even though the itself is perfecttly aligned...
Here is a snack for Expo with the whole code reduced to the necessary and with different example sizes: https://repl.it/#PaulHuchner/Centered-Text-in-Circles
I have tried the previous answer with only Text and calculating line-height. which looks like a little overkill and didn't work for me. So here's my answer.
I am using View as the container with justifyContent:center
<View style={{
width: 40,
height: 40,
borderRadius: 20,
borderWidth: 1,
borderColor: 'black',
borderStyle: 'solid',
justifyContent: 'center'}}>
<Text style={{fontSize: 20,textAlign: 'center'}}>20</Text></View>
You're trying to set the same fontSize and lineHeight as the circle's diameter, which has borderWidth of 10 included to it.
Due to the borderWidth, the text is being cut and overlayed over the circle. The lineHeight assigned to the cut Text is more than required, hence it is displayed misaligned.
Therefore you need to reduce the fontSize and the lineHeight based on the borderRadius of the circle, to function properly for all dimensions.
<Text style = {{
textAlign: 'center',
backgroundColor:'green',
fontSize:fontSize - 2 * borderWidth, //... One for top and one for bottom alignment
lineHeight:fontSize - (Platform.OS === 'ios' ? 2 * borderWidth : borderWidth), //... One for top and one for bottom alignment
}}>
Here's a snack link
The solution that worked the best for me was instead of using a Text Element, instead, use a plus Icon. The difference is that the viewBox of "+" as a character isn't centered.
If that is confusing look at these three letters
A+a
Notice that "A" is taller than "+" and also "a". So instead, use a PLUS icon instead and it will be perfectly centered such as 24x24 px. This drove me mad!

Setting a border for react native TextInput

Using React native 0.26,
My component is something like this
const Search = () => {
return (
<View style={styles.backgroundImage}>
<TextInput style={styles.textInput} onChangeText={(text) => console.log(text)} placeholder={"Enter Search Term"}/>
</View>
)
}
And my styles :
const styles = StyleSheet.create({
backgroundImage: {
flex : 1,
flexDirection: "column",
justifyContent: 'center',
alignItems: 'center'
},
textInput: {
justifyContent: "center",
alignItems: "stretch",
borderRightWidth: 30,
borderLeftWidth: 30,
height: 50,
borderColor: "#FFFFFF",
}
})
The problems that I am facing are
The border right width and left width do not seem to have any effect and the placeholder text just begins on the left edge.
The background of TextInput is "grey" its the same as the View's background. I was expecting it to be white by default, (Feels transparent).
With iOS simulator how to bring up the keyboard, I would like to set returnKeyType and see how the keyboard looks (and have some code on onSubmitEditing and test).
Screenshot below :
1 You cannot declare a specific border directly on the TextInput unless multiline is enabled (For example borderLeftWidth will not work unless multiline={true} is enabled but borderWidth will work), but you can just wrap the TextInput in a View and give it a border.
inputContainer: {
borderLeftWidth: 4,
borderRightWidth: 4,
height: 70
},
input: {
height: 70,
backgroundColor: '#ffffff',
paddingLeft: 15,
paddingRight: 15
}
2 You need to declare a backgroundColor for the TextInput.
3 To make the native keyboard show up, you need to go to the simulator menu and disconnect your hardware. Go to Hardware -> Keyboard -> Connect Hardware Keyboard, toggle it off.
As of react-native: 0.61.5 you can directly set the borderBottomWidth on TextInput. Like below in inline style or if you want in separate style object.
style = {{borderBottomWidth : 1.0}}
By default the boderWidth will be set for 0. So use borderWidth : 5 defaults for (Top, Right, Bottom, Left).
If you want to asign width individually you have options like,
style = {{
borderStartWidth : 2
borderEndWidth : 3
borderTopWidth : 1
boderLeftWidth: 2
borderRightWidth: 3
borderBottomWidth : 4
}}