diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/App.native.tsx | 25 | ||||
-rw-r--r-- | src/App.web.tsx | 25 | ||||
-rw-r--r-- | src/state/shell/selected-feed.tsx | 61 | ||||
-rw-r--r-- | src/view/com/pager/Pager.web.tsx | 2 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 31 | ||||
-rw-r--r-- | src/view/shell/desktop/Feeds.tsx | 67 |
6 files changed, 148 insertions, 63 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx index 50a80d9fe..1284154f3 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -32,6 +32,7 @@ import {Provider as MutedThreadsProvider} from 'state/muted-threads' import {Provider as InvitesStateProvider} from 'state/invites' import {Provider as PrefsStateProvider} from 'state/preferences' import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' +import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed' import I18nProvider from './locale/i18nProvider' import { Provider as SessionProvider, @@ -72,17 +73,19 @@ function InnerApp() { // Resets the entire tree below when it changes: key={currentAccount?.did}> <LoggedOutViewProvider> - <UnreadNotifsProvider> - <ThemeProvider theme={theme}> - {/* All components should be within this provider */} - <RootSiblingParent> - <GestureHandlerRootView style={s.h100pct}> - <TestCtrls /> - <Shell /> - </GestureHandlerRootView> - </RootSiblingParent> - </ThemeProvider> - </UnreadNotifsProvider> + <SelectedFeedProvider> + <UnreadNotifsProvider> + <ThemeProvider theme={theme}> + {/* All components should be within this provider */} + <RootSiblingParent> + <GestureHandlerRootView style={s.h100pct}> + <TestCtrls /> + <Shell /> + </GestureHandlerRootView> + </RootSiblingParent> + </ThemeProvider> + </UnreadNotifsProvider> + </SelectedFeedProvider> </LoggedOutViewProvider> </React.Fragment> </Splash> diff --git a/src/App.web.tsx b/src/App.web.tsx index 9d1bd3506..f10bb194d 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -22,6 +22,7 @@ import {Provider as MutedThreadsProvider} from 'state/muted-threads' import {Provider as InvitesStateProvider} from 'state/invites' import {Provider as PrefsStateProvider} from 'state/preferences' import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' +import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed' import I18nProvider from './locale/i18nProvider' import { Provider as SessionProvider, @@ -52,17 +53,19 @@ function InnerApp() { // Resets the entire tree below when it changes: key={currentAccount?.did}> <LoggedOutViewProvider> - <UnreadNotifsProvider> - <ThemeProvider theme={theme}> - {/* All components should be within this provider */} - <RootSiblingParent> - <SafeAreaProvider> - <Shell /> - </SafeAreaProvider> - </RootSiblingParent> - <ToastContainer /> - </ThemeProvider> - </UnreadNotifsProvider> + <SelectedFeedProvider> + <UnreadNotifsProvider> + <ThemeProvider theme={theme}> + {/* All components should be within this provider */} + <RootSiblingParent> + <SafeAreaProvider> + <Shell /> + </SafeAreaProvider> + </RootSiblingParent> + <ToastContainer /> + </ThemeProvider> + </UnreadNotifsProvider> + </SelectedFeedProvider> </LoggedOutViewProvider> </React.Fragment> </Alf> diff --git a/src/state/shell/selected-feed.tsx b/src/state/shell/selected-feed.tsx new file mode 100644 index 000000000..a05d8661b --- /dev/null +++ b/src/state/shell/selected-feed.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import * as persisted from '#/state/persisted' +import {isWeb} from '#/platform/detection' + +type StateContext = string +type SetContext = (v: string) => void + +const stateContext = React.createContext<StateContext>('home') +const setContext = React.createContext<SetContext>((_: string) => {}) + +function getInitialFeed() { + if (isWeb) { + if (window.location.pathname === '/') { + const params = new URLSearchParams(window.location.search) + const feedFromUrl = params.get('feed') + if (feedFromUrl) { + // If explicitly booted from a link like /?feed=..., prefer that. + return feedFromUrl + } + } + const feedFromSession = sessionStorage.getItem('lastSelectedHomeFeed') + if (feedFromSession) { + // Fall back to a previously chosen feed for this browser tab. + return feedFromSession + } + } + const feedFromPersisted = persisted.get('lastSelectedHomeFeed') + if (feedFromPersisted) { + // Fall back to the last chosen one across all tabs. + return feedFromPersisted + } + return 'home' +} + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState(getInitialFeed) + + const saveState = React.useCallback((feed: string) => { + setState(feed) + if (isWeb) { + try { + sessionStorage.setItem('lastSelectedHomeFeed', feed) + } catch {} + } + persisted.write('lastSelectedHomeFeed', feed) + }, []) + + return ( + <stateContext.Provider value={state}> + <setContext.Provider value={saveState}>{children}</setContext.Provider> + </stateContext.Provider> + ) +} + +export function useSelectedFeed() { + return React.useContext(stateContext) +} + +export function useSetSelectedFeed() { + return React.useContext(setContext) +} diff --git a/src/view/com/pager/Pager.web.tsx b/src/view/com/pager/Pager.web.tsx index d7113bb05..42982ef7f 100644 --- a/src/view/com/pager/Pager.web.tsx +++ b/src/view/com/pager/Pager.web.tsx @@ -31,7 +31,7 @@ export const Pager = React.forwardRef(function PagerImpl( const anchorRef = React.useRef(null) React.useImperativeHandle(ref, () => ({ - setPage: (index: number) => setSelectedPage(index), + setPage: (index: number) => onTabBarSelect(index), })) const onTabBarSelect = React.useCallback( diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 7228a9e1b..cb2abf1bc 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -7,7 +7,7 @@ import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState' import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed' import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState' import {FeedsTabBar} from '../com/pager/FeedsTabBar' -import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager' +import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager' import {FeedPage} from 'view/com/feeds/FeedPage' import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA' import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell' @@ -16,25 +16,19 @@ import {usePinnedFeedsInfos, FeedSourceInfo} from '#/state/queries/feed' import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' import {emitSoftReset} from '#/state/events' import {useSession} from '#/state/session' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import * as persisted from '#/state/persisted' +import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> export function HomeScreen(props: Props) { const {data: preferences} = usePreferencesQuery() const {feeds: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} = usePinnedFeedsInfos() - const {isDesktop} = useWebMediaQueries() - const [rawInitialFeed] = React.useState<string>( - () => persisted.get('lastSelectedHomeFeed') ?? 'home', - ) if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) { return ( <HomeScreenReady {...props} preferences={preferences} pinnedFeedInfos={pinnedFeedInfos} - rawInitialFeed={isDesktop ? 'home' : rawInitialFeed} /> ) } else { @@ -49,11 +43,9 @@ export function HomeScreen(props: Props) { function HomeScreenReady({ preferences, pinnedFeedInfos, - rawInitialFeed, }: Props & { preferences: UsePreferencesQueryResponse pinnedFeedInfos: FeedSourceInfo[] - rawInitialFeed: string }) { const allFeeds = React.useMemo(() => { const feeds: FeedDescriptor[] = [] @@ -68,12 +60,24 @@ function HomeScreenReady({ return feeds }, [pinnedFeedInfos]) - const [rawSelectedFeed, setSelectedFeed] = - React.useState<string>(rawInitialFeed) + const rawSelectedFeed = useSelectedFeed() + const setSelectedFeed = useSetSelectedFeed() const maybeFoundIndex = allFeeds.indexOf(rawSelectedFeed as FeedDescriptor) const selectedIndex = Math.max(0, maybeFoundIndex) const selectedFeed = allFeeds[selectedIndex] + const pagerRef = React.useRef<PagerRef>(null) + const lastPagerReportedIndexRef = React.useRef(selectedIndex) + React.useLayoutEffect(() => { + // Since the pager is not a controlled component, adjust it imperatively + // if the selected index gets out of sync with what it last reported. + // This is supposed to only happen on the web when you use the right nav. + if (selectedIndex !== lastPagerReportedIndexRef.current) { + lastPagerReportedIndexRef.current = selectedIndex + pagerRef.current?.setPage(selectedIndex) + } + }, [selectedIndex]) + const {hasSession} = useSession() const setMinimalShellMode = useSetMinimalShellMode() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() @@ -93,7 +97,7 @@ function HomeScreenReady({ setDrawerSwipeDisabled(index > 0) const feed = allFeeds[index] setSelectedFeed(feed) - persisted.write('lastSelectedHomeFeed', feed) + lastPagerReportedIndexRef.current = index }, [setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds], ) @@ -147,6 +151,7 @@ function HomeScreenReady({ return hasSession ? ( <Pager key={allFeeds.join(',')} + ref={pagerRef} testID="homeScreen" initialPage={selectedIndex} onPageSelected={onPageSelected} diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx index a8f5f1c66..c3b1caa35 100644 --- a/src/view/shell/desktop/Feeds.tsx +++ b/src/view/shell/desktop/Feeds.tsx @@ -1,18 +1,24 @@ import React from 'react' import {View, StyleSheet} from 'react-native' -import {useNavigationState} from '@react-navigation/native' +import {useNavigationState, useNavigation} from '@react-navigation/native' import {usePalette} from 'lib/hooks/usePalette' import {TextLink} from 'view/com/util/Link' import {getCurrentRoute} from 'lib/routes/helpers' import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' import {usePinnedFeedsInfos} from '#/state/queries/feed' +import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' +import {FeedDescriptor} from '#/state/queries/post-feed' +import {NavigationProp} from 'lib/routes/types' +import {emitSoftReset} from '#/state/events' export function DesktopFeeds() { const pal = usePalette('default') const {_} = useLingui() - const {feeds} = usePinnedFeedsInfos() - + const {feeds: pinnedFeedInfos} = usePinnedFeedsInfos() + const selectedFeed = useSelectedFeed() + const setSelectedFeed = useSetSelectedFeed() + const navigation = useNavigation<NavigationProp>() const route = useNavigationState(state => { if (!state) { return {name: 'Home'} @@ -22,30 +28,34 @@ export function DesktopFeeds() { return ( <View style={[styles.container, pal.view]}> - <FeedItem href="/" title="Following" current={route.name === 'Home'} /> - {feeds - .filter(f => f.displayName !== 'Following') - .map(feed => { - try { - const params = route.params as Record<string, string> - const routeName = - feed.type === 'feed' ? 'ProfileFeed' : 'ProfileList' - return ( - <FeedItem - key={feed.uri} - href={feed.route.href} - title={feed.displayName} - current={ - route.name === routeName && - params.name === feed.route.params.name && - params.rkey === feed.route.params.rkey - } - /> - ) - } catch { - return null - } - })} + {pinnedFeedInfos.map(feedInfo => { + const uri = feedInfo.uri + let feed: FeedDescriptor + if (!uri) { + feed = 'home' + } else if (uri.includes('app.bsky.feed.generator')) { + feed = `feedgen|${uri}` + } else if (uri.includes('app.bsky.graph.list')) { + feed = `list|${uri}` + } else { + return null + } + return ( + <FeedItem + key={feed} + href={'/?' + new URLSearchParams([['feed', feed]])} + title={feedInfo.displayName} + current={route.name === 'Home' && feed === selectedFeed} + onPress={() => { + setSelectedFeed(feed) + navigation.navigate('Home') + if (feed === selectedFeed) { + emitSoftReset() + } + }} + /> + ) + })} <View style={{paddingTop: 8, paddingBottom: 6}}> <TextLink type="lg" @@ -62,10 +72,12 @@ function FeedItem({ title, href, current, + onPress, }: { title: string href: string current: boolean + onPress: () => void }) { const pal = usePalette('default') return ( @@ -74,6 +86,7 @@ function FeedItem({ type="xl" href={href} text={title} + onPress={onPress} style={[ current ? pal.text : pal.textLight, {letterSpacing: 0.15, fontWeight: current ? '500' : 'normal'}, |