Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

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