Bluesky app fork with some witchin' additions 馃挮
at main 312 lines 11 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import Animated from 'react-native-reanimated' 4import {msg, plural} from '@lingui/core/macro' 5import {useLingui} from '@lingui/react' 6import {Trans} from '@lingui/react/macro' 7import {useNavigationState} from '@react-navigation/native' 8 9import {useHideBottomBarBorder} from '#/lib/hooks/useHideBottomBarBorder' 10import {useMinimalShellFooterTransform} from '#/lib/hooks/useMinimalShellTransform' 11import {getCurrentRoute, isTab} from '#/lib/routes/helpers' 12import {makeProfileLink} from '#/lib/routes/links' 13import {type CommonNavigatorParams} from '#/lib/routes/types' 14import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations' 15import {useUnreadNotifications} from '#/state/queries/notifications/unread' 16import {useProfileQuery} from '#/state/queries/profile' 17import {useSession} from '#/state/session' 18import {useLoggedOutViewControls} from '#/state/shell/logged-out' 19import {useShellLayout} from '#/state/shell/shell-layout' 20import {useCloseAllActiveElements} from '#/state/util' 21import {Link} from '#/view/com/util/Link' 22import {UserAvatar} from '#/view/com/util/UserAvatar' 23import {Logo} from '#/view/icons/Logo' 24import {Logotype} from '#/view/icons/Logotype' 25import {atoms as a, useTheme} from '#/alf' 26import {Button, ButtonText} from '#/components/Button' 27import {useDialogControl} from '#/components/Dialog' 28import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount' 29import { 30 Bell_Filled_Corner0_Rounded as BellFilled, 31 Bell_Stroke2_Corner0_Rounded as Bell, 32} from '#/components/icons/Bell' 33import { 34 HomeOpen_Filled_Corner0_Rounded as HomeFilled, 35 HomeOpen_Stoke2_Corner0_Rounded as Home, 36} from '#/components/icons/HomeOpen' 37import { 38 MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled, 39 MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlass, 40} from '#/components/icons/MagnifyingGlass' 41import { 42 Message_Stroke2_Corner0_Rounded as Message, 43 Message_Stroke2_Corner0_Rounded_Filled as MessageFilled, 44} from '#/components/icons/Message' 45import {Text} from '#/components/Typography' 46import {styles} from './BottomBarStyles' 47 48export function BottomBarWeb() { 49 const {_} = useLingui() 50 const {hasSession, currentAccount} = useSession() 51 const t = useTheme() 52 const footerMinimalShellTransform = useMinimalShellFooterTransform() 53 const {requestSwitchToAccount} = useLoggedOutViewControls() 54 const closeAllActiveElements = useCloseAllActiveElements() 55 const {footerHeight} = useShellLayout() 56 const hideBorder = useHideBottomBarBorder() 57 const accountSwitchControl = useDialogControl() 58 const {data: profile} = useProfileQuery({did: currentAccount?.did}) 59 const iconWidth = 26 60 61 const unreadMessageCount = useUnreadMessageCount() 62 const notificationCountStr = useUnreadNotifications() 63 64 const showSignIn = React.useCallback(() => { 65 closeAllActiveElements() 66 requestSwitchToAccount({requestedAccount: 'none'}) 67 }, [requestSwitchToAccount, closeAllActiveElements]) 68 69 const showCreateAccount = React.useCallback(() => { 70 closeAllActiveElements() 71 requestSwitchToAccount({requestedAccount: 'new'}) 72 // setShowLoggedOut(true) 73 }, [requestSwitchToAccount, closeAllActiveElements]) 74 75 const onLongPressProfile = React.useCallback(() => { 76 accountSwitchControl.open() 77 }, [accountSwitchControl]) 78 79 return ( 80 <> 81 <SwitchAccountDialog control={accountSwitchControl} /> 82 83 <Animated.View 84 role="navigation" 85 style={[ 86 styles.bottomBar, 87 styles.bottomBarWeb, 88 t.atoms.bg, 89 hideBorder 90 ? {borderColor: t.atoms.bg.backgroundColor} 91 : t.atoms.border_contrast_low, 92 footerMinimalShellTransform, 93 ]} 94 onLayout={event => footerHeight.set(event.nativeEvent.layout.height)}> 95 {hasSession ? ( 96 <> 97 <NavItem routeName="Home" href="/"> 98 {({isActive}) => { 99 const Icon = isActive ? HomeFilled : Home 100 return ( 101 <Icon 102 aria-hidden={true} 103 width={iconWidth + 1} 104 style={[styles.ctrlIcon, t.atoms.text, styles.homeIcon]} 105 /> 106 ) 107 }} 108 </NavItem> 109 <NavItem routeName="Search" href="/search"> 110 {({isActive}) => { 111 const Icon = isActive ? MagnifyingGlassFilled : MagnifyingGlass 112 return ( 113 <Icon 114 aria-hidden={true} 115 width={iconWidth + 2} 116 style={[styles.ctrlIcon, t.atoms.text, styles.searchIcon]} 117 /> 118 ) 119 }} 120 </NavItem> 121 122 {hasSession && ( 123 <> 124 <NavItem 125 routeName="Messages" 126 href="/messages" 127 notificationCount={unreadMessageCount.numUnread} 128 hasNew={unreadMessageCount.hasNew}> 129 {({isActive}) => { 130 const Icon = isActive ? MessageFilled : Message 131 return ( 132 <Icon 133 aria-hidden={true} 134 width={iconWidth - 1} 135 style={[ 136 styles.ctrlIcon, 137 t.atoms.text, 138 styles.messagesIcon, 139 ]} 140 /> 141 ) 142 }} 143 </NavItem> 144 <NavItem 145 routeName="Notifications" 146 href="/notifications" 147 notificationCount={notificationCountStr}> 148 {({isActive}) => { 149 const Icon = isActive ? BellFilled : Bell 150 return ( 151 <Icon 152 aria-hidden={true} 153 width={iconWidth} 154 style={[styles.ctrlIcon, t.atoms.text, styles.bellIcon]} 155 /> 156 ) 157 }} 158 </NavItem> 159 <NavItem 160 routeName="Profile" 161 href={ 162 currentAccount 163 ? makeProfileLink({ 164 did: currentAccount.did, 165 handle: currentAccount.handle, 166 }) 167 : '/' 168 } 169 onLongPress={onLongPressProfile}> 170 {({isActive}) => ( 171 <View style={styles.ctrlIconSizingWrapper}> 172 <View 173 style={[ 174 styles.ctrlIcon, 175 styles.profileIcon, 176 isActive && [ 177 styles.onProfile, 178 {borderColor: t.atoms.text.color}, 179 ], 180 ]}> 181 <UserAvatar 182 avatar={profile?.avatar} 183 size={iconWidth - 3} 184 type={ 185 profile?.associated?.labeler ? 'labeler' : 'user' 186 } 187 /> 188 </View> 189 </View> 190 )} 191 </NavItem> 192 </> 193 )} 194 </> 195 ) : ( 196 <> 197 <View 198 style={[ 199 a.w_full, 200 a.flex_row, 201 a.align_center, 202 a.justify_between, 203 a.gap_sm, 204 { 205 paddingTop: 14, 206 paddingBottom: 14, 207 paddingLeft: 14, 208 paddingRight: 6, 209 }, 210 ]}> 211 <View style={[a.flex_row, a.align_center, a.gap_md]}> 212 <Logo width={32} /> 213 <View style={{paddingTop: 4}}> 214 <Logotype width={80} fill={t.atoms.text.color} /> 215 </View> 216 </View> 217 218 <View style={[a.flex_row, a.flex_wrap, a.gap_sm]}> 219 <Button 220 onPress={showCreateAccount} 221 label={_(msg`Create account`)} 222 size="small" 223 variant="solid" 224 color="primary"> 225 <ButtonText> 226 <Trans>Create account</Trans> 227 </ButtonText> 228 </Button> 229 <Button 230 onPress={showSignIn} 231 label={_(msg`Sign in`)} 232 size="small" 233 variant="solid" 234 color="secondary"> 235 <ButtonText> 236 <Trans>Sign in</Trans> 237 </ButtonText> 238 </Button> 239 </View> 240 </View> 241 </> 242 )} 243 </Animated.View> 244 </> 245 ) 246} 247 248const NavItem: React.FC<{ 249 children: (props: {isActive: boolean}) => React.ReactNode 250 href: string 251 routeName: string 252 hasNew?: boolean 253 notificationCount?: string 254 onLongPress?: () => void 255}> = ({children, href, routeName, hasNew, notificationCount, onLongPress}) => { 256 const t = useTheme() 257 const {_} = useLingui() 258 const {currentAccount} = useSession() 259 const currentRoute = useNavigationState(state => { 260 if (!state) { 261 return {name: 'Home'} 262 } 263 return getCurrentRoute(state) 264 }) 265 266 // Checks whether we're on someone else's profile 267 const isOnDifferentProfile = 268 currentRoute.name === 'Profile' && 269 routeName === 'Profile' && 270 (currentRoute.params as CommonNavigatorParams['Profile']).name !== 271 currentAccount?.handle 272 273 const isActive = 274 currentRoute.name === 'Profile' 275 ? isTab(currentRoute.name, routeName) && 276 (currentRoute.params as CommonNavigatorParams['Profile']).name === 277 (routeName === 'Profile' 278 ? currentAccount?.handle 279 : (currentRoute.params as CommonNavigatorParams['Profile']).name) 280 : isTab(currentRoute.name, routeName) 281 282 return ( 283 <Link 284 href={href} 285 style={[styles.ctrl, a.pb_lg]} 286 navigationAction={isOnDifferentProfile ? 'push' : 'navigate'} 287 aria-role="link" 288 aria-label={routeName} 289 accessible={true} 290 onLongPress={onLongPress}> 291 {children({isActive})} 292 {notificationCount ? ( 293 <View 294 style={[ 295 styles.notificationCount, 296 styles.notificationCountWeb, 297 {backgroundColor: t.palette.primary_500}, 298 ]} 299 aria-label={_( 300 msg`${plural(notificationCount, { 301 one: '# unread item', 302 other: '# unread items', 303 })}`, 304 )}> 305 <Text style={styles.notificationCountLabel}>{notificationCount}</Text> 306 </View> 307 ) : hasNew ? ( 308 <View style={styles.hasNewBadge} /> 309 ) : null} 310 </Link> 311 ) 312}