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