authorgravatar for me@pixp.ccAlex <me@pixp.cc> 2026-10-11 11:28:25-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-11 11:28:25-07:00
logb4af60253bf013ebd146e23d8f66af94854da5d6
treea288cff1f512a40e49f99400c1479134f88bbc89
parent4bd0ecca19934dce38e16cd42eba5bf81c170d91
signature Signed by PGP key B5690EEEBB952194

fix(mobile): restore compact iPad trackpad scrolling (#13665)


1 files changed, 52 insertions(+), 45 deletions(-)

apps/mobile/src/features/home/HomeScreen.tsx+52-45
...@@ -25,6 +25,7 @@ import {...@@ -25,6 +25,7 @@ import {
25 type NativeSyntheticEvent,25 type NativeSyntheticEvent,
26} from "react-native";26} from "react-native";
27import { ScrollViewMarker } from "react-native-screens";27import { ScrollViewMarker } from "react-native-screens";
28import { GestureDetector, useNativeGesture } from "react-native-gesture-handler";
28import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable";29import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable";
29import { useSafeAreaInsets } from "react-native-safe-area-context";30import { useSafeAreaInsets } from "react-native-safe-area-context";
3031
...@@ -280,6 +281,8 @@ export function HomeScreen(props: HomeScreenProps) {...@@ -280,6 +281,8 @@ export function HomeScreen(props: HomeScreenProps) {
280 const fullSwipeWidth = primaryColumn && columnMetrics ? columnMetrics.width - 20 : undefined;281 const fullSwipeWidth = primaryColumn && columnMetrics ? columnMetrics.width - 20 : undefined;
281 const queuedThreadKeys = useQueuedThreadKeys();282 const queuedThreadKeys = useQueuedThreadKeys();
282 const openSwipeableRef = useRef<SwipeableMethods | null>(null);283 const openSwipeableRef = useRef<SwipeableMethods | null>(null);
284 // Let trackpad scrolling coexist with each row's horizontal swipe gesture.
285 const homeScrollGesture = useNativeGesture();
283 const insets = useSafeAreaInsets();286 const insets = useSafeAreaInsets();
284 const { fabClearance } = useAndroidControlSizing();287 const { fabClearance } = useAndroidControlSizing();
285 const screenMetrics = useNativeLayoutMetrics();288 const screenMetrics = useNativeLayoutMetrics();
...@@ -807,6 +810,7 @@ export function HomeScreen(props: HomeScreenProps) {...@@ -807,6 +810,7 @@ export function HomeScreen(props: HomeScreenProps) {
807 onMoveThread={handleMoveThread}810 onMoveThread={handleMoveThread}
808 onSwipeableClose={handleSwipeableClose}811 onSwipeableClose={handleSwipeableClose}
809 onSwipeableWillOpen={handleSwipeableWillOpen}812 onSwipeableWillOpen={handleSwipeableWillOpen}
813 simultaneousSwipeGesture={homeScrollGesture}
810 activationKey={item.key}814 activationKey={item.key}
811 />815 />
812 );816 );
...@@ -826,6 +830,7 @@ export function HomeScreen(props: HomeScreenProps) {...@@ -826,6 +830,7 @@ export function HomeScreen(props: HomeScreenProps) {
826 handleSwipeableWillOpen,830 handleSwipeableWillOpen,
827 handleUnsettleThread,831 handleUnsettleThread,
828 handleSetThreadAutoSettle,832 handleSetThreadAutoSettle,
833 homeScrollGesture,
829 autoSettleOptOutEnvironmentIds,834 autoSettleOptOutEnvironmentIds,
830 pinningEnvironmentIds,835 pinningEnvironmentIds,
831 autoSettleOptOutEnvironmentIds,836 autoSettleOptOutEnvironmentIds,
...@@ -1012,51 +1017,53 @@ export function HomeScreen(props: HomeScreenProps) {...@@ -1012,51 +1017,53 @@ export function HomeScreen(props: HomeScreenProps) {
1012 rebuilds and `itemsAreEqual` keeps a minute tick (or an unrelated1017 rebuilds and `itemsAreEqual` keeps a minute tick (or an unrelated
1013 shell update) from re-rendering untouched rows. */}1018 shell update) from re-rendering untouched rows. */}
1014 <SwipeableScrollGateProvider enabled={swipeEnabled} activation={swipeRowActivation}>1019 <SwipeableScrollGateProvider enabled={swipeEnabled} activation={swipeRowActivation}>
1015 <LegendList1020 <GestureDetector gesture={homeScrollGesture}>
1016 ref={listRef}1021 <LegendList
1017 onLoad={() => activateVisibleRows(threadListV2Items)}1022 ref={listRef}
1018 onTouchStart={(event) => trackListTouches(event, true)}1023 onLoad={() => activateVisibleRows(threadListV2Items)}
1019 onTouchEnd={(event) => trackListTouches(event, false)}1024 onTouchStart={(event) => trackListTouches(event, true)}
1020 onTouchCancel={(event) => trackListTouches(event, false)}1025 onTouchEnd={(event) => trackListTouches(event, false)}
1021 renderScrollComponent={Platform.OS === "ios" ? renderHomeScrollView : undefined}1026 onTouchCancel={(event) => trackListTouches(event, false)}
1022 data={threadListV2Items}1027 renderScrollComponent={Platform.OS === "ios" ? renderHomeScrollView : undefined}
1023 renderItem={renderV2Item}1028 data={threadListV2Items}
1024 keyExtractor={v2KeyExtractor}1029 renderItem={renderV2Item}
1025 getItemType={(item) => item.type}1030 keyExtractor={v2KeyExtractor}
1026 itemsAreEqual={threadListV2ListItemsAreEqual}1031 getItemType={(item) => item.type}
1027 estimatedItemSize={ESTIMATED_THREAD_LIST_V2_ROW_HEIGHT}1032 itemsAreEqual={threadListV2ListItemsAreEqual}
1028 drawDistance={THREAD_LIST_V2_DRAW_DISTANCE}1033 estimatedItemSize={ESTIMATED_THREAD_LIST_V2_ROW_HEIGHT}
1029 recycleItems1034 drawDistance={THREAD_LIST_V2_DRAW_DISTANCE}
1030 extraData={v2ExtraData}1035 recycleItems
1031 ListHeaderComponent={v2ListHeader}1036 extraData={v2ExtraData}
1032 ListFooterComponent={1037 ListHeaderComponent={v2ListHeader}
1033 settledShelfExpanded && threadListV2Layout.hiddenSettledCount > 0 ? (1038 ListFooterComponent={
1034 <ThreadListV2ShowMoreRow1039 settledShelfExpanded && threadListV2Layout.hiddenSettledCount > 0 ? (
1035 hiddenCount={threadListV2Layout.hiddenSettledCount}1040 <ThreadListV2ShowMoreRow
1036 onPress={showMoreSettled}1041 hiddenCount={threadListV2Layout.hiddenSettledCount}
1037 />1042 onPress={showMoreSettled}
1038 ) : null1043 />
1039 }1044 ) : null
1040 ListEmptyComponent={v2ListEmpty}1045 }
1041 style={{ flex: 1 }}1046 ListEmptyComponent={v2ListEmpty}
1042 automaticallyAdjustsScrollIndicatorInsets={Platform.OS === "ios"}1047 style={{ flex: 1 }}
1043 contentInsetAdjustmentBehavior="never"1048 automaticallyAdjustsScrollIndicatorInsets={Platform.OS === "ios"}
1044 contentInset={Platform.OS === "ios" ? { top: insets.top } : undefined}1049 contentInsetAdjustmentBehavior="never"
1045 contentInsetStartAdjustment={Platform.OS === "ios" ? insets.top : 0}1050 contentInset={Platform.OS === "ios" ? { top: insets.top } : undefined}
1046 showsVerticalScrollIndicator={false}1051 contentInsetStartAdjustment={Platform.OS === "ios" ? insets.top : 0}
1047 keyboardDismissMode="on-drag"1052 showsVerticalScrollIndicator={false}
1048 keyboardShouldPersistTaps="handled"1053 keyboardDismissMode="on-drag"
1049 {...scrollGateHandlers}1054 keyboardShouldPersistTaps="handled"
1050 scrollEventThrottle={16}1055 {...scrollGateHandlers}
1051 contentContainerStyle={{1056 scrollEventThrottle={16}
1052 paddingLeft: (contentSideInsets?.left ?? 0) + (primaryColumn ? 8 : 0),1057 contentContainerStyle={{
1053 paddingRight: (contentSideInsets?.right ?? 0) + (primaryColumn ? 8 : 0),1058 paddingLeft: (contentSideInsets?.left ?? 0) + (primaryColumn ? 8 : 0),
1054 paddingBottom:1059 paddingRight: (contentSideInsets?.right ?? 0) + (primaryColumn ? 8 : 0),
1055 Platform.OS === "ios"1060 paddingBottom:
1056 ? iosBottomClearance1061 Platform.OS === "ios"
1057 : Math.max(insets.bottom, 16) + (Platform.OS === "android" ? fabClearance : 88),1062 ? iosBottomClearance
1058 }}1063 : Math.max(insets.bottom, 16) + (Platform.OS === "android" ? fabClearance : 88),
1059 />1064 }}
1065 />
1066 </GestureDetector>
1060 </SwipeableScrollGateProvider>1067 </SwipeableScrollGateProvider>
1061 </View>1068 </View>
1062 </View>1069 </View>