Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[Videos] Add error boundary to native (#4914)

* move error fallback to own component

* use error boundary on native

---------

Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com>

authored by

Samuel Newman
Samuel Newman
and committed by
GitHub
c2131bb0 ab0da7c8

+116 -64
+48 -23
src/view/com/util/post-embeds/VideoEmbed.tsx
··· 1 - import React from 'react' 1 + import React, {useCallback, useState} from 'react' 2 2 import {View} from 'react-native' 3 - import {msg} from '@lingui/macro' 3 + import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {VideoEmbedInnerNative} from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative' ··· 8 8 import {Button, ButtonIcon} from '#/components/Button' 9 9 import {Play_Filled_Corner2_Rounded as PlayIcon} from '#/components/icons/Play' 10 10 import {VisibilityView} from '../../../../../modules/expo-bluesky-swiss-army' 11 + import {ErrorBoundary} from '../ErrorBoundary' 11 12 import {useActiveVideoView} from './ActiveVideoContext' 13 + import * as VideoFallback from './VideoEmbedInner/VideoFallback' 12 14 13 15 export function VideoEmbed({source}: {source: string}) { 14 16 const t = useTheme() 15 17 const {active, setActive} = useActiveVideoView({source}) 16 18 const {_} = useLingui() 17 19 20 + const [key, setKey] = useState(0) 21 + const renderError = useCallback( 22 + (error: unknown) => ( 23 + <VideoError error={error} retry={() => setKey(key + 1)} /> 24 + ), 25 + [key], 26 + ) 27 + 18 28 return ( 19 29 <View 20 30 style={[ ··· 25 35 t.atoms.bg_contrast_25, 26 36 a.my_xs, 27 37 ]}> 28 - <VisibilityView 29 - enabled={true} 30 - onChangeStatus={isActive => { 31 - if (isActive) { 32 - setActive() 33 - } 34 - }}> 35 - {active ? ( 36 - <VideoEmbedInnerNative /> 37 - ) : ( 38 - <Button 39 - style={[a.flex_1, t.atoms.bg_contrast_25]} 40 - onPress={setActive} 41 - label={_(msg`Play video`)} 42 - variant="ghost" 43 - color="secondary" 44 - size="large"> 45 - <ButtonIcon icon={PlayIcon} /> 46 - </Button> 47 - )} 48 - </VisibilityView> 38 + <ErrorBoundary renderError={renderError} key={key}> 39 + <VisibilityView 40 + enabled={true} 41 + onChangeStatus={isActive => { 42 + if (isActive) { 43 + setActive() 44 + } 45 + }}> 46 + {active ? ( 47 + <VideoEmbedInnerNative /> 48 + ) : ( 49 + <Button 50 + style={[a.flex_1, t.atoms.bg_contrast_25]} 51 + onPress={setActive} 52 + label={_(msg`Play video`)} 53 + variant="ghost" 54 + color="secondary" 55 + size="large"> 56 + <ButtonIcon icon={PlayIcon} /> 57 + </Button> 58 + )} 59 + </VisibilityView> 60 + </ErrorBoundary> 49 61 </View> 50 62 ) 51 63 } 64 + 65 + function VideoError({retry}: {error: unknown; retry: () => void}) { 66 + return ( 67 + <VideoFallback.Container> 68 + <VideoFallback.Text> 69 + <Trans> 70 + An error occurred while loading the video. Please try again later. 71 + </Trans> 72 + </VideoFallback.Text> 73 + <VideoFallback.RetryButton onPress={retry} /> 74 + </VideoFallback.Container> 75 + ) 76 + }
+7 -41
src/view/com/util/post-embeds/VideoEmbed.web.tsx
··· 1 1 import React, {useCallback, useEffect, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 - import {msg, Trans} from '@lingui/macro' 4 - import {useLingui} from '@lingui/react' 3 + import {Trans} from '@lingui/macro' 5 4 6 5 import { 7 6 HLSUnsupportedError, 8 7 VideoEmbedInnerWeb, 9 8 } from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb' 10 9 import {atoms as a, useTheme} from '#/alf' 11 - import {Button, ButtonText} from '#/components/Button' 12 - import {Text} from '#/components/Typography' 13 10 import {ErrorBoundary} from '../ErrorBoundary' 14 11 import {useActiveVideoView} from './ActiveVideoContext' 12 + import * as VideoFallback from './VideoEmbedInner/VideoFallback' 15 13 16 14 export function VideoEmbed({source}: {source: string}) { 17 15 const t = useTheme() ··· 138 136 } 139 137 140 138 function VideoError({error, retry}: {error: unknown; retry: () => void}) { 141 - const t = useTheme() 142 - const {_} = useLingui() 143 - 144 139 const isHLS = error instanceof HLSUnsupportedError 145 140 146 141 return ( 147 - <View 148 - style={[ 149 - a.flex_1, 150 - t.atoms.bg_contrast_25, 151 - a.justify_center, 152 - a.align_center, 153 - a.px_lg, 154 - a.border, 155 - t.atoms.border_contrast_low, 156 - a.rounded_sm, 157 - a.gap_lg, 158 - ]}> 159 - <Text 160 - style={[ 161 - a.text_center, 162 - t.atoms.text_contrast_high, 163 - a.text_md, 164 - a.leading_snug, 165 - {maxWidth: 300}, 166 - ]}> 142 + <VideoFallback.Container> 143 + <VideoFallback.Text> 167 144 {isHLS ? ( 168 145 <Trans> 169 146 Your browser does not support the video format. Please try a ··· 174 151 An error occurred while loading the video. Please try again later. 175 152 </Trans> 176 153 )} 177 - </Text> 178 - {!isHLS && ( 179 - <Button 180 - onPress={retry} 181 - size="small" 182 - color="secondary_inverted" 183 - variant="solid" 184 - label={_(msg`Retry`)}> 185 - <ButtonText> 186 - <Trans>Retry</Trans> 187 - </ButtonText> 188 - </Button> 189 - )} 190 - </View> 154 + </VideoFallback.Text> 155 + {!isHLS && <VideoFallback.RetryButton onPress={retry} />} 156 + </VideoFallback.Container> 191 157 ) 192 158 }
+61
src/view/com/util/post-embeds/VideoEmbedInner/VideoFallback.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {atoms as a, useTheme} from '#/alf' 7 + import {Button, ButtonText} from '#/components/Button' 8 + import {Text as TypoText} from '#/components/Typography' 9 + 10 + export function Container({children}: {children: React.ReactNode}) { 11 + const t = useTheme() 12 + return ( 13 + <View 14 + style={[ 15 + a.flex_1, 16 + t.atoms.bg_contrast_25, 17 + a.justify_center, 18 + a.align_center, 19 + a.px_lg, 20 + a.border, 21 + t.atoms.border_contrast_low, 22 + a.rounded_sm, 23 + a.gap_lg, 24 + ]}> 25 + {children} 26 + </View> 27 + ) 28 + } 29 + 30 + export function Text({children}: {children: React.ReactNode}) { 31 + const t = useTheme() 32 + return ( 33 + <TypoText 34 + style={[ 35 + a.text_center, 36 + t.atoms.text_contrast_high, 37 + a.text_md, 38 + a.leading_snug, 39 + {maxWidth: 300}, 40 + ]}> 41 + {children} 42 + </TypoText> 43 + ) 44 + } 45 + 46 + export function RetryButton({onPress}: {onPress: () => void}) { 47 + const {_} = useLingui() 48 + 49 + return ( 50 + <Button 51 + onPress={onPress} 52 + size="small" 53 + color="secondary_inverted" 54 + variant="solid" 55 + label={_(msg`Retry`)}> 56 + <ButtonText> 57 + <Trans>Retry</Trans> 58 + </ButtonText> 59 + </Button> 60 + ) 61 + }