about summary refs log tree commit diff
path: root/src/view/com/modals
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/modals')
-rw-r--r--src/view/com/modals/AltImage.tsx147
-rw-r--r--src/view/com/modals/EmbedConsent.tsx153
-rw-r--r--src/view/com/modals/Modal.tsx4
-rw-r--r--src/view/com/modals/Modal.web.tsx3
4 files changed, 225 insertions, 82 deletions
diff --git a/src/view/com/modals/AltImage.tsx b/src/view/com/modals/AltImage.tsx
index a2e918317..5156511d6 100644
--- a/src/view/com/modals/AltImage.tsx
+++ b/src/view/com/modals/AltImage.tsx
@@ -1,14 +1,12 @@
 import React, {useMemo, useCallback, useState} from 'react'
 import {
   ImageStyle,
-  KeyboardAvoidingView,
-  ScrollView,
   StyleSheet,
-  TextInput,
   TouchableOpacity,
   View,
   useWindowDimensions,
 } from 'react-native'
+import {ScrollView, TextInput} from './util'
 import {Image} from 'expo-image'
 import {usePalette} from 'lib/hooks/usePalette'
 import {gradients, s} from 'lib/styles'
@@ -17,13 +15,13 @@ import {MAX_ALT_TEXT} from 'lib/constants'
 import {useTheme} from 'lib/ThemeContext'
 import {Text} from '../util/text/Text'
 import LinearGradient from 'react-native-linear-gradient'
-import {isAndroid, isWeb} from 'platform/detection'
+import {isWeb} from 'platform/detection'
 import {ImageModel} from 'state/models/media/image'
 import {useLingui} from '@lingui/react'
 import {Trans, msg} from '@lingui/macro'
 import {useModalControls} from '#/state/modals'
 
-export const snapPoints = ['fullscreen']
+export const snapPoints = ['100%']
 
 interface Props {
   image: ImageModel
@@ -54,102 +52,86 @@ export function Component({image}: Props) {
     }
   }, [image, windim])
 
+  const onUpdate = useCallback(
+    (v: string) => {
+      v = enforceLen(v, MAX_ALT_TEXT)
+      setAltText(v)
+      image.setAltText(v)
+    },
+    [setAltText, image],
+  )
+
   const onPressSave = useCallback(() => {
     image.setAltText(altText)
     closeModal()
   }, [closeModal, image, altText])
 
-  const onPressCancel = () => {
-    closeModal()
-  }
-
   return (
-    <KeyboardAvoidingView
-      behavior={isAndroid ? 'height' : 'padding'}
-      style={[pal.view, styles.container]}>
-      <ScrollView
-        testID="altTextImageModal"
-        style={styles.scrollContainer}
-        keyboardShouldPersistTaps="always"
-        nativeID="imageAltText">
-        <View style={styles.scrollInner}>
-          <View style={[pal.viewLight, styles.imageContainer]}>
-            <Image
-              testID="selectedPhotoImage"
-              style={imageStyles}
-              source={{
-                uri: image.cropped?.path ?? image.path,
-              }}
-              contentFit="contain"
-              accessible={true}
-              accessibilityIgnoresInvertColors
-            />
-          </View>
-          <TextInput
-            testID="altTextImageInput"
-            style={[styles.textArea, pal.border, pal.text]}
-            keyboardAppearance={theme.colorScheme}
-            multiline
-            placeholder="Add alt text"
-            placeholderTextColor={pal.colors.textLight}
-            value={altText}
-            onChangeText={text => setAltText(enforceLen(text, MAX_ALT_TEXT))}
-            accessibilityLabel={_(msg`Image alt text`)}
-            accessibilityHint=""
-            accessibilityLabelledBy="imageAltText"
-            autoFocus
+    <ScrollView
+      testID="altTextImageModal"
+      style={[pal.view, styles.scrollContainer]}
+      keyboardShouldPersistTaps="always"
+      nativeID="imageAltText">
+      <View style={styles.scrollInner}>
+        <View style={[pal.viewLight, styles.imageContainer]}>
+          <Image
+            testID="selectedPhotoImage"
+            style={imageStyles}
+            source={{
+              uri: image.cropped?.path ?? image.path,
+            }}
+            contentFit="contain"
+            accessible={true}
+            accessibilityIgnoresInvertColors
           />
-          <View style={styles.buttonControls}>
-            <TouchableOpacity
-              testID="altTextImageSaveBtn"
-              onPress={onPressSave}
-              accessibilityLabel={_(msg`Save alt text`)}
-              accessibilityHint={`Saves alt text, which reads: ${altText}`}
-              accessibilityRole="button">
-              <LinearGradient
-                colors={[gradients.blueLight.start, gradients.blueLight.end]}
-                start={{x: 0, y: 0}}
-                end={{x: 1, y: 1}}
-                style={[styles.button]}>
-                <Text type="button-lg" style={[s.white, s.bold]}>
-                  <Trans>Save</Trans>
-                </Text>
-              </LinearGradient>
-            </TouchableOpacity>
-            <TouchableOpacity
-              testID="altTextImageCancelBtn"
-              onPress={onPressCancel}
-              accessibilityRole="button"
-              accessibilityLabel={_(msg`Cancel add image alt text`)}
-              accessibilityHint=""
-              onAccessibilityEscape={onPressCancel}>
-              <View style={[styles.button]}>
-                <Text type="button-lg" style={[pal.textLight]}>
-                  <Trans>Cancel</Trans>
-                </Text>
-              </View>
-            </TouchableOpacity>
-          </View>
         </View>
-      </ScrollView>
-    </KeyboardAvoidingView>
+        <TextInput
+          testID="altTextImageInput"
+          style={[styles.textArea, pal.border, pal.text]}
+          keyboardAppearance={theme.colorScheme}
+          multiline
+          placeholder={_(msg`Add alt text`)}
+          placeholderTextColor={pal.colors.textLight}
+          value={altText}
+          onChangeText={onUpdate}
+          accessibilityLabel={_(msg`Image alt text`)}
+          accessibilityHint=""
+          accessibilityLabelledBy="imageAltText"
+          autoFocus
+        />
+        <View style={styles.buttonControls}>
+          <TouchableOpacity
+            testID="altTextImageSaveBtn"
+            onPress={onPressSave}
+            accessibilityLabel={_(msg`Save alt text`)}
+            accessibilityHint=""
+            accessibilityRole="button">
+            <LinearGradient
+              colors={[gradients.blueLight.start, gradients.blueLight.end]}
+              start={{x: 0, y: 0}}
+              end={{x: 1, y: 1}}
+              style={[styles.button]}>
+              <Text type="button-lg" style={[s.white, s.bold]}>
+                <Trans>Done</Trans>
+              </Text>
+            </LinearGradient>
+          </TouchableOpacity>
+        </View>
+      </View>
+    </ScrollView>
   )
 }
 
 const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    height: '100%',
-    width: '100%',
-    paddingVertical: isWeb ? 0 : 18,
-  },
   scrollContainer: {
     flex: 1,
     height: '100%',
     paddingHorizontal: isWeb ? 0 : 12,
+    paddingVertical: isWeb ? 0 : 24,
   },
   scrollInner: {
     gap: 12,
+    paddingTop: isWeb ? 0 : 12,
   },
   imageContainer: {
     borderRadius: 8,
@@ -173,5 +155,6 @@ const styles = StyleSheet.create({
   },
   buttonControls: {
     gap: 8,
+    paddingBottom: isWeb ? 0 : 50,
   },
 })
diff --git a/src/view/com/modals/EmbedConsent.tsx b/src/view/com/modals/EmbedConsent.tsx
new file mode 100644
index 000000000..04104c52e
--- /dev/null
+++ b/src/view/com/modals/EmbedConsent.tsx
@@ -0,0 +1,153 @@
+import React from 'react'
+import {StyleSheet, TouchableOpacity, View} from 'react-native'
+import LinearGradient from 'react-native-linear-gradient'
+import {s, colors, gradients} from 'lib/styles'
+import {Text} from '../util/text/Text'
+import {ScrollView} from './util'
+import {usePalette} from 'lib/hooks/usePalette'
+import {
+  EmbedPlayerSource,
+  embedPlayerSources,
+  externalEmbedLabels,
+} from '#/lib/strings/embed-player'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useModalControls} from '#/state/modals'
+import {useSetExternalEmbedPref} from '#/state/preferences/external-embeds-prefs'
+import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
+
+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 (
+    <ScrollView
+      testID="embedConsentModal"
+      style={[
+        s.flex1,
+        pal.view,
+        isMobile
+          ? {paddingHorizontal: 20, paddingTop: 10}
+          : {paddingHorizontal: 30},
+      ]}>
+      <Text style={[pal.text, styles.title]}>
+        <Trans>External Media</Trans>
+      </Text>
+
+      <Text style={pal.text}>
+        <Trans>
+          This content is hosted by {externalEmbedLabels[source]}. Do you want
+          to enable external media?
+        </Trans>
+      </Text>
+      <View style={[s.mt10]} />
+      <Text style={pal.textLight}>
+        <Trans>
+          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.
+        </Trans>
+      </Text>
+      <View style={[s.mt20]} />
+      <TouchableOpacity
+        testID="enableAllBtn"
+        onPress={onShowAllPress}
+        accessibilityRole="button"
+        accessibilityLabel={_(
+          msg`Show embeds from ${externalEmbedLabels[source]}`,
+        )}
+        accessibilityHint=""
+        onAccessibilityEscape={closeModal}>
+        <LinearGradient
+          colors={[gradients.blueLight.start, gradients.blueLight.end]}
+          start={{x: 0, y: 0}}
+          end={{x: 1, y: 1}}
+          style={[styles.btn]}>
+          <Text style={[s.white, s.bold, s.f18]}>
+            <Trans>Enable External Media</Trans>
+          </Text>
+        </LinearGradient>
+      </TouchableOpacity>
+      <View style={[s.mt10]} />
+      <TouchableOpacity
+        testID="enableSourceBtn"
+        onPress={onShowPress}
+        accessibilityRole="button"
+        accessibilityLabel={_(
+          msg`Never load embeds from ${externalEmbedLabels[source]}`,
+        )}
+        accessibilityHint=""
+        onAccessibilityEscape={closeModal}>
+        <View style={[styles.btn, pal.btn]}>
+          <Text style={[pal.text, s.bold, s.f18]}>
+            <Trans>Enable {externalEmbedLabels[source]} only</Trans>
+          </Text>
+        </View>
+      </TouchableOpacity>
+      <View style={[s.mt10]} />
+      <TouchableOpacity
+        testID="disableSourceBtn"
+        onPress={onHidePress}
+        accessibilityRole="button"
+        accessibilityLabel={_(
+          msg`Never load embeds from ${externalEmbedLabels[source]}`,
+        )}
+        accessibilityHint=""
+        onAccessibilityEscape={closeModal}>
+        <View style={[styles.btn, pal.btn]}>
+          <Text style={[pal.text, s.bold, s.f18]}>
+            <Trans>No thanks</Trans>
+          </Text>
+        </View>
+      </TouchableOpacity>
+    </ScrollView>
+  )
+}
+
+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 2aac20dac..f9d211d07 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -38,6 +38,7 @@ import * as VerifyEmailModal from './VerifyEmail'
 import * as ChangeEmailModal from './ChangeEmail'
 import * as SwitchAccountModal from './SwitchAccount'
 import * as LinkWarningModal from './LinkWarning'
+import * as EmbedConsentModal from './EmbedConsent'
 
 const DEFAULT_SNAPPOINTS = ['90%']
 const HANDLE_HEIGHT = 24
@@ -176,6 +177,9 @@ export function ModalsContainer() {
   } else if (activeModal?.name === 'link-warning') {
     snapPoints = LinkWarningModal.snapPoints
     element = <LinkWarningModal.Component {...activeModal} />
+  } else if (activeModal?.name === 'embed-consent') {
+    snapPoints = EmbedConsentModal.snapPoints
+    element = <EmbedConsentModal.Component {...activeModal} />
   } else {
     return null
   }
diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx
index 12138f54d..c43a8a6ce 100644
--- a/src/view/com/modals/Modal.web.tsx
+++ b/src/view/com/modals/Modal.web.tsx
@@ -34,6 +34,7 @@ import * as BirthDateSettingsModal from './BirthDateSettings'
 import * as VerifyEmailModal from './VerifyEmail'
 import * as ChangeEmailModal from './ChangeEmail'
 import * as LinkWarningModal from './LinkWarning'
+import * as EmbedConsentModal from './EmbedConsent'
 
 export function ModalsContainer() {
   const {isModalActive, activeModals} = useModals()
@@ -129,6 +130,8 @@ function Modal({modal}: {modal: ModalIface}) {
     element = <ChangeEmailModal.Component />
   } else if (modal.name === 'link-warning') {
     element = <LinkWarningModal.Component {...modal} />
+  } else if (modal.name === 'embed-consent') {
+    element = <EmbedConsentModal.Component {...modal} />
   } else {
     return null
   }