Fixes to the composer UX around images and scrolling

This commit is contained in:
Paul Frazee 2022-12-16 14:48:37 -06:00
parent 3aded6887d
commit 4ef3afb604
5 changed files with 116 additions and 80 deletions

View file

@ -4,6 +4,7 @@ import {
ActivityIndicator,
KeyboardAvoidingView,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TextInput,
@ -32,6 +33,7 @@ import {SelectedPhoto} from './SelectedPhoto'
const MAX_TEXT_LENGTH = 256
const DANGER_TEXT_LENGTH = MAX_TEXT_LENGTH
const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
export const ComposePost = observer(function ComposePost({
replyTo,
@ -48,6 +50,7 @@ export const ComposePost = observer(function ComposePost({
const [processingState, setProcessingState] = useState('')
const [error, setError] = useState('')
const [text, setText] = useState('')
const [isSelectingPhotos, setIsSelectingPhotos] = useState(false)
const [selectedPhotos, setSelectedPhotos] = useState<string[]>([])
const autocompleteView = useMemo<UserAutocompleteViewModel>(
@ -81,10 +84,17 @@ export const ComposePost = observer(function ComposePost({
}
}, [])
const onPressSelectPhotos = () => {
if (isSelectingPhotos) {
setIsSelectingPhotos(false)
} else if (selectedPhotos.length < 4) {
setIsSelectingPhotos(true)
}
}
const onSelectPhotos = (photos: string[]) => {
setSelectedPhotos(photos)
setIsSelectingPhotos(false)
}
const onChangeText = (newText: string) => {
setText(newText)
@ -211,55 +221,71 @@ export const ComposePost = observer(function ComposePost({
<Text style={[s.red4, s.flex1]}>{error}</Text>
</View>
)}
{replyTo ? (
<View style={styles.replyToLayout}>
<ScrollView style={s.flex1}>
{replyTo ? (
<View style={styles.replyToLayout}>
<UserAvatar
handle={replyTo.author.handle}
displayName={replyTo.author.displayName}
avatar={replyTo.author.avatar}
size={50}
/>
<View style={styles.replyToPost}>
<TextLink
href={`/profile/${replyTo.author.handle}`}
text={replyTo.author.displayName || replyTo.author.handle}
style={[s.f16, s.bold]}
/>
<Text style={[s.f16, s['lh16-1.3']]} numberOfLines={6}>
{replyTo.text}
</Text>
</View>
</View>
) : undefined}
<View style={[styles.textInputLayout, selectTextInputLayout]}>
<UserAvatar
handle={replyTo.author.handle}
displayName={replyTo.author.displayName}
avatar={replyTo.author.avatar}
handle={store.me.handle || ''}
displayName={store.me.displayName}
avatar={store.me.avatar}
size={50}
/>
<View style={styles.replyToPost}>
<TextLink
href={`/profile/${replyTo.author.handle}`}
text={replyTo.author.displayName || replyTo.author.handle}
style={[s.f16, s.bold]}
/>
<Text style={[s.f16, s['lh16-1.3']]} numberOfLines={6}>
{replyTo.text}
</Text>
</View>
<TextInput
ref={textInput}
multiline
scrollEnabled
onChangeText={(text: string) => onChangeText(text)}
placeholder={selectTextInputPlaceholder}
style={styles.textInput}>
{textDecorated}
</TextInput>
</View>
) : undefined}
<View style={[styles.textInputLayout, selectTextInputLayout]}>
<UserAvatar
handle={store.me.handle || ''}
displayName={store.me.displayName}
avatar={store.me.avatar}
size={50}
/>
<TextInput
ref={textInput}
multiline
scrollEnabled
onChangeText={(text: string) => onChangeText(text)}
placeholder={selectTextInputPlaceholder}
style={styles.textInput}>
{textDecorated}
</TextInput>
</View>
<SelectedPhoto
selectedPhotos={selectedPhotos}
onSelectPhotos={onSelectPhotos}
/>
{localPhotos.photos != null && selectedPhotos.length < 4 && (
<PhotoCarouselPicker
<SelectedPhoto
selectedPhotos={selectedPhotos}
onSelectPhotos={onSelectPhotos}
localPhotos={localPhotos}
/>
)}
</ScrollView>
{isSelectingPhotos &&
localPhotos.photos != null &&
selectedPhotos.length < 4 && (
<PhotoCarouselPicker
selectedPhotos={selectedPhotos}
onSelectPhotos={onSelectPhotos}
localPhotos={localPhotos}
/>
)}
<View style={styles.bottomBar}>
<TouchableOpacity
onPress={onPressSelectPhotos}
style={[s.pl5]}
hitSlop={HITSLOP}>
<FontAwesomeIcon
icon={['far', 'image']}
style={{
color: selectedPhotos.length < 4 ? colors.blue3 : colors.gray3,
}}
size={24}
/>
</TouchableOpacity>
<View style={s.flex1} />
<Text style={[s.mr10, {color: progressColor}]}>
{MAX_TEXT_LENGTH - text.length}
@ -392,5 +418,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
borderTopWidth: 1,
borderTopColor: colors.gray2,
backgroundColor: colors.white,
},
})

View file

@ -86,6 +86,11 @@ export const PhotoCarouselPicker = ({
style={{color: colors.blue3}}
/>
</TouchableOpacity>
<TouchableOpacity
style={[styles.galleryButton, styles.photo]}
onPress={handleOpenGallery}>
<FontAwesomeIcon icon="image" style={{color: colors.blue3}} size={24} />
</TouchableOpacity>
{localPhotos.photos.map((item: any, index: number) => (
<TouchableOpacity
key={`local-image-${index}`}
@ -94,11 +99,6 @@ export const PhotoCarouselPicker = ({
<Image style={styles.photo} source={{uri: item.node.image.uri}} />
</TouchableOpacity>
))}
<TouchableOpacity
style={[styles.galleryButton, styles.photo]}
onPress={handleOpenGallery}>
<FontAwesomeIcon icon="image" style={{color: colors.blue3}} size={24} />
</TouchableOpacity>
</ScrollView>
)
}
@ -109,6 +109,7 @@ const styles = StyleSheet.create({
maxHeight: 96,
padding: 8,
overflow: 'hidden',
backgroundColor: colors.white,
},
galleryButton: {
borderWidth: 1,

View file

@ -193,7 +193,7 @@ export const FeedItem = observer(function FeedItem({
style={styles.postText}
/>
</View>
<PostEmbeds embed={item.embed} style={{marginBottom: 10}} />
<PostEmbeds embed={item.embed} style={styles.postEmbeds} />
<PostCtrls
replyCount={item.replyCount}
repostCount={item.repostCount}
@ -278,4 +278,7 @@ const styles = StyleSheet.create({
fontSize: 16,
lineHeight: 20.8, // 1.3 of 16px
},
postEmbeds: {
marginBottom: 10,
},
})

View file

@ -1,4 +1,4 @@
import React, {useState, useEffect, useMemo} from 'react'
import React, {useState, useEffect} from 'react'
import {
Image,
ImageStyle,
@ -8,6 +8,7 @@ import {
Text,
TouchableWithoutFeedback,
View,
ViewStyle,
} from 'react-native'
import {colors} from '../../../lib/styles'
@ -30,39 +31,29 @@ export function AutoSizedImage({
const [error, setError] = useState<string | undefined>()
const [imgInfo, setImgInfo] = useState<Dim | undefined>()
const [containerInfo, setContainerInfo] = useState<Dim | undefined>()
const calculatedStyle = useMemo(() => {
if (imgInfo && containerInfo) {
// imgInfo.height / imgInfo.width = x / containerInfo.width
// x = imgInfo.height / imgInfo.width * containerInfo.width
return {
height: Math.min(
MAX_HEIGHT,
(imgInfo.height / imgInfo.width) * containerInfo.width,
),
}
}
return undefined
}, [imgInfo, containerInfo])
useEffect(() => {
let aborted = false
Image.getSize(
uri,
(width: number, height: number) => {
if (!aborted) {
setImgInfo({width, height})
}
},
(error: any) => {
if (!aborted) {
setError(String(error))
}
},
)
if (!imgInfo) {
Image.getSize(
uri,
(width: number, height: number) => {
console.log('gotSize')
if (!aborted) {
setImgInfo({width, height})
}
},
(error: any) => {
if (!aborted) {
setError(String(error))
}
},
)
}
return () => {
aborted = true
}
}, [uri])
}, [uri, imgInfo])
const onLayout = (evt: LayoutChangeEvent) => {
setContainerInfo({
@ -71,6 +62,18 @@ export function AutoSizedImage({
})
}
let calculatedStyle: StyleProp<ViewStyle> | undefined
if (imgInfo && containerInfo) {
// imgInfo.height / imgInfo.width = x / containerInfo.width
// x = imgInfo.height / imgInfo.width * containerInfo.width
calculatedStyle = {
height: Math.min(
MAX_HEIGHT,
(imgInfo.height / imgInfo.width) * containerInfo.width,
),
}
}
return (
<View style={style}>
<TouchableWithoutFeedback onPress={onPress}>