1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Refine accessibility

This commit is contained in:
Zhiyuan Zheng
2021-04-09 21:43:12 +02:00
parent 9258f4b934
commit d4b28df091
57 changed files with 661 additions and 142 deletions

View File

@ -55,6 +55,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
({ item }: { item: ComposeStateDraft }) => {
return (
<Pressable
accessibilityHint={t('content.draftsList.content.accessibilityHint')}
style={[styles.draft, { backgroundColor: theme.backgroundDefault }]}
onPress={async () => {
setCheckingAttachments(true)
@ -181,7 +182,10 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
visible={checkingAttachments}
children={
<View
style={[styles.modal, { backgroundColor: theme.backgroundOverlayInvert }]}
style={[
styles.modal,
{ backgroundColor: theme.backgroundOverlayInvert }
]}
children={
<Text
children='检查附件在服务器的状态…'

View File

@ -1,3 +1,4 @@
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext } from 'react'
@ -22,6 +23,7 @@ export interface Props {
const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
const { t } = useTranslation('screenCompose')
const { theme } = useTheme()
const { screenReaderEnabled } = useAccessibility()
const { composeState, composeDispatch } = useContext(ComposeContext)
const theAttachmentRemote = composeState.attachments.uploads[index].remote!
@ -160,9 +162,11 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
</Animated.View>
</PanGestureHandler>
</View>
<Text style={[styles.imageFocusText, { color: theme.primaryDefault }]}>
{t('content.editAttachment.content.imageFocus')}
</Text>
{screenReaderEnabled ? null : (
<Text style={[styles.imageFocusText, { color: theme.primaryDefault }]}>
{t('content.editAttachment.content.imageFocus')}
</Text>
)}
</>
)
}

View File

@ -22,6 +22,9 @@ const ComposeEditAttachmentSubmit: React.FC<Props> = ({ index }) => {
return (
<HeaderRight
accessibilityLabel={t(
'content.editAttachment.header.right.accessibilityLabel'
)}
type='icon'
content='Save'
loading={isSubmitting}
@ -39,8 +42,8 @@ const ComposeEditAttachmentSubmit: React.FC<Props> = ({ index }) => {
) {
formData.append(
'focus',
`${theAttachment.meta.focus.x || 0},${-theAttachment.meta.focus.y ||
0}`
`${theAttachment.meta?.focus?.x || 0},${-theAttachment.meta?.focus
?.y || 0}`
)
}

View File

@ -4,8 +4,20 @@ import { useSearchQuery } from '@utils/queryHooks/search'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { forEach, groupBy, sortBy } from 'lodash'
import React, { useCallback, useContext, useEffect, useMemo } from 'react'
import { FlatList, StyleSheet, View } from 'react-native'
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef
} from 'react'
import {
AccessibilityInfo,
findNodeHandle,
FlatList,
StyleSheet,
View
} from 'react-native'
import { Circle } from 'react-native-animated-spinkit'
import ComposeActions from './Root/Actions'
import ComposePosting from './Posting'
@ -44,6 +56,15 @@ const ComposeRoot = React.memo(
const { reduceMotionEnabled } = useAccessibility()
const { theme } = useTheme()
const accessibleRefDrafts = useRef(null)
const accessibleRefAttachments = useRef(null)
const accessibleRefEmojis = useRef(null)
useEffect(() => {
const tagDrafts = findNodeHandle(accessibleRefDrafts.current)
tagDrafts && AccessibilityInfo.setAccessibilityFocus(tagDrafts)
}, [accessibleRefDrafts.current])
const { composeState, composeDispatch } = useContext(ComposeContext)
const { isFetching, data, refetch } = useSearchQuery({
@ -106,6 +127,16 @@ const ComposeRoot = React.memo(
[composeState]
)
const ListFooter = useCallback(
() => (
<ComposeRootFooter
accessibleRefAttachments={accessibleRefAttachments}
accessibleRefEmojis={accessibleRefEmojis}
/>
),
[]
)
return (
<View style={styles.base}>
<FlatList
@ -113,14 +144,14 @@ const ComposeRoot = React.memo(
ListEmptyComponent={listEmpty}
keyboardShouldPersistTaps='always'
ListHeaderComponent={ComposeRootHeader}
ListFooterComponent={ComposeRootFooter}
ListFooterComponent={ListFooter}
ItemSeparatorComponent={ComponentSeparator}
// @ts-ignore
data={data ? data[composeState.tag?.type] : undefined}
keyExtractor={() => Math.random().toString()}
/>
<ComposeActions />
<ComposeDrafts />
<ComposeDrafts accessibleRefDrafts={accessibleRefDrafts} />
<ComposePosting />
</View>
)

View File

@ -164,20 +164,50 @@ const ComposeActions: React.FC = () => {
return (
<View
accessibilityRole='toolbar'
style={[
styles.additions,
{ backgroundColor: theme.backgroundDefault, borderTopColor: theme.border }
{
backgroundColor: theme.backgroundDefault,
borderTopColor: theme.border
}
]}
>
<Pressable
accessibilityRole='button'
accessibilityLabel={t(
'content.root.actions.attachment.accessibilityLabel'
)}
accessibilityHint={t(
'content.root.actions.attachment.accessibilityHint'
)}
accessibilityState={{
disabled: composeState.poll.active
}}
style={styles.button}
onPress={attachmentOnPress}
children={<Icon name='Aperture' size={24} color={attachmentColor} />}
/>
<Pressable
accessibilityRole='button'
accessibilityLabel={t('content.root.actions.poll.accessibilityLabel')}
accessibilityHint={t('content.root.actions.poll.accessibilityHint')}
accessibilityState={{
disabled: composeState.attachments.uploads.length ? true : false,
expanded: composeState.poll.active
}}
style={styles.button}
onPress={pollOnPress}
children={<Icon name='BarChart2' size={24} color={pollColor} />}
/>
<Pressable
accessibilityRole='button'
accessibilityLabel={t(
'content.root.actions.visibility.accessibilityLabel',
{ visibility: composeState.visibility }
)}
accessibilityState={{ disabled: composeState.visibilityLock }}
style={styles.button}
onPress={visibilityOnPress}
children={
<Icon
@ -190,18 +220,34 @@ const ComposeActions: React.FC = () => {
}
/>
<Pressable
accessibilityRole='button'
accessibilityLabel={t(
'content.root.actions.spoiler.accessibilityLabel'
)}
accessibilityState={{ expanded: composeState.spoiler.active }}
style={styles.button}
onPress={spoilerOnPress}
children={
<Icon
name='AlertTriangle'
size={24}
color={
composeState.spoiler.active ? theme.primaryDefault : theme.secondary
composeState.spoiler.active
? theme.primaryDefault
: theme.secondary
}
/>
}
/>
<Pressable
accessibilityRole='button'
accessibilityLabel={t('content.root.actions.emoji.accessibilityLabel')}
accessibilityHint={t('content.root.actions.emoji.accessibilityHint')}
accessibilityState={{
disabled: composeState.emoji.emojis ? false : true,
expanded: composeState.emoji.active
}}
style={styles.button}
onPress={emojiOnPress}
children={<Icon name='Smile' size={24} color={emojiColor} />}
/>
@ -210,6 +256,12 @@ const ComposeActions: React.FC = () => {
}
const styles = StyleSheet.create({
button: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: '100%'
},
additions: {
height: 45,
borderTopWidth: StyleSheet.hairlineWidth,

View File

@ -3,13 +3,17 @@ import { useNavigation } from '@react-navigation/native'
import { getInstanceDrafts } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
import React, { useContext, useEffect } from 'react'
import React, { RefObject, useContext, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, View } from 'react-native'
import { useSelector } from 'react-redux'
import ComposeContext from '../utils/createContext'
const ComposeDrafts: React.FC = () => {
export interface Props {
accessibleRefDrafts: RefObject<View>
}
const ComposeDrafts: React.FC<Props> = ({ accessibleRefDrafts }) => {
const { t } = useTranslation('screenCompose')
const navigation = useNavigation()
const { composeState } = useContext(ComposeContext)
@ -24,6 +28,7 @@ const ComposeDrafts: React.FC = () => {
if (!composeState.dirty && instanceDrafts?.length) {
return (
<View
ref={accessibleRefDrafts}
style={styles.base}
children={
<Button

View File

@ -3,15 +3,30 @@ import ComposeEmojis from '@screens/Compose/Root/Footer/Emojis'
import ComposePoll from '@screens/Compose/Root/Footer/Poll'
import ComposeReply from '@screens/Compose/Root/Footer/Reply'
import ComposeContext from '@screens/Compose/utils/createContext'
import React, { useContext } from 'react'
import React, { RefObject, useContext } from 'react'
import { SectionList, View } from 'react-native'
const ComposeRootFooter: React.FC = () => {
export interface Props {
accessibleRefAttachments: RefObject<View>
accessibleRefEmojis: RefObject<SectionList>
}
const ComposeRootFooter: React.FC<Props> = ({
accessibleRefAttachments,
accessibleRefEmojis
}) => {
const { composeState } = useContext(ComposeContext)
return (
<>
{composeState.emoji.active ? <ComposeEmojis /> : null}
{composeState.attachments.uploads.length ? <ComposeAttachments /> : null}
{composeState.emoji.active ? (
<ComposeEmojis accessibleRefEmojis={accessibleRefEmojis} />
) : null}
{composeState.attachments.uploads.length ? (
<ComposeAttachments
accessibleRefAttachments={accessibleRefAttachments}
/>
) : null}
{composeState.poll.active ? <ComposePoll /> : null}
{composeState.replyToStatus ? <ComposeReply /> : null}
</>

View File

@ -8,6 +8,7 @@ import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager'
import React, {
RefObject,
useCallback,
useContext,
useEffect,
@ -28,9 +29,13 @@ import ComposeContext from '../../utils/createContext'
import { ExtendedAttachment } from '../../utils/types'
import addAttachment from './addAttachment'
export interface Props {
accessibleRefAttachments: RefObject<View>
}
const DEFAULT_HEIGHT = 200
const ComposeAttachments: React.FC = () => {
const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
const { showActionSheetWithOptions } = useActionSheet()
const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('screenCompose')
@ -153,6 +158,10 @@ const ComposeAttachments: React.FC = () => {
) : (
<View style={styles.actions}>
<Button
accessibilityLabel={t(
'content.root.footer.attachments.remove.accessibilityLabel',
{ attachment: index + 1 }
)}
type='icon'
content='X'
spacing='M'
@ -169,6 +178,10 @@ const ComposeAttachments: React.FC = () => {
}}
/>
<Button
accessibilityLabel={t(
'content.root.footer.attachments.edit.accessibilityLabel',
{ attachment: index + 1 }
)}
type='icon'
content='Edit'
spacing='M'
@ -192,6 +205,10 @@ const ComposeAttachments: React.FC = () => {
const listFooter = useMemo(
() => (
<Pressable
accessible
accessibilityLabel={t(
'content.root.footer.attachments.upload.accessibilityLabel'
)}
style={[
styles.container,
{
@ -233,7 +250,7 @@ const ComposeAttachments: React.FC = () => {
[]
)
return (
<View style={styles.base}>
<View style={styles.base} ref={accessibleRefAttachments} accessible>
<Pressable style={styles.sensitive} onPress={sensitiveOnPress}>
<Icon
name={composeState.attachments.sensitive ? 'CheckCircle' : 'Circle'}

View File

@ -3,14 +3,30 @@ import haptics from '@components/haptics'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useContext, useMemo } from 'react'
import { Pressable, SectionList, StyleSheet, Text, View } from 'react-native'
import React, {
RefObject,
useCallback,
useContext,
useEffect,
useMemo
} from 'react'
import { useTranslation } from 'react-i18next'
import {
AccessibilityInfo,
findNodeHandle,
Pressable,
SectionList,
StyleSheet,
Text,
View
} from 'react-native'
import FastImage from 'react-native-fast-image'
import validUrl from 'valid-url'
import updateText from '../../updateText'
import ComposeContext from '../../utils/createContext'
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
const { t } = useTranslation()
const { reduceMotionEnabled } = useAccessibility()
const { composeState, composeDispatch } = useContext(ComposeContext)
@ -29,6 +45,12 @@ const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
if (validUrl.isHttpsUri(uri)) {
return (
<FastImage
accessibilityLabel={t('common:customEmoji.accessibilityLabel', {
emoji: emoji.shortcode
})}
accessibilityHint={t(
'screenCompose:content.root.footer.emojis.accessibilityHint'
)}
source={{ uri: reduceMotionEnabled ? emoji.static_url : emoji.url }}
style={styles.emoji}
/>
@ -42,10 +64,21 @@ const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
)
}
const ComposeEmojis: React.FC = () => {
export interface Props {
accessibleRefEmojis: RefObject<SectionList>
}
const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
const { composeState } = useContext(ComposeContext)
const { theme } = useTheme()
useEffect(() => {
const tagEmojis = findNodeHandle(accessibleRefEmojis.current)
if (composeState.emoji.active) {
tagEmojis && AccessibilityInfo.setAccessibilityFocus(tagEmojis)
}
}, [composeState.emoji.active])
const listHeader = useCallback(
({ section: { title } }) => (
<Text style={[styles.group, { color: theme.secondary }]}>{title}</Text>
@ -73,6 +106,8 @@ const ComposeEmojis: React.FC = () => {
return (
<View style={styles.base}>
<SectionList
accessible
ref={accessibleRefEmojis}
horizontal
keyboardShouldPersistTaps='always'
sections={composeState.emoji.emojis || []}

View File

@ -48,6 +48,10 @@ const ComposePoll: React.FC = () => {
color={theme.secondary}
/>
<TextInput
accessibilityLabel={t(
'content.root.footer.poll.option.placeholder.accessibilityLabel',
{ index: i + 1 }
)}
keyboardAppearance={mode}
{...(i === 0 && firstRender && { autoFocus: true })}
style={[
@ -80,6 +84,19 @@ const ComposePoll: React.FC = () => {
<View style={styles.controlAmount}>
<View style={styles.firstButton}>
<Button
{...((total > 2)
? {
accessibilityLabel: t(
'content.root.footer.poll.quantity.reduce.accessibilityLabel',
{ amount: total - 1 }
)
}
: {
accessibilityHint: t(
'content.root.footer.poll.quantity.reduce.accessibilityHint',
{ amount: total }
)
})}
onPress={() => {
analytics('compose_poll_reduce_press')
total > 2 &&
@ -95,6 +112,19 @@ const ComposePoll: React.FC = () => {
/>
</View>
<Button
{...(total < 4
? {
accessibilityLabel: t(
'content.root.footer.poll.quantity.increase.accessibilityLabel',
{ amount: total + 1 }
)
}
: {
accessibilityHint: t(
'content.root.footer.poll.quantity.increase.accessibilityHint',
{ amount: total }
)
})}
onPress={() => {
analytics('compose_poll_increase_press')
total < 4 &&

View File

@ -1,6 +1,6 @@
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext } from 'react'
import React, { useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, TextInput } from 'react-native'
import formatText from '../../formatText'
@ -45,7 +45,6 @@ const ComposeSpoilerInput: React.FC = () => {
payload: { selection: { start, end } }
})
}}
ref={composeState.textInputFocus.refs.spoiler}
scrollEnabled={false}
onFocus={() =>
composeDispatch({

View File

@ -37,7 +37,7 @@ const composeInitialState: Omit<ComposeState, 'timestamp'> = {
replyToStatus: undefined,
textInputFocus: {
current: 'text',
refs: { text: createRef(), spoiler: createRef() }
refs: { text: createRef() }
}
}

View File

@ -63,7 +63,7 @@ export type ComposeState = {
replyToStatus?: Mastodon.Status
textInputFocus: {
current: 'text' | 'spoiler'
refs: { text: RefObject<TextInput>; spoiler: RefObject<TextInput> }
refs: { text: RefObject<TextInput> }
}
}