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