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>
This commit is contained in:
Mary
2024-09-24 23:21:06 +07:00
committed by GitHub
parent 8ea89469ef
commit ed512d6dc5
6 changed files with 136 additions and 211 deletions
-189
View File
@@ -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<RNTextInput>(null)
const scrollViewRef = React.useRef<RNScrollView>(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<ImageStyle>(() => {
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 (
<ScrollView
testID="altTextImageModal"
style={[pal.view, styles.scrollContainer]}
keyboardShouldPersistTaps="always"
ref={scrollViewRef}
nativeID="imageAltText">
<View style={styles.scrollInner}>
<View style={[pal.viewLight, styles.imageContainer]}>
<Image
testID="selectedPhotoImage"
style={imageStyles}
source={{uri: (image.transformed ?? image.source).path}}
contentFit="contain"
accessible={true}
accessibilityIgnoresInvertColors
enableLiveTextInteraction
/>
</View>
<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"
// @ts-ignore This is fine, type is weird on the BottomSheetTextInput
ref={inputRef}
/>
<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({
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,
},
})
+1 -5
View File
@@ -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 = <SelfLabelModal.Component {...activeModal} />
} else if (activeModal?.name === 'alt-text-image') {
snapPoints = AltImageModal.snapPoints
element = <AltImageModal.Component {...activeModal} />
} else if (activeModal?.name === 'change-handle') {
snapPoints = ChangeHandleModal.snapPoints
element = <ChangeHandleModal.Component {...activeModal} />
+3 -6
View File
@@ -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 = <ContentLanguagesSettingsModal.Component />
} else if (modal.name === 'post-languages-settings') {
element = <PostLanguagesSettingsModal.Component />
} else if (modal.name === 'alt-text-image') {
element = <AltTextImageModal.Component {...modal} />
} else if (modal.name === 'verify-email') {
element = <VerifyEmailModal.Component {...modal} />
} else if (modal.name === 'change-email') {