PWI: Refactor Shell (#1989)

* Vendor createNativeStackNavigator for further tweaks

* Completely disable withAuthRequired

* Render LoggedOut for protected routes

* Move web shell into the navigator

* Simplify the logic

* Add login modal

* Delete withAuthRequired

* Reset app state on session change

* Move TS suppression
zio/stable
dan 2023-11-24 22:31:33 +00:00 committed by GitHub
parent 4b59a21cac
commit f2d164ec23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1627 additions and 1665 deletions

View File

@ -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,6 +70,10 @@ function InnerApp() {
*/
return (
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<LoggedOutViewProvider>
<UnreadNotifsProvider>
<ThemeProvider theme={colorMode}>
<analytics.Provider>
@ -82,6 +87,8 @@ function InnerApp() {
</analytics.Provider>
</ThemeProvider>
</UnreadNotifsProvider>
</LoggedOutViewProvider>
</React.Fragment>
)
}

View File

@ -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,6 +58,10 @@ function InnerApp() {
*/
return (
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<LoggedOutViewProvider>
<UnreadNotifsProvider>
<ThemeProvider theme={colorMode}>
<analytics.Provider>
@ -70,6 +75,8 @@ function InnerApp() {
</analytics.Provider>
</ThemeProvider>
</UnreadNotifsProvider>
</LoggedOutViewProvider>
</React.Fragment>
)
}

View File

@ -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<AllNavigatorParams>()
const HomeTab = createNativeStackNavigator<HomeTabNavigatorParams>()
const SearchTab = createNativeStackNavigator<SearchTabNavigatorParams>()
const FeedsTab = createNativeStackNavigator<FeedsTabNavigatorParams>()
const HomeTab = createNativeStackNavigatorWithAuth<HomeTabNavigatorParams>()
const SearchTab = createNativeStackNavigatorWithAuth<SearchTabNavigatorParams>()
const FeedsTab = createNativeStackNavigatorWithAuth<FeedsTabNavigatorParams>()
const NotificationsTab =
createNativeStackNavigator<NotificationsTabNavigatorParams>()
const MyProfileTab = createNativeStackNavigator<MyProfileTabNavigatorParams>()
const Flat = createNativeStackNavigator<FlatNavigatorParams>()
createNativeStackNavigatorWithAuth<NotificationsTabNavigatorParams>()
const MyProfileTab =
createNativeStackNavigatorWithAuth<MyProfileTabNavigatorParams>()
const Flat = createNativeStackNavigatorWithAuth<FlatNavigatorParams>()
const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
/**
@ -97,37 +98,37 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
<Stack.Screen
name="Lists"
component={ListsScreen}
options={{title: title('Lists')}}
options={{title: title('Lists'), requireAuth: true}}
/>
<Stack.Screen
name="Moderation"
getComponent={() => ModerationScreen}
options={{title: title('Moderation')}}
options={{title: title('Moderation'), requireAuth: true}}
/>
<Stack.Screen
name="ModerationModlists"
getComponent={() => ModerationModlistsScreen}
options={{title: title('Moderation Lists')}}
options={{title: title('Moderation Lists'), requireAuth: true}}
/>
<Stack.Screen
name="ModerationMutedAccounts"
getComponent={() => ModerationMutedAccounts}
options={{title: title('Muted Accounts')}}
options={{title: title('Muted Accounts'), requireAuth: true}}
/>
<Stack.Screen
name="ModerationBlockedAccounts"
getComponent={() => ModerationBlockedAccounts}
options={{title: title('Blocked Accounts')}}
options={{title: title('Blocked Accounts'), requireAuth: true}}
/>
<Stack.Screen
name="Settings"
getComponent={() => SettingsScreen}
options={{title: title('Settings')}}
options={{title: title('Settings'), requireAuth: true}}
/>
<Stack.Screen
name="LanguageSettings"
getComponent={() => LanguageSettingsScreen}
options={{title: title('Language Settings')}}
options={{title: title('Language Settings'), requireAuth: true}}
/>
<Stack.Screen
name="Profile"
@ -154,7 +155,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
<Stack.Screen
name="ProfileList"
getComponent={() => ProfileListScreen}
options={{title: title('List')}}
options={{title: title('List'), requireAuth: true}}
/>
<Stack.Screen
name="PostThread"
@ -184,12 +185,12 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
<Stack.Screen
name="Debug"
getComponent={() => DebugScreen}
options={{title: title('Debug')}}
options={{title: title('Debug'), requireAuth: true}}
/>
<Stack.Screen
name="Log"
getComponent={() => LogScreen}
options={{title: title('Log')}}
options={{title: title('Log'), requireAuth: true}}
/>
<Stack.Screen
name="Support"
@ -219,22 +220,22 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
<Stack.Screen
name="AppPasswords"
getComponent={() => AppPasswords}
options={{title: title('App Passwords')}}
options={{title: title('App Passwords'), requireAuth: true}}
/>
<Stack.Screen
name="SavedFeeds"
getComponent={() => SavedFeeds}
options={{title: title('Edit My Feeds')}}
options={{title: title('Edit My Feeds'), requireAuth: true}}
/>
<Stack.Screen
name="PreferencesHomeFeed"
getComponent={() => PreferencesHomeFeed}
options={{title: title('Home Feed Preferences')}}
options={{title: title('Home Feed Preferences'), requireAuth: true}}
/>
<Stack.Screen
name="PreferencesThreads"
getComponent={() => PreferencesThreads}
options={{title: title('Threads Preferences')}}
options={{title: title('Threads Preferences'), requireAuth: true}}
/>
</>
)
@ -339,6 +340,7 @@ function NotificationsTabNavigator() {
<NotificationsTab.Screen
name="Notifications"
getComponent={() => NotificationsScreen}
options={{requireAuth: true}}
/>
{commonScreens(NotificationsTab as typeof HomeTab)}
</NotificationsTab.Navigator>
@ -357,8 +359,8 @@ function MyProfileTabNavigator() {
contentStyle,
}}>
<MyProfileTab.Screen
name="MyProfile"
// @ts-ignore // TODO: fix this broken type in ProfileScreen
name="MyProfile"
getComponent={() => ProfileScreen}
initialParams={{
name: 'me',
@ -405,7 +407,7 @@ const FlatNavigator = () => {
<Flat.Screen
name="Notifications"
getComponent={() => NotificationsScreen}
options={{title: title('Notifications')}}
options={{title: title('Notifications'), requireAuth: true}}
/>
{commonScreens(Flat as typeof HomeTab, numUnread)}
</Flat.Navigator>

View File

@ -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 (
<ShellLayoutProvder>
<LoggedOutViewProvider>
<DrawerOpenProvider>
<DrawerSwipableProvider>
<MinimalModeProvider>
<ColorModeProvider>
<OnboardingProvider>
<ComposerProvider>
<TickEveryMinuteProvider>
{children}
</TickEveryMinuteProvider>
<TickEveryMinuteProvider>{children}</TickEveryMinuteProvider>
</ComposerProvider>
</OnboardingProvider>
</ColorModeProvider>
</MinimalModeProvider>
</DrawerSwipableProvider>
</DrawerOpenProvider>
</LoggedOutViewProvider>
</ShellLayoutProvder>
)
}

View File

@ -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 = <P extends object>(
Component: React.ComponentType<P>,
options: {
isPublic?: boolean // TODO(pwi) need to enable in TF somehow
} = {},
): React.FC<P> =>
function AuthRequired(props: P) {
const {isInitialLoad, hasSession} = useSession()
const onboardingState = useOnboardingState()
const {showLoggedOut} = useLoggedOutView()
const {setShowLoggedOut} = useLoggedOutViewControls()
if (isInitialLoad) {
return <Loading />
}
if (!hasSession) {
if (showLoggedOut) {
return <LoggedOut onDismiss={() => setShowLoggedOut(false)} />
} else if (!options?.isPublic || IS_PROD) {
return <LoggedOut />
}
}
if (onboardingState.isActive) {
return <Onboarding />
}
return <Component {...props} />
}
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 (
<CenteredView style={[styles.loading, pal.view]}>
<ActivityIndicator size="large" />
<Text type="2xl" style={[styles.loadingText, pal.textLight]}>
{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...'}
</Text>
{isTakingTooLong ? (
<TouchableOpacity
onPress={() => {
Linking.openURL(STATUS_PAGE_URL)
}}
accessibilityRole="button">
<Text type="2xl" style={[styles.loadingText, pal.link]}>
Check Bluesky status page
</Text>
</TouchableOpacity>
) : null}
</CenteredView>
)
}
const styles = StyleSheet.create({
loading: {
height: '100%',
alignContent: 'center',
justifyContent: 'center',
paddingBottom: 100,
},
loadingText: {
paddingVertical: 20,
paddingHorizontal: 20,
textAlign: 'center',
},
})

View File

@ -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,8 +31,7 @@ import {ErrorScreen} from '../com/util/error/ErrorScreen'
import {cleanError} from '#/lib/strings/errors'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'>
export const AppPasswords = withAuthRequired(
function AppPasswordsImpl({}: Props) {
export function AppPasswords({}: Props) {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {screen} = useAnalytics()
@ -86,8 +84,8 @@ export const AppPasswords = withAuthRequired(
<View style={[styles.empty, pal.viewLight]}>
<Text type="lg" style={[pal.text, styles.emptyText]}>
<Trans>
You have not created any app passwords yet. You can create one
by pressing the button below.
You have not created any app passwords yet. You can create one by
pressing the button below.
</Trans>
</Text>
</View>
@ -177,8 +175,7 @@ export const AppPasswords = withAuthRequired(
<ActivityIndicator />
</CenteredView>
)
},
)
}
function AppPasswordsHeader() {
const {isTabletOrDesktop} = useWebMediaQueries()

View File

@ -2,7 +2,6 @@ import React from 'react'
import {ActivityIndicator, StyleSheet, View, RefreshControl} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {FAB} from 'view/com/util/fab/FAB'
import {Link} from 'view/com/util/Link'
@ -88,8 +87,7 @@ type FlatlistSlice =
key: string
}
export const FeedsScreen = withAuthRequired(
function FeedsScreenImpl(_props: Props) {
export function FeedsScreen(_props: Props) {
const pal = usePalette('default')
const {openComposer} = useComposerControls()
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
@ -287,9 +285,7 @@ export const FeedsScreen = withAuthRequired(
for (const page of popularFeeds.pages || []) {
slices = slices.concat(
page.feeds
.filter(
feed => !preferences?.feeds?.saved.includes(feed.uri),
)
.filter(feed => !preferences?.feeds?.saved.includes(feed.uri))
.map(feed => ({
key: `popularFeed:${feed.uri}`,
type: 'popularFeed',
@ -516,9 +512,7 @@ export const FeedsScreen = withAuthRequired(
)}
</View>
)
},
{isPublic: true},
)
}
function SavedFeed({feedUri}: {feedUri: string}) {
const pal = usePalette('default')

View File

@ -3,7 +3,6 @@ import {View, ActivityIndicator, StyleSheet} from 'react-native'
import {useFocusEffect} from '@react-navigation/native'
import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types'
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
@ -17,8 +16,7 @@ import {emitSoftReset} from '#/state/events'
import {useSession} from '#/state/session'
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
export const HomeScreen = withAuthRequired(
function HomeScreenImpl(props: Props) {
export function HomeScreen(props: Props) {
const {hasSession} = useSession()
const {data: preferences} = usePreferencesQuery()
@ -35,11 +33,7 @@ export const HomeScreen = withAuthRequired(
</View>
)
}
},
{
isPublic: true,
},
)
}
function HomeScreenPublic() {
const setMinimalShellMode = useSetMinimalShellMode()

View File

@ -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'
@ -18,8 +17,7 @@ import {useModalControls} from '#/state/modals'
import {Trans} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
export const ListsScreen = withAuthRequired(
function ListsScreenImpl({}: Props) {
export function ListsScreen({}: Props) {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {isMobile} = useWebMediaQueries()
@ -83,5 +81,4 @@ export const ListsScreen = withAuthRequired(
<MyLists filter="curate" style={s.flexGrow1} />
</View>
)
},
)
}

View File

@ -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,8 +20,7 @@ import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>
export const ModerationScreen = withAuthRequired(
function Moderation({}: Props) {
export function ModerationScreen({}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
@ -113,8 +111,7 @@ export const ModerationScreen = withAuthRequired(
</Link>
</CenteredView>
)
},
)
}
const styles = StyleSheet.create({
desktopContainer: {

View File

@ -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,8 +29,7 @@ type Props = NativeStackScreenProps<
CommonNavigatorParams,
'ModerationBlockedAccounts'
>
export const ModerationBlockedAccounts = withAuthRequired(
function ModerationBlockedAccountsImpl({}: Props) {
export function ModerationBlockedAccounts({}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
@ -131,9 +129,9 @@ export const ModerationBlockedAccounts = withAuthRequired(
<View style={[styles.empty, pal.viewLight]}>
<Text type="lg" style={[pal.text, styles.emptyText]}>
<Trans>
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.
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.
</Trans>
</Text>
</View>
@ -168,8 +166,7 @@ export const ModerationBlockedAccounts = withAuthRequired(
)}
</CenteredView>
)
},
)
}
const styles = StyleSheet.create({
container: {

View File

@ -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,8 +16,7 @@ import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'>
export const ModerationModlistsScreen = withAuthRequired(
function ModerationModlistsScreenImpl({}: Props) {
export function ModerationModlistsScreen({}: Props) {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {isMobile} = useWebMediaQueries()
@ -82,5 +80,4 @@ export const ModerationModlistsScreen = withAuthRequired(
<MyLists filter="mod" style={s.flexGrow1} />
</View>
)
},
)
}

View File

@ -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,8 +29,7 @@ type Props = NativeStackScreenProps<
CommonNavigatorParams,
'ModerationMutedAccounts'
>
export const ModerationMutedAccounts = withAuthRequired(
function ModerationMutedAccountsImpl({}: Props) {
export function ModerationMutedAccounts({}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
@ -130,9 +128,9 @@ export const ModerationMutedAccounts = withAuthRequired(
<View style={[styles.empty, pal.viewLight]}>
<Text type="lg" style={[pal.text, styles.emptyText]}>
<Trans>
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.
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.
</Trans>
</Text>
</View>
@ -167,8 +165,7 @@ export const ModerationMutedAccounts = withAuthRequired(
)}
</CenteredView>
)
},
)
}
const styles = StyleSheet.create({
container: {

View File

@ -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,8 +27,7 @@ type Props = NativeStackScreenProps<
NotificationsTabNavigatorParams,
'Notifications'
>
export const NotificationsScreen = withAuthRequired(
function NotificationsScreenImpl({}: Props) {
export function NotificationsScreen({}: Props) {
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll()
@ -125,5 +123,4 @@ export const NotificationsScreen = withAuthRequired(
)}
</View>
)
},
)
}

View File

@ -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,8 +10,7 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'>
export const PostLikedByScreen = withAuthRequired(
({route}: Props) => {
export const PostLikedByScreen = ({route}: Props) => {
const setMinimalShellMode = useSetMinimalShellMode()
const {name, rkey} = route.params
const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
@ -30,6 +28,4 @@ export const PostLikedByScreen = withAuthRequired(
<PostLikedByComponent uri={uri} />
</View>
)
},
{isPublic: true},
)
}

View File

@ -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,8 +10,7 @@ import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'>
export const PostRepostedByScreen = withAuthRequired(
({route}: Props) => {
export const PostRepostedByScreen = ({route}: Props) => {
const {name, rkey} = route.params
const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
const setMinimalShellMode = useSetMinimalShellMode()
@ -30,6 +28,4 @@ export const PostRepostedByScreen = withAuthRequired(
<PostRepostedByComponent uri={uri} />
</View>
)
},
{isPublic: true},
)
}

View File

@ -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,8 +26,7 @@ import {CenteredView} from '../com/util/Views'
import {useComposerControls} from '#/state/shell/composer'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
export const PostThreadScreen = withAuthRequired(
function PostThreadScreenImpl({route}: Props) {
export function PostThreadScreen({route}: Props) {
const queryClient = useQueryClient()
const {_} = useLingui()
const {fabMinimalShellTransform} = useMinimalShellMode()
@ -103,9 +101,7 @@ export const PostThreadScreen = withAuthRequired(
)}
</View>
)
},
{isPublic: true},
)
}
const styles = StyleSheet.create({
prompt: {

View File

@ -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,8 +42,7 @@ interface SectionRef {
}
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
export const ProfileScreen = withAuthRequired(
function ProfileScreenImpl({route}: Props) {
export function ProfileScreen({route}: Props) {
const {currentAccount} = useSession()
const name =
route.params.name === 'me' ? currentAccount?.did : route.params.name
@ -115,11 +113,7 @@ export const ProfileScreen = withAuthRequired(
/>
</CenteredView>
)
},
{
isPublic: true,
},
)
}
function ProfileScreenLoaded({
profile: profileUnshadowed,

View File

@ -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,8 +68,7 @@ interface SectionRef {
}
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'>
export const ProfileFeedScreen = withAuthRequired(
function ProfileFeedScreenImpl(props: Props) {
export function ProfileFeedScreen(props: Props) {
const {rkey, name: handleOrDid} = props.route.params
const pal = usePalette('default')
@ -128,11 +126,7 @@ export const ProfileFeedScreen = withAuthRequired(
</View>
</CenteredView>
)
},
{
isPublic: true,
},
)
}
function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) {
const {data: preferences} = usePreferencesQuery()

View File

@ -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,8 +10,7 @@ import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeedLikedBy'>
export const ProfileFeedLikedByScreen = withAuthRequired(
({route}: Props) => {
export const ProfileFeedLikedByScreen = ({route}: Props) => {
const setMinimalShellMode = useSetMinimalShellMode()
const {name, rkey} = route.params
const uri = makeRecordUri(name, 'app.bsky.feed.generator', rkey)
@ -30,6 +28,4 @@ export const ProfileFeedLikedByScreen = withAuthRequired(
<PostLikedByComponent uri={uri} />
</View>
)
},
{isPublic: true},
)
}

View File

@ -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,8 +9,7 @@ import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'>
export const ProfileFollowersScreen = withAuthRequired(
({route}: Props) => {
export const ProfileFollowersScreen = ({route}: Props) => {
const {name} = route.params
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
@ -28,6 +26,4 @@ export const ProfileFollowersScreen = withAuthRequired(
<ProfileFollowersComponent name={name} />
</View>
)
},
{isPublic: true},
)
}

View File

@ -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,8 +9,7 @@ import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'>
export const ProfileFollowsScreen = withAuthRequired(
({route}: Props) => {
export const ProfileFollowsScreen = ({route}: Props) => {
const {name} = route.params
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
@ -28,6 +26,4 @@ export const ProfileFollowsScreen = withAuthRequired(
<ProfileFollowsComponent name={name} />
</View>
)
},
{isPublic: true},
)
}

View File

@ -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,8 +63,7 @@ interface SectionRef {
}
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileList'>
export const ProfileListScreen = withAuthRequired(
function ProfileListScreenImpl(props: Props) {
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(),
@ -98,8 +96,7 @@ export const ProfileListScreen = withAuthRequired(
</View>
</CenteredView>
)
},
)
}
function ProfileListScreenLoaded({
route,

View File

@ -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<CommonNavigatorParams, 'SavedFeeds'>
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) {
</ScrollView>
</CenteredView>
)
})
}
function ListItem({
feedUri,

View File

@ -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

View File

@ -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

View File

@ -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<CommonNavigatorParams, 'Settings'>
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) {
</ScrollView>
</View>
)
})
}
function EmailConfirmationNotice() {
const pal = usePalette('default')

View File

@ -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<ParamListBase>,
StackRouterOptions,
StackActionHelpers<ParamListBase>,
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 <LoggedOut />
}
if (showLoggedOut) {
return <LoggedOut onDismiss={() => setShowLoggedOut(false)} />
}
if (onboardingState.isActive) {
return <Onboarding />
}
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 <View />
} else {
return descriptor.render()
}
},
}
}
return (
<NavigationContent>
<NativeStackView
{...rest}
state={state}
navigation={navigation}
descriptors={newDescriptors}
/>
{isWeb && isMobile && <BottomBarWeb />}
{isWeb && !isMobile && (
<>
<DesktopLeftNav />
<DesktopRightNav />
</>
)}
</NavigationContent>
)
}
export const createNativeStackNavigatorWithAuth = createNavigatorFactory<
StackNavigationState<ParamListBase>,
NativeStackNavigationOptionsWithAuth,
NativeStackNavigationEventMap,
typeof NativeStackNavigator
>(NativeStackNavigator)

View File

@ -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<NavigationProp>()
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 (
<View style={[s.hContentRegion, {overflow: 'hidden'}]}>
<View style={s.hContentRegion}>
@ -51,22 +39,9 @@ function ShellInner() {
<FlatNavigator />
</ErrorBoundary>
</View>
{showSideNavs && (
<>
<DesktopLeftNav />
<DesktopRightNav />
</>
)}
<Composer winHeight={0} />
{showBottomBar && <BottomBarWeb />}
<ModalsContainer />
<Lightbox />
{!isDesktop && isDrawerOpen && (
<TouchableOpacity
onPress={() => setDrawerOpen(false)}