246 lines
6.6 KiB
TypeScript
246 lines
6.6 KiB
TypeScript
import React from 'react'
|
|
import {
|
|
Animated,
|
|
FlatList,
|
|
StyleSheet,
|
|
View,
|
|
useWindowDimensions,
|
|
} from 'react-native'
|
|
import {useFocusEffect, useIsFocused} from '@react-navigation/native'
|
|
import {observer} from 'mobx-react-lite'
|
|
import useAppState from 'react-native-appstate-hook'
|
|
import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types'
|
|
import {FeedModel} from 'state/models/feed-view'
|
|
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
|
import {Feed} from '../com/posts/Feed'
|
|
import {LoadLatestBtn} from '../com/util/LoadLatestBtn'
|
|
import {TabBar, TabBarProps} from 'view/com/util/TabBar'
|
|
import {Pager, PageSelectedEvent} from 'view/com/util/Pager'
|
|
import {FAB} from '../com/util/FAB'
|
|
import {useStores} from 'state/index'
|
|
import {usePalette} from 'lib/hooks/usePalette'
|
|
import {s} from 'lib/styles'
|
|
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
|
import {useAnalytics} from 'lib/analytics'
|
|
import {ComposeIcon2} from 'lib/icons'
|
|
import {clamp} from 'lodash'
|
|
|
|
const TAB_BAR_HEIGHT = 82
|
|
const BOTTOM_BAR_HEIGHT = 48
|
|
|
|
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
|
export const HomeScreen = withAuthRequired((_opts: Props) => {
|
|
const store = useStores()
|
|
const [selectedPage, setSelectedPage] = React.useState(0)
|
|
|
|
const algoFeed = React.useMemo(() => {
|
|
const feed = new FeedModel(store, 'goodstuff', {})
|
|
feed.setup()
|
|
return feed
|
|
}, [store])
|
|
|
|
useFocusEffect(
|
|
React.useCallback(() => {
|
|
store.shell.setIsDrawerSwipeDisabled(selectedPage > 0)
|
|
return () => {
|
|
store.shell.setIsDrawerSwipeDisabled(false)
|
|
}
|
|
}, [store, selectedPage]),
|
|
)
|
|
|
|
const onPageSelected = React.useCallback(
|
|
(e: PageSelectedEvent) => {
|
|
setSelectedPage(e.nativeEvent.position)
|
|
store.shell.setIsDrawerSwipeDisabled(e.nativeEvent.position > 0)
|
|
},
|
|
[store],
|
|
)
|
|
|
|
const onPressSelected = React.useCallback(() => {
|
|
store.emitScreenSoftReset()
|
|
}, [store])
|
|
|
|
const renderTabBar = React.useCallback(
|
|
(props: TabBarProps) => {
|
|
return <FloatingTabBar {...props} onPressSelected={onPressSelected} />
|
|
},
|
|
[onPressSelected],
|
|
)
|
|
|
|
return (
|
|
<Pager
|
|
onPageSelected={onPageSelected}
|
|
renderTabBar={renderTabBar}
|
|
tabBarPosition="bottom">
|
|
<FeedPage
|
|
key="1"
|
|
isPageFocused={selectedPage === 0}
|
|
feed={store.me.mainFeed}
|
|
/>
|
|
<FeedPage key="2" isPageFocused={selectedPage === 1} feed={algoFeed} />
|
|
</Pager>
|
|
)
|
|
})
|
|
|
|
const FloatingTabBar = observer((props: TabBarProps) => {
|
|
const store = useStores()
|
|
const safeAreaInsets = useSafeAreaInsets()
|
|
const pal = usePalette('default')
|
|
const interp = useAnimatedValue(0)
|
|
|
|
const pad = React.useMemo(
|
|
() => ({
|
|
paddingBottom: clamp(safeAreaInsets.bottom, 15, 20),
|
|
}),
|
|
[safeAreaInsets],
|
|
)
|
|
|
|
React.useEffect(() => {
|
|
Animated.timing(interp, {
|
|
toValue: store.shell.minimalShellMode ? 0 : 1,
|
|
duration: 100,
|
|
useNativeDriver: true,
|
|
isInteraction: false,
|
|
}).start()
|
|
}, [interp, store.shell.minimalShellMode])
|
|
const transform = {
|
|
transform: [
|
|
{translateY: Animated.multiply(interp, -1 * BOTTOM_BAR_HEIGHT)},
|
|
],
|
|
}
|
|
|
|
return (
|
|
<Animated.View
|
|
style={[pal.view, pal.border, styles.tabBar, pad, transform]}>
|
|
<TabBar
|
|
{...props}
|
|
items={['Following', "What's hot"]}
|
|
indicatorPosition="top"
|
|
indicatorColor={pal.colors.link}
|
|
/>
|
|
</Animated.View>
|
|
)
|
|
})
|
|
|
|
const FeedPage = observer(
|
|
({isPageFocused, feed}: {feed: FeedModel; isPageFocused: boolean}) => {
|
|
const store = useStores()
|
|
const onMainScroll = useOnMainScroll(store)
|
|
const {screen, track} = useAnalytics()
|
|
const scrollElRef = React.useRef<FlatList>(null)
|
|
const {appState} = useAppState({
|
|
onForeground: () => doPoll(true),
|
|
})
|
|
const isScreenFocused = useIsFocused()
|
|
const winDim = useWindowDimensions()
|
|
const containerStyle = React.useMemo(
|
|
() => ({height: winDim.height - TAB_BAR_HEIGHT}),
|
|
[winDim],
|
|
)
|
|
|
|
const doPoll = React.useCallback(
|
|
(knownActive = false) => {
|
|
if (
|
|
(!knownActive && appState !== 'active') ||
|
|
!isScreenFocused ||
|
|
!isPageFocused
|
|
) {
|
|
return
|
|
}
|
|
if (feed.isLoading) {
|
|
return
|
|
}
|
|
store.log.debug('HomeScreen: Polling for new posts')
|
|
feed.checkForLatest()
|
|
},
|
|
[appState, isScreenFocused, isPageFocused, store, feed],
|
|
)
|
|
|
|
const scrollToTop = React.useCallback(() => {
|
|
scrollElRef.current?.scrollToOffset({offset: 0})
|
|
}, [scrollElRef])
|
|
|
|
const onSoftReset = React.useCallback(() => {
|
|
if (isPageFocused) {
|
|
scrollToTop()
|
|
}
|
|
}, [isPageFocused, scrollToTop])
|
|
|
|
useFocusEffect(
|
|
React.useCallback(() => {
|
|
const softResetSub = store.onScreenSoftReset(onSoftReset)
|
|
const feedCleanup = feed.registerListeners()
|
|
const pollInterval = setInterval(doPoll, 15e3)
|
|
|
|
screen('Feed')
|
|
store.log.debug('HomeScreen: Updating feed')
|
|
if (feed.hasContent) {
|
|
feed.update()
|
|
}
|
|
|
|
return () => {
|
|
clearInterval(pollInterval)
|
|
softResetSub.remove()
|
|
feedCleanup()
|
|
}
|
|
}, [store, doPoll, onSoftReset, screen, feed]),
|
|
)
|
|
|
|
const onPressCompose = React.useCallback(() => {
|
|
track('HomeScreen:PressCompose')
|
|
store.shell.openComposer({})
|
|
}, [store, track])
|
|
|
|
const onPressTryAgain = React.useCallback(() => {
|
|
feed.refresh()
|
|
}, [feed])
|
|
|
|
const onPressLoadLatest = React.useCallback(() => {
|
|
feed.refresh()
|
|
scrollToTop()
|
|
}, [feed, scrollToTop])
|
|
|
|
return (
|
|
<View style={containerStyle}>
|
|
<Feed
|
|
testID="homeFeed"
|
|
key="default"
|
|
feed={feed}
|
|
scrollElRef={scrollElRef}
|
|
style={s.hContentRegion}
|
|
showPostFollowBtn
|
|
onPressTryAgain={onPressTryAgain}
|
|
onScroll={onMainScroll}
|
|
/>
|
|
{feed.hasNewLatest && !feed.isRefreshing && (
|
|
<LoadLatestBtn onPress={onPressLoadLatest} />
|
|
)}
|
|
<FAB
|
|
testID="composeFAB"
|
|
onPress={onPressCompose}
|
|
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
|
/>
|
|
</View>
|
|
)
|
|
},
|
|
)
|
|
|
|
const styles = StyleSheet.create({
|
|
tabBar: {
|
|
position: 'absolute',
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingHorizontal: 10,
|
|
borderTopWidth: 1,
|
|
paddingTop: 0,
|
|
paddingBottom: 30,
|
|
},
|
|
tabBarAvi: {
|
|
marginRight: 4,
|
|
},
|
|
})
|