| ... | ... | @@ -45,14 +45,7 @@ import { |
| 45 | 45 | import { TouchableOpacity } from "react-native-gesture-handler"; |
| 46 | 46 | import ImageViewing from "react-native-image-viewing"; |
| 47 | 47 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 48 | | import Animated, { |
| 49 | | FadeIn, |
| 50 | | FadeInUp, |
| 51 | | useSharedValue, |
| 52 | | withTiming, |
| 53 | | type LayoutAnimationsValues, |
| 54 | | type SharedValue, |
| 55 | | } from "react-native-reanimated"; |
| 48 | import Animated, { FadeIn, FadeInUp, type SharedValue } from "react-native-reanimated"; |
| 56 | 49 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 57 | 50 | import { useFontFamily } from "../../lib/useFontFamily"; |
| 58 | 51 | import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic"; |
| ... | ... | @@ -117,14 +110,6 @@ function formatMessageTime(input: string): string { |
| 117 | 110 | return MESSAGE_TIME_FORMATTER.format(timestamp); |
| 118 | 111 | } |
| 119 | 112 | |
| 120 | | // Rows shift when content above them grows (streaming text, work-log folds); |
| 121 | | // animating the container position turns those jumps into slides. Applied |
| 122 | | // conditionally — see the gated transition in ThreadFeed: while browsing |
| 123 | | // history the animation must NOT run, or every estimate→actual size |
| 124 | | // correction plays as a visible slide against the instant scroll-offset |
| 125 | | // compensation from maintainVisibleContentPosition. |
| 126 | | const FEED_ITEM_LAYOUT_DURATION_MS = 180; |
| 127 | | |
| 128 | 113 | // Pre-measurement heights for getFixedItemSize, mirroring renderFeedEntry's |
| 129 | 114 | // classNames. The fold row's min-h-11 (44px) stays taller than its single |
| 130 | 115 | // text-sm line at every supported base font size (26px at the 22pt maximum), |
| ... | ... | @@ -1457,11 +1442,6 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { |
| 1457 | 1442 | }, |
| 1458 | 1443 | [props.onHeaderMaterialVisibilityChange], |
| 1459 | 1444 | ); |
| 1460 | | // True while the viewport sits within ~one screen of the list end — the |
| 1461 | | // only region where layout shifts should animate. Starts true because the |
| 1462 | | // list opens pinned to the end. |
| 1463 | | const nearListEnd = useSharedValue(true); |
| 1464 | | |
| 1465 | 1445 | const handleScroll = useCallback( |
| 1466 | 1446 | (event: NativeSyntheticEvent<NativeScrollEvent>) => { |
| 1467 | 1447 | // anchorTopInset, not topContentInset: under automatic insets the list |
| ... | ... | @@ -1469,10 +1449,6 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { |
| 1469 | 1449 | // UIKit's adjustedContentInset, so topContentInset is 0 here). Add the |
| 1470 | 1450 | // header height back or the material toggles a full header too late. |
| 1471 | 1451 | reportHeaderMaterialVisibility(event.nativeEvent.contentOffset.y + anchorTopInset > 6); |
| 1472 | | const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent; |
| 1473 | | nearListEnd.value = |
| 1474 | | contentSize.height - layoutMeasurement.height - contentOffset.y < layoutMeasurement.height; |
| 1475 | | |
| 1476 | 1452 | // Latch bookkeeping. LegendList recomputes its inset-aware end distance |
| 1477 | 1453 | // before invoking this handler, so getState() is current. Returning to |
| 1478 | 1454 | // the end re-arms follow no matter who scrolled (the user, or our own |
| ... | ... | @@ -1488,7 +1464,7 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { |
| 1488 | 1464 | } |
| 1489 | 1465 | } |
| 1490 | 1466 | }, |
| 1491 | | [reportHeaderMaterialVisibility, anchorTopInset, nearListEnd, props.listRef, setEndFollow], |
| 1467 | [reportHeaderMaterialVisibility, anchorTopInset, props.listRef, setEndFollow], |
| 1492 | 1468 | ); |
| 1493 | 1469 | const handleScrollBeginDrag = useCallback(() => { |
| 1494 | 1470 | userScrollSessionRef.current = true; |
| ... | ... | @@ -1508,33 +1484,6 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { |
| 1508 | 1484 | userScrollSessionRef.current = false; |
| 1509 | 1485 | }, []); |
| 1510 | 1486 | |
| 1511 | | // Gated variant of the 180ms feed layout slide. Instant while browsing |
| 1512 | | // history: maintainVisibleContentPosition compensates the scroll offset in |
| 1513 | | // the same frame a row's measured size lands, so an instant reposition is |
| 1514 | | // invisible — animating it is exactly what made cold upward scrolls slide |
| 1515 | | // and jump. Near the end the slide stays on: streaming growth and sends |
| 1516 | | // shift rows at rest, where the animation is the thing preventing a hard |
| 1517 | | // visual snap. |
| 1518 | | const feedItemLayoutTransition = useMemo(() => { |
| 1519 | | return (values: LayoutAnimationsValues) => { |
| 1520 | | "worklet"; |
| 1521 | | const duration = nearListEnd.value ? FEED_ITEM_LAYOUT_DURATION_MS : 0; |
| 1522 | | return { |
| 1523 | | initialValues: { |
| 1524 | | originX: values.currentOriginX, |
| 1525 | | originY: values.currentOriginY, |
| 1526 | | width: values.currentWidth, |
| 1527 | | height: values.currentHeight, |
| 1528 | | }, |
| 1529 | | animations: { |
| 1530 | | originX: withTiming(values.targetOriginX, { duration }), |
| 1531 | | originY: withTiming(values.targetOriginY, { duration }), |
| 1532 | | width: withTiming(values.targetWidth, { duration }), |
| 1533 | | height: withTiming(values.targetHeight, { duration }), |
| 1534 | | }, |
| 1535 | | }; |
| 1536 | | }; |
| 1537 | | }, [nearListEnd]); |
| 1538 | 1487 | const handleViewportLayout = useCallback((event: LayoutChangeEvent) => { |
| 1539 | 1488 | const nextWidth = Math.round(event.nativeEvent.layout.width); |
| 1540 | 1489 | const nextHeight = Math.round(event.nativeEvent.layout.height); |
| ... | ... | @@ -1886,7 +1835,6 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { |
| 1886 | 1835 | } |
| 1887 | 1836 | : { scrollIndicatorInsets: { top: topContentInset, bottom: 0 } })} |
| 1888 | 1837 | {...(anchoredEndSpace ? { anchoredEndSpace } : {})} |
| 1889 | | itemLayoutAnimation={feedItemLayoutTransition} |
| 1890 | 1838 | // Patched LegendList prop (patches/@legendapp__list@3.2.0.patch): |
| 1891 | 1839 | // lets its scroll math clamp programmatic scrolls to -headerInset |
| 1892 | 1840 | // instead of 0, so initialScrollAtEnd/maintainScrollAtEnd on short |