From bfaa6d73f37f251259c521befa9e9ee8ea877560 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 27 Jun 2023 09:52:49 -0500 Subject: [PATCH] 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 --- package.json | 2 +- 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 +- yarn.lock | 8 +- 6 files changed, 274 insertions(+), 185 deletions(-) diff --git a/package.json b/package.json index 0fe3e7f6..638bb6c9 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "react-native-reanimated": "^3.3.0", "react-native-root-siblings": "^4.1.1", "react-native-safe-area-context": "^4.4.1", - "react-native-screens": "^3.13.1", + "react-native-screens": "^3.20.0", "react-native-splash-screen": "^3.3.0", "react-native-svg": "13.4.0", "react-native-url-polyfill": "^1.3.0", diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx index f46c0533..c226d25c 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]) + 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 ? ( - - {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 07270d55..e1145a0f 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, }, - title: { - textAlign: 'center', - fontWeight: 'bold', - fontSize: 24, + scrollContainer: { + flex: 1, + height: '100%', + paddingHorizontal: isDesktopWeb ? 0 : 12, + }, + scrollInner: { + gap: 12, + }, + 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 723db289..a7a64b17 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 53ef1731..7f2244b7 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', + }, }) diff --git a/yarn.lock b/yarn.lock index 63a3ca9c..35357c55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15339,10 +15339,10 @@ react-native-safe-area-context@^4.4.1: resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.5.3.tgz#e98eb1a73a6b3846d296545fe74760754dbaaa69" integrity sha512-ihYeGDEBSkYH+1aWnadNhVtclhppVgd/c0tm4mj0+HV11FoiWJ8N6ocnnZnRLvM5Fxc+hUqxR9bm5AXU3rXiyA== -react-native-screens@^3.13.1: - version "3.20.0" - resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.20.0.tgz#4d154177395e5541387d9a05bc2e12e54d2fb5b1" - integrity sha512-joWUKWAVHxymP3mL9gYApFHAsbd9L6ZcmpoZa6Sl3W/82bvvNVMqcfP7MeNqVCg73qZ8yL4fW+J/syusHleUgg== +react-native-screens@^3.20.0: + version "3.22.0" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.22.0.tgz#7d892baf964fddb642b5eec71a09e2aeb501e578" + integrity sha512-csLypBSXIt/egh37YJmokETptZJCtZdoZBsZNLR9n31GesDyVogprT+MM22dEPDuxPxt/mFWq+lSpVwk7khuTw== dependencies: react-freeze "^1.0.0" warn-once "^0.1.0"