my fork of the bluesky client
1import React, {memo} from 'react'
2import {StyleProp, View, ViewStyle} from 'react-native'
3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5
6import {cleanError} from '#/lib/strings/errors'
7import {CenteredView} from '#/view/com/util/Views'
8import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf'
9import {Button, ButtonText} from '#/components/Button'
10import {Error} from '#/components/Error'
11import {Loader} from '#/components/Loader'
12import {Text} from '#/components/Typography'
13
14export function ListFooter({
15 isFetchingNextPage,
16 hasNextPage,
17 error,
18 onRetry,
19 height,
20 style,
21 showEndMessage = false,
22 endMessageText,
23}: {
24 isFetchingNextPage?: boolean
25 hasNextPage?: boolean
26 error?: string
27 onRetry?: () => Promise<unknown>
28 height?: number
29 style?: StyleProp<ViewStyle>
30 showEndMessage?: boolean
31 endMessageText?: string
32}) {
33 const t = useTheme()
34
35 return (
36 <View
37 style={[
38 a.w_full,
39 a.align_center,
40 a.border_t,
41 a.pb_lg,
42 t.atoms.border_contrast_low,
43 {height: height ?? 180, paddingTop: 30},
44 flatten(style),
45 ]}>
46 {isFetchingNextPage ? (
47 <Loader size="xl" />
48 ) : error ? (
49 <ListFooterMaybeError error={error} onRetry={onRetry} />
50 ) : !hasNextPage && showEndMessage ? (
51 <Text style={[a.text_sm, t.atoms.text_contrast_low]}>
52 {endMessageText ?? <Trans>You have reached the end</Trans>}
53 </Text>
54 ) : null}
55 </View>
56 )
57}
58
59function ListFooterMaybeError({
60 error,
61 onRetry,
62}: {
63 error?: string
64 onRetry?: () => Promise<unknown>
65}) {
66 const t = useTheme()
67 const {_} = useLingui()
68
69 if (!error) return null
70
71 return (
72 <View style={[a.w_full, a.px_lg]}>
73 <View
74 style={[
75 a.flex_row,
76 a.gap_md,
77 a.p_md,
78 a.rounded_sm,
79 a.align_center,
80 t.atoms.bg_contrast_25,
81 ]}>
82 <Text
83 style={[a.flex_1, a.text_sm, t.atoms.text_contrast_medium]}
84 numberOfLines={2}>
85 {error ? (
86 cleanError(error)
87 ) : (
88 <Trans>Oops, something went wrong!</Trans>
89 )}
90 </Text>
91 <Button
92 variant="gradient"
93 label={_(msg`Press to retry`)}
94 style={[
95 a.align_center,
96 a.justify_center,
97 a.rounded_sm,
98 a.overflow_hidden,
99 a.px_md,
100 a.py_sm,
101 ]}
102 onPress={onRetry}>
103 <ButtonText>
104 <Trans>Retry</Trans>
105 </ButtonText>
106 </Button>
107 </View>
108 </View>
109 )
110}
111
112export function ListHeaderDesktop({
113 title,
114 subtitle,
115}: {
116 title: string
117 subtitle?: string
118}) {
119 const {gtTablet} = useBreakpoints()
120 const t = useTheme()
121
122 if (!gtTablet) return null
123
124 return (
125 <View
126 style={[
127 a.w_full,
128 a.py_sm,
129 a.px_xl,
130 a.gap_xs,
131 a.justify_center,
132 {minHeight: 50},
133 ]}>
134 <Text style={[a.text_2xl, a.font_bold]}>{title}</Text>
135 {subtitle ? (
136 <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
137 {subtitle}
138 </Text>
139 ) : undefined}
140 </View>
141 )
142}
143
144let ListMaybePlaceholder = ({
145 isLoading,
146 noEmpty,
147 isError,
148 emptyTitle,
149 emptyMessage,
150 errorTitle,
151 errorMessage,
152 emptyType = 'page',
153 onRetry,
154 onGoBack,
155 hideBackButton,
156 sideBorders,
157 topBorder = true,
158}: {
159 isLoading: boolean
160 noEmpty?: boolean
161 isError?: boolean
162 emptyTitle?: string
163 emptyMessage?: string
164 errorTitle?: string
165 errorMessage?: string
166 emptyType?: 'page' | 'results'
167 onRetry?: () => Promise<unknown>
168 onGoBack?: () => void
169 hideBackButton?: boolean
170 sideBorders?: boolean
171 topBorder?: boolean
172}): React.ReactNode => {
173 const t = useTheme()
174 const {_} = useLingui()
175 const {gtMobile, gtTablet} = useBreakpoints()
176
177 if (isLoading) {
178 return (
179 <CenteredView
180 style={[
181 a.h_full_vh,
182 a.align_center,
183 !gtMobile ? a.justify_between : a.gap_5xl,
184 t.atoms.border_contrast_low,
185 {paddingTop: 175, paddingBottom: 110},
186 ]}
187 sideBorders={sideBorders ?? gtMobile}
188 topBorder={topBorder && !gtTablet}>
189 <View style={[a.w_full, a.align_center, {top: 100}]}>
190 <Loader size="xl" />
191 </View>
192 </CenteredView>
193 )
194 }
195
196 if (isError) {
197 return (
198 <Error
199 title={errorTitle ?? _(msg`Oops!`)}
200 message={errorMessage ?? _(msg`Something went wrong!`)}
201 onRetry={onRetry}
202 onGoBack={onGoBack}
203 sideBorders={sideBorders}
204 hideBackButton={hideBackButton}
205 />
206 )
207 }
208
209 if (!noEmpty) {
210 return (
211 <Error
212 title={
213 emptyTitle ??
214 (emptyType === 'results'
215 ? _(msg`No results found`)
216 : _(msg`Page not found`))
217 }
218 message={
219 emptyMessage ??
220 _(msg`We're sorry! We can't find the page you were looking for.`)
221 }
222 onRetry={onRetry}
223 onGoBack={onGoBack}
224 hideBackButton={hideBackButton}
225 sideBorders={sideBorders}
226 />
227 )
228 }
229
230 return null
231}
232ListMaybePlaceholder = memo(ListMaybePlaceholder)
233export {ListMaybePlaceholder}