From e81bb86682ad73023a70a601feff5b47c27f5a8f Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 10 Apr 2024 14:59:54 +0100 Subject: [PATCH] [iPad support] Custom tab navigator for native tablet, with side nav (#3385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix fab position * show left nav * custom tab navigator for tablets * re-disable right nav on tablet * fix unread count not being rounded * use mobile subpage header on native tablet * fix feed being hidden under header * rename misleading variable name * remove drawer on native tablet, as on web tablet * fix prompt styles on tablet * import isWeb (ts-ignore 😡) * disable emoji picker on tablet * prettier format --- src/Navigation.tsx | 350 +++++++++++++----- src/components/Prompt.tsx | 9 +- src/lib/routes/types.ts | 12 +- src/view/com/composer/Composer.tsx | 10 +- src/view/com/home/HomeHeaderLayoutMobile.tsx | 57 +-- src/view/com/profile/ProfileSubpageHeader.tsx | 37 +- src/view/com/util/SimpleViewHeader.tsx | 13 +- src/view/com/util/fab/FABInner.tsx | 4 +- .../com/util/load-latest/LoadLatestBtn.tsx | 31 +- src/view/screens/Search/Search.tsx | 6 +- .../createNativeStackNavigatorWithAuth.tsx | 18 +- src/view/shell/desktop/LeftNav.tsx | 89 ++--- src/view/shell/desktop/RightNav.tsx | 40 +- src/view/shell/index.tsx | 57 +-- 14 files changed, 462 insertions(+), 271 deletions(-) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index ab40ff4220..bff1c43568 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import {JSX} from 'react/jsx-runtime' +import {View} from 'react-native' import {i18n, MessageDescriptor} from '@lingui/core' import {msg} from '@lingui/macro' import { @@ -15,11 +16,12 @@ import { StackActions, } from '@react-navigation/native' -import {timeout} from 'lib/async/timeout' -import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' -import {usePalette} from 'lib/hooks/usePalette' -import {buildStateObject} from 'lib/routes/helpers' -import { +import {init as initAnalytics} from '#/lib/analytics/analytics' +import {timeout} from '#/lib/async/timeout' +import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' +import {useWebScrollRestoration} from '#/lib/hooks/useWebScrollRestoration' +import {buildStateObject} from '#/lib/routes/helpers' +import type { AllNavigatorParams, BottomTabNavigatorParams, FeedsTabNavigatorParams, @@ -28,59 +30,60 @@ import { MyProfileTabNavigatorParams, NotificationsTabNavigatorParams, SearchTabNavigatorParams, -} from 'lib/routes/types' -import {RouteParams, State} from 'lib/routes/types' -import {bskyTitle} from 'lib/strings/headings' -import {isAndroid, isNative} from 'platform/detection' -import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds' -import {AppPasswords} from 'view/screens/AppPasswords' -import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts' -import {ModerationMutedAccounts} from 'view/screens/ModerationMutedAccounts' -import {PreferencesFollowingFeed} from 'view/screens/PreferencesFollowingFeed' -import {PreferencesThreads} from 'view/screens/PreferencesThreads' -import {SavedFeeds} from 'view/screens/SavedFeeds' -import HashtagScreen from '#/screens/Hashtag' -import {ModerationScreen} from '#/screens/Moderation' -import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' -import {init as initAnalytics} from './lib/analytics/analytics' -import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration' -import {attachRouteToLogEvents, logEvent} from './lib/statsig/statsig' -import {router} from './routes' -import {useModalControls} from './state/modals' -import {useUnreadNotifications} from './state/queries/notifications/unread' -import {useSession} from './state/session' + SideTabNavigatorParams, +} from '#/lib/routes/types' +import {RouteParams, State} from '#/lib/routes/types' +import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig' +import {bskyTitle} from '#/lib/strings/headings' +import {isAndroid, isNative, isNativeTablet} from '#/platform/detection' +import {useModalControls} from '#/state/modals' +import {useUnreadNotifications} from '#/state/queries/notifications/unread' +import {useSession} from '#/state/session' import { setEmailConfirmationRequested, shouldRequestEmailConfirmation, -} from './state/shell/reminders' -import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines' -import {CopyrightPolicyScreen} from './view/screens/CopyrightPolicy' -import {DebugModScreen} from './view/screens/DebugMod' -import {FeedsScreen} from './view/screens/Feeds' -import {HomeScreen} from './view/screens/Home' -import {LanguageSettingsScreen} from './view/screens/LanguageSettings' -import {ListsScreen} from './view/screens/Lists' -import {LogScreen} from './view/screens/Log' -import {ModerationModlistsScreen} from './view/screens/ModerationModlists' -import {NotFoundScreen} from './view/screens/NotFound' -import {NotificationsScreen} from './view/screens/Notifications' -import {PostLikedByScreen} from './view/screens/PostLikedBy' -import {PostRepostedByScreen} from './view/screens/PostRepostedBy' -import {PostThreadScreen} from './view/screens/PostThread' -import {PrivacyPolicyScreen} from './view/screens/PrivacyPolicy' -import {ProfileScreen} from './view/screens/Profile' -import {ProfileFeedScreen} from './view/screens/ProfileFeed' -import {ProfileFeedLikedByScreen} from './view/screens/ProfileFeedLikedBy' -import {ProfileFollowersScreen} from './view/screens/ProfileFollowers' -import {ProfileFollowsScreen} from './view/screens/ProfileFollows' -import {ProfileListScreen} from './view/screens/ProfileList' -import {SearchScreen} from './view/screens/Search' -import {SettingsScreen} from './view/screens/Settings' -import {Storybook} from './view/screens/Storybook' -import {SupportScreen} from './view/screens/Support' -import {TermsOfServiceScreen} from './view/screens/TermsOfService' -import {BottomBar} from './view/shell/bottom-bar/BottomBar' -import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth' +} from '#/state/shell/reminders' +import {AppPasswords} from '#/view/screens/AppPasswords' +import {CommunityGuidelinesScreen} from '#/view/screens/CommunityGuidelines' +import {CopyrightPolicyScreen} from '#/view/screens/CopyrightPolicy' +import {DebugModScreen} from '#/view/screens/DebugMod' +import {FeedsScreen} from '#/view/screens/Feeds' +import {HomeScreen} from '#/view/screens/Home' +import {LanguageSettingsScreen} from '#/view/screens/LanguageSettings' +import {ListsScreen} from '#/view/screens/Lists' +import {LogScreen} from '#/view/screens/Log' +import {ModerationBlockedAccounts} from '#/view/screens/ModerationBlockedAccounts' +import {ModerationModlistsScreen} from '#/view/screens/ModerationModlists' +import {ModerationMutedAccounts} from '#/view/screens/ModerationMutedAccounts' +import {NotFoundScreen} from '#/view/screens/NotFound' +import {NotificationsScreen} from '#/view/screens/Notifications' +import {PostLikedByScreen} from '#/view/screens/PostLikedBy' +import {PostRepostedByScreen} from '#/view/screens/PostRepostedBy' +import {PostThreadScreen} from '#/view/screens/PostThread' +import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds' +import {PreferencesFollowingFeed} from '#/view/screens/PreferencesFollowingFeed' +import {PreferencesThreads} from '#/view/screens/PreferencesThreads' +import {PrivacyPolicyScreen} from '#/view/screens/PrivacyPolicy' +import {ProfileScreen} from '#/view/screens/Profile' +import {ProfileFeedScreen} from '#/view/screens/ProfileFeed' +import {ProfileFeedLikedByScreen} from '#/view/screens/ProfileFeedLikedBy' +import {ProfileFollowersScreen} from '#/view/screens/ProfileFollowers' +import {ProfileFollowsScreen} from '#/view/screens/ProfileFollows' +import {ProfileListScreen} from '#/view/screens/ProfileList' +import {SavedFeeds} from '#/view/screens/SavedFeeds' +import {SearchScreen} from '#/view/screens/Search' +import {SettingsScreen} from '#/view/screens/Settings' +import {Storybook} from '#/view/screens/Storybook' +import {SupportScreen} from '#/view/screens/Support' +import {TermsOfServiceScreen} from '#/view/screens/TermsOfService' +import {BottomBar} from '#/view/shell/bottom-bar/BottomBar' +import {createNativeStackNavigatorWithAuth} from '#/view/shell/createNativeStackNavigatorWithAuth' +import {LeftNav} from '#/view/shell/desktop/LeftNav' +import HashtagScreen from '#/screens/Hashtag' +import {ModerationScreen} from '#/screens/Moderation' +import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' +import {atoms as a, useTheme} from '#/alf' +import {router} from '#/routes' const navigationRef = createNavigationContainerRef() @@ -91,15 +94,23 @@ const NotificationsTab = createNativeStackNavigatorWithAuth() const MyProfileTab = createNativeStackNavigatorWithAuth() +const ListsTab = createNativeStackNavigatorWithAuth() +const ModerationTab = createNativeStackNavigatorWithAuth() +const SettingsTab = createNativeStackNavigatorWithAuth() const Flat = createNativeStackNavigatorWithAuth() -const Tab = createBottomTabNavigator() + +const MobileTab = createBottomTabNavigator() +const TabletTab = createBottomTabNavigator() /** * These "common screens" are reused across stacks. */ -function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { +function commonScreens( + Stack: typeof HomeTab, + opts?: {unreadCountLabel?: string}, +) { const title = (page: MessageDescriptor) => - bskyTitle(i18n._(page), unreadCountLabel) + bskyTitle(i18n._(page), opts?.unreadCountLabel) return ( <> @@ -108,16 +119,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { getComponent={() => NotFoundScreen} options={{title: title(msg`Not Found`)}} /> - - ModerationScreen} - options={{title: title(msg`Moderation`), requireAuth: true}} - /> ModerationModlistsScreen} @@ -133,11 +134,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { getComponent={() => ModerationBlockedAccounts} options={{title: title(msg`Blocked Accounts`), requireAuth: true}} /> - SettingsScreen} - options={{title: title(msg`Settings`), requireAuth: true}} - /> LanguageSettingsScreen} @@ -147,7 +143,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { name="Profile" getComponent={() => ProfileScreen} options={({route}) => ({ - title: bskyTitle(`@${route.params.name}`, unreadCountLabel), + title: bskyTitle(`@${route.params.name}`, opts?.unreadCountLabel), })} /> HashtagScreen} options={{title: title(msg`Hashtag`)}} /> + {!isNativeTablet && ( + <> + ModerationScreen} + options={{title: title(msg`Moderation`), requireAuth: true}} + /> + + SettingsScreen} + options={{title: title(msg`Settings`), requireAuth: true}} + /> + + )} ) } /** * The TabsNavigator is used by native mobile to represent the routes - * in 3 distinct tab-stacks with a different root screen on each. + * in the distinct tab-stacks with a different root screen on each. */ function TabsNavigator() { + return isNativeTablet ? : +} + +function TabsNavigatorMobile() { const tabBar = React.useCallback( (props: JSX.IntrinsicAttributes & BottomTabBarProps) => ( @@ -298,28 +317,88 @@ function TabsNavigator() { ) return ( - - HomeTabNavigator} /> - SearchTabNavigator} /> - FeedsTabNavigator} /> - HomeTabNavigator} /> + SearchTabNavigator} + /> + FeedsTabNavigator} + /> + NotificationsTabNavigator} /> - MyProfileTabNavigator} /> - + + ) +} + +/** + * The TabNavigatorTablet is used by native tablet to represent the routes + * in all the extra tabs shown in the tablet left nav. + */ +function TabNavigatorTablet() { + // we can't style the wrapper component of the tab navigator + // to set it to flex_row, so put the left nav outside of the tab navigator + const tabBar = React.useCallback(() => null, []) + + return ( + + + + HomeTabNavigator} + /> + SearchTabNavigator} + /> + FeedsTabNavigator} + /> + NotificationsTabNavigator} + /> + ListsTabNavigator} + /> + ModerationTabNavigator} + /> + MyProfileTabNavigator} + /> + SettingsTabNavigator} + /> + + ) } function HomeTabNavigator() { - const pal = usePalette('default') + const t = useTheme() return ( SearchScreen} /> {commonScreens(SearchTab as typeof HomeTab)} @@ -360,7 +439,7 @@ function SearchTabNavigator() { } function FeedsTabNavigator() { - const pal = usePalette('default') + const t = useTheme() return ( + ListsScreen} + options={{requireAuth: true}} + /> + {commonScreens(ListsTab as typeof HomeTab)} + + ) +} + +function ModerationTabNavigator() { + const t = useTheme() + return ( + + ModerationScreen} + options={{requireAuth: true}} + /> + {commonScreens(ModerationTab as typeof HomeTab)} + + ) +} + +function SettingsTabNavigator() { + const t = useTheme() + return ( + + SettingsScreen} + options={{requireAuth: true}} + /> + {commonScreens(SettingsTab as typeof HomeTab)} + + ) +} + /** * The FlatNavigator is used by Web to represent the routes * in a single ("flat") stack. */ const FlatNavigator = () => { - const pal = usePalette('default') + const t = useTheme() const numUnread = useUnreadNotifications() const screenListeners = useWebScrollRestoration() const title = (page: MessageDescriptor) => bskyTitle(i18n._(page), numUnread) @@ -446,7 +591,7 @@ const FlatNavigator = () => { fullScreenGestureEnabled: true, headerShown: false, animationDuration: 250, - contentStyle: pal.view, + contentStyle: t.atoms.bg, }}> { getComponent={() => NotificationsScreen} options={{title: title(msg`Notifications`), requireAuth: true}} /> - {commonScreens(Flat as typeof HomeTab, numUnread)} + {commonScreens(Flat as typeof HomeTab, {unreadCountLabel: numUnread})} ) } @@ -515,12 +660,26 @@ const LINKING = { if (name === 'Search') { return buildStateObject('SearchTab', 'Search', params) } + if (name === 'Feeds') { + return buildStateObject('FeedsTab', 'Feeds', params) + } if (name === 'Notifications') { return buildStateObject('NotificationsTab', 'Notifications', params) } if (name === 'Home') { return buildStateObject('HomeTab', 'Home', params) } + if (isNativeTablet) { + if (name === 'Lists') { + return buildStateObject('ListsTab', 'Lists', params) + } + if (name === 'Moderation') { + return buildStateObject('ModerationTab', 'Moderation', params) + } + if (name === 'Settings') { + return buildStateObject('SettingsTab', 'Settings', params) + } + } // if the path is something else, like a post, profile, or even settings, we need to initialize the home tab as pre-existing state otherwise the back button will not work return buildStateObject('HomeTab', name, params, [ { @@ -703,5 +862,6 @@ export { reset, resetToTab, RoutesContainer, + TabNavigatorTablet, TabsNavigator, } diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx index c92fe26523..8b8ec8a46f 100644 --- a/src/components/Prompt.tsx +++ b/src/components/Prompt.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {isNativeTablet} from '#/platform/detection' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button, ButtonColor, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' @@ -43,7 +44,11 @@ export function Outer({ + style={[ + gtMobile && !isNativeTablet + ? {width: 'auto', maxWidth: 400} + : a.w_full, + ]}> {children} @@ -81,7 +86,7 @@ export function Actions({children}: React.PropsWithChildren<{}>) { a.w_full, a.gap_md, a.justify_end, - gtMobile + gtMobile && !isNativeTablet ? [a.flex_row, a.flex_row_reverse, a.justify_start] : [a.flex_col], ]}> diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 95af2f237d..db2044bfc0 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -47,6 +47,17 @@ export type BottomTabNavigatorParams = CommonNavigatorParams & { MyProfileTab: undefined } +export type SideTabNavigatorParams = CommonNavigatorParams & { + HomeTab: undefined + SearchTab: undefined + FeedsTab: undefined + NotificationsTab: undefined + MyProfileTab: undefined + ListsTab: undefined + ModerationTab: undefined + SettingsTab: undefined +} + export type HomeTabNavigatorParams = CommonNavigatorParams & { Home: undefined } @@ -72,7 +83,6 @@ export type FlatNavigatorParams = CommonNavigatorParams & { Search: {q?: string} Feeds: undefined Notifications: undefined - Hashtag: {tag: string; author?: string} } export type AllNavigatorParams = CommonNavigatorParams & { diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 0b4d68b075..d4a2353f12 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -44,13 +44,7 @@ import {insertMentionAt} from 'lib/strings/mention-manip' import {shortenLinks} from 'lib/strings/rich-text-manip' import {toShortUrl} from 'lib/strings/url-helpers' import {colors, gradients, s} from 'lib/styles' -import { - isAndroid, - isIOS, - isNative, - isNativeTablet, - isWeb, -} from 'platform/detection' +import {isAndroid, isIOS, isNative, isWeb} from 'platform/detection' import {useDialogStateControlContext} from 'state/dialogs' import {GalleryModel} from 'state/models/media/gallery' import {ComposerOpts} from 'state/shell/composer' @@ -490,7 +484,7 @@ export const ComposePost = observer(function ComposePost({ ) : null} - {!(isMobile || isNativeTablet) ? ( + {!isMobile && !isNative ? ( - - - - - + {!isNativeTablet ? ( + + + + + + ) : ( + // subtract width of LeftNav + + )} diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index eaf00f3e66..b715008477 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -1,24 +1,25 @@ import React from 'react' import {Pressable, StyleSheet, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' + +import {emitSoftReset} from '#/state/events' +import {ImagesLightbox, useLightboxControls} from '#/state/lightbox' +import {useSetDrawerOpen} from '#/state/shell' +import {BACK_HITSLOP} from 'lib/constants' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {Text} from '../util/text/Text' -import {TextLink} from '../util/Link' -import {UserAvatar, UserAvatarType} from '../util/UserAvatar' -import {LoadingPlaceholder} from '../util/LoadingPlaceholder' -import {CenteredView} from '../util/Views' -import {sanitizeHandle} from 'lib/strings/handles' import {makeProfileLink} from 'lib/routes/links' import {NavigationProp} from 'lib/routes/types' -import {BACK_HITSLOP} from 'lib/constants' -import {isNative} from 'platform/detection' -import {useLightboxControls, ImagesLightbox} from '#/state/lightbox' -import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' -import {useSetDrawerOpen} from '#/state/shell' -import {emitSoftReset} from '#/state/events' +import {sanitizeHandle} from 'lib/strings/handles' +import {isNative, isNativeTablet} from 'platform/detection' +import {TextLink} from '../util/Link' +import {LoadingPlaceholder} from '../util/LoadingPlaceholder' +import {Text} from '../util/text/Text' +import {UserAvatar, UserAvatarType} from '../util/UserAvatar' +import {CenteredView} from '../util/Views' export function ProfileSubpageHeader({ isLoading, @@ -71,9 +72,11 @@ export function ProfileSubpageHeader({ } }, [openLightbox, avatar]) + const isMobileLayout = isMobile || isNativeTablet + return ( - {isMobile && ( + {isMobileLayout && ( @@ -119,7 +122,7 @@ export function ProfileSubpageHeader({ gap: 10, paddingTop: 14, paddingBottom: 6, - paddingHorizontal: isMobile ? 12 : 14, + paddingHorizontal: isMobileLayout ? 12 : 14, }}> )} - {!isMobile && ( + {!isMobileLayout && ( - ) : ( + ) : !isNativeTablet ? ( - )} + ) : null} ) : null} {children} diff --git a/src/view/com/util/fab/FABInner.tsx b/src/view/com/util/fab/FABInner.tsx index cdf11c76e4..a01756da06 100644 --- a/src/view/com/util/fab/FABInner.tsx +++ b/src/view/com/util/fab/FABInner.tsx @@ -4,7 +4,7 @@ import Animated from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {LinearGradient} from 'expo-linear-gradient' -import {isNativeTablet, isWeb} from '#/platform/detection' +import {isWeb} from '#/platform/detection' import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {clamp} from 'lib/numbers' @@ -40,7 +40,7 @@ export function FABInner({testID, icon, ...props}: FABProps) { styles.outer, size, tabletSpacing, - (isMobile || isNativeTablet) && fabMinimalShellTransform, + isMobile && fabMinimalShellTransform, ]}> - - {showIndicator && } + + {showIndicator && ( + + )} ) } diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index c0f4cf1950..3827e1cb13 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -461,7 +461,7 @@ export function SearchScreen( const moderationOpts = useModerationOpts() const search = useActorAutocompleteFn() const setMinimalShellMode = useSetMinimalShellMode() - const {isTabletOrDesktop, isTabletOrMobile} = useWebMediaQueries() + const {isTabletOrDesktop, isMobile} = useWebMediaQueries() const searchDebounceTimeout = React.useRef( undefined, @@ -637,8 +637,8 @@ export function SearchScreen( pal.view, isTabletOrDesktop && {paddingTop: 10}, ]} - sideBorders={isTabletOrDesktop}> - {isTabletOrMobile && ( + sideBorders={isMobile}> + {isMobile && ( } {isWeb && !showBottomBar && ( <> - + )} diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 097ca2fbfb..79787b0b4e 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,52 +1,54 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {PressableWithHover} from 'view/com/util/PressableWithHover' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import { useLinkProps, useNavigation, useNavigationState, } from '@react-navigation/native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {Text} from 'view/com/util/text/Text' -import {UserAvatar} from 'view/com/util/UserAvatar' -import {Link} from 'view/com/util/Link' -import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' + +import {isInvalidHandle} from '#/lib/strings/handles' +import {isWeb} from '#/platform/detection' +import {emitSoftReset} from '#/state/events' +import {useFetchHandle} from '#/state/queries/handle' +import {useUnreadNotifications} from '#/state/queries/notifications/unread' +import {useProfileQuery} from '#/state/queries/profile' +import {useSession} from '#/state/session' +import {useComposerControls} from '#/state/shell/composer' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {s, colors} from 'lib/styles' import { - HomeIcon, - HomeIconSolid, - MagnifyingGlassIcon2, - MagnifyingGlassIcon2Solid, BellIcon, BellIconSolid, - UserIcon, - UserIconSolid, CogIcon, CogIconSolid, ComposeIcon2, - ListIcon, - HashtagIcon, HandIcon, + HashtagIcon, + HomeIcon, + HomeIconSolid, + ListIcon, + MagnifyingGlassIcon2, + MagnifyingGlassIcon2Solid, + UserIcon, + UserIconSolid, } from 'lib/icons' -import {getCurrentRoute, isTab, isStateAtTabRoot} from 'lib/routes/helpers' -import {NavigationProp, CommonNavigatorParams} from 'lib/routes/types' -import {router} from '../../../routes' +import {getCurrentRoute, isStateAtTabRoot, isTab} from 'lib/routes/helpers' import {makeProfileLink} from 'lib/routes/links' -import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' -import {useProfileQuery} from '#/state/queries/profile' -import {useSession} from '#/state/session' -import {useUnreadNotifications} from '#/state/queries/notifications/unread' -import {useComposerControls} from '#/state/shell/composer' -import {useFetchHandle} from '#/state/queries/handle' -import {emitSoftReset} from '#/state/events' +import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types' +import {colors, s} from 'lib/styles' import {NavSignupCard} from '#/view/shell/NavSignupCard' -import {isInvalidHandle} from '#/lib/strings/handles' +import {Link} from 'view/com/util/Link' +import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' +import {PressableWithHover} from 'view/com/util/PressableWithHover' +import {Text} from 'view/com/util/text/Text' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {router} from '../../../routes' function ProfileCard() { const {currentAccount} = useSession() @@ -266,7 +268,7 @@ function ComposeBtn() { ) } -export function DesktopLeftNav() { +export function LeftNav() { const {hasSession, currentAccount} = useSession() const pal = usePalette('default') const {_} = useLingui() @@ -445,17 +447,19 @@ export function DesktopLeftNav() { } const styles = StyleSheet.create({ - leftNav: { - // @ts-ignore web only - position: 'fixed', - top: 10, - // @ts-ignore web only - left: 'calc(50vw - 300px - 220px - 20px)', - width: 220, - // @ts-ignore web only - maxHeight: 'calc(100vh - 10px)', - overflowY: 'auto', - }, + // @ts-ignore web only + leftNav: isWeb + ? { + position: 'fixed', + top: 10, + left: 'calc(50vw - 300px - 220px - 20px)', + width: 220, + maxHeight: 'calc(100vh - 10px)', + overflowY: 'auto', + } + : { + paddingTop: 12, + }, leftNavTablet: { top: 0, left: 0, @@ -513,6 +517,7 @@ const styles = StyleSheet.create({ fontWeight: 'bold', paddingHorizontal: 4, borderRadius: 6, + overflow: 'hidden', }, navItemCountTablet: { left: 18, diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index c1f4987248..3f97f42b3f 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -1,16 +1,17 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import {usePalette} from 'lib/hooks/usePalette' -import {DesktopSearch} from './Search' -import {DesktopFeeds} from './Feeds' -import {Text} from 'view/com/util/text/Text' -import {TextLink} from 'view/com/util/Link' -import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants' -import {s} from 'lib/styles' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useSession} from '#/state/session' +import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants' +import {usePalette} from 'lib/hooks/usePalette' +import {s} from 'lib/styles' +import {TextLink} from 'view/com/util/Link' +import {Text} from 'view/com/util/text/Text' +import {DesktopFeeds} from './Feeds' +import {DesktopSearch} from './Search' export function DesktopRightNav({routeName}: {routeName: string}) { const pal = usePalette('default') @@ -97,15 +98,18 @@ export function DesktopRightNav({routeName}: {routeName: string}) { } const styles = StyleSheet.create({ - rightNav: { - // @ts-ignore web only - position: 'fixed', - // @ts-ignore web only - left: 'calc(50vw + 300px + 20px)', - width: 300, - maxHeight: '100%', - overflowY: 'auto', - }, + // @ts-ignore web only + rightNav: isWeb + ? { + position: 'fixed', + left: 'calc(50vw + 300px + 20px)', + width: 300, + maxHeight: '100%', + overflowY: 'auto', + } + : { + width: 300, + }, message: { paddingVertical: 18, diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index f29183095a..086abea09e 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -1,37 +1,38 @@ 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, isNativeTablet} 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 {Outlet as PortalOutlet} from '#/components/Portal' +import {RoutesContainer, TabsNavigator} from '../../Navigation' +import {Composer} from './Composer' +import {DrawerContent} from './Drawer' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -87,15 +88,19 @@ function ShellInner() { style={containerPadding} importantForAccessibility={importantForAccessibility}> - + {!isNativeTablet ? ( + + + + ) : ( - + )}