diff options
Diffstat (limited to 'src/view/com/post-thread/PostThread.tsx')
-rw-r--r-- | src/view/com/post-thread/PostThread.tsx | 337 |
1 files changed, 155 insertions, 182 deletions
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index 4f7d0d3c6..1212f992d 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useRef} from 'react' -import {StyleSheet, useWindowDimensions, View} from 'react-native' +import {useWindowDimensions, View} from 'react-native' import {runOnJS} from 'react-native-reanimated' import {AppBskyFeedDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' @@ -22,15 +22,16 @@ import { import {usePreferencesQuery} from '#/state/queries/preferences' import {useSession} from '#/state/session' import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' -import {usePalette} from 'lib/hooks/usePalette' import {useSetTitle} from 'lib/hooks/useSetTitle' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {sanitizeDisplayName} from 'lib/strings/display-names' import {cleanError} from 'lib/strings/errors' +import {CenteredView} from 'view/com/util/Views' +import {atoms as a, useTheme} from '#/alf' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {Text} from '#/components/Typography' import {ComposePrompt} from '../composer/Prompt' import {List, ListMethods} from '../util/List' -import {Text} from '../util/text/Text' import {ViewHeader} from '../util/ViewHeader' import {PostThreadItem} from './PostThreadItem' import {PostThreadShowHiddenReplies} from './PostThreadShowHiddenReplies' @@ -45,7 +46,6 @@ const MAINTAIN_VISIBLE_CONTENT_POSITION = { minIndexForVisible: 0, } -const TOP_COMPONENT = {_reactKey: '__top_component__'} const REPLY_PROMPT = {_reactKey: '__reply__'} const LOAD_MORE = {_reactKey: '__load_more__'} const SHOW_HIDDEN_REPLIES = {_reactKey: '__show_hidden_replies__'} @@ -66,7 +66,6 @@ type YieldedItem = type RowItem = | YieldedItem // TODO: TS doesn't actually enforce it's one of these, it only enforces matching shape. - | typeof TOP_COMPONENT | typeof REPLY_PROMPT | typeof LOAD_MORE @@ -91,7 +90,7 @@ export function PostThread({ }) { const {hasSession} = useSession() const {_} = useLingui() - const pal = usePalette('default') + const t = useTheme() const {isMobile, isTabletOrMobile} = useWebMediaQueries() const initialNumToRender = useInitialNumToRender() const {height: windowHeight} = useWindowDimensions() @@ -224,32 +223,21 @@ export function PostThread({ const {parents, highlightedPost, replies} = skeleton let arr: RowItem[] = [] if (highlightedPost.type === 'post') { - const isRoot = - !highlightedPost.parent && !highlightedPost.ctx.isParentLoading - if (isRoot) { - // No parents to load. - arr.push(TOP_COMPONENT) - } else { - if (highlightedPost.ctx.isParentLoading || deferParents) { - // We're loading parents of the highlighted post. - // In this case, we don't render anything above the post. - // If you add something here, you'll need to update both - // maintainVisibleContentPosition and onContentSizeChange - // to "hold onto" the correct row instead of the first one. - } else { - // Everything is loaded - let startIndex = Math.max(0, parents.length - maxParents) - if (startIndex === 0) { - arr.push(TOP_COMPONENT) - } else { - // When progressively revealing parents, rendering a placeholder - // here will cause scrolling jumps. Don't add it unless you test it. - // QT'ing this thread is a great way to test all the scrolling hacks: - // https://bsky.app/profile/www.mozzius.dev/post/3kjqhblh6qk2o - } - for (let i = startIndex; i < parents.length; i++) { - arr.push(parents[i]) - } + // We want to wait for parents to load before rendering. + // If you add something here, you'll need to update both + // maintainVisibleContentPosition and onContentSizeChange + // to "hold onto" the correct row instead of the first one. + + if (!highlightedPost.ctx.isParentLoading && !deferParents) { + // When progressively revealing parents, rendering a placeholder + // here will cause scrolling jumps. Don't add it unless you test it. + // QT'ing this thread is a great way to test all the scrolling hacks: + // https://bsky.app/profile/www.mozzius.dev/post/3kjqhblh6qk2o + + // Everything is loaded + let startIndex = Math.max(0, parents.length - maxParents) + for (let i = startIndex; i < parents.length; i++) { + arr.push(parents[i]) } } arr.push(highlightedPost) @@ -323,117 +311,100 @@ export function PostThread({ setMaxReplies(prev => prev + 50) }, [isFetching, maxReplies, posts.length]) - const renderItem = React.useCallback( - ({item, index}: {item: RowItem; index: number}) => { - if (item === TOP_COMPONENT) { - return isTabletOrMobile ? ( - <ViewHeader - title={_(msg({message: `Post`, context: 'description'}))} - /> - ) : null - } else if (item === REPLY_PROMPT && hasSession) { - return ( - <View> - {!isMobile && <ComposePrompt onPressCompose={onPressReply} />} - </View> - ) - } else if (item === SHOW_HIDDEN_REPLIES) { - return ( - <PostThreadShowHiddenReplies - type="hidden" - onPress={() => - setHiddenRepliesState(HiddenRepliesState.ShowAndOverridePostHider) - } - /> - ) - } else if (item === SHOW_MUTED_REPLIES) { - return ( - <PostThreadShowHiddenReplies - type="muted" - onPress={() => - setHiddenRepliesState(HiddenRepliesState.ShowAndOverridePostHider) + const hasParents = + skeleton?.highlightedPost?.type === 'post' && + (skeleton.highlightedPost.ctx.isParentLoading || + Boolean(skeleton?.parents && skeleton.parents.length > 0)) + const showHeader = + isNative || (isTabletOrMobile && (!hasParents || !isFetching)) + + const renderItem = ({item, index}: {item: RowItem; index: number}) => { + if (item === REPLY_PROMPT && hasSession) { + return ( + <View> + {!isMobile && <ComposePrompt onPressCompose={onPressReply} />} + </View> + ) + } else if (item === SHOW_HIDDEN_REPLIES || item === SHOW_MUTED_REPLIES) { + return ( + <PostThreadShowHiddenReplies + type={item === SHOW_HIDDEN_REPLIES ? 'hidden' : 'muted'} + onPress={() => + setHiddenRepliesState(HiddenRepliesState.ShowAndOverridePostHider) + } + hideTopBorder={index === 0} + /> + ) + } else if (isThreadNotFound(item)) { + return ( + <View + style={[ + a.p_lg, + index !== 0 && a.border_t, + t.atoms.border_contrast_low, + t.atoms.bg_contrast_25, + ]}> + <Text style={[a.font_bold, a.text_md, t.atoms.text_contrast_medium]}> + <Trans>Deleted post.</Trans> + </Text> + </View> + ) + } else if (isThreadBlocked(item)) { + return ( + <View + style={[ + a.p_lg, + index !== 0 && a.border_t, + t.atoms.border_contrast_low, + t.atoms.bg_contrast_25, + ]}> + <Text style={[a.font_bold, a.text_md, t.atoms.text_contrast_medium]}> + <Trans>Blocked post.</Trans> + </Text> + </View> + ) + } else if (isThreadPost(item)) { + const prev = isThreadPost(posts[index - 1]) + ? (posts[index - 1] as ThreadPost) + : undefined + const next = isThreadPost(posts[index + 1]) + ? (posts[index + 1] as ThreadPost) + : undefined + const showChildReplyLine = (next?.ctx.depth || 0) > item.ctx.depth + const showParentReplyLine = + (item.ctx.depth < 0 && !!item.parent) || item.ctx.depth > 1 + const hasUnrevealedParents = + index === 0 && skeleton?.parents && maxParents < skeleton.parents.length + return ( + <View + ref={item.ctx.isHighlightedPost ? highlightedPostRef : undefined} + onLayout={deferParents ? () => setDeferParents(false) : undefined}> + <PostThreadItem + post={item.post} + record={item.record} + moderation={threadModerationCache.get(item)} + treeView={treeView} + depth={item.ctx.depth} + prevPost={prev} + nextPost={next} + isHighlightedPost={item.ctx.isHighlightedPost} + hasMore={item.ctx.hasMore} + showChildReplyLine={showChildReplyLine} + showParentReplyLine={showParentReplyLine} + hasPrecedingItem={showParentReplyLine || !!hasUnrevealedParents} + overrideBlur={ + hiddenRepliesState === + HiddenRepliesState.ShowAndOverridePostHider && + item.ctx.depth > 0 } + onPostReply={refetch} + hideTopBorder={index === 0 && !item.ctx.isParentLoading} /> - ) - } else if (isThreadNotFound(item)) { - return ( - <View style={[pal.border, pal.viewLight, styles.itemContainer]}> - <Text type="lg-bold" style={pal.textLight}> - <Trans>Deleted post.</Trans> - </Text> - </View> - ) - } else if (isThreadBlocked(item)) { - return ( - <View style={[pal.border, pal.viewLight, styles.itemContainer]}> - <Text type="lg-bold" style={pal.textLight}> - <Trans>Blocked post.</Trans> - </Text> - </View> - ) - } else if (isThreadPost(item)) { - const prev = isThreadPost(posts[index - 1]) - ? (posts[index - 1] as ThreadPost) - : undefined - const next = isThreadPost(posts[index + 1]) - ? (posts[index + 1] as ThreadPost) - : undefined - const showChildReplyLine = (next?.ctx.depth || 0) > item.ctx.depth - const showParentReplyLine = - (item.ctx.depth < 0 && !!item.parent) || item.ctx.depth > 1 - const hasUnrevealedParents = - index === 0 && - skeleton?.parents && - maxParents < skeleton.parents.length - return ( - <View - ref={item.ctx.isHighlightedPost ? highlightedPostRef : undefined} - onLayout={deferParents ? () => setDeferParents(false) : undefined}> - <PostThreadItem - post={item.post} - record={item.record} - moderation={threadModerationCache.get(item)} - treeView={treeView} - depth={item.ctx.depth} - prevPost={prev} - nextPost={next} - isHighlightedPost={item.ctx.isHighlightedPost} - hasMore={item.ctx.hasMore} - showChildReplyLine={showChildReplyLine} - showParentReplyLine={showParentReplyLine} - hasPrecedingItem={showParentReplyLine || !!hasUnrevealedParents} - overrideBlur={ - hiddenRepliesState === - HiddenRepliesState.ShowAndOverridePostHider && - item.ctx.depth > 0 - } - onPostReply={refetch} - /> - </View> - ) - } - return null - }, - [ - hasSession, - isTabletOrMobile, - _, - isMobile, - onPressReply, - pal.border, - pal.viewLight, - pal.textLight, - posts, - skeleton?.parents, - maxParents, - deferParents, - treeView, - refetch, - threadModerationCache, - hiddenRepliesState, - setHiddenRepliesState, - ], - ) + </View> + ) + } + return null + } if (!thread || !preferences || error) { return ( @@ -449,39 +420,49 @@ export function PostThread({ } return ( - <ScrollProvider onMomentumEnd={onMomentumEnd}> - <List - ref={ref} - data={posts} - renderItem={renderItem} - keyExtractor={keyExtractor} - onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb} - onStartReached={onStartReached} - onEndReached={onEndReached} - onEndReachedThreshold={2} - onScrollToTop={onScrollToTop} - maintainVisibleContentPosition={ - isNative ? MAINTAIN_VISIBLE_CONTENT_POSITION : undefined - } - // @ts-ignore our .web version only -prf - desktopFixedHeight - removeClippedSubviews={isAndroid ? false : undefined} - ListFooterComponent={ - <ListFooter - // Using `isFetching` over `isFetchingNextPage` is done on purpose here so we get the loader on - // initial render - isFetchingNextPage={isFetching} - error={cleanError(threadError)} - onRetry={refetch} - // 300 is based on the minimum height of a post. This is enough extra height for the `maintainVisPos` to - // work without causing weird jumps on web or glitches on native - height={windowHeight - 200} - /> - } - initialNumToRender={initialNumToRender} - windowSize={11} - /> - </ScrollProvider> + <CenteredView style={[a.flex_1]} sideBorders={true}> + {showHeader && ( + <ViewHeader + title={_(msg({message: `Post`, context: 'description'}))} + showBorder + /> + )} + + <ScrollProvider onMomentumEnd={onMomentumEnd}> + <List + ref={ref} + data={posts} + renderItem={renderItem} + keyExtractor={keyExtractor} + onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb} + onStartReached={onStartReached} + onEndReached={onEndReached} + onEndReachedThreshold={2} + onScrollToTop={onScrollToTop} + maintainVisibleContentPosition={ + isNative ? MAINTAIN_VISIBLE_CONTENT_POSITION : undefined + } + // @ts-ignore our .web version only -prf + desktopFixedHeight + removeClippedSubviews={isAndroid ? false : undefined} + ListFooterComponent={ + <ListFooter + // Using `isFetching` over `isFetchingNextPage` is done on purpose here so we get the loader on + // initial render + isFetchingNextPage={isFetching} + error={cleanError(threadError)} + onRetry={refetch} + // 300 is based on the minimum height of a post. This is enough extra height for the `maintainVisPos` to + // work without causing weird jumps on web or glitches on native + height={windowHeight - 200} + /> + } + initialNumToRender={initialNumToRender} + windowSize={11} + sideBorders={false} + /> + </ScrollProvider> + </CenteredView> ) } @@ -630,11 +611,3 @@ function hasBranchingReplies(node?: ThreadNode) { } return true } - -const styles = StyleSheet.create({ - itemContainer: { - borderTopWidth: 1, - paddingHorizontal: 18, - paddingVertical: 18, - }, -}) |