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