From bfaa6d73f37f251259c521befa9e9ee8ea877560 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 27 Jun 2023 09:52:49 -0500 Subject: 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 --- src/view/com/composer/photos/Gallery.tsx | 244 +++++++++++++++++-------------- src/view/com/modals/AltImage.tsx | 167 +++++++++++++-------- src/view/com/util/images/Gallery.tsx | 19 ++- src/view/com/util/post-embeds/index.tsx | 19 ++- 4 files changed, 269 insertions(+), 180 deletions(-) (limited to 'src') diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx index f46c05333..c226d25cc 100644 --- a/src/view/com/composer/photos/Gallery.tsx +++ b/src/view/com/composer/photos/Gallery.tsx @@ -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]) - - const imageStyle = getImageStyle() - const handleAddImageAltText = useCallback( - (image: ImageModel) => { - Keyboard.dismiss() - openAltTextModal(store, image) - }, - [store], - ) - const handleRemovePhoto = useCallback( - (image: ImageModel) => { - gallery.remove(image) - }, - [gallery], - ) + if (gallery.size === 1) { + side = 250 + } else { + side = (isDesktopWeb ? 560 : 350) / gallery.size + } - 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 ? ( - - {gallery.images.map(image => ( - - { - handleAddImageAltText(image) - }} - style={imageControlLabelStyle}> - ALT - - + <> + + {gallery.images.map(image => ( + { - handleEditPhoto(image) + Keyboard.dismiss() + openAltTextModal(store, image) }} - style={styles.imageControl}> - + style={[styles.altTextControl, altTextControlStyle]}> + ALT + {image.altText.length > 0 ? ( + + ) : undefined} + + gallery.edit(image)} + style={styles.imageControl}> + + + gallery.remove(image)} + style={styles.imageControl}> + + + handleRemovePhoto(image)} - style={styles.imageControl}> - - - + onPress={() => { + Keyboard.dismiss() + openAltTextModal(store, image) + }} + style={styles.altTextHiddenRegion} + /> - + + + ))} + + + + - ))} - + + Alt text describes images for blind and low-vision users, and helps + give context to everyone. + + + ) : 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', }, }) diff --git a/src/view/com/modals/AltImage.tsx b/src/view/com/modals/AltImage.tsx index 07270d557..e1145a0fe 100644 --- a/src/view/com/modals/AltImage.tsx +++ b/src/view/com/modals/AltImage.tsx @@ -1,5 +1,15 @@ -import React, {useCallback, useState} from 'react' -import {StyleSheet, TextInput, TouchableOpacity, View} from 'react-native' +import React, {useMemo, useCallback, useState} from 'react' +import { + ImageStyle, + KeyboardAvoidingView, + ScrollView, + StyleSheet, + TextInput, + TouchableOpacity, + View, + useWindowDimensions, +} from 'react-native' +import {Image} from 'expo-image' import {usePalette} from 'lib/hooks/usePalette' import {gradients, s} from 'lib/styles' import {enforceLen} from 'lib/strings/helpers' @@ -8,7 +18,7 @@ import {useTheme} from 'lib/ThemeContext' import {Text} from '../util/text/Text' import LinearGradient from 'react-native-linear-gradient' import {useStores} from 'state/index' -import {isDesktopWeb} from 'platform/detection' +import {isDesktopWeb, isAndroid} from 'platform/detection' import {ImageModel} from 'state/models/media/image' export const snapPoints = ['fullscreen'] @@ -22,6 +32,24 @@ export function Component({image}: Props) { const store = useStores() const theme = useTheme() const [altText, setAltText] = useState(image.altText) + const windim = useWindowDimensions() + + const imageStyles = useMemo(() => { + const maxWidth = isDesktopWeb ? 450 : windim.width + if (image.height > image.width) { + return { + resizeMode: 'contain', + width: '100%', + aspectRatio: 1, + borderRadius: 8, + } + } + return { + width: '100%', + height: (maxWidth / image.width) * image.height, + borderRadius: 8, + } + }, [image, windim]) const onPressSave = useCallback(() => { image.setAltText(altText) @@ -33,69 +61,94 @@ export function Component({image}: Props) { } return ( - - Add alt text - setAltText(enforceLen(text, MAX_ALT_TEXT))} - accessibilityLabel="Image alt text" - accessibilityHint="Sets image alt text for screenreaders" - accessibilityLabelledBy="imageAltText" - /> - - - - - Save - - - - - - - Cancel - + + + + + + + setAltText(enforceLen(text, MAX_ALT_TEXT))} + accessibilityLabel="Image alt text" + accessibilityHint="" + accessibilityLabelledBy="imageAltText" + autoFocus + /> + + + + + Save + + + + + + + Cancel + + + - - - + + + ) } const styles = StyleSheet.create({ container: { - gap: 18, - paddingVertical: isDesktopWeb ? 0 : 18, - paddingHorizontal: isDesktopWeb ? 0 : 12, + flex: 1, height: '100%', width: '100%', + paddingVertical: isDesktopWeb ? 0 : 18, + }, + scrollContainer: { + flex: 1, + height: '100%', + paddingHorizontal: isDesktopWeb ? 0 : 12, + }, + scrollInner: { + gap: 12, }, - title: { - textAlign: 'center', - fontWeight: 'bold', - fontSize: 24, + imageContainer: { + borderRadius: 8, }, textArea: { borderWidth: 1, diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx index 723db289c..a7a64b171 100644 --- a/src/view/com/util/images/Gallery.tsx +++ b/src/view/com/util/images/Gallery.tsx @@ -45,23 +45,28 @@ export const GalleryItem: FC = ({ accessibilityIgnoresInvertColors /> - {image.alt === '' ? null : ALT} + {image.alt === '' ? null : ( + + ALT + + )} ) } const styles = StyleSheet.create({ - alt: { + altContainer: { backgroundColor: 'rgba(0, 0, 0, 0.75)', borderRadius: 6, - color: 'white', - fontSize: 12, - fontWeight: 'bold', - letterSpacing: 1, - paddingHorizontal: 10, + paddingHorizontal: 6, paddingVertical: 3, position: 'absolute', left: 6, bottom: 6, }, + alt: { + color: 'white', + fontSize: 10, + fontWeight: 'bold', + }, }) diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index 53ef17318..7f2244b7b 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -126,7 +126,11 @@ export function PostEmbeds({ onPress={() => openLightbox(0)} onPressIn={() => onPressIn(0)} style={styles.singleImage}> - {alt === '' ? null : ALT} + {alt === '' ? null : ( + + ALT + + )} ) @@ -201,17 +205,18 @@ const styles = StyleSheet.create({ borderRadius: 8, marginTop: 4, }, - alt: { + altContainer: { backgroundColor: 'rgba(0, 0, 0, 0.75)', borderRadius: 6, - color: 'white', - fontSize: 12, - fontWeight: 'bold', - letterSpacing: 1, - paddingHorizontal: 10, + paddingHorizontal: 6, paddingVertical: 3, position: 'absolute', left: 6, bottom: 6, }, + alt: { + color: 'white', + fontSize: 10, + fontWeight: 'bold', + }, }) -- cgit 1.4.1