Use ALF for the embed consent modal (#3336)

This commit is contained in:
Samuel Newman 2024-04-09 00:58:18 +01:00 committed by GitHub
parent 2bc20b1752
commit a49a5a351d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 252 additions and 283 deletions

View file

@ -1,6 +1,4 @@
import {EmbedPlayerParams, getGifDims} from 'lib/strings/embed-player'
import React from 'react'
import {Image, ImageLoadEventData} from 'expo-image'
import {
ActivityIndicator,
GestureResponderEvent,
@ -9,13 +7,17 @@ import {
StyleSheet,
View,
} from 'react-native'
import {isIOS, isNative, isWeb} from '#/platform/detection'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useExternalEmbedsPrefs} from 'state/preferences'
import {useModalControls} from 'state/modals'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {Image, ImageLoadEventData} from 'expo-image'
import {AppBskyEmbedExternal} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {EmbedPlayerParams, getGifDims} from '#/lib/strings/embed-player'
import {isIOS, isNative, isWeb} from '#/platform/detection'
import {useExternalEmbedsPrefs} from '#/state/preferences'
import {useDialogControl} from '#/components/Dialog'
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
export function ExternalGifEmbed({
link,
@ -25,8 +27,9 @@ export function ExternalGifEmbed({
params: EmbedPlayerParams
}) {
const externalEmbedsPrefs = useExternalEmbedsPrefs()
const {openModal} = useModalControls()
const {_} = useLingui()
const consentDialogControl = useDialogControl()
const thumbHasLoaded = React.useRef(false)
const viewWidth = React.useRef(0)
@ -57,11 +60,7 @@ export function ExternalGifEmbed({
// Show consent if this is the first load
if (externalEmbedsPrefs?.[params.source] === undefined) {
openModal({
name: 'embed-consent',
source: params.source,
onAccept: load,
})
consentDialogControl.open()
return
}
// If the player isn't active, we want to activate it and prefetch the gif
@ -84,7 +83,13 @@ export function ExternalGifEmbed({
}
})
},
[externalEmbedsPrefs, isPlayerActive, load, openModal, params.source],
[
consentDialogControl,
externalEmbedsPrefs,
isPlayerActive,
load,
params.source,
],
)
const onLoad = React.useCallback((e: ImageLoadEventData) => {
@ -98,47 +103,55 @@ export function ExternalGifEmbed({
}, [])
return (
<Pressable
style={[
{height: imageDims.height},
styles.topRadius,
styles.gifContainer,
]}
onPress={onPlayPress}
onLayout={onLayout}
accessibilityRole="button"
accessibilityHint={_(msg`Plays the GIF`)}
accessibilityLabel={_(msg`Play ${link.title}`)}>
{(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay
<View style={[styles.layer, styles.overlayLayer]}>
<View style={[styles.overlayContainer, styles.topRadius]}>
{!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
<FontAwesomeIcon icon="play" size={42} color="white" />
) : (
// Activity indicator while gif loads
<ActivityIndicator size="large" color="white" />
)}
</View>
</View>
)}
<Image
source={{
uri:
!isPrefetched || (isWeb && !isAnimating)
? link.thumb
: params.playerUri,
}} // Web uses the thumb to control playback
style={{flex: 1}}
ref={imageRef}
onLoad={onLoad}
autoplay={isAnimating}
contentFit="contain"
accessibilityIgnoresInvertColors
accessibilityLabel={link.title}
accessibilityHint={link.title}
cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios
<>
<EmbedConsentDialog
control={consentDialogControl}
source={params.source}
onAccept={load}
/>
</Pressable>
<Pressable
style={[
{height: imageDims.height},
styles.topRadius,
styles.gifContainer,
]}
onPress={onPlayPress}
onLayout={onLayout}
accessibilityRole="button"
accessibilityHint={_(msg`Plays the GIF`)}
accessibilityLabel={_(msg`Play ${link.title}`)}>
{(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay
<View style={[styles.layer, styles.overlayLayer]}>
<View style={[styles.overlayContainer, styles.topRadius]}>
{!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active
<FontAwesomeIcon icon="play" size={42} color="white" />
) : (
// Activity indicator while gif loads
<ActivityIndicator size="large" color="white" />
)}
</View>
</View>
)}
<Image
source={{
uri:
!isPrefetched || (isWeb && !isAnimating)
? link.thumb
: params.playerUri,
}} // Web uses the thumb to control playback
style={{flex: 1}}
ref={imageRef}
onLoad={onLoad}
autoplay={isAnimating}
contentFit="contain"
accessibilityIgnoresInvertColors
accessibilityLabel={link.title}
accessibilityHint={link.title}
cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios
/>
</Pressable>
</>
)
}

View file

@ -13,20 +13,23 @@ import Animated, {
useAnimatedRef,
useFrameCallback,
} from 'react-native-reanimated'
import {Image} from 'expo-image'
import {WebView} from 'react-native-webview'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {WebView} from 'react-native-webview'
import {Image} from 'expo-image'
import {AppBskyEmbedExternal} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {AppBskyEmbedExternal} from '@atproto/api'
import {EmbedPlayerParams, getPlayerAspect} from 'lib/strings/embed-player'
import {NavigationProp} from '#/lib/routes/types'
import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
import {isNative} from '#/platform/detection'
import {useExternalEmbedsPrefs} from '#/state/preferences'
import {atoms as a} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
import {EventStopper} from '../EventStopper'
import {isNative} from 'platform/detection'
import {NavigationProp} from 'lib/routes/types'
import {useExternalEmbedsPrefs} from 'state/preferences'
import {useModalControls} from 'state/modals'
interface ShouldStartLoadRequest {
url: string
@ -48,7 +51,7 @@ function PlaceholderOverlay({
if (isPlayerActive && !isLoading) return null
return (
<View style={[styles.layer, styles.overlayLayer]}>
<View style={[a.absolute, a.inset_0, styles.overlayLayer]}>
<Pressable
accessibilityRole="button"
accessibilityLabel={_(msg`Play Video`)}
@ -89,7 +92,7 @@ function Player({
if (!isPlayerActive) return null
return (
<EventStopper style={[styles.layer, styles.playerLayer]}>
<EventStopper style={[a.absolute, a.inset_0, styles.playerLayer]}>
<WebView
javaScriptEnabled={true}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
@ -119,7 +122,7 @@ export function ExternalPlayer({
const insets = useSafeAreaInsets()
const windowDims = useWindowDimensions()
const externalEmbedsPrefs = useExternalEmbedsPrefs()
const {openModal} = useModalControls()
const consentDialogControl = useDialogControl()
const [isPlayerActive, setPlayerActive] = React.useState(false)
const [isLoading, setIsLoading] = React.useState(true)
@ -187,37 +190,47 @@ export function ExternalPlayer({
event.preventDefault()
if (externalEmbedsPrefs?.[params.source] === undefined) {
openModal({
name: 'embed-consent',
source: params.source,
onAccept: () => {
setPlayerActive(true)
},
})
consentDialogControl.open()
return
}
setPlayerActive(true)
},
[externalEmbedsPrefs, openModal, params.source],
[externalEmbedsPrefs, consentDialogControl, params.source],
)
const onAcceptConsent = React.useCallback(() => {
setPlayerActive(true)
}, [])
return (
<Animated.View ref={viewRef} collapsable={false} style={[aspect]}>
{link.thumb && (!isPlayerActive || isLoading) && (
<Image
style={[{flex: 1}, styles.topRadius]}
source={{uri: link.thumb}}
accessibilityIgnoresInvertColors
/>
)}
<PlaceholderOverlay
isLoading={isLoading}
isPlayerActive={isPlayerActive}
onPress={onPlayPress}
<>
<EmbedConsentDialog
control={consentDialogControl}
source={params.source}
onAccept={onAcceptConsent}
/>
<Player isPlayerActive={isPlayerActive} params={params} onLoad={onLoad} />
</Animated.View>
<Animated.View ref={viewRef} collapsable={false} style={[aspect]}>
{link.thumb && (!isPlayerActive || isLoading) && (
<Image
style={[a.flex_1, styles.topRadius]}
source={{uri: link.thumb}}
accessibilityIgnoresInvertColors
/>
)}
<PlaceholderOverlay
isLoading={isLoading}
isPlayerActive={isPlayerActive}
onPress={onPlayPress}
/>
<Player
isPlayerActive={isPlayerActive}
params={params}
onLoad={onLoad}
/>
</Animated.View>
</>
)
}
@ -226,13 +239,6 @@ const styles = StyleSheet.create({
borderTopLeftRadius: 6,
borderTopRightRadius: 6,
},
layer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
overlayContainer: {
flex: 1,
justifyContent: 'center',