From ed512d6dc5390555232bb4ac3f96f477751c33b1 Mon Sep 17 00:00:00 2001
From: Mary <148872143+mary-ext@users.noreply.github.com>
Date: Tue, 24 Sep 2024 23:21:06 +0700
Subject: Revamp edit image alt text dialog (#5461)
* revamp alt dialog
* readd the limit check
don't trim with enforceLen, it ruins copy-pasting long text and it's overall annoying behavior
* Update src/view/com/composer/photos/ImageAltTextDialog.tsx
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
---------
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
---
src/state/modals/index.tsx | 8 -
src/view/com/composer/photos/Gallery.tsx | 14 +-
.../com/composer/photos/ImageAltTextDialog.tsx | 121 +++++++++++++
src/view/com/modals/AltImage.tsx | 189 ---------------------
src/view/com/modals/Modal.tsx | 6 +-
src/view/com/modals/Modal.web.tsx | 9 +-
6 files changed, 136 insertions(+), 211 deletions(-)
create mode 100644 src/view/com/composer/photos/ImageAltTextDialog.tsx
delete mode 100644 src/view/com/modals/AltImage.tsx
(limited to 'src')
diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx
index 467853a25..9bc96cf5e 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 {ComposerImage} from '../gallery'
export interface EditProfileModal {
name: 'edit-profile'
@@ -43,12 +42,6 @@ export interface CropImageModal {
onSelect: (img?: RNImage) => void
}
-export interface AltTextImageModal {
- name: 'alt-text-image'
- image: ComposerImage
- onChange: (next: ComposerImage) => void
-}
-
export interface DeleteAccountModal {
name: 'delete-account'
}
@@ -131,7 +124,6 @@ export type Modal =
| ListAddRemoveUsersModal
// Posts
- | AltTextImageModal
| CropImageModal
| SelfLabelModal
diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx
index 775413e81..83c1e3c80 100644
--- a/src/view/com/composer/photos/Gallery.tsx
+++ b/src/view/com/composer/photos/Gallery.tsx
@@ -18,9 +18,10 @@ import {Dimensions} from '#/lib/media/types'
import {colors, s} from '#/lib/styles'
import {isNative} from '#/platform/detection'
import {ComposerImage, cropImage} from '#/state/gallery'
-import {useModalControls} from '#/state/modals'
import {Text} from '#/view/com/util/text/Text'
import {useTheme} from '#/alf'
+import * as Dialog from '#/components/Dialog'
+import {ImageAltTextDialog} from './ImageAltTextDialog'
const IMAGE_GAP = 8
@@ -141,7 +142,8 @@ const GalleryItem = ({
}: GalleryItemProps): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
- const {openModal} = useModalControls()
+
+ const altTextControl = Dialog.useDialogControl()
const onImageEdit = () => {
if (isNative) {
@@ -153,7 +155,7 @@ const GalleryItem = ({
const onAltTextEdit = () => {
Keyboard.dismiss()
- openModal({name: 'alt-text-image', image, onChange})
+ altTextControl.open()
}
return (
@@ -229,6 +231,12 @@ const GalleryItem = ({
accessible={true}
accessibilityIgnoresInvertColors
/>
+
+
)
}
diff --git a/src/view/com/composer/photos/ImageAltTextDialog.tsx b/src/view/com/composer/photos/ImageAltTextDialog.tsx
new file mode 100644
index 000000000..123e1066a
--- /dev/null
+++ b/src/view/com/composer/photos/ImageAltTextDialog.tsx
@@ -0,0 +1,121 @@
+import React from 'react'
+import {ImageStyle, useWindowDimensions, View} from 'react-native'
+import {Image} from 'expo-image'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {MAX_ALT_TEXT} from '#/lib/constants'
+import {isWeb} from '#/platform/detection'
+import {ComposerImage} from '#/state/gallery'
+import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonText} from '#/components/Button'
+import * as Dialog from '#/components/Dialog'
+import * as TextField from '#/components/forms/TextField'
+import {Text} from '#/components/Typography'
+
+type Props = {
+ control: Dialog.DialogOuterProps['control']
+ image: ComposerImage
+ onChange: (next: ComposerImage) => void
+}
+
+export const ImageAltTextDialog = (props: Props): React.ReactNode => {
+ return (
+
+
+
+
+
+ )
+}
+
+const ImageAltTextInner = ({
+ control,
+ image,
+ onChange,
+}: Props): React.ReactNode => {
+ const {_} = useLingui()
+ const t = useTheme()
+
+ const windim = useWindowDimensions()
+
+ const [altText, setAltText] = React.useState(image.alt)
+
+ const onPressSubmit = React.useCallback(() => {
+ control.close()
+ onChange({...image, alt: altText.trim()})
+ }, [control, image, altText, onChange])
+
+ const imageStyle = React.useMemo(() => {
+ const maxWidth = isWeb ? 450 : windim.width
+ const source = image.transformed ?? image.source
+
+ if (source.height > source.width) {
+ return {
+ resizeMode: 'contain',
+ width: '100%',
+ aspectRatio: 1,
+ borderRadius: 8,
+ }
+ }
+ return {
+ width: '100%',
+ height: (maxWidth / source.width) * source.height,
+ borderRadius: 8,
+ }
+ }, [image, windim])
+
+ return (
+
+
+
+
+
+ Add alt text
+
+
+
+
+
+
+
+
+
+
+ Descriptive alt text
+
+
+ setAltText(text)}
+ value={altText}
+ multiline
+ numberOfLines={3}
+ autoFocus
+ />
+
+
+
+
+
+ )
+}
diff --git a/src/view/com/modals/AltImage.tsx b/src/view/com/modals/AltImage.tsx
deleted file mode 100644
index c711f73a5..000000000
--- a/src/view/com/modals/AltImage.tsx
+++ /dev/null
@@ -1,189 +0,0 @@
-import React, {useCallback, useMemo, useState} from 'react'
-import {
- ImageStyle,
- ScrollView as RNScrollView,
- StyleSheet,
- TextInput as RNTextInput,
- TouchableOpacity,
- useWindowDimensions,
- View,
-} from 'react-native'
-import {Image} from 'expo-image'
-import {LinearGradient} from 'expo-linear-gradient'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {ComposerImage} from '#/state/gallery'
-import {useModalControls} from '#/state/modals'
-import {MAX_ALT_TEXT} from 'lib/constants'
-import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible'
-import {usePalette} from 'lib/hooks/usePalette'
-import {enforceLen} from 'lib/strings/helpers'
-import {gradients, s} from 'lib/styles'
-import {useTheme} from 'lib/ThemeContext'
-import {isAndroid, isWeb} from 'platform/detection'
-import {Text} from '../util/text/Text'
-import {ScrollView, TextInput} from './util'
-
-export const snapPoints = ['100%']
-
-interface Props {
- image: ComposerImage
- onChange: (next: ComposerImage) => void
-}
-
-export function Component({image, onChange}: Props) {
- const pal = usePalette('default')
- const theme = useTheme()
- const {_} = useLingui()
- const [altText, setAltText] = useState(image.alt)
- const windim = useWindowDimensions()
- const {closeModal} = useModalControls()
- const inputRef = React.useRef(null)
- const scrollViewRef = React.useRef(null)
- const keyboardShown = useIsKeyboardVisible()
-
- // Autofocus hack when we open the modal. We have to wait for the animation to complete first
- React.useEffect(() => {
- if (isAndroid) return
- setTimeout(() => {
- inputRef.current?.focus()
- }, 500)
- }, [])
-
- // We'd rather be at the bottom here so that we can easily dismiss the modal instead of having to scroll
- // (especially on android, it acts weird)
- React.useEffect(() => {
- if (keyboardShown[0]) {
- scrollViewRef.current?.scrollToEnd()
- }
- }, [keyboardShown])
-
- const imageStyles = useMemo(() => {
- const maxWidth = isWeb ? 450 : windim.width
- const media = image.transformed ?? image.source
- if (media.height > media.width) {
- return {
- resizeMode: 'contain',
- width: '100%',
- aspectRatio: 1,
- borderRadius: 8,
- }
- }
- return {
- width: '100%',
- height: (maxWidth / media.width) * media.height,
- borderRadius: 8,
- }
- }, [image, windim])
-
- const onUpdate = useCallback(
- (v: string) => {
- v = enforceLen(v, MAX_ALT_TEXT)
- setAltText(v)
- },
- [setAltText],
- )
-
- const onPressSave = useCallback(() => {
- onChange({
- ...image,
- alt: altText,
- })
-
- closeModal()
- }, [closeModal, image, altText, onChange])
-
- return (
-
-
-
-
-
-
-
-
-
-
- Done
-
-
-
-
-
-
- )
-}
-
-const styles = StyleSheet.create({
- scrollContainer: {
- flex: 1,
- height: '100%',
- paddingHorizontal: isWeb ? 0 : 12,
- paddingVertical: isWeb ? 0 : 24,
- },
- scrollInner: {
- gap: 12,
- paddingTop: isWeb ? 0 : 12,
- },
- imageContainer: {
- borderRadius: 8,
- },
- textArea: {
- borderWidth: 1,
- borderRadius: 6,
- paddingTop: 10,
- paddingHorizontal: 12,
- fontSize: 16,
- height: 100,
- textAlignVertical: 'top',
- },
- button: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- width: '100%',
- borderRadius: 32,
- padding: 10,
- },
- buttonControls: {
- gap: 8,
- paddingBottom: isWeb ? 0 : 50,
- },
-})
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index fd881ebc4..90e93821c 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -3,12 +3,11 @@ import {StyleSheet} from 'react-native'
import {SafeAreaView} from 'react-native-safe-area-context'
import BottomSheet from '@discord/bottom-sheet/src'
+import {usePalette} from '#/lib/hooks/usePalette'
import {useModalControls, useModals} from '#/state/modals'
-import {usePalette} from 'lib/hooks/usePalette'
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
import * as AddAppPassword from './AddAppPasswords'
-import * as AltImageModal from './AltImage'
import * as ChangeEmailModal from './ChangeEmail'
import * as ChangeHandleModal from './ChangeHandle'
import * as ChangePasswordModal from './ChangePassword'
@@ -74,9 +73,6 @@ export function ModalsContainer() {
} else if (activeModal?.name === 'self-label') {
snapPoints = SelfLabelModal.snapPoints
element =
- } else if (activeModal?.name === 'alt-text-image') {
- snapPoints = AltImageModal.snapPoints
- element =
} else if (activeModal?.name === 'change-handle') {
snapPoints = ChangeHandleModal.snapPoints
element =
diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx
index fe24695d2..c1024751f 100644
--- a/src/view/com/modals/Modal.web.tsx
+++ b/src/view/com/modals/Modal.web.tsx
@@ -2,13 +2,12 @@ import React from 'react'
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
+import {usePalette} from '#/lib/hooks/usePalette'
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
+import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import type {Modal as ModalIface} from '#/state/modals'
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'
@@ -53,7 +52,7 @@ function Modal({modal}: {modal: ModalIface}) {
}
const onPressMask = () => {
- if (modal.name === 'crop-image' || modal.name === 'alt-text-image') {
+ if (modal.name === 'crop-image') {
return // dont close on mask presses during crop
}
closeModal()
@@ -88,8 +87,6 @@ function Modal({modal}: {modal: ModalIface}) {
element =
} else if (modal.name === 'post-languages-settings') {
element =
- } else if (modal.name === 'alt-text-image') {
- element =
} else if (modal.name === 'verify-email') {
element =
} else if (modal.name === 'change-email') {
--
cgit 1.4.1