Improvements to the alt text behaviors in the composer (#910)

* Add an image preview in the alt modal

* Composer: add info about alt text and a green checkmark when done

* Shrink the alt visual indicator a bit so it doesnt obscure the image

* Fix typo

* Fix: avoid requiring multiple tabs to save alt text

* update react-native-screens

* Improve the alt text help tip

* Remove redundant hints

---------

Co-authored-by: Ansh Nanda <anshnanda10@gmail.com>
This commit is contained in:
Paul Frazee 2023-06-27 09:52:49 -05:00 committed by GitHub
parent 25b3e14926
commit bfaa6d73f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 274 additions and 185 deletions

View file

@ -1,16 +1,16 @@
import React, {useCallback} from 'react'
import React from 'react'
import {ImageStyle, Keyboard} from 'react-native'
import {GalleryModel} from 'state/models/media/gallery'
import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {colors} from 'lib/styles'
import {s, colors} from 'lib/styles'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {ImageModel} from 'state/models/media/image'
import {Image} from 'expo-image'
import {Text} from 'view/com/util/text/Text'
import {isDesktopWeb} from 'platform/detection'
import {openAltTextModal} from 'lib/media/alt-text'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
interface Props {
gallery: GalleryModel
@ -18,67 +18,39 @@ interface Props {
export const Gallery = observer(function ({gallery}: Props) {
const store = useStores()
const getImageStyle = useCallback(() => {
let side: number
const pal = usePalette('default')
if (gallery.size === 1) {
side = 250
} else {
side = (isDesktopWeb ? 560 : 350) / gallery.size
}
let side: number
return {
height: side,
width: side,
}
}, [gallery])
if (gallery.size === 1) {
side = 250
} else {
side = (isDesktopWeb ? 560 : 350) / gallery.size
}
const imageStyle = getImageStyle()
const handleAddImageAltText = useCallback(
(image: ImageModel) => {
Keyboard.dismiss()
openAltTextModal(store, image)
},
[store],
)
const handleRemovePhoto = useCallback(
(image: ImageModel) => {
gallery.remove(image)
},
[gallery],
)
const handleEditPhoto = useCallback(
(image: ImageModel) => {
gallery.edit(image)
},
[gallery],
)
const imageStyle = {
height: side,
width: side,
}
const isOverflow = !isDesktopWeb && gallery.size > 2
const imageControlLabelStyle = {
borderRadius: 5,
paddingHorizontal: 10,
position: 'absolute' as const,
zIndex: 1,
...(isOverflow
? {
left: 4,
bottom: 4,
}
: isDesktopWeb && gallery.size < 3
? {
left: 8,
top: 8,
}
: {
left: 4,
top: 4,
}),
}
const altTextControlStyle = isOverflow
? {
left: 4,
bottom: 4,
}
: isDesktopWeb && gallery.size < 3
? {
left: 8,
top: 8,
}
: {
left: 4,
top: 4,
}
const imageControlsSubgroupStyle = {
const imageControlsStyle = {
display: 'flex' as const,
flexDirection: 'row' as const,
position: 'absolute' as const,
@ -103,63 +75,90 @@ export const Gallery = observer(function ({gallery}: Props) {
}
return !gallery.isEmpty ? (
<View testID="selectedPhotosView" style={styles.gallery}>
{gallery.images.map(image => (
<View key={`selected-image-${image.path}`} style={[imageStyle]}>
<TouchableOpacity
testID="altTextButton"
accessibilityRole="button"
accessibilityLabel="Add alt text"
accessibilityHint=""
onPress={() => {
handleAddImageAltText(image)
}}
style={imageControlLabelStyle}>
<Text style={styles.imageControlTextContent}>ALT</Text>
</TouchableOpacity>
<View style={imageControlsSubgroupStyle}>
<>
<View testID="selectedPhotosView" style={styles.gallery}>
{gallery.images.map(image => (
<View key={`selected-image-${image.path}`} style={[imageStyle]}>
<TouchableOpacity
testID="editPhotoButton"
testID="altTextButton"
accessibilityRole="button"
accessibilityLabel="Edit image"
accessibilityLabel="Add alt text"
accessibilityHint=""
onPress={() => {
handleEditPhoto(image)
Keyboard.dismiss()
openAltTextModal(store, image)
}}
style={styles.imageControl}>
<FontAwesomeIcon
icon="pen"
size={12}
style={{color: colors.white}}
/>
style={[styles.altTextControl, altTextControlStyle]}>
<Text style={styles.altTextControlLabel}>ALT</Text>
{image.altText.length > 0 ? (
<FontAwesomeIcon
icon="check"
size={10}
style={{color: colors.green3}}
/>
) : undefined}
</TouchableOpacity>
<View style={imageControlsStyle}>
<TouchableOpacity
testID="editPhotoButton"
accessibilityRole="button"
accessibilityLabel="Edit image"
accessibilityHint=""
onPress={() => gallery.edit(image)}
style={styles.imageControl}>
<FontAwesomeIcon
icon="pen"
size={12}
style={{color: colors.white}}
/>
</TouchableOpacity>
<TouchableOpacity
testID="removePhotoButton"
accessibilityRole="button"
accessibilityLabel="Remove image"
accessibilityHint=""
onPress={() => gallery.remove(image)}
style={styles.imageControl}>
<FontAwesomeIcon
icon="xmark"
size={16}
style={{color: colors.white}}
/>
</TouchableOpacity>
</View>
<TouchableOpacity
testID="removePhotoButton"
accessibilityRole="button"
accessibilityLabel="Remove image"
accessibilityLabel="Add alt text"
accessibilityHint=""
onPress={() => handleRemovePhoto(image)}
style={styles.imageControl}>
<FontAwesomeIcon
icon="xmark"
size={16}
style={{color: colors.white}}
/>
</TouchableOpacity>
</View>
onPress={() => {
Keyboard.dismiss()
openAltTextModal(store, image)
}}
style={styles.altTextHiddenRegion}
/>
<Image
testID="selectedPhotoImage"
style={[styles.image, imageStyle] as ImageStyle}
source={{
uri: image.cropped?.path ?? image.path,
}}
accessible={true}
accessibilityIgnoresInvertColors
/>
<Image
testID="selectedPhotoImage"
style={[styles.image, imageStyle] as ImageStyle}
source={{
uri: image.cropped?.path ?? image.path,
}}
accessible={true}
accessibilityIgnoresInvertColors
/>
</View>
))}
</View>
<View style={[styles.reminder]}>
<View style={[styles.infoIcon, pal.viewLight]}>
<FontAwesomeIcon icon="info" size={12} color={pal.colors.text} />
</View>
))}
</View>
<Text type="sm" style={[pal.textLight, s.flex1]}>
Alt text describes images for blind and low-vision users, and helps
give context to everyone.
</Text>
</View>
</>
) : null
})
@ -179,19 +178,46 @@ const styles = StyleSheet.create({
height: 24,
borderRadius: 12,
backgroundColor: 'rgba(0, 0, 0, 0.75)',
borderWidth: 0.5,
alignItems: 'center',
justifyContent: 'center',
},
imageControlTextContent: {
altTextControl: {
position: 'absolute',
zIndex: 1,
borderRadius: 6,
backgroundColor: 'rgba(0, 0, 0, 0.75)',
paddingHorizontal: 8,
paddingVertical: 3,
flexDirection: 'row',
alignItems: 'center',
},
altTextControlLabel: {
color: 'white',
fontSize: 12,
fontWeight: 'bold',
letterSpacing: 1,
backgroundColor: 'rgba(0, 0, 0, 0.75)',
borderWidth: 0.5,
paddingHorizontal: 10,
paddingVertical: 3,
},
altTextHiddenRegion: {
position: 'absolute',
left: 4,
right: 4,
bottom: 4,
top: 30,
zIndex: 1,
},
reminder: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
borderRadius: 8,
paddingVertical: 14,
},
infoIcon: {
width: 22,
height: 22,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
},
})