mostly working
This commit is contained in:
@@ -1,17 +1,15 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {
|
import {Button, FlatList, View, ViewToken} from 'react-native'
|
||||||
FlatList,
|
|
||||||
LayoutChangeEvent,
|
|
||||||
ListRenderItemInfo,
|
|
||||||
View,
|
|
||||||
ViewToken,
|
|
||||||
} from 'react-native'
|
|
||||||
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
import {ClipClop as ClipClopInterface} from '#/components/ClipClopTest/RandomClipClops'
|
import {atoms as a} from '#/alf'
|
||||||
|
import {
|
||||||
|
ClipClop,
|
||||||
|
placeholderClops,
|
||||||
|
} from '#/components/ClipClopTest/RandomClipClops'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
function ClipClop({message}: {message: ClipClopInterface}) {
|
function ClipClopItem({item}: {item: ClipClop}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -25,57 +23,80 @@ function ClipClop({message}: {message: ClipClopInterface}) {
|
|||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<Text style={{lineHeight: 1.2}}>{message.text}</Text>
|
<Text style={{lineHeight: 1.2}}>{item.text}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderItem({item}: ListRenderItemInfo<ClipClopInterface>) {
|
function renderItem({item}: {item: ClipClop}) {
|
||||||
return <ClipClop message={item} />
|
return <ClipClopItem item={item} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyExtractor(item: ClipClopInterface, index: number) {
|
// Generate unique key list item.
|
||||||
return `${item.id}-${index}`
|
export const generateUniqueKey = () =>
|
||||||
}
|
`_${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
|
||||||
function HeaderPlaceholder({height}: {height: number}) {
|
export const ClipClopList = () => {
|
||||||
return <View style={{height}} />
|
const flatListRef = React.useRef<FlatList>(null)
|
||||||
}
|
|
||||||
|
|
||||||
export function ClipClopList({clipClops}: {clipClops: ClipClopInterface[]}) {
|
// Whenever we reach the end (visually the top), we don't want to keep calling it. We will set `isFetching` to true
|
||||||
const flatlistRef = React.useRef<FlatList>(null)
|
// once the request for new posts starts. Then, we will change it back to false after the content size changes.
|
||||||
const contentHeight = React.useRef(0)
|
const isFetching = React.useRef(false)
|
||||||
|
|
||||||
const totalClipClops = React.useRef(0)
|
// We use this to know if we should scroll after a new clop is added to the list
|
||||||
|
const isAtBottom = React.useRef(false)
|
||||||
|
|
||||||
const isAtBottom = React.useRef(true)
|
// Because the viewableItemsChanged callback won't have access to the updated state, we use a ref to store the
|
||||||
const [placeholderHeight, setPlaceholderHeight] = React.useState(0)
|
// total number of clops
|
||||||
|
const totalClops = React.useRef(10)
|
||||||
|
|
||||||
// This creates a placeholder that adjusts with the whitespace above the last chat item
|
const [clops, setClops] = React.useState(
|
||||||
const onScreenLayout = React.useCallback((e: LayoutChangeEvent) => {
|
Array.from(Array(10).keys()).map(n => ({
|
||||||
setPlaceholderHeight(e.nativeEvent.layout.height)
|
id: generateUniqueKey(),
|
||||||
}, [])
|
text: placeholderClops[n % placeholderClops.length],
|
||||||
|
})),
|
||||||
const onContentSizeChange = React.useCallback(
|
|
||||||
(_: number, height: number) => {
|
|
||||||
if (isAtBottom.current) {
|
|
||||||
flatlistRef.current?.scrollToOffset({offset: height, animated: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
contentHeight.current = height
|
|
||||||
totalClipClops.current = clipClops.length
|
|
||||||
},
|
|
||||||
[clipClops],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const addOldClops = () => {
|
||||||
|
setClops(prev => {
|
||||||
|
const oldClops: ClipClop[] = Array.from(Array(5).keys()).map(n => ({
|
||||||
|
id: generateUniqueKey(),
|
||||||
|
text:
|
||||||
|
placeholderClops[n % placeholderClops.length] + generateUniqueKey(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
totalClops.current += oldClops.length
|
||||||
|
return prev.concat(oldClops)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNewClip = () => {
|
||||||
|
setClops(prev => {
|
||||||
|
const newClops = Array.from(Array(1).keys())
|
||||||
|
.map(n => ({
|
||||||
|
id: generateUniqueKey(),
|
||||||
|
text:
|
||||||
|
placeholderClops[n % placeholderClops.length] + generateUniqueKey(),
|
||||||
|
}))
|
||||||
|
.reverse()
|
||||||
|
|
||||||
|
totalClops.current += newClops.length
|
||||||
|
return newClops.concat(prev)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => {
|
const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => {
|
||||||
return [
|
return [
|
||||||
(info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => {
|
(info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => {
|
||||||
const lastVisibleItem =
|
const firstVisibleIndex = info.viewableItems[0]?.index
|
||||||
info.viewableItems[info.viewableItems.length - 1]
|
|
||||||
const lastViewableIndex = lastVisibleItem?.index ?? -1
|
|
||||||
|
|
||||||
isAtBottom.current = lastViewableIndex >= totalClipClops.current - 2
|
isAtBottom.current = Number(firstVisibleIndex) < 2
|
||||||
|
|
||||||
|
console.log({
|
||||||
|
firstVisibleIndex,
|
||||||
|
totalClops: totalClops.current,
|
||||||
|
isAtBottom: isAtBottom.current,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
itemVisiblePercentThreshold: 100,
|
itemVisiblePercentThreshold: 100,
|
||||||
@@ -84,21 +105,50 @@ export function ClipClopList({clipClops}: {clipClops: ClipClopInterface[]}) {
|
|||||||
]
|
]
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const onContentSizeChange = React.useCallback(
|
||||||
|
(_: number, height: number) => {
|
||||||
|
if (isAtBottom.current) {
|
||||||
|
flatListRef.current?.scrollToOffset({offset: 0, animated: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
isFetching.current = false
|
||||||
|
},
|
||||||
|
[clops],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onEndReached = () => {
|
||||||
|
if (isFetching.current) return
|
||||||
|
isFetching.current = true
|
||||||
|
|
||||||
|
// We wouldn't actually use a timeout, but there would be a delay while loading
|
||||||
|
setTimeout(() => {
|
||||||
|
addOldClops()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}} onLayout={onScreenLayout}>
|
<View style={{flex: 1, marginBottom: 100}}>
|
||||||
|
<Button title="Add Old Clops" onPress={addOldClops} />
|
||||||
<FlatList
|
<FlatList
|
||||||
data={clipClops}
|
data={clops}
|
||||||
|
keyExtractor={item => item.id}
|
||||||
|
maintainVisibleContentPosition={{
|
||||||
|
minIndexForVisible: 1,
|
||||||
|
}}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={keyExtractor}
|
onContentSizeChange={onContentSizeChange}
|
||||||
// maintainVisibleContentPosition={{
|
|
||||||
// minIndexForVisible: 0,
|
|
||||||
// }}
|
|
||||||
// ListHeaderComponent={<HeaderPlaceholder height={placeholderHeight} />}
|
|
||||||
onViewableItemsChanged={onViewableItemsChanged}
|
onViewableItemsChanged={onViewableItemsChanged}
|
||||||
viewabilityConfig={viewabilityConfig}
|
viewabilityConfig={viewabilityConfig}
|
||||||
ref={flatlistRef}
|
ref={flatListRef}
|
||||||
onContentSizeChange={onContentSizeChange}
|
onScrollToIndexFailed={info => {
|
||||||
|
console.log('Failed to scroll to index', info)
|
||||||
|
}}
|
||||||
|
initialNumToRender={20}
|
||||||
|
maxToRenderPerBatch={20}
|
||||||
|
inverted
|
||||||
|
onEndReached={onEndReached}
|
||||||
/>
|
/>
|
||||||
|
<Button title="Add New Clip" onPress={addNewClip} />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,52 +51,7 @@ type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function HomeScreen(props: Props) {
|
export function HomeScreen(props: Props) {
|
||||||
const lastMessageId = React.useRef(0)
|
return <ClipClopList />
|
||||||
const [clipClops, setClipClops] = React.useState<ClipClop[]>([])
|
|
||||||
|
|
||||||
const addMessage = () => {
|
|
||||||
lastMessageId.current += 1
|
|
||||||
|
|
||||||
setClipClops(prev => {
|
|
||||||
return [
|
|
||||||
...prev,
|
|
||||||
|
|
||||||
{
|
|
||||||
id: lastMessageId.current.toString(),
|
|
||||||
text: `${
|
|
||||||
placeholderClops[lastMessageId.current % placeholderClops.length]
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const addMessages = (count: number) => {
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
addMessage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CenteredView style={{flex: 1, marginBottom: 100}}>
|
|
||||||
<View style={{flexDirection: 'row'}}>
|
|
||||||
<Button
|
|
||||||
title="Add One"
|
|
||||||
onPress={() => {
|
|
||||||
addMessages(1)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
title="Add Five"
|
|
||||||
onPress={() => {
|
|
||||||
addMessages(5)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button title="Reset" onPress={() => setClipClops([])} />
|
|
||||||
</View>
|
|
||||||
<ClipClopList clipClops={clipClops} />
|
|
||||||
</CenteredView>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function HomeScreenReady({
|
function HomeScreenReady({
|
||||||
|
|||||||
Reference in New Issue
Block a user