diff --git a/src/components/dialogs/EmbedConsent.tsx b/src/components/dialogs/EmbedConsent.tsx new file mode 100644 index 0000000000..c3fefd9f09 --- /dev/null +++ b/src/components/dialogs/EmbedConsent.tsx @@ -0,0 +1,119 @@ +import React, {useCallback} from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import { + type EmbedPlayerSource, + embedPlayerSources, + externalEmbedLabels, +} from '#/lib/strings/embed-player' +import {useSetExternalEmbedPref} from '#/state/preferences' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import * as Dialog from '#/components/Dialog' +import {Button, ButtonText} from '../Button' +import {Text} from '../Typography' + +export function EmbedConsentDialog({ + control, + source, + onAccept, +}: { + control: Dialog.DialogControlProps + source: EmbedPlayerSource + onAccept: () => void +}) { + const {_} = useLingui() + const t = useTheme() + const setExternalEmbedPref = useSetExternalEmbedPref() + const {gtMobile} = useBreakpoints() + + const onShowAllPress = useCallback(() => { + for (const key of embedPlayerSources) { + setExternalEmbedPref(key, 'show') + } + onAccept() + control.close() + }, [control, onAccept, setExternalEmbedPref]) + + const onShowPress = useCallback(() => { + setExternalEmbedPref(source, 'show') + onAccept() + control.close() + }, [control, onAccept, setExternalEmbedPref, source]) + + const onHidePress = useCallback(() => { + setExternalEmbedPref(source, 'hide') + control.close() + }, [control, setExternalEmbedPref, source]) + + return ( + + + + + + + External Media + + + + + + This content is hosted by {externalEmbedLabels[source]}. Do you + want to enable external media? + + + + + + External media may allow websites to collect information about + you and your device. No information is sent or requested until + you press the "play" button. + + + + + + + + + + + + ) +} diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index e0bcc2f0fd..cc0f9c8b83 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -3,7 +3,6 @@ import {Image as RNImage} from 'react-native-image-crop-picker' import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' -import {EmbedPlayerSource} from '#/lib/strings/embed-player' import {GalleryModel} from '#/state/models/media/gallery' import {ImageModel} from '#/state/models/media/image' import {ThreadgateSetting} from '../queries/threadgate' @@ -125,12 +124,6 @@ export interface LinkWarningModal { share?: boolean } -export interface EmbedConsentModal { - name: 'embed-consent' - source: EmbedPlayerSource - onAccept: () => void -} - export interface InAppBrowserConsentModal { name: 'in-app-browser-consent' href: string @@ -169,7 +162,6 @@ export type Modal = // Generic | LinkWarningModal - | EmbedConsentModal | InAppBrowserConsentModal const ModalContext = React.createContext<{ diff --git a/src/view/com/modals/EmbedConsent.tsx b/src/view/com/modals/EmbedConsent.tsx deleted file mode 100644 index 9419447288..0000000000 --- a/src/view/com/modals/EmbedConsent.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import React from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {LinearGradient} from 'expo-linear-gradient' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import { - EmbedPlayerSource, - embedPlayerSources, - externalEmbedLabels, -} from '#/lib/strings/embed-player' -import {useModalControls} from '#/state/modals' -import {useSetExternalEmbedPref} from '#/state/preferences/external-embeds-prefs' -import {usePalette} from 'lib/hooks/usePalette' -import {colors, gradients, s} from 'lib/styles' -import {Text} from '../util/text/Text' -import {ScrollView} from './util' - -export const snapPoints = [450] - -export function Component({ - onAccept, - source, -}: { - onAccept: () => void - source: EmbedPlayerSource -}) { - const pal = usePalette('default') - const {closeModal} = useModalControls() - const {_} = useLingui() - const setExternalEmbedPref = useSetExternalEmbedPref() - const {isMobile} = useWebMediaQueries() - - const onShowAllPress = React.useCallback(() => { - for (const key of embedPlayerSources) { - setExternalEmbedPref(key, 'show') - } - onAccept() - closeModal() - }, [closeModal, onAccept, setExternalEmbedPref]) - - const onShowPress = React.useCallback(() => { - setExternalEmbedPref(source, 'show') - onAccept() - closeModal() - }, [closeModal, onAccept, setExternalEmbedPref, source]) - - const onHidePress = React.useCallback(() => { - setExternalEmbedPref(source, 'hide') - closeModal() - }, [closeModal, setExternalEmbedPref, source]) - - return ( - - - External Media - - - - - This content is hosted by {externalEmbedLabels[source]}. Do you want - to enable external media? - - - - - - External media may allow websites to collect information about you and - your device. No information is sent or requested until you press the - "play" button. - - - - - - - Enable External Media - - - - - - - - Enable {externalEmbedLabels[source]} only - - - - - - - - No thanks - - - - - ) -} - -const styles = StyleSheet.create({ - title: { - textAlign: 'center', - fontWeight: 'bold', - fontSize: 24, - marginBottom: 12, - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - borderRadius: 32, - padding: 14, - backgroundColor: colors.gray1, - }, -}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 85ffccf12b..6524813015 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -15,7 +15,6 @@ import * as ChangePasswordModal from './ChangePassword' import * as CreateOrEditListModal from './CreateOrEditList' import * as DeleteAccountModal from './DeleteAccount' import * as EditProfileModal from './EditProfile' -import * as EmbedConsentModal from './EmbedConsent' import * as InAppBrowserConsentModal from './InAppBrowserConsent' import * as InviteCodesModal from './InviteCodes' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' @@ -116,9 +115,6 @@ export function ModalsContainer() { } else if (activeModal?.name === 'link-warning') { snapPoints = LinkWarningModal.snapPoints element = - } else if (activeModal?.name === 'embed-consent') { - snapPoints = EmbedConsentModal.snapPoints - element = } else if (activeModal?.name === 'in-app-browser-consent') { snapPoints = InAppBrowserConsentModal.snapPoints element = diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 7e5d548ace..f95c748111 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -1,33 +1,32 @@ import React from 'react' -import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native' +import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' -import {useModals, useModalControls} from '#/state/modals' +import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' import type {Modal as ModalIface} from '#/state/modals' -import * as EditProfileModal from './EditProfile' +import {useModalControls, useModals} from '#/state/modals' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import * as AddAppPassword from './AddAppPasswords' +import * as AltTextImageModal from './AltImage' +import * as ChangeEmailModal from './ChangeEmail' +import * as ChangeHandleModal from './ChangeHandle' +import * as ChangePasswordModal from './ChangePassword' import * as CreateOrEditListModal from './CreateOrEditList' -import * as UserAddRemoveLists from './UserAddRemoveLists' -import * as ListAddUserModal from './ListAddRemoveUsers' -import * as DeleteAccountModal from './DeleteAccount' -import * as RepostModal from './Repost' -import * as SelfLabelModal from './SelfLabel' -import * as ThreadgateModal from './Threadgate' import * as CropImageModal from './crop-image/CropImage.web' -import * as AltTextImageModal from './AltImage' +import * as DeleteAccountModal from './DeleteAccount' import * as EditImageModal from './EditImage' -import * as ChangeHandleModal from './ChangeHandle' +import * as EditProfileModal from './EditProfile' import * as InviteCodesModal from './InviteCodes' -import * as AddAppPassword from './AddAppPasswords' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' -import * as VerifyEmailModal from './VerifyEmail' -import * as ChangeEmailModal from './ChangeEmail' -import * as ChangePasswordModal from './ChangePassword' import * as LinkWarningModal from './LinkWarning' -import * as EmbedConsentModal from './EmbedConsent' +import * as ListAddUserModal from './ListAddRemoveUsers' +import * as RepostModal from './Repost' +import * as SelfLabelModal from './SelfLabel' +import * as ThreadgateModal from './Threadgate' +import * as UserAddRemoveLists from './UserAddRemoveLists' +import * as VerifyEmailModal from './VerifyEmail' export function ModalsContainer() { const {isModalActive, activeModals} = useModals() @@ -112,8 +111,6 @@ function Modal({modal}: {modal: ModalIface}) { element = } else if (modal.name === 'link-warning') { element = - } else if (modal.name === 'embed-consent') { - element = } else { return null } diff --git a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx b/src/view/com/util/post-embeds/ExternalGifEmbed.tsx index f06c8b794d..b2720752ca 100644 --- a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx +++ b/src/view/com/util/post-embeds/ExternalGifEmbed.tsx @@ -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 {Image, ImageLoadEventData} from 'expo-image' +import {AppBskyEmbedExternal} from '@atproto/api' 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 {AppBskyEmbedExternal} from '@atproto/api' +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 ( - - {(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay - - - {!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active - - ) : ( - // Activity indicator while gif loads - - )} - - - )} - + - + + + {(!isPrefetched || !isAnimating) && ( // If we have not loaded or are not animating, show the overlay + + + {!isAnimating || !isPlayerActive ? ( // Play button when not animating or not active + + ) : ( + // Activity indicator while gif loads + + )} + + + )} + + + ) } diff --git a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx index cf2db5b333..9fdede877d 100644 --- a/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx +++ b/src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx @@ -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 ( - + + { - setPlayerActive(true) - }, - }) + consentDialogControl.open() return } setPlayerActive(true) }, - [externalEmbedsPrefs, openModal, params.source], + [externalEmbedsPrefs, consentDialogControl, params.source], ) + const onAcceptConsent = React.useCallback(() => { + setPlayerActive(true) + }, []) + return ( - - {link.thumb && (!isPlayerActive || isLoading) && ( - - )} - + - - + + + {link.thumb && (!isPlayerActive || isLoading) && ( + + )} + + + + ) } @@ -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',