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