diff options
Diffstat (limited to 'src')
23 files changed, 518 insertions, 477 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 070c57960..99c0ebf3c 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -193,7 +193,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { <Stack.Screen name="ProfileFeed" getComponent={() => ProfileFeedScreen} - options={{title: title(msg`Feed`), requireAuth: true}} + options={{title: title(msg`Feed`)}} /> <Stack.Screen name="ProfileFeedLikedBy" @@ -331,11 +331,7 @@ function HomeTabNavigator() { animationDuration: 250, contentStyle: pal.view, }}> - <HomeTab.Screen - name="Home" - getComponent={() => HomeScreen} - options={{requireAuth: true}} - /> + <HomeTab.Screen name="Home" getComponent={() => HomeScreen} /> {commonScreens(HomeTab)} </HomeTab.Navigator> ) @@ -371,11 +367,7 @@ function FeedsTabNavigator() { animationDuration: 250, contentStyle: pal.view, }}> - <FeedsTab.Screen - name="Feeds" - getComponent={() => FeedsScreen} - options={{requireAuth: true}} - /> + <FeedsTab.Screen name="Feeds" getComponent={() => FeedsScreen} /> {commonScreens(FeedsTab as typeof HomeTab)} </FeedsTab.Navigator> ) @@ -451,7 +443,7 @@ const FlatNavigator = () => { <Flat.Screen name="Home" getComponent={() => HomeScreen} - options={{title: title(msg`Home`), requireAuth: true}} + options={{title: title(msg`Home`)}} /> <Flat.Screen name="Search" @@ -461,7 +453,7 @@ const FlatNavigator = () => { <Flat.Screen name="Feeds" getComponent={() => FeedsScreen} - options={{title: title(msg`Feeds`), requireAuth: true}} + options={{title: title(msg`Feeds`)}} /> <Flat.Screen name="Notifications" diff --git a/src/components/AppLanguageDropdown.tsx b/src/components/AppLanguageDropdown.tsx new file mode 100644 index 000000000..dea9e66fb --- /dev/null +++ b/src/components/AppLanguageDropdown.tsx @@ -0,0 +1,67 @@ +import React from 'react' +import {View} from 'react-native' +import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' + +import {sanitizeAppLanguageSetting} from '#/locale/helpers' +import {APP_LANGUAGES} from '#/locale/languages' +import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' +import {atoms as a, useTheme} from '#/alf' +import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' + +export function AppLanguageDropdown() { + const t = useTheme() + + const langPrefs = useLanguagePrefs() + const setLangPrefs = useLanguagePrefsApi() + const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) + + const onChangeAppLanguage = React.useCallback( + (value: Parameters<PickerSelectProps['onValueChange']>[0]) => { + if (!value) return + if (sanitizedLang !== value) { + setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) + } + }, + [sanitizedLang, setLangPrefs], + ) + + return ( + <View style={a.relative}> + <RNPickerSelect + placeholder={{}} + value={sanitizedLang} + onValueChange={onChangeAppLanguage} + items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ + label: l.name, + value: l.code2, + key: l.code2, + }))} + useNativeAndroidPickerStyle={false} + style={{ + inputAndroid: { + color: t.atoms.text_contrast_medium.color, + fontSize: 16, + paddingRight: 12 + 4, + }, + inputIOS: { + color: t.atoms.text.color, + fontSize: 16, + paddingRight: 12 + 4, + }, + }} + /> + + <View + style={[ + a.absolute, + a.inset_0, + {left: 'auto'}, + {pointerEvents: 'none'}, + a.align_center, + a.justify_center, + ]}> + <ChevronDown fill={t.atoms.text.color} size="xs" /> + </View> + </View> + ) +} diff --git a/src/components/AppLanguageDropdown.web.tsx b/src/components/AppLanguageDropdown.web.tsx new file mode 100644 index 000000000..302a30ca6 --- /dev/null +++ b/src/components/AppLanguageDropdown.web.tsx @@ -0,0 +1,62 @@ +import React from 'react' +import {View} from 'react-native' + +import {sanitizeAppLanguageSetting} from '#/locale/helpers' +import {APP_LANGUAGES} from '#/locale/languages' +import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' +import {atoms as a, useTheme} from '#/alf' +import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' +import {Text} from '#/components/Typography' + +export function AppLanguageDropdown() { + const t = useTheme() + + const langPrefs = useLanguagePrefs() + const setLangPrefs = useLanguagePrefsApi() + + const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) + + const onChangeAppLanguage = React.useCallback( + (ev: React.ChangeEvent<HTMLSelectElement>) => { + const value = ev.target.value + + if (!value) return + if (sanitizedLang !== value) { + setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) + } + }, + [sanitizedLang, setLangPrefs], + ) + + return ( + <View style={[a.flex_row, a.gap_sm, a.align_center, a.flex_shrink]}> + <Text aria-hidden={true} style={t.atoms.text_contrast_medium}> + {APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name} + </Text> + <ChevronDown fill={t.atoms.text.color} size="xs" style={a.flex_shrink} /> + + <select + value={sanitizedLang} + onChange={onChangeAppLanguage} + style={{ + cursor: 'pointer', + MozAppearance: 'none', + WebkitAppearance: 'none', + appearance: 'none', + position: 'absolute', + inset: 0, + width: '100%', + color: 'transparent', + background: 'transparent', + border: 0, + padding: 0, + }}> + {APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ( + <option key={l.code2} value={l.code2}> + {l.name} + </option> + ))} + </select> + </View> + ) +} diff --git a/src/components/dialogs/Context.tsx b/src/components/dialogs/Context.tsx index 87bd5c2ed..c9dff9a99 100644 --- a/src/components/dialogs/Context.tsx +++ b/src/components/dialogs/Context.tsx @@ -6,10 +6,12 @@ type Control = Dialog.DialogOuterProps['control'] type ControlsContext = { mutedWordsDialogControl: Control + signinDialogControl: Control } const ControlsContext = React.createContext({ mutedWordsDialogControl: {} as Control, + signinDialogControl: {} as Control, }) export function useGlobalDialogsControlContext() { @@ -18,9 +20,10 @@ export function useGlobalDialogsControlContext() { export function Provider({children}: React.PropsWithChildren<{}>) { const mutedWordsDialogControl = Dialog.useDialogControl() + const signinDialogControl = Dialog.useDialogControl() const ctx = React.useMemo<ControlsContext>( - () => ({mutedWordsDialogControl}), - [mutedWordsDialogControl], + () => ({mutedWordsDialogControl, signinDialogControl}), + [mutedWordsDialogControl, signinDialogControl], ) return ( diff --git a/src/components/dialogs/Signin.tsx b/src/components/dialogs/Signin.tsx new file mode 100644 index 000000000..488eb5c73 --- /dev/null +++ b/src/components/dialogs/Signin.tsx @@ -0,0 +1,99 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {isNative} from '#/platform/detection' +import {useLoggedOutViewControls} from '#/state/shell/logged-out' +import {useCloseAllActiveElements} from '#/state/util' +import {Logo} from '#/view/icons/Logo' +import {Logotype} from '#/view/icons/Logotype' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' +import {Text} from '#/components/Typography' + +export function SigninDialog() { + const {signinDialogControl: control} = useGlobalDialogsControlContext() + return ( + <Dialog.Outer control={control}> + <Dialog.Handle /> + <SigninDialogInner control={control} /> + </Dialog.Outer> + ) +} + +function SigninDialogInner({}: {control: Dialog.DialogOuterProps['control']}) { + const t = useTheme() + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + const {requestSwitchToAccount} = useLoggedOutViewControls() + const closeAllActiveElements = useCloseAllActiveElements() + + const showSignIn = React.useCallback(() => { + closeAllActiveElements() + requestSwitchToAccount({requestedAccount: 'none'}) + }, [requestSwitchToAccount, closeAllActiveElements]) + + const showCreateAccount = React.useCallback(() => { + closeAllActiveElements() + requestSwitchToAccount({requestedAccount: 'new'}) + }, [requestSwitchToAccount, closeAllActiveElements]) + + return ( + <Dialog.ScrollableInner + label={_(msg`Sign into Bluesky or create a new account`)} + style={[gtMobile ? {width: 'auto', maxWidth: 420} : a.w_full]}> + <View> + <View + style={[ + a.flex_row, + a.align_center, + a.justify_center, + a.gap_sm, + a.pb_lg, + ]}> + <Logo width={36} /> + <View style={{paddingTop: 6}}> + <Logotype width={120} fill={t.atoms.text.color} /> + </View> + </View> + + <Text style={[a.text_lg, a.text_center, t.atoms.text, a.pb_2xl]}> + <Trans> + Sign in or create your account to join the conversation! + </Trans> + </Text> + + <View style={[a.flex_col, a.gap_md]}> + <Button + variant="solid" + color="primary" + size="large" + onPress={showCreateAccount} + label={_(msg`Create an account`)}> + <ButtonText> + <Trans>Create an account</Trans> + </ButtonText> + </Button> + + <Button + variant="solid" + color="secondary" + size="large" + onPress={showSignIn} + label={_(msg`Sign in`)}> + <ButtonText> + <Trans>Sign in</Trans> + </ButtonText> + </Button> + </View> + + {isNative && <View style={{height: 10}} />} + </View> + + <Dialog.Close /> + </Dialog.ScrollableInner> + ) +} diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index d0fd5e20b..b9145822c 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -18,7 +18,7 @@ import {useModalControls} from '#/state/modals' import {useLabelerSubscriptionMutation} from '#/state/queries/labeler' import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' import {usePreferencesQuery} from '#/state/queries/preferences' -import {useSession} from '#/state/session' +import {useRequireAuth, useSession} from '#/state/session' import {useAnalytics} from 'lib/analytics/analytics' import {useHaptics} from 'lib/haptics' import {useProfileShadow} from 'state/cache/profile-shadow' @@ -64,6 +64,7 @@ let ProfileHeaderLabeler = ({ const {currentAccount, hasSession} = useSession() const {openModal} = useModalControls() const {track} = useAnalytics() + const requireAuth = useRequireAuth() const playHaptic = useHaptics() const cantSubscribePrompt = Prompt.usePromptControl() const isSelf = currentAccount?.did === profile.did @@ -125,27 +126,32 @@ let ProfileHeaderLabeler = ({ }) }, [track, openModal, profile]) - const onPressSubscribe = React.useCallback(async () => { - if (!canSubscribe) { - cantSubscribePrompt.open() - return - } - try { - await toggleSubscription({ - did: profile.did, - subscribe: !isSubscribed, - }) - } catch (e: any) { - // setSubscriptionError(e.message) - logger.error(`Failed to subscribe to labeler`, {message: e.message}) - } - }, [ - toggleSubscription, - isSubscribed, - profile, - canSubscribe, - cantSubscribePrompt, - ]) + const onPressSubscribe = React.useCallback( + () => + requireAuth(async () => { + if (!canSubscribe) { + cantSubscribePrompt.open() + return + } + try { + await toggleSubscription({ + did: profile.did, + subscribe: !isSubscribed, + }) + } catch (e: any) { + // setSubscriptionError(e.message) + logger.error(`Failed to subscribe to labeler`, {message: e.message}) + } + }), + [ + requireAuth, + toggleSubscription, + isSubscribed, + profile, + canSubscribe, + cantSubscribePrompt, + ], + ) const isMe = React.useMemo( () => currentAccount?.did === profile.did, @@ -184,7 +190,6 @@ let ProfileHeaderLabeler = ({ ? _(msg`Unsubscribe from this labeler`) : _(msg`Subscribe to this labeler`) } - disabled={!hasSession} onPress={onPressSubscribe}> {state => ( <View diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index d6c6ff7bd..accef12ed 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -220,7 +220,6 @@ let ProfileHeaderStandard = ({ ? _(msg`Unfollow ${profile.handle}`) : _(msg`Follow ${profile.handle}`) } - disabled={!hasSession} onPress={ profile.viewer?.following ? onPressUnfollow : onPressFollow } diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx index 74674b0cb..e17461588 100644 --- a/src/screens/Signup/index.tsx +++ b/src/screens/Signup/index.tsx @@ -22,6 +22,7 @@ import {StepCaptcha} from '#/screens/Signup/StepCaptcha' import {StepHandle} from '#/screens/Signup/StepHandle' import {StepInfo} from '#/screens/Signup/StepInfo' import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {AppLanguageDropdown} from '#/components/AppLanguageDropdown' import {Button, ButtonText} from '#/components/Button' import {Divider} from '#/components/Divider' import {InlineLinkText} from '#/components/Link' @@ -212,10 +213,14 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { <Divider /> - <View style={[a.w_full, a.py_lg]}> - <Text style={[t.atoms.text_contrast_medium]}> + <View + style={[a.w_full, a.py_lg, a.flex_row, a.gap_lg, a.align_center]}> + <AppLanguageDropdown /> + <Text style={[t.atoms.text, !gtMobile && a.text_md]}> <Trans>Having trouble?</Trans>{' '} - <InlineLinkText to={FEEDBACK_FORM_URL({email: state.email})}> + <InlineLinkText + to={FEEDBACK_FORM_URL({email: state.email})} + style={[!gtMobile && a.text_md]}> <Trans>Contact support</Trans> </InlineLinkText> </Text> diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index c56912491..0d3de8969 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -17,7 +17,7 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {STALE} from '#/state/queries' import {usePreferencesQuery} from '#/state/queries/preferences' -import {getAgent} from '#/state/session' +import {getAgent, useSession} from '#/state/session' import {router} from '#/routes' export type FeedSourceFeedInfo = { @@ -216,17 +216,38 @@ const FOLLOWING_FEED_STUB: FeedSourceInfo = { likeCount: 0, likeUri: '', } +const DISCOVER_FEED_STUB: FeedSourceInfo = { + type: 'feed', + displayName: 'Discover', + uri: '', + route: { + href: '/', + name: 'Home', + params: {}, + }, + cid: '', + avatar: '', + description: new RichText({text: ''}), + creatorDid: '', + creatorHandle: '', + likeCount: 0, + likeUri: '', +} const pinnedFeedInfosQueryKeyRoot = 'pinnedFeedsInfos' export function usePinnedFeedsInfos() { + const {hasSession} = useSession() const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery() const pinnedUris = preferences?.feeds?.pinned ?? [] return useQuery({ staleTime: STALE.INFINITY, enabled: !isLoadingPrefs, - queryKey: [pinnedFeedInfosQueryKeyRoot, pinnedUris.join(',')], + queryKey: [ + pinnedFeedInfosQueryKeyRoot, + (hasSession ? 'authed:' : 'unauthed:') + pinnedUris.join(','), + ], queryFn: async () => { let resolved = new Map() @@ -264,7 +285,7 @@ export function usePinnedFeedsInfos() { ) // The returned result will have the original order. - const result = [FOLLOWING_FEED_STUB] + const result = [hasSession ? FOLLOWING_FEED_STUB : DISCOVER_FEED_STUB] await Promise.allSettled([feedsPromise, ...listsPromises]) for (let pinnedUri of pinnedUris) { if (resolved.has(pinnedUri)) { diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index 5c7cc1591..b88181ebd 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -15,8 +15,8 @@ import {logger} from '#/logger' import {isWeb} from '#/platform/detection' import * as persisted from '#/state/persisted' import {PUBLIC_BSKY_AGENT} from '#/state/queries' -import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' +import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' import {IS_DEV} from '#/env' import {emitSessionDropped} from '../events' import {readLabelers} from './agent-config' @@ -702,8 +702,8 @@ export function useSessionApi() { export function useRequireAuth() { const {hasSession} = useSession() - const {setShowLoggedOut} = useLoggedOutViewControls() const closeAll = useCloseAllActiveElements() + const {signinDialogControl} = useGlobalDialogsControlContext() return React.useCallback( (fn: () => void) => { @@ -711,10 +711,10 @@ export function useRequireAuth() { fn() } else { closeAll() - setShowLoggedOut(true) + signinDialogControl.open() } }, - [hasSession, setShowLoggedOut, closeAll], + [hasSession, signinDialogControl, closeAll], ) } diff --git a/src/view/com/auth/HomeLoggedOutCTA.tsx b/src/view/com/auth/HomeLoggedOutCTA.tsx deleted file mode 100644 index 4c8c35da7..000000000 --- a/src/view/com/auth/HomeLoggedOutCTA.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import React from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {colors, s} from '#/lib/styles' -import {useLoggedOutViewControls} from '#/state/shell/logged-out' -import {TextLink} from '../util/Link' -import {Text} from '../util/text/Text' -import {ScrollView} from '../util/Views' - -export function HomeLoggedOutCTA() { - const pal = usePalette('default') - const {_} = useLingui() - const {isMobile} = useWebMediaQueries() - const {requestSwitchToAccount} = useLoggedOutViewControls() - - const showCreateAccount = React.useCallback(() => { - requestSwitchToAccount({requestedAccount: 'new'}) - }, [requestSwitchToAccount]) - - const showSignIn = React.useCallback(() => { - requestSwitchToAccount({requestedAccount: 'none'}) - }, [requestSwitchToAccount]) - - return ( - <ScrollView style={styles.container} testID="loggedOutCTA"> - <View style={[styles.hero, isMobile && styles.heroMobile]}> - <Text style={[styles.title, pal.link]}> - <Trans>Bluesky</Trans> - </Text> - <Text - style={[ - styles.subtitle, - isMobile && styles.subtitleMobile, - pal.textLight, - ]}> - <Trans>See what's next</Trans> - </Text> - </View> - <View - testID="signinOrCreateAccount" - style={isMobile ? undefined : styles.btnsDesktop}> - <TouchableOpacity - testID="createAccountButton" - style={[ - styles.btn, - isMobile && styles.btnMobile, - {backgroundColor: colors.blue3}, - ]} - onPress={showCreateAccount} - accessibilityRole="button" - accessibilityLabel={_(msg`Create new account`)} - accessibilityHint={_( - msg`Opens flow to create a new Bluesky account`, - )}> - <Text - style={[ - s.white, - styles.btnLabel, - isMobile && styles.btnLabelMobile, - ]}> - <Trans>Create a new account</Trans> - </Text> - </TouchableOpacity> - <TouchableOpacity - testID="signInButton" - style={[styles.btn, isMobile && styles.btnMobile, pal.btn]} - onPress={showSignIn} - accessibilityRole="button" - accessibilityLabel={_(msg`Sign in`)} - accessibilityHint={_( - msg`Opens flow to sign into your existing Bluesky account`, - )}> - <Text - style={[ - pal.text, - styles.btnLabel, - isMobile && styles.btnLabelMobile, - ]}> - <Trans>Sign in</Trans> - </Text> - </TouchableOpacity> - </View> - - <View style={[styles.footer, pal.view, pal.border]}> - <TextLink - type="2xl" - href="https://bsky.social" - text={_(msg`Business`)} - style={[styles.footerLink, pal.link]} - /> - <TextLink - type="2xl" - href="https://bsky.social/about/blog" - text={_(msg`Blog`)} - style={[styles.footerLink, pal.link]} - /> - <TextLink - type="2xl" - href="https://bsky.social/about/join" - text={_(msg`Jobs`)} - style={[styles.footerLink, pal.link]} - /> - </View> - </ScrollView> - ) -} - -const styles = StyleSheet.create({ - container: { - height: '100%', - }, - hero: { - justifyContent: 'center', - paddingTop: 100, - paddingBottom: 30, - }, - heroMobile: { - paddingBottom: 50, - }, - title: { - textAlign: 'center', - fontSize: 68, - fontWeight: 'bold', - }, - subtitle: { - textAlign: 'center', - fontSize: 48, - fontWeight: 'bold', - }, - subtitleMobile: { - fontSize: 42, - }, - btnsDesktop: { - flexDirection: 'row', - justifyContent: 'center', - gap: 20, - marginHorizontal: 20, - }, - btn: { - borderRadius: 32, - width: 230, - paddingVertical: 12, - marginBottom: 20, - }, - btnMobile: { - flex: 1, - width: 'auto', - marginHorizontal: 20, - paddingVertical: 16, - }, - btnLabel: { - textAlign: 'center', - fontSize: 18, - }, - btnLabelMobile: { - textAlign: 'center', - fontSize: 21, - }, - - footer: { - flexDirection: 'row', - gap: 20, - justifyContent: 'center', - }, - footerLink: {}, -}) diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx index 763b01dfa..8eac1ab82 100644 --- a/src/view/com/auth/SplashScreen.tsx +++ b/src/view/com/auth/SplashScreen.tsx @@ -1,19 +1,15 @@ import React from 'react' import {View} from 'react-native' -import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {sanitizeAppLanguageSetting} from '#/locale/helpers' -import {APP_LANGUAGES} from '#/locale/languages' -import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' import {Logo} from '#/view/icons/Logo' import {Logotype} from '#/view/icons/Logotype' import {ErrorBoundary} from 'view/com/util/ErrorBoundary' import {atoms as a, useTheme} from '#/alf' +import {AppLanguageDropdown} from '#/components/AppLanguageDropdown' import {Button, ButtonText} from '#/components/Button' -import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' import {Text} from '#/components/Typography' import {CenteredView} from '../util/Views' @@ -27,22 +23,8 @@ export const SplashScreen = ({ const t = useTheme() const {_} = useLingui() - const langPrefs = useLanguagePrefs() - const setLangPrefs = useLanguagePrefsApi() const insets = useSafeAreaInsets() - const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) - - const onChangeAppLanguage = React.useCallback( - (value: Parameters<PickerSelectProps['onValueChange']>[0]) => { - if (!value) return - if (sanitizedLang !== value) { - setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) - } - }, - [sanitizedLang, setLangPrefs], - ) - return ( <CenteredView style={[a.h_full, a.flex_1]}> <ErrorBoundary> @@ -99,43 +81,7 @@ export const SplashScreen = ({ a.justify_center, a.align_center, ]}> - <View style={a.relative}> - <RNPickerSelect - placeholder={{}} - value={sanitizedLang} - onValueChange={onChangeAppLanguage} - items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ - label: l.name, - value: l.code2, - key: l.code2, - }))} - useNativeAndroidPickerStyle={false} - style={{ - inputAndroid: { - color: t.atoms.text_contrast_medium.color, - fontSize: 16, - paddingRight: 12 + 4, - }, - inputIOS: { - color: t.atoms.text.color, - fontSize: 16, - paddingRight: 12 + 4, - }, - }} - /> - - <View - style={[ - a.absolute, - a.inset_0, - {left: 'auto'}, - {pointerEvents: 'none'}, - a.align_center, - a.justify_center, - ]}> - <ChevronDown fill={t.atoms.text.color} size="xs" /> - </View> - </View> + <AppLanguageDropdown /> </View> <View style={{height: insets.bottom}} /> </ErrorBoundary> diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx index 7a2ee16cf..f905e1e8d 100644 --- a/src/view/com/auth/SplashScreen.web.tsx +++ b/src/view/com/auth/SplashScreen.web.tsx @@ -4,16 +4,13 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {sanitizeAppLanguageSetting} from '#/locale/helpers' -import {APP_LANGUAGES} from '#/locale/languages' -import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {Logo} from '#/view/icons/Logo' import {Logotype} from '#/view/icons/Logotype' import {ErrorBoundary} from 'view/com/util/ErrorBoundary' import {atoms as a, useTheme} from '#/alf' +import {AppLanguageDropdown} from '#/components/AppLanguageDropdown' import {Button, ButtonText} from '#/components/Button' -import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' import {CenteredView} from '../util/Views' @@ -131,23 +128,6 @@ export const SplashScreen = ({ function Footer() { const t = useTheme() - const langPrefs = useLanguagePrefs() - const setLangPrefs = useLanguagePrefsApi() - - const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) - - const onChangeAppLanguage = React.useCallback( - (ev: React.ChangeEvent<HTMLSelectElement>) => { - const value = ev.target.value - - if (!value) return - if (sanitizedLang !== value) { - setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) - } - }, - [sanitizedLang, setLangPrefs], - ) - return ( <View style={[ @@ -174,39 +154,7 @@ function Footer() { <View style={a.flex_1} /> - <View style={[a.flex_row, a.gap_sm, a.align_center, a.flex_shrink]}> - <Text aria-hidden={true} style={t.atoms.text_contrast_medium}> - {APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name} - </Text> - <ChevronDown - fill={t.atoms.text.color} - size="xs" - style={a.flex_shrink} - /> - - <select - value={sanitizedLang} - onChange={onChangeAppLanguage} - style={{ - cursor: 'pointer', - MozAppearance: 'none', - WebkitAppearance: 'none', - appearance: 'none', - position: 'absolute', - inset: 0, - width: '100%', - color: 'transparent', - background: 'transparent', - border: 0, - padding: 0, - }}> - {APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ( - <option key={l.code2} value={l.code2}> - {l.name} - </option> - ))} - </select> - </View> + <AppLanguageDropdown /> </View> ) } diff --git a/src/view/com/home/HomeHeaderLayout.web.tsx b/src/view/com/home/HomeHeaderLayout.web.tsx index 9818b56f6..644d4cab6 100644 --- a/src/view/com/home/HomeHeaderLayout.web.tsx +++ b/src/view/com/home/HomeHeaderLayout.web.tsx @@ -1,20 +1,22 @@ import React from 'react' import {StyleSheet, View} from 'react-native' import Animated from 'react-native-reanimated' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile' -import {Logo} from '#/view/icons/Logo' -import {Link} from '../util/Link' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' -import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + import {CogIcon} from '#/lib/icons' -import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' +import {useSession} from '#/state/session' import {useShellLayout} from '#/state/shell/shell-layout' +import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {Logo} from '#/view/icons/Logo' +import {Link} from '../util/Link' +import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile' export function HomeHeaderLayout(props: { children: React.ReactNode @@ -38,32 +40,35 @@ function HomeHeaderLayoutDesktopAndTablet({ const pal = usePalette('default') const {headerMinimalShellTransform} = useMinimalShellMode() const {headerHeight} = useShellLayout() + const {hasSession} = useSession() const {_} = useLingui() return ( <> - <View style={[pal.view, pal.border, styles.bar, styles.topBar]}> - <Link - href="/settings/following-feed" - hitSlop={10} - accessibilityRole="button" - accessibilityLabel={_(msg`Following Feed Preferences`)} - accessibilityHint=""> - <FontAwesomeIcon - icon="sliders" - style={pal.textLight as FontAwesomeIconStyle} - /> - </Link> - <Logo width={28} /> - <Link - href="/settings/saved-feeds" - hitSlop={10} - accessibilityRole="button" - accessibilityLabel={_(msg`Edit Saved Feeds`)} - accessibilityHint={_(msg`Opens screen to edit Saved Feeds`)}> - <CogIcon size={22} strokeWidth={2} style={pal.textLight} /> - </Link> - </View> + {hasSession && ( + <View style={[pal.view, pal.border, styles.bar, styles.topBar]}> + <Link + href="/settings/following-feed" + hitSlop={10} + accessibilityRole="button" + accessibilityLabel={_(msg`Following Feed Preferences`)} + accessibilityHint=""> + <FontAwesomeIcon + icon="sliders" + style={pal.textLight as FontAwesomeIconStyle} + /> + </Link> + <Logo width={28} /> + <Link + href="/settings/saved-feeds" + hitSlop={10} + accessibilityRole="button" + accessibilityLabel={_(msg`Edit Saved Feeds`)} + accessibilityHint={_(msg`Opens screen to edit Saved Feeds`)}> + <CogIcon size={22} strokeWidth={2} style={pal.textLight} /> + </Link> + </View> + )} {tabBarAnchor} <Animated.View onLayout={e => { diff --git a/src/view/com/home/HomeHeaderLayoutMobile.tsx b/src/view/com/home/HomeHeaderLayoutMobile.tsx index d7b7231c6..78fa9af86 100644 --- a/src/view/com/home/HomeHeaderLayoutMobile.tsx +++ b/src/view/com/home/HomeHeaderLayoutMobile.tsx @@ -1,23 +1,24 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {usePalette} from 'lib/hooks/usePalette' -import {Link} from '../util/Link' +import Animated from 'react-native-reanimated' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' -import {HITSLOP_10} from 'lib/constants' -import Animated from 'react-native-reanimated' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' + +import {useSession} from '#/state/session' import {useSetDrawerOpen} from '#/state/shell/drawer-open' import {useShellLayout} from '#/state/shell/shell-layout' +import {HITSLOP_10} from 'lib/constants' +import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' +import {usePalette} from 'lib/hooks/usePalette' import {isWeb} from 'platform/detection' import {Logo} from '#/view/icons/Logo' - -import {IS_DEV} from '#/env' import {atoms} from '#/alf' -import {Link as Link2} from '#/components/Link' import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette' +import {Link as Link2} from '#/components/Link' +import {IS_DEV} from '#/env' +import {Link} from '../util/Link' export function HomeHeaderLayoutMobile({ children, @@ -30,6 +31,7 @@ export function HomeHeaderLayoutMobile({ const setDrawerOpen = useSetDrawerOpen() const {headerHeight} = useShellLayout() const {headerMinimalShellTransform} = useMinimalShellMode() + const {hasSession} = useSession() const onPressAvi = React.useCallback(() => { setDrawerOpen(true) @@ -76,18 +78,20 @@ export function HomeHeaderLayoutMobile({ <ColorPalette size="md" /> </Link2> )} - <Link - testID="viewHeaderHomeFeedPrefsBtn" - href="/settings/following-feed" - hitSlop={HITSLOP_10} - accessibilityRole="button" - accessibilityLabel={_(msg`Following Feed Preferences`)} - accessibilityHint=""> - <FontAwesomeIcon - icon="sliders" - style={pal.textLight as FontAwesomeIconStyle} - /> - </Link> + {hasSession && ( + <Link + testID="viewHeaderHomeFeedPrefsBtn" + href="/settings/following-feed" + hitSlop={HITSLOP_10} + accessibilityRole="button" + accessibilityLabel={_(msg`Following Feed Preferences`)} + accessibilityHint=""> + <FontAwesomeIcon + icon="sliders" + style={pal.textLight as FontAwesomeIconStyle} + /> + </Link> + )} </View> </View> {children} diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 2e3bf08db..e64ab08df 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -1,52 +1,53 @@ import React from 'react' import { ActivityIndicator, - StyleSheet, - View, type FlatList, Pressable, + StyleSheet, + View, } from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' -import {ViewHeader} from 'view/com/util/ViewHeader' -import {FAB} from 'view/com/util/fab/FAB' -import {Link} from 'view/com/util/Link' -import {NativeStackScreenProps, FeedsTabNavigatorParams} from 'lib/routes/types' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {ComposeIcon2, CogIcon, MagnifyingGlassIcon2} from 'lib/icons' -import {s} from 'lib/styles' -import {atoms as a, useTheme} from '#/alf' -import {SearchInput, SearchInputRef} from 'view/com/util/forms/SearchInput' -import {UserAvatar} from 'view/com/util/UserAvatar' -import { - LoadingPlaceholder, - FeedFeedLoadingPlaceholder, -} from 'view/com/util/LoadingPlaceholder' -import {ErrorMessage} from 'view/com/util/error/ErrorMessage' -import debounce from 'lodash.debounce' -import {Text} from 'view/com/util/text/Text' -import {List} from 'view/com/util/List' -import {useFocusEffect} from '@react-navigation/native' -import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' -import {Trans, msg} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useSetMinimalShellMode} from '#/state/shell' -import {usePreferencesQuery} from '#/state/queries/preferences' +import {useFocusEffect} from '@react-navigation/native' +import debounce from 'lodash.debounce' + +import {isNative, isWeb} from '#/platform/detection' import { + getAvatarTypeFromUri, useFeedSourceInfoQuery, useGetPopularFeedsQuery, useSearchPopularFeedsMutation, - getAvatarTypeFromUri, } from '#/state/queries/feed' -import {cleanError} from 'lib/strings/errors' -import {useComposerControls} from '#/state/shell/composer' +import {usePreferencesQuery} from '#/state/queries/preferences' import {useSession} from '#/state/session' -import {isNative, isWeb} from '#/platform/detection' +import {useSetMinimalShellMode} from '#/state/shell' +import {useComposerControls} from '#/state/shell/composer' import {HITSLOP_10} from 'lib/constants' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {CogIcon, ComposeIcon2, MagnifyingGlassIcon2} from 'lib/icons' +import {FeedsTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {cleanError} from 'lib/strings/errors' +import {s} from 'lib/styles' +import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' +import {ErrorMessage} from 'view/com/util/error/ErrorMessage' +import {FAB} from 'view/com/util/fab/FAB' +import {SearchInput, SearchInputRef} from 'view/com/util/forms/SearchInput' +import {Link} from 'view/com/util/Link' +import {List} from 'view/com/util/List' +import { + FeedFeedLoadingPlaceholder, + LoadingPlaceholder, +} from 'view/com/util/LoadingPlaceholder' +import {Text} from 'view/com/util/text/Text' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {ViewHeader} from 'view/com/util/ViewHeader' +import {atoms as a, useTheme} from '#/alf' import {IconCircle} from '#/components/IconCircle' -import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle' import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass' +import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle' type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'> @@ -100,6 +101,22 @@ type FlatlistSlice = key: string } +// HACK +// the protocol doesn't yet tell us which feeds are personalized +// this list is used to filter out feed recommendations from logged out users +// for the ones we know need it +// -prf +const KNOWN_AUTHED_ONLY_FEEDS = [ + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app + 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed + 'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed + 'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow + 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz + 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky + 'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz + 'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why +] + export function FeedsScreen(_props: Props) { const pal = usePalette('default') const {openComposer} = useComposerControls() @@ -299,7 +316,15 @@ export function FeedsScreen(_props: Props) { for (const page of popularFeeds.pages || []) { slices = slices.concat( page.feeds - .filter(feed => !preferences?.feeds?.saved.includes(feed.uri)) + .filter(feed => { + if ( + !hasSession && + KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri) + ) { + return false + } + return !preferences?.feeds?.saved.includes(feed.uri) + }) .map(feed => ({ key: `popularFeed:${feed.uri}`, type: 'popularFeed', diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 39bdac669..7a2a88265 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -2,6 +2,7 @@ import React from 'react' import {ActivityIndicator, AppState, StyleSheet, View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' +import {PROD_DEFAULT_FEED} from '#/lib/constants' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useSetTitle} from '#/lib/hooks/useSetTitle' import {logEvent, LogEvents, useGate} from '#/lib/statsig/statsig' @@ -19,7 +20,6 @@ import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState' import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState' import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed' -import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA' import {HomeHeader} from '../com/home/HomeHeader' type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> @@ -231,7 +231,12 @@ function HomeScreenReady({ onPageSelected={onPageSelected} onPageScrollStateChanged={onPageScrollStateChanged} renderTabBar={renderTabBar}> - <HomeLoggedOutCTA /> + <FeedPage + testID="customFeedPage" + isPageFocused + feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`} + renderEmptyState={renderCustomFeedEmptyState} + /> </Pager> ) } diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index c391f8050..f71e1330e 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -184,8 +184,7 @@ function ProfileScreenLoaded({ const showRepliesTab = hasSession const showMediaTab = !hasLabeler const showLikesTab = isMe - const showFeedsTab = - hasSession && (isMe || (profile.associated?.feedgens || 0) > 0) + const showFeedsTab = isMe || (profile.associated?.feedgens || 0) > 0 const showListsTab = hasSession && (isMe || (profile.associated?.lists || 0) > 0) diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index 8a7fa5e71..b97faafad 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -71,6 +71,7 @@ import {UserAvatar} from 'view/com/util/UserAvatar' import {ScrollView} from 'view/com/util/Views' import {useDialogControl} from '#/components/Dialog' import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' +import {navigate, resetToTab} from '#/Navigation' import {ExportCarDialog} from './ExportCarDialog' function SettingsAccountCard({account}: {account: SessionAccount}) { @@ -104,7 +105,14 @@ function SettingsAccountCard({account}: {account: SessionAccount}) { <TouchableOpacity testID="signOutBtn" onPress={() => { - logout('Settings') + if (isNative) { + logout('Settings') + resetToTab('HomeTab') + } else { + navigate('Home').then(() => { + logout('Settings') + }) + } }} accessibilityRole="button" accessibilityLabel={_(msg`Sign out`)} diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 1bf5647f6..a7342179d 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -9,49 +9,49 @@ import { View, ViewStyle, } from 'react-native' -import {useNavigation, StackActions} from '@react-navigation/native' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' -import {s, colors} from 'lib/styles' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {StackActions, useNavigation} from '@react-navigation/native' + +import {emitSoftReset} from '#/state/events' +import {useUnreadNotifications} from '#/state/queries/notifications/unread' +import {useProfileQuery} from '#/state/queries/profile' +import {SessionAccount, useSession} from '#/state/session' +import {useSetDrawerOpen} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants' +import {useNavigationTabState} from 'lib/hooks/useNavigationTabState' +import {usePalette} from 'lib/hooks/usePalette' import { - HomeIcon, - HomeIconSolid, BellIcon, BellIconSolid, - UserIcon, CogIcon, + HandIcon, + HashtagIcon, + HomeIcon, + HomeIconSolid, + ListIcon, MagnifyingGlassIcon2, MagnifyingGlassIcon2Solid, + UserIcon, UserIconSolid, - HashtagIcon, - ListIcon, - HandIcon, } from 'lib/icons' -import {UserAvatar} from 'view/com/util/UserAvatar' -import {Text} from 'view/com/util/text/Text' -import {useTheme} from 'lib/ThemeContext' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {pluralize} from 'lib/strings/helpers' import {getTabState, TabState} from 'lib/routes/helpers' import {NavigationProp} from 'lib/routes/types' -import {useNavigationTabState} from 'lib/hooks/useNavigationTabState' +import {pluralize} from 'lib/strings/helpers' +import {colors, s} from 'lib/styles' +import {useTheme} from 'lib/ThemeContext' import {isWeb} from 'platform/detection' -import {formatCountShortOnly} from 'view/com/util/numeric/format' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useSetDrawerOpen} from '#/state/shell' -import {useSession, SessionAccount} from '#/state/session' -import {useProfileQuery} from '#/state/queries/profile' -import {useUnreadNotifications} from '#/state/queries/notifications/unread' -import {emitSoftReset} from '#/state/events' import {NavSignupCard} from '#/view/shell/NavSignupCard' -import {TextLink} from '../com/util/Link' - +import {formatCountShortOnly} from 'view/com/util/numeric/format' +import {Text} from 'view/com/util/text/Text' +import {UserAvatar} from 'view/com/util/UserAvatar' import {useTheme as useAlfTheme} from '#/alf' +import {TextLink} from '../com/util/Link' let DrawerProfileCard = ({ account, @@ -246,7 +246,11 @@ let DrawerContent = ({}: {}): React.ReactNode => { <SettingsMenuItem onPress={onPressSettings} /> </> ) : ( - <SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} /> + <> + <HomeMenuItem isActive={isAtHome} onPress={onPressHome} /> + <FeedsMenuItem isActive={isAtFeeds} onPress={onPressMyFeeds} /> + <SearchMenuItem isActive={isAtSearch} onPress={onPressSearch} /> + </> )} <View style={styles.smallSpacer} /> diff --git a/src/view/shell/NavSignupCard.tsx b/src/view/shell/NavSignupCard.tsx index 83d141498..aa807f0cc 100644 --- a/src/view/shell/NavSignupCard.tsx +++ b/src/view/shell/NavSignupCard.tsx @@ -3,13 +3,16 @@ import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {s} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {Text} from '#/view/com/util/text/Text' -import {Button} from '#/view/com/util/forms/Button' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' +import {usePalette} from 'lib/hooks/usePalette' +import {s} from 'lib/styles' +import {Button} from '#/view/com/util/forms/Button' +import {Text} from '#/view/com/util/text/Text' import {Logo} from '#/view/icons/Logo' +import {atoms as a} from '#/alf' +import {AppLanguageDropdown} from '#/components/AppLanguageDropdown' +import {Link} from '#/components/Link' let NavSignupCard = ({}: {}): React.ReactNode => { const {_} = useLingui() @@ -35,7 +38,9 @@ let NavSignupCard = ({}: {}): React.ReactNode => { paddingTop: 6, marginBottom: 24, }}> - <Logo width={48} /> + <Link to="/" label="Bluesky - Home"> + <Logo width={48} /> + </Link> <View style={{paddingTop: 18}}> <Text type="md-bold" style={[pal.text]}> @@ -62,6 +67,10 @@ let NavSignupCard = ({}: {}): React.ReactNode => { </Text> </Button> </View> + + <View style={[a.pt_2xl, a.w_full]}> + <AppLanguageDropdown /> + </View> </View> ) } diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index f29183095..c554112ed 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -1,37 +1,39 @@ import React from 'react' -import {StatusBar} from 'expo-status-bar' import { + BackHandler, DimensionValue, StyleSheet, useWindowDimensions, View, - BackHandler, } from 'react-native' -import {useSafeAreaInsets} from 'react-native-safe-area-context' import {Drawer} from 'react-native-drawer-layout' +import Animated from 'react-native-reanimated' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {StatusBar} from 'expo-status-bar' import {useNavigationState} from '@react-navigation/native' -import {ModalsContainer} from 'view/com/modals/Modal' -import {Lightbox} from 'view/com/lightbox/Lightbox' -import {ErrorBoundary} from 'view/com/util/ErrorBoundary' -import {DrawerContent} from './Drawer' -import {Composer} from './Composer' -import {useTheme} from 'lib/ThemeContext' -import {usePalette} from 'lib/hooks/usePalette' -import {RoutesContainer, TabsNavigator} from '../../Navigation' -import {isStateAtTabRoot} from 'lib/routes/helpers' + +import {useSession} from '#/state/session' import { useIsDrawerOpen, - useSetDrawerOpen, useIsDrawerSwipeDisabled, + useSetDrawerOpen, } from '#/state/shell' -import {isAndroid} from 'platform/detection' -import {useSession} from '#/state/session' import {useCloseAnyActiveElement} from '#/state/util' +import {usePalette} from 'lib/hooks/usePalette' import * as notifications from 'lib/notifications/notifications' -import {Outlet as PortalOutlet} from '#/components/Portal' -import {MutedWordsDialog} from '#/components/dialogs/MutedWords' +import {isStateAtTabRoot} from 'lib/routes/helpers' +import {useTheme} from 'lib/ThemeContext' +import {isAndroid} from 'platform/detection' import {useDialogStateContext} from 'state/dialogs' -import Animated from 'react-native-reanimated' +import {Lightbox} from 'view/com/lightbox/Lightbox' +import {ModalsContainer} from 'view/com/modals/Modal' +import {ErrorBoundary} from 'view/com/util/ErrorBoundary' +import {MutedWordsDialog} from '#/components/dialogs/MutedWords' +import {SigninDialog} from '#/components/dialogs/Signin' +import {Outlet as PortalOutlet} from '#/components/Portal' +import {RoutesContainer, TabsNavigator} from '../../Navigation' +import {Composer} from './Composer' +import {DrawerContent} from './Drawer' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -101,6 +103,7 @@ function ShellInner() { <Composer winHeight={winDim.height} /> <ModalsContainer /> <MutedWordsDialog /> + <SigninDialog /> <Lightbox /> <PortalOutlet /> </> diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 02993ac46..51fb4a0a1 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -1,24 +1,25 @@ import React, {useEffect} from 'react' -import {View, StyleSheet, TouchableOpacity} from 'react-native' -import {useNavigation} from '@react-navigation/native' +import {StyleSheet, TouchableOpacity, View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' -import {ErrorBoundary} from '../com/util/ErrorBoundary' +import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' +import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' +import {useCloseAllActiveElements} from '#/state/util' +import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' +import {NavigationProp} from 'lib/routes/types' +import {colors, s} from 'lib/styles' +import {MutedWordsDialog} from '#/components/dialogs/MutedWords' +import {SigninDialog} from '#/components/dialogs/Signin' +import {Outlet as PortalOutlet} from '#/components/Portal' +import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries' +import {FlatNavigator, RoutesContainer} from '../../Navigation' import {Lightbox} from '../com/lightbox/Lightbox' import {ModalsContainer} from '../com/modals/Modal' +import {ErrorBoundary} from '../com/util/ErrorBoundary' import {Composer} from './Composer.web' -import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' -import {s, colors} from 'lib/styles' -import {RoutesContainer, FlatNavigator} from '../../Navigation' import {DrawerContent} from './Drawer' -import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries' -import {NavigationProp} from 'lib/routes/types' -import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' -import {useCloseAllActiveElements} from '#/state/util' -import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' -import {Outlet as PortalOutlet} from '#/components/Portal' -import {MutedWordsDialog} from '#/components/dialogs/MutedWords' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -45,6 +46,7 @@ function ShellInner() { <Composer winHeight={0} /> <ModalsContainer /> <MutedWordsDialog /> + <SigninDialog /> <Lightbox /> <PortalOutlet /> |