diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Navigation.tsx | 14 | ||||
-rw-r--r-- | src/lib/hooks/useOnboarding.ts | 23 | ||||
-rw-r--r-- | src/state/models/ui/shell.ts | 7 | ||||
-rw-r--r-- | src/view/com/auth/onboarding/RecommendedFeeds.tsx | 34 | ||||
-rw-r--r-- | src/view/com/auth/onboarding/Welcome.tsx | 84 | ||||
-rw-r--r-- | src/view/com/modals/Modal.web.tsx | 10 | ||||
-rw-r--r-- | src/view/com/modals/OnboardingModal.tsx | 40 | ||||
-rw-r--r-- | src/view/com/util/ViewHeader.tsx | 74 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 10 | ||||
-rw-r--r-- | src/view/screens/onboarding/RecommendedFeeds.tsx | 20 | ||||
-rw-r--r-- | src/view/screens/onboarding/Welcome.tsx | 32 |
11 files changed, 235 insertions, 113 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 058a15fa2..334370ab0 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -67,8 +67,8 @@ import {getRoutingInstrumentation} from 'lib/sentry' import {bskyTitle} from 'lib/strings/headings' import {JSX} from 'react/jsx-runtime' import {timeout} from 'lib/async/timeout' -import {Welcome, WelcomeHeaderRight} from 'view/com/auth/onboarding/Welcome' -import {RecommendedFeeds} from 'view/com/auth/onboarding/RecommendedFeeds' +import {RecommendedFeedsScreen} from 'view/screens/onboarding/RecommendedFeeds' +import {WelcomeScreen} from 'view/screens/onboarding/Welcome' const navigationRef = createNavigationContainerRef<AllNavigatorParams>() @@ -223,20 +223,16 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { /> <Stack.Screen name="Welcome" - component={Welcome} + component={WelcomeScreen} options={{ title: title('Welcome'), presentation: 'card', - headerShown: true, - headerTransparent: true, - headerTitle: '', - headerBackVisible: false, - headerRight: props => <WelcomeHeaderRight {...props} />, + gestureEnabled: false, }} /> <Stack.Screen name="RecommendedFeeds" - component={RecommendedFeeds} + component={RecommendedFeedsScreen} options={{ title: title('Recommended Feeds'), }} diff --git a/src/lib/hooks/useOnboarding.ts b/src/lib/hooks/useOnboarding.ts new file mode 100644 index 000000000..cdf24bc14 --- /dev/null +++ b/src/lib/hooks/useOnboarding.ts @@ -0,0 +1,23 @@ +import React from 'react' +import {useStores} from 'state/index' +import {useNavigation} from '@react-navigation/native' +import {NavigationProp} from 'lib/routes/types' +import {isNative, isWeb} from 'platform/detection' + +export function useOnboarding() { + const store = useStores() + const navigation = useNavigation<NavigationProp>() + + React.useEffect(() => { + if (store.onboarding.isActive) { + if (isWeb) { + store.shell.openModal({name: 'onboarding'}) + return + } + if (isNative) { + navigation.navigate('Welcome') + return + } + } + }, [store.onboarding.isActive, navigation, store.shell]) +} diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index a64047f9f..4c36dfdc6 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -140,6 +140,10 @@ export interface PreferencesHomeFeed { name: 'preferences-home-feed' } +export interface OnboardingModal { + name: 'onboarding' +} + export type Modal = // Account | AddAppPasswordModal @@ -175,6 +179,9 @@ export type Modal = // Generic | ConfirmModal + // Onboarding (only used on web) + | OnboardingModal + interface LightboxModel {} export class ProfileImageLightbox implements LightboxModel { diff --git a/src/view/com/auth/onboarding/RecommendedFeeds.tsx b/src/view/com/auth/onboarding/RecommendedFeeds.tsx index 2f4c63daf..fa9d69f98 100644 --- a/src/view/com/auth/onboarding/RecommendedFeeds.tsx +++ b/src/view/com/auth/onboarding/RecommendedFeeds.tsx @@ -3,14 +3,12 @@ import {FlatList, StyleSheet, View} from 'react-native' import {Text} from 'view/com/util/text/Text' import {usePalette} from 'lib/hooks/usePalette' import {Button} from 'view/com/util/forms/Button' -import {NativeStackScreenProps} from '@react-navigation/native-stack' -import {HomeTabNavigatorParams} from 'lib/routes/types' -import {useStores} from 'state/index' import {observer} from 'mobx-react-lite' import {CustomFeed} from 'view/com/feeds/CustomFeed' import {useCustomFeed} from 'lib/hooks/useCustomFeed' import {makeRecordUri} from 'lib/strings/url-helpers' import {ViewHeader} from 'view/com/util/ViewHeader' +import {isDesktopWeb} from 'platform/detection' const TEMPORARY_RECOMMENDED_FEEDS = [ { @@ -119,21 +117,15 @@ const TEMPORARY_RECOMMENDED_FEEDS = [ }, ] -type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'RecommendedFeeds'> -export const RecommendedFeeds = observer(({navigation}: Props) => { +type Props = { + next: () => void +} +export const RecommendedFeeds = observer(({next}: Props) => { const pal = usePalette('default') - const store = useStores() - - const next = () => { - const nextScreenName = store.onboarding.next('RecommendedFeeds') - if (nextScreenName) { - navigation.navigate(nextScreenName) - } - } return ( <View style={[styles.container]} testID="recommendedFeedsScreen"> - <ViewHeader title="Recommended Feeds" canGoBack /> + <ViewHeader title="Recommended Feeds" canGoBack={true} /> <Text type="lg-medium" style={[pal.text, styles.header]}> Check out some recommended feeds. Click + to add them to your list of pinned feeds. @@ -167,7 +159,19 @@ const Item = ({item}: {item: ItemProps}) => { const data = useCustomFeed(uri) if (!data) return null return ( - <CustomFeed item={data} key={uri} showDescription showLikes showSaveBtn /> + <CustomFeed + item={data} + key={uri} + showDescription + showLikes + showSaveBtn + style={[ + { + // @ts-ignore + cursor: isDesktopWeb ? 'pointer' : 'auto', + }, + ]} + /> ) } diff --git a/src/view/com/auth/onboarding/Welcome.tsx b/src/view/com/auth/onboarding/Welcome.tsx index a1e97a32f..a322e4d4f 100644 --- a/src/view/com/auth/onboarding/Welcome.tsx +++ b/src/view/com/auth/onboarding/Welcome.tsx @@ -5,40 +5,45 @@ import {s} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Button} from 'view/com/util/forms/Button' -import {NativeStackScreenProps} from '@react-navigation/native-stack' -import {HomeTabNavigatorParams} from 'lib/routes/types' -import {useStores} from 'state/index' import {observer} from 'mobx-react-lite' -import {HeaderButtonProps} from '@react-navigation/native-stack/lib/typescript/src/types' -import {NavigationProp, useNavigation} from '@react-navigation/native' +import {ViewHeader} from 'view/com/util/ViewHeader' +import {isDesktopWeb} from 'platform/detection' -type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Welcome'> -export const Welcome = observer(({navigation}: Props) => { - const pal = usePalette('default') - const store = useStores() - - // make sure bottom nav is hidden - React.useEffect(() => { - if (!store.shell.minimalShellMode) { - store.shell.setMinimalShellMode(true) - } - }, [store.shell.minimalShellMode, store]) +type Props = { + next: () => void + skip: () => void +} - const next = () => { - const nextScreenName = store.onboarding.next('Welcome') - if (nextScreenName) { - navigation.navigate(nextScreenName) - } - } +export const Welcome = observer(({next, skip}: Props) => { + const pal = usePalette('default') return ( - <View style={[styles.container]}> - <View testID="welcomeScreen"> + <View style={[styles.container]} testID="welcomeOnboarding"> + <ViewHeader + showOnDesktop + showBorder={false} + showBackButton={false} + title="" + renderButton={() => { + return ( + <Pressable + accessibilityRole="button" + style={[s.flexRow, s.alignCenter]} + onPress={skip}> + <Text style={[pal.link]}>Skip</Text> + <FontAwesomeIcon + icon={'chevron-right'} + size={14} + color={pal.colors.link} + /> + </Pressable> + ) + }} + /> + <View> <Text style={[pal.text, styles.title]}>Welcome to </Text> <Text style={[pal.text, pal.link, styles.title]}>Bluesky</Text> - <View style={styles.spacer} /> - <View style={[styles.row]}> <FontAwesomeIcon icon={'globe'} size={36} color={pal.colors.link} /> <View style={[styles.rowText]}> @@ -85,35 +90,10 @@ export const Welcome = observer(({navigation}: Props) => { ) }) -export const WelcomeHeaderRight = (props: HeaderButtonProps) => { - const {canGoBack} = props - const pal = usePalette('default') - const navigation = useNavigation<NavigationProp<HomeTabNavigatorParams>>() - const store = useStores() - return ( - <Pressable - accessibilityRole="button" - style={[s.flexRow, s.alignCenter]} - onPress={() => { - if (canGoBack) { - store.onboarding.skip() - navigation.goBack() - } - }}> - <Text style={[pal.link]}>Skip</Text> - <FontAwesomeIcon - icon={'chevron-right'} - size={14} - color={pal.colors.link} - /> - </Pressable> - ) -} - const styles = StyleSheet.create({ container: { flex: 1, - marginVertical: 60, + marginBottom: isDesktopWeb ? 30 : 60, marginHorizontal: 16, justifyContent: 'space-between', }, diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 6aef1b71c..20da99e81 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -27,7 +27,7 @@ import * as ContentFilteringSettingsModal from './ContentFilteringSettings' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' import * as ModerationDetailsModal from './ModerationDetails' - +import * as OnboardingModal from './OnboardingModal' import * as PreferencesHomeFeed from './PreferencesHomeFeed' export const ModalsContainer = observer(function ModalsContainer() { @@ -55,7 +55,11 @@ function Modal({modal}: {modal: ModalIface}) { } const onPressMask = () => { - if (modal.name === 'crop-image' || modal.name === 'edit-image') { + if ( + modal.name === 'crop-image' || + modal.name === 'edit-image' || + modal.name === 'onboarding' + ) { return // dont close on mask presses during crop } store.shell.closeModal() @@ -110,6 +114,8 @@ function Modal({modal}: {modal: ModalIface}) { element = <PreferencesHomeFeed.Component /> } else if (modal.name === 'moderation-details') { element = <ModerationDetailsModal.Component {...modal} /> + } else if (modal.name === 'onboarding') { + element = <OnboardingModal.Component /> } else { return null } diff --git a/src/view/com/modals/OnboardingModal.tsx b/src/view/com/modals/OnboardingModal.tsx new file mode 100644 index 000000000..3862736cf --- /dev/null +++ b/src/view/com/modals/OnboardingModal.tsx @@ -0,0 +1,40 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import {useStores} from 'state/index' + +import {usePalette} from 'lib/hooks/usePalette' +import {isDesktopWeb} from 'platform/detection' +import {Welcome} from '../auth/onboarding/Welcome' +import {observer} from 'mobx-react-lite' +import {RecommendedFeeds} from '../auth/onboarding/RecommendedFeeds' + +export const snapPoints = ['90%'] + +export const Component = observer(({}: {}) => { + const pal = usePalette('default') + const store = useStores() + + const next = () => { + const nextScreenName = store.onboarding.next() + if (nextScreenName === 'Home') { + store.shell.closeModal() + } + } + + return ( + <View style={[styles.container, pal.view]} testID="onboardingModal"> + {store.onboarding.step === 'Welcome' ? <Welcome next={next} /> : null} + {store.onboarding.step === 'RecommendedFeeds' ? ( + <RecommendedFeeds next={next} /> + ) : null} + </View> + ) +}) + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingBottom: isDesktopWeb ? 0 : 50, + maxHeight: '750px', + }, +}) diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index f5a921ac0..47e631072 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -17,6 +17,7 @@ const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} export const ViewHeader = observer(function ({ title, canGoBack, + showBackButton = true, hideOnScroll, showOnDesktop, showBorder, @@ -24,6 +25,7 @@ export const ViewHeader = observer(function ({ }: { title: string canGoBack?: boolean + showBackButton?: boolean hideOnScroll?: boolean showOnDesktop?: boolean showBorder?: boolean @@ -49,7 +51,13 @@ export const ViewHeader = observer(function ({ if (isDesktopWeb) { if (showOnDesktop) { - return <DesktopWebHeader title={title} renderButton={renderButton} /> + return ( + <DesktopWebHeader + title={title} + renderButton={renderButton} + showBorder={showBorder} + /> + ) } return null } else { @@ -59,30 +67,32 @@ export const ViewHeader = observer(function ({ return ( <Container hideOnScroll={hideOnScroll || false} showBorder={showBorder}> - <TouchableOpacity - testID="viewHeaderDrawerBtn" - onPress={canGoBack ? onPressBack : onPressMenu} - hitSlop={BACK_HITSLOP} - style={canGoBack ? styles.backBtn : styles.backBtnWide} - accessibilityRole="button" - accessibilityLabel={canGoBack ? 'Back' : 'Menu'} - accessibilityHint={ - canGoBack ? '' : 'Access navigation links and settings' - }> - {canGoBack ? ( - <FontAwesomeIcon - size={18} - icon="angle-left" - style={[styles.backIcon, pal.text]} - /> - ) : ( - <FontAwesomeIcon - size={18} - icon="bars" - style={[styles.backIcon, pal.textLight]} - /> - )} - </TouchableOpacity> + {showBackButton ? ( + <TouchableOpacity + testID="viewHeaderDrawerBtn" + onPress={canGoBack ? onPressBack : onPressMenu} + hitSlop={BACK_HITSLOP} + style={canGoBack ? styles.backBtn : styles.backBtnWide} + accessibilityRole="button" + accessibilityLabel={canGoBack ? 'Back' : 'Menu'} + accessibilityHint={ + canGoBack ? '' : 'Access navigation links and settings' + }> + {canGoBack ? ( + <FontAwesomeIcon + size={18} + icon="angle-left" + style={[styles.backIcon, pal.text]} + /> + ) : ( + <FontAwesomeIcon + size={18} + icon="bars" + style={[styles.backIcon, pal.textLight]} + /> + )} + </TouchableOpacity> + ) : null} <View style={styles.titleContainer} pointerEvents="none"> <Text type="title" style={[pal.text, styles.title]}> {title} @@ -101,13 +111,23 @@ export const ViewHeader = observer(function ({ function DesktopWebHeader({ title, renderButton, + showBorder = true, }: { title: string renderButton?: () => JSX.Element + showBorder?: boolean }) { const pal = usePalette('default') return ( - <CenteredView style={[styles.header, styles.desktopHeader, pal.border]}> + <CenteredView + style={[ + styles.header, + styles.desktopHeader, + pal.border, + { + borderBottomWidth: showBorder ? 1 : 0, + }, + ]}> <View style={styles.titleContainer} pointerEvents="none"> <Text type="title-lg" style={[pal.text, styles.title]}> {title} @@ -195,13 +215,11 @@ const styles = StyleSheet.create({ width: '100%', }, desktopHeader: { - borderBottomWidth: 1, paddingVertical: 12, }, border: { borderBottomWidth: 1, }, - titleContainer: { marginLeft: 'auto', marginRight: 'auto', diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 7017c698c..4397200e4 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -21,6 +21,7 @@ import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {useAnalytics} from 'lib/analytics/analytics' import {ComposeIcon2} from 'lib/icons' import {isDesktopWeb, isMobileWebMediaQuery, isWeb} from 'platform/detection' +import {useOnboarding} from 'lib/hooks/useOnboarding' const HEADER_OFFSET_MOBILE = 78 const HEADER_OFFSET_DESKTOP = 50 @@ -31,7 +32,7 @@ const POLL_FREQ = 30e3 // 30sec type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> export const HomeScreen = withAuthRequired( - observer(({navigation}: Props) => { + observer(({}: Props) => { const store = useStores() const pagerRef = React.useRef<PagerRef>(null) const [selectedPage, setSelectedPage] = React.useState(0) @@ -39,12 +40,7 @@ export const HomeScreen = withAuthRequired( const [requestedCustomFeeds, setRequestedCustomFeeds] = React.useState< string[] >([]) - - React.useEffect(() => { - if (store.onboarding.isActive) { - navigation.navigate('Welcome') - } - }, [store.onboarding.isActive, navigation]) + useOnboarding() React.useEffect(() => { const {pinned} = store.me.savedFeeds diff --git a/src/view/screens/onboarding/RecommendedFeeds.tsx b/src/view/screens/onboarding/RecommendedFeeds.tsx new file mode 100644 index 000000000..d27278456 --- /dev/null +++ b/src/view/screens/onboarding/RecommendedFeeds.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {HomeTabNavigatorParams} from 'lib/routes/types' +import {useStores} from 'state/index' +import {observer} from 'mobx-react-lite' +import {RecommendedFeeds} from 'view/com/auth/onboarding/RecommendedFeeds' + +type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'RecommendedFeeds'> +export const RecommendedFeedsScreen = observer(({navigation}: Props) => { + const store = useStores() + + const next = () => { + const nextScreenName = store.onboarding.next('RecommendedFeeds') + if (nextScreenName) { + navigation.navigate(nextScreenName) + } + } + + return <RecommendedFeeds next={next} /> +}) diff --git a/src/view/screens/onboarding/Welcome.tsx b/src/view/screens/onboarding/Welcome.tsx new file mode 100644 index 000000000..ea3e5ed77 --- /dev/null +++ b/src/view/screens/onboarding/Welcome.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {HomeTabNavigatorParams} from 'lib/routes/types' +import {useStores} from 'state/index' +import {observer} from 'mobx-react-lite' +import {Welcome} from 'view/com/auth/onboarding/Welcome' + +type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Welcome'> +export const WelcomeScreen = observer(({navigation}: Props) => { + const store = useStores() + + // make sure bottom nav is hidden + React.useEffect(() => { + if (!store.shell.minimalShellMode) { + store.shell.setMinimalShellMode(true) + } + }, [store.shell.minimalShellMode, store]) + + const next = () => { + const nextScreenName = store.onboarding.next('Welcome') + if (nextScreenName) { + navigation.navigate(nextScreenName) + } + } + + const skip = () => { + store.onboarding.skip() + navigation.navigate('Home') + } + + return <Welcome next={next} skip={skip} /> +}) |