Rework logged out state to preserve routing and work for web
parent
b5c64a03b6
commit
774fb83719
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
})
|
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -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',
|
||||
},
|
||||
})
|
|
@ -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',
|
||||
},
|
||||
})
|
|
@ -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>
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -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>
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
// rendering
|
||||
// =
|
||||
|
||||
const renderHeader = () => {
|
||||
if (!uiState) {
|
||||
return <View />
|
||||
const onPressCompose = React.useCallback(() => {
|
||||
track('ProfileScreen:PressCompose')
|
||||
store.shell.openComposer({})
|
||||
}, [store, track])
|
||||
const onSelectView = (index: number) => {
|
||||
uiState.setSelectedViewIndex(index)
|
||||
}
|
||||
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}
|
||||
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
|
||||
// =
|
||||
|
||||
const renderHeader = () => {
|
||||
if (!uiState) {
|
||||
return <View />
|
||||
}
|
||||
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>
|
||||
}
|
||||
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 {
|
||||
items = items.concat([EMPTY_ITEM])
|
||||
renderItem = () => <Text>TODO</Text>
|
||||
}
|
||||
}
|
||||
}
|
||||
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}
|
||||
/>
|
||||
</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>
|
||||
}
|
||||
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 {
|
||||
items = items.concat([EMPTY_ITEM])
|
||||
renderItem = () => <Text>TODO</Text>
|
||||
}
|
||||
}
|
||||
}
|
||||
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}
|
||||
/>
|
||||
) : (
|
||||
<CenteredView>{renderHeader()}</CenteredView>
|
||||
)}
|
||||
<FAB
|
||||
testID="composeFAB"
|
||||
onPress={onPressCompose}
|
||||
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
||||
/>
|
||||
) : 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} />}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
</View>
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
function LoadingMoreFooter() {
|
||||
return (
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
return cleanup
|
||||
}, [store, autocompleteView, lastRenderTime, setRenderTime]),
|
||||
)
|
||||
|
||||
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 {
|
||||
autocompleteView.setActive(false)
|
||||
const onSoftReset = () => {
|
||||
scrollElRef.current?.scrollTo({x: 0, y: 0})
|
||||
}
|
||||
}
|
||||
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>
|
||||
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()
|
||||
|
||||
return cleanup
|
||||
}, [store, autocompleteView, lastRenderTime, setRenderTime]),
|
||||
)
|
||||
|
||||
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 {
|
||||
autocompleteView.setActive(false)
|
||||
}
|
||||
}
|
||||
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>
|
||||
) : 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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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} />
|
||||
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>
|
||||
<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>
|
||||
</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
|
||||
|
||||
<View style={styles.spacer20} />
|
||||
|
||||
<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]}>
|
||||
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]}>
|
||||
Danger zone
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<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']}
|
||||
<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()
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue