diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index d6cf6dac56..07c1fd6b7a 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -42,10 +42,11 @@ import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' import {FeedErrorMessage} from './FeedErrorMessage' +import {FeedItem} from './FeedItem' import {FeedShutdownMsg} from './FeedShutdownMsg' -import {FeedSlice} from './FeedSlice' +import {ViewFullThread} from './ViewFullThread' -type FeedItem = +type FeedRow = | { type: 'loading' key: string @@ -71,6 +72,18 @@ type FeedItem = key: string slice: FeedPostSlice } + | { + type: 'sliceItem' + key: string + slice: FeedPostSlice + indexInSlice: number + showReplyTo: boolean + } + | { + type: 'sliceViewFullThread' + key: string + uri: string + } | { type: 'interstitialFeeds' key: string @@ -101,7 +114,7 @@ const followInterstitialType = 'interstitialFollows' const progressGuideInterstitialType = 'interstitialProgressGuide' const interstials: Record< 'following' | 'discover' | 'profile', - (FeedItem & { + (FeedRow & { type: | 'interstitialFeeds' | 'interstitialFollows' @@ -139,9 +152,9 @@ const interstials: Record< ], } -export function getFeedPostSlice(feedItem: FeedItem): FeedPostSlice | null { - if (feedItem.type === 'slice') { - return feedItem.slice +export function getFeedPostSlice(feedRow: FeedRow): FeedPostSlice | null { + if (feedRow.type === 'sliceItem') { + return feedRow.slice } else { return null } @@ -303,8 +316,8 @@ let Feed = ({ } }, [pollInterval]) - const feedItems: FeedItem[] = React.useMemo(() => { - let arr: FeedItem[] = [] + const feedItems: FeedRow[] = React.useMemo(() => { + let arr: FeedRow[] = [] if (KNOWN_SHUTDOWN_FEEDS.includes(feedUri)) { arr.push({ type: 'feedShutdownMsg', @@ -324,13 +337,50 @@ let Feed = ({ }) } else if (data) { for (const page of data?.pages) { - arr = arr.concat( - page.slices.map(s => ({ - type: 'slice', - slice: s, - key: s._reactKey, - })), - ) + for (const slice of page.slices) { + if (slice.isIncompleteThread && slice.items.length >= 3) { + const beforeLast = slice.items.length - 2 + const last = slice.items.length - 1 + arr.push({ + type: 'sliceItem', + key: slice.items[0]._reactKey, + slice: slice, + indexInSlice: 0, + showReplyTo: false, + }) + arr.push({ + type: 'sliceViewFullThread', + key: slice._reactKey + '-viewFullThread', + uri: slice.items[0].uri, + }) + arr.push({ + type: 'sliceItem', + key: slice.items[beforeLast]._reactKey, + slice: slice, + indexInSlice: beforeLast, + showReplyTo: + slice.items[beforeLast].parentAuthor?.did !== + slice.items[beforeLast].post.author.did, + }) + arr.push({ + type: 'sliceItem', + key: slice.items[last]._reactKey, + slice: slice, + indexInSlice: last, + showReplyTo: false, + }) + } else { + for (let i = 0; i < slice.items.length; i++) { + arr.push({ + type: 'sliceItem', + key: slice.items[i]._reactKey, + slice: slice, + indexInSlice: i, + showReplyTo: i === 0, + }) + } + } + } } } if (isError && !isEmpty) { @@ -454,10 +504,10 @@ let Feed = ({ // = const renderItem = React.useCallback( - ({item, index}: ListRenderItemInfo) => { - if (item.type === 'empty') { + ({item: row, index: rowIndex}: ListRenderItemInfo) => { + if (row.type === 'empty') { return renderEmptyState() - } else if (item.type === 'error') { + } else if (row.type === 'error') { return ( ) - } else if (item.type === 'loadMoreError') { + } else if (row.type === 'loadMoreError') { return ( ) - } else if (item.type === 'loading') { + } else if (row.type === 'loading') { return - } else if (item.type === 'feedShutdownMsg') { + } else if (row.type === 'feedShutdownMsg') { return - } else if (item.type === feedInterstitialType) { + } else if (row.type === feedInterstitialType) { return - } else if (item.type === followInterstitialType) { + } else if (row.type === followInterstitialType) { return - } else if (item.type === progressGuideInterstitialType) { + } else if (row.type === progressGuideInterstitialType) { return - } else if (item.type === 'slice') { - if (item.slice.isFallbackMarker) { + } else if (row.type === 'sliceItem') { + const slice = row.slice + if (slice.isFallbackMarker) { // HACK // tell the user we fell back to discover // see home.ts (feed api) for more info // -prf return } - return + const indexInSlice = row.indexInSlice + const item = slice.items[indexInSlice] + return ( + + ) + } else if (row.type === 'sliceViewFullThread') { + return } else { return null } @@ -574,3 +649,17 @@ export {Feed} const styles = StyleSheet.create({ feedFooter: {paddingTop: 20}, }) + +function isThreadParentAt(arr: Array, i: number) { + if (arr.length === 1) { + return false + } + return i < arr.length - 1 +} + +function isThreadChildAt(arr: Array, i: number) { + if (arr.length === 1) { + return false + } + return i > 0 +} diff --git a/src/view/com/posts/FeedSlice.tsx b/src/view/com/posts/FeedSlice.tsx deleted file mode 100644 index 09335fa0ed..0000000000 --- a/src/view/com/posts/FeedSlice.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import React, {memo} from 'react' -import {StyleSheet, View} from 'react-native' -import Svg, {Circle, Line} from 'react-native-svg' -import {AtUri} from '@atproto/api' -import {Trans} from '@lingui/macro' - -import {usePalette} from '#/lib/hooks/usePalette' -import {makeProfileLink} from '#/lib/routes/links' -import {FeedPostSlice} from '#/state/queries/post-feed' -import {useInteractionState} from '#/components/hooks/useInteractionState' -import {SubtleWebHover} from '#/components/SubtleWebHover' -import {Link} from '../util/Link' -import {Text} from '../util/text/Text' -import {FeedItem} from './FeedItem' - -let FeedSlice = ({ - slice, - hideTopBorder, -}: { - slice: FeedPostSlice - hideTopBorder?: boolean -}): React.ReactNode => { - if (slice.isIncompleteThread && slice.items.length >= 3) { - const beforeLast = slice.items.length - 2 - const last = slice.items.length - 1 - return ( - <> - - - - - - ) - } - - return ( - <> - {slice.items.map((item, i) => ( - - ))} - - ) -} -FeedSlice = memo(FeedSlice) -export {FeedSlice} - -function ViewFullThread({uri}: {uri: string}) { - const { - state: hover, - onIn: onHoverIn, - onOut: onHoverOut, - } = useInteractionState() - const pal = usePalette('default') - const itemHref = React.useMemo(() => { - const urip = new AtUri(uri) - return makeProfileLink({did: urip.hostname, handle: ''}, 'post', urip.rkey) - }, [uri]) - - return ( - - - - - - - - - - - - - View full thread - - - ) -} - -const styles = StyleSheet.create({ - viewFullThread: { - flexDirection: 'row', - gap: 10, - paddingLeft: 18, - }, - viewFullThreadDots: { - width: 42, - alignItems: 'center', - }, -}) - -function isThreadParentAt(arr: Array, i: number) { - if (arr.length === 1) { - return false - } - return i < arr.length - 1 -} - -function isThreadChildAt(arr: Array, i: number) { - if (arr.length === 1) { - return false - } - return i > 0 -} diff --git a/src/view/com/posts/ViewFullThread.tsx b/src/view/com/posts/ViewFullThread.tsx new file mode 100644 index 0000000000..0b347f22ce --- /dev/null +++ b/src/view/com/posts/ViewFullThread.tsx @@ -0,0 +1,72 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import Svg, {Circle, Line} from 'react-native-svg' +import {AtUri} from '@atproto/api' +import {Trans} from '@lingui/macro' + +import {usePalette} from '#/lib/hooks/usePalette' +import {makeProfileLink} from '#/lib/routes/links' +import {useInteractionState} from '#/components/hooks/useInteractionState' +import {SubtleWebHover} from '#/components/SubtleWebHover' +import {Link} from '../util/Link' +import {Text} from '../util/text/Text' + +export function ViewFullThread({uri}: {uri: string}) { + const { + state: hover, + onIn: onHoverIn, + onOut: onHoverOut, + } = useInteractionState() + const pal = usePalette('default') + const itemHref = React.useMemo(() => { + const urip = new AtUri(uri) + return makeProfileLink({did: urip.hostname, handle: ''}, 'post', urip.rkey) + }, [uri]) + + return ( + + + + + + + + + + + + + View full thread + + + ) +} + +const styles = StyleSheet.create({ + viewFullThread: { + flexDirection: 'row', + gap: 10, + paddingLeft: 18, + }, + viewFullThreadDots: { + width: 42, + alignItems: 'center', + }, +})