Use ALF for the embed consent modal (#3336)
This commit is contained in:
parent
2bc20b1752
commit
a49a5a351d
7 changed files with 252 additions and 283 deletions
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue