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 {useGate} from '#/lib/statsig/statsig'
14import {useHomeBadge} from '#/state/home-badge'
15import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations'
16import {useUnreadNotifications} from '#/state/queries/notifications/unread'
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 {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 {
27 Bell_Filled_Corner0_Rounded as BellFilled,
28 Bell_Stroke2_Corner0_Rounded as Bell,
29} from '#/components/icons/Bell'
30import {
31 HomeOpen_Filled_Corner0_Rounded as HomeFilled,
32 HomeOpen_Stoke2_Corner0_Rounded as Home,
33} from '#/components/icons/HomeOpen'
34import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
35import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass'
36import {
37 Message_Stroke2_Corner0_Rounded as Message,
38 Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
39} from '#/components/icons/Message'
40import {
41 UserCircle_Filled_Corner0_Rounded as UserCircleFilled,
42 UserCircle_Stroke2_Corner0_Rounded as UserCircle,
43} from '#/components/icons/UserCircle'
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 iconWidth = 26
57
58 const unreadMessageCount = useUnreadMessageCount()
59 const notificationCountStr = useUnreadNotifications()
60 const hasHomeBadge = useHomeBadge()
61 const gate = useGate()
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 return (
75 <Animated.View
76 role="navigation"
77 style={[
78 styles.bottomBar,
79 styles.bottomBarWeb,
80 t.atoms.bg,
81 hideBorder
82 ? {borderColor: t.atoms.bg.backgroundColor}
83 : t.atoms.border_contrast_low,
84 footerMinimalShellTransform,
85 ]}
86 onLayout={event => footerHeight.set(event.nativeEvent.layout.height)}>
87 {hasSession ? (
88 <>
89 <NavItem
90 routeName="Home"
91 href="/"
92 hasNew={hasHomeBadge && gate('remove_show_latest_button')}>
93 {({isActive}) => {
94 const Icon = isActive ? HomeFilled : Home
95 return (
96 <Icon
97 aria-hidden={true}
98 width={iconWidth + 1}
99 style={[styles.ctrlIcon, t.atoms.text, styles.homeIcon]}
100 />
101 )
102 }}
103 </NavItem>
104 <NavItem routeName="Search" href="/search">
105 {({isActive}) => {
106 const Icon = isActive ? MagnifyingGlassFilled : MagnifyingGlass
107 return (
108 <Icon
109 aria-hidden={true}
110 width={iconWidth + 2}
111 style={[styles.ctrlIcon, t.atoms.text, styles.searchIcon]}
112 />
113 )
114 }}
115 </NavItem>
116
117 {hasSession && (
118 <>
119 <NavItem
120 routeName="Messages"
121 href="/messages"
122 notificationCount={unreadMessageCount.numUnread}
123 hasNew={unreadMessageCount.hasNew}>
124 {({isActive}) => {
125 const Icon = isActive ? MessageFilled : Message
126 return (
127 <Icon
128 aria-hidden={true}
129 width={iconWidth - 1}
130 style={[
131 styles.ctrlIcon,
132 t.atoms.text,
133 styles.messagesIcon,
134 ]}
135 />
136 )
137 }}
138 </NavItem>
139 <NavItem
140 routeName="Notifications"
141 href="/notifications"
142 notificationCount={notificationCountStr}>
143 {({isActive}) => {
144 const Icon = isActive ? BellFilled : Bell
145 return (
146 <Icon
147 aria-hidden={true}
148 width={iconWidth}
149 style={[styles.ctrlIcon, t.atoms.text, styles.bellIcon]}
150 />
151 )
152 }}
153 </NavItem>
154 <NavItem
155 routeName="Profile"
156 href={
157 currentAccount
158 ? makeProfileLink({
159 did: currentAccount.did,
160 handle: currentAccount.handle,
161 })
162 : '/'
163 }>
164 {({isActive}) => {
165 const Icon = isActive ? UserCircleFilled : UserCircle
166 return (
167 <Icon
168 aria-hidden={true}
169 width={iconWidth}
170 style={[
171 styles.ctrlIcon,
172 t.atoms.text,
173 styles.profileIcon,
174 ]}
175 />
176 )
177 }}
178 </NavItem>
179 </>
180 )}
181 </>
182 ) : (
183 <>
184 <View
185 style={{
186 width: '100%',
187 flexDirection: 'row',
188 alignItems: 'center',
189 justifyContent: 'space-between',
190 paddingTop: 14,
191 paddingBottom: 14,
192 paddingLeft: 14,
193 paddingRight: 6,
194 gap: 8,
195 }}>
196 <View style={{flexDirection: 'row', alignItems: 'center', gap: 12}}>
197 <Logo width={32} />
198 <View style={{paddingTop: 4}}>
199 <Logotype width={80} fill={t.atoms.text.color} />
200 </View>
201 </View>
202
203 <View style={[a.flex_row, a.flex_wrap, a.gap_sm]}>
204 <Button
205 onPress={showCreateAccount}
206 label={_(msg`Create account`)}
207 size="small"
208 variant="solid"
209 color="primary">
210 <ButtonText>
211 <Trans>Create account</Trans>
212 </ButtonText>
213 </Button>
214 <Button
215 onPress={showSignIn}
216 label={_(msg`Sign in`)}
217 size="small"
218 variant="solid"
219 color="secondary">
220 <ButtonText>
221 <Trans>Sign in</Trans>
222 </ButtonText>
223 </Button>
224 </View>
225 </View>
226 </>
227 )}
228 </Animated.View>
229 )
230}
231
232const NavItem: React.FC<{
233 children: (props: {isActive: boolean}) => React.ReactNode
234 href: string
235 routeName: string
236 hasNew?: boolean
237 notificationCount?: string
238}> = ({children, href, routeName, hasNew, notificationCount}) => {
239 const t = useTheme()
240 const {_} = useLingui()
241 const {currentAccount} = useSession()
242 const currentRoute = useNavigationState(state => {
243 if (!state) {
244 return {name: 'Home'}
245 }
246 return getCurrentRoute(state)
247 })
248
249 // Checks whether we're on someone else's profile
250 const isOnDifferentProfile =
251 currentRoute.name === 'Profile' &&
252 routeName === 'Profile' &&
253 (currentRoute.params as CommonNavigatorParams['Profile']).name !==
254 currentAccount?.handle
255
256 const isActive =
257 currentRoute.name === 'Profile'
258 ? isTab(currentRoute.name, routeName) &&
259 (currentRoute.params as CommonNavigatorParams['Profile']).name ===
260 (routeName === 'Profile'
261 ? currentAccount?.handle
262 : (currentRoute.params as CommonNavigatorParams['Profile']).name)
263 : isTab(currentRoute.name, routeName)
264
265 return (
266 <Link
267 href={href}
268 style={[styles.ctrl, a.pb_lg]}
269 navigationAction={isOnDifferentProfile ? 'push' : 'navigate'}
270 aria-role="link"
271 aria-label={routeName}
272 accessible={true}>
273 {children({isActive})}
274 {notificationCount ? (
275 <View
276 style={[
277 styles.notificationCount,
278 styles.notificationCountWeb,
279 {backgroundColor: t.palette.primary_500},
280 ]}
281 aria-label={_(
282 msg`${plural(notificationCount, {
283 one: '# unread item',
284 other: '# unread items',
285 })}`,
286 )}>
287 <Text style={styles.notificationCountLabel}>{notificationCount}</Text>
288 </View>
289 ) : hasNew ? (
290 <View style={styles.hasNewBadge} />
291 ) : null}
292 </Link>
293 )
294}