mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

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

at react-sdui 373 lines 13 kB view raw
1import React, {ComponentProps} from 'react' 2import {GestureResponderEvent, TouchableOpacity, View} from 'react-native' 3import Animated from 'react-native-reanimated' 4import {useSafeAreaInsets} from 'react-native-safe-area-context' 5import {msg, Trans} from '@lingui/macro' 6import {useLingui} from '@lingui/react' 7import {BottomTabBarProps} from '@react-navigation/bottom-tabs' 8import {StackActions} from '@react-navigation/native' 9 10import {useAnalytics} from '#/lib/analytics/analytics' 11import {useHaptics} from '#/lib/haptics' 12import {useDedupe} from '#/lib/hooks/useDedupe' 13import {useMinimalShellFooterTransform} from '#/lib/hooks/useMinimalShellTransform' 14import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState' 15import {usePalette} from '#/lib/hooks/usePalette' 16import {clamp} from '#/lib/numbers' 17import {getTabState, TabState} from '#/lib/routes/helpers' 18import {s} from '#/lib/styles' 19import {emitSoftReset} from '#/state/events' 20import {useUnreadMessageCount} from '#/state/queries/messages/list-converations' 21import {useUnreadNotifications} from '#/state/queries/notifications/unread' 22import {useProfileQuery} from '#/state/queries/profile' 23import {useSession} from '#/state/session' 24import {useLoggedOutViewControls} from '#/state/shell/logged-out' 25import {useShellLayout} from '#/state/shell/shell-layout' 26import {useCloseAllActiveElements} from '#/state/util' 27import {Button} from '#/view/com/util/forms/Button' 28import {Text} from '#/view/com/util/text/Text' 29import {UserAvatar} from '#/view/com/util/UserAvatar' 30import {Logo} from '#/view/icons/Logo' 31import {Logotype} from '#/view/icons/Logotype' 32import {useDialogControl} from '#/components/Dialog' 33import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount' 34import { 35 Bell_Filled_Corner0_Rounded as BellFilled, 36 Bell_Stroke2_Corner0_Rounded as Bell, 37} from '#/components/icons/Bell' 38import { 39 HomeOpen_Filled_Corner0_Rounded as HomeFilled, 40 HomeOpen_Stoke2_Corner0_Rounded as Home, 41} from '#/components/icons/HomeOpen' 42import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass' 43import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2' 44import { 45 Message_Stroke2_Corner0_Rounded as Message, 46 Message_Stroke2_Corner0_Rounded_Filled as MessageFilled, 47} from '#/components/icons/Message' 48import {HomeTourExploreWrapper} from '#/tours/HomeTour' 49import {styles} from './BottomBarStyles' 50 51type TabOptions = 52 | 'Home' 53 | 'Search' 54 | 'Notifications' 55 | 'MyProfile' 56 | 'Feeds' 57 | 'Messages' 58 59export function BottomBar({navigation}: BottomTabBarProps) { 60 const {hasSession, currentAccount} = useSession() 61 const pal = usePalette('default') 62 const {_} = useLingui() 63 const safeAreaInsets = useSafeAreaInsets() 64 const {track} = useAnalytics() 65 const {footerHeight} = useShellLayout() 66 const {isAtHome, isAtSearch, isAtNotifications, isAtMyProfile, isAtMessages} = 67 useNavigationTabState() 68 const numUnreadNotifications = useUnreadNotifications() 69 const numUnreadMessages = useUnreadMessageCount() 70 const footerMinimalShellTransform = useMinimalShellFooterTransform() 71 const {data: profile} = useProfileQuery({did: currentAccount?.did}) 72 const {requestSwitchToAccount} = useLoggedOutViewControls() 73 const closeAllActiveElements = useCloseAllActiveElements() 74 const dedupe = useDedupe() 75 const accountSwitchControl = useDialogControl() 76 const playHaptic = useHaptics() 77 const iconWidth = 28 78 79 const showSignIn = React.useCallback(() => { 80 closeAllActiveElements() 81 requestSwitchToAccount({requestedAccount: 'none'}) 82 }, [requestSwitchToAccount, closeAllActiveElements]) 83 84 const showCreateAccount = React.useCallback(() => { 85 closeAllActiveElements() 86 requestSwitchToAccount({requestedAccount: 'new'}) 87 // setShowLoggedOut(true) 88 }, [requestSwitchToAccount, closeAllActiveElements]) 89 90 const onPressTab = React.useCallback( 91 (tab: TabOptions) => { 92 track(`MobileShell:${tab}ButtonPressed`) 93 const state = navigation.getState() 94 const tabState = getTabState(state, tab) 95 if (tabState === TabState.InsideAtRoot) { 96 emitSoftReset() 97 } else if (tabState === TabState.Inside) { 98 dedupe(() => navigation.dispatch(StackActions.popToTop())) 99 } else { 100 dedupe(() => navigation.navigate(`${tab}Tab`)) 101 } 102 }, 103 [track, navigation, dedupe], 104 ) 105 const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab]) 106 const onPressSearch = React.useCallback( 107 () => onPressTab('Search'), 108 [onPressTab], 109 ) 110 const onPressNotifications = React.useCallback( 111 () => onPressTab('Notifications'), 112 [onPressTab], 113 ) 114 const onPressProfile = React.useCallback(() => { 115 onPressTab('MyProfile') 116 }, [onPressTab]) 117 const onPressMessages = React.useCallback(() => { 118 onPressTab('Messages') 119 }, [onPressTab]) 120 121 const onLongPressProfile = React.useCallback(() => { 122 playHaptic() 123 accountSwitchControl.open() 124 }, [accountSwitchControl, playHaptic]) 125 126 return ( 127 <> 128 <SwitchAccountDialog control={accountSwitchControl} /> 129 130 <Animated.View 131 style={[ 132 styles.bottomBar, 133 pal.view, 134 pal.border, 135 {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)}, 136 footerMinimalShellTransform, 137 ]} 138 onLayout={e => { 139 footerHeight.value = e.nativeEvent.layout.height 140 }}> 141 {hasSession ? ( 142 <> 143 <Btn 144 testID="bottomBarHomeBtn" 145 icon={ 146 isAtHome ? ( 147 <HomeFilled 148 width={iconWidth + 1} 149 style={[styles.ctrlIcon, pal.text, styles.homeIcon]} 150 /> 151 ) : ( 152 <Home 153 width={iconWidth + 1} 154 style={[styles.ctrlIcon, pal.text, styles.homeIcon]} 155 /> 156 ) 157 } 158 onPress={onPressHome} 159 accessibilityRole="tab" 160 accessibilityLabel={_(msg`Home`)} 161 accessibilityHint="" 162 /> 163 <Btn 164 testID="bottomBarSearchBtn" 165 icon={ 166 <HomeTourExploreWrapper> 167 {isAtSearch ? ( 168 <MagnifyingGlassFilled 169 width={iconWidth + 2} 170 style={[styles.ctrlIcon, pal.text, styles.searchIcon]} 171 /> 172 ) : ( 173 <MagnifyingGlass 174 width={iconWidth + 2} 175 style={[styles.ctrlIcon, pal.text, styles.searchIcon]} 176 /> 177 )} 178 </HomeTourExploreWrapper> 179 } 180 onPress={onPressSearch} 181 accessibilityRole="search" 182 accessibilityLabel={_(msg`Search`)} 183 accessibilityHint="" 184 /> 185 <Btn 186 testID="bottomBarMessagesBtn" 187 icon={ 188 isAtMessages ? ( 189 <MessageFilled 190 width={iconWidth - 1} 191 style={[styles.ctrlIcon, pal.text, styles.feedsIcon]} 192 /> 193 ) : ( 194 <Message 195 width={iconWidth - 1} 196 style={[styles.ctrlIcon, pal.text, styles.feedsIcon]} 197 /> 198 ) 199 } 200 onPress={onPressMessages} 201 notificationCount={numUnreadMessages.numUnread} 202 accessible={true} 203 accessibilityRole="tab" 204 accessibilityLabel={_(msg`Chat`)} 205 accessibilityHint={ 206 numUnreadMessages.count > 0 207 ? `${numUnreadMessages.numUnread} unread` 208 : '' 209 } 210 /> 211 <Btn 212 testID="bottomBarNotificationsBtn" 213 icon={ 214 isAtNotifications ? ( 215 <BellFilled 216 width={iconWidth} 217 style={[styles.ctrlIcon, pal.text, styles.bellIcon]} 218 /> 219 ) : ( 220 <Bell 221 width={iconWidth} 222 style={[styles.ctrlIcon, pal.text, styles.bellIcon]} 223 /> 224 ) 225 } 226 onPress={onPressNotifications} 227 notificationCount={numUnreadNotifications} 228 accessible={true} 229 accessibilityRole="tab" 230 accessibilityLabel={_(msg`Notifications`)} 231 accessibilityHint={ 232 numUnreadNotifications === '' 233 ? '' 234 : `${numUnreadNotifications} unread` 235 } 236 /> 237 <Btn 238 testID="bottomBarProfileBtn" 239 icon={ 240 <View style={styles.ctrlIconSizingWrapper}> 241 {isAtMyProfile ? ( 242 <View 243 style={[ 244 styles.ctrlIcon, 245 pal.text, 246 styles.profileIcon, 247 styles.onProfile, 248 {borderColor: pal.text.color}, 249 ]}> 250 <UserAvatar 251 avatar={profile?.avatar} 252 size={iconWidth - 3} 253 // See https://github.com/bluesky-social/social-app/pull/1801: 254 usePlainRNImage={true} 255 type={profile?.associated?.labeler ? 'labeler' : 'user'} 256 /> 257 </View> 258 ) : ( 259 <View 260 style={[styles.ctrlIcon, pal.text, styles.profileIcon]}> 261 <UserAvatar 262 avatar={profile?.avatar} 263 size={iconWidth - 3} 264 // See https://github.com/bluesky-social/social-app/pull/1801: 265 usePlainRNImage={true} 266 type={profile?.associated?.labeler ? 'labeler' : 'user'} 267 /> 268 </View> 269 )} 270 </View> 271 } 272 onPress={onPressProfile} 273 onLongPress={onLongPressProfile} 274 accessibilityRole="tab" 275 accessibilityLabel={_(msg`Profile`)} 276 accessibilityHint="" 277 /> 278 </> 279 ) : ( 280 <> 281 <View 282 style={{ 283 width: '100%', 284 flexDirection: 'row', 285 alignItems: 'center', 286 justifyContent: 'space-between', 287 paddingTop: 14, 288 paddingBottom: 2, 289 paddingLeft: 14, 290 paddingRight: 6, 291 gap: 8, 292 }}> 293 <View 294 style={{flexDirection: 'row', alignItems: 'center', gap: 8}}> 295 <Logo width={28} /> 296 <View style={{paddingTop: 4}}> 297 <Logotype width={80} fill={pal.text.color} /> 298 </View> 299 </View> 300 301 <View 302 style={{flexDirection: 'row', alignItems: 'center', gap: 4}}> 303 <Button 304 onPress={showCreateAccount} 305 accessibilityHint={_(msg`Sign up`)} 306 accessibilityLabel={_(msg`Sign up`)}> 307 <Text type="md" style={[{color: 'white'}, s.bold]}> 308 <Trans>Sign up</Trans> 309 </Text> 310 </Button> 311 312 <Button 313 type="default" 314 onPress={showSignIn} 315 accessibilityHint={_(msg`Sign in`)} 316 accessibilityLabel={_(msg`Sign in`)}> 317 <Text type="md" style={[pal.text, s.bold]}> 318 <Trans>Sign in</Trans> 319 </Text> 320 </Button> 321 </View> 322 </View> 323 </> 324 )} 325 </Animated.View> 326 </> 327 ) 328} 329 330interface BtnProps 331 extends Pick< 332 ComponentProps<typeof TouchableOpacity>, 333 | 'accessible' 334 | 'accessibilityRole' 335 | 'accessibilityHint' 336 | 'accessibilityLabel' 337 > { 338 testID?: string 339 icon: JSX.Element 340 notificationCount?: string 341 onPress?: (event: GestureResponderEvent) => void 342 onLongPress?: (event: GestureResponderEvent) => void 343} 344 345function Btn({ 346 testID, 347 icon, 348 notificationCount, 349 onPress, 350 onLongPress, 351 accessible, 352 accessibilityHint, 353 accessibilityLabel, 354}: BtnProps) { 355 return ( 356 <TouchableOpacity 357 testID={testID} 358 style={styles.ctrl} 359 onPress={onLongPress ? onPress : undefined} 360 onPressIn={onLongPress ? undefined : onPress} 361 onLongPress={onLongPress} 362 accessible={accessible} 363 accessibilityLabel={accessibilityLabel} 364 accessibilityHint={accessibilityHint}> 365 {icon} 366 {notificationCount ? ( 367 <View style={[styles.notificationCount]}> 368 <Text style={styles.notificationCountLabel}>{notificationCount}</Text> 369 </View> 370 ) : undefined} 371 </TouchableOpacity> 372 ) 373}