mostly working

This commit is contained in:
Hailey
2024-04-25 12:03:13 -07:00
parent 1155351224
commit dce3d89357
2 changed files with 104 additions and 99 deletions
+103 -53
View File
@@ -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>
) )
} }
+1 -46
View File
@@ -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({