import {type ComponentProps, type JSX, memo, useCallback} from 'react'
import {
Linking,
Pressable,
ScrollView,
TouchableOpacity,
View,
} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg, plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
import {StackActions, useNavigation} from '@react-navigation/native'
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
import {type PressableScale} from '#/lib/custom-animations/PressableScale'
import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
import {
formatCountsMetricNumber,
shouldShowCountsMetricLabelOnly,
shouldShowProfileCountsMetric,
} from '#/lib/metrics-display'
import {getTabState, TabState} from '#/lib/routes/helpers'
import {type SharedNavTab, TAB_TO_NAV_ITEM} from '#/lib/routes/tab-to-nav-item'
import {type NavigationProp} from '#/lib/routes/types'
import {getShapeStyle} from '#/lib/shapes'
import {sanitizeHandle} from '#/lib/strings/handles'
import {emitSoftReset} from '#/state/events'
import {badgeText, useBadgePreference} from '#/state/preferences/badge-text'
import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
import {
useFollowersMetricsDisplay,
useFollowingMetricsDisplay,
useNotificationsTabBadgeDisplay,
} from '#/state/preferences/metrics-display-preference'
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
import {useProfileQuery} from '#/state/queries/profile'
import {type SessionAccount, useSession} from '#/state/session'
import {useSetDrawerOpen, useThemePrefs} from '#/state/shell'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {useLogoVariant} from '#/view/icons/useLogoVariant'
import {NavSignupCard} from '#/view/shell/NavSignupCard'
import {atoms as a, tokens, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {Divider} from '#/components/Divider'
import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRight} from '#/components/icons/ArrowShareRight'
import {
Bell_Filled_Corner0_Rounded as BellFilled,
Bell_Stroke2_Corner0_Rounded as Bell,
} from '#/components/icons/Bell'
import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark'
import {BulletList_Stroke2_Corner0_Rounded as List} from '#/components/icons/BulletList'
import {
Hashtag_Filled_Corner0_Rounded as HashtagFilled,
Hashtag_Stroke2_Corner0_Rounded as Hashtag,
} from '#/components/icons/Hashtag'
import {
HomeOpen_Filled_Corner0_Rounded as HomeFilled,
HomeOpen_Stoke2_Corner0_Rounded as Home,
} from '#/components/icons/HomeOpen'
import {
MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled,
MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlass,
} from '#/components/icons/MagnifyingGlass'
import {
Message_Stroke2_Corner0_Rounded as Message,
Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
} from '#/components/icons/Message'
import {SettingsGear2_Stroke2_Corner0_Rounded as Settings} from '#/components/icons/SettingsGear2'
import {
UserCircle_Filled_Corner0_Rounded as UserCircleFilled,
UserCircle_Stroke2_Corner0_Rounded as UserCircle,
} from '#/components/icons/UserCircle'
import {InlineLinkText} from '#/components/Link'
import {OTAChannelNotice} from '#/components/OTAChannelNotice'
import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE, IS_WEB} from '#/env'
import {InviteFriendsDialog} from '#/features/inviteFriends'
import {useActorStatus} from '#/features/liveNow'
import {accentForeground} from '#/features/themes/accentForeground'
import {useActiveThemeUpdate} from '#/features/themes/api'
const iconWidth = 26
let DrawerProfileCard = ({
account,
onPressProfile,
onPressShare,
}: {
account: SessionAccount
onPressProfile: () => void
onPressShare?: () => void
}): React.ReactNode => {
const {_, i18n} = useLingui()
const t = useTheme()
const {data: profile} = useProfileQuery({did: account.did})
const {isActive: live} = useActorStatus(profile)
const followersMetricsDisplay = useFollowersMetricsDisplay()
const followingMetricsDisplay = useFollowingMetricsDisplay()
const followersCount = profile?.followersCount ?? 0
const followingCount = profile?.followsCount ?? 0
const showFollowers = shouldShowProfileCountsMetric(
followersMetricsDisplay,
followersCount,
)
const showFollowing = shouldShowProfileCountsMetric(
followingMetricsDisplay,
followingCount,
)
return (
{profile?.displayName || account.handle}
{profile && }
{onPressShare && (
[
a.ml_auto,
{
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: t.palette.contrast_50,
alignItems: 'center',
justifyContent: 'center',
opacity: pressed ? 0.7 : 1,
},
]}>
)}
{sanitizeHandle(account.handle, '@')}
{!showFollowers && !showFollowing ? null : (
{showFollowers ? (
{!shouldShowCountsMetricLabelOnly(
followersMetricsDisplay,
followersCount,
) ? (
{formatCountsMetricNumber(
i18n,
followersMetricsDisplay,
followersCount,
)}
) : null}{' '}
) : null}
{showFollowers && showFollowing ? (
{' '}
·{' '}
) : null}
{showFollowing ? (
{!shouldShowCountsMetricLabelOnly(
followingMetricsDisplay,
followingCount,
) ? (
{formatCountsMetricNumber(
i18n,
followingMetricsDisplay,
followingCount,
)}
) : null}{' '}
) : null}
)}
)
}
DrawerProfileCard = memo(DrawerProfileCard)
export {DrawerProfileCard}
let DrawerContent = ({}: React.PropsWithoutRef<{}>): React.ReactNode => {
const t = useTheme()
const insets = useSafeAreaInsets()
const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation()
const ax = useAnalytics()
const {
isAtHome,
isAtSearch,
isAtFeeds,
isAtBookmarks,
isAtNotifications,
isAtMyProfile,
isAtMessages,
} = useNavigationTabState()
const {hasSession, currentAccount} = useSession()
const inviteFriendsControl = useDialogControl()
// events
// =
const onPressTab = useCallback(
(tab: SharedNavTab, surface: 'drawer' | 'drawerHeader' = 'drawer') => {
ax.metric('nav:click', {
item: TAB_TO_NAV_ITEM[tab],
surface,
})
const state = navigation.getState()
setDrawerOpen(false)
if (IS_WEB) {
// hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh
if (tab === 'MyProfile') {
navigation.navigate('Profile', {name: currentAccount!.handle})
} else {
// @ts-expect-error struggles with string unions, apparently
navigation.navigate(tab)
}
} else {
const tabState = getTabState(state, tab)
if (tabState === TabState.InsideAtRoot) {
emitSoftReset()
} else if (tabState === TabState.Inside) {
// find the correct navigator in which to pop-to-top
const target = state.routes.find(route => route.name === `${tab}Tab`)
?.state?.key
if (target) {
// if we found it, trigger pop-to-top
navigation.dispatch({
...StackActions.popToTop(),
target,
})
} else {
// fallback: reset navigation
navigation.reset({
index: 0,
routes: [{name: `${tab}Tab`}],
})
}
} else {
navigation.navigate(`${tab}Tab`)
}
}
},
[navigation, setDrawerOpen, currentAccount, ax],
)
const onPressHome = useCallback(() => onPressTab('Home'), [onPressTab])
const onPressSearch = useCallback(() => onPressTab('Search'), [onPressTab])
const onPressMessages = useCallback(
() => onPressTab('Messages'),
[onPressTab],
)
const onPressNotifications = useCallback(
() => onPressTab('Notifications'),
[onPressTab],
)
const onPressProfile = useCallback(() => {
onPressTab('MyProfile')
}, [onPressTab])
const onPressDrawerHeaderProfile = useCallback(() => {
onPressTab('MyProfile', 'drawerHeader')
}, [onPressTab])
const onPressMyFeeds = useCallback(() => {
ax.metric('nav:click', {item: 'feeds', surface: 'drawer'})
navigation.navigate('Feeds')
setDrawerOpen(false)
}, [navigation, setDrawerOpen, ax])
const onPressLists = useCallback(() => {
ax.metric('nav:click', {item: 'lists', surface: 'drawer'})
navigation.navigate('Lists')
setDrawerOpen(false)
}, [navigation, setDrawerOpen, ax])
const onPressBookmarks = useCallback(() => {
ax.metric('nav:click', {item: 'saved', surface: 'drawer'})
navigation.navigate('Bookmarks')
setDrawerOpen(false)
}, [navigation, setDrawerOpen, ax])
const onPressSettings = useCallback(() => {
ax.metric('nav:click', {item: 'settings', surface: 'drawer'})
navigation.navigate('Settings')
setDrawerOpen(false)
}, [navigation, setDrawerOpen, ax])
const onPressFeedback = useCallback(() => {
void Linking.openURL(
FEEDBACK_FORM_URL({
email: currentAccount?.email,
handle: currentAccount?.handle,
}),
)
}, [currentAccount])
const onPressHelp = useCallback(() => {
void Linking.openURL(HELP_DESK_URL)
}, [])
// rendering
// =
return (
{hasSession && currentAccount ? (
{
ax.metric('invite:dialog:open', {logContext: 'Drawer'})
setDrawerOpen(false)
inviteFriendsControl.open()
}
: undefined
}
/>
) : (
)}
{hasSession ? (
<>
>
) : (
<>
>
)}
)
}
DrawerContent = memo(DrawerContent)
export {DrawerContent}
let DrawerFooter = ({
onPressFeedback,
onPressHelp,
}: {
onPressFeedback: () => void
onPressHelp: () => void
}): React.ReactNode => {
const {_} = useLingui()
const insets = useSafeAreaInsets()
return (
)
}
DrawerFooter = memo(DrawerFooter)
interface MenuItemProps extends ComponentProps {
icon: JSX.Element
label: string
count?: string
hasNew?: boolean
bold?: boolean
}
let SearchMenuItem = ({
isActive,
onPress,
}: {
isActive: boolean
onPress: () => void
}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
return (
) : (
)
}
label={_(msg`Explore`)}
bold={isActive}
onPress={onPress}
/>
)
}
SearchMenuItem = memo(SearchMenuItem)
let HomeMenuItem = ({
isActive,
onPress,
}: {
isActive: boolean
onPress: () => void
}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
return (
) : (
)
}
label={_(msg`Home`)}
bold={isActive}
onPress={onPress}
/>
)
}
HomeMenuItem = memo(HomeMenuItem)
let ChatMenuItem = ({
isActive,
onPress,
}: {
isActive: boolean
onPress: () => void
}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
return (
) : (
)
}
label={_(msg`Chat`)}
bold={isActive}
onPress={onPress}
/>
)
}
ChatMenuItem = memo(ChatMenuItem)
let NotificationsMenuItem = ({
isActive,
onPress,
}: {
isActive: boolean
onPress: () => void
}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
const numUnreadNotifications = useUnreadNotifications()
const [notificationsCustomText] = useBadgePreference('notificationsBadgeText')
const notificationsTabBadgeDisplay = useNotificationsTabBadgeDisplay()
const notificationsText =
notificationsTabBadgeDisplay === 'text'
? notificationsCustomText
: undefined
return (
) : (
)
}
label={_(msg`Notifications`)}
accessibilityHint={
numUnreadNotifications === ''
? ''
: _(
plural(numUnreadNotifications ?? 0, {
one: '# unread item',
other: '# unread items',
}),
)
}
count={
notificationsTabBadgeDisplay === 'exact' ||
notificationsTabBadgeDisplay === 'text'
? badgeText(numUnreadNotifications, notificationsText)
: undefined
}
hasNew={
notificationsTabBadgeDisplay === 'visible' &&
numUnreadNotifications !== ''
}
bold={isActive}
onPress={onPress}
/>
)
}
NotificationsMenuItem = memo(NotificationsMenuItem)
let FeedsMenuItem = ({
isActive,
onPress,
}: {
isActive: boolean
onPress: () => void
}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
return (
) : (
)
}
label={_(msg`Feeds`)}
bold={isActive}
onPress={onPress}
/>
)
}
FeedsMenuItem = memo(FeedsMenuItem)
let ListsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
return (
}
label={_(msg`Lists`)}
onPress={onPress}
/>
)
}
ListsMenuItem = memo(ListsMenuItem)
let BookmarksMenuItem = ({
isActive,
onPress,
}: {
isActive: boolean
onPress: () => void
}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
return (
) : (
)
}
label={_(msg({message: 'Saved', context: 'link to bookmarks screen'}))}
onPress={onPress}
/>
)
}
BookmarksMenuItem = memo(BookmarksMenuItem)
let ProfileMenuItem = ({
isActive,
onPress,
}: {
isActive: boolean
onPress: () => void
}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
return (
) : (
)
}
label={_(msg`Profile`)}
onPress={onPress}
/>
)
}
ProfileMenuItem = memo(ProfileMenuItem)
let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
const {activeTheme} = useThemePrefs()
const themeUpdate = useActiveThemeUpdate(activeTheme)
return (
}
label={_(msg`Settings`)}
hasNew={Boolean(themeUpdate)}
onPress={onPress}
/>
)
}
SettingsMenuItem = memo(SettingsMenuItem)
function MenuItem({icon, label, count, hasNew, bold, onPress}: MenuItemProps) {
const t = useTheme()
const enableSquareButtons = useEnableSquareButtons()
return (
)
}
function ExtraLinks() {
const {_} = useLingui()
const t = useTheme()
const logoVariant = useLogoVariant()
return (
Terms of Service
Privacy Policy
{logoVariant === 'kawaii' && (
Kawaii logo by{' '}
ovvie
)}
)
}