diff options
author | dan <dan.abramov@gmail.com> | 2023-12-12 21:50:43 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-12 13:50:43 -0800 |
commit | 5c701f8e0bced2fe26544acc04732bdc0fcd8c40 (patch) | |
tree | d0ac3658fb3c5092d8da913b75387c43d219a1b3 /src | |
parent | a5e25a7a16cdcde64628e942c073a119bc1d7a1e (diff) | |
download | voidsky-5c701f8e0bced2fe26544acc04732bdc0fcd8c40.tar.zst |
Re-rendering improvements for like/unlike (#2180)
* Add a few memos * Memo PostDropdownBtn better * More memo * More granularity * Extract PostContent * Fix a usage I missed * oops
Diffstat (limited to 'src')
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 4 | ||||
-rw-r--r-- | src/view/com/posts/FeedItem.tsx | 123 | ||||
-rw-r--r-- | src/view/com/util/PostMeta.tsx | 6 | ||||
-rw-r--r-- | src/view/com/util/UserAvatar.tsx | 26 | ||||
-rw-r--r-- | src/view/com/util/forms/PostDropdownBtn.tsx | 38 | ||||
-rw-r--r-- | src/view/com/util/post-ctrls/PostCtrls.tsx | 42 | ||||
-rw-r--r-- | src/view/com/util/post-ctrls/RepostButton.tsx | 8 |
7 files changed, 160 insertions, 87 deletions
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 2636fdfbd..8d1211707 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -328,7 +328,9 @@ let PostThreadItemLoaded = ({ </View> <PostDropdownBtn testID="postDropdownBtn" - post={post} + postAuthor={post.author} + postCid={post.cid} + postUri={post.uri} record={record} style={{ paddingVertical: 6, diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index f054a40f2..20d199745 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -102,10 +102,6 @@ let FeedItemInner = ({ }): React.ReactNode => { const {openComposer} = useComposerControls() const pal = usePalette('default') - const [limitLines, setLimitLines] = useState( - () => countLines(richText.text) >= MAX_POST_LINES, - ) - const href = useMemo(() => { const urip = new AtUri(post.uri) return makeProfileLink(post.author, 'post', urip.rkey) @@ -134,10 +130,6 @@ let FeedItemInner = ({ }) }, [post, record, openComposer]) - const onPressShowMore = React.useCallback(() => { - setLimitLines(false) - }, [setLimitLines]) - const outerStyles = [ styles.outer, pal.view, @@ -286,48 +278,12 @@ let FeedItemInner = ({ </Text> </View> )} - <ContentHider - testID="contentHider-post" - moderation={moderation.content} - ignoreMute - childContainerStyle={styles.contentHiderChild}> - <PostAlerts moderation={moderation.content} style={styles.alert} /> - {richText.text ? ( - <View style={styles.postTextContainer}> - <RichText - testID="postText" - type="post-text" - richText={richText} - lineHeight={1.3} - numberOfLines={limitLines ? MAX_POST_LINES : undefined} - style={s.flex1} - /> - </View> - ) : undefined} - {limitLines ? ( - <TextLink - text="Show More" - style={pal.link} - onPress={onPressShowMore} - href="#" - /> - ) : undefined} - {post.embed ? ( - <ContentHider - testID="contentHider-embed" - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - ignoreMute={isEmbedByEmbedder(post.embed, post.author.did)} - ignoreQuoteDecisions - style={styles.embed}> - <PostEmbeds - embed={post.embed} - moderation={moderation.embed} - moderationDecisions={moderation.decisions} - /> - </ContentHider> - ) : null} - </ContentHider> + <PostContent + moderation={moderation} + richText={richText} + postEmbed={post.embed} + postAuthor={post.author} + /> <PostCtrls post={post} record={record} onPressReply={onPressReply} /> </View> </View> @@ -336,6 +292,73 @@ let FeedItemInner = ({ } FeedItemInner = memo(FeedItemInner) +let PostContent = ({ + moderation, + richText, + postEmbed, + postAuthor, +}: { + moderation: PostModeration + richText: RichTextAPI + postEmbed: AppBskyFeedDefs.PostView['embed'] + postAuthor: AppBskyFeedDefs.PostView['author'] +}): React.ReactNode => { + const pal = usePalette('default') + const [limitLines, setLimitLines] = useState( + () => countLines(richText.text) >= MAX_POST_LINES, + ) + + const onPressShowMore = React.useCallback(() => { + setLimitLines(false) + }, [setLimitLines]) + + return ( + <ContentHider + testID="contentHider-post" + moderation={moderation.content} + ignoreMute + childContainerStyle={styles.contentHiderChild}> + <PostAlerts moderation={moderation.content} style={styles.alert} /> + {richText.text ? ( + <View style={styles.postTextContainer}> + <RichText + testID="postText" + type="post-text" + richText={richText} + lineHeight={1.3} + numberOfLines={limitLines ? MAX_POST_LINES : undefined} + style={s.flex1} + /> + </View> + ) : undefined} + {limitLines ? ( + <TextLink + text="Show More" + style={pal.link} + onPress={onPressShowMore} + href="#" + /> + ) : undefined} + {postEmbed ? ( + <ContentHider + testID="contentHider-embed" + moderation={moderation.embed} + moderationDecisions={moderation.decisions} + ignoreMute={isEmbedByEmbedder(postEmbed, postAuthor.did)} + ignoreQuoteDecisions + style={styles.embed}> + <PostEmbeds + embed={postEmbed} + moderation={moderation.embed} + moderationDecisions={moderation.decisions} + /> + </ContentHider> + ) : null} + </ContentHider> + ) +} +PostContent = memo(PostContent) + const styles = StyleSheet.create({ outer: { borderTopWidth: 1, diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index fa5f12f6b..eef7094cd 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, {memo} from 'react' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' import {Text} from './text/Text' import {TextLinkOnWebOnly} from './Link' @@ -29,7 +29,7 @@ interface PostMetaOpts { style?: StyleProp<ViewStyle> } -export function PostMeta(opts: PostMetaOpts) { +let PostMeta = (opts: PostMetaOpts): React.ReactNode => { const pal = usePalette('default') const displayName = opts.author.displayName || opts.author.handle const handle = opts.author.handle @@ -92,6 +92,8 @@ export function PostMeta(opts: PostMetaOpts) { </View> ) } +PostMeta = memo(PostMeta) +export {PostMeta} const styles = StyleSheet.create({ container: { diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 395e9eb3a..00ff7e1ec 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react' +import React, {memo, useMemo} from 'react' import {Image, StyleSheet, View} from 'react-native' import Svg, {Circle, Rect, Path} from 'react-native-svg' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' @@ -43,13 +43,13 @@ interface PreviewableUserAvatarProps extends BaseUserAvatarProps { const BLUR_AMOUNT = isWeb ? 5 : 100 -export function DefaultAvatar({ +let DefaultAvatar = ({ type, size, }: { type: UserAvatarType size: number -}) { +}): React.ReactNode => { if (type === 'algo') { // Font Awesome Pro 6.4.0 by @fontawesome -https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. return ( @@ -112,14 +112,16 @@ export function DefaultAvatar({ </Svg> ) } +DefaultAvatar = memo(DefaultAvatar) +export {DefaultAvatar} -export function UserAvatar({ +let UserAvatar = ({ type = 'user', size, avatar, moderation, usePlainRNImage = false, -}: UserAvatarProps) { +}: UserAvatarProps): React.ReactNode => { const pal = usePalette('default') const aviStyle = useMemo(() => { @@ -182,13 +184,15 @@ export function UserAvatar({ </View> ) } +UserAvatar = memo(UserAvatar) +export {UserAvatar} -export function EditableUserAvatar({ +let EditableUserAvatar = ({ type = 'user', size, avatar, onSelectNewAvatar, -}: EditableUserAvatarProps) { +}: EditableUserAvatarProps): React.ReactNode => { const pal = usePalette('default') const {_} = useLingui() const {requestCameraAccessIfNeeded} = useCameraPermission() @@ -323,14 +327,20 @@ export function EditableUserAvatar({ </NativeDropdown> ) } +EditableUserAvatar = memo(EditableUserAvatar) +export {EditableUserAvatar} -export function PreviewableUserAvatar(props: PreviewableUserAvatarProps) { +let PreviewableUserAvatar = ( + props: PreviewableUserAvatarProps, +): React.ReactNode => { return ( <UserPreviewLink did={props.did} handle={props.handle}> <UserAvatar {...props} /> </UserPreviewLink> ) } +PreviewableUserAvatar = memo(PreviewableUserAvatar) +export {PreviewableUserAvatar} const styles = StyleSheet.create({ editButtonContainer: { diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 63590e92d..8c4b03dd9 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -1,8 +1,8 @@ -import React from 'react' +import React, {memo} from 'react' import {Linking, StyleProp, View, ViewStyle} from 'react-native' import Clipboard from '@react-native-clipboard/clipboard' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {AppBskyFeedDefs, AppBskyFeedPost, AtUri} from '@atproto/api' +import {AppBskyActorDefs, AppBskyFeedPost, AtUri} from '@atproto/api' import {toShareUrl} from 'lib/strings/url-helpers' import {useTheme} from 'lib/ThemeContext' import {shareUrl} from 'lib/sharing' @@ -19,23 +19,26 @@ import {usePostDeleteMutation} from '#/state/queries/post' import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads' import {useLanguagePrefs} from '#/state/preferences' import {logger} from '#/logger' -import {Shadow} from '#/state/cache/types' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useSession} from '#/state/session' import {isWeb} from '#/platform/detection' -export function PostDropdownBtn({ +let PostDropdownBtn = ({ testID, - post, + postAuthor, + postCid, + postUri, record, style, }: { testID: string - post: Shadow<AppBskyFeedDefs.PostView> + postAuthor: AppBskyActorDefs.ProfileViewBasic + postCid: string + postUri: string record: AppBskyFeedPost.Record style?: StyleProp<ViewStyle> -}) { +}): React.ReactNode => { const {hasSession, currentAccount} = useSession() const theme = useTheme() const {_} = useLingui() @@ -46,13 +49,13 @@ export function PostDropdownBtn({ const toggleThreadMute = useToggleThreadMute() const postDeleteMutation = usePostDeleteMutation() - const rootUri = record.reply?.root?.uri || post.uri + const rootUri = record.reply?.root?.uri || postUri const isThreadMuted = mutedThreads.includes(rootUri) - const isAuthor = post.author.did === currentAccount?.did + const isAuthor = postAuthor.did === currentAccount?.did const href = React.useMemo(() => { - const urip = new AtUri(post.uri) - return makeProfileLink(post.author, 'post', urip.rkey) - }, [post.uri, post.author]) + const urip = new AtUri(postUri) + return makeProfileLink(postAuthor, 'post', urip.rkey) + }, [postUri, postAuthor]) const translatorUrl = getTranslatorLink( record.text, @@ -60,7 +63,7 @@ export function PostDropdownBtn({ ) const onDeletePost = React.useCallback(() => { - postDeleteMutation.mutateAsync({uri: post.uri}).then( + postDeleteMutation.mutateAsync({uri: postUri}).then( () => { Toast.show('Post deleted') }, @@ -69,7 +72,7 @@ export function PostDropdownBtn({ Toast.show('Failed to delete post, please try again') }, ) - }, [post, postDeleteMutation]) + }, [postUri, postDeleteMutation]) const onToggleThreadMute = React.useCallback(() => { try { @@ -163,8 +166,8 @@ export function PostDropdownBtn({ onPress() { openModal({ name: 'report', - uri: post.uri, - cid: post.cid, + uri: postUri, + cid: postCid, }) }, testID: 'postDropdownReportBtn', @@ -211,3 +214,6 @@ export function PostDropdownBtn({ </EventStopper> ) } + +PostDropdownBtn = memo(PostDropdownBtn) +export {PostDropdownBtn} diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index c0c5d470e..83ea3e8c9 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react' +import React, {memo, useCallback} from 'react' import { StyleProp, StyleSheet, @@ -27,7 +27,7 @@ import {useComposerControls} from '#/state/shell/composer' import {Shadow} from '#/state/cache/types' import {useRequireAuth} from '#/state/session' -export function PostCtrls({ +let PostCtrls = ({ big, post, record, @@ -39,7 +39,7 @@ export function PostCtrls({ record: AppBskyFeedPost.Record style?: StyleProp<ViewStyle> onPressReply: () => void -}) { +}): React.ReactNode => { const theme = useTheme() const {openComposer} = useComposerControls() const {closeModal} = useModalControls() @@ -71,7 +71,14 @@ export function PostCtrls({ likeCount: post.likeCount || 0, }) } - }, [post, postLikeMutation, postUnlikeMutation]) + }, [ + post.viewer?.like, + post.uri, + post.cid, + post.likeCount, + postLikeMutation, + postUnlikeMutation, + ]) const onRepost = useCallback(() => { closeModal() @@ -89,7 +96,15 @@ export function PostCtrls({ repostCount: post.repostCount || 0, }) } - }, [post, closeModal, postRepostMutation, postUnrepostMutation]) + }, [ + post.uri, + post.cid, + post.viewer?.repost, + post.repostCount, + closeModal, + postRepostMutation, + postUnrepostMutation, + ]) const onQuote = useCallback(() => { closeModal() @@ -103,7 +118,16 @@ export function PostCtrls({ }, }) Haptics.default() - }, [post, record, openComposer, closeModal]) + }, [ + post.uri, + post.cid, + post.author, + post.indexedAt, + record.text, + openComposer, + closeModal, + ]) + return ( <View style={[styles.ctrls, style]}> <TouchableOpacity @@ -179,7 +203,9 @@ export function PostCtrls({ {big ? undefined : ( <PostDropdownBtn testID="postDropdownBtn" - post={post} + postAuthor={post.author} + postCid={post.cid} + postUri={post.uri} record={record} style={styles.ctrlPad} /> @@ -189,6 +215,8 @@ export function PostCtrls({ </View> ) } +PostCtrls = memo(PostCtrls) +export {PostCtrls} const styles = StyleSheet.create({ ctrls: { diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx index 1d34a88ab..620852d8e 100644 --- a/src/view/com/util/post-ctrls/RepostButton.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react' +import React, {memo, useCallback} from 'react' import {StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native' import {RepostIcon} from 'lib/icons' import {s, colors} from 'lib/styles' @@ -17,13 +17,13 @@ interface Props { onQuote: () => void } -export const RepostButton = ({ +let RepostButton = ({ isReposted, repostCount, big, onRepost, onQuote, -}: Props) => { +}: Props): React.ReactNode => { const theme = useTheme() const {openModal} = useModalControls() const requireAuth = useRequireAuth() @@ -80,6 +80,8 @@ export const RepostButton = ({ </TouchableOpacity> ) } +RepostButton = memo(RepostButton) +export {RepostButton} const styles = StyleSheet.create({ control: { |