diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/com/composer/Composer.tsx | 18 | ||||
-rw-r--r-- | src/view/com/post-thread/PostLikedBy.tsx | 4 | ||||
-rw-r--r-- | src/view/com/post-thread/PostQuotes.tsx | 141 | ||||
-rw-r--r-- | src/view/com/post-thread/PostRepostedBy.tsx | 4 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 30 | ||||
-rw-r--r-- | src/view/com/util/post-ctrls/PostCtrls.tsx | 12 | ||||
-rw-r--r-- | src/view/screens/PostLikedBy.tsx | 32 | ||||
-rw-r--r-- | src/view/screens/PostRepostedBy.tsx | 32 | ||||
-rw-r--r-- | src/view/shell/Composer.ios.tsx | 1 | ||||
-rw-r--r-- | src/view/shell/Composer.tsx | 1 | ||||
-rw-r--r-- | src/view/shell/Composer.web.tsx | 1 |
11 files changed, 203 insertions, 73 deletions
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index dba37d82b..0efbe70e6 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -116,6 +116,7 @@ export const ComposePost = observer(function ComposePost({ replyTo, onPost, quote: initQuote, + quoteCount, mention: initMention, openPicker, text: initText, @@ -392,7 +393,22 @@ export const ComposePost = observer(function ComposePost({ emitPostCreated() } setLangPrefs.savePostLanguageToHistory() - onPost?.(postUri) + if (quote) { + // We want to wait for the quote count to update before we call `onPost`, which will refetch data + whenAppViewReady(agent, quote.uri, res => { + const thread = res.data.thread + if ( + AppBskyFeedDefs.isThreadViewPost(thread) && + thread.post.quoteCount !== quoteCount + ) { + onPost?.(postUri) + return true + } + return false + }) + } else { + onPost?.(postUri) + } onClose() Toast.show( replyTo diff --git a/src/view/com/post-thread/PostLikedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx index da230aade..c3e3f9e17 100644 --- a/src/view/com/post-thread/PostLikedBy.tsx +++ b/src/view/com/post-thread/PostLikedBy.tsx @@ -8,13 +8,13 @@ import {logger} from '#/logger' import {useLikedByQuery} from '#/state/queries/post-liked-by' import {useResolveUriQuery} from '#/state/queries/resolve-uri' import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' +import {List} from '#/view/com/util/List' import { ListFooter, ListHeaderDesktop, ListMaybePlaceholder, } from '#/components/Lists' -import {ProfileCardWithFollowBtn} from '../profile/ProfileCard' -import {List} from '../util/List' function renderItem({item}: {item: GetLikes.Like}) { return <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} /> diff --git a/src/view/com/post-thread/PostQuotes.tsx b/src/view/com/post-thread/PostQuotes.tsx new file mode 100644 index 000000000..d573d27a1 --- /dev/null +++ b/src/view/com/post-thread/PostQuotes.tsx @@ -0,0 +1,141 @@ +import React, {useCallback, useState} from 'react' +import { + AppBskyFeedDefs, + AppBskyFeedPost, + ModerationDecision, +} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {usePostQuotesQuery} from '#/state/queries/post-quotes' +import {useResolveUriQuery} from '#/state/queries/resolve-uri' +import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import {Post} from 'view/com/post/Post' +import { + ListFooter, + ListHeaderDesktop, + ListMaybePlaceholder, +} from '#/components/Lists' +import {List} from '../util/List' + +function renderItem({ + item, + index, +}: { + item: { + post: AppBskyFeedDefs.PostView + moderation: ModerationDecision + record: AppBskyFeedPost.Record + } + index: number +}) { + return <Post post={item.post} hideTopBorder={index === 0 && !isWeb} /> +} + +function keyExtractor(item: { + post: AppBskyFeedDefs.PostView + moderation: ModerationDecision + record: AppBskyFeedPost.Record +}) { + return item.post.uri +} + +export function PostQuotes({uri}: {uri: string}) { + const {_} = useLingui() + const initialNumToRender = useInitialNumToRender() + + const [isPTRing, setIsPTRing] = useState(false) + + const { + data: resolvedUri, + error: resolveError, + isLoading: isLoadingUri, + } = useResolveUriQuery(uri) + const { + data, + isLoading: isLoadingQuotes, + isFetchingNextPage, + hasNextPage, + fetchNextPage, + error, + refetch, + } = usePostQuotesQuery(resolvedUri?.uri) + + const moderationOpts = useModerationOpts() + + const isError = Boolean(resolveError || error) + + const quotes = + data?.pages + .flatMap(page => + page.posts.map(post => { + if (!AppBskyFeedPost.isRecord(post.record) || !moderationOpts) { + return null + } + const moderation = moderatePost(post, moderationOpts) + return {post, record: post.record, moderation} + }), + ) + .filter(item => item !== null) ?? [] + + const onRefresh = useCallback(async () => { + setIsPTRing(true) + try { + await refetch() + } catch (err) { + logger.error('Failed to refresh quotes', {message: err}) + } + setIsPTRing(false) + }, [refetch, setIsPTRing]) + + const onEndReached = useCallback(async () => { + if (isFetchingNextPage || !hasNextPage || isError) return + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more quotes', {message: err}) + } + }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) + + if (isLoadingUri || isLoadingQuotes || isError) { + return ( + <ListMaybePlaceholder + isLoading={isLoadingUri || isLoadingQuotes} + isError={isError} + /> + ) + } + + // loaded + // = + return ( + <List + data={quotes} + renderItem={renderItem} + keyExtractor={keyExtractor} + refreshing={isPTRing} + onRefresh={onRefresh} + onEndReached={onEndReached} + onEndReachedThreshold={4} + ListHeaderComponent={<ListHeaderDesktop title={_(msg`Quotes`)} />} + ListFooterComponent={ + <ListFooter + isFetchingNextPage={isFetchingNextPage} + error={cleanError(error)} + onRetry={fetchNextPage} + showEndMessage + endMessageText={_(msg`That's all, folks!`)} + /> + } + // @ts-ignore our .web version only -prf + desktopFixedHeight + initialNumToRender={initialNumToRender} + windowSize={11} + /> + ) +} diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index 9038549a5..0d1e86aec 100644 --- a/src/view/com/post-thread/PostRepostedBy.tsx +++ b/src/view/com/post-thread/PostRepostedBy.tsx @@ -8,13 +8,13 @@ import {logger} from '#/logger' import {usePostRepostedByQuery} from '#/state/queries/post-reposted-by' import {useResolveUriQuery} from '#/state/queries/resolve-uri' import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' +import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' +import {List} from '#/view/com/util/List' import { ListFooter, ListHeaderDesktop, ListMaybePlaceholder, } from '#/components/Lists' -import {ProfileCardWithFollowBtn} from '../profile/ProfileCard' -import {List} from '../util/List' function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) { return <ProfileCardWithFollowBtn key={item.did} profile={item} /> diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 0ff360143..26a5f2f03 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -199,6 +199,11 @@ let PostThreadItemLoaded = ({ return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by') }, [post.uri, post.author]) const repostsTitle = _(msg`Reposts of this post`) + const quotesHref = React.useMemo(() => { + const urip = new AtUri(post.uri) + return makeProfileLink(post.author, 'post', urip.rkey, 'quotes') + }, [post.uri, post.author]) + const quotesTitle = _(msg`Quotes of this post`) const translatorUrl = getTranslatorLink( record?.text || '', @@ -343,7 +348,9 @@ let PostThreadItemLoaded = ({ translatorUrl={translatorUrl} needsTranslation={needsTranslation} /> - {post.repostCount !== 0 || post.likeCount !== 0 ? ( + {post.repostCount !== 0 || + post.likeCount !== 0 || + post.quoteCount !== 0 ? ( // Show this section unless we're *sure* it has no engagement. <View style={[styles.expandedInfo, pal.border]}> {post.repostCount != null && post.repostCount !== 0 ? ( @@ -382,6 +389,26 @@ let PostThreadItemLoaded = ({ </Text> </Link> ) : null} + {post.quoteCount != null && post.quoteCount !== 0 ? ( + <Link + style={styles.expandedInfoItem} + href={quotesHref} + title={quotesTitle}> + <Text + testID="quoteCount-expanded" + type="lg" + style={pal.textLight}> + <Text type="xl-bold" style={pal.text}> + {formatCount(post.quoteCount)} + </Text>{' '} + <Plural + value={post.quoteCount} + one="quote" + other="quotes" + /> + </Text> + </Link> + ) : null} </View> ) : null} <View style={[s.pl10, s.pr10]}> @@ -391,6 +418,7 @@ let PostThreadItemLoaded = ({ record={record} richText={richText} onPressReply={onPressReply} + onPostReply={onPostReply} logContext="PostThreadItem" /> </View> diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 478b8f0f8..ad5863846 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -58,6 +58,7 @@ let PostCtrls = ({ feedContext, style, onPressReply, + onPostReply, logContext, }: { big?: boolean @@ -67,6 +68,7 @@ let PostCtrls = ({ feedContext?: string | undefined style?: StyleProp<ViewStyle> onPressReply: () => void + onPostReply?: (postUri: string | undefined) => void logContext: 'FeedItem' | 'PostThreadItem' | 'Post' }): React.ReactNode => { const t = useTheme() @@ -169,16 +171,20 @@ let PostCtrls = ({ author: post.author, indexedAt: post.indexedAt, }, + quoteCount: post.quoteCount, + onPost: onPostReply, }) }, [ - openComposer, + sendInteraction, post.uri, post.cid, post.author, post.indexedAt, - record.text, - sendInteraction, + post.quoteCount, feedContext, + openComposer, + record.text, + onPostReply, ]) const onShare = useCallback(() => { diff --git a/src/view/screens/PostLikedBy.tsx b/src/view/screens/PostLikedBy.tsx deleted file mode 100644 index 5ff5a1932..000000000 --- a/src/view/screens/PostLikedBy.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import {View} from 'react-native' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' - -import {useSetMinimalShellMode} from '#/state/shell' -import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' -import {makeRecordUri} from 'lib/strings/url-helpers' -import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy' -import {ViewHeader} from '../com/util/ViewHeader' - -type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'> -export const PostLikedByScreen = ({route}: Props) => { - const setMinimalShellMode = useSetMinimalShellMode() - const {name, rkey} = route.params - const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - const {_} = useLingui() - - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) - - return ( - <View style={{flex: 1}}> - <ViewHeader title={_(msg`Liked By`)} /> - <PostLikedByComponent uri={uri} /> - </View> - ) -} diff --git a/src/view/screens/PostRepostedBy.tsx b/src/view/screens/PostRepostedBy.tsx deleted file mode 100644 index eaacc6780..000000000 --- a/src/view/screens/PostRepostedBy.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import {View} from 'react-native' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' - -import {useSetMinimalShellMode} from '#/state/shell' -import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' -import {makeRecordUri} from 'lib/strings/url-helpers' -import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy' -import {ViewHeader} from '../com/util/ViewHeader' - -type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'> -export const PostRepostedByScreen = ({route}: Props) => { - const {name, rkey} = route.params - const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - const setMinimalShellMode = useSetMinimalShellMode() - const {_} = useLingui() - - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) - - return ( - <View style={{flex: 1}}> - <ViewHeader title={_(msg`Reposted By`)} /> - <PostRepostedByComponent uri={uri} /> - </View> - ) -} diff --git a/src/view/shell/Composer.ios.tsx b/src/view/shell/Composer.ios.tsx index a732e0cde..7d3780801 100644 --- a/src/view/shell/Composer.ios.tsx +++ b/src/view/shell/Composer.ios.tsx @@ -33,6 +33,7 @@ export const Composer = observer(function ComposerImpl({}: { replyTo={state?.replyTo} onPost={state?.onPost} quote={state?.quote} + quoteCount={state?.quoteCount} mention={state?.mention} text={state?.text} imageUris={state?.imageUris} diff --git a/src/view/shell/Composer.tsx b/src/view/shell/Composer.tsx index b978d6b85..1c97df9c3 100644 --- a/src/view/shell/Composer.tsx +++ b/src/view/shell/Composer.tsx @@ -55,6 +55,7 @@ export const Composer = observer(function ComposerImpl({ replyTo={state.replyTo} onPost={state.onPost} quote={state.quote} + quoteCount={state.quoteCount} mention={state.mention} text={state.text} imageUris={state.imageUris} diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx index 64353db23..5d80dc422 100644 --- a/src/view/shell/Composer.web.tsx +++ b/src/view/shell/Composer.web.tsx @@ -58,6 +58,7 @@ export function Composer({}: {winHeight: number}) { <ComposePost replyTo={state.replyTo} quote={state.quote} + quoteCount={state?.quoteCount} onPost={state.onPost} mention={state.mention} openPicker={onOpenPicker} |