diff options
author | Hailey <153161762+haileyok@users.noreply.github.com> | 2024-01-08 21:37:12 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-08 21:37:12 -0800 |
commit | dda5ca27feb54e7101cb80d9a38a7edd5440d0ec (patch) | |
tree | 25906f7b0d9b42d96629184a48a618496876fcb4 /src | |
parent | 153c25e1fee6037b2a8595108f007da072b055c1 (diff) | |
download | voidsky-dda5ca27feb54e7101cb80d9a38a7edd5440d0ec.tar.zst |
add expandable context to composer when replying to post (#2419)
* add expand replyTo text with animation * add images, quote to replyTo * support withmedia * adjust layout * add embed to all needed openComposer calls * adjust gap * organize imports
Diffstat (limited to 'src')
-rw-r--r-- | src/state/shell/composer.tsx | 1 | ||||
-rw-r--r-- | src/view/com/composer/Composer.tsx | 31 | ||||
-rw-r--r-- | src/view/com/composer/ComposerReplyTo.tsx | 254 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 1 | ||||
-rw-r--r-- | src/view/com/post/Post.tsx | 1 | ||||
-rw-r--r-- | src/view/com/posts/FeedItem.tsx | 1 | ||||
-rw-r--r-- | src/view/screens/PostThread.tsx | 1 |
7 files changed, 261 insertions, 29 deletions
diff --git a/src/state/shell/composer.tsx b/src/state/shell/composer.tsx index 9cf8ef8de..51376fd5a 100644 --- a/src/state/shell/composer.tsx +++ b/src/state/shell/composer.tsx @@ -11,6 +11,7 @@ export interface ComposerOptsPostRef { displayName?: string avatar?: string } + embed?: AppBskyEmbedRecord.ViewRecord['embed'] } export interface ComposerOptsQuote { uri: string diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index b15afe6f0..a834cfc0e 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -29,8 +29,6 @@ import {UserAvatar} from '../util/UserAvatar' import * as apilib from 'lib/api/index' import {ComposerOpts} from 'state/shell/composer' import {s, colors, gradients} from 'lib/styles' -import {sanitizeDisplayName} from 'lib/strings/display-names' -import {sanitizeHandle} from 'lib/strings/handles' import {cleanError} from 'lib/strings/errors' import {shortenLinks} from 'lib/strings/rich-text-manip' import {toShortUrl} from 'lib/strings/url-helpers' @@ -63,6 +61,7 @@ import {useComposerControls} from '#/state/shell/composer' import {emitPostCreated} from '#/state/events' import {ThreadgateSetting} from '#/state/queries/threadgate' import {logger} from '#/logger' +import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo' type Props = ComposerOpts export const ComposePost = observer(function ComposePost({ @@ -379,22 +378,7 @@ export const ComposePost = observer(function ComposePost({ <ScrollView style={styles.scrollView} keyboardShouldPersistTaps="always"> - {replyTo ? ( - <View style={[pal.border, styles.replyToLayout]}> - <UserAvatar avatar={replyTo.author.avatar} size={50} /> - <View style={styles.replyToPost}> - <Text type="xl-medium" style={[pal.text]}> - {sanitizeDisplayName( - replyTo.author.displayName || - sanitizeHandle(replyTo.author.handle), - )} - </Text> - <Text type="post-text" style={pal.text} numberOfLines={6}> - {replyTo.text} - </Text> - </View> - </View> - ) : undefined} + {replyTo ? <ComposerReplyTo replyTo={replyTo} /> : undefined} <View style={[ @@ -549,17 +533,6 @@ const styles = StyleSheet.create({ textInputLayoutMobile: { flex: 1, }, - replyToLayout: { - flexDirection: 'row', - borderTopWidth: 1, - paddingTop: 16, - paddingBottom: 16, - }, - replyToPost: { - flex: 1, - paddingLeft: 13, - paddingRight: 8, - }, addExtLinkBtn: { borderWidth: 1, borderRadius: 24, diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx new file mode 100644 index 000000000..678c8581f --- /dev/null +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -0,0 +1,254 @@ +import React from 'react' +import {LayoutAnimation, Pressable, StyleSheet, View} from 'react-native' +import {Image} from 'expo-image' +import {useLingui} from '@lingui/react' +import {msg} from '@lingui/macro' +import { + AppBskyEmbedImages, + AppBskyEmbedRecord, + AppBskyEmbedRecordWithMedia, + AppBskyFeedPost, +} from '@atproto/api' +import {ComposerOptsPostRef} from 'state/shell/composer' +import {usePalette} from 'lib/hooks/usePalette' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {Text} from 'view/com/util/text/Text' +import QuoteEmbed from 'view/com/util/post-embeds/QuoteEmbed' + +export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { + const pal = usePalette('default') + const {_} = useLingui() + const {embed} = replyTo + + const [showFull, setShowFull] = React.useState(false) + + const onPress = React.useCallback(() => { + setShowFull(prev => !prev) + LayoutAnimation.configureNext({ + duration: 350, + update: {type: 'spring', springDamping: 0.7}, + }) + }, []) + + const quote = React.useMemo(() => { + if ( + AppBskyEmbedRecord.isView(embed) && + AppBskyEmbedRecord.isViewRecord(embed.record) && + AppBskyFeedPost.isRecord(embed.record.value) + ) { + // Not going to include the images right now + return { + author: embed.record.author, + cid: embed.record.cid, + uri: embed.record.uri, + indexedAt: embed.record.indexedAt, + text: embed.record.value.text, + } + } else if ( + AppBskyEmbedRecordWithMedia.isView(embed) && + AppBskyEmbedRecord.isViewRecord(embed.record.record) && + AppBskyFeedPost.isRecord(embed.record.record.value) + ) { + return { + author: embed.record.record.author, + cid: embed.record.record.cid, + uri: embed.record.record.uri, + indexedAt: embed.record.record.indexedAt, + text: embed.record.record.value.text, + } + } + }, [embed]) + + const images = React.useMemo(() => { + if (AppBskyEmbedImages.isView(embed)) { + return embed.images + } else if ( + AppBskyEmbedRecordWithMedia.isView(embed) && + AppBskyEmbedImages.isView(embed.media) + ) { + return embed.media.images + } + }, [embed]) + + return ( + <Pressable + style={[pal.border, styles.replyToLayout]} + onPress={onPress} + accessibilityRole="button" + accessibilityLabel={_( + msg`Expand or collapse the full post you are replying to`, + )} + accessibilityHint={_( + msg`Expand or collapse the full post you are replying to`, + )}> + <UserAvatar avatar={replyTo.author.avatar} size={50} /> + <View style={styles.replyToPost}> + <Text type="xl-medium" style={[pal.text]}> + {sanitizeDisplayName( + replyTo.author.displayName || sanitizeHandle(replyTo.author.handle), + )} + </Text> + <View style={styles.replyToBody}> + <View style={styles.replyToText}> + <Text + type="post-text" + style={pal.text} + numberOfLines={!showFull ? 6 : undefined}> + {replyTo.text} + </Text> + </View> + {images && ( + <ComposerReplyToImages images={images} showFull={showFull} /> + )} + </View> + {showFull && quote && <QuoteEmbed quote={quote} />} + </View> + </Pressable> + ) +} + +function ComposerReplyToImages({ + images, +}: { + images: AppBskyEmbedImages.ViewImage[] + showFull: boolean +}) { + return ( + <View + style={{ + width: 65, + flexDirection: 'column', + alignItems: 'center', + }}> + <View style={styles.imagesContainer}> + {(images.length === 1 && ( + <Image + source={{uri: images[0].thumb}} + style={styles.singleImage} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + )) || + (images.length === 2 && ( + <View style={[styles.imagesInner, styles.imagesRow]}> + <Image + source={{uri: images[0].thumb}} + style={styles.doubleImageTall} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + <Image + source={{uri: images[1].thumb}} + style={styles.doubleImageTall} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + </View> + )) || + (images.length === 3 && ( + <View style={[styles.imagesInner, styles.imagesRow]}> + <Image + source={{uri: images[0].thumb}} + style={styles.doubleImageTall} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + <View style={styles.imagesInner}> + <Image + source={{uri: images[1].thumb}} + style={styles.doubleImage} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + <Image + source={{uri: images[2].thumb}} + style={styles.doubleImage} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + </View> + </View> + )) || + (images.length === 4 && ( + <View style={styles.imagesInner}> + <View style={[styles.imagesInner, styles.imagesRow]}> + <Image + source={{uri: images[0].thumb}} + style={styles.doubleImage} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + <Image + source={{uri: images[1].thumb}} + style={styles.doubleImage} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + </View> + <View style={[styles.imagesInner, styles.imagesRow]}> + <Image + source={{uri: images[2].thumb}} + style={styles.doubleImage} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + <Image + source={{uri: images[3].thumb}} + style={styles.doubleImage} + cachePolicy="memory-disk" + accessibilityIgnoresInvertColors + /> + </View> + </View> + ))} + </View> + </View> + ) +} + +const styles = StyleSheet.create({ + replyToLayout: { + flexDirection: 'row', + borderTopWidth: 1, + paddingTop: 16, + paddingBottom: 16, + }, + replyToPost: { + flex: 1, + paddingLeft: 13, + paddingRight: 8, + }, + replyToBody: { + flexDirection: 'row', + gap: 10, + }, + replyToText: { + flex: 1, + flexGrow: 1, + }, + imagesContainer: { + borderRadius: 6, + overflow: 'hidden', + marginTop: 2, + }, + imagesInner: { + gap: 2, + }, + imagesRow: { + flexDirection: 'row', + }, + singleImage: { + width: 65, + height: 65, + }, + doubleImageTall: { + width: 32.5, + height: 65, + }, + doubleImage: { + width: 32.5, + height: 32.5, + }, +}) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 986fd70b2..fc03c0d95 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -214,6 +214,7 @@ let PostThreadItemLoaded = ({ displayName: post.author.displayName, avatar: post.author.avatar, }, + embed: post.embed, }, onPost: onPostReply, }) diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index fca4171c3..ac4689d2f 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -118,6 +118,7 @@ function PostInner({ displayName: post.author.displayName, avatar: post.author.avatar, }, + embed: post.embed, }, }) }, [openComposer, post, record]) diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 942d7bf71..2a5abcd68 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -131,6 +131,7 @@ let FeedItemInner = ({ displayName: post.author.displayName, avatar: post.author.avatar, }, + embed: post.embed, }, }) }, [post, record, openComposer]) diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index 9f50c8b73..6f8434412 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -67,6 +67,7 @@ export function PostThreadScreen({route}: Props) { displayName: thread.post.author.displayName, avatar: thread.post.author.avatar, }, + embed: thread.post.embed, }, onPost: () => queryClient.invalidateQueries({ |