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