diff options
26 files changed, 1049 insertions, 1064 deletions
diff --git a/src/state/models/session.ts b/src/state/models/session.ts index b79283be1..7c4d0931c 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -122,7 +122,9 @@ export class SessionModel { try { return await this.resumeSession(sess) } finally { - this.isResumingSession = false + runInAction(() => { + this.isResumingSession = false + }) } } else { this.rootStore.log.debug( diff --git a/src/view/com/login/CreateAccount.tsx b/src/view/com/auth/CreateAccount.tsx index a24dc4e35..a24dc4e35 100644 --- a/src/view/com/login/CreateAccount.tsx +++ b/src/view/com/auth/CreateAccount.tsx diff --git a/src/view/com/auth/LoggedOut.tsx b/src/view/com/auth/LoggedOut.tsx new file mode 100644 index 000000000..47dd51d9c --- /dev/null +++ b/src/view/com/auth/LoggedOut.tsx @@ -0,0 +1,67 @@ +import React from 'react' +import {SafeAreaView} from 'react-native' +import {observer} from 'mobx-react-lite' +import {Signin} from 'view/com/auth/Signin' +import {CreateAccount} from 'view/com/auth/CreateAccount' +import {ErrorBoundary} from 'view/com/util/ErrorBoundary' +import {s} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' +import {useStores} from 'state/index' +import {useAnalytics} from 'lib/analytics' +import {SplashScreen} from './SplashScreen' +import {CenteredView} from '../util/Views' + +enum ScreenState { + S_SigninOrCreateAccount, + S_Signin, + S_CreateAccount, +} + +export const LoggedOut = observer(() => { + const pal = usePalette('default') + const store = useStores() + const {screen} = useAnalytics() + const [screenState, setScreenState] = React.useState<ScreenState>( + ScreenState.S_SigninOrCreateAccount, + ) + + React.useEffect(() => { + screen('Login') + store.shell.setMinimalShellMode(true) + }, [store, screen]) + + if ( + store.session.isResumingSession || + screenState === ScreenState.S_SigninOrCreateAccount + ) { + return ( + <SplashScreen + onPressSignin={() => setScreenState(ScreenState.S_Signin)} + onPressCreateAccount={() => setScreenState(ScreenState.S_CreateAccount)} + /> + ) + } + + return ( + <CenteredView style={[s.hContentRegion, pal.view]}> + <SafeAreaView testID="noSessionView" style={s.hContentRegion}> + <ErrorBoundary> + {screenState === ScreenState.S_Signin ? ( + <Signin + onPressBack={() => + setScreenState(ScreenState.S_SigninOrCreateAccount) + } + /> + ) : undefined} + {screenState === ScreenState.S_CreateAccount ? ( + <CreateAccount + onPressBack={() => + setScreenState(ScreenState.S_SigninOrCreateAccount) + } + /> + ) : undefined} + </ErrorBoundary> + </SafeAreaView> + </CenteredView> + ) +}) diff --git a/src/view/com/login/Logo.tsx b/src/view/com/auth/Logo.tsx index 7601ea31f..ac408cd2f 100644 --- a/src/view/com/login/Logo.tsx +++ b/src/view/com/auth/Logo.tsx @@ -1,28 +1,19 @@ import React from 'react' -import {StyleSheet} from 'react-native' -import LinearGradient from 'react-native-linear-gradient' -import {s, gradients} from 'lib/styles' +import {StyleSheet, View} from 'react-native' +import {s, colors} from 'lib/styles' import {Text} from '../util/text/Text' export const LogoTextHero = () => { return ( - <LinearGradient - colors={[gradients.blue.start, gradients.blue.end]} - start={{x: 0, y: 0}} - end={{x: 1, y: 1}} - style={[styles.textHero]}> + <View style={[styles.textHero]}> <Text type="title-lg" style={[s.white, s.bold]}> Bluesky </Text> - </LinearGradient> + </View> ) } const styles = StyleSheet.create({ - logo: { - flexDirection: 'row', - justifyContent: 'center', - }, textHero: { flexDirection: 'row', alignItems: 'center', @@ -30,5 +21,6 @@ const styles = StyleSheet.create({ paddingRight: 20, paddingVertical: 15, marginBottom: 20, + backgroundColor: colors.blue3, }, }) diff --git a/src/view/com/login/Signin.tsx b/src/view/com/auth/Signin.tsx index 6faf5ff12..6faf5ff12 100644 --- a/src/view/com/login/Signin.tsx +++ b/src/view/com/auth/Signin.tsx diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx new file mode 100644 index 000000000..27943f64d --- /dev/null +++ b/src/view/com/auth/SplashScreen.tsx @@ -0,0 +1,92 @@ +import React from 'react' +import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native' +import Image, {Source as ImageSource} from 'view/com/util/images/Image' +import {Text} from 'view/com/util/text/Text' +import {ErrorBoundary} from 'view/com/util/ErrorBoundary' +import {colors} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' +import {CLOUD_SPLASH} from 'lib/assets' +import {CenteredView} from '../util/Views' + +export const SplashScreen = ({ + onPressSignin, + onPressCreateAccount, +}: { + onPressSignin: () => void + onPressCreateAccount: () => void +}) => { + const pal = usePalette('default') + return ( + <CenteredView style={styles.container}> + <Image source={CLOUD_SPLASH as ImageSource} style={styles.bgImg} /> + <SafeAreaView testID="noSessionView" style={styles.container}> + <ErrorBoundary> + <View style={styles.hero}> + <View style={styles.heroText}> + <Text style={styles.title}>Bluesky</Text> + </View> + </View> + <View testID="signinOrCreateAccount" style={styles.btns}> + <TouchableOpacity + testID="createAccountButton" + style={[pal.view, styles.btn]} + onPress={onPressCreateAccount}> + <Text style={[pal.link, styles.btnLabel]}> + Create a new account + </Text> + </TouchableOpacity> + <TouchableOpacity + testID="signInButton" + style={[pal.view, styles.btn]} + onPress={onPressSignin}> + <Text style={[pal.link, styles.btnLabel]}>Sign in</Text> + </TouchableOpacity> + </View> + </ErrorBoundary> + </SafeAreaView> + </CenteredView> + ) +} + +const styles = StyleSheet.create({ + container: { + height: '100%', + }, + hero: { + flex: 2, + justifyContent: 'center', + }, + bgImg: { + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + }, + heroText: { + backgroundColor: colors.white, + paddingTop: 10, + paddingBottom: 20, + }, + btns: { + paddingBottom: 40, + }, + title: { + textAlign: 'center', + color: colors.blue3, + fontSize: 68, + fontWeight: 'bold', + }, + btn: { + borderRadius: 4, + paddingVertical: 16, + marginBottom: 20, + marginHorizontal: 20, + backgroundColor: colors.blue3, + }, + btnLabel: { + textAlign: 'center', + fontSize: 21, + color: colors.white, + }, +}) diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx new file mode 100644 index 000000000..05d0355d9 --- /dev/null +++ b/src/view/com/auth/SplashScreen.web.tsx @@ -0,0 +1,102 @@ +import React from 'react' +import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {Text} from 'view/com/util/text/Text' +import {TextLink} from '../util/Link' +import {ErrorBoundary} from 'view/com/util/ErrorBoundary' +import {s, colors} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' +import {CenteredView} from '../util/Views' + +export const SplashScreen = ({ + onPressSignin, + onPressCreateAccount, +}: { + onPressSignin: () => void + onPressCreateAccount: () => void +}) => { + const pal = usePalette('default') + return ( + <CenteredView style={styles.container}> + <View testID="noSessionView" style={styles.containerInner}> + <ErrorBoundary> + <Text style={styles.title}>Bluesky</Text> + <Text style={styles.subtitle}>See what's next</Text> + <View testID="signinOrCreateAccount" style={styles.btns}> + <TouchableOpacity + testID="createAccountButton" + style={[styles.btn, {backgroundColor: colors.blue3}]} + onPress={onPressCreateAccount}> + <Text style={[s.white, styles.btnLabel]}> + Create a new account + </Text> + </TouchableOpacity> + <TouchableOpacity + testID="signInButton" + style={[styles.btn, pal.btn]} + onPress={onPressSignin}> + <Text style={[pal.link, styles.btnLabel]}>Sign in</Text> + </TouchableOpacity> + </View> + <Text + type="xl" + style={[styles.notice, pal.textLight]} + lineHeight={1.3}> + Bluesky will launch soon.{' '} + <TextLink + type="xl" + text="Join the waitlist" + href="#" + style={pal.link} + />{' '} + to try the beta before it's publicly available. + </Text> + </ErrorBoundary> + </View> + </CenteredView> + ) +} + +const styles = StyleSheet.create({ + container: { + height: '100%', + backgroundColor: colors.gray1, + }, + containerInner: { + backgroundColor: colors.white, + paddingVertical: 40, + paddingBottom: 50, + paddingHorizontal: 20, + }, + title: { + textAlign: 'center', + color: colors.blue3, + fontSize: 68, + fontWeight: 'bold', + paddingBottom: 10, + }, + subtitle: { + textAlign: 'center', + color: colors.gray5, + fontSize: 52, + fontWeight: 'bold', + paddingBottom: 30, + }, + btns: { + flexDirection: 'row', + paddingBottom: 40, + }, + btn: { + flex: 1, + borderRadius: 30, + paddingVertical: 12, + marginHorizontal: 10, + }, + btnLabel: { + textAlign: 'center', + fontSize: 18, + }, + notice: { + paddingHorizontal: 40, + textAlign: 'center', + }, +}) diff --git a/src/view/com/auth/withAuthRequired.tsx b/src/view/com/auth/withAuthRequired.tsx new file mode 100644 index 000000000..11b67f383 --- /dev/null +++ b/src/view/com/auth/withAuthRequired.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import {ActivityIndicator, StyleSheet, View} from 'react-native' +import {observer} from 'mobx-react-lite' +import {useStores} from 'state/index' +import {LoggedOut} from './LoggedOut' +import {Text} from '../util/text/Text' +import {usePalette} from 'lib/hooks/usePalette' + +export const withAuthRequired = <P extends object>( + Component: React.ComponentType<P>, +): React.FC<P> => + observer((props: P) => { + const store = useStores() + if (store.session.isResumingSession) { + return <Loading /> + } + if (!store.session.hasSession) { + return <LoggedOut /> + } + return <Component {...props} /> + }) + +function Loading() { + const pal = usePalette('default') + return ( + <View style={[styles.loading, pal.view]}> + <ActivityIndicator size="large" /> + <Text type="2xl" style={[styles.loadingText, pal.textLight]}> + Firing up the grill... + </Text> + </View> + ) +} + +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/Home.tsx b/src/view/screens/Home.tsx index 505b1fcfe..adc73315c 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -4,6 +4,7 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native' import {observer} from 'mobx-react-lite' import useAppState from 'react-native-appstate-hook' import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types' +import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewHeader} from '../com/util/ViewHeader' import {Feed} from '../com/posts/Feed' import {LoadLatestBtn} from '../com/util/LoadLatestBtn' @@ -18,95 +19,97 @@ import {ComposeIcon2} from 'lib/icons' const HEADER_HEIGHT = 42 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> -export const HomeScreen = observer(function Home(_opts: Props) { - const store = useStores() - const onMainScroll = useOnMainScroll(store) - const {screen, track} = useAnalytics() - const scrollElRef = React.useRef<FlatList>(null) - const {appState} = useAppState({ - onForeground: () => doPoll(true), - }) - const isFocused = useIsFocused() +export const HomeScreen = withAuthRequired( + observer(function Home(_opts: Props) { + const store = useStores() + const onMainScroll = useOnMainScroll(store) + const {screen, track} = useAnalytics() + const scrollElRef = React.useRef<FlatList>(null) + const {appState} = useAppState({ + onForeground: () => doPoll(true), + }) + const isFocused = useIsFocused() - const doPoll = React.useCallback( - (knownActive = false) => { - if ((!knownActive && appState !== 'active') || !isFocused) { - return - } - if (store.me.mainFeed.isLoading) { - return - } - store.log.debug('HomeScreen: Polling for new posts') - store.me.mainFeed.checkForLatest() - }, - [appState, isFocused, store], - ) + const doPoll = React.useCallback( + (knownActive = false) => { + if ((!knownActive && appState !== 'active') || !isFocused) { + return + } + if (store.me.mainFeed.isLoading) { + return + } + store.log.debug('HomeScreen: Polling for new posts') + store.me.mainFeed.checkForLatest() + }, + [appState, isFocused, store], + ) - const scrollToTop = React.useCallback(() => { - // NOTE: the feed is offset by the height of the collapsing header, - // so we scroll to the negative of that height -prf - scrollElRef.current?.scrollToOffset({offset: -HEADER_HEIGHT}) - }, [scrollElRef]) + const scrollToTop = React.useCallback(() => { + // NOTE: the feed is offset by the height of the collapsing header, + // so we scroll to the negative of that height -prf + scrollElRef.current?.scrollToOffset({offset: -HEADER_HEIGHT}) + }, [scrollElRef]) - useFocusEffect( - React.useCallback(() => { - const softResetSub = store.onScreenSoftReset(scrollToTop) - const feedCleanup = store.me.mainFeed.registerListeners() - const pollInterval = setInterval(doPoll, 15e3) + useFocusEffect( + React.useCallback(() => { + const softResetSub = store.onScreenSoftReset(scrollToTop) + const feedCleanup = store.me.mainFeed.registerListeners() + const pollInterval = setInterval(doPoll, 15e3) - screen('Feed') - store.log.debug('HomeScreen: Updating feed') - if (store.me.mainFeed.hasContent) { - store.me.mainFeed.update() - } + screen('Feed') + store.log.debug('HomeScreen: Updating feed') + if (store.me.mainFeed.hasContent) { + store.me.mainFeed.update() + } - return () => { - clearInterval(pollInterval) - softResetSub.remove() - feedCleanup() - } - }, [store, doPoll, scrollToTop, screen]), - ) + return () => { + clearInterval(pollInterval) + softResetSub.remove() + feedCleanup() + } + }, [store, doPoll, scrollToTop, screen]), + ) - const onPressCompose = React.useCallback(() => { - track('HomeScreen:PressCompose') - store.shell.openComposer({}) - }, [store, track]) + const onPressCompose = React.useCallback(() => { + track('HomeScreen:PressCompose') + store.shell.openComposer({}) + }, [store, track]) - const onPressTryAgain = React.useCallback(() => { - store.me.mainFeed.refresh() - }, [store]) + const onPressTryAgain = React.useCallback(() => { + store.me.mainFeed.refresh() + }, [store]) - const onPressLoadLatest = React.useCallback(() => { - store.me.mainFeed.refresh() - scrollToTop() - }, [store, scrollToTop]) + const onPressLoadLatest = React.useCallback(() => { + store.me.mainFeed.refresh() + scrollToTop() + }, [store, scrollToTop]) - return ( - <View style={s.hContentRegion}> - {store.shell.isOnboarding && <WelcomeBanner />} - <Feed - testID="homeFeed" - key="default" - feed={store.me.mainFeed} - scrollElRef={scrollElRef} - style={s.hContentRegion} - showPostFollowBtn - onPressTryAgain={onPressTryAgain} - onScroll={onMainScroll} - headerOffset={store.shell.isOnboarding ? 0 : HEADER_HEIGHT} - /> - {!store.shell.isOnboarding && ( - <ViewHeader title="Bluesky" canGoBack={false} hideOnScroll /> - )} - {store.me.mainFeed.hasNewLatest && !store.me.mainFeed.isRefreshing && ( - <LoadLatestBtn onPress={onPressLoadLatest} /> - )} - <FAB - testID="composeFAB" - onPress={onPressCompose} - icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />} - /> - </View> - ) -}) + return ( + <View style={s.hContentRegion}> + {store.shell.isOnboarding && <WelcomeBanner />} + <Feed + testID="homeFeed" + key="default" + feed={store.me.mainFeed} + scrollElRef={scrollElRef} + style={s.hContentRegion} + showPostFollowBtn + onPressTryAgain={onPressTryAgain} + onScroll={onMainScroll} + headerOffset={store.shell.isOnboarding ? 0 : HEADER_HEIGHT} + /> + {!store.shell.isOnboarding && ( + <ViewHeader title="Bluesky" canGoBack={false} hideOnScroll /> + )} + {store.me.mainFeed.hasNewLatest && !store.me.mainFeed.isRefreshing && ( + <LoadLatestBtn onPress={onPressLoadLatest} /> + )} + <FAB + testID="composeFAB" + onPress={onPressCompose} + icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />} + /> + </View> + ) + }), +) diff --git a/src/view/screens/Login.tsx b/src/view/screens/Login.tsx deleted file mode 100644 index 8ff9fc9c2..000000000 --- a/src/view/screens/Login.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import React, {useEffect, useState} from 'react' -import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native' -import Image, {Source as ImageSource} from 'view/com/util/images/Image' -import {observer} from 'mobx-react-lite' -import {Signin} from '../com/login/Signin' -import {CreateAccount} from '../com/login/CreateAccount' -import {Text} from '../com/util/text/Text' -import {ErrorBoundary} from '../com/util/ErrorBoundary' -import {colors} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {useStores} from 'state/index' -import {CLOUD_SPLASH} from 'lib/assets' -import {useAnalytics} from 'lib/analytics' - -enum ScreenState { - S_SigninOrCreateAccount, - S_Signin, - S_CreateAccount, -} - -const SigninOrCreateAccount = ({ - onPressSignin, - onPressCreateAccount, -}: { - onPressSignin: () => void - onPressCreateAccount: () => void -}) => { - const {screen} = useAnalytics() - - useEffect(() => { - screen('Login') - }, [screen]) - - const pal = usePalette('default') - return ( - <> - <View style={styles.hero}> - <View style={styles.heroText}> - <Text style={styles.title}>Bluesky</Text> - </View> - </View> - <View testID="signinOrCreateAccount" style={styles.btns}> - <TouchableOpacity - testID="createAccountButton" - style={[pal.view, styles.btn]} - onPress={onPressCreateAccount}> - <Text style={[pal.link, styles.btnLabel]}>Create a new account</Text> - </TouchableOpacity> - <TouchableOpacity - testID="signInButton" - style={[pal.view, styles.btn]} - onPress={onPressSignin}> - <Text style={[pal.link, styles.btnLabel]}>Sign in</Text> - </TouchableOpacity> - </View> - </> - ) -} - -export const Login = observer(() => { - const pal = usePalette('default') - const store = useStores() - const [screenState, setScreenState] = useState<ScreenState>( - ScreenState.S_SigninOrCreateAccount, - ) - - if ( - store.session.isResumingSession || - screenState === ScreenState.S_SigninOrCreateAccount - ) { - return ( - <View style={styles.container}> - <Image source={CLOUD_SPLASH as ImageSource} style={styles.bgImg} /> - <SafeAreaView testID="noSessionView" style={styles.container}> - <ErrorBoundary> - {!store.session.isResumingSession && ( - <SigninOrCreateAccount - onPressSignin={() => setScreenState(ScreenState.S_Signin)} - onPressCreateAccount={() => - setScreenState(ScreenState.S_CreateAccount) - } - /> - )} - </ErrorBoundary> - </SafeAreaView> - </View> - ) - } - - return ( - <View style={[styles.container, pal.view]}> - <SafeAreaView testID="noSessionView" style={styles.container}> - <ErrorBoundary> - {screenState === ScreenState.S_Signin ? ( - <Signin - onPressBack={() => - setScreenState(ScreenState.S_SigninOrCreateAccount) - } - /> - ) : undefined} - {screenState === ScreenState.S_CreateAccount ? ( - <CreateAccount - onPressBack={() => - setScreenState(ScreenState.S_SigninOrCreateAccount) - } - /> - ) : undefined} - </ErrorBoundary> - </SafeAreaView> - </View> - ) -}) - -const styles = StyleSheet.create({ - container: { - height: '100%', - }, - outer: { - flex: 1, - }, - hero: { - flex: 2, - justifyContent: 'center', - }, - bgImg: { - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - }, - heroText: { - backgroundColor: colors.white, - paddingTop: 10, - paddingBottom: 20, - }, - btns: { - paddingBottom: 40, - }, - title: { - textAlign: 'center', - color: colors.blue3, - fontSize: 68, - fontWeight: 'bold', - }, - subtitle: { - textAlign: 'center', - color: colors.blue3, - fontSize: 18, - }, - btn: { - borderRadius: 4, - paddingVertical: 16, - marginBottom: 20, - marginHorizontal: 20, - backgroundColor: colors.blue3, - }, - btnLabel: { - textAlign: 'center', - fontSize: 21, - // fontWeight: '500', - color: colors.white, - }, -}) diff --git a/src/view/screens/Login.web.tsx b/src/view/screens/Login.web.tsx deleted file mode 100644 index 1980d8dc5..000000000 --- a/src/view/screens/Login.web.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import React, {useState} from 'react' -import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native' -import {observer} from 'mobx-react-lite' -import {CenteredView} from '../com/util/Views' -import {Signin} from '../com/login/Signin' -import {CreateAccount} from '../com/login/CreateAccount' -import {Text} from '../com/util/text/Text' -import {ErrorBoundary} from '../com/util/ErrorBoundary' -import {colors} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' - -enum ScreenState { - S_SigninOrCreateAccount, - S_Signin, - S_CreateAccount, -} - -const SigninOrCreateAccount = ({ - onPressSignin, - onPressCreateAccount, -}: { - onPressSignin: () => void - onPressCreateAccount: () => void -}) => { - const pal = usePalette('default') - return ( - <> - <View style={styles.hero}> - <View style={styles.heroText}> - <Text style={styles.title}>Bluesky</Text> - </View> - </View> - <View testID="signinOrCreateAccount" style={styles.btns}> - <TouchableOpacity - testID="createAccountButton" - style={[pal.view, styles.btn]} - onPress={onPressCreateAccount}> - <Text style={[pal.link, styles.btnLabel]}>New account</Text> - </TouchableOpacity> - <TouchableOpacity - testID="signInButton" - style={[pal.view, styles.btn]} - onPress={onPressSignin}> - <Text style={[pal.link, styles.btnLabel]}>Sign in</Text> - </TouchableOpacity> - </View> - </> - ) -} - -export const Login = observer(() => { - const pal = usePalette('default') - const [screenState, setScreenState] = useState<ScreenState>( - ScreenState.S_SigninOrCreateAccount, - ) - - if (screenState === ScreenState.S_SigninOrCreateAccount) { - return ( - <CenteredView style={[styles.container, styles.vertCenter]}> - <ErrorBoundary> - <SigninOrCreateAccount - onPressSignin={() => setScreenState(ScreenState.S_Signin)} - onPressCreateAccount={() => - setScreenState(ScreenState.S_CreateAccount) - } - /> - </ErrorBoundary> - </CenteredView> - ) - } - - return ( - <CenteredView - style={[ - styles.container, - styles.containerBorder, - pal.view, - pal.borderDark, - ]}> - <SafeAreaView testID="noSessionView" style={styles.container}> - <ErrorBoundary> - {screenState === ScreenState.S_Signin ? ( - <Signin - onPressBack={() => - setScreenState(ScreenState.S_SigninOrCreateAccount) - } - /> - ) : undefined} - {screenState === ScreenState.S_CreateAccount ? ( - <CreateAccount - onPressBack={() => - setScreenState(ScreenState.S_SigninOrCreateAccount) - } - /> - ) : undefined} - </ErrorBoundary> - </SafeAreaView> - </CenteredView> - ) -}) - -const styles = StyleSheet.create({ - container: { - height: '100%', - }, - containerBorder: { - borderLeftWidth: 1, - borderRightWidth: 1, - }, - vertCenter: { - justifyContent: 'center', - }, - bgImg: { - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - }, - hero: {}, - heroText: { - backgroundColor: colors.white, - paddingTop: 10, - paddingBottom: 20, - }, - btns: { - flexDirection: 'row', - paddingTop: 40, - }, - title: { - textAlign: 'center', - color: colors.blue3, - fontSize: 68, - fontWeight: 'bold', - }, - subtitle: { - textAlign: 'center', - color: colors.blue3, - fontSize: 18, - }, - btn: { - flex: 1, - borderRadius: 4, - paddingVertical: 16, - marginBottom: 20, - marginHorizontal: 20, - borderWidth: 1, - borderColor: colors.blue3, - }, - btnLabel: { - textAlign: 'center', - fontSize: 21, - fontWeight: '500', - color: colors.blue3, - }, -}) diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx index 492177d1f..b704f9c45 100644 --- a/src/view/screens/Notifications.tsx +++ b/src/view/screens/Notifications.tsx @@ -1,11 +1,13 @@ import React, {useEffect} from 'react' import {FlatList, View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' +import {observer} from 'mobx-react-lite' import useAppState from 'react-native-appstate-hook' 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 {useStores} from 'state/index' @@ -19,77 +21,79 @@ type Props = NativeStackScreenProps< NotificationsTabNavigatorParams, 'Notifications' > -export const NotificationsScreen = ({}: Props) => { - const store = useStores() - const onMainScroll = useOnMainScroll(store) - const scrollElRef = React.useRef<FlatList>(null) - const {screen} = useAnalytics() - const {appState} = useAppState({ - onForeground: () => doPoll(true), - }) +export const NotificationsScreen = withAuthRequired( + observer(({}: Props) => { + const store = useStores() + const onMainScroll = useOnMainScroll(store) + const scrollElRef = React.useRef<FlatList>(null) + const {screen} = useAnalytics() + const {appState} = useAppState({ + onForeground: () => doPoll(true), + }) - // event handlers - // = - const onPressTryAgain = () => { - store.me.notifications.refresh() - } - const scrollToTop = React.useCallback(() => { - scrollElRef.current?.scrollToOffset({offset: 0}) - }, [scrollElRef]) + // event handlers + // = + const onPressTryAgain = () => { + store.me.notifications.refresh() + } + const scrollToTop = React.useCallback(() => { + scrollElRef.current?.scrollToOffset({offset: 0}) + }, [scrollElRef]) - // periodic polling - // = - const doPoll = React.useCallback( - async (isForegrounding = false) => { - if (isForegrounding) { - // app is foregrounding, refresh optimistically - store.log.debug('NotificationsScreen: Refreshing on app foreground') - await Promise.all([ - store.me.notifications.loadUnreadCount(), - store.me.notifications.refresh(), - ]) - } else if (appState === 'active') { - // periodic poll, refresh if there are new notifs - store.log.debug('NotificationsScreen: Polling for new notifications') - const didChange = await store.me.notifications.loadUnreadCount() - if (didChange) { - store.log.debug('NotificationsScreen: Loading new notifications') - await store.me.notifications.loadLatest() + // periodic polling + // = + const doPoll = React.useCallback( + async (isForegrounding = false) => { + if (isForegrounding) { + // app is foregrounding, refresh optimistically + store.log.debug('NotificationsScreen: Refreshing on app foreground') + await Promise.all([ + store.me.notifications.loadUnreadCount(), + store.me.notifications.refresh(), + ]) + } else if (appState === 'active') { + // periodic poll, refresh if there are new notifs + store.log.debug('NotificationsScreen: Polling for new notifications') + const didChange = await store.me.notifications.loadUnreadCount() + if (didChange) { + store.log.debug('NotificationsScreen: Loading new notifications') + await store.me.notifications.loadLatest() + } } - } - }, - [appState, store], - ) - useEffect(() => { - const pollInterval = setInterval(doPoll, NOTIFICATIONS_POLL_INTERVAL) - return () => clearInterval(pollInterval) - }, [doPoll]) + }, + [appState, store], + ) + useEffect(() => { + const pollInterval = setInterval(doPoll, NOTIFICATIONS_POLL_INTERVAL) + return () => clearInterval(pollInterval) + }, [doPoll]) - // on-visible setup - // = - useFocusEffect( - React.useCallback(() => { - store.log.debug('NotificationsScreen: Updating feed') - const softResetSub = store.onScreenSoftReset(scrollToTop) - store.me.notifications.update() - screen('Notifications') + // on-visible setup + // = + useFocusEffect( + React.useCallback(() => { + store.log.debug('NotificationsScreen: Updating feed') + const softResetSub = store.onScreenSoftReset(scrollToTop) + store.me.notifications.update() + screen('Notifications') - return () => { - softResetSub.remove() - store.me.notifications.markAllRead() - } - }, [store, screen, scrollToTop]), - ) + return () => { + softResetSub.remove() + store.me.notifications.markAllRead() + } + }, [store, screen, scrollToTop]), + ) - return ( - <View style={s.hContentRegion}> - <ViewHeader title="Notifications" canGoBack={false} /> - <Feed - view={store.me.notifications} - onPressTryAgain={onPressTryAgain} - onScroll={onMainScroll} - scrollElRef={scrollElRef} - /> - </View> - ) -} + return ( + <View style={s.hContentRegion}> + <ViewHeader title="Notifications" canGoBack={false} /> + <Feed + view={store.me.notifications} + onPressTryAgain={onPressTryAgain} + onScroll={onMainScroll} + scrollElRef={scrollElRef} + /> + </View> + ) + }), +) diff --git a/src/view/screens/PostRepostedBy.tsx b/src/view/screens/PostRepostedBy.tsx index 1a63445e5..19f0af18b 100644 --- a/src/view/screens/PostRepostedBy.tsx +++ b/src/view/screens/PostRepostedBy.tsx @@ -1,6 +1,7 @@ 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' @@ -8,7 +9,7 @@ import {useStores} from 'state/index' import {makeRecordUri} from 'lib/strings/url-helpers' type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'> -export const PostRepostedByScreen = ({route}: Props) => { +export const PostRepostedByScreen = withAuthRequired(({route}: Props) => { const store = useStores() const {name, rkey} = route.params const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) @@ -25,4 +26,4 @@ export const PostRepostedByScreen = ({route}: Props) => { <PostRepostedByComponent uri={uri} /> </View> ) -} +}) diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index 0e9feae0b..ad54126b6 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -3,6 +3,7 @@ import {StyleSheet, View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' 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' @@ -16,7 +17,7 @@ import {isDesktopWeb} from 'platform/detection' const SHELL_FOOTER_HEIGHT = 44 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'> -export const PostThreadScreen = ({route}: Props) => { +export const PostThreadScreen = withAuthRequired(({route}: Props) => { const store = useStores() const safeAreaInsets = useSafeAreaInsets() const {name, rkey} = route.params @@ -84,7 +85,7 @@ export const PostThreadScreen = ({route}: Props) => { )} </View> ) -} +}) const styles = StyleSheet.create({ prompt: { diff --git a/src/view/screens/PostUpvotedBy.tsx b/src/view/screens/PostUpvotedBy.tsx index b1690721b..35b55f3c4 100644 --- a/src/view/screens/PostUpvotedBy.tsx +++ b/src/view/screens/PostUpvotedBy.tsx @@ -2,13 +2,14 @@ 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 {PostVotedBy as PostLikedByComponent} from '../com/post-thread/PostVotedBy' import {useStores} from 'state/index' import {makeRecordUri} from 'lib/strings/url-helpers' type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostUpvotedBy'> -export const PostUpvotedByScreen = ({route}: Props) => { +export const PostUpvotedByScreen = withAuthRequired(({route}: Props) => { const store = useStores() const {name, rkey} = route.params const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) @@ -25,4 +26,4 @@ export const PostUpvotedByScreen = ({route}: Props) => { <PostLikedByComponent uri={uri} direction="up" /> </View> ) -} +}) diff --git a/src/view/screens/PrivacyPolicy.tsx b/src/view/screens/PrivacyPolicy.tsx index d5476ab52..ec39ac2d8 100644 --- a/src/view/screens/PrivacyPolicy.tsx +++ b/src/view/screens/PrivacyPolicy.tsx @@ -26,7 +26,7 @@ export const PrivacyPolicyScreen = (_props: Props) => { <ViewHeader title="Privacy Policy" /> <ScrollView style={[s.hContentRegion, pal.view]}> <View style={[s.p20]}> - <Text type="title-xl" style={[pal.text, s.pb20]}> + <Text type="title-xl" style={[pal.text, s.bold, s.pb20]}> Privacy Policy </Text> <PrivacyPolicyHtml /> diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index e0d0a5884..b5073f28d 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -3,6 +3,7 @@ import {ActivityIndicator, StyleSheet, View} from 'react-native' import {observer} from 'mobx-react-lite' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' +import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ViewSelector} from '../com/util/ViewSelector' import {CenteredView} from '../com/util/Views' import {ProfileUiModel, Sections} from 'state/models/profile-ui' @@ -25,178 +26,182 @@ const END_ITEM = {_reactKey: '__end__'} const EMPTY_ITEM = {_reactKey: '__empty__'} type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'> -export const ProfileScreen = observer(({route}: Props) => { - const store = useStores() - const {screen, track} = useAnalytics() +export const ProfileScreen = withAuthRequired( + observer(({route}: Props) => { + const store = useStores() + const {screen, track} = useAnalytics() - useEffect(() => { - screen('Profile') - }, [screen]) + useEffect(() => { + screen('Profile') + }, [screen]) - const onMainScroll = useOnMainScroll(store) - const [hasSetup, setHasSetup] = useState<boolean>(false) - const uiState = React.useMemo( - () => new ProfileUiModel(store, {user: route.params.name}), - [route.params.name, store], - ) + const onMainScroll = useOnMainScroll(store) + const [hasSetup, setHasSetup] = useState<boolean>(false) + const uiState = React.useMemo( + () => new ProfileUiModel(store, {user: route.params.name}), + [route.params.name, store], + ) - useFocusEffect( - React.useCallback(() => { - let aborted = false - const feedCleanup = uiState.feed.registerListeners() - if (hasSetup) { - uiState.update() - } else { - uiState.setup().then(() => { - if (aborted) { - return - } - setHasSetup(true) - }) - } - return () => { - aborted = true - feedCleanup() - } - }, [hasSetup, uiState]), - ) + useFocusEffect( + React.useCallback(() => { + let aborted = false + const feedCleanup = uiState.feed.registerListeners() + if (hasSetup) { + uiState.update() + } else { + uiState.setup().then(() => { + if (aborted) { + return + } + setHasSetup(true) + }) + } + return () => { + aborted = true + feedCleanup() + } + }, [hasSetup, uiState]), + ) - // events - // = + // events + // = - const onPressCompose = React.useCallback(() => { - track('ProfileScreen:PressCompose') - store.shell.openComposer({}) - }, [store, track]) - const onSelectView = (index: number) => { - uiState.setSelectedViewIndex(index) - } - const onRefresh = () => { - uiState - .refresh() - .catch((err: any) => - store.log.error('Failed to refresh user profile', err), - ) - } - const onEndReached = () => { - uiState - .loadMore() - .catch((err: any) => - store.log.error('Failed to load more entries in user profile', err), - ) - } - const onPressTryAgain = () => { - uiState.setup() - } + const onPressCompose = React.useCallback(() => { + track('ProfileScreen:PressCompose') + store.shell.openComposer({}) + }, [store, track]) + const onSelectView = (index: number) => { + uiState.setSelectedViewIndex(index) + } + const onRefresh = () => { + uiState + .refresh() + .catch((err: any) => + store.log.error('Failed to refresh user profile', err), + ) + } + const onEndReached = () => { + uiState + .loadMore() + .catch((err: any) => + store.log.error('Failed to load more entries in user profile', err), + ) + } + const onPressTryAgain = () => { + uiState.setup() + } - // rendering - // = + // rendering + // = - const renderHeader = () => { - if (!uiState) { - return <View /> + const renderHeader = () => { + if (!uiState) { + return <View /> + } + return <ProfileHeader view={uiState.profile} onRefreshAll={onRefresh} /> } - return <ProfileHeader view={uiState.profile} onRefreshAll={onRefresh} /> - } - let renderItem - let Footer - let items: any[] = [] - if (uiState) { - if (uiState.isInitialLoading) { - items = items.concat([LOADING_ITEM]) - renderItem = () => <PostFeedLoadingPlaceholder /> - } else if (uiState.currentView.hasError) { - items = items.concat([ - { - _reactKey: '__error__', - error: uiState.currentView.error, - }, - ]) - renderItem = (item: any) => ( - <View style={s.p5}> - <ErrorMessage - message={item.error} - onPressTryAgain={onPressTryAgain} - /> - </View> - ) - } else { - if ( - uiState.selectedView === Sections.Posts || - uiState.selectedView === Sections.PostsWithReplies - ) { - if (uiState.feed.hasContent) { - if (uiState.selectedView === Sections.Posts) { - items = uiState.feed.nonReplyFeed - } else { - items = uiState.feed.feed.slice() - } - if (!uiState.feed.hasMore) { - items = items.concat([END_ITEM]) - } else if (uiState.feed.isLoading) { - Footer = LoadingMoreFooter - } - renderItem = (item: any) => { - if (item === END_ITEM) { - return <Text style={styles.endItem}>- end of feed -</Text> + let renderItem + let Footer + let items: any[] = [] + if (uiState) { + if (uiState.isInitialLoading) { + items = items.concat([LOADING_ITEM]) + renderItem = () => <PostFeedLoadingPlaceholder /> + } else if (uiState.currentView.hasError) { + items = items.concat([ + { + _reactKey: '__error__', + error: uiState.currentView.error, + }, + ]) + renderItem = (item: any) => ( + <View style={s.p5}> + <ErrorMessage + message={item.error} + onPressTryAgain={onPressTryAgain} + /> + </View> + ) + } else { + if ( + uiState.selectedView === Sections.Posts || + uiState.selectedView === Sections.PostsWithReplies + ) { + if (uiState.feed.hasContent) { + if (uiState.selectedView === Sections.Posts) { + items = uiState.feed.nonReplyFeed + } else { + items = uiState.feed.feed.slice() + } + if (!uiState.feed.hasMore) { + items = items.concat([END_ITEM]) + } else if (uiState.feed.isLoading) { + Footer = LoadingMoreFooter } - return <FeedItem item={item} ignoreMuteFor={uiState.profile.did} /> + renderItem = (item: any) => { + if (item === END_ITEM) { + return <Text style={styles.endItem}>- end of feed -</Text> + } + return ( + <FeedItem item={item} ignoreMuteFor={uiState.profile.did} /> + ) + } + } else if (uiState.feed.isEmpty) { + items = items.concat([EMPTY_ITEM]) + renderItem = () => ( + <EmptyState + icon={['far', 'message']} + message="No posts yet!" + style={styles.emptyState} + /> + ) } - } else if (uiState.feed.isEmpty) { + } else { items = items.concat([EMPTY_ITEM]) - renderItem = () => ( - <EmptyState - icon={['far', 'message']} - message="No posts yet!" - style={styles.emptyState} - /> - ) + renderItem = () => <Text>TODO</Text> } - } else { - items = items.concat([EMPTY_ITEM]) - renderItem = () => <Text>TODO</Text> } } - } - if (!renderItem) { - renderItem = () => <View /> - } + if (!renderItem) { + renderItem = () => <View /> + } - return ( - <View testID="profileView" style={styles.container}> - {uiState.profile.hasError ? ( - <ErrorScreen - testID="profileErrorScreen" - title="Failed to load profile" - message={`There was an issue when attempting to load ${route.params.name}`} - details={uiState.profile.error} - onPressTryAgain={onPressTryAgain} - /> - ) : uiState.profile.hasLoaded ? ( - <ViewSelector - swipeEnabled - sections={uiState.selectorItems} - items={items} - renderHeader={renderHeader} - renderItem={renderItem} - ListFooterComponent={Footer} - refreshing={uiState.isRefreshing || false} - onSelectView={onSelectView} - onScroll={onMainScroll} - onRefresh={onRefresh} - onEndReached={onEndReached} + return ( + <View testID="profileView" style={styles.container}> + {uiState.profile.hasError ? ( + <ErrorScreen + testID="profileErrorScreen" + title="Failed to load profile" + message={`There was an issue when attempting to load ${route.params.name}`} + details={uiState.profile.error} + onPressTryAgain={onPressTryAgain} + /> + ) : uiState.profile.hasLoaded ? ( + <ViewSelector + swipeEnabled + sections={uiState.selectorItems} + items={items} + renderHeader={renderHeader} + renderItem={renderItem} + ListFooterComponent={Footer} + refreshing={uiState.isRefreshing || false} + onSelectView={onSelectView} + onScroll={onMainScroll} + onRefresh={onRefresh} + onEndReached={onEndReached} + /> + ) : ( + <CenteredView>{renderHeader()}</CenteredView> + )} + <FAB + testID="composeFAB" + onPress={onPressCompose} + icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />} /> - ) : ( - <CenteredView>{renderHeader()}</CenteredView> - )} - <FAB - testID="composeFAB" - onPress={onPressCompose} - icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />} - /> - </View> - ) -}) + </View> + ) + }), +) function LoadingMoreFooter() { return ( diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx index b248cdc3a..e2f95fbe4 100644 --- a/src/view/screens/ProfileFollowers.tsx +++ b/src/view/screens/ProfileFollowers.tsx @@ -2,12 +2,13 @@ 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 {useStores} from 'state/index' type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'> -export const ProfileFollowersScreen = ({route}: Props) => { +export const ProfileFollowersScreen = withAuthRequired(({route}: Props) => { const store = useStores() const {name} = route.params @@ -23,4 +24,4 @@ export const ProfileFollowersScreen = ({route}: Props) => { <ProfileFollowersComponent name={name} /> </View> ) -} +}) diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx index 7edf8edba..f70944f55 100644 --- a/src/view/screens/ProfileFollows.tsx +++ b/src/view/screens/ProfileFollows.tsx @@ -2,12 +2,13 @@ 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 {useStores} from 'state/index' type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'> -export const ProfileFollowsScreen = ({route}: Props) => { +export const ProfileFollowsScreen = withAuthRequired(({route}: Props) => { const store = useStores() const {name} = route.params @@ -23,4 +24,4 @@ export const ProfileFollowsScreen = ({route}: Props) => { <ProfileFollowsComponent name={name} /> </View> ) -} +}) diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx index a50d5c6a7..19535a164 100644 --- a/src/view/screens/Search.tsx +++ b/src/view/screens/Search.tsx @@ -12,6 +12,7 @@ import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' +import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ScrollView} from '../com/util/Views' import { NativeStackScreenProps, @@ -36,159 +37,161 @@ const MENU_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} const FIVE_MIN = 5 * 60 * 1e3 type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'> -export const SearchScreen = observer<Props>(({}: Props) => { - const pal = usePalette('default') - const theme = useTheme() - const store = useStores() - const {track} = useAnalytics() - const scrollElRef = React.useRef<ScrollView>(null) - const onMainScroll = useOnMainScroll(store) - const textInput = React.useRef<TextInput>(null) - const [lastRenderTime, setRenderTime] = React.useState<number>(Date.now()) // used to trigger reloads - const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false) - const [query, setQuery] = React.useState<string>('') - const autocompleteView = React.useMemo<UserAutocompleteViewModel>( - () => new UserAutocompleteViewModel(store), - [store], - ) +export const SearchScreen = withAuthRequired( + observer<Props>(({}: Props) => { + const pal = usePalette('default') + const theme = useTheme() + const store = useStores() + const {track} = useAnalytics() + const scrollElRef = React.useRef<ScrollView>(null) + const onMainScroll = useOnMainScroll(store) + const textInput = React.useRef<TextInput>(null) + const [lastRenderTime, setRenderTime] = React.useState<number>(Date.now()) // used to trigger reloads + const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false) + const [query, setQuery] = React.useState<string>('') + const autocompleteView = React.useMemo<UserAutocompleteViewModel>( + () => new UserAutocompleteViewModel(store), + [store], + ) - const onSoftReset = () => { - scrollElRef.current?.scrollTo({x: 0, y: 0}) - } + const onSoftReset = () => { + scrollElRef.current?.scrollTo({x: 0, y: 0}) + } - useFocusEffect( - React.useCallback(() => { - const softResetSub = store.onScreenSoftReset(onSoftReset) - const cleanup = () => { - softResetSub.remove() - } + useFocusEffect( + React.useCallback(() => { + const softResetSub = store.onScreenSoftReset(onSoftReset) + const cleanup = () => { + softResetSub.remove() + } - const now = Date.now() - if (now - lastRenderTime > FIVE_MIN) { - setRenderTime(Date.now()) // trigger reload of suggestions - } - store.shell.setMinimalShellMode(false) - autocompleteView.setup() + const now = Date.now() + if (now - lastRenderTime > FIVE_MIN) { + setRenderTime(Date.now()) // trigger reload of suggestions + } + store.shell.setMinimalShellMode(false) + autocompleteView.setup() - return cleanup - }, [store, autocompleteView, lastRenderTime, setRenderTime]), - ) + return cleanup + }, [store, autocompleteView, lastRenderTime, setRenderTime]), + ) - const onPressMenu = () => { - track('ViewHeader:MenuButtonClicked') - store.shell.openDrawer() - } + const onPressMenu = () => { + track('ViewHeader:MenuButtonClicked') + store.shell.openDrawer() + } - const onChangeQuery = (text: string) => { - setQuery(text) - if (text.length > 0) { - autocompleteView.setActive(true) - autocompleteView.setPrefix(text) - } else { + const onChangeQuery = (text: string) => { + setQuery(text) + if (text.length > 0) { + autocompleteView.setActive(true) + autocompleteView.setPrefix(text) + } else { + autocompleteView.setActive(false) + } + } + const onPressClearQuery = () => { + setQuery('') + } + const onPressCancelSearch = () => { + setQuery('') autocompleteView.setActive(false) + textInput.current?.blur() } - } - const onPressClearQuery = () => { - setQuery('') - } - const onPressCancelSearch = () => { - setQuery('') - autocompleteView.setActive(false) - textInput.current?.blur() - } - return ( - <TouchableWithoutFeedback onPress={Keyboard.dismiss}> - <ScrollView - ref={scrollElRef} - testID="searchScrollView" - style={[pal.view, styles.container]} - onScroll={onMainScroll} - scrollEventThrottle={100}> - <View style={[pal.view, pal.border, styles.header]}> - <TouchableOpacity - testID="viewHeaderBackOrMenuBtn" - onPress={onPressMenu} - hitSlop={MENU_HITSLOP} - style={styles.headerMenuBtn}> - <UserAvatar size={30} avatar={store.me.avatar} /> - </TouchableOpacity> - <View - style={[ - {backgroundColor: pal.colors.backgroundLight}, - styles.headerSearchContainer, - ]}> - <MagnifyingGlassIcon - style={[pal.icon, styles.headerSearchIcon]} - size={21} - /> - <TextInput - testID="searchTextInput" - ref={textInput} - placeholder="Search" - placeholderTextColor={pal.colors.textLight} - selectTextOnFocus - returnKeyType="search" - value={query} - style={[pal.text, styles.headerSearchInput]} - keyboardAppearance={theme.colorScheme} - onFocus={() => setIsInputFocused(true)} - onBlur={() => setIsInputFocused(false)} - onChangeText={onChangeQuery} - /> - {query ? ( - <TouchableOpacity onPress={onPressClearQuery}> - <FontAwesomeIcon - icon="xmark" - size={16} - style={pal.textLight as FontAwesomeIconStyle} - /> - </TouchableOpacity> + return ( + <TouchableWithoutFeedback onPress={Keyboard.dismiss}> + <ScrollView + ref={scrollElRef} + testID="searchScrollView" + style={[pal.view, styles.container]} + onScroll={onMainScroll} + scrollEventThrottle={100}> + <View style={[pal.view, pal.border, styles.header]}> + <TouchableOpacity + testID="viewHeaderBackOrMenuBtn" + onPress={onPressMenu} + hitSlop={MENU_HITSLOP} + style={styles.headerMenuBtn}> + <UserAvatar size={30} avatar={store.me.avatar} /> + </TouchableOpacity> + <View + style={[ + {backgroundColor: pal.colors.backgroundLight}, + styles.headerSearchContainer, + ]}> + <MagnifyingGlassIcon + style={[pal.icon, styles.headerSearchIcon]} + size={21} + /> + <TextInput + testID="searchTextInput" + ref={textInput} + placeholder="Search" + placeholderTextColor={pal.colors.textLight} + selectTextOnFocus + returnKeyType="search" + value={query} + style={[pal.text, styles.headerSearchInput]} + keyboardAppearance={theme.colorScheme} + onFocus={() => setIsInputFocused(true)} + onBlur={() => setIsInputFocused(false)} + onChangeText={onChangeQuery} + /> + {query ? ( + <TouchableOpacity onPress={onPressClearQuery}> + <FontAwesomeIcon + icon="xmark" + size={16} + style={pal.textLight as FontAwesomeIconStyle} + /> + </TouchableOpacity> + ) : undefined} + </View> + {query || isInputFocused ? ( + <View style={styles.headerCancelBtn}> + <TouchableOpacity onPress={onPressCancelSearch}> + <Text style={pal.text}>Cancel</Text> + </TouchableOpacity> + </View> ) : undefined} </View> - {query || isInputFocused ? ( - <View style={styles.headerCancelBtn}> - <TouchableOpacity onPress={onPressCancelSearch}> - <Text style={pal.text}>Cancel</Text> - </TouchableOpacity> + {query && autocompleteView.searchRes.length ? ( + <> + {autocompleteView.searchRes.map(item => ( + <ProfileCard + key={item.did} + handle={item.handle} + displayName={item.displayName} + avatar={item.avatar} + /> + ))} + </> + ) : query && !autocompleteView.searchRes.length ? ( + <View> + <Text style={[pal.textLight, styles.searchPrompt]}> + No results found for {autocompleteView.prefix} + </Text> </View> - ) : undefined} - </View> - {query && autocompleteView.searchRes.length ? ( - <> - {autocompleteView.searchRes.map(item => ( - <ProfileCard - key={item.did} - handle={item.handle} - displayName={item.displayName} - avatar={item.avatar} - /> - ))} - </> - ) : query && !autocompleteView.searchRes.length ? ( - <View> - <Text style={[pal.textLight, styles.searchPrompt]}> - No results found for {autocompleteView.prefix} - </Text> - </View> - ) : isInputFocused ? ( - <View> - <Text style={[pal.textLight, styles.searchPrompt]}> - Search for users on the network - </Text> - </View> - ) : ( - <ScrollView onScroll={Keyboard.dismiss}> - <WhoToFollow key={`wtf-${lastRenderTime}`} /> - <SuggestedPosts key={`sp-${lastRenderTime}`} /> - <View style={s.footerSpacer} /> - </ScrollView> - )} - <View style={s.footerSpacer} /> - </ScrollView> - </TouchableWithoutFeedback> - ) -}) + ) : isInputFocused ? ( + <View> + <Text style={[pal.textLight, styles.searchPrompt]}> + Search for users on the network + </Text> + </View> + ) : ( + <ScrollView onScroll={Keyboard.dismiss}> + <WhoToFollow key={`wtf-${lastRenderTime}`} /> + <SuggestedPosts key={`sp-${lastRenderTime}`} /> + <View style={s.footerSpacer} /> + </ScrollView> + )} + <View style={s.footerSpacer} /> + </ScrollView> + </TouchableWithoutFeedback> + ) + }), +) const styles = StyleSheet.create({ container: { diff --git a/src/view/screens/Search.web.tsx b/src/view/screens/Search.web.tsx index 75b5f01ce..29b884493 100644 --- a/src/view/screens/Search.web.tsx +++ b/src/view/screens/Search.web.tsx @@ -1,6 +1,7 @@ import React from 'react' import {StyleSheet, View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' +import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {ScrollView} from '../com/util/Views' import {observer} from 'mobx-react-lite' import { @@ -17,46 +18,48 @@ import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' const FIVE_MIN = 5 * 60 * 1e3 type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'> -export const SearchScreen = observer(({}: Props) => { - const pal = usePalette('default') - const store = useStores() - const scrollElRef = React.useRef<ScrollView>(null) - const onMainScroll = useOnMainScroll(store) - const [lastRenderTime, setRenderTime] = React.useState<number>(Date.now()) // used to trigger reloads +export const SearchScreen = withAuthRequired( + observer(({}: Props) => { + const pal = usePalette('default') + const store = useStores() + const scrollElRef = React.useRef<ScrollView>(null) + const onMainScroll = useOnMainScroll(store) + const [lastRenderTime, setRenderTime] = React.useState<number>(Date.now()) // used to trigger reloads - const onSoftReset = () => { - scrollElRef.current?.scrollTo({x: 0, y: 0}) - } + const onSoftReset = () => { + scrollElRef.current?.scrollTo({x: 0, y: 0}) + } - useFocusEffect( - React.useCallback(() => { - const softResetSub = store.onScreenSoftReset(onSoftReset) + useFocusEffect( + React.useCallback(() => { + const softResetSub = store.onScreenSoftReset(onSoftReset) - const now = Date.now() - if (now - lastRenderTime > FIVE_MIN) { - setRenderTime(Date.now()) // trigger reload of suggestions - } - store.shell.setMinimalShellMode(false) + const now = Date.now() + if (now - lastRenderTime > FIVE_MIN) { + setRenderTime(Date.now()) // trigger reload of suggestions + } + store.shell.setMinimalShellMode(false) - return () => { - softResetSub.remove() - } - }, [store, lastRenderTime, setRenderTime]), - ) + return () => { + softResetSub.remove() + } + }, [store, lastRenderTime, setRenderTime]), + ) - return ( - <ScrollView - ref={scrollElRef} - testID="searchScrollView" - style={[pal.view, styles.container]} - onScroll={onMainScroll} - scrollEventThrottle={100}> - <WhoToFollow key={`wtf-${lastRenderTime}`} /> - <SuggestedPosts key={`sp-${lastRenderTime}`} /> - <View style={s.footerSpacer} /> - </ScrollView> - ) -}) + return ( + <ScrollView + ref={scrollElRef} + testID="searchScrollView" + style={[pal.view, styles.container]} + onScroll={onMainScroll} + scrollEventThrottle={100}> + <WhoToFollow key={`wtf-${lastRenderTime}`} /> + <SuggestedPosts key={`sp-${lastRenderTime}`} /> + <View style={s.footerSpacer} /> + </ScrollView> + ) + }), +) const styles = StyleSheet.create({ container: { diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 2e5d2c001..a79a357b7 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -16,6 +16,7 @@ import { } from '@fortawesome/react-native-fontawesome' import {observer} from 'mobx-react-lite' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' +import {withAuthRequired} from 'view/com/auth/withAuthRequired' import * as AppInfo from 'lib/app-info' import {useStores} from 'state/index' import {s, colors} from 'lib/styles' @@ -33,235 +34,237 @@ import {useAnalytics} from 'lib/analytics' import {NavigationProp} from 'lib/routes/types' type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> -export const SettingsScreen = observer(function Settings({}: Props) { - const theme = useTheme() - const pal = usePalette('default') - const store = useStores() - const navigation = useNavigation<NavigationProp>() - const {screen, track} = useAnalytics() - const [isSwitching, setIsSwitching] = React.useState(false) +export const SettingsScreen = withAuthRequired( + observer(function Settings({}: Props) { + const theme = useTheme() + const pal = usePalette('default') + const store = useStores() + const navigation = useNavigation<NavigationProp>() + const {screen, track} = useAnalytics() + const [isSwitching, setIsSwitching] = React.useState(false) - useFocusEffect( - React.useCallback(() => { - screen('Settings') - store.shell.setMinimalShellMode(false) - }, [screen, store]), - ) + useFocusEffect( + React.useCallback(() => { + screen('Settings') + store.shell.setMinimalShellMode(false) + }, [screen, store]), + ) - const onPressSwitchAccount = async (acct: AccountData) => { - track('Settings:SwitchAccountButtonClicked') - setIsSwitching(true) - if (await store.session.resumeSession(acct)) { + const onPressSwitchAccount = async (acct: AccountData) => { + track('Settings:SwitchAccountButtonClicked') + setIsSwitching(true) + if (await store.session.resumeSession(acct)) { + setIsSwitching(false) + navigation.navigate('HomeTab') + navigation.dispatch(StackActions.popToTop()) + Toast.show(`Signed in as ${acct.displayName || acct.handle}`) + return + } setIsSwitching(false) + Toast.show('Sorry! We need you to enter your password.') navigation.navigate('HomeTab') navigation.dispatch(StackActions.popToTop()) - Toast.show(`Signed in as ${acct.displayName || acct.handle}`) - return + store.session.clear() + } + const onPressAddAccount = () => { + track('Settings:AddAccountButtonClicked') + store.session.clear() + } + const onPressChangeHandle = () => { + track('Settings:ChangeHandleButtonClicked') + store.shell.openModal({ + name: 'change-handle', + onChanged() { + setIsSwitching(true) + store.session.reloadFromServer().then( + () => { + setIsSwitching(false) + Toast.show('Your handle has been updated') + }, + err => { + store.log.error( + 'Failed to reload from server after handle update', + {err}, + ) + setIsSwitching(false) + }, + ) + }, + }) + } + const onPressSignout = () => { + track('Settings:SignOutButtonClicked') + store.session.logout() + } + const onPressDeleteAccount = () => { + store.shell.openModal({name: 'delete-account'}) } - setIsSwitching(false) - Toast.show('Sorry! We need you to enter your password.') - navigation.navigate('HomeTab') - navigation.dispatch(StackActions.popToTop()) - store.session.clear() - } - const onPressAddAccount = () => { - track('Settings:AddAccountButtonClicked') - store.session.clear() - } - const onPressChangeHandle = () => { - track('Settings:ChangeHandleButtonClicked') - store.shell.openModal({ - name: 'change-handle', - onChanged() { - setIsSwitching(true) - store.session.reloadFromServer().then( - () => { - setIsSwitching(false) - Toast.show('Your handle has been updated') - }, - err => { - store.log.error( - 'Failed to reload from server after handle update', - {err}, - ) - setIsSwitching(false) - }, - ) - }, - }) - } - const onPressSignout = () => { - track('Settings:SignOutButtonClicked') - store.session.logout() - } - const onPressDeleteAccount = () => { - store.shell.openModal({name: 'delete-account'}) - } - return ( - <View style={[s.hContentRegion]} testID="settingsScreen"> - <ViewHeader title="Settings" /> - <ScrollView style={s.hContentRegion}> - <View style={styles.spacer20} /> - <View style={[s.flexRow, styles.heading]}> - <Text type="xl-bold" style={pal.text}> - Signed in as - </Text> - <View style={s.flex1} /> - </View> - {isSwitching ? ( - <View style={[pal.view, styles.linkCard]}> - <ActivityIndicator /> + return ( + <View style={[s.hContentRegion]} testID="settingsScreen"> + <ViewHeader title="Settings" /> + <ScrollView style={s.hContentRegion}> + <View style={styles.spacer20} /> + <View style={[s.flexRow, styles.heading]}> + <Text type="xl-bold" style={pal.text}> + Signed in as + </Text> + <View style={s.flex1} /> </View> - ) : ( - <Link - href={`/profile/${store.me.handle}`} - title="Your profile" - noFeedback> + {isSwitching ? ( <View style={[pal.view, styles.linkCard]}> + <ActivityIndicator /> + </View> + ) : ( + <Link + href={`/profile/${store.me.handle}`} + title="Your profile" + noFeedback> + <View style={[pal.view, styles.linkCard]}> + <View style={styles.avi}> + <UserAvatar size={40} avatar={store.me.avatar} /> + </View> + <View style={[s.flex1]}> + <Text type="md-bold" style={pal.text} numberOfLines={1}> + {store.me.displayName || store.me.handle} + </Text> + <Text type="sm" style={pal.textLight} numberOfLines={1}> + {store.me.handle} + </Text> + </View> + <TouchableOpacity + testID="signOutBtn" + onPress={isSwitching ? undefined : onPressSignout}> + <Text type="lg" style={pal.link}> + Sign out + </Text> + </TouchableOpacity> + </View> + </Link> + )} + {store.session.switchableAccounts.map(account => ( + <TouchableOpacity + testID={`switchToAccountBtn-${account.handle}`} + key={account.did} + style={[pal.view, styles.linkCard, isSwitching && styles.dimmed]} + onPress={ + isSwitching ? undefined : () => onPressSwitchAccount(account) + }> <View style={styles.avi}> - <UserAvatar size={40} avatar={store.me.avatar} /> + <UserAvatar size={40} avatar={account.aviUrl} /> </View> <View style={[s.flex1]}> - <Text type="md-bold" style={pal.text} numberOfLines={1}> - {store.me.displayName || store.me.handle} + <Text type="md-bold" style={pal.text}> + {account.displayName || account.handle} </Text> - <Text type="sm" style={pal.textLight} numberOfLines={1}> - {store.me.handle} + <Text type="sm" style={pal.textLight}> + {account.handle} </Text> </View> - <TouchableOpacity - testID="signOutBtn" - onPress={isSwitching ? undefined : onPressSignout}> - <Text type="lg" style={pal.link}> - Sign out - </Text> - </TouchableOpacity> - </View> - </Link> - )} - {store.session.switchableAccounts.map(account => ( + <AccountDropdownBtn handle={account.handle} /> + </TouchableOpacity> + ))} <TouchableOpacity - testID={`switchToAccountBtn-${account.handle}`} - key={account.did} - style={[pal.view, styles.linkCard, isSwitching && styles.dimmed]} - onPress={ - isSwitching ? undefined : () => onPressSwitchAccount(account) - }> - <View style={styles.avi}> - <UserAvatar size={40} avatar={account.aviUrl} /> - </View> - <View style={[s.flex1]}> - <Text type="md-bold" style={pal.text}> - {account.displayName || account.handle} - </Text> - <Text type="sm" style={pal.textLight}> - {account.handle} - </Text> + testID="switchToNewAccountBtn" + style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]} + onPress={isSwitching ? undefined : onPressAddAccount}> + <View style={[styles.iconContainer, pal.btn]}> + <FontAwesomeIcon + icon="plus" + style={pal.text as FontAwesomeIconStyle} + /> </View> - <AccountDropdownBtn handle={account.handle} /> + <Text type="lg" style={pal.text}> + Add account + </Text> </TouchableOpacity> - ))} - <TouchableOpacity - testID="switchToNewAccountBtn" - style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]} - onPress={isSwitching ? undefined : onPressAddAccount}> - <View style={[styles.iconContainer, pal.btn]}> - <FontAwesomeIcon - icon="plus" - style={pal.text as FontAwesomeIconStyle} - /> - </View> - <Text type="lg" style={pal.text}> - Add account - </Text> - </TouchableOpacity> - <View style={styles.spacer20} /> + <View style={styles.spacer20} /> - <Text type="xl-bold" style={[pal.text, styles.heading]}> - Advanced - </Text> - <TouchableOpacity - testID="changeHandleBtn" - style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]} - onPress={isSwitching ? undefined : onPressChangeHandle}> - <View style={[styles.iconContainer, pal.btn]}> - <FontAwesomeIcon - icon="at" - style={pal.text as FontAwesomeIconStyle} - /> - </View> - <Text type="lg" style={pal.text}> - Change my handle + <Text type="xl-bold" style={[pal.text, styles.heading]}> + Advanced </Text> - </TouchableOpacity> + <TouchableOpacity + testID="changeHandleBtn" + style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]} + onPress={isSwitching ? undefined : onPressChangeHandle}> + <View style={[styles.iconContainer, pal.btn]}> + <FontAwesomeIcon + icon="at" + style={pal.text as FontAwesomeIconStyle} + /> + </View> + <Text type="lg" style={pal.text}> + Change my handle + </Text> + </TouchableOpacity> - <View style={styles.spacer20} /> + <View style={styles.spacer20} /> - <Text type="xl-bold" style={[pal.text, styles.heading]}> - Danger zone - </Text> - <TouchableOpacity - style={[pal.view, styles.linkCard]} - onPress={onPressDeleteAccount}> - <View - style={[ - styles.iconContainer, - theme.colorScheme === 'dark' - ? styles.trashIconContainerDark - : styles.trashIconContainerLight, - ]}> - <FontAwesomeIcon - icon={['far', 'trash-can']} + <Text type="xl-bold" style={[pal.text, styles.heading]}> + Danger zone + </Text> + <TouchableOpacity + style={[pal.view, styles.linkCard]} + onPress={onPressDeleteAccount}> + <View + style={[ + styles.iconContainer, + theme.colorScheme === 'dark' + ? styles.trashIconContainerDark + : styles.trashIconContainerLight, + ]}> + <FontAwesomeIcon + icon={['far', 'trash-can']} + style={ + theme.colorScheme === 'dark' + ? styles.dangerDark + : styles.dangerLight + } + size={21} + /> + </View> + <Text + type="lg" style={ theme.colorScheme === 'dark' ? styles.dangerDark : styles.dangerLight - } - size={21} - /> - </View> - <Text - type="lg" - style={ - theme.colorScheme === 'dark' - ? styles.dangerDark - : styles.dangerLight - }> - Delete my account - </Text> - </TouchableOpacity> + }> + Delete my account + </Text> + </TouchableOpacity> - <View style={styles.spacer20} /> + <View style={styles.spacer20} /> - <Text type="xl-bold" style={[pal.text, styles.heading]}> - Developer tools - </Text> - <Link - style={[pal.view, styles.linkCardNoIcon]} - href="/sys/log" - title="System log"> - <Text type="lg" style={pal.text}> - System log + <Text type="xl-bold" style={[pal.text, styles.heading]}> + Developer tools </Text> - </Link> - <Link - style={[pal.view, styles.linkCardNoIcon]} - href="/sys/debug" - title="Debug tools"> - <Text type="lg" style={pal.text}> - Storybook + <Link + style={[pal.view, styles.linkCardNoIcon]} + href="/sys/log" + title="System log"> + <Text type="lg" style={pal.text}> + System log + </Text> + </Link> + <Link + style={[pal.view, styles.linkCardNoIcon]} + href="/sys/debug" + title="Debug tools"> + <Text type="lg" style={pal.text}> + Storybook + </Text> + </Link> + <Text type="sm" style={[styles.buildInfo, pal.textLight]}> + Build version {AppInfo.appVersion} ({AppInfo.buildVersion}) </Text> - </Link> - <Text type="sm" style={[styles.buildInfo, pal.textLight]}> - Build version {AppInfo.appVersion} ({AppInfo.buildVersion}) - </Text> - <View style={s.footerSpacer} /> - </ScrollView> - </View> - ) -}) + <View style={s.footerSpacer} /> + </ScrollView> + </View> + ) + }), +) function AccountDropdownBtn({handle}: {handle: string}) { const store = useStores() diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 46c77178b..65757b072 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -131,7 +131,7 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() { return ( <View style={styles.leftNav}> - <ProfileCard /> + {store.session.hasSession && <ProfileCard />} <BackBtn /> <NavItem href="/" @@ -164,14 +164,16 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() { } label="Notifications" /> - <NavItem - href={`/profile/${store.me.handle}`} - icon={<UserIcon strokeWidth={1.75} size={28} style={pal.text} />} - iconFilled={ - <UserIconSolid strokeWidth={1.75} size={28} style={pal.text} /> - } - label="Profile" - /> + {store.session.hasSession && ( + <NavItem + href={`/profile/${store.me.handle}`} + icon={<UserIcon strokeWidth={1.75} size={28} style={pal.text} />} + iconFilled={ + <UserIconSolid strokeWidth={1.75} size={28} style={pal.text} /> + } + label="Profile" + /> + )} <NavItem href="/settings" icon={<CogIcon strokeWidth={1.75} size={28} style={pal.text} />} @@ -180,7 +182,7 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() { } label="Settings" /> - <ComposeBtn /> + {store.session.hasSession && <ComposeBtn />} </View> ) }) diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 58fb31392..3f196cb70 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -7,12 +7,14 @@ import {Text} from 'view/com/util/text/Text' import {TextLink} from 'view/com/util/Link' import {FEEDBACK_FORM_URL} from 'lib/constants' import {s} from 'lib/styles' +import {useStores} from 'state/index' export const DesktopRightNav = observer(function DesktopRightNav() { + const store = useStores() const pal = usePalette('default') return ( <View style={[styles.rightNav, pal.view]}> - <DesktopSearch /> + {store.session.hasSession && <DesktopSearch />} <View style={styles.message}> <Text type="md" style={[pal.textLight, styles.messageLine]}> Welcome to Bluesky! This is a beta application that's still in diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 116915ff4..15f9ef58c 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -5,7 +5,6 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import {Drawer} from 'react-native-drawer-layout' import {useNavigationState} from '@react-navigation/native' import {useStores} from 'state/index' -import {Login} from 'view/screens/Login' import {ModalsContainer} from 'view/com/modals/Modal' import {Lightbox} from 'view/com/lightbox/Lightbox' import {Text} from 'view/com/util/text/Text' @@ -104,20 +103,6 @@ export const Shell: React.FC = observer(() => { ) } - if (!store.session.hasSession) { - return ( - <View style={styles.outerContainer}> - <StatusBar - barStyle={ - theme.colorScheme === 'dark' ? 'light-content' : 'dark-content' - } - /> - <Login /> - <ModalsContainer /> - </View> - ) - } - return ( <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}> <StatusBar diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 9a97505e8..a588c99a1 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -4,7 +4,6 @@ import {View, StyleSheet} from 'react-native' import {useStores} from 'state/index' import {DesktopLeftNav} from './desktop/LeftNav' import {DesktopRightNav} from './desktop/RightNav' -import {Login} from '../screens/Login' import {ErrorBoundary} from '../com/util/ErrorBoundary' import {Lightbox} from '../com/lightbox/Lightbox' import {ModalsContainer} from '../com/modals/Modal' @@ -45,21 +44,10 @@ const ShellInner = observer(() => { export const Shell: React.FC = observer(() => { const pageBg = useColorSchemeStyle(styles.bgLight, styles.bgDark) - const store = useStores() if (isMobileWeb) { return <NoMobileWeb /> } - - if (!store.session.hasSession) { - return ( - <View style={[s.hContentRegion, pageBg]}> - <Login /> - <ModalsContainer /> - </View> - ) - } - return ( <View style={[s.hContentRegion, pageBg]}> <RoutesContainer> |