+3
-3
src/screens/Messages/components/MessagesList.tsx
+3
-3
src/screens/Messages/components/MessagesList.tsx
···
1
1
import React, {useCallback, useRef} from 'react'
2
-
import {FlatList, LayoutChangeEvent, View} from 'react-native'
2
+
import {LayoutChangeEvent, View} from 'react-native'
3
3
import {
4
4
KeyboardStickyView,
5
5
useKeyboardHandler,
···
33
33
EmojiPicker,
34
34
EmojiPickerState,
35
35
} from '#/view/com/composer/text-input/web/EmojiPicker.web'
36
-
import {List} from '#/view/com/util/List'
36
+
import {List, ListMethods} from '#/view/com/util/List'
37
37
import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled'
38
38
import {MessageInput} from '#/screens/Messages/components/MessageInput'
39
39
import {MessageListError} from '#/screens/Messages/components/MessageListError'
···
94
94
const getPost = useGetPost()
95
95
const {embedUri, setEmbed} = useMessageEmbed()
96
96
97
-
const flatListRef = useAnimatedRef<FlatList>()
97
+
const flatListRef = useAnimatedRef<ListMethods>()
98
98
99
99
const [newMessagesPill, setNewMessagesPill] = React.useState({
100
100
show: false,
+2
-1
src/screens/Onboarding/Layout.tsx
+2
-1
src/screens/Onboarding/Layout.tsx
···
1
1
import React from 'react'
2
2
import {View} from 'react-native'
3
+
import Animated from 'react-native-reanimated'
3
4
import {useSafeAreaInsets} from 'react-native-safe-area-context'
4
5
import {msg} from '@lingui/macro'
5
6
import {useLingui} from '@lingui/react'
···
35
36
const {gtMobile} = useBreakpoints()
36
37
const onboardDispatch = useOnboardingDispatch()
37
38
const {state, dispatch} = React.useContext(Context)
38
-
const scrollview = React.useRef<ScrollView>(null)
39
+
const scrollview = React.useRef<Animated.ScrollView>(null)
39
40
const prevActiveStep = React.useRef<string>(state.activeStep)
40
41
41
42
React.useEffect(() => {
+2
-2
src/view/com/pager/PagerWithHeader.web.tsx
+2
-2
src/view/com/pager/PagerWithHeader.web.tsx
···
1
1
import * as React from 'react'
2
-
import {FlatList, ScrollView, StyleSheet, View} from 'react-native'
2
+
import {ScrollView, StyleSheet, View} from 'react-native'
3
3
import {useAnimatedRef} from 'react-native-reanimated'
4
4
5
5
import {usePalette} from '#/lib/hooks/usePalette'
···
11
11
export interface PagerWithHeaderChildParams {
12
12
headerHeight: number
13
13
isFocused: boolean
14
-
scrollElRef: React.MutableRefObject<FlatList<any> | ScrollView | null>
14
+
scrollElRef: React.MutableRefObject<ListMethods | ScrollView | null>
15
15
}
16
16
17
17
export interface PagerWithHeaderProps {
+126
-114
src/view/com/util/List.tsx
+126
-114
src/view/com/util/List.tsx
···
1
1
import React, {memo} from 'react'
2
-
import {FlatListProps, RefreshControl, ViewToken} from 'react-native'
3
-
import {runOnJS, useSharedValue} from 'react-native-reanimated'
2
+
import {RefreshControl, ViewToken} from 'react-native'
3
+
import {
4
+
FlatListPropsWithLayout,
5
+
runOnJS,
6
+
useSharedValue,
7
+
} from 'react-native-reanimated'
4
8
import {updateActiveVideoViewAsync} from '@haileyok/bluesky-video'
5
9
6
10
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
···
13
17
import {FlatList_INTERNAL} from './Views'
14
18
15
19
export type ListMethods = FlatList_INTERNAL
16
-
export type ListProps<ItemT> = Omit<
17
-
FlatListProps<ItemT>,
20
+
export type ListProps<ItemT = any> = Omit<
21
+
FlatListPropsWithLayout<ItemT>,
18
22
| 'onMomentumScrollBegin' // Use ScrollContext instead.
19
23
| 'onMomentumScrollEnd' // Use ScrollContext instead.
20
24
| 'onScroll' // Use ScrollContext instead.
···
22
26
| 'onScrollEndDrag' // Use ScrollContext instead.
23
27
| 'refreshControl' // Pass refreshing and/or onRefresh instead.
24
28
| 'contentOffset' // Pass headerOffset instead.
29
+
| 'progressViewOffset' // Can't be an animated value
25
30
> & {
26
31
onScrolledDownChange?: (isScrolledDown: boolean) => void
27
32
headerOffset?: number
···
32
37
// Web only prop to contain the scroll to the container rather than the window
33
38
disableFullWindowScroll?: boolean
34
39
sideBorders?: boolean
40
+
progressViewOffset?: number
35
41
}
36
42
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
37
43
38
44
const SCROLLED_DOWN_LIMIT = 200
39
45
40
-
function ListImpl<ItemT>(
41
-
{
42
-
onScrolledDownChange,
43
-
refreshing,
44
-
onRefresh,
45
-
onItemSeen,
46
-
headerOffset,
47
-
style,
48
-
progressViewOffset,
49
-
...props
50
-
}: ListProps<ItemT>,
51
-
ref: React.Ref<ListMethods>,
52
-
) {
53
-
const isScrolledDown = useSharedValue(false)
54
-
const t = useTheme()
55
-
const dedupe = useDedupe(400)
56
-
const {activeLightbox} = useLightbox()
57
-
58
-
function handleScrolledDownChange(didScrollDown: boolean) {
59
-
onScrolledDownChange?.(didScrollDown)
60
-
}
61
-
62
-
// Intentionally destructured outside the main thread closure.
63
-
// See https://github.com/bluesky-social/social-app/pull/4108.
64
-
const {
65
-
onBeginDrag: onBeginDragFromContext,
66
-
onEndDrag: onEndDragFromContext,
67
-
onScroll: onScrollFromContext,
68
-
onMomentumEnd: onMomentumEndFromContext,
69
-
} = useScrollHandlers()
70
-
const scrollHandler = useAnimatedScrollHandler({
71
-
onBeginDrag(e, ctx) {
72
-
onBeginDragFromContext?.(e, ctx)
73
-
},
74
-
onEndDrag(e, ctx) {
75
-
runOnJS(updateActiveVideoViewAsync)()
76
-
onEndDragFromContext?.(e, ctx)
46
+
let List = React.forwardRef<ListMethods, ListProps>(
47
+
(
48
+
{
49
+
onScrolledDownChange,
50
+
refreshing,
51
+
onRefresh,
52
+
onItemSeen,
53
+
headerOffset,
54
+
style,
55
+
progressViewOffset,
56
+
...props
77
57
},
78
-
onScroll(e, ctx) {
79
-
onScrollFromContext?.(e, ctx)
58
+
ref,
59
+
): React.ReactElement => {
60
+
const isScrolledDown = useSharedValue(false)
61
+
const t = useTheme()
62
+
const dedupe = useDedupe(400)
63
+
const {activeLightbox} = useLightbox()
80
64
81
-
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
82
-
if (isScrolledDown.get() !== didScrollDown) {
83
-
isScrolledDown.set(didScrollDown)
84
-
if (onScrolledDownChange != null) {
85
-
runOnJS(handleScrolledDownChange)(didScrollDown)
86
-
}
87
-
}
65
+
function handleScrolledDownChange(didScrollDown: boolean) {
66
+
onScrolledDownChange?.(didScrollDown)
67
+
}
88
68
89
-
if (isIOS) {
90
-
runOnJS(dedupe)(updateActiveVideoViewAsync)
91
-
}
92
-
},
93
-
// Note: adding onMomentumBegin here makes simulator scroll
94
-
// lag on Android. So either don't add it, or figure out why.
95
-
onMomentumEnd(e, ctx) {
96
-
runOnJS(updateActiveVideoViewAsync)()
97
-
onMomentumEndFromContext?.(e, ctx)
98
-
},
99
-
})
69
+
// Intentionally destructured outside the main thread closure.
70
+
// See https://github.com/bluesky-social/social-app/pull/4108.
71
+
const {
72
+
onBeginDrag: onBeginDragFromContext,
73
+
onEndDrag: onEndDragFromContext,
74
+
onScroll: onScrollFromContext,
75
+
onMomentumEnd: onMomentumEndFromContext,
76
+
} = useScrollHandlers()
77
+
const scrollHandler = useAnimatedScrollHandler({
78
+
onBeginDrag(e, ctx) {
79
+
onBeginDragFromContext?.(e, ctx)
80
+
},
81
+
onEndDrag(e, ctx) {
82
+
runOnJS(updateActiveVideoViewAsync)()
83
+
onEndDragFromContext?.(e, ctx)
84
+
},
85
+
onScroll(e, ctx) {
86
+
onScrollFromContext?.(e, ctx)
100
87
101
-
const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => {
102
-
if (!onItemSeen) {
103
-
return [undefined, undefined]
104
-
}
105
-
return [
106
-
(info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => {
107
-
for (const item of info.changed) {
108
-
if (item.isViewable) {
109
-
onItemSeen(item.item)
88
+
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
89
+
if (isScrolledDown.get() !== didScrollDown) {
90
+
isScrolledDown.set(didScrollDown)
91
+
if (onScrolledDownChange != null) {
92
+
runOnJS(handleScrolledDownChange)(didScrollDown)
110
93
}
111
94
}
95
+
96
+
if (isIOS) {
97
+
runOnJS(dedupe)(updateActiveVideoViewAsync)
98
+
}
112
99
},
113
-
{
114
-
itemVisiblePercentThreshold: 40,
115
-
minimumViewTime: 0.5e3,
100
+
// Note: adding onMomentumBegin here makes simulator scroll
101
+
// lag on Android. So either don't add it, or figure out why.
102
+
onMomentumEnd(e, ctx) {
103
+
runOnJS(updateActiveVideoViewAsync)()
104
+
onMomentumEndFromContext?.(e, ctx)
116
105
},
117
-
]
118
-
}, [onItemSeen])
106
+
})
119
107
120
-
let refreshControl
121
-
if (refreshing !== undefined || onRefresh !== undefined) {
122
-
refreshControl = (
123
-
<RefreshControl
124
-
refreshing={refreshing ?? false}
125
-
onRefresh={onRefresh}
126
-
tintColor={t.atoms.text.color}
127
-
titleColor={t.atoms.text.color}
128
-
progressViewOffset={progressViewOffset ?? headerOffset}
129
-
/>
130
-
)
131
-
}
108
+
const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => {
109
+
if (!onItemSeen) {
110
+
return [undefined, undefined]
111
+
}
112
+
return [
113
+
(info: {
114
+
viewableItems: Array<ViewToken>
115
+
changed: Array<ViewToken>
116
+
}) => {
117
+
for (const item of info.changed) {
118
+
if (item.isViewable) {
119
+
onItemSeen(item.item)
120
+
}
121
+
}
122
+
},
123
+
{
124
+
itemVisiblePercentThreshold: 40,
125
+
minimumViewTime: 0.5e3,
126
+
},
127
+
]
128
+
}, [onItemSeen])
132
129
133
-
let contentOffset
134
-
if (headerOffset != null) {
135
-
style = addStyle(style, {
136
-
paddingTop: headerOffset,
137
-
})
138
-
contentOffset = {x: 0, y: headerOffset * -1}
139
-
}
130
+
let refreshControl
131
+
if (refreshing !== undefined || onRefresh !== undefined) {
132
+
refreshControl = (
133
+
<RefreshControl
134
+
refreshing={refreshing ?? false}
135
+
onRefresh={onRefresh}
136
+
tintColor={t.atoms.text.color}
137
+
titleColor={t.atoms.text.color}
138
+
progressViewOffset={progressViewOffset ?? headerOffset}
139
+
/>
140
+
)
141
+
}
140
142
141
-
return (
142
-
<FlatList_INTERNAL
143
-
{...props}
144
-
scrollIndicatorInsets={{right: 1}}
145
-
contentOffset={contentOffset}
146
-
refreshControl={refreshControl}
147
-
onScroll={scrollHandler}
148
-
scrollsToTop={!activeLightbox}
149
-
scrollEventThrottle={1}
150
-
onViewableItemsChanged={onViewableItemsChanged}
151
-
viewabilityConfig={viewabilityConfig}
152
-
showsVerticalScrollIndicator={!isAndroid}
153
-
style={style}
154
-
ref={ref}
155
-
/>
156
-
)
157
-
}
143
+
let contentOffset
144
+
if (headerOffset != null) {
145
+
style = addStyle(style, {
146
+
paddingTop: headerOffset,
147
+
})
148
+
contentOffset = {x: 0, y: headerOffset * -1}
149
+
}
158
150
159
-
export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
160
-
props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>},
161
-
) => React.ReactElement
151
+
return (
152
+
<FlatList_INTERNAL
153
+
{...props}
154
+
scrollIndicatorInsets={{right: 1}}
155
+
contentOffset={contentOffset}
156
+
refreshControl={refreshControl}
157
+
onScroll={scrollHandler}
158
+
scrollsToTop={!activeLightbox}
159
+
scrollEventThrottle={1}
160
+
onViewableItemsChanged={onViewableItemsChanged}
161
+
viewabilityConfig={viewabilityConfig}
162
+
showsVerticalScrollIndicator={!isAndroid}
163
+
style={style}
164
+
// @ts-expect-error FlatList_INTERNAL ref type is wrong -sfn
165
+
ref={ref}
166
+
/>
167
+
)
168
+
},
169
+
)
170
+
List.displayName = 'List'
171
+
172
+
List = memo(List)
173
+
export {List}
+1
src/view/com/util/ViewSelector.tsx
+1
src/view/com/util/ViewSelector.tsx
-19
src/view/com/util/Views.d.ts
-19
src/view/com/util/Views.d.ts
···
1
-
import React from 'react'
2
-
import {ViewProps} from 'react-native'
3
-
export {FlatList as FlatList_INTERNAL, ScrollView} from 'react-native'
4
-
export function CenteredView({
5
-
style,
6
-
sideBorders,
7
-
...props
8
-
}: React.PropsWithChildren<
9
-
ViewProps & {
10
-
/**
11
-
* @platform web
12
-
*/
13
-
sideBorders?: boolean
14
-
/**
15
-
* @platform web
16
-
*/
17
-
topBorder?: boolean
18
-
}
19
-
>)
-7
src/view/com/util/Views.jsx
-7
src/view/com/util/Views.jsx
···
1
-
import {View} from 'react-native'
2
-
import Animated from 'react-native-reanimated'
3
-
4
-
// If you explode these into functions, don't forget to forwardRef!
5
-
export const FlatList_INTERNAL = Animated.FlatList
6
-
export const CenteredView = View
7
-
export const ScrollView = Animated.ScrollView
+28
src/view/com/util/Views.tsx
+28
src/view/com/util/Views.tsx
···
1
+
import {forwardRef} from 'react'
2
+
import {FlatListComponent} from 'react-native'
3
+
import {View, ViewProps} from 'react-native'
4
+
import Animated from 'react-native-reanimated'
5
+
import {FlatListPropsWithLayout} from 'react-native-reanimated'
6
+
7
+
// If you explode these into functions, don't forget to forwardRef!
8
+
9
+
/**
10
+
* Avoid using `FlatList_INTERNAL` and use `List` where possible.
11
+
* The types are a bit wrong on `FlatList_INTERNAL`
12
+
*/
13
+
export const FlatList_INTERNAL = Animated.FlatList
14
+
export type FlatList_INTERNAL<ItemT = any> = Omit<
15
+
FlatListComponent<ItemT, FlatListPropsWithLayout<ItemT>>,
16
+
'CellRendererComponent'
17
+
>
18
+
export const ScrollView = Animated.ScrollView
19
+
export type ScrollView = typeof Animated.ScrollView
20
+
21
+
export const CenteredView = forwardRef<
22
+
View,
23
+
React.PropsWithChildren<
24
+
ViewProps & {sideBorders?: boolean; topBorder?: boolean}
25
+
>
26
+
>(function CenteredView(props, ref) {
27
+
return <View ref={ref} {...props} />
28
+
})
+3
-3
src/view/screens/Feeds.tsx
+3
-3
src/view/screens/Feeds.tsx
···
1
1
import React from 'react'
2
-
import {ActivityIndicator, type FlatList, StyleSheet, View} from 'react-native'
2
+
import {ActivityIndicator, StyleSheet, View} from 'react-native'
3
3
import {AppBskyFeedDefs} from '@atproto/api'
4
4
import {msg, Trans} from '@lingui/macro'
5
5
import {useLingui} from '@lingui/react'
···
25
25
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
26
26
import {FAB} from '#/view/com/util/fab/FAB'
27
27
import {TextLink} from '#/view/com/util/Link'
28
-
import {List} from '#/view/com/util/List'
28
+
import {List, ListMethods} from '#/view/com/util/List'
29
29
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
30
30
import {Text} from '#/view/com/util/text/Text'
31
31
import {ViewHeader} from '#/view/com/util/ViewHeader'
···
130
130
error: searchError,
131
131
} = useSearchPopularFeedsMutation()
132
132
const {hasSession} = useSession()
133
-
const listRef = React.useRef<FlatList>(null)
133
+
const listRef = React.useRef<ListMethods>(null)
134
134
135
135
/**
136
136
* A search query is present. We may not have search results yet.
+3
-3
src/view/screens/Storybook/ListContained.tsx
+3
-3
src/view/screens/Storybook/ListContained.tsx
···
1
1
import React from 'react'
2
-
import {FlatList, View} from 'react-native'
2
+
import {View} from 'react-native'
3
3
4
4
import {ScrollProvider} from '#/lib/ScrollContext'
5
-
import {List} from '#/view/com/util/List'
5
+
import {List, ListMethods} from '#/view/com/util/List'
6
6
import {Button, ButtonText} from '#/components/Button'
7
7
import * as Toggle from '#/components/forms/Toggle'
8
8
import {Text} from '#/components/Typography'
9
9
10
10
export function ListContained() {
11
11
const [animated, setAnimated] = React.useState(false)
12
-
const ref = React.useRef<FlatList>(null)
12
+
const ref = React.useRef<ListMethods>(null)
13
13
14
14
const data = React.useMemo(() => {
15
15
return Array.from({length: 100}, (_, i) => ({