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