From 1de724b24b9607d4ee83dc0dbb92c13b2b77dcaf Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Sun, 19 Mar 2023 18:53:57 -0500 Subject: Add custom feeds selector, rework search, simplify onboarding (#325) * Get home screen's swipable pager working with the drawer * Add tab bar to pager * Implement popular & following views on home screen * Visual tune-up * Move the feed selector to the footer * Fix to 'new posts' poll * Add the view header as a feed item * Use the native driver on the tabbar indicator to improve perf * Reduce home polling to the currently active page; also reuse some code * Add soft reset on tap selected in tab bar * Remove explicit 'onboarding' flow * Choose good stuff based on service * Add foaf-based follow discovery * Fall back to who to follow * Fix backgrounds * Switch to the off-spec goodstuff route * 1.8 * Fix for dev & staging * Swap the tab bar items and rename suggested to what's hot * Go to whats-hot by default if you have no follows * Implement pager and tabbar for desktop web * Pin deps to make expo happy * Add language filtering to goodstuff --- src/view/screens/Home.tsx | 144 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 112 insertions(+), 32 deletions(-) (limited to 'src/view/screens/Home.tsx') diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index adc73315c..4950bc0fd 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -1,26 +1,97 @@ import React from 'react' -import {FlatList, View} from 'react-native' +import {FlatList, 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 {ViewHeader} from '../com/util/ViewHeader' import {Feed} from '../com/posts/Feed' +import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState' import {LoadLatestBtn} from '../com/util/LoadLatestBtn' -import {WelcomeBanner} from '../com/util/WelcomeBanner' +import {FeedsTabBar} from './home/FeedsTabBar' +import {Pager, RenderTabBarFnProps} from 'view/com/util/pager/Pager' import {FAB} from '../com/util/FAB' import {useStores} from 'state/index' import {s} from 'lib/styles' import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {useAnalytics} from 'lib/analytics' import {ComposeIcon2} from 'lib/icons' +import {isDesktopWeb} from 'platform/detection' -const HEADER_HEIGHT = 42 +const TAB_BAR_HEIGHT = 82 type Props = NativeStackScreenProps -export const HomeScreen = withAuthRequired( - observer(function Home(_opts: Props) { +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( + (index: number) => { + setSelectedPage(index) + store.shell.setIsDrawerSwipeDisabled(index > 0) + }, + [store], + ) + + const onPressSelected = React.useCallback(() => { + store.emitScreenSoftReset() + }, [store]) + + const renderTabBar = React.useCallback( + (props: RenderTabBarFnProps) => { + return + }, + [onPressSelected], + ) + + const renderFollowingEmptyState = React.useCallback(() => { + return + }, []) + + const initialPage = store.me.follows.isEmpty ? 1 : 0 + return ( + + + + + ) +}) + +const FeedPage = observer( + ({ + isPageFocused, + feed, + renderEmptyState, + }: { + feed: FeedModel + isPageFocused: boolean + renderEmptyState?: () => JSX.Element + }) => { const store = useStores() const onMainScroll = useOnMainScroll(store) const {screen, track} = useAnalytics() @@ -28,38 +99,51 @@ export const HomeScreen = withAuthRequired( const {appState} = useAppState({ onForeground: () => doPoll(true), }) - const isFocused = useIsFocused() + const isScreenFocused = useIsFocused() + const winDim = useWindowDimensions() + const containerStyle = React.useMemo( + () => ({height: winDim.height - (isDesktopWeb ? 0 : TAB_BAR_HEIGHT)}), + [winDim], + ) const doPoll = React.useCallback( (knownActive = false) => { - if ((!knownActive && appState !== 'active') || !isFocused) { + if ( + (!knownActive && appState !== 'active') || + !isScreenFocused || + !isPageFocused + ) { return } - if (store.me.mainFeed.isLoading) { + if (feed.isLoading) { return } store.log.debug('HomeScreen: Polling for new posts') - store.me.mainFeed.checkForLatest() + feed.checkForLatest() }, - [appState, isFocused, store], + [appState, isScreenFocused, isPageFocused, store, feed], ) const scrollToTop = React.useCallback(() => { - // NOTE: the feed is offset by the height of the collapsing header, - // so we scroll to the negative of that height -prf - scrollElRef.current?.scrollToOffset({offset: -HEADER_HEIGHT}) + scrollElRef.current?.scrollToOffset({offset: 0}) }, [scrollElRef]) + const onSoftReset = React.useCallback(() => { + if (isPageFocused) { + scrollToTop() + } + }, [isPageFocused, scrollToTop]) + useFocusEffect( React.useCallback(() => { - const softResetSub = store.onScreenSoftReset(scrollToTop) - const feedCleanup = store.me.mainFeed.registerListeners() + const softResetSub = store.onScreenSoftReset(onSoftReset) + const feedCleanup = feed.registerListeners() const pollInterval = setInterval(doPoll, 15e3) screen('Feed') store.log.debug('HomeScreen: Updating feed') - if (store.me.mainFeed.hasContent) { - store.me.mainFeed.update() + if (feed.hasContent) { + feed.update() } return () => { @@ -67,7 +151,7 @@ export const HomeScreen = withAuthRequired( softResetSub.remove() feedCleanup() } - }, [store, doPoll, scrollToTop, screen]), + }, [store, doPoll, onSoftReset, screen, feed]), ) const onPressCompose = React.useCallback(() => { @@ -76,32 +160,28 @@ export const HomeScreen = withAuthRequired( }, [store, track]) const onPressTryAgain = React.useCallback(() => { - store.me.mainFeed.refresh() - }, [store]) + feed.refresh() + }, [feed]) const onPressLoadLatest = React.useCallback(() => { - store.me.mainFeed.refresh() + feed.refresh() scrollToTop() - }, [store, scrollToTop]) + }, [feed, scrollToTop]) return ( - - {store.shell.isOnboarding && } + - {!store.shell.isOnboarding && ( - - )} - {store.me.mainFeed.hasNewLatest && !store.me.mainFeed.isRefreshing && ( + {feed.hasNewLatest && !feed.isRefreshing && ( )} ) - }), + }, ) -- cgit 1.4.1