Bluesky app fork with some witchin' additions 💫

Dejank navigation between thread posts (#2625)

* Dejank parent thread spinner

* Fix bottom border/spinner jank

* Revert unnecessary change

authored by danabra.mov and committed by

GitHub ef84f3a2 de6b380f

+23 -45
+23 -38
src/view/com/post-thread/PostThread.tsx
··· 45 45 import {logger} from '#/logger' 46 46 import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped' 47 47 48 - const MAINTAIN_VISIBLE_CONTENT_POSITION = {minIndexForVisible: 2} 48 + const MAINTAIN_VISIBLE_CONTENT_POSITION = {minIndexForVisible: 1} 49 49 50 50 const TOP_COMPONENT = {_reactKey: '__top_component__'} 51 - const PARENT_SPINNER = {_reactKey: '__parent_spinner__'} 52 51 const REPLY_PROMPT = {_reactKey: '__reply__'} 53 52 const DELETED = {_reactKey: '__deleted__'} 54 53 const BLOCKED = {_reactKey: '__blocked__'} ··· 59 58 type YieldedItem = 60 59 | ThreadPost 61 60 | typeof TOP_COMPONENT 62 - | typeof PARENT_SPINNER 63 61 | typeof REPLY_PROMPT 64 62 | typeof DELETED 65 63 | typeof BLOCKED 66 - | typeof PARENT_SPINNER 67 64 68 65 export function PostThread({ 69 66 uri, ··· 152 149 const {hasSession} = useSession() 153 150 const {_} = useLingui() 154 151 const pal = usePalette('default') 155 - const {isTablet, isMobile, isDesktop, isTabletOrMobile} = useWebMediaQueries() 152 + const {isMobile, isTabletOrMobile} = useWebMediaQueries() 156 153 const ref = useRef<ListMethods>(null) 157 154 const highlightedPostRef = useRef<View | null>(null) 158 155 const needsScrollAdjustment = useRef<boolean>( ··· 168 165 169 166 // construct content 170 167 const posts = React.useMemo(() => { 171 - let arr = [TOP_COMPONENT].concat( 172 - Array.from( 173 - flattenThreadSkeleton( 174 - sortThread(thread, threadViewPrefs), 175 - hasSession, 176 - treeView, 177 - ), 168 + let arr = Array.from( 169 + flattenThreadSkeleton( 170 + sortThread(thread, threadViewPrefs), 171 + hasSession, 172 + treeView, 178 173 ), 179 174 ) 180 175 if (arr.length > maxVisible) { ··· 215 210 // wait for loading to finish 216 211 if (thread.type === 'post' && !!thread.parent) { 217 212 function onMeasure(pageY: number) { 218 - let spinnerHeight = 0 219 - if (isDesktop) { 220 - spinnerHeight = 40 221 - } else if (isTabletOrMobile) { 222 - spinnerHeight = 82 223 - } 224 213 ref.current?.scrollToOffset({ 225 214 animated: false, 226 - offset: pageY - spinnerHeight, 215 + offset: pageY, 227 216 }) 228 217 } 229 218 if (isNative) { ··· 242 231 } 243 232 needsScrollAdjustment.current = false 244 233 } 245 - }, [thread, isDesktop, isTabletOrMobile]) 234 + }, [thread]) 246 235 247 236 const onPTR = React.useCallback(async () => { 248 237 setIsPTRing(true) ··· 257 246 const renderItem = React.useCallback( 258 247 ({item, index}: {item: YieldedItem; index: number}) => { 259 248 if (item === TOP_COMPONENT) { 260 - return isTablet ? ( 249 + return isTabletOrMobile ? ( 261 250 <ViewHeader 262 251 title={_(msg({message: `Post`, context: 'description'}))} 263 252 /> 264 253 ) : null 265 - } else if (item === PARENT_SPINNER) { 266 - return ( 267 - <View style={styles.parentSpinner}> 268 - <ActivityIndicator /> 269 - </View> 270 - ) 271 254 } else if (item === REPLY_PROMPT && hasSession) { 272 255 return ( 273 256 <View> ··· 318 301 // @ts-ignore web-only 319 302 style={{ 320 303 // Leave enough space below that the scroll doesn't jump 321 - height: isNative ? 400 : '100vh', 304 + height: isNative ? 600 : '100vh', 322 305 borderTopWidth: 1, 323 306 borderColor: pal.colors.border, 324 307 }} ··· 326 309 ) 327 310 } else if (item === CHILD_SPINNER) { 328 311 return ( 329 - <View style={styles.childSpinner}> 312 + <View style={[pal.border, styles.childSpinner]}> 330 313 <ActivityIndicator /> 331 314 </View> 332 315 ) ··· 361 344 }, 362 345 [ 363 346 hasSession, 364 - isTablet, 347 + isTabletOrMobile, 365 348 isMobile, 366 349 onPressReply, 367 350 pal.border, ··· 507 490 node: ThreadNode, 508 491 hasSession: boolean, 509 492 treeView: boolean, 493 + isTraversingReplies: boolean = false, 510 494 ): Generator<YieldedItem, void> { 511 495 if (node.type === 'post') { 512 - if (node.parent) { 513 - yield* flattenThreadSkeleton(node.parent, hasSession, treeView) 514 - } else if (node.ctx.isParentLoading) { 515 - yield PARENT_SPINNER 496 + if (!node.ctx.isParentLoading) { 497 + if (node.parent) { 498 + yield* flattenThreadSkeleton(node.parent, hasSession, treeView, false) 499 + } else if (!isTraversingReplies) { 500 + yield TOP_COMPONENT 501 + } 516 502 } 517 503 if (!hasSession && node.ctx.depth > 0 && hasPwiOptOut(node)) { 518 504 return ··· 523 509 } 524 510 if (node.replies?.length) { 525 511 for (const reply of node.replies) { 526 - yield* flattenThreadSkeleton(reply, hasSession, treeView) 512 + yield* flattenThreadSkeleton(reply, hasSession, treeView, true) 527 513 if (!treeView && !node.ctx.isHighlightedPost) { 528 514 break 529 515 } ··· 567 553 paddingHorizontal: 18, 568 554 paddingVertical: 18, 569 555 }, 570 - parentSpinner: { 571 - paddingVertical: 10, 572 - }, 573 556 childSpinner: { 557 + borderTopWidth: 1, 558 + paddingTop: 40, 574 559 paddingBottom: 200, 575 560 }, 576 561 })
-7
src/view/screens/PostThread.tsx
··· 5 5 import {useQueryClient} from '@tanstack/react-query' 6 6 import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 7 7 import {makeRecordUri} from 'lib/strings/url-helpers' 8 - import {ViewHeader} from '../com/util/ViewHeader' 9 8 import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread' 10 9 import {ComposePrompt} from 'view/com/composer/Prompt' 11 10 import {s} from 'lib/styles' ··· 18 17 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 19 18 import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' 20 19 import {useSetMinimalShellMode} from '#/state/shell' 21 - import {useLingui} from '@lingui/react' 22 - import {msg} from '@lingui/macro' 23 20 import {useResolveUriQuery} from '#/state/queries/resolve-uri' 24 21 import {ErrorMessage} from '../com/util/error/ErrorMessage' 25 22 import {CenteredView} from '../com/util/Views' ··· 30 27 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'> 31 28 export function PostThreadScreen({route}: Props) { 32 29 const queryClient = useQueryClient() 33 - const {_} = useLingui() 34 30 const {hasSession} = useSession() 35 31 const {fabMinimalShellTransform} = useMinimalShellMode() 36 32 const setMinimalShellMode = useSetMinimalShellMode() ··· 79 75 80 76 return ( 81 77 <View style={s.hContentRegion}> 82 - {isMobile && ( 83 - <ViewHeader title={_(msg({message: 'Post', context: 'description'}))} /> 84 - )} 85 78 <View style={s.flex1}> 86 79 {uriError ? ( 87 80 <CenteredView>