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