diff --git a/src/App.native.tsx b/src/App.native.tsx index b9f9519c94..64c7e718fe 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -28,6 +28,7 @@ import {Provider as LightboxStateProvider} from 'state/lightbox' 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 I18nProvider from './locale/i18nProvider' import { Provider as SessionProvider, @@ -42,7 +43,7 @@ SplashScreen.preventAutoHideAsync() function InnerApp() { const colorMode = useColorMode() - const {isInitialLoad} = useSession() + const {isInitialLoad, currentAccount} = useSession() const {resumeSession} = useSessionApi() // init @@ -69,19 +70,25 @@ function InnerApp() { */ return ( - - - - {/* All components should be within this provider */} - - - - - - - - - + + + + + + {/* All components should be within this provider */} + + + + + + + + + + + ) } diff --git a/src/App.web.tsx b/src/App.web.tsx index fa0b225c0c..e939dda6dc 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -22,6 +22,7 @@ import {Provider as LightboxStateProvider} from 'state/lightbox' 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 I18nProvider from './locale/i18nProvider' import { Provider as SessionProvider, @@ -34,7 +35,7 @@ import * as persisted from '#/state/persisted' enableFreeze(true) function InnerApp() { - const {isInitialLoad} = useSession() + const {isInitialLoad, currentAccount} = useSession() const {resumeSession} = useSessionApi() const colorMode = useColorMode() @@ -57,19 +58,25 @@ function InnerApp() { */ return ( - - - - {/* All components should be within this provider */} - - - - - - - - - + + + + + + {/* All components should be within this provider */} + + + + + + + + + + + ) } diff --git a/src/Navigation.tsx b/src/Navigation.tsx index fb88dc84f8..4718349b58 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -9,7 +9,6 @@ import { DefaultTheme, DarkTheme, } from '@react-navigation/native' -import {createNativeStackNavigator} from '@react-navigation/native-stack' import { BottomTabBarProps, createBottomTabNavigator, @@ -69,16 +68,18 @@ import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts' import {SavedFeeds} from 'view/screens/SavedFeeds' import {PreferencesHomeFeed} from 'view/screens/PreferencesHomeFeed' import {PreferencesThreads} from 'view/screens/PreferencesThreads' +import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth' const navigationRef = createNavigationContainerRef() -const HomeTab = createNativeStackNavigator() -const SearchTab = createNativeStackNavigator() -const FeedsTab = createNativeStackNavigator() +const HomeTab = createNativeStackNavigatorWithAuth() +const SearchTab = createNativeStackNavigatorWithAuth() +const FeedsTab = createNativeStackNavigatorWithAuth() const NotificationsTab = - createNativeStackNavigator() -const MyProfileTab = createNativeStackNavigator() -const Flat = createNativeStackNavigator() + createNativeStackNavigatorWithAuth() +const MyProfileTab = + createNativeStackNavigatorWithAuth() +const Flat = createNativeStackNavigatorWithAuth() const Tab = createBottomTabNavigator() /** @@ -97,37 +98,37 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { ModerationScreen} - options={{title: title('Moderation')}} + options={{title: title('Moderation'), requireAuth: true}} /> ModerationModlistsScreen} - options={{title: title('Moderation Lists')}} + options={{title: title('Moderation Lists'), requireAuth: true}} /> ModerationMutedAccounts} - options={{title: title('Muted Accounts')}} + options={{title: title('Muted Accounts'), requireAuth: true}} /> ModerationBlockedAccounts} - options={{title: title('Blocked Accounts')}} + options={{title: title('Blocked Accounts'), requireAuth: true}} /> SettingsScreen} - options={{title: title('Settings')}} + options={{title: title('Settings'), requireAuth: true}} /> LanguageSettingsScreen} - options={{title: title('Language Settings')}} + options={{title: title('Language Settings'), requireAuth: true}} /> ProfileListScreen} - options={{title: title('List')}} + options={{title: title('List'), requireAuth: true}} /> DebugScreen} - options={{title: title('Debug')}} + options={{title: title('Debug'), requireAuth: true}} /> LogScreen} - options={{title: title('Log')}} + options={{title: title('Log'), requireAuth: true}} /> AppPasswords} - options={{title: title('App Passwords')}} + options={{title: title('App Passwords'), requireAuth: true}} /> SavedFeeds} - options={{title: title('Edit My Feeds')}} + options={{title: title('Edit My Feeds'), requireAuth: true}} /> PreferencesHomeFeed} - options={{title: title('Home Feed Preferences')}} + options={{title: title('Home Feed Preferences'), requireAuth: true}} /> PreferencesThreads} - options={{title: title('Threads Preferences')}} + options={{title: title('Threads Preferences'), requireAuth: true}} /> ) @@ -339,6 +340,7 @@ function NotificationsTabNavigator() { NotificationsScreen} + options={{requireAuth: true}} /> {commonScreens(NotificationsTab as typeof HomeTab)} @@ -357,8 +359,8 @@ function MyProfileTabNavigator() { contentStyle, }}> ProfileScreen} initialParams={{ name: 'me', @@ -405,7 +407,7 @@ const FlatNavigator = () => { NotificationsScreen} - options={{title: title('Notifications')}} + options={{title: title('Notifications'), requireAuth: true}} /> {commonScreens(Flat as typeof HomeTab, numUnread)} diff --git a/src/state/shell/index.tsx b/src/state/shell/index.tsx index 897a66020f..53f05055c3 100644 --- a/src/state/shell/index.tsx +++ b/src/state/shell/index.tsx @@ -7,7 +7,6 @@ import {Provider as ColorModeProvider} from './color-mode' import {Provider as OnboardingProvider} from './onboarding' import {Provider as ComposerProvider} from './composer' import {Provider as TickEveryMinuteProvider} from './tick-every-minute' -import {Provider as LoggedOutViewProvider} from './logged-out' export {useIsDrawerOpen, useSetDrawerOpen} from './drawer-open' export { @@ -23,23 +22,19 @@ export {useTickEveryMinute} from './tick-every-minute' export function Provider({children}: React.PropsWithChildren<{}>) { return ( - - - - - - - - - {children} - - - - - - - - + + + + + + + {children} + + + + + + ) } diff --git a/src/view/com/auth/withAuthRequired.tsx b/src/view/com/auth/withAuthRequired.tsx deleted file mode 100644 index 7a91385451..0000000000 --- a/src/view/com/auth/withAuthRequired.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react' -import { - ActivityIndicator, - Linking, - StyleSheet, - TouchableOpacity, -} from 'react-native' -import {CenteredView} from '../util/Views' -import {LoggedOut} from './LoggedOut' -import {Onboarding} from './Onboarding' -import {Text} from '../util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {STATUS_PAGE_URL} from 'lib/constants' -import {useOnboardingState} from '#/state/shell' -import {useSession} from '#/state/session' -import { - useLoggedOutView, - useLoggedOutViewControls, -} from '#/state/shell/logged-out' -import {IS_PROD} from '#/env' - -export const withAuthRequired =

( - Component: React.ComponentType

, - options: { - isPublic?: boolean // TODO(pwi) need to enable in TF somehow - } = {}, -): React.FC

=> - function AuthRequired(props: P) { - const {isInitialLoad, hasSession} = useSession() - const onboardingState = useOnboardingState() - const {showLoggedOut} = useLoggedOutView() - const {setShowLoggedOut} = useLoggedOutViewControls() - - if (isInitialLoad) { - return - } - if (!hasSession) { - if (showLoggedOut) { - return setShowLoggedOut(false)} /> - } else if (!options?.isPublic || IS_PROD) { - return - } - } - if (onboardingState.isActive) { - return - } - return - } - -function Loading() { - const pal = usePalette('default') - - const [isTakingTooLong, setIsTakingTooLong] = React.useState(false) - React.useEffect(() => { - const t = setTimeout(() => setIsTakingTooLong(true), 15e3) // 15 seconds - return () => clearTimeout(t) - }, [setIsTakingTooLong]) - - return ( - - - - {isTakingTooLong - ? "This is taking too long. There may be a problem with your internet or with the service, but we're going to try a couple more times..." - : 'Connecting...'} - - {isTakingTooLong ? ( - { - Linking.openURL(STATUS_PAGE_URL) - }} - accessibilityRole="button"> - - Check Bluesky status page - - - ) : null} - - ) -} - -const styles = StyleSheet.create({ - loading: { - height: '100%', - alignContent: 'center', - justifyContent: 'center', - paddingBottom: 100, - }, - loadingText: { - paddingVertical: 20, - paddingHorizontal: 20, - textAlign: 'center', - }, -}) diff --git a/src/view/screens/AppPasswords.tsx b/src/view/screens/AppPasswords.tsx index bc77a48cde..154035f228 100644 --- a/src/view/screens/AppPasswords.tsx +++ b/src/view/screens/AppPasswords.tsx @@ -12,7 +12,6 @@ import {Button} from '../com/util/forms/Button' import * as Toast from '../com/util/Toast' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {CommonNavigatorParams} from 'lib/routes/types' import {useAnalytics} from 'lib/analytics/analytics' @@ -32,125 +31,111 @@ import {ErrorScreen} from '../com/util/error/ErrorScreen' import {cleanError} from '#/lib/strings/errors' type Props = NativeStackScreenProps -export const AppPasswords = withAuthRequired( - function AppPasswordsImpl({}: Props) { - const pal = usePalette('default') - const setMinimalShellMode = useSetMinimalShellMode() - const {screen} = useAnalytics() - const {isTabletOrDesktop} = useWebMediaQueries() - const {openModal} = useModalControls() - const {data: appPasswords, error} = useAppPasswordsQuery() +export function AppPasswords({}: Props) { + const pal = usePalette('default') + const setMinimalShellMode = useSetMinimalShellMode() + const {screen} = useAnalytics() + const {isTabletOrDesktop} = useWebMediaQueries() + const {openModal} = useModalControls() + const {data: appPasswords, error} = useAppPasswordsQuery() - useFocusEffect( - React.useCallback(() => { - screen('AppPasswords') - setMinimalShellMode(false) - }, [screen, setMinimalShellMode]), + useFocusEffect( + React.useCallback(() => { + screen('AppPasswords') + setMinimalShellMode(false) + }, [screen, setMinimalShellMode]), + ) + + const onAdd = React.useCallback(async () => { + openModal({name: 'add-app-password'}) + }, [openModal]) + + if (error) { + return ( + + + ) + } - const onAdd = React.useCallback(async () => { - openModal({name: 'add-app-password'}) - }, [openModal]) - - if (error) { - return ( - + + + + + You have not created any app passwords yet. You can create one by + pressing the button below. + + + + {!isTabletOrDesktop && } + - + - - - - - ) - }, -) + + + + + + ) +} diff --git a/src/view/screens/Moderation.tsx b/src/view/screens/Moderation.tsx index 10b72fe9ec..4d8d8cad73 100644 --- a/src/view/screens/Moderation.tsx +++ b/src/view/screens/Moderation.tsx @@ -6,7 +6,6 @@ import { FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {s} from 'lib/styles' import {CenteredView} from '../com/util/Views' import {ViewHeader} from '../com/util/ViewHeader' @@ -21,100 +20,98 @@ import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' type Props = NativeStackScreenProps -export const ModerationScreen = withAuthRequired( - function Moderation({}: Props) { - const pal = usePalette('default') - const {_} = useLingui() - const setMinimalShellMode = useSetMinimalShellMode() - const {screen, track} = useAnalytics() - const {isTabletOrDesktop} = useWebMediaQueries() - const {openModal} = useModalControls() +export function ModerationScreen({}: Props) { + const pal = usePalette('default') + const {_} = useLingui() + const setMinimalShellMode = useSetMinimalShellMode() + const {screen, track} = useAnalytics() + const {isTabletOrDesktop} = useWebMediaQueries() + const {openModal} = useModalControls() - useFocusEffect( - React.useCallback(() => { - screen('Moderation') - setMinimalShellMode(false) - }, [screen, setMinimalShellMode]), - ) + useFocusEffect( + React.useCallback(() => { + screen('Moderation') + setMinimalShellMode(false) + }, [screen, setMinimalShellMode]), + ) - const onPressContentFiltering = React.useCallback(() => { - track('Moderation:ContentfilteringButtonClicked') - openModal({name: 'content-filtering-settings'}) - }, [track, openModal]) + const onPressContentFiltering = React.useCallback(() => { + track('Moderation:ContentfilteringButtonClicked') + openModal({name: 'content-filtering-settings'}) + }, [track, openModal]) - return ( - - - - - - - - - Content filtering - - - - - - - - Moderation lists - - - - - - - - Muted accounts - - - - - - - - Blocked accounts - - - - ) - }, -) + return ( + + + + + + + + + Content filtering + + + + + + + + Moderation lists + + + + + + + + Muted accounts + + + + + + + + Blocked accounts + + + + ) +} const styles = StyleSheet.create({ desktopContainer: { diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx index c03275f5d8..8f6e2f7296 100644 --- a/src/view/screens/ModerationBlockedAccounts.tsx +++ b/src/view/screens/ModerationBlockedAccounts.tsx @@ -10,7 +10,6 @@ import {AppBskyActorDefs as ActorDefs} from '@atproto/api' import {Text} from '../com/util/text/Text' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {CommonNavigatorParams} from 'lib/routes/types' import {useAnalytics} from 'lib/analytics/analytics' @@ -30,146 +29,144 @@ type Props = NativeStackScreenProps< CommonNavigatorParams, 'ModerationBlockedAccounts' > -export const ModerationBlockedAccounts = withAuthRequired( - function ModerationBlockedAccountsImpl({}: Props) { - const pal = usePalette('default') - const {_} = useLingui() - const setMinimalShellMode = useSetMinimalShellMode() - const {isTabletOrDesktop} = useWebMediaQueries() - const {screen} = useAnalytics() - const [isPTRing, setIsPTRing] = React.useState(false) - const { - data, - isFetching, - isError, - error, - refetch, - hasNextPage, - fetchNextPage, - isFetchingNextPage, - } = useMyBlockedAccountsQuery() - const isEmpty = !isFetching && !data?.pages[0]?.blocks.length - const profiles = React.useMemo(() => { - if (data?.pages) { - return data.pages.flatMap(page => page.blocks) - } - return [] - }, [data]) +export function ModerationBlockedAccounts({}: Props) { + const pal = usePalette('default') + const {_} = useLingui() + const setMinimalShellMode = useSetMinimalShellMode() + const {isTabletOrDesktop} = useWebMediaQueries() + const {screen} = useAnalytics() + const [isPTRing, setIsPTRing] = React.useState(false) + const { + data, + isFetching, + isError, + error, + refetch, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + } = useMyBlockedAccountsQuery() + const isEmpty = !isFetching && !data?.pages[0]?.blocks.length + const profiles = React.useMemo(() => { + if (data?.pages) { + return data.pages.flatMap(page => page.blocks) + } + return [] + }, [data]) - useFocusEffect( - React.useCallback(() => { - screen('BlockedAccounts') - setMinimalShellMode(false) - }, [screen, setMinimalShellMode]), - ) + useFocusEffect( + React.useCallback(() => { + screen('BlockedAccounts') + setMinimalShellMode(false) + }, [screen, setMinimalShellMode]), + ) - const onRefresh = React.useCallback(async () => { - setIsPTRing(true) - try { - await refetch() - } catch (err) { - logger.error('Failed to refresh my muted accounts', {error: err}) - } - setIsPTRing(false) - }, [refetch, setIsPTRing]) + const onRefresh = React.useCallback(async () => { + setIsPTRing(true) + try { + await refetch() + } catch (err) { + logger.error('Failed to refresh my muted accounts', {error: err}) + } + setIsPTRing(false) + }, [refetch, setIsPTRing]) - const onEndReached = React.useCallback(async () => { - if (isFetching || !hasNextPage || isError) return + const onEndReached = React.useCallback(async () => { + if (isFetching || !hasNextPage || isError) return - try { - await fetchNextPage() - } catch (err) { - logger.error('Failed to load more of my muted accounts', {error: err}) - } - }, [isFetching, hasNextPage, isError, fetchNextPage]) + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more of my muted accounts', {error: err}) + } + }, [isFetching, hasNextPage, isError, fetchNextPage]) - const renderItem = ({ - item, - index, - }: { - item: ActorDefs.ProfileView - index: number - }) => ( - - ) - return ( - ( + + ) + return ( + + + - - - - Blocked accounts cannot reply in your threads, mention you, or - otherwise interact with you. You will not see their content and they - will be prevented from seeing yours. - - - {isEmpty ? ( - - {isError ? ( - - ) : ( - - - - You have not blocked any accounts yet. To block an account, - go to their profile and selected "Block account" from the - menu on their account. - - - - )} - - ) : ( - item.did} - refreshControl={ - - } - onEndReached={onEndReached} - renderItem={renderItem} - initialNumToRender={15} - // FIXME(dan) + styles.description, + pal.text, + isTabletOrDesktop && styles.descriptionDesktop, + ]}> + + Blocked accounts cannot reply in your threads, mention you, or + otherwise interact with you. You will not see their content and they + will be prevented from seeing yours. + + + {isEmpty ? ( + + {isError ? ( + + ) : ( + + + + You have not blocked any accounts yet. To block an account, go + to their profile and selected "Block account" from the menu on + their account. + + + + )} + + ) : ( + item.did} + refreshControl={ + + } + onEndReached={onEndReached} + renderItem={renderItem} + initialNumToRender={15} + // FIXME(dan) - ListFooterComponent={() => ( - - {(isFetching || isFetchingNextPage) && } - - )} - // @ts-ignore our .web version only -prf - desktopFixedHeight - /> - )} - - ) - }, -) + ListFooterComponent={() => ( + + {(isFetching || isFetchingNextPage) && } + + )} + // @ts-ignore our .web version only -prf + desktopFixedHeight + /> + )} + + ) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/screens/ModerationModlists.tsx b/src/view/screens/ModerationModlists.tsx index be0eb3850c..145b35a425 100644 --- a/src/view/screens/ModerationModlists.tsx +++ b/src/view/screens/ModerationModlists.tsx @@ -4,7 +4,6 @@ import {useFocusEffect, useNavigation} from '@react-navigation/native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {AtUri} from '@atproto/api' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {MyLists} from '#/view/com/lists/MyLists' import {Text} from 'view/com/util/text/Text' import {Button} from 'view/com/util/forms/Button' @@ -17,70 +16,68 @@ import {useSetMinimalShellMode} from '#/state/shell' import {useModalControls} from '#/state/modals' type Props = NativeStackScreenProps -export const ModerationModlistsScreen = withAuthRequired( - function ModerationModlistsScreenImpl({}: Props) { - const pal = usePalette('default') - const setMinimalShellMode = useSetMinimalShellMode() - const {isMobile} = useWebMediaQueries() - const navigation = useNavigation() - const {openModal} = useModalControls() +export function ModerationModlistsScreen({}: Props) { + const pal = usePalette('default') + const setMinimalShellMode = useSetMinimalShellMode() + const {isMobile} = useWebMediaQueries() + const navigation = useNavigation() + const {openModal} = useModalControls() - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) - const onPressNewList = React.useCallback(() => { - openModal({ - name: 'create-or-edit-list', - purpose: 'app.bsky.graph.defs#modlist', - onSave: (uri: string) => { - try { - const urip = new AtUri(uri) - navigation.navigate('ProfileList', { - name: urip.hostname, - rkey: urip.rkey, - }) - } catch {} - }, - }) - }, [openModal, navigation]) + const onPressNewList = React.useCallback(() => { + openModal({ + name: 'create-or-edit-list', + purpose: 'app.bsky.graph.defs#modlist', + onSave: (uri: string) => { + try { + const urip = new AtUri(uri) + navigation.navigate('ProfileList', { + name: urip.hostname, + rkey: urip.rkey, + }) + } catch {} + }, + }) + }, [openModal, navigation]) - return ( - - - - - Moderation Lists + return ( + + + + + Moderation Lists + + + Public, shareable lists of users to mute or block in bulk. + + + + - - - - - ) - }, -) + + + + + + ) +} diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index c0ff17eb46..41aee9f2ff 100644 --- a/src/view/screens/ModerationMutedAccounts.tsx +++ b/src/view/screens/ModerationMutedAccounts.tsx @@ -10,7 +10,6 @@ import {AppBskyActorDefs as ActorDefs} from '@atproto/api' import {Text} from '../com/util/text/Text' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {CommonNavigatorParams} from 'lib/routes/types' import {useAnalytics} from 'lib/analytics/analytics' @@ -30,145 +29,143 @@ type Props = NativeStackScreenProps< CommonNavigatorParams, 'ModerationMutedAccounts' > -export const ModerationMutedAccounts = withAuthRequired( - function ModerationMutedAccountsImpl({}: Props) { - const pal = usePalette('default') - const {_} = useLingui() - const setMinimalShellMode = useSetMinimalShellMode() - const {isTabletOrDesktop} = useWebMediaQueries() - const {screen} = useAnalytics() - const [isPTRing, setIsPTRing] = React.useState(false) - const { - data, - isFetching, - isError, - error, - refetch, - hasNextPage, - fetchNextPage, - isFetchingNextPage, - } = useMyMutedAccountsQuery() - const isEmpty = !isFetching && !data?.pages[0]?.mutes.length - const profiles = React.useMemo(() => { - if (data?.pages) { - return data.pages.flatMap(page => page.mutes) - } - return [] - }, [data]) +export function ModerationMutedAccounts({}: Props) { + const pal = usePalette('default') + const {_} = useLingui() + const setMinimalShellMode = useSetMinimalShellMode() + const {isTabletOrDesktop} = useWebMediaQueries() + const {screen} = useAnalytics() + const [isPTRing, setIsPTRing] = React.useState(false) + const { + data, + isFetching, + isError, + error, + refetch, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + } = useMyMutedAccountsQuery() + const isEmpty = !isFetching && !data?.pages[0]?.mutes.length + const profiles = React.useMemo(() => { + if (data?.pages) { + return data.pages.flatMap(page => page.mutes) + } + return [] + }, [data]) - useFocusEffect( - React.useCallback(() => { - screen('MutedAccounts') - setMinimalShellMode(false) - }, [screen, setMinimalShellMode]), - ) + useFocusEffect( + React.useCallback(() => { + screen('MutedAccounts') + setMinimalShellMode(false) + }, [screen, setMinimalShellMode]), + ) - const onRefresh = React.useCallback(async () => { - setIsPTRing(true) - try { - await refetch() - } catch (err) { - logger.error('Failed to refresh my muted accounts', {error: err}) - } - setIsPTRing(false) - }, [refetch, setIsPTRing]) + const onRefresh = React.useCallback(async () => { + setIsPTRing(true) + try { + await refetch() + } catch (err) { + logger.error('Failed to refresh my muted accounts', {error: err}) + } + setIsPTRing(false) + }, [refetch, setIsPTRing]) - const onEndReached = React.useCallback(async () => { - if (isFetching || !hasNextPage || isError) return + const onEndReached = React.useCallback(async () => { + if (isFetching || !hasNextPage || isError) return - try { - await fetchNextPage() - } catch (err) { - logger.error('Failed to load more of my muted accounts', {error: err}) - } - }, [isFetching, hasNextPage, isError, fetchNextPage]) + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more of my muted accounts', {error: err}) + } + }, [isFetching, hasNextPage, isError, fetchNextPage]) - const renderItem = ({ - item, - index, - }: { - item: ActorDefs.ProfileView - index: number - }) => ( - - ) - return ( - ( + + ) + return ( + + + - - - - Muted accounts have their posts removed from your feed and from your - notifications. Mutes are completely private. - - - {isEmpty ? ( - - {isError ? ( - - ) : ( - - - - You have not muted any accounts yet. To mute an account, go - to their profile and selected "Mute account" from the menu - on their account. - - - - )} - - ) : ( - item.did} - refreshControl={ - - } - onEndReached={onEndReached} - renderItem={renderItem} - initialNumToRender={15} - // FIXME(dan) + styles.description, + pal.text, + isTabletOrDesktop && styles.descriptionDesktop, + ]}> + + Muted accounts have their posts removed from your feed and from your + notifications. Mutes are completely private. + + + {isEmpty ? ( + + {isError ? ( + + ) : ( + + + + You have not muted any accounts yet. To mute an account, go to + their profile and selected "Mute account" from the menu on + their account. + + + + )} + + ) : ( + item.did} + refreshControl={ + + } + onEndReached={onEndReached} + renderItem={renderItem} + initialNumToRender={15} + // FIXME(dan) - ListFooterComponent={() => ( - - {(isFetching || isFetchingNextPage) && } - - )} - // @ts-ignore our .web version only -prf - desktopFixedHeight - /> - )} - - ) - }, -) + ListFooterComponent={() => ( + + {(isFetching || isFetchingNextPage) && } + + )} + // @ts-ignore our .web version only -prf + desktopFixedHeight + /> + )} + + ) +} const styles = StyleSheet.create({ container: { diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx index 4ed9c7f74d..8516d1667f 100644 --- a/src/view/screens/Notifications.tsx +++ b/src/view/screens/Notifications.tsx @@ -6,7 +6,6 @@ import { NativeStackScreenProps, NotificationsTabNavigatorParams, } from 'lib/routes/types' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from '../com/util/ViewHeader' import {Feed} from '../com/notifications/Feed' import {TextLink} from 'view/com/util/Link' @@ -28,102 +27,100 @@ type Props = NativeStackScreenProps< NotificationsTabNavigatorParams, 'Notifications' > -export const NotificationsScreen = withAuthRequired( - function NotificationsScreenImpl({}: Props) { - const {_} = useLingui() - const setMinimalShellMode = useSetMinimalShellMode() - const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll() - const scrollElRef = React.useRef(null) - const {screen} = useAnalytics() - const pal = usePalette('default') - const {isDesktop} = useWebMediaQueries() - const unreadNotifs = useUnreadNotifications() - const queryClient = useQueryClient() - const hasNew = !!unreadNotifs +export function NotificationsScreen({}: Props) { + const {_} = useLingui() + const setMinimalShellMode = useSetMinimalShellMode() + const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll() + const scrollElRef = React.useRef(null) + const {screen} = useAnalytics() + const pal = usePalette('default') + const {isDesktop} = useWebMediaQueries() + const unreadNotifs = useUnreadNotifications() + const queryClient = useQueryClient() + const hasNew = !!unreadNotifs - // event handlers - // = - const scrollToTop = React.useCallback(() => { - scrollElRef.current?.scrollToOffset({offset: 0}) - resetMainScroll() - }, [scrollElRef, resetMainScroll]) + // event handlers + // = + const scrollToTop = React.useCallback(() => { + scrollElRef.current?.scrollToOffset({offset: 0}) + resetMainScroll() + }, [scrollElRef, resetMainScroll]) - const onPressLoadLatest = React.useCallback(() => { - scrollToTop() - queryClient.invalidateQueries({ - queryKey: NOTIFS_RQKEY(), - }) - }, [scrollToTop, queryClient]) + const onPressLoadLatest = React.useCallback(() => { + scrollToTop() + queryClient.invalidateQueries({ + queryKey: NOTIFS_RQKEY(), + }) + }, [scrollToTop, queryClient]) - // on-visible setup - // = - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - logger.debug('NotificationsScreen: Updating feed') - screen('Notifications') - return listenSoftReset(onPressLoadLatest) - }, [screen, onPressLoadLatest, setMinimalShellMode]), - ) + // on-visible setup + // = + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + logger.debug('NotificationsScreen: Updating feed') + screen('Notifications') + return listenSoftReset(onPressLoadLatest) + }, [screen, onPressLoadLatest, setMinimalShellMode]), + ) - const ListHeaderComponent = React.useCallback(() => { - if (isDesktop) { - return ( - - - Notifications{' '} - {hasNew && ( - - )} - - } - onPress={emitSoftReset} - /> - - ) - } - return <> - }, [isDesktop, pal, hasNew]) - - return ( - - - - {(isScrolledDown || hasNew) && ( - { + if (isDesktop) { + return ( + + + Notifications{' '} + {hasNew && ( + + )} + + } + onPress={emitSoftReset} /> - )} - - ) - }, -) + + ) + } + return <> + }, [isDesktop, pal, hasNew]) + + return ( + + + + {(isScrolledDown || hasNew) && ( + + )} + + ) +} diff --git a/src/view/screens/PostLikedBy.tsx b/src/view/screens/PostLikedBy.tsx index 2209310d06..7cbb811024 100644 --- a/src/view/screens/PostLikedBy.tsx +++ b/src/view/screens/PostLikedBy.tsx @@ -2,7 +2,6 @@ import React from 'react' import {View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from '../com/util/ViewHeader' import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy' import {makeRecordUri} from 'lib/strings/url-helpers' @@ -11,25 +10,22 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' type Props = NativeStackScreenProps -export const PostLikedByScreen = withAuthRequired( - ({route}: Props) => { - const setMinimalShellMode = useSetMinimalShellMode() - const {name, rkey} = route.params - const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - const {_} = useLingui() +export const PostLikedByScreen = ({route}: Props) => { + const setMinimalShellMode = useSetMinimalShellMode() + const {name, rkey} = route.params + const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) + const {_} = useLingui() - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) - return ( - - - - - ) - }, - {isPublic: true}, -) + return ( + + + + + ) +} diff --git a/src/view/screens/PostRepostedBy.tsx b/src/view/screens/PostRepostedBy.tsx index 5b3b5f8fae..de95f33bf9 100644 --- a/src/view/screens/PostRepostedBy.tsx +++ b/src/view/screens/PostRepostedBy.tsx @@ -1,7 +1,6 @@ import React from 'react' import {View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {ViewHeader} from '../com/util/ViewHeader' import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy' @@ -11,25 +10,22 @@ import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' type Props = NativeStackScreenProps -export const PostRepostedByScreen = withAuthRequired( - ({route}: Props) => { - const {name, rkey} = route.params - const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - const setMinimalShellMode = useSetMinimalShellMode() - const {_} = useLingui() +export const PostRepostedByScreen = ({route}: Props) => { + const {name, rkey} = route.params + const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) + const setMinimalShellMode = useSetMinimalShellMode() + const {_} = useLingui() - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) - return ( - - - - - ) - }, - {isPublic: true}, -) + return ( + + + + + ) +} diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index 11574e2837..4b1f517487 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -5,7 +5,6 @@ import {useFocusEffect} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {makeRecordUri} from 'lib/strings/url-helpers' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from '../com/util/ViewHeader' import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread' import {ComposePrompt} from 'view/com/composer/Prompt' @@ -27,85 +26,82 @@ import {CenteredView} from '../com/util/Views' import {useComposerControls} from '#/state/shell/composer' type Props = NativeStackScreenProps -export const PostThreadScreen = withAuthRequired( - function PostThreadScreenImpl({route}: Props) { - const queryClient = useQueryClient() - const {_} = useLingui() - const {fabMinimalShellTransform} = useMinimalShellMode() - const setMinimalShellMode = useSetMinimalShellMode() - const {openComposer} = useComposerControls() - const safeAreaInsets = useSafeAreaInsets() - const {name, rkey} = route.params - const {isMobile} = useWebMediaQueries() - const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - const {data: resolvedUri, error: uriError} = useResolveUriQuery(uri) +export function PostThreadScreen({route}: Props) { + const queryClient = useQueryClient() + const {_} = useLingui() + const {fabMinimalShellTransform} = useMinimalShellMode() + const setMinimalShellMode = useSetMinimalShellMode() + const {openComposer} = useComposerControls() + const safeAreaInsets = useSafeAreaInsets() + const {name, rkey} = route.params + const {isMobile} = useWebMediaQueries() + const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) + const {data: resolvedUri, error: uriError} = useResolveUriQuery(uri) - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) + + const onPressReply = React.useCallback(() => { + if (!resolvedUri) { + return + } + const thread = queryClient.getQueryData( + POST_THREAD_RQKEY(resolvedUri.uri), ) - - const onPressReply = React.useCallback(() => { - if (!resolvedUri) { - return - } - const thread = queryClient.getQueryData( - POST_THREAD_RQKEY(resolvedUri.uri), - ) - if (thread?.type !== 'post') { - return - } - openComposer({ - replyTo: { - uri: thread.post.uri, - cid: thread.post.cid, - text: thread.record.text, - author: { - handle: thread.post.author.handle, - displayName: thread.post.author.displayName, - avatar: thread.post.author.avatar, - }, + if (thread?.type !== 'post') { + return + } + openComposer({ + replyTo: { + uri: thread.post.uri, + cid: thread.post.cid, + text: thread.record.text, + author: { + handle: thread.post.author.handle, + displayName: thread.post.author.displayName, + avatar: thread.post.author.avatar, }, - onPost: () => - queryClient.invalidateQueries({ - queryKey: POST_THREAD_RQKEY(resolvedUri.uri || ''), - }), - }) - }, [openComposer, queryClient, resolvedUri]) + }, + onPost: () => + queryClient.invalidateQueries({ + queryKey: POST_THREAD_RQKEY(resolvedUri.uri || ''), + }), + }) + }, [openComposer, queryClient, resolvedUri]) - return ( - - {isMobile && } - - {uriError ? ( - - - - ) : ( - - )} - - {isMobile && ( - - - + return ( + + {isMobile && } + + {uriError ? ( + + + + ) : ( + )} - ) - }, - {isPublic: true}, -) + {isMobile && ( + + + + )} + + ) +} const styles = StyleSheet.create({ prompt: { diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 48c914054e..8282b2105d 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -5,7 +5,6 @@ import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewSelectorHandle} from '../com/util/ViewSelector' import {CenteredView, FlatList} from '../com/util/Views' import {ScreenHider} from 'view/com/util/moderation/ScreenHider' @@ -43,83 +42,78 @@ interface SectionRef { } type Props = NativeStackScreenProps -export const ProfileScreen = withAuthRequired( - function ProfileScreenImpl({route}: Props) { - const {currentAccount} = useSession() - const name = - route.params.name === 'me' ? currentAccount?.did : route.params.name - const moderationOpts = useModerationOpts() - const { - data: resolvedDid, - error: resolveError, - refetch: refetchDid, - isFetching: isFetchingDid, - } = useResolveDidQuery(name) - const { - data: profile, - error: profileError, - refetch: refetchProfile, - isFetching: isFetchingProfile, - } = useProfileQuery({ - did: resolvedDid?.did, - }) +export function ProfileScreen({route}: Props) { + const {currentAccount} = useSession() + const name = + route.params.name === 'me' ? currentAccount?.did : route.params.name + const moderationOpts = useModerationOpts() + const { + data: resolvedDid, + error: resolveError, + refetch: refetchDid, + isFetching: isFetchingDid, + } = useResolveDidQuery(name) + const { + data: profile, + error: profileError, + refetch: refetchProfile, + isFetching: isFetchingProfile, + } = useProfileQuery({ + did: resolvedDid?.did, + }) - const onPressTryAgain = React.useCallback(() => { - if (resolveError) { - refetchDid() - } else { - refetchProfile() - } - }, [resolveError, refetchDid, refetchProfile]) + const onPressTryAgain = React.useCallback(() => { + if (resolveError) { + refetchDid() + } else { + refetchProfile() + } + }, [resolveError, refetchDid, refetchProfile]) - if (isFetchingDid || isFetchingProfile || !moderationOpts) { - return ( - - - - ) - } - if (resolveError || profileError) { - return ( - - - - ) - } - if (profile && moderationOpts) { - return ( - + - ) - } - // should never happen + + ) + } + if (resolveError || profileError) { return ( ) - }, - { - isPublic: true, - }, -) + } + if (profile && moderationOpts) { + return ( + + ) + } + // should never happen + return ( + + + + ) +} function ProfileScreenLoaded({ profile: profileUnshadowed, diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index 642793edac..da01cfca65 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -16,7 +16,6 @@ import {CommonNavigatorParams} from 'lib/routes/types' import {makeRecordUri} from 'lib/strings/url-helpers' import {colors, s} from 'lib/styles' import {FeedDescriptor} from '#/state/queries/post-feed' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' import {Feed} from 'view/com/posts/Feed' @@ -69,70 +68,65 @@ interface SectionRef { } type Props = NativeStackScreenProps -export const ProfileFeedScreen = withAuthRequired( - function ProfileFeedScreenImpl(props: Props) { - const {rkey, name: handleOrDid} = props.route.params +export function ProfileFeedScreen(props: Props) { + const {rkey, name: handleOrDid} = props.route.params - const pal = usePalette('default') - const {_} = useLingui() - const navigation = useNavigation() + const pal = usePalette('default') + const {_} = useLingui() + const navigation = useNavigation() - const uri = useMemo( - () => makeRecordUri(handleOrDid, 'app.bsky.feed.generator', rkey), - [rkey, handleOrDid], - ) - const {error, data: resolvedUri} = useResolveUriQuery(uri) + const uri = useMemo( + () => makeRecordUri(handleOrDid, 'app.bsky.feed.generator', rkey), + [rkey, handleOrDid], + ) + const {error, data: resolvedUri} = useResolveUriQuery(uri) - const onPressBack = React.useCallback(() => { - if (navigation.canGoBack()) { - navigation.goBack() - } else { - navigation.navigate('Home') - } - }, [navigation]) - - if (error) { - return ( - - - - Could not load feed - - - {error.toString()} - - - - - - - - ) + const onPressBack = React.useCallback(() => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.navigate('Home') } + }, [navigation]) - return resolvedUri ? ( - - ) : ( + if (error) { + return ( - - + + + Could not load feed + + + {error.toString()} + + + + + ) - }, - { - isPublic: true, - }, -) + } + + return resolvedUri ? ( + + ) : ( + + + + + + ) +} function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) { const {data: preferences} = usePreferencesQuery() diff --git a/src/view/screens/ProfileFeedLikedBy.tsx b/src/view/screens/ProfileFeedLikedBy.tsx index 6399c8a0b9..0460670e19 100644 --- a/src/view/screens/ProfileFeedLikedBy.tsx +++ b/src/view/screens/ProfileFeedLikedBy.tsx @@ -2,7 +2,6 @@ import React from 'react' import {View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from '../com/util/ViewHeader' import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy' import {makeRecordUri} from 'lib/strings/url-helpers' @@ -11,25 +10,22 @@ import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' type Props = NativeStackScreenProps -export const ProfileFeedLikedByScreen = withAuthRequired( - ({route}: Props) => { - const setMinimalShellMode = useSetMinimalShellMode() - const {name, rkey} = route.params - const uri = makeRecordUri(name, 'app.bsky.feed.generator', rkey) - const {_} = useLingui() +export const ProfileFeedLikedByScreen = ({route}: Props) => { + const setMinimalShellMode = useSetMinimalShellMode() + const {name, rkey} = route.params + const uri = makeRecordUri(name, 'app.bsky.feed.generator', rkey) + const {_} = useLingui() - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) - return ( - - - - - ) - }, - {isPublic: true}, -) + return ( + + + + + ) +} diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx index 71c0e4a9cb..2cad08cb5d 100644 --- a/src/view/screens/ProfileFollowers.tsx +++ b/src/view/screens/ProfileFollowers.tsx @@ -2,7 +2,6 @@ import React from 'react' import {View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from '../com/util/ViewHeader' import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers' import {useSetMinimalShellMode} from '#/state/shell' @@ -10,24 +9,21 @@ import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' type Props = NativeStackScreenProps -export const ProfileFollowersScreen = withAuthRequired( - ({route}: Props) => { - const {name} = route.params - const setMinimalShellMode = useSetMinimalShellMode() - const {_} = useLingui() +export const ProfileFollowersScreen = ({route}: Props) => { + const {name} = route.params + const setMinimalShellMode = useSetMinimalShellMode() + const {_} = useLingui() - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) - return ( - - - - - ) - }, - {isPublic: true}, -) + return ( + + + + + ) +} diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx index bb3f2040f6..80502b98bc 100644 --- a/src/view/screens/ProfileFollows.tsx +++ b/src/view/screens/ProfileFollows.tsx @@ -2,7 +2,6 @@ import React from 'react' import {View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from '../com/util/ViewHeader' import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows' import {useSetMinimalShellMode} from '#/state/shell' @@ -10,24 +9,21 @@ import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' type Props = NativeStackScreenProps -export const ProfileFollowsScreen = withAuthRequired( - ({route}: Props) => { - const {name} = route.params - const setMinimalShellMode = useSetMinimalShellMode() - const {_} = useLingui() +export const ProfileFollowsScreen = ({route}: Props) => { + const {name} = route.params + const setMinimalShellMode = useSetMinimalShellMode() + const {_} = useLingui() - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) - return ( - - - - - ) - }, - {isPublic: true}, -) + return ( + + + + + ) +} diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx index a8c55250fc..cc6d85e6f5 100644 --- a/src/view/screens/ProfileList.tsx +++ b/src/view/screens/ProfileList.tsx @@ -12,7 +12,6 @@ import {useNavigation} from '@react-navigation/native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {AppBskyGraphDefs, AtUri, RichText as RichTextAPI} from '@atproto/api' import {useQueryClient} from '@tanstack/react-query' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' import {Feed} from 'view/com/posts/Feed' @@ -64,42 +63,40 @@ interface SectionRef { } type Props = NativeStackScreenProps -export const ProfileListScreen = withAuthRequired( - function ProfileListScreenImpl(props: Props) { - const {name: handleOrDid, rkey} = props.route.params - const {data: resolvedUri, error: resolveError} = useResolveUriQuery( - AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(), - ) - const {data: list, error: listError} = useListQuery(resolvedUri?.uri) +export function ProfileListScreen(props: Props) { + const {name: handleOrDid, rkey} = props.route.params + const {data: resolvedUri, error: resolveError} = useResolveUriQuery( + AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(), + ) + const {data: list, error: listError} = useListQuery(resolvedUri?.uri) - if (resolveError) { - return ( - - - - ) - } - if (listError) { - return ( - - - - ) - } - - return resolvedUri && list ? ( - - ) : ( + if (resolveError) { + return ( - - - + ) - }, -) + } + if (listError) { + return ( + + + + ) + } + + return resolvedUri && list ? ( + + ) : ( + + + + + + ) +} function ProfileListScreenLoaded({ route, diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx index 4c13a2be17..640d76a5c5 100644 --- a/src/view/screens/SavedFeeds.tsx +++ b/src/view/screens/SavedFeeds.tsx @@ -14,7 +14,6 @@ import {useAnalytics} from 'lib/analytics/analytics' import {usePalette} from 'lib/hooks/usePalette' import {CommonNavigatorParams} from 'lib/routes/types' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from 'view/com/util/ViewHeader' import {ScrollView, CenteredView} from 'view/com/util/Views' import {Text} from 'view/com/util/text/Text' @@ -51,7 +50,7 @@ const HITSLOP_BOTTOM = { } type Props = NativeStackScreenProps -export const SavedFeeds = withAuthRequired(function SavedFeedsImpl({}: Props) { +export function SavedFeeds({}: Props) { const pal = usePalette('default') const {_} = useLingui() const {isMobile, isTabletOrDesktop} = useWebMediaQueries() @@ -147,7 +146,7 @@ export const SavedFeeds = withAuthRequired(function SavedFeedsImpl({}: Props) { ) -}) +} function ListItem({ feedUri, diff --git a/src/view/screens/Search/index.tsx b/src/view/screens/Search/index.tsx index 69130ecfd6..a65149bf73 100644 --- a/src/view/screens/Search/index.tsx +++ b/src/view/screens/Search/index.tsx @@ -1,6 +1,3 @@ -import {withAuthRequired} from '#/view/com/auth/withAuthRequired' import {SearchScreenMobile} from '#/view/screens/Search/Search' -export const SearchScreen = withAuthRequired(SearchScreenMobile, { - isPublic: true, -}) +export const SearchScreen = SearchScreenMobile diff --git a/src/view/screens/Search/index.web.tsx b/src/view/screens/Search/index.web.tsx index 03fb7589c3..8e039e3cdf 100644 --- a/src/view/screens/Search/index.web.tsx +++ b/src/view/screens/Search/index.web.tsx @@ -1,6 +1,3 @@ -import {withAuthRequired} from '#/view/com/auth/withAuthRequired' import {SearchScreenDesktop} from '#/view/screens/Search/Search' -export const SearchScreen = withAuthRequired(SearchScreenDesktop, { - isPublic: true, -}) +export const SearchScreen = SearchScreenDesktop diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index cc4348fff1..88cc2d5320 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -20,7 +20,6 @@ import { FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' import * as AppInfo from 'lib/app-info' import {s, colors} from 'lib/styles' import {ScrollView} from '../com/util/Views' @@ -141,7 +140,7 @@ function SettingsAccountCard({account}: {account: SessionAccount}) { } type Props = NativeStackScreenProps -export const SettingsScreen = withAuthRequired(function Settings({}: Props) { +export function SettingsScreen({}: Props) { const queryClient = useQueryClient() const colorMode = useColorMode() const setColorMode = useSetColorMode() @@ -731,7 +730,7 @@ export const SettingsScreen = withAuthRequired(function Settings({}: Props) { ) -}) +} function EmailConfirmationNotice() { const pal = usePalette('default') diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx new file mode 100644 index 0000000000..c7b5d1d2e3 --- /dev/null +++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx @@ -0,0 +1,150 @@ +import * as React from 'react' +import {View} from 'react-native' + +// Based on @react-navigation/native-stack/src/createNativeStackNavigator.ts +// MIT License +// Copyright (c) 2017 React Navigation Contributors + +import { + createNavigatorFactory, + EventArg, + ParamListBase, + StackActionHelpers, + StackActions, + StackNavigationState, + StackRouter, + StackRouterOptions, + useNavigationBuilder, +} from '@react-navigation/native' +import type { + NativeStackNavigationEventMap, + NativeStackNavigationOptions, +} from '@react-navigation/native-stack' +import type {NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types' +import {NativeStackView} from '@react-navigation/native-stack' + +import {BottomBarWeb} from './bottom-bar/BottomBarWeb' +import {DesktopLeftNav} from './desktop/LeftNav' +import {DesktopRightNav} from './desktop/RightNav' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {useOnboardingState} from '#/state/shell' +import { + useLoggedOutView, + useLoggedOutViewControls, +} from '#/state/shell/logged-out' +import {useSession} from '#/state/session' +import {isWeb} from 'platform/detection' +import {LoggedOut} from '../com/auth/LoggedOut' +import {Onboarding} from '../com/auth/Onboarding' + +type NativeStackNavigationOptionsWithAuth = NativeStackNavigationOptions & { + requireAuth?: boolean +} + +function NativeStackNavigator({ + id, + initialRouteName, + children, + screenListeners, + screenOptions, + ...rest +}: NativeStackNavigatorProps) { + // --- this is copy and pasted from the original native stack navigator --- + const {state, descriptors, navigation, NavigationContent} = + useNavigationBuilder< + StackNavigationState, + StackRouterOptions, + StackActionHelpers, + NativeStackNavigationOptionsWithAuth, + NativeStackNavigationEventMap + >(StackRouter, { + id, + initialRouteName, + children, + screenListeners, + screenOptions, + }) + React.useEffect( + () => + // @ts-expect-error: there may not be a tab navigator in parent + navigation?.addListener?.('tabPress', (e: any) => { + const isFocused = navigation.isFocused() + + // Run the operation in the next frame so we're sure all listeners have been run + // This is necessary to know if preventDefault() has been called + requestAnimationFrame(() => { + if ( + state.index > 0 && + isFocused && + !(e as EventArg<'tabPress', true>).defaultPrevented + ) { + // When user taps on already focused tab and we're inside the tab, + // reset the stack to replicate native behaviour + navigation.dispatch({ + ...StackActions.popToTop(), + target: state.key, + }) + } + }) + }), + [navigation, state.index, state.key], + ) + + // --- our custom logic starts here --- + const {hasSession} = useSession() + const activeRoute = state.routes[state.index] + const activeDescriptor = descriptors[activeRoute.key] + const activeRouteRequiresAuth = activeDescriptor.options.requireAuth ?? false + const onboardingState = useOnboardingState() + const {showLoggedOut} = useLoggedOutView() + const {setShowLoggedOut} = useLoggedOutViewControls() + const {isMobile} = useWebMediaQueries() + if (activeRouteRequiresAuth && !hasSession) { + return + } + if (showLoggedOut) { + return setShowLoggedOut(false)} /> + } + if (onboardingState.isActive) { + return + } + const newDescriptors: typeof descriptors = {} + for (let key in descriptors) { + const descriptor = descriptors[key] + const requireAuth = descriptor.options.requireAuth ?? false + newDescriptors[key] = { + ...descriptor, + render() { + if (requireAuth && !hasSession) { + return + } else { + return descriptor.render() + } + }, + } + } + return ( + + + {isWeb && isMobile && } + {isWeb && !isMobile && ( + <> + + + + )} + + ) +} + +export const createNativeStackNavigatorWithAuth = createNavigatorFactory< + StackNavigationState, + NativeStackNavigationOptionsWithAuth, + NativeStackNavigationEventMap, + typeof NativeStackNavigator +>(NativeStackNavigator) diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 74477243d6..38da860bdc 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -1,7 +1,5 @@ import React, {useEffect} from 'react' import {View, StyleSheet, TouchableOpacity} from 'react-native' -import {DesktopLeftNav} from './desktop/LeftNav' -import {DesktopRightNav} from './desktop/RightNav' import {ErrorBoundary} from '../com/util/ErrorBoundary' import {Lightbox} from '../com/lightbox/Lightbox' import {ModalsContainer} from '../com/modals/Modal' @@ -11,27 +9,19 @@ import {s, colors} from 'lib/styles' import {RoutesContainer, FlatNavigator} from '../../Navigation' import {DrawerContent} from './Drawer' import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries' -import {BottomBarWeb} from './bottom-bar/BottomBarWeb' import {useNavigation} from '@react-navigation/native' import {NavigationProp} from 'lib/routes/types' import {useAuxClick} from 'lib/hooks/useAuxClick' import {t} from '@lingui/macro' -import { - useIsDrawerOpen, - useSetDrawerOpen, - useOnboardingState, -} from '#/state/shell' +import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' import {useCloseAllActiveElements} from '#/state/util' -import {useLoggedOutView} from '#/state/shell/logged-out' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() const setDrawerOpen = useSetDrawerOpen() - const onboardingState = useOnboardingState() - const {isDesktop, isMobile} = useWebMediaQueries() + const {isDesktop} = useWebMediaQueries() const navigator = useNavigation() const closeAllActiveElements = useCloseAllActiveElements() - const {showLoggedOut} = useLoggedOutView() useAuxClick() @@ -42,8 +32,6 @@ function ShellInner() { return unsubscribe }, [navigator, closeAllActiveElements]) - const showBottomBar = isMobile && !onboardingState.isActive - const showSideNavs = !isMobile && !onboardingState.isActive && !showLoggedOut return ( @@ -51,22 +39,9 @@ function ShellInner() { - - {showSideNavs && ( - <> - - - - )} - - - {showBottomBar && } - - - {!isDesktop && isDrawerOpen && ( setDrawerOpen(false)}