Focus the text input on tap during the composer
parent
302acaccb6
commit
726ff6bb01
|
@ -9,6 +9,7 @@ import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TextInput,
|
TextInput,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import LinearGradient from 'react-native-linear-gradient'
|
import LinearGradient from 'react-native-linear-gradient'
|
||||||
|
@ -88,6 +89,9 @@ export const ComposePost = observer(function ComposePost({
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const onPressContainer = () => {
|
||||||
|
textInput.current?.focus()
|
||||||
|
}
|
||||||
const onPressSelectPhotos = () => {
|
const onPressSelectPhotos = () => {
|
||||||
if (isSelectingPhotos) {
|
if (isSelectingPhotos) {
|
||||||
setIsSelectingPhotos(false)
|
setIsSelectingPhotos(false)
|
||||||
|
@ -183,156 +187,162 @@ export const ComposePost = observer(function ComposePost({
|
||||||
testID="composePostView"
|
testID="composePostView"
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
style={[pal.view, styles.outer]}>
|
style={[pal.view, styles.outer]}>
|
||||||
<SafeAreaView style={s.flex1}>
|
<TouchableWithoutFeedback onPressIn={onPressContainer}>
|
||||||
<View style={styles.topbar}>
|
<SafeAreaView style={s.flex1}>
|
||||||
<TouchableOpacity
|
<View style={styles.topbar}>
|
||||||
testID="composerCancelButton"
|
|
||||||
onPress={onPressCancel}>
|
|
||||||
<Text style={[pal.link, s.f18]}>Cancel</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={s.flex1} />
|
|
||||||
{isProcessing ? (
|
|
||||||
<View style={styles.postBtn}>
|
|
||||||
<ActivityIndicator />
|
|
||||||
</View>
|
|
||||||
) : canPost ? (
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
testID="composerPublishButton"
|
testID="composerCancelButton"
|
||||||
onPress={onPressPublish}>
|
onPress={onPressCancel}>
|
||||||
<LinearGradient
|
<Text style={[pal.link, s.f18]}>Cancel</Text>
|
||||||
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
|
||||||
start={{x: 0, y: 0}}
|
|
||||||
end={{x: 1, y: 1}}
|
|
||||||
style={styles.postBtn}>
|
|
||||||
<Text style={[s.white, s.f16, s.bold]}>
|
|
||||||
{replyTo ? 'Reply' : 'Post'}
|
|
||||||
</Text>
|
|
||||||
</LinearGradient>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
) : (
|
<View style={s.flex1} />
|
||||||
<View style={[styles.postBtn, pal.btn]}>
|
{isProcessing ? (
|
||||||
<Text style={[pal.textLight, s.f16, s.bold]}>Post</Text>
|
<View style={styles.postBtn}>
|
||||||
</View>
|
<ActivityIndicator />
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
{isProcessing ? (
|
|
||||||
<View style={[pal.btn, styles.processingLine]}>
|
|
||||||
<Text style={s.black}>{processingState}</Text>
|
|
||||||
</View>
|
|
||||||
) : undefined}
|
|
||||||
{error !== '' && (
|
|
||||||
<View style={styles.errorLine}>
|
|
||||||
<View style={styles.errorIcon}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="exclamation"
|
|
||||||
style={{color: colors.red4}}
|
|
||||||
size={10}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text style={[s.red4, s.flex1]}>{error}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<ScrollView style={s.flex1}>
|
|
||||||
{replyTo ? (
|
|
||||||
<View style={[pal.border, styles.replyToLayout]}>
|
|
||||||
<UserAvatar
|
|
||||||
handle={replyTo.author.handle}
|
|
||||||
displayName={replyTo.author.displayName}
|
|
||||||
avatar={replyTo.author.avatar}
|
|
||||||
size={50}
|
|
||||||
/>
|
|
||||||
<View style={styles.replyToPost}>
|
|
||||||
<TextLink
|
|
||||||
type="xl-medium"
|
|
||||||
href={`/profile/${replyTo.author.handle}`}
|
|
||||||
text={replyTo.author.displayName || replyTo.author.handle}
|
|
||||||
style={[pal.text]}
|
|
||||||
/>
|
|
||||||
<Text type="post-text" style={pal.text} numberOfLines={6}>
|
|
||||||
{replyTo.text}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
) : canPost ? (
|
||||||
) : undefined}
|
<TouchableOpacity
|
||||||
<View
|
testID="composerPublishButton"
|
||||||
style={[pal.border, styles.textInputLayout, selectTextInputLayout]}>
|
onPress={onPressPublish}>
|
||||||
<UserAvatar
|
<LinearGradient
|
||||||
handle={store.me.handle || ''}
|
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
||||||
displayName={store.me.displayName}
|
start={{x: 0, y: 0}}
|
||||||
avatar={store.me.avatar}
|
end={{x: 1, y: 1}}
|
||||||
size={50}
|
style={styles.postBtn}>
|
||||||
/>
|
<Text style={[s.white, s.f16, s.bold]}>
|
||||||
<TextInput
|
{replyTo ? 'Reply' : 'Post'}
|
||||||
testID="composerTextInput"
|
</Text>
|
||||||
ref={textInput}
|
</LinearGradient>
|
||||||
multiline
|
</TouchableOpacity>
|
||||||
scrollEnabled
|
|
||||||
onChangeText={(text: string) => onChangeText(text)}
|
|
||||||
placeholder={selectTextInputPlaceholder}
|
|
||||||
placeholderTextColor={pal.colors.textLight}
|
|
||||||
style={[pal.text, styles.textInput]}>
|
|
||||||
{textDecorated}
|
|
||||||
</TextInput>
|
|
||||||
</View>
|
|
||||||
<SelectedPhoto
|
|
||||||
selectedPhotos={selectedPhotos}
|
|
||||||
onSelectPhotos={onSelectPhotos}
|
|
||||||
/>
|
|
||||||
</ScrollView>
|
|
||||||
{isSelectingPhotos &&
|
|
||||||
localPhotos.photos != null &&
|
|
||||||
selectedPhotos.length < 4 && (
|
|
||||||
<PhotoCarouselPicker
|
|
||||||
selectedPhotos={selectedPhotos}
|
|
||||||
onSelectPhotos={onSelectPhotos}
|
|
||||||
localPhotos={localPhotos}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<View style={[pal.border, styles.bottomBar]}>
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="composerSelectPhotosButton"
|
|
||||||
onPress={onPressSelectPhotos}
|
|
||||||
style={[s.pl5]}
|
|
||||||
hitSlop={HITSLOP}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={['far', 'image']}
|
|
||||||
style={selectedPhotos.length < 4 ? pal.link : pal.textLight}
|
|
||||||
size={24}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={s.flex1} />
|
|
||||||
<Text style={[s.mr10, {color: progressColor}]}>
|
|
||||||
{MAX_TEXT_LENGTH - text.length}
|
|
||||||
</Text>
|
|
||||||
<View>
|
|
||||||
{text.length > DANGER_TEXT_LENGTH ? (
|
|
||||||
<ProgressPie
|
|
||||||
size={30}
|
|
||||||
borderWidth={4}
|
|
||||||
borderColor={progressColor}
|
|
||||||
color={progressColor}
|
|
||||||
progress={Math.min(
|
|
||||||
(text.length - MAX_TEXT_LENGTH) / MAX_TEXT_LENGTH,
|
|
||||||
1,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<ProgressCircle
|
<View style={[styles.postBtn, pal.btn]}>
|
||||||
size={30}
|
<Text style={[pal.textLight, s.f16, s.bold]}>Post</Text>
|
||||||
borderWidth={1}
|
</View>
|
||||||
borderColor={colors.gray2}
|
|
||||||
color={progressColor}
|
|
||||||
progress={text.length / MAX_TEXT_LENGTH}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
{isProcessing ? (
|
||||||
<Autocomplete
|
<View style={[pal.btn, styles.processingLine]}>
|
||||||
active={autocompleteView.isActive}
|
<Text style={s.black}>{processingState}</Text>
|
||||||
items={autocompleteView.suggestions}
|
</View>
|
||||||
onSelect={onSelectAutocompleteItem}
|
) : undefined}
|
||||||
/>
|
{error !== '' && (
|
||||||
</SafeAreaView>
|
<View style={styles.errorLine}>
|
||||||
|
<View style={styles.errorIcon}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="exclamation"
|
||||||
|
style={{color: colors.red4}}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={[s.red4, s.flex1]}>{error}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<ScrollView style={s.flex1}>
|
||||||
|
{replyTo ? (
|
||||||
|
<View style={[pal.border, styles.replyToLayout]}>
|
||||||
|
<UserAvatar
|
||||||
|
handle={replyTo.author.handle}
|
||||||
|
displayName={replyTo.author.displayName}
|
||||||
|
avatar={replyTo.author.avatar}
|
||||||
|
size={50}
|
||||||
|
/>
|
||||||
|
<View style={styles.replyToPost}>
|
||||||
|
<TextLink
|
||||||
|
type="xl-medium"
|
||||||
|
href={`/profile/${replyTo.author.handle}`}
|
||||||
|
text={replyTo.author.displayName || replyTo.author.handle}
|
||||||
|
style={[pal.text]}
|
||||||
|
/>
|
||||||
|
<Text type="post-text" style={pal.text} numberOfLines={6}>
|
||||||
|
{replyTo.text}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
) : undefined}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
pal.border,
|
||||||
|
styles.textInputLayout,
|
||||||
|
selectTextInputLayout,
|
||||||
|
]}>
|
||||||
|
<UserAvatar
|
||||||
|
handle={store.me.handle || ''}
|
||||||
|
displayName={store.me.displayName}
|
||||||
|
avatar={store.me.avatar}
|
||||||
|
size={50}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
testID="composerTextInput"
|
||||||
|
ref={textInput}
|
||||||
|
multiline
|
||||||
|
scrollEnabled
|
||||||
|
onChangeText={(text: string) => onChangeText(text)}
|
||||||
|
placeholder={selectTextInputPlaceholder}
|
||||||
|
placeholderTextColor={pal.colors.textLight}
|
||||||
|
style={[pal.text, styles.textInput]}>
|
||||||
|
{textDecorated}
|
||||||
|
</TextInput>
|
||||||
|
</View>
|
||||||
|
<SelectedPhoto
|
||||||
|
selectedPhotos={selectedPhotos}
|
||||||
|
onSelectPhotos={onSelectPhotos}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
|
{isSelectingPhotos &&
|
||||||
|
localPhotos.photos != null &&
|
||||||
|
selectedPhotos.length < 4 && (
|
||||||
|
<PhotoCarouselPicker
|
||||||
|
selectedPhotos={selectedPhotos}
|
||||||
|
onSelectPhotos={onSelectPhotos}
|
||||||
|
localPhotos={localPhotos}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<View style={[pal.border, styles.bottomBar]}>
|
||||||
|
<TouchableOpacity
|
||||||
|
testID="composerSelectPhotosButton"
|
||||||
|
onPress={onPressSelectPhotos}
|
||||||
|
style={[s.pl5]}
|
||||||
|
hitSlop={HITSLOP}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={['far', 'image']}
|
||||||
|
style={selectedPhotos.length < 4 ? pal.link : pal.textLight}
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={s.flex1} />
|
||||||
|
<Text style={[s.mr10, {color: progressColor}]}>
|
||||||
|
{MAX_TEXT_LENGTH - text.length}
|
||||||
|
</Text>
|
||||||
|
<View>
|
||||||
|
{text.length > DANGER_TEXT_LENGTH ? (
|
||||||
|
<ProgressPie
|
||||||
|
size={30}
|
||||||
|
borderWidth={4}
|
||||||
|
borderColor={progressColor}
|
||||||
|
color={progressColor}
|
||||||
|
progress={Math.min(
|
||||||
|
(text.length - MAX_TEXT_LENGTH) / MAX_TEXT_LENGTH,
|
||||||
|
1,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ProgressCircle
|
||||||
|
size={30}
|
||||||
|
borderWidth={1}
|
||||||
|
borderColor={colors.gray2}
|
||||||
|
color={progressColor}
|
||||||
|
progress={text.length / MAX_TEXT_LENGTH}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Autocomplete
|
||||||
|
active={autocompleteView.isActive}
|
||||||
|
items={autocompleteView.suggestions}
|
||||||
|
onSelect={onSelectAutocompleteItem}
|
||||||
|
/>
|
||||||
|
</SafeAreaView>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue