mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {View} from 'react-native'
3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5import {useFocusEffect, useIsFocused} from '@react-navigation/native'
6import {useQueryClient} from '@tanstack/react-query'
7
8import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
9import {ComposeIcon2} from '#/lib/icons'
10import {
11 NativeStackScreenProps,
12 NotificationsTabNavigatorParams,
13} from '#/lib/routes/types'
14import {s} from '#/lib/styles'
15import {logger} from '#/logger'
16import {isNative, isWeb} from '#/platform/detection'
17import {emitSoftReset, listenSoftReset} from '#/state/events'
18import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
19import {
20 useUnreadNotifications,
21 useUnreadNotificationsApi,
22} from '#/state/queries/notifications/unread'
23import {truncateAndInvalidate} from '#/state/queries/util'
24import {useSetMinimalShellMode} from '#/state/shell'
25import {useComposerControls} from '#/state/shell/composer'
26import {Feed} from '#/view/com/notifications/Feed'
27import {FAB} from '#/view/com/util/fab/FAB'
28import {ListMethods} from '#/view/com/util/List'
29import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
30import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
31import {atoms as a, useBreakpoints, useTheme} from '#/alf'
32import {Button, ButtonIcon} from '#/components/Button'
33import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
34import * as Layout from '#/components/Layout'
35import {Link} from '#/components/Link'
36import {Loader} from '#/components/Loader'
37
38type Props = NativeStackScreenProps<
39 NotificationsTabNavigatorParams,
40 'Notifications'
41>
42export function NotificationsScreen({route: {params}}: Props) {
43 const t = useTheme()
44 const {gtTablet} = useBreakpoints()
45 const {_} = useLingui()
46 const setMinimalShellMode = useSetMinimalShellMode()
47 const [isScrolledDown, setIsScrolledDown] = React.useState(false)
48 const [isLoadingLatest, setIsLoadingLatest] = React.useState(false)
49 const scrollElRef = React.useRef<ListMethods>(null)
50 const queryClient = useQueryClient()
51 const unreadNotifs = useUnreadNotifications()
52 const unreadApi = useUnreadNotificationsApi()
53 const hasNew = !!unreadNotifs
54 const isScreenFocused = useIsFocused()
55 const {openComposer} = useComposerControls()
56
57 // event handlers
58 // =
59 const scrollToTop = React.useCallback(() => {
60 scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0})
61 setMinimalShellMode(false)
62 }, [scrollElRef, setMinimalShellMode])
63
64 const onPressLoadLatest = React.useCallback(() => {
65 scrollToTop()
66 if (hasNew) {
67 // render what we have now
68 truncateAndInvalidate(queryClient, NOTIFS_RQKEY())
69 } else {
70 // check with the server
71 setIsLoadingLatest(true)
72 unreadApi
73 .checkUnread({invalidate: true})
74 .catch(() => undefined)
75 .then(() => setIsLoadingLatest(false))
76 }
77 }, [scrollToTop, queryClient, unreadApi, hasNew, setIsLoadingLatest])
78
79 const onFocusCheckLatest = useNonReactiveCallback(() => {
80 // on focus, check for latest, but only invalidate if the user
81 // isnt scrolled down to avoid moving content underneath them
82 let currentIsScrolledDown
83 if (isNative) {
84 currentIsScrolledDown = isScrolledDown
85 } else {
86 // On the web, this isn't always updated in time so
87 // we're just going to look it up synchronously.
88 currentIsScrolledDown = window.scrollY > 200
89 }
90 unreadApi.checkUnread({invalidate: !currentIsScrolledDown})
91 })
92
93 // on-visible setup
94 // =
95 useFocusEffect(
96 React.useCallback(() => {
97 setMinimalShellMode(false)
98 logger.debug('NotificationsScreen: Focus')
99 onFocusCheckLatest()
100 }, [setMinimalShellMode, onFocusCheckLatest]),
101 )
102 React.useEffect(() => {
103 if (!isScreenFocused) {
104 return
105 }
106 return listenSoftReset(onPressLoadLatest)
107 }, [onPressLoadLatest, isScreenFocused])
108
109 return (
110 <Layout.Screen testID="notificationsScreen">
111 <Layout.Header.Outer>
112 <Layout.Header.MenuButton />
113 <Layout.Header.Content>
114 <Button
115 label={_(msg`Notifications`)}
116 accessibilityHint={_(msg`Refresh notifications`)}
117 onPress={emitSoftReset}
118 style={[a.justify_start]}>
119 {({hovered}) => (
120 <Layout.Header.TitleText
121 style={[a.w_full, hovered && a.underline]}>
122 <Trans>Notifications</Trans>
123 {isWeb && gtTablet && hasNew && (
124 <View
125 style={[
126 a.rounded_full,
127 {
128 width: 8,
129 height: 8,
130 bottom: 3,
131 left: 6,
132 backgroundColor: t.palette.primary_500,
133 },
134 ]}
135 />
136 )}
137 </Layout.Header.TitleText>
138 )}
139 </Button>
140 </Layout.Header.Content>
141 <Layout.Header.Slot>
142 <Link
143 to="/notifications/settings"
144 label={_(msg`Notification settings`)}
145 size="small"
146 variant="ghost"
147 color="secondary"
148 shape="round"
149 style={[a.justify_center]}>
150 <ButtonIcon
151 icon={isLoadingLatest ? Loader : SettingsIcon}
152 size="lg"
153 />
154 </Link>
155 </Layout.Header.Slot>
156 </Layout.Header.Outer>
157
158 <MainScrollProvider>
159 <Feed
160 onScrolledDownChange={setIsScrolledDown}
161 scrollElRef={scrollElRef}
162 overridePriorityNotifications={params?.show === 'all'}
163 />
164 </MainScrollProvider>
165 {(isScrolledDown || hasNew) && (
166 <LoadLatestBtn
167 onPress={onPressLoadLatest}
168 label={_(msg`Load new notifications`)}
169 showIndicator={hasNew}
170 />
171 )}
172 <FAB
173 testID="composeFAB"
174 onPress={() => openComposer({})}
175 icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
176 accessibilityRole="button"
177 accessibilityLabel={_(msg`New post`)}
178 accessibilityHint=""
179 />
180 </Layout.Screen>
181 )
182}