Compare commits

...

11 Commits

Author SHA1 Message Date
Samuel Newman ff7788dab5 set explicit width and overlap leftnav 2024-04-11 11:10:50 +01:00
Samuel Newman 1700ad751f add contentContainer styles to prop 2024-04-11 10:45:57 +01:00
Samuel Newman 776e0c5a9f add horizontal margin to the generic list component 2024-04-11 10:42:59 +01:00
Samuel Newman e81bb86682 [iPad support] Custom tab navigator for native tablet, with side nav (#3385)
* 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
2024-04-10 16:07:21 +01:00
Samuel Newman b335a3ca58 hide emoji picker 2024-04-10 16:04:41 +01:00
Samuel Newman 3727badd3a fix fab position 2024-04-10 16:04:13 +01:00
Samuel Newman fbcd9848c1 fix feed being hidden under header 2024-04-10 16:04:01 +01:00
Samuel Newman ea7a672ae8 Fix auth flow, FAB, and and load latest button 2024-04-10 15:52:30 +01:00
Paul Frazee 0bbfe966d8 Fix some bugs in tablet mode 2024-04-10 13:19:18 +01:00
Paul Frazee 98c7278867 Add tablet detection 2024-04-10 13:19:18 +01:00
Paul Frazee 106e494b8b Enable tablet mode 2024-04-10 13:19:18 +01:00
24 changed files with 583 additions and 316 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ module.exports = function (config) {
userInterfaceStyle: 'automatic',
splash: SPLASH_CONFIG,
ios: {
supportsTablet: false,
supportsTablet: true,
bundleIdentifier: 'xyz.blueskyweb.app',
config: {
usesNonExemptEncryption: false,
+254 -95
View File
@@ -15,11 +15,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 +29,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 {useTheme} from '#/alf'
import {router} from '#/routes'
const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
@@ -91,15 +93,23 @@ const NotificationsTab =
createNativeStackNavigatorWithAuth<NotificationsTabNavigatorParams>()
const MyProfileTab =
createNativeStackNavigatorWithAuth<MyProfileTabNavigatorParams>()
const ListsTab = createNativeStackNavigatorWithAuth<FlatNavigatorParams>()
const ModerationTab = createNativeStackNavigatorWithAuth<FlatNavigatorParams>()
const SettingsTab = createNativeStackNavigatorWithAuth<FlatNavigatorParams>()
const Flat = createNativeStackNavigatorWithAuth<FlatNavigatorParams>()
const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
const MobileTab = createBottomTabNavigator<BottomTabNavigatorParams>()
const TabletTab = createBottomTabNavigator<SideTabNavigatorParams>()
/**
* 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 +118,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => NotFoundScreen}
options={{title: title(msg`Not Found`)}}
/>
<Stack.Screen
name="Lists"
component={ListsScreen}
options={{title: title(msg`Lists`), requireAuth: true}}
/>
<Stack.Screen
name="Moderation"
getComponent={() => ModerationScreen}
options={{title: title(msg`Moderation`), requireAuth: true}}
/>
<Stack.Screen
name="ModerationModlists"
getComponent={() => ModerationModlistsScreen}
@@ -133,11 +133,6 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => ModerationBlockedAccounts}
options={{title: title(msg`Blocked Accounts`), requireAuth: true}}
/>
<Stack.Screen
name="Settings"
getComponent={() => SettingsScreen}
options={{title: title(msg`Settings`), requireAuth: true}}
/>
<Stack.Screen
name="LanguageSettings"
getComponent={() => LanguageSettingsScreen}
@@ -147,7 +142,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),
})}
/>
<Stack.Screen
@@ -281,15 +276,38 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => HashtagScreen}
options={{title: title(msg`Hashtag`)}}
/>
{!isNativeTablet && (
<>
<Stack.Screen
name="Moderation"
getComponent={() => ModerationScreen}
options={{title: title(msg`Moderation`), requireAuth: true}}
/>
<Stack.Screen
name="Lists"
component={ListsScreen}
options={{title: title(msg`Lists`), requireAuth: true}}
/>
<Stack.Screen
name="Settings"
getComponent={() => 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 ? <TabNavigatorTablet /> : <TabsNavigatorMobile />
}
function TabsNavigatorMobile() {
const tabBar = React.useCallback(
(props: JSX.IntrinsicAttributes & BottomTabBarProps) => (
<BottomBar {...props} />
@@ -298,28 +316,88 @@ function TabsNavigator() {
)
return (
<Tab.Navigator
<MobileTab.Navigator
initialRouteName="HomeTab"
backBehavior="initialRoute"
screenOptions={{headerShown: false, lazy: true}}
tabBar={tabBar}>
<Tab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} />
<Tab.Screen name="SearchTab" getComponent={() => SearchTabNavigator} />
<Tab.Screen name="FeedsTab" getComponent={() => FeedsTabNavigator} />
<Tab.Screen
<MobileTab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} />
<MobileTab.Screen
name="SearchTab"
getComponent={() => SearchTabNavigator}
/>
<MobileTab.Screen
name="FeedsTab"
getComponent={() => FeedsTabNavigator}
/>
<MobileTab.Screen
name="NotificationsTab"
getComponent={() => NotificationsTabNavigator}
/>
<Tab.Screen
<MobileTab.Screen
name="MyProfileTab"
getComponent={() => MyProfileTabNavigator}
/>
</Tab.Navigator>
</MobileTab.Navigator>
)
}
/**
* 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 (
<>
<LeftNav />
<TabletTab.Navigator
initialRouteName="HomeTab"
backBehavior="initialRoute"
screenOptions={{headerShown: false, lazy: true}}
tabBar={tabBar}>
<TabletTab.Screen
name="HomeTab"
getComponent={() => HomeTabNavigator}
/>
<TabletTab.Screen
name="SearchTab"
getComponent={() => SearchTabNavigator}
/>
<TabletTab.Screen
name="FeedsTab"
getComponent={() => FeedsTabNavigator}
/>
<TabletTab.Screen
name="NotificationsTab"
getComponent={() => NotificationsTabNavigator}
/>
<TabletTab.Screen
name="ListsTab"
getComponent={() => ListsTabNavigator}
/>
<TabletTab.Screen
name="ModerationTab"
getComponent={() => ModerationTabNavigator}
/>
<TabletTab.Screen
name="MyProfileTab"
getComponent={() => MyProfileTabNavigator}
/>
<TabletTab.Screen
name="SettingsTab"
getComponent={() => SettingsTabNavigator}
/>
</TabletTab.Navigator>
</>
)
}
function HomeTabNavigator() {
const pal = usePalette('default')
const t = useTheme()
return (
<HomeTab.Navigator
@@ -329,7 +407,7 @@ function HomeTabNavigator() {
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
contentStyle: t.atoms.bg,
}}>
<HomeTab.Screen
name="Home"
@@ -342,7 +420,7 @@ function HomeTabNavigator() {
}
function SearchTabNavigator() {
const pal = usePalette('default')
const t = useTheme()
return (
<SearchTab.Navigator
screenOptions={{
@@ -351,7 +429,7 @@ function SearchTabNavigator() {
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
contentStyle: t.atoms.bg,
}}>
<SearchTab.Screen name="Search" getComponent={() => SearchScreen} />
{commonScreens(SearchTab as typeof HomeTab)}
@@ -360,7 +438,7 @@ function SearchTabNavigator() {
}
function FeedsTabNavigator() {
const pal = usePalette('default')
const t = useTheme()
return (
<FeedsTab.Navigator
screenOptions={{
@@ -369,7 +447,7 @@ function FeedsTabNavigator() {
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
contentStyle: t.atoms.bg,
}}>
<FeedsTab.Screen
name="Feeds"
@@ -382,7 +460,7 @@ function FeedsTabNavigator() {
}
function NotificationsTabNavigator() {
const pal = usePalette('default')
const t = useTheme()
return (
<NotificationsTab.Navigator
screenOptions={{
@@ -391,7 +469,7 @@ function NotificationsTabNavigator() {
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
contentStyle: t.atoms.bg,
}}>
<NotificationsTab.Screen
name="Notifications"
@@ -404,7 +482,7 @@ function NotificationsTabNavigator() {
}
function MyProfileTabNavigator() {
const pal = usePalette('default')
const t = useTheme()
return (
<MyProfileTab.Navigator
screenOptions={{
@@ -413,7 +491,7 @@ function MyProfileTabNavigator() {
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
contentStyle: t.atoms.bg,
}}>
<MyProfileTab.Screen
// @ts-ignore // TODO: fix this broken type in ProfileScreen
@@ -428,12 +506,78 @@ function MyProfileTabNavigator() {
)
}
function ListsTabNavigator() {
const t = useTheme()
return (
<ListsTab.Navigator
screenOptions={{
animation: isAndroid ? 'none' : undefined,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: t.atoms.bg,
}}>
<ListsTab.Screen
name="Lists"
getComponent={() => ListsScreen}
options={{requireAuth: true}}
/>
{commonScreens(ListsTab as typeof HomeTab)}
</ListsTab.Navigator>
)
}
function ModerationTabNavigator() {
const t = useTheme()
return (
<ModerationTab.Navigator
screenOptions={{
animation: isAndroid ? 'none' : undefined,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: t.atoms.bg,
}}>
<ModerationTab.Screen
name="Moderation"
getComponent={() => ModerationScreen}
options={{requireAuth: true}}
/>
{commonScreens(ModerationTab as typeof HomeTab)}
</ModerationTab.Navigator>
)
}
function SettingsTabNavigator() {
const t = useTheme()
return (
<SettingsTab.Navigator
screenOptions={{
animation: isAndroid ? 'none' : undefined,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: t.atoms.bg,
}}>
<SettingsTab.Screen
name="Settings"
getComponent={() => SettingsScreen}
options={{requireAuth: true}}
/>
{commonScreens(SettingsTab as typeof HomeTab)}
</SettingsTab.Navigator>
)
}
/**
* 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 +590,7 @@ const FlatNavigator = () => {
fullScreenGestureEnabled: true,
headerShown: false,
animationDuration: 250,
contentStyle: pal.view,
contentStyle: t.atoms.bg,
}}>
<Flat.Screen
name="Home"
@@ -468,7 +612,7 @@ const FlatNavigator = () => {
getComponent={() => NotificationsScreen}
options={{title: title(msg`Notifications`), requireAuth: true}}
/>
{commonScreens(Flat as typeof HomeTab, numUnread)}
{commonScreens(Flat as typeof HomeTab, {unreadCountLabel: numUnread})}
</Flat.Navigator>
)
}
@@ -515,12 +659,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 +861,6 @@ export {
reset,
resetToTab,
RoutesContainer,
TabNavigatorTablet,
TabsNavigator,
}
+7 -2
View File
@@ -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({
<Dialog.ScrollableInner
accessibilityLabelledBy={titleId}
accessibilityDescribedBy={descriptionId}
style={[gtMobile ? {width: 'auto', maxWidth: 400} : a.w_full]}>
style={[
gtMobile && !isNativeTablet
? {width: 'auto', maxWidth: 400}
: a.w_full,
]}>
{children}
</Dialog.ScrollableInner>
</Context.Provider>
@@ -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],
]}>
+10 -1
View File
@@ -1,5 +1,5 @@
import {useMediaQuery} from 'react-responsive'
import {isNative} from 'platform/detection'
import {isNative, isNativeTablet} from 'platform/detection'
export function useWebMediaQueries() {
const isDesktop = useMediaQuery({minWidth: 1300})
@@ -7,6 +7,15 @@ export function useWebMediaQueries() {
const isMobile = useMediaQuery({maxWidth: 800 - 1})
const isTabletOrMobile = isMobile || isTablet
const isTabletOrDesktop = isDesktop || isTablet
if (isNativeTablet) {
return {
isMobile: false,
isTablet: true,
isTabletOrMobile: true,
isTabletOrDesktop: true,
isDesktop: false,
}
}
if (isNative) {
return {
isMobile: true,
+11 -1
View File
@@ -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 & {
+1
View File
@@ -5,6 +5,7 @@ import {dedupArray} from 'lib/functions'
export const isIOS = Platform.OS === 'ios'
export const isAndroid = Platform.OS === 'android'
export const isNative = isIOS || isAndroid
export const isNativeTablet = Platform.OS === 'ios' && Platform.isPad
export const devicePlatform = isIOS ? 'ios' : isAndroid ? 'android' : 'web'
export const isWeb = !isNative
export const isMobileWebMediaQuery = 'only screen and (max-width: 1300px)'
+3 -1
View File
@@ -58,7 +58,9 @@ export const SplashScreen = ({
<Trans>What's up?</Trans>
</Text>
</View>
<View testID="signinOrCreateAccount">
<View
testID="signinOrCreateAccount"
style={[a.w_full, a.mx_auto, {maxWidth: 450}]}>
<Button
testID="createAccountButton"
onPress={onPressCreateAccount}
+1 -1
View File
@@ -484,7 +484,7 @@ export const ComposePost = observer(function ComposePost({
<OpenCameraBtn gallery={gallery} />
</>
) : null}
{!isMobile ? (
{!isMobile && !isNative ? (
<Pressable
onPress={onEmojiButtonPress}
accessibilityRole="button"
+3 -3
View File
@@ -7,7 +7,7 @@ import {useQueryClient} from '@tanstack/react-query'
import {getRootNavigation, getTabState, TabState} from '#/lib/routes/helpers'
import {logEvent, useGate} from '#/lib/statsig/statsig'
import {isNative} from '#/platform/detection'
import {isNative, isNativeTablet} from '#/platform/detection'
import {listenSoftReset} from '#/state/events'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
@@ -155,9 +155,9 @@ export function FeedPage({
}
function useHeaderOffset() {
const {isDesktop, isTablet} = useWebMediaQueries()
const {isTabletOrDesktop} = useWebMediaQueries()
const {fontScale} = useWindowDimensions()
if (isDesktop || isTablet) {
if (isTabletOrDesktop && !isNativeTablet) {
return 0
}
const navBarHeight = 42
+55 -33
View File
@@ -1,23 +1,26 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {usePalette} from 'lib/hooks/usePalette'
import {Link} from '../util/Link'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
import {HITSLOP_10} from 'lib/constants'
import {
StyleSheet,
TouchableOpacity,
useWindowDimensions,
View,
} from 'react-native'
import Animated from 'react-native-reanimated'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {useSetDrawerOpen} from '#/state/shell/drawer-open'
import {useShellLayout} from '#/state/shell/shell-layout'
import {isWeb} from 'platform/detection'
import {HITSLOP_10} from 'lib/constants'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {isNativeTablet, 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 {atoms, useTheme} from '#/alf'
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,
@@ -25,11 +28,22 @@ export function HomeHeaderLayoutMobile({
children: React.ReactNode
tabBarAnchor: JSX.Element | null | undefined
}) {
const pal = usePalette('default')
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen()
const {headerHeight} = useShellLayout()
const {headerMinimalShellTransform} = useMinimalShellMode()
const {width} = useWindowDimensions()
const t = useTheme()
let tabBarTablet
if (isNativeTablet && width > 677) {
tabBarTablet = {
marginLeft: (width - 600) / 2,
width: 600,
borderLeftWidth: 1,
borderRightWidth: 1,
}
}
const onPressAvi = React.useCallback(() => {
setDrawerOpen(true)
@@ -37,27 +51,35 @@ export function HomeHeaderLayoutMobile({
return (
<Animated.View
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
style={[
t.atoms.bg,
t.atoms.border_contrast_medium,
styles.tabBar,
tabBarTablet,
headerMinimalShellTransform,
]}
onLayout={e => {
headerHeight.value = e.nativeEvent.layout.height
}}>
<View style={[pal.view, styles.topBar]}>
<View style={[pal.view, {width: 100}]}>
<TouchableOpacity
testID="viewHeaderDrawerBtn"
onPress={onPressAvi}
accessibilityRole="button"
accessibilityLabel={_(msg`Open navigation`)}
accessibilityHint={_(
msg`Access profile and other navigation links`,
)}
hitSlop={HITSLOP_10}>
<FontAwesomeIcon
icon="bars"
size={18}
color={pal.colors.textLight}
/>
</TouchableOpacity>
<View style={[t.atoms.bg, styles.topBar]}>
<View style={[t.atoms.bg, {width: 100}]}>
{!isNativeTablet ? (
<TouchableOpacity
testID="viewHeaderDrawerBtn"
onPress={onPressAvi}
accessibilityRole="button"
accessibilityLabel={_(msg`Open navigation`)}
accessibilityHint={_(
msg`Access profile and other navigation links`,
)}
hitSlop={HITSLOP_10}>
<FontAwesomeIcon
icon="bars"
size={18}
color={t.atoms.text_contrast_medium.color}
/>
</TouchableOpacity>
) : null}
</View>
<View>
<Logo width={30} />
@@ -68,7 +90,7 @@ export function HomeHeaderLayoutMobile({
atoms.justify_end,
atoms.align_center,
atoms.gap_md,
pal.view,
t.atoms.bg,
{width: 100},
]}>
{IS_DEV && (
@@ -85,7 +107,7 @@ export function HomeHeaderLayoutMobile({
accessibilityHint="">
<FontAwesomeIcon
icon="sliders"
style={pal.textLight as FontAwesomeIconStyle}
color={t.atoms.text_contrast_medium.color}
/>
</Link>
</View>
+27 -9
View File
@@ -1,26 +1,30 @@
import * as React from 'react'
import {
LayoutChangeEvent,
NativeScrollEvent,
ScrollView,
StyleSheet,
useWindowDimensions,
View,
NativeScrollEvent,
} from 'react-native'
import Animated, {
useAnimatedStyle,
useSharedValue,
AnimatedRef,
runOnJS,
runOnUI,
scrollTo,
useAnimatedRef,
AnimatedRef,
SharedValue,
useAnimatedRef,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {TabBar} from './TabBar'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {ListMethods} from '../util/List'
import {ScrollProvider} from '#/lib/ScrollContext'
import {isNativeTablet} from '#/platform/detection'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {useTheme} from '#/alf'
import {ListMethods} from '../util/List'
import {TabBar} from './TabBar'
export interface PagerWithHeaderChildParams {
headerHeight: number
@@ -234,10 +238,24 @@ let PagerTabBar = ({
],
}))
const headerRef = React.useRef(null)
const {width} = useWindowDimensions()
const t = useTheme()
let tabBarTablet
if (isNativeTablet && width > 677) {
tabBarTablet = {
marginLeft: (width - 600) / 2,
width: 600,
borderLeftWidth: 1,
borderRightWidth: 1,
...t.atoms.border_contrast_medium,
}
}
return (
<Animated.View
pointerEvents="box-none"
style={[styles.tabBarMobile, headerTransform]}>
style={[styles.tabBarMobile, tabBarTablet, headerTransform]}>
<View ref={headerRef} pointerEvents="box-none" collapsable={false}>
{renderHeader?.()}
{
+10 -8
View File
@@ -1,11 +1,12 @@
import React, {useRef, useMemo, useEffect, useState, useCallback} from 'react'
import {StyleSheet, View, ScrollView, LayoutChangeEvent} from 'react-native'
import {Text} from '../util/text/Text'
import {PressableWithHover} from '../util/PressableWithHover'
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {LayoutChangeEvent, ScrollView, StyleSheet, View} from 'react-native'
import {isNative, isNativeTablet} from '#/platform/detection'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {PressableWithHover} from '../util/PressableWithHover'
import {Text} from '../util/text/Text'
import {DraggableScrollView} from './DraggableScrollView'
import {isNative} from '#/platform/detection'
export interface TabBarProps {
testID?: string
@@ -36,8 +37,9 @@ export function TabBar({
() => ({borderBottomColor: indicatorColor || pal.colors.link}),
[indicatorColor, pal],
)
const {isDesktop, isTablet} = useWebMediaQueries()
const styles = isDesktop || isTablet ? desktopStyles : mobileStyles
const {isTabletOrDesktop} = useWebMediaQueries()
const styles =
isTabletOrDesktop && !isNativeTablet ? desktopStyles : mobileStyles
useEffect(() => {
if (isNative) {
@@ -137,7 +139,7 @@ export function TabBar({
onPress={() => onPressItem(i)}>
<View style={[styles.itemInner, selected && indicatorStyle]}>
<Text
type={isDesktop || isTablet ? 'xl-bold' : 'lg-bold'}
type={isTabletOrDesktop ? 'xl-bold' : 'lg-bold'}
testID={testID ? `${testID}-${item}` : undefined}
style={selected ? pal.text : pal.textLight}>
{item}
+20 -17
View File
@@ -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 (
<CenteredView style={pal.view}>
{isMobile && (
{isMobileLayout && (
<View
style={[
{
@@ -82,7 +85,7 @@ export function ProfileSubpageHeader({
borderBottomWidth: 1,
paddingTop: isNative ? 0 : 8,
paddingBottom: 8,
paddingHorizontal: isMobile ? 12 : 14,
paddingHorizontal: isMobileLayout ? 12 : 14,
},
pal.border,
]}>
@@ -119,7 +122,7 @@ export function ProfileSubpageHeader({
gap: 10,
paddingTop: 14,
paddingBottom: 6,
paddingHorizontal: isMobile ? 12 : 14,
paddingHorizontal: isMobileLayout ? 12 : 14,
}}>
<Pressable
testID="headerAviButton"
@@ -170,7 +173,7 @@ export function ProfileSubpageHeader({
</Text>
)}
</View>
{!isMobile && (
{!isMobileLayout && (
<View
style={{
flexDirection: 'row',
+23 -8
View File
@@ -1,11 +1,13 @@
import React, {memo} from 'react'
import {FlatListProps, RefreshControl} from 'react-native'
import {FlatList_INTERNAL} from './Views'
import {addStyle} from 'lib/styles'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {FlatListProps, RefreshControl, useWindowDimensions} from 'react-native'
import {runOnJS, useSharedValue} from 'react-native-reanimated'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {usePalette} from '#/lib/hooks/usePalette'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {isNativeTablet} from '#/platform/detection'
import {addStyle} from 'lib/styles'
import {useTheme} from '#/alf'
import {FlatList_INTERNAL} from './Views'
export type ListMethods = FlatList_INTERNAL
export type ListProps<ItemT> = Omit<
@@ -30,13 +32,15 @@ function ListImpl<ItemT>(
onRefresh,
headerOffset,
style,
contentContainerStyle,
...props
}: ListProps<ItemT>,
ref: React.Ref<ListMethods>,
) {
const isScrolledDown = useSharedValue(false)
const contextScrollHandlers = useScrollHandlers()
const pal = usePalette('default')
const t = useTheme()
const {width} = useWindowDimensions()
function handleScrolledDownChange(didScrollDown: boolean) {
onScrolledDownChange?.(didScrollDown)
@@ -68,8 +72,8 @@ function ListImpl<ItemT>(
<RefreshControl
refreshing={refreshing ?? false}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
tintColor={t.atoms.text.color}
titleColor={t.atoms.text.color}
progressViewOffset={headerOffset}
/>
)
@@ -83,6 +87,16 @@ function ListImpl<ItemT>(
contentOffset = {x: 0, y: headerOffset * -1}
}
if (isNativeTablet && width > 677) {
contentContainerStyle = addStyle(contentContainerStyle, {
marginLeft: (width - 600) / 2,
width: 600,
borderLeftWidth: 1,
borderRightWidth: 1,
...t.atoms.border_contrast_medium,
})
}
return (
<FlatList_INTERNAL
{...props}
@@ -92,6 +106,7 @@ function ListImpl<ItemT>(
onScroll={scrollHandler}
scrollEventThrottle={1}
style={style}
contentContainerStyle={contentContainerStyle}
ref={ref}
/>
)
+7 -6
View File
@@ -8,13 +8,14 @@ import {
} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useNavigation} from '@react-navigation/native'
import {CenteredView} from './Views'
import {isNativeTablet, isWeb} from '#/platform/detection'
import {useSetDrawerOpen} from '#/state/shell'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useAnalytics} from 'lib/analytics/analytics'
import {NavigationProp} from 'lib/routes/types'
import {useSetDrawerOpen} from '#/state/shell'
import {isWeb} from '#/platform/detection'
import {CenteredView} from './Views'
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
@@ -71,13 +72,13 @@ export function SimpleViewHeader({
icon="angle-left"
style={[styles.backIcon, pal.text]}
/>
) : (
) : !isNativeTablet ? (
<FontAwesomeIcon
size={18}
icon="bars"
style={[styles.backIcon, pal.textLight]}
/>
)}
) : null}
</TouchableOpacity>
) : null}
{children}
+2 -1
View File
@@ -1,7 +1,8 @@
import React from 'react'
import {View} from 'react-native'
import {FABInner, FABProps} from './FABInner'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {FABInner, FABProps} from './FABInner'
export const FAB = (_opts: FABProps) => {
const {isDesktop} = useWebMediaQueries()
@@ -1,7 +1,6 @@
import React from 'react'
import {ScrollView, StyleSheet, View} from 'react-native'
import {isWeb} from '#/platform/detection'
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@@ -73,9 +72,7 @@ export const LoggedOutLayout = ({
contentContainerStyle={styles.scrollViewContentContainer}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag">
<View style={[styles.contentWrapper, isWeb && a.my_auto]}>
{children}
</View>
<View style={[styles.contentWrapper]}>{children}</View>
</ScrollView>
</View>
) : (
@@ -92,6 +89,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
// @ts-ignore web only
height: '100vh',
flex: 1,
},
side: {
flex: 1,
@@ -111,8 +109,9 @@ const styles = StyleSheet.create({
flex: 1,
},
scrollViewContentContainer: {
flex: 1,
flexGrow: 1,
paddingHorizontal: 40,
justifyContent: 'center',
},
leadinText: {
fontSize: 36,
@@ -1,5 +1,5 @@
import React from 'react'
import {isNative} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
export const withBreakpoints = <P extends object>(
@@ -8,12 +8,12 @@ export const withBreakpoints = <P extends object>(
Desktop: React.ComponentType<P>,
): React.FC<P> =>
function WithBreakpoints(props: P) {
const {isMobile, isTabletOrMobile} = useWebMediaQueries()
const {isMobile, isTablet} = useWebMediaQueries()
if (isMobile || isNative) {
if (isMobile) {
return <Mobile {...props} />
}
if (isTabletOrMobile) {
if (isTablet) {
return <Tablet {...props} />
}
return <Desktop {...props} />
+24 -18
View File
@@ -1,17 +1,19 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import Animated from 'react-native-reanimated'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useMediaQuery} from 'react-responsive'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {colors} from 'lib/styles'
import {HITSLOP_20} from 'lib/constants'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {HITSLOP_20} from '#/lib/constants'
import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {colors} from '#/lib/styles'
import {isNativeTablet, isWeb} from '#/platform/detection'
import {useSession} from '#/state/session'
import {useTheme} from '#/alf'
const AnimatedTouchableOpacity =
Animated.createAnimatedComponent(TouchableOpacity)
import {isWeb} from 'platform/detection'
import {useSession} from 'state/session'
export function LoadLatestBtn({
onPress,
@@ -22,7 +24,7 @@ export function LoadLatestBtn({
label: string
showIndicator: boolean
}) {
const pal = usePalette('default')
const t = useTheme()
const {hasSession} = useSession()
const {isDesktop, isTablet, isMobile, isTabletOrMobile} = useWebMediaQueries()
const {fabMinimalShellTransform} = useMinimalShellMode()
@@ -42,9 +44,9 @@ export function LoadLatestBtn({
(isTallViewport
? styles.loadLatestOutOfLine
: styles.loadLatestInline),
isTablet && styles.loadLatestInline,
pal.borderDark,
pal.view,
isTablet && !isNativeTablet && styles.loadLatestInline,
t.atoms.border_contrast_high,
t.atoms.bg,
showBottomBar && fabMinimalShellTransform,
]}
onPress={onPress}
@@ -52,8 +54,10 @@ export function LoadLatestBtn({
accessibilityRole="button"
accessibilityLabel={label}
accessibilityHint="">
<FontAwesomeIcon icon="angle-up" color={pal.colors.text} size={19} />
{showIndicator && <View style={[styles.indicator, pal.borderDark]} />}
<FontAwesomeIcon icon="angle-up" color={t.atoms.text.color} size={19} />
{showIndicator && (
<View style={[styles.indicator, t.atoms.border_contrast_high]} />
)}
</AnimatedTouchableOpacity>
)
}
@@ -72,10 +76,12 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
loadLatestInline: {
// @ts-ignore web only
left: 'calc(50vw - 282px)',
},
// @ts-ignore web only
loadLatestInline: isWeb
? {
left: 'calc(50vw - 282px)',
}
: {},
loadLatestOutOfLine: {
// @ts-ignore web only
left: 'calc(50vw - 382px)',
+3 -3
View File
@@ -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<NodeJS.Timeout | undefined>(
undefined,
@@ -637,8 +637,8 @@ export function SearchScreen(
pal.view,
isTabletOrDesktop && {paddingTop: 10},
]}
sideBorders={isTabletOrDesktop}>
{isTabletOrMobile && (
sideBorders={isMobile}>
{isMobile && (
<Pressable
testID="viewHeaderBackOrMenuBtn"
onPress={onPressMenu}
@@ -1,11 +1,8 @@
import * as React from 'react'
import {View} from 'react-native'
import {PWI_ENABLED, NEW_ONBOARDING_ENABLED} from '#/lib/build-flags'
// Based on @react-navigation/native-stack/src/createNativeStackNavigator.ts
// MIT License
// Copyright (c) 2017 React Navigation Contributors
import {
createNavigatorFactory,
EventArg,
@@ -21,24 +18,25 @@ import type {
NativeStackNavigationEventMap,
NativeStackNavigationOptions,
} from '@react-navigation/native-stack'
import type {NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types'
import {NativeStackView} from '@react-navigation/native-stack'
import type {NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types'
import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
import {DesktopLeftNav} from './desktop/LeftNav'
import {DesktopRightNav} from './desktop/RightNav'
import {NEW_ONBOARDING_ENABLED, PWI_ENABLED} from '#/lib/build-flags'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {useSession} from '#/state/session'
import {useOnboardingState} from '#/state/shell'
import {
useLoggedOutView,
useLoggedOutViewControls,
} from '#/state/shell/logged-out'
import {useSession} from '#/state/session'
import {isWeb} from 'platform/detection'
import {Deactivated} from '#/screens/Deactivated'
import {Onboarding as NewOnboarding} from '#/screens/Onboarding'
import {LoggedOut} from '../com/auth/LoggedOut'
import {Onboarding} from '../com/auth/Onboarding'
import {Onboarding as NewOnboarding} from '#/screens/Onboarding'
import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
import {LeftNav} from './desktop/LeftNav'
import {DesktopRightNav} from './desktop/RightNav'
type NativeStackNavigationOptionsWithAuth = NativeStackNavigationOptions & {
requireAuth?: boolean
@@ -149,7 +147,7 @@ function NativeStackNavigator({
{isWeb && showBottomBar && <BottomBarWeb />}
{isWeb && !showBottomBar && (
<>
<DesktopLeftNav />
<LeftNav />
<DesktopRightNav routeName={activeRoute.name} />
</>
)}
+52 -45
View File
@@ -1,52 +1,55 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {PressableWithHover} from 'view/com/util/PressableWithHover'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
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,12 +269,13 @@ function ComposeBtn() {
)
}
export function DesktopLeftNav() {
export function LeftNav() {
const {hasSession, currentAccount} = useSession()
const pal = usePalette('default')
const {_} = useLingui()
const {isDesktop, isTablet} = useWebMediaQueries()
const numUnread = useUnreadNotifications()
const {top} = useSafeAreaInsets()
if (!hasSession && !isDesktop) {
return null
@@ -281,7 +285,7 @@ export function DesktopLeftNav() {
<View
style={[
styles.leftNav,
isTablet && styles.leftNavTablet,
isTablet && [styles.leftNavTablet, {top}],
pal.view,
pal.border,
]}>
@@ -445,25 +449,27 @@ 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,
right: 'auto',
borderRightWidth: 1,
height: '100%',
width: 76,
alignItems: 'center',
position: 'absolute',
zIndex: 1,
},
profileCard: {
@@ -513,6 +519,7 @@ const styles = StyleSheet.create({
fontWeight: 'bold',
paddingHorizontal: 4,
borderRadius: 6,
overflow: 'hidden',
},
navItemCountTablet: {
left: 18,
+22 -18
View File
@@ -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,
+31 -26
View File
@@ -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}>
<ErrorBoundary>
<Drawer
renderDrawerContent={renderDrawerContent}
open={isDrawerOpen}
onOpen={onOpenDrawer}
onClose={onCloseDrawer}
swipeEdgeWidth={winDim.width / 2}
swipeEnabled={!canGoBack && hasSession && !isDrawerSwipeDisabled}>
{!isNativeTablet ? (
<Drawer
renderDrawerContent={renderDrawerContent}
open={isDrawerOpen}
onOpen={onOpenDrawer}
onClose={onCloseDrawer}
swipeEdgeWidth={winDim.width / 2}
swipeEnabled={!canGoBack && hasSession && !isDrawerSwipeDisabled}>
<TabsNavigator />
</Drawer>
) : (
<TabsNavigator />
</Drawer>
)}
</ErrorBoundary>
</Animated.View>
<Composer winHeight={winDim.height} />