this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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