this repo has no description
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}