diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index 4f7d0d3c..1212f992 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 ? (
-
- ) : null
- } else if (item === REPLY_PROMPT && hasSession) {
- return (
-
- {!isMobile && }
-
- )
- } else if (item === SHOW_HIDDEN_REPLIES) {
- return (
-
- 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 (
+
+ {!isMobile && }
+
+ )
+ } else if (item === SHOW_HIDDEN_REPLIES || item === SHOW_MUTED_REPLIES) {
+ return (
+
+ setHiddenRepliesState(HiddenRepliesState.ShowAndOverridePostHider)
+ }
+ hideTopBorder={index === 0}
+ />
+ )
+ } else if (isThreadNotFound(item)) {
+ return (
+
+
+ Deleted post.
+
+
+ )
+ } else if (isThreadBlocked(item)) {
+ return (
+
+
+ Blocked post.
+
+
+ )
+ } 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 (
+ setDeferParents(false) : undefined}>
+ 0
}
+ onPostReply={refetch}
+ hideTopBorder={index === 0 && !item.ctx.isParentLoading}
/>
- )
- } else if (item === SHOW_MUTED_REPLIES) {
- return (
-
- setHiddenRepliesState(HiddenRepliesState.ShowAndOverridePostHider)
- }
- />
- )
- } else if (isThreadNotFound(item)) {
- return (
-
-
- Deleted post.
-
-
- )
- } else if (isThreadBlocked(item)) {
- return (
-
-
- Blocked post.
-
-
- )
- } 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 (
- setDeferParents(false) : undefined}>
- 0
- }
- onPostReply={refetch}
- />
-
- )
- }
- return null
- },
- [
- hasSession,
- isTabletOrMobile,
- _,
- isMobile,
- onPressReply,
- pal.border,
- pal.viewLight,
- pal.textLight,
- posts,
- skeleton?.parents,
- maxParents,
- deferParents,
- treeView,
- refetch,
- threadModerationCache,
- hiddenRepliesState,
- setHiddenRepliesState,
- ],
- )
+
+ )
+ }
+ return null
+ }
if (!thread || !preferences || error) {
return (
@@ -449,39 +420,49 @@ export function PostThread({
}
return (
-
-
- }
- initialNumToRender={initialNumToRender}
- windowSize={11}
- />
-
+
+ {showHeader && (
+
+ )}
+
+
+
+ }
+ initialNumToRender={initialNumToRender}
+ windowSize={11}
+ sideBorders={false}
+ />
+
+
)
}
@@ -630,11 +611,3 @@ function hasBranchingReplies(node?: ThreadNode) {
}
return true
}
-
-const styles = StyleSheet.create({
- itemContainer: {
- borderTopWidth: 1,
- paddingHorizontal: 18,
- paddingVertical: 18,
- },
-})
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 0ff040b9..99fbda6d 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -65,6 +65,7 @@ export function PostThreadItem({
hasPrecedingItem,
overrideBlur,
onPostReply,
+ hideTopBorder,
}: {
post: AppBskyFeedDefs.PostView
record: AppBskyFeedPost.Record
@@ -80,6 +81,7 @@ export function PostThreadItem({
hasPrecedingItem: boolean
overrideBlur: boolean
onPostReply: () => void
+ hideTopBorder?: boolean
}) {
const postShadowed = usePostShadow(post)
const richText = useMemo(
@@ -91,7 +93,7 @@ export function PostThreadItem({
[record],
)
if (postShadowed === POST_TOMBSTONE) {
- return
+ return
}
if (richText && moderation) {
return (
@@ -113,16 +115,25 @@ export function PostThreadItem({
hasPrecedingItem={hasPrecedingItem}
overrideBlur={overrideBlur}
onPostReply={onPostReply}
+ hideTopBorder={hideTopBorder}
/>
)
}
return null
}
-function PostThreadItemDeleted() {
+function PostThreadItemDeleted({hideTopBorder}: {hideTopBorder?: boolean}) {
const pal = usePalette('default')
return (
-
+
This post has been deleted.
@@ -147,6 +158,7 @@ let PostThreadItemLoaded = ({
hasPrecedingItem,
overrideBlur,
onPostReply,
+ hideTopBorder,
}: {
post: Shadow
record: AppBskyFeedPost.Record
@@ -163,6 +175,7 @@ let PostThreadItemLoaded = ({
hasPrecedingItem: boolean
overrideBlur: boolean
onPostReply: () => void
+ hideTopBorder?: boolean
}): React.ReactNode => {
const pal = usePalette('default')
const {_} = useLingui()
@@ -237,7 +250,7 @@ let PostThreadItemLoaded = ({
styles.replyLine,
{
flexGrow: 1,
- backgroundColor: pal.colors.border,
+ backgroundColor: pal.colors.replyLine,
},
]}
/>
@@ -247,7 +260,14 @@ let PostThreadItemLoaded = ({
@@ -395,7 +415,8 @@ let PostThreadItemLoaded = ({
depth={depth}
showParentReplyLine={!!showParentReplyLine}
treeView={treeView}
- hasPrecedingItem={hasPrecedingItem}>
+ hasPrecedingItem={hasPrecedingItem}
+ hideTopBorder={hideTopBorder}>
) {
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default')
@@ -617,6 +640,7 @@ function PostOuterWrapper({
styles.outer,
pal.border,
showParentReplyLine && hasPrecedingItem && styles.noTopBorder,
+ hideTopBorder && styles.noTopBorder,
styles.cursor,
]}>
{children}
@@ -677,10 +701,15 @@ const styles = StyleSheet.create({
paddingLeft: 8,
},
outerHighlighted: {
- paddingTop: 16,
+ borderTopWidth: 0,
+ paddingTop: 4,
paddingLeft: 8,
paddingRight: 8,
},
+ outerHighlightedRoot: {
+ borderTopWidth: 1,
+ paddingTop: 16,
+ },
noTopBorder: {
borderTopWidth: 0,
},
diff --git a/src/view/com/post-thread/PostThreadShowHiddenReplies.tsx b/src/view/com/post-thread/PostThreadShowHiddenReplies.tsx
index 99890652..7c021d88 100644
--- a/src/view/com/post-thread/PostThreadShowHiddenReplies.tsx
+++ b/src/view/com/post-thread/PostThreadShowHiddenReplies.tsx
@@ -11,9 +11,11 @@ import {Text} from '#/components/Typography'
export function PostThreadShowHiddenReplies({
type,
onPress,
+ hideTopBorder,
}: {
type: 'hidden' | 'muted'
onPress: () => void
+ hideTopBorder?: boolean
}) {
const {_} = useLingui()
const t = useTheme()
@@ -31,7 +33,7 @@ export function PostThreadShowHiddenReplies({
a.gap_sm,
a.py_lg,
a.px_xl,
- a.border_t,
+ !hideTopBorder && a.border_t,
t.atoms.border_contrast_low,
hovered || pressed ? t.atoms.bg_contrast_25 : t.atoms.bg,
]}>