mirror of
https://github.com/tooot-app/app
synced 2025-04-15 02:42:04 +02:00
Refine accessibility
This commit is contained in:
parent
9258f4b934
commit
d4b28df091
@ -31,6 +31,7 @@ const ComponentAccount: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessibilityRole='button'
|
||||||
style={[styles.itemDefault, styles.itemAccount]}
|
style={[styles.itemDefault, styles.itemAccount]}
|
||||||
onPress={customOnPress || onPress}
|
onPress={customOnPress || onPress}
|
||||||
>
|
>
|
||||||
|
@ -4,6 +4,7 @@ import layoutAnimation from '@utils/styles/layoutAnimation'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useEffect, useMemo, useRef } from 'react'
|
import React, { useEffect, useMemo, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
|
AccessibilityProps,
|
||||||
Pressable,
|
Pressable,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
@ -14,15 +15,18 @@ import {
|
|||||||
import { Flow } from 'react-native-animated-spinkit'
|
import { Flow } from 'react-native-animated-spinkit'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
accessibilityLabel?: AccessibilityProps['accessibilityLabel']
|
||||||
|
accessibilityHint?: AccessibilityProps['accessibilityHint']
|
||||||
|
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
|
|
||||||
type: 'icon' | 'text'
|
type: 'icon' | 'text'
|
||||||
content: string
|
content: string
|
||||||
|
|
||||||
|
selected?: boolean
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
destructive?: boolean
|
destructive?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
active?: boolean
|
|
||||||
|
|
||||||
strokeWidth?: number
|
strokeWidth?: number
|
||||||
size?: 'S' | 'M' | 'L'
|
size?: 'S' | 'M' | 'L'
|
||||||
@ -34,13 +38,15 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Button: React.FC<Props> = ({
|
const Button: React.FC<Props> = ({
|
||||||
|
accessibilityLabel,
|
||||||
|
accessibilityHint,
|
||||||
style: customStyle,
|
style: customStyle,
|
||||||
type,
|
type,
|
||||||
content,
|
content,
|
||||||
|
selected,
|
||||||
loading = false,
|
loading = false,
|
||||||
destructive = false,
|
destructive = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
active = false,
|
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
size = 'M',
|
size = 'M',
|
||||||
spacing = 'S',
|
spacing = 'S',
|
||||||
@ -57,7 +63,7 @@ const Button: React.FC<Props> = ({
|
|||||||
} else {
|
} else {
|
||||||
mounted.current = true
|
mounted.current = true
|
||||||
}
|
}
|
||||||
}, [content, loading, disabled, active])
|
}, [content, loading, disabled])
|
||||||
|
|
||||||
const loadingSpinkit = useMemo(
|
const loadingSpinkit = useMemo(
|
||||||
() => (
|
() => (
|
||||||
@ -68,40 +74,22 @@ const Button: React.FC<Props> = ({
|
|||||||
[mode]
|
[mode]
|
||||||
)
|
)
|
||||||
|
|
||||||
const colorContent = useMemo(() => {
|
const mainColor = useMemo(() => {
|
||||||
if (active) {
|
if (selected) {
|
||||||
return theme.blue
|
return theme.blue
|
||||||
|
} else if (overlay) {
|
||||||
|
return theme.primaryOverlay
|
||||||
|
} else if (disabled || loading) {
|
||||||
|
return theme.disabled
|
||||||
} else {
|
} else {
|
||||||
if (overlay) {
|
if (destructive) {
|
||||||
return theme.primaryOverlay
|
return theme.red
|
||||||
} else {
|
} else {
|
||||||
if (disabled) {
|
return theme.primaryDefault
|
||||||
return theme.secondary
|
|
||||||
} else {
|
|
||||||
if (destructive) {
|
|
||||||
return theme.red
|
|
||||||
} else {
|
|
||||||
return theme.primaryDefault
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [mode, disabled])
|
}, [mode, disabled, loading, selected])
|
||||||
const colorBorder = useMemo(() => {
|
|
||||||
if (active) {
|
|
||||||
return theme.blue
|
|
||||||
} else {
|
|
||||||
if (disabled || loading) {
|
|
||||||
return theme.secondary
|
|
||||||
} else {
|
|
||||||
if (destructive) {
|
|
||||||
return theme.red
|
|
||||||
} else {
|
|
||||||
return theme.primaryDefault
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [mode, loading, disabled])
|
|
||||||
const colorBackground = useMemo(() => {
|
const colorBackground = useMemo(() => {
|
||||||
if (overlay) {
|
if (overlay) {
|
||||||
return theme.backgroundOverlayInvert
|
return theme.backgroundOverlayInvert
|
||||||
@ -117,7 +105,7 @@ const Button: React.FC<Props> = ({
|
|||||||
<>
|
<>
|
||||||
<Icon
|
<Icon
|
||||||
name={content}
|
name={content}
|
||||||
color={colorContent}
|
color={mainColor}
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
style={{ opacity: loading ? 0 : 1 }}
|
style={{ opacity: loading ? 0 : 1 }}
|
||||||
size={StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1)}
|
size={StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1)}
|
||||||
@ -130,7 +118,7 @@ const Button: React.FC<Props> = ({
|
|||||||
<>
|
<>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: colorContent,
|
color: mainColor,
|
||||||
fontSize:
|
fontSize:
|
||||||
StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1),
|
StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1),
|
||||||
fontWeight: destructive
|
fontWeight: destructive
|
||||||
@ -145,7 +133,7 @@ const Button: React.FC<Props> = ({
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [mode, content, loading, disabled, active])
|
}, [mode, content, loading, disabled])
|
||||||
|
|
||||||
enum spacingMapping {
|
enum spacingMapping {
|
||||||
XS = 'S',
|
XS = 'S',
|
||||||
@ -156,11 +144,20 @@ const Button: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessible
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
accessibilityHint={accessibilityHint}
|
||||||
|
accessibilityRole='button'
|
||||||
|
accessibilityState={{
|
||||||
|
selected,
|
||||||
|
disabled: disabled || selected,
|
||||||
|
busy: loading
|
||||||
|
}}
|
||||||
style={[
|
style={[
|
||||||
styles.button,
|
styles.button,
|
||||||
{
|
{
|
||||||
borderWidth: overlay ? 0 : 1,
|
borderWidth: overlay ? 0 : 1,
|
||||||
borderColor: colorBorder,
|
borderColor: mainColor,
|
||||||
backgroundColor: colorBackground,
|
backgroundColor: colorBackground,
|
||||||
paddingVertical: StyleConstants.Spacing[spacing],
|
paddingVertical: StyleConstants.Spacing[spacing],
|
||||||
paddingHorizontal:
|
paddingHorizontal:
|
||||||
@ -171,7 +168,7 @@ const Button: React.FC<Props> = ({
|
|||||||
testID='base'
|
testID='base'
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
children={children}
|
children={children}
|
||||||
disabled={disabled || active || loading}
|
disabled={selected || disabled || loading}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
|
AccessibilityProps,
|
||||||
Image,
|
Image,
|
||||||
ImageStyle,
|
ImageStyle,
|
||||||
Pressable,
|
Pressable,
|
||||||
@ -18,6 +19,9 @@ import { Blurhash } from 'react-native-blurhash'
|
|||||||
// preview, original, remote -> first show preview, then original, if original failed, then remote
|
// preview, original, remote -> first show preview, then original, if original failed, then remote
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
accessibilityLabel?: AccessibilityProps['accessibilityLabel']
|
||||||
|
accessibilityHint?: AccessibilityProps['accessibilityHint']
|
||||||
|
|
||||||
hidden?: boolean
|
hidden?: boolean
|
||||||
uri: { preview?: string; original?: string; remote?: string }
|
uri: { preview?: string; original?: string; remote?: string }
|
||||||
blurhash?: string
|
blurhash?: string
|
||||||
@ -36,6 +40,8 @@ export interface Props {
|
|||||||
|
|
||||||
const GracefullyImage = React.memo(
|
const GracefullyImage = React.memo(
|
||||||
({
|
({
|
||||||
|
accessibilityLabel,
|
||||||
|
accessibilityHint,
|
||||||
hidden = false,
|
hidden = false,
|
||||||
uri,
|
uri,
|
||||||
blurhash,
|
blurhash,
|
||||||
@ -103,10 +109,7 @@ const GracefullyImage = React.memo(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[styles.blurhash, { backgroundColor: theme.disabled }]}
|
||||||
styles.blurhash,
|
|
||||||
{ backgroundColor: theme.disabled }
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -117,6 +120,11 @@ const GracefullyImage = React.memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
{...(onPress
|
||||||
|
? { accessibilityRole: 'imagebutton' }
|
||||||
|
: { accessibilityRole: 'image' })}
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
accessibilityHint={accessibilityHint}
|
||||||
style={[style, dimension, { backgroundColor: theme.shimmerDefault }]}
|
style={[style, dimension, { backgroundColor: theme.shimmerDefault }]}
|
||||||
{...(onPress
|
{...(onPress
|
||||||
? hidden
|
? hidden
|
||||||
|
@ -28,7 +28,11 @@ const ComponentHashtag: React.FC<Props> = ({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable style={styles.itemDefault} onPress={customOnPress || onPress}>
|
<Pressable
|
||||||
|
accessibilityRole='button'
|
||||||
|
style={styles.itemDefault}
|
||||||
|
onPress={customOnPress || onPress}
|
||||||
|
>
|
||||||
<Text style={[styles.itemHashtag, { color: theme.primaryDefault }]}>
|
<Text style={[styles.itemHashtag, { color: theme.primaryDefault }]}>
|
||||||
#{hashtag.name}
|
#{hashtag.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -2,10 +2,20 @@ import Icon from '@components/Icon'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
import {
|
||||||
|
AccessibilityProps,
|
||||||
|
Pressable,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View
|
||||||
|
} from 'react-native'
|
||||||
import { Flow } from 'react-native-animated-spinkit'
|
import { Flow } from 'react-native-animated-spinkit'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
accessibilityLabel?: string
|
||||||
|
accessibilityHint?: string
|
||||||
|
accessibilityState?: AccessibilityProps['accessibilityState']
|
||||||
|
|
||||||
type?: 'icon' | 'text'
|
type?: 'icon' | 'text'
|
||||||
content: string
|
content: string
|
||||||
native?: boolean
|
native?: boolean
|
||||||
@ -18,6 +28,11 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HeaderRight: React.FC<Props> = ({
|
const HeaderRight: React.FC<Props> = ({
|
||||||
|
// Accessibility - Start
|
||||||
|
accessibilityLabel,
|
||||||
|
accessibilityHint,
|
||||||
|
accessibilityState,
|
||||||
|
// Accessibility - End
|
||||||
type = 'icon',
|
type = 'icon',
|
||||||
content,
|
content,
|
||||||
native = true,
|
native = true,
|
||||||
@ -75,6 +90,10 @@ const HeaderRight: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
accessibilityHint={accessibilityHint}
|
||||||
|
accessibilityRole='button'
|
||||||
|
accessibilityState={accessibilityState}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
children={children}
|
children={children}
|
||||||
disabled={disabled || loading}
|
disabled={disabled || loading}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import React, { createElement } from 'react'
|
import React, { createElement } from 'react'
|
||||||
import { StyleProp, View, ViewStyle } from 'react-native'
|
import { AccessibilityProps, StyleProp, View, ViewStyle } from 'react-native'
|
||||||
import * as FeatherIcon from 'react-native-feather'
|
import * as FeatherIcon from 'react-native-feather'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
accessibilityLabel?: AccessibilityProps['accessibilityLabel']
|
||||||
|
|
||||||
name: string
|
name: string
|
||||||
size: number
|
size: number
|
||||||
color: string
|
color: string
|
||||||
@ -12,6 +14,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Icon: React.FC<Props> = ({
|
const Icon: React.FC<Props> = ({
|
||||||
|
accessibilityLabel,
|
||||||
name,
|
name,
|
||||||
size,
|
size,
|
||||||
color,
|
color,
|
||||||
@ -21,6 +24,7 @@ const Icon: React.FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
style={[
|
style={[
|
||||||
style,
|
style,
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||||
import { useAppsQuery } from '@utils/queryHooks/apps'
|
import { useAppsQuery } from '@utils/queryHooks/apps'
|
||||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||||
import { getInstances } from '@utils/slices/instancesSlice'
|
import { getInstances } from '@utils/slices/instancesSlice'
|
||||||
@ -7,7 +8,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { RefObject, useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
@ -19,6 +20,7 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
View
|
View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { Placeholder } from 'rn-placeholder'
|
import { Placeholder } from 'rn-placeholder'
|
||||||
import analytics from './analytics'
|
import analytics from './analytics'
|
||||||
@ -26,16 +28,19 @@ import InstanceAuth from './Instance/Auth'
|
|||||||
import InstanceInfo from './Instance/Info'
|
import InstanceInfo from './Instance/Info'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
scrollViewRef?: RefObject<ScrollView>
|
||||||
disableHeaderImage?: boolean
|
disableHeaderImage?: boolean
|
||||||
goBack?: boolean
|
goBack?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComponentInstance: React.FC<Props> = ({
|
const ComponentInstance: React.FC<Props> = ({
|
||||||
|
scrollViewRef,
|
||||||
disableHeaderImage,
|
disableHeaderImage,
|
||||||
goBack = false
|
goBack = false
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('componentInstance')
|
const { t } = useTranslation('componentInstance')
|
||||||
const { mode, theme } = useTheme()
|
const { mode, theme } = useTheme()
|
||||||
|
const { screenReaderEnabled } = useAccessibility()
|
||||||
|
|
||||||
const instances = useSelector(getInstances, () => true)
|
const instances = useSelector(getInstances, () => true)
|
||||||
const [domain, setDomain] = useState<string>()
|
const [domain, setDomain] = useState<string>()
|
||||||
@ -139,6 +144,8 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<View style={styles.inputRow}>
|
<View style={styles.inputRow}>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
accessible={false}
|
||||||
|
accessibilityRole='none'
|
||||||
style={[
|
style={[
|
||||||
styles.prefix,
|
styles.prefix,
|
||||||
{
|
{
|
||||||
@ -148,12 +155,8 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
editable={false}
|
editable={false}
|
||||||
children={
|
placeholder='https://'
|
||||||
<Text
|
placeholderTextColor={theme.primaryDefault}
|
||||||
style={{ color: theme.primaryDefault }}
|
|
||||||
children='https://'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[
|
style={[
|
||||||
@ -172,10 +175,14 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
keyboardType='url'
|
keyboardType='url'
|
||||||
textContentType='URL'
|
textContentType='URL'
|
||||||
onSubmitEditing={onSubmitEditing}
|
onSubmitEditing={onSubmitEditing}
|
||||||
placeholder={t('server.textInput.placeholder')}
|
placeholder={' ' + t('server.textInput.placeholder')}
|
||||||
placeholderTextColor={theme.secondary}
|
placeholderTextColor={theme.secondary}
|
||||||
returnKeyType='go'
|
returnKeyType='go'
|
||||||
keyboardAppearance={mode}
|
keyboardAppearance={mode}
|
||||||
|
{...(scrollViewRef && {
|
||||||
|
onFocus: () =>
|
||||||
|
setTimeout(() => scrollViewRef.current?.scrollToEnd(), 150)
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
@ -229,9 +236,21 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
color={theme.secondary}
|
color={theme.secondary}
|
||||||
style={styles.disclaimerIcon}
|
style={styles.disclaimerIcon}
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.disclaimerText, { color: theme.secondary }]}>
|
<Text
|
||||||
|
style={[styles.disclaimerText, { color: theme.secondary }]}
|
||||||
|
accessibilityRole='link'
|
||||||
|
onPress={() => {
|
||||||
|
if (screenReaderEnabled) {
|
||||||
|
analytics('view_privacy')
|
||||||
|
WebBrowser.openBrowserAsync(
|
||||||
|
'https://tooot.app/privacy-policy'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('server.disclaimer.base')}
|
{t('server.disclaimer.base')}
|
||||||
<Text
|
<Text
|
||||||
|
accessible
|
||||||
style={{ color: theme.blue }}
|
style={{ color: theme.blue }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('view_privacy')
|
analytics('view_privacy')
|
||||||
@ -265,8 +284,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
prefix: {
|
prefix: {
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
...StyleConstants.FontStyle.M,
|
...StyleConstants.FontStyle.M
|
||||||
paddingRight: StyleConstants.Spacing.XS
|
|
||||||
},
|
},
|
||||||
textInput: {
|
textInput: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
@ -16,8 +16,10 @@ const InstanceInfo = React.memo(
|
|||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.base, style]}>
|
<View style={[styles.base, style]} accessible>
|
||||||
<Text style={[styles.header, { color: theme.primaryDefault }]}>{header}</Text>
|
<Text style={[styles.header, { color: theme.primaryDefault }]}>
|
||||||
|
{header}
|
||||||
|
</Text>
|
||||||
{content ? (
|
{content ? (
|
||||||
<Text style={[styles.content, { color: theme.primaryDefault }]}>
|
<Text style={[styles.content, { color: theme.primaryDefault }]}>
|
||||||
{content}
|
{content}
|
||||||
|
@ -7,7 +7,11 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MenuContainer: React.FC<Props> = ({ children }) => {
|
const MenuContainer: React.FC<Props> = ({ children }) => {
|
||||||
return <View style={styles.base}>{children}</View>
|
return (
|
||||||
|
<View style={styles.base}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { ColorDefinitions } from '@utils/styles/themes'
|
import { ColorDefinitions } from '@utils/styles/themes'
|
||||||
@ -41,6 +42,7 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
onPress
|
onPress
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const { screenReaderEnabled } = useAccessibility()
|
||||||
|
|
||||||
const loadingSpinkit = useMemo(
|
const loadingSpinkit = useMemo(
|
||||||
() => (
|
() => (
|
||||||
@ -55,11 +57,22 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View
|
||||||
|
style={styles.base}
|
||||||
|
accessible
|
||||||
|
accessibilityRole={switchValue ? 'switch' : 'button'}
|
||||||
|
accessibilityState={switchValue ? { checked: switchValue } : undefined}
|
||||||
|
>
|
||||||
<TapGestureHandler
|
<TapGestureHandler
|
||||||
onHandlerStateChange={({ nativeEvent }) =>
|
onHandlerStateChange={async ({ nativeEvent }) => {
|
||||||
nativeEvent.state === State.ACTIVE && !loading && onPress && onPress()
|
if (nativeEvent.state === State.ACTIVE && !loading) {
|
||||||
}
|
if (screenReaderEnabled && switchOnValueChange) {
|
||||||
|
switchOnValueChange()
|
||||||
|
} else {
|
||||||
|
if (onPress) onPress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<View style={styles.core}>
|
<View style={styles.core}>
|
||||||
<View style={styles.front}>
|
<View style={styles.front}>
|
||||||
|
@ -3,6 +3,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { getTheme } from '@utils/styles/themes'
|
import { getTheme } from '@utils/styles/themes'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { AccessibilityInfo } from 'react-native'
|
||||||
import FlashMessage, {
|
import FlashMessage, {
|
||||||
hideMessage,
|
hideMessage,
|
||||||
showMessage
|
showMessage
|
||||||
@ -36,6 +37,8 @@ const displayMessage = ({
|
|||||||
mode: 'light' | 'dark'
|
mode: 'light' | 'dark'
|
||||||
type: 'success' | 'error' | 'warning'
|
type: 'success' | 'error' | 'warning'
|
||||||
}) => {
|
}) => {
|
||||||
|
AccessibilityInfo.announceForAccessibility(message + '.' + description)
|
||||||
|
|
||||||
enum iconMapping {
|
enum iconMapping {
|
||||||
success = 'CheckCircle',
|
success = 'CheckCircle',
|
||||||
error = 'XCircle',
|
error = 'XCircle',
|
||||||
@ -98,7 +101,10 @@ const Message = React.memo(
|
|||||||
...StyleConstants.FontStyle.M,
|
...StyleConstants.FontStyle.M,
|
||||||
fontWeight: StyleConstants.Font.Weight.Bold
|
fontWeight: StyleConstants.Font.Weight.Bold
|
||||||
}}
|
}}
|
||||||
textStyle={{ color: theme.primaryDefault, ...StyleConstants.FontStyle.S }}
|
textStyle={{
|
||||||
|
color: theme.primaryDefault,
|
||||||
|
...StyleConstants.FontStyle.S
|
||||||
|
}}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
textProps={{ numberOfLines: 2 }}
|
textProps={{ numberOfLines: 2 }}
|
||||||
/>
|
/>
|
||||||
|
@ -4,6 +4,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { adaptiveScale } from '@utils/styles/scaling'
|
import { adaptiveScale } from '@utils/styles/scaling'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, Text } from 'react-native'
|
import { StyleSheet, Text } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
@ -27,6 +28,7 @@ const ParseEmojis = React.memo(
|
|||||||
adaptiveSize = false,
|
adaptiveSize = false,
|
||||||
fontBold = false
|
fontBold = false
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { t } = useTranslation('componentParse')
|
||||||
const { reduceMotionEnabled } = useAccessibility()
|
const { reduceMotionEnabled } = useAccessibility()
|
||||||
|
|
||||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||||
@ -69,10 +71,10 @@ const ParseEmojis = React.memo(
|
|||||||
return emojiShortcode === `:${emoji.shortcode}:`
|
return emojiShortcode === `:${emoji.shortcode}:`
|
||||||
})
|
})
|
||||||
if (emojiIndex === -1) {
|
if (emojiIndex === -1) {
|
||||||
return <Text key={emojiShortcode}>{emojiShortcode}</Text>
|
return <Text key={emojiShortcode + i}>{emojiShortcode}</Text>
|
||||||
} else {
|
} else {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
return <Text key={emojiShortcode}> </Text>
|
return <Text key={emojiShortcode + i}> </Text>
|
||||||
} else {
|
} else {
|
||||||
const uri = reduceMotionEnabled
|
const uri = reduceMotionEnabled
|
||||||
? emojis[emojiIndex].static_url
|
? emojis[emojiIndex].static_url
|
||||||
@ -80,7 +82,7 @@ const ParseEmojis = React.memo(
|
|||||||
if (validUrl.isHttpsUri(uri)) {
|
if (validUrl.isHttpsUri(uri)) {
|
||||||
return (
|
return (
|
||||||
<FastImage
|
<FastImage
|
||||||
key={emojiShortcode}
|
key={emojiShortcode + i}
|
||||||
source={{ uri }}
|
source={{ uri }}
|
||||||
style={styles.image}
|
style={styles.image}
|
||||||
/>
|
/>
|
||||||
@ -91,7 +93,7 @@ const ParseEmojis = React.memo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return <Text key={str}>{str}</Text>
|
return <Text key={i}>{str}</Text>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
|
@ -53,6 +53,7 @@ const renderNode = ({
|
|||||||
: true
|
: true
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
|
accessible
|
||||||
key={index}
|
key={index}
|
||||||
style={{
|
style={{
|
||||||
color: theme.blue,
|
color: theme.blue,
|
||||||
@ -251,6 +252,7 @@ const ParseHTML = React.memo(
|
|||||||
/>
|
/>
|
||||||
{expandAllow ? (
|
{expandAllow ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessibilityLabel=''
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('status_readmore', { allow: expandAllow, expanded })
|
analytics('status_readmore', { allow: expandAllow, expanded })
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
|
@ -62,6 +62,7 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessible={highlighted ? false : true}
|
||||||
style={[
|
style={[
|
||||||
styles.statusView,
|
styles.statusView,
|
||||||
{
|
{
|
||||||
@ -84,6 +85,7 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
<TimelineAvatar
|
<TimelineAvatar
|
||||||
queryKey={disableOnPress ? undefined : queryKey}
|
queryKey={disableOnPress ? undefined : queryKey}
|
||||||
account={actualStatus.account}
|
account={actualStatus.account}
|
||||||
|
highlighted={highlighted}
|
||||||
/>
|
/>
|
||||||
<TimelineHeaderDefault
|
<TimelineHeaderDefault
|
||||||
queryKey={disableOnPress ? undefined : queryKey}
|
queryKey={disableOnPress ? undefined : queryKey}
|
||||||
|
@ -84,7 +84,11 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<TimelineAvatar queryKey={queryKey} account={actualAccount} />
|
<TimelineAvatar
|
||||||
|
queryKey={queryKey}
|
||||||
|
account={actualAccount}
|
||||||
|
highlighted={highlighted}
|
||||||
|
/>
|
||||||
<TimelineHeaderNotification
|
<TimelineHeaderNotification
|
||||||
queryKey={queryKey}
|
queryKey={queryKey}
|
||||||
notification={notification}
|
notification={notification}
|
||||||
|
@ -269,12 +269,28 @@ const TimelineActions: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<View style={styles.actions}>
|
<View style={styles.actions}>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
{...(highlighted
|
||||||
|
? {
|
||||||
|
accessibilityLabel: t(
|
||||||
|
'shared.actions.reply.accessibilityLabel'
|
||||||
|
),
|
||||||
|
accessibilityRole: 'button'
|
||||||
|
}
|
||||||
|
: { accessibilityLabel: '' })}
|
||||||
style={styles.action}
|
style={styles.action}
|
||||||
onPress={onPressReply}
|
onPress={onPressReply}
|
||||||
children={childrenReply}
|
children={childrenReply}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Pressable
|
<Pressable
|
||||||
|
{...(highlighted
|
||||||
|
? {
|
||||||
|
accessibilityLabel: t(
|
||||||
|
'shared.actions.reblogged.accessibilityLabel'
|
||||||
|
),
|
||||||
|
accessibilityRole: 'button'
|
||||||
|
}
|
||||||
|
: { accessibilityLabel: '' })}
|
||||||
style={styles.action}
|
style={styles.action}
|
||||||
onPress={onPressReblog}
|
onPress={onPressReblog}
|
||||||
children={childrenReblog}
|
children={childrenReblog}
|
||||||
@ -284,12 +300,28 @@ const TimelineActions: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Pressable
|
<Pressable
|
||||||
|
{...(highlighted
|
||||||
|
? {
|
||||||
|
accessibilityLabel: t(
|
||||||
|
'shared.actions.favourited.accessibilityLabel'
|
||||||
|
),
|
||||||
|
accessibilityRole: 'button'
|
||||||
|
}
|
||||||
|
: { accessibilityLabel: '' })}
|
||||||
style={styles.action}
|
style={styles.action}
|
||||||
onPress={onPressFavourite}
|
onPress={onPressFavourite}
|
||||||
children={childrenFavourite}
|
children={childrenFavourite}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Pressable
|
<Pressable
|
||||||
|
{...(highlighted
|
||||||
|
? {
|
||||||
|
accessibilityLabel: t(
|
||||||
|
'shared.actions.bookmarked.accessibilityLabel'
|
||||||
|
),
|
||||||
|
accessibilityRole: 'button'
|
||||||
|
}
|
||||||
|
: { accessibilityLabel: '' })}
|
||||||
style={styles.action}
|
style={styles.action}
|
||||||
onPress={onPressBookmark}
|
onPress={onPressBookmark}
|
||||||
children={childrenBookmark}
|
children={childrenBookmark}
|
||||||
|
@ -28,6 +28,16 @@ const TimelineActionsUsers = React.memo(
|
|||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
{status.reblogs_count > 0 ? (
|
{status.reblogs_count > 0 ? (
|
||||||
<Text
|
<Text
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'shared.actionsUsers.reblogged_by.accessibilityLabel',
|
||||||
|
{
|
||||||
|
count: status.reblogs_count
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
accessibilityHint={t(
|
||||||
|
'shared.actionsUsers.reblogged_by.accessibilityHint'
|
||||||
|
)}
|
||||||
|
accessibilityRole='button'
|
||||||
style={[styles.text, { color: theme.secondary }]}
|
style={[styles.text, { color: theme.secondary }]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('timeline_shared_actionsusers_press_boosted', {
|
analytics('timeline_shared_actionsusers_press_boosted', {
|
||||||
@ -41,13 +51,23 @@ const TimelineActionsUsers = React.memo(
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('shared.actionsUsers.reblogged_by', {
|
{t('shared.actionsUsers.reblogged_by.text', {
|
||||||
count: status.reblogs_count
|
count: status.reblogs_count
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
{status.favourites_count > 0 ? (
|
{status.favourites_count > 0 ? (
|
||||||
<Text
|
<Text
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'shared.actionsUsers.favourited_by.accessibilityLabel',
|
||||||
|
{
|
||||||
|
count: status.reblogs_count
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
accessibilityHint={t(
|
||||||
|
'shared.actionsUsers.favourited_by.accessibilityHint'
|
||||||
|
)}
|
||||||
|
accessibilityRole='button'
|
||||||
style={[styles.text, { color: theme.secondary }]}
|
style={[styles.text, { color: theme.secondary }]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('timeline_shared_actionsusers_press_boosted', {
|
analytics('timeline_shared_actionsusers_press_boosted', {
|
||||||
@ -61,7 +81,7 @@ const TimelineActionsUsers = React.memo(
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('shared.actionsUsers.favourited_by', {
|
{t('shared.actionsUsers.favourited_by.text', {
|
||||||
count: status.favourites_count
|
count: status.favourites_count
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -52,6 +52,7 @@ const AttachmentAudio: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
accessibilityLabel={audio.description}
|
||||||
style={[
|
style={[
|
||||||
styles.base,
|
styles.base,
|
||||||
{
|
{
|
||||||
|
@ -23,6 +23,7 @@ const AttachmentImage = React.memo(
|
|||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<GracefullyImage
|
<GracefullyImage
|
||||||
|
accessibilityLabel={image.description}
|
||||||
hidden={sensitiveShown}
|
hidden={sensitiveShown}
|
||||||
uri={{ original: image.preview_url, remote: image.remote_url }}
|
uri={{ original: image.preview_url, remote: image.remote_url }}
|
||||||
blurhash={image.blurhash}
|
blurhash={image.blurhash}
|
||||||
|
@ -47,7 +47,11 @@ const AttachmentUnsupported: React.FC<Props> = ({
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.text,
|
styles.text,
|
||||||
{ color: attachment.blurhash ? theme.backgroundDefault : theme.primaryDefault }
|
{
|
||||||
|
color: attachment.blurhash
|
||||||
|
? theme.backgroundDefault
|
||||||
|
: theme.primaryDefault
|
||||||
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t('shared.attachment.unsupported.text')}
|
{t('shared.attachment.unsupported.text')}
|
||||||
@ -58,9 +62,9 @@ const AttachmentUnsupported: React.FC<Props> = ({
|
|||||||
content={t('shared.attachment.unsupported.button')}
|
content={t('shared.attachment.unsupported.button')}
|
||||||
size='S'
|
size='S'
|
||||||
overlay
|
overlay
|
||||||
onPress={async () => {
|
onPress={() => {
|
||||||
analytics('timeline_shared_attachment_unsupported_press')
|
analytics('timeline_shared_attachment_unsupported_press')
|
||||||
attachment.remote_url && (await openLink(attachment.remote_url))
|
attachment.remote_url && openLink(attachment.remote_url)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -62,6 +62,7 @@ const AttachmentVideo: React.FC<Props> = ({
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Video
|
<Video
|
||||||
|
accessibilityLabel={video.description}
|
||||||
ref={videoPlayer}
|
ref={videoPlayer}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
@ -5,14 +5,17 @@ import { StackNavigationProp } from '@react-navigation/stack'
|
|||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
account: Mastodon.Account
|
account: Mastodon.Account
|
||||||
|
highlighted: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineAvatar = React.memo(
|
const TimelineAvatar = React.memo(
|
||||||
({ queryKey, account }: Props) => {
|
({ queryKey, account, highlighted }: Props) => {
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
const navigation = useNavigation<
|
const navigation = useNavigation<
|
||||||
StackNavigationProp<Nav.TabLocalStackParamList>
|
StackNavigationProp<Nav.TabLocalStackParamList>
|
||||||
>()
|
>()
|
||||||
@ -26,6 +29,14 @@ const TimelineAvatar = React.memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GracefullyImage
|
<GracefullyImage
|
||||||
|
{...(highlighted && {
|
||||||
|
accessibilityLabel: t('shared.avatar.accessibilityLabel', {
|
||||||
|
name: account.display_name
|
||||||
|
}),
|
||||||
|
accessibilityHint: t('shared.avatar.accessibilityHint', {
|
||||||
|
name: account.display_name
|
||||||
|
})
|
||||||
|
})}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
uri={{ original: account.avatar_static }}
|
uri={{ original: account.avatar_static }}
|
||||||
dimension={{
|
dimension={{
|
||||||
@ -35,8 +46,7 @@ const TimelineAvatar = React.memo(
|
|||||||
style={{
|
style={{
|
||||||
borderRadius: StyleConstants.Avatar.M,
|
borderRadius: StyleConstants.Avatar.M,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginRight: StyleConstants.Spacing.S,
|
marginRight: StyleConstants.Spacing.S
|
||||||
backgroundColor: 'red'
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -18,6 +18,8 @@ const TimelineCard = React.memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessible
|
||||||
|
accessibilityRole='link'
|
||||||
style={[styles.card, { borderColor: theme.border }]}
|
style={[styles.card, { borderColor: theme.border }]}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
analytics('timeline_shared_card_press')
|
analytics('timeline_shared_card_press')
|
||||||
|
@ -4,6 +4,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
import HeaderSharedAccount from './HeaderShared/Account'
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
import HeaderSharedApplication from './HeaderShared/Application'
|
import HeaderSharedApplication from './HeaderShared/Application'
|
||||||
@ -19,6 +20,7 @@ export interface Props {
|
|||||||
|
|
||||||
const TimelineHeaderDefault = React.memo(
|
const TimelineHeaderDefault = React.memo(
|
||||||
({ queryKey, rootQueryKey, status }: Props) => {
|
({ queryKey, rootQueryKey, status }: Props) => {
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
@ -36,6 +38,7 @@ const TimelineHeaderDefault = React.memo(
|
|||||||
|
|
||||||
{queryKey ? (
|
{queryKey ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessibilityHint={t('shared.header.actions.accessibilityHint')}
|
||||||
style={styles.action}
|
style={styles.action}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
navigation.navigate('Screen-Actions', {
|
navigation.navigate('Screen-Actions', {
|
||||||
|
@ -2,6 +2,7 @@ import { ParseEmojis } from '@root/components/Parse'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -11,12 +12,19 @@ export interface Props {
|
|||||||
|
|
||||||
const HeaderSharedAccount = React.memo(
|
const HeaderSharedAccount = React.memo(
|
||||||
({ account, withoutName = false }: Props) => {
|
({ account, withoutName = false }: Props) => {
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
{withoutName ? null : (
|
{withoutName ? null : (
|
||||||
<Text style={styles.name} numberOfLines={1}>
|
<Text
|
||||||
|
accessibilityHint={t(
|
||||||
|
'shared.header.shared.account.name.accessibilityHint'
|
||||||
|
)}
|
||||||
|
style={styles.name}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
<ParseEmojis
|
<ParseEmojis
|
||||||
content={account.display_name || account.username}
|
content={account.display_name || account.username}
|
||||||
emojis={account.emojis}
|
emojis={account.emojis}
|
||||||
@ -25,6 +33,9 @@ const HeaderSharedAccount = React.memo(
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Text
|
<Text
|
||||||
|
accessibilityHint={t(
|
||||||
|
'shared.header.shared.account.account.accessibilityHint'
|
||||||
|
)}
|
||||||
style={[styles.acct, { color: theme.secondary }]}
|
style={[styles.acct, { color: theme.secondary }]}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
|
@ -17,6 +17,7 @@ const HeaderSharedApplication = React.memo(
|
|||||||
|
|
||||||
return application && application.name !== 'Web' ? (
|
return application && application.name !== 'Web' ? (
|
||||||
<Text
|
<Text
|
||||||
|
accessibilityRole='link'
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
analytics('timeline_shared_header_application_press', {
|
analytics('timeline_shared_header_application_press', {
|
||||||
application
|
application
|
||||||
|
@ -2,6 +2,7 @@ import Icon from '@components/Icon'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet } from 'react-native'
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -10,10 +11,12 @@ export interface Props {
|
|||||||
|
|
||||||
const HeaderSharedMuted = React.memo(
|
const HeaderSharedMuted = React.memo(
|
||||||
({ muted }: Props) => {
|
({ muted }: Props) => {
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return muted ? (
|
return muted ? (
|
||||||
<Icon
|
<Icon
|
||||||
|
accessibilityLabel={t('shared.header.shared.muted.accessibilityLabel')}
|
||||||
name='VolumeX'
|
name='VolumeX'
|
||||||
size={StyleConstants.Font.Size.S}
|
size={StyleConstants.Font.Size.S}
|
||||||
color={theme.secondary}
|
color={theme.secondary}
|
||||||
|
@ -2,6 +2,7 @@ import Icon from '@components/Icon'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet } from 'react-native'
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -10,12 +11,16 @@ export interface Props {
|
|||||||
|
|
||||||
const HeaderSharedVisibility = React.memo(
|
const HeaderSharedVisibility = React.memo(
|
||||||
({ visibility }: Props) => {
|
({ visibility }: Props) => {
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
switch (visibility) {
|
switch (visibility) {
|
||||||
case 'private':
|
case 'private':
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'shared.header.shared.visibility.private.accessibilityLabel'
|
||||||
|
)}
|
||||||
name='Lock'
|
name='Lock'
|
||||||
size={StyleConstants.Font.Size.S}
|
size={StyleConstants.Font.Size.S}
|
||||||
color={theme.secondary}
|
color={theme.secondary}
|
||||||
@ -25,6 +30,9 @@ const HeaderSharedVisibility = React.memo(
|
|||||||
case 'direct':
|
case 'direct':
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'shared.header.shared.visibility.direct.accessibilityLabel'
|
||||||
|
)}
|
||||||
name='Mail'
|
name='Mail'
|
||||||
size={StyleConstants.Font.Size.S}
|
size={StyleConstants.Font.Size.S}
|
||||||
color={theme.secondary}
|
color={theme.secondary}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
"cancel": "Cancel"
|
"cancel": "Cancel"
|
||||||
},
|
},
|
||||||
|
"customEmoji": {
|
||||||
|
"accessibilityLabel": "Custom emoji {{emoji}}"
|
||||||
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": "{{function}} succeed"
|
"message": "{{function}} succeed"
|
||||||
|
@ -29,19 +29,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"favourited": {
|
"reply": {
|
||||||
"function": "Favourite toot"
|
"accessibilityLabel": "Reply to this toot"
|
||||||
},
|
},
|
||||||
"reblogged": {
|
"reblogged": {
|
||||||
|
"accessibilityLabel": "Boost this toot",
|
||||||
"function": "Boost toot"
|
"function": "Boost toot"
|
||||||
},
|
},
|
||||||
|
"favourited": {
|
||||||
|
"accessibilityLabel": "Add this toot to favourites",
|
||||||
|
"function": "Favourite toot"
|
||||||
|
},
|
||||||
"bookmarked": {
|
"bookmarked": {
|
||||||
|
"accessibilityLabel": "Add this toot to bookmarks",
|
||||||
"function": "Bookmark toot"
|
"function": "Bookmark toot"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actionsUsers": {
|
"actionsUsers": {
|
||||||
"reblogged_by": "$t(screenTabs:shared.users.statuses.reblogged_by)",
|
"reblogged_by": {
|
||||||
"favourited_by": "$t(screenTabs:shared.users.statuses.favourited_by)"
|
"accessibilityLabel": "{{count}} users have boosted this toot",
|
||||||
|
"accessibilityHint": "Tap to know the users",
|
||||||
|
"text": "$t(screenTabs:shared.users.statuses.reblogged_by)"
|
||||||
|
},
|
||||||
|
"favourited_by": {
|
||||||
|
"accessibilityLabel": "{{count}} users have favourited this toot",
|
||||||
|
"accessibilityHint": "Tap to know the users",
|
||||||
|
"text": "$t(screenTabs:shared.users.statuses.favourited_by)"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"attachment": {
|
"attachment": {
|
||||||
"sensitive": {
|
"sensitive": {
|
||||||
@ -52,13 +66,36 @@
|
|||||||
"button": "Try remote link"
|
"button": "Try remote link"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"avatar": {
|
||||||
|
"accessibilityLabel": "Avatar of {{name}}",
|
||||||
|
"accessibilityHint": "Tap to go to {{name}}'s page"
|
||||||
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"expandHint": "hidden content"
|
"expandHint": "hidden content"
|
||||||
},
|
},
|
||||||
"fullConversation": "Read conversations",
|
"fullConversation": "Read conversations",
|
||||||
"header": {
|
"header": {
|
||||||
"shared": {
|
"shared": {
|
||||||
"application": "Tooted with {{application}}"
|
"account": {
|
||||||
|
"name": {
|
||||||
|
"accessibilityHint": "User's display name"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"accessibilityHint": "User's account"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application": "Tooted with {{application}}",
|
||||||
|
"muted": {
|
||||||
|
"accessibilityLabel": "Toot muted"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"direct": {
|
||||||
|
"accessibilityLabel": "Toot is a direct message"
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"accessibilityLabel": "Toot is visible to followers only"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"conversation": {
|
"conversation": {
|
||||||
"withAccounts": "With",
|
"withAccounts": "With",
|
||||||
@ -67,6 +104,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"accessibilityHint": "Actions for this toot, such as its posted user, toot itself",
|
||||||
"account": {
|
"account": {
|
||||||
"heading": "About user",
|
"heading": "About user",
|
||||||
"mute": {
|
"mute": {
|
||||||
|
@ -45,15 +45,38 @@
|
|||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"attachments": {
|
"attachments": {
|
||||||
"sensitive": "Mark attachments as sensitive"
|
"sensitive": "Mark attachments as sensitive",
|
||||||
|
"remove": {
|
||||||
|
"accessibilityLabel": "Remove uploaded attachment, number {{attachment}}"
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"accessibilityLabel": "Edit uploaded attachment, number {{attachment}}"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"accessibilityLabel": "Upload more attachments"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"emojis": {
|
||||||
|
"accessibilityHint": "Tap to add emoji to toot"
|
||||||
},
|
},
|
||||||
"poll": {
|
"poll": {
|
||||||
"option": {
|
"option": {
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
|
"accessibilityLabel": "Field number {{index}}",
|
||||||
"single": "Single choice",
|
"single": "Single choice",
|
||||||
"multiple": "Multiple choice"
|
"multiple": "Multiple choice"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"quantity": {
|
||||||
|
"reduce": {
|
||||||
|
"accessibilityLabel": "Reduce poll options to {{amount}}",
|
||||||
|
"accessibilityHint": "Minimum poll options quantity reached, currently has {{amount}}"
|
||||||
|
},
|
||||||
|
"increase": {
|
||||||
|
"accessibilityLabel": "Increase poll options to {{amount}}",
|
||||||
|
"accessibilityHint": "Maximum poll options quantity reached, currently has {{amount}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"multiple": {
|
"multiple": {
|
||||||
"heading": "Choice type",
|
"heading": "Choice type",
|
||||||
"options": {
|
"options": {
|
||||||
@ -79,6 +102,8 @@
|
|||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"attachment": {
|
"attachment": {
|
||||||
|
"accessibilityLabel": "Upload attachment",
|
||||||
|
"accessibilityHint": "Poll function will be disabled when there is any attachment",
|
||||||
"actions": {
|
"actions": {
|
||||||
"options": {
|
"options": {
|
||||||
"library": "Upload from photo library",
|
"library": "Upload from photo library",
|
||||||
@ -113,7 +138,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"poll": {
|
||||||
|
"accessibilityLabel": "Add poll",
|
||||||
|
"accessibilityHint": "Attachment function will be disabled when poll is active"
|
||||||
|
},
|
||||||
"visibility": {
|
"visibility": {
|
||||||
|
"accessibilityLabel": "Toot visibility is {{visibility}}",
|
||||||
"title": "Toot visibility",
|
"title": "Toot visibility",
|
||||||
"options": {
|
"options": {
|
||||||
"public": "Public",
|
"public": "Public",
|
||||||
@ -122,6 +152,13 @@
|
|||||||
"direct": "Direct message",
|
"direct": "Direct message",
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"spoiler": {
|
||||||
|
"accessibilityLabel": "Spoiler"
|
||||||
|
},
|
||||||
|
"emoji": {
|
||||||
|
"accessibilityLabel": "Add emoji",
|
||||||
|
"accessibilityHint": "Open emoji selection panel, swipe horizontally to change page"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"drafts": "Draft ({{count}})",
|
"drafts": "Draft ({{count}})",
|
||||||
@ -131,6 +168,7 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"title": "Edit attachment",
|
"title": "Edit attachment",
|
||||||
"right": {
|
"right": {
|
||||||
|
"accessibilityLabel": "Save editing attachment",
|
||||||
"failed": {
|
"failed": {
|
||||||
"title": "Editing failed",
|
"title": "Editing failed",
|
||||||
"button": "Try again"
|
"button": "Try again"
|
||||||
@ -150,6 +188,7 @@
|
|||||||
"title": "Draft"
|
"title": "Draft"
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
|
"accessibilityHint": "Saved draft, tap to edit this draft",
|
||||||
"textEmpty": "Content empty"
|
"textEmpty": "Content empty"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
|
"actions": {
|
||||||
|
"accessibilityLabel": "More actions of this image",
|
||||||
|
"accessibilityHint": "You can save or share this image"
|
||||||
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"save": "Save image",
|
"save": "Save image",
|
||||||
"share": "Share iamge",
|
"share": "Share image",
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
},
|
},
|
||||||
"save": {
|
"save": {
|
||||||
|
@ -17,6 +17,18 @@
|
|||||||
"name": "About me"
|
"name": "About me"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"common": {
|
||||||
|
"search": {
|
||||||
|
"accessibilityLabel": "Search",
|
||||||
|
"accessibilityHint": "Search for hashtags, users or toots"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"filter": {
|
||||||
|
"accessibilityLabel": "Filter",
|
||||||
|
"accessibilityHint": "Filter shown notifications' types"
|
||||||
|
}
|
||||||
|
},
|
||||||
"me": {
|
"me": {
|
||||||
"stacks": {
|
"stacks": {
|
||||||
"bookmarks": {
|
"bookmarks": {
|
||||||
@ -175,6 +187,10 @@
|
|||||||
},
|
},
|
||||||
"shared": {
|
"shared": {
|
||||||
"account": {
|
"account": {
|
||||||
|
"actions": {
|
||||||
|
"accessibilityLabel": "Actions for user {{user}}",
|
||||||
|
"accessibilityHint": "You can mute, blokc, report or share this user"
|
||||||
|
},
|
||||||
"moved": "User moved",
|
"moved": "User moved",
|
||||||
"created_at": "Registered on: {{date}}",
|
"created_at": "Registered on: {{date}}",
|
||||||
"summary": {
|
"summary": {
|
||||||
|
@ -224,7 +224,7 @@ const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
|
|||||||
onPress={() => navigation.goBack()}
|
onPress={() => navigation.goBack()}
|
||||||
/>
|
/>
|
||||||
<HeaderCenter content={t('screenAnnouncements:heading')} />
|
<HeaderCenter content={t('screenAnnouncements:heading')} />
|
||||||
<View style={{ opacity: 0 }}>
|
<View style={{ opacity: 0 }} accessible={false}>
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
content='MoreHorizontal'
|
content='MoreHorizontal'
|
||||||
native={false}
|
native={false}
|
||||||
|
@ -55,6 +55,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||||||
({ item }: { item: ComposeStateDraft }) => {
|
({ item }: { item: ComposeStateDraft }) => {
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessibilityHint={t('content.draftsList.content.accessibilityHint')}
|
||||||
style={[styles.draft, { backgroundColor: theme.backgroundDefault }]}
|
style={[styles.draft, { backgroundColor: theme.backgroundDefault }]}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
setCheckingAttachments(true)
|
setCheckingAttachments(true)
|
||||||
@ -181,7 +182,10 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
|||||||
visible={checkingAttachments}
|
visible={checkingAttachments}
|
||||||
children={
|
children={
|
||||||
<View
|
<View
|
||||||
style={[styles.modal, { backgroundColor: theme.backgroundOverlayInvert }]}
|
style={[
|
||||||
|
styles.modal,
|
||||||
|
{ backgroundColor: theme.backgroundOverlayInvert }
|
||||||
|
]}
|
||||||
children={
|
children={
|
||||||
<Text
|
<Text
|
||||||
children='检查附件在服务器的状态…'
|
children='检查附件在服务器的状态…'
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
@ -22,6 +23,7 @@ export interface Props {
|
|||||||
const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
|
const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
|
||||||
const { t } = useTranslation('screenCompose')
|
const { t } = useTranslation('screenCompose')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const { screenReaderEnabled } = useAccessibility()
|
||||||
|
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
const theAttachmentRemote = composeState.attachments.uploads[index].remote!
|
const theAttachmentRemote = composeState.attachments.uploads[index].remote!
|
||||||
@ -160,9 +162,11 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
|
|||||||
</Animated.View>
|
</Animated.View>
|
||||||
</PanGestureHandler>
|
</PanGestureHandler>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.imageFocusText, { color: theme.primaryDefault }]}>
|
{screenReaderEnabled ? null : (
|
||||||
{t('content.editAttachment.content.imageFocus')}
|
<Text style={[styles.imageFocusText, { color: theme.primaryDefault }]}>
|
||||||
</Text>
|
{t('content.editAttachment.content.imageFocus')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,9 @@ const ComposeEditAttachmentSubmit: React.FC<Props> = ({ index }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'content.editAttachment.header.right.accessibilityLabel'
|
||||||
|
)}
|
||||||
type='icon'
|
type='icon'
|
||||||
content='Save'
|
content='Save'
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
@ -39,8 +42,8 @@ const ComposeEditAttachmentSubmit: React.FC<Props> = ({ index }) => {
|
|||||||
) {
|
) {
|
||||||
formData.append(
|
formData.append(
|
||||||
'focus',
|
'focus',
|
||||||
`${theAttachment.meta.focus.x || 0},${-theAttachment.meta.focus.y ||
|
`${theAttachment.meta?.focus?.x || 0},${-theAttachment.meta?.focus
|
||||||
0}`
|
?.y || 0}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,20 @@ import { useSearchQuery } from '@utils/queryHooks/search'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { forEach, groupBy, sortBy } from 'lodash'
|
import { forEach, groupBy, sortBy } from 'lodash'
|
||||||
import React, { useCallback, useContext, useEffect, useMemo } from 'react'
|
import React, {
|
||||||
import { FlatList, StyleSheet, View } from 'react-native'
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef
|
||||||
|
} from 'react'
|
||||||
|
import {
|
||||||
|
AccessibilityInfo,
|
||||||
|
findNodeHandle,
|
||||||
|
FlatList,
|
||||||
|
StyleSheet,
|
||||||
|
View
|
||||||
|
} from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
import { Circle } from 'react-native-animated-spinkit'
|
||||||
import ComposeActions from './Root/Actions'
|
import ComposeActions from './Root/Actions'
|
||||||
import ComposePosting from './Posting'
|
import ComposePosting from './Posting'
|
||||||
@ -44,6 +56,15 @@ const ComposeRoot = React.memo(
|
|||||||
const { reduceMotionEnabled } = useAccessibility()
|
const { reduceMotionEnabled } = useAccessibility()
|
||||||
const { theme } = useTheme()
|
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 { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
|
|
||||||
const { isFetching, data, refetch } = useSearchQuery({
|
const { isFetching, data, refetch } = useSearchQuery({
|
||||||
@ -106,6 +127,16 @@ const ComposeRoot = React.memo(
|
|||||||
[composeState]
|
[composeState]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ListFooter = useCallback(
|
||||||
|
() => (
|
||||||
|
<ComposeRootFooter
|
||||||
|
accessibleRefAttachments={accessibleRefAttachments}
|
||||||
|
accessibleRefEmojis={accessibleRefEmojis}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<FlatList
|
<FlatList
|
||||||
@ -113,14 +144,14 @@ const ComposeRoot = React.memo(
|
|||||||
ListEmptyComponent={listEmpty}
|
ListEmptyComponent={listEmpty}
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
ListHeaderComponent={ComposeRootHeader}
|
ListHeaderComponent={ComposeRootHeader}
|
||||||
ListFooterComponent={ComposeRootFooter}
|
ListFooterComponent={ListFooter}
|
||||||
ItemSeparatorComponent={ComponentSeparator}
|
ItemSeparatorComponent={ComponentSeparator}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
data={data ? data[composeState.tag?.type] : undefined}
|
data={data ? data[composeState.tag?.type] : undefined}
|
||||||
keyExtractor={() => Math.random().toString()}
|
keyExtractor={() => Math.random().toString()}
|
||||||
/>
|
/>
|
||||||
<ComposeActions />
|
<ComposeActions />
|
||||||
<ComposeDrafts />
|
<ComposeDrafts accessibleRefDrafts={accessibleRefDrafts} />
|
||||||
<ComposePosting />
|
<ComposePosting />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -164,20 +164,50 @@ const ComposeActions: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
accessibilityRole='toolbar'
|
||||||
style={[
|
style={[
|
||||||
styles.additions,
|
styles.additions,
|
||||||
{ backgroundColor: theme.backgroundDefault, borderTopColor: theme.border }
|
{
|
||||||
|
backgroundColor: theme.backgroundDefault,
|
||||||
|
borderTopColor: theme.border
|
||||||
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Pressable
|
<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}
|
onPress={attachmentOnPress}
|
||||||
children={<Icon name='Aperture' size={24} color={attachmentColor} />}
|
children={<Icon name='Aperture' size={24} color={attachmentColor} />}
|
||||||
/>
|
/>
|
||||||
<Pressable
|
<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}
|
onPress={pollOnPress}
|
||||||
children={<Icon name='BarChart2' size={24} color={pollColor} />}
|
children={<Icon name='BarChart2' size={24} color={pollColor} />}
|
||||||
/>
|
/>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessibilityRole='button'
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'content.root.actions.visibility.accessibilityLabel',
|
||||||
|
{ visibility: composeState.visibility }
|
||||||
|
)}
|
||||||
|
accessibilityState={{ disabled: composeState.visibilityLock }}
|
||||||
|
style={styles.button}
|
||||||
onPress={visibilityOnPress}
|
onPress={visibilityOnPress}
|
||||||
children={
|
children={
|
||||||
<Icon
|
<Icon
|
||||||
@ -190,18 +220,34 @@ const ComposeActions: React.FC = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessibilityRole='button'
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'content.root.actions.spoiler.accessibilityLabel'
|
||||||
|
)}
|
||||||
|
accessibilityState={{ expanded: composeState.spoiler.active }}
|
||||||
|
style={styles.button}
|
||||||
onPress={spoilerOnPress}
|
onPress={spoilerOnPress}
|
||||||
children={
|
children={
|
||||||
<Icon
|
<Icon
|
||||||
name='AlertTriangle'
|
name='AlertTriangle'
|
||||||
size={24}
|
size={24}
|
||||||
color={
|
color={
|
||||||
composeState.spoiler.active ? theme.primaryDefault : theme.secondary
|
composeState.spoiler.active
|
||||||
|
? theme.primaryDefault
|
||||||
|
: theme.secondary
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Pressable
|
<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}
|
onPress={emojiOnPress}
|
||||||
children={<Icon name='Smile' size={24} color={emojiColor} />}
|
children={<Icon name='Smile' size={24} color={emojiColor} />}
|
||||||
/>
|
/>
|
||||||
@ -210,6 +256,12 @@ const ComposeActions: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
button: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
additions: {
|
additions: {
|
||||||
height: 45,
|
height: 45,
|
||||||
borderTopWidth: StyleSheet.hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
@ -3,13 +3,17 @@ import { useNavigation } from '@react-navigation/native'
|
|||||||
import { getInstanceDrafts } from '@utils/slices/instancesSlice'
|
import { getInstanceDrafts } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
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 { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import ComposeContext from '../utils/createContext'
|
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 { t } = useTranslation('screenCompose')
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const { composeState } = useContext(ComposeContext)
|
const { composeState } = useContext(ComposeContext)
|
||||||
@ -24,6 +28,7 @@ const ComposeDrafts: React.FC = () => {
|
|||||||
if (!composeState.dirty && instanceDrafts?.length) {
|
if (!composeState.dirty && instanceDrafts?.length) {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
ref={accessibleRefDrafts}
|
||||||
style={styles.base}
|
style={styles.base}
|
||||||
children={
|
children={
|
||||||
<Button
|
<Button
|
||||||
|
@ -3,15 +3,30 @@ import ComposeEmojis from '@screens/Compose/Root/Footer/Emojis'
|
|||||||
import ComposePoll from '@screens/Compose/Root/Footer/Poll'
|
import ComposePoll from '@screens/Compose/Root/Footer/Poll'
|
||||||
import ComposeReply from '@screens/Compose/Root/Footer/Reply'
|
import ComposeReply from '@screens/Compose/Root/Footer/Reply'
|
||||||
import ComposeContext from '@screens/Compose/utils/createContext'
|
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)
|
const { composeState } = useContext(ComposeContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{composeState.emoji.active ? <ComposeEmojis /> : null}
|
{composeState.emoji.active ? (
|
||||||
{composeState.attachments.uploads.length ? <ComposeAttachments /> : null}
|
<ComposeEmojis accessibleRefEmojis={accessibleRefEmojis} />
|
||||||
|
) : null}
|
||||||
|
{composeState.attachments.uploads.length ? (
|
||||||
|
<ComposeAttachments
|
||||||
|
accessibleRefAttachments={accessibleRefAttachments}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{composeState.poll.active ? <ComposePoll /> : null}
|
{composeState.poll.active ? <ComposePoll /> : null}
|
||||||
{composeState.replyToStatus ? <ComposeReply /> : null}
|
{composeState.replyToStatus ? <ComposeReply /> : null}
|
||||||
</>
|
</>
|
||||||
|
@ -8,6 +8,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, {
|
import React, {
|
||||||
|
RefObject,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -28,9 +29,13 @@ import ComposeContext from '../../utils/createContext'
|
|||||||
import { ExtendedAttachment } from '../../utils/types'
|
import { ExtendedAttachment } from '../../utils/types'
|
||||||
import addAttachment from './addAttachment'
|
import addAttachment from './addAttachment'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
accessibleRefAttachments: RefObject<View>
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_HEIGHT = 200
|
const DEFAULT_HEIGHT = 200
|
||||||
|
|
||||||
const ComposeAttachments: React.FC = () => {
|
const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||||
const { showActionSheetWithOptions } = useActionSheet()
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
const { t } = useTranslation('screenCompose')
|
const { t } = useTranslation('screenCompose')
|
||||||
@ -153,6 +158,10 @@ const ComposeAttachments: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
<View style={styles.actions}>
|
<View style={styles.actions}>
|
||||||
<Button
|
<Button
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'content.root.footer.attachments.remove.accessibilityLabel',
|
||||||
|
{ attachment: index + 1 }
|
||||||
|
)}
|
||||||
type='icon'
|
type='icon'
|
||||||
content='X'
|
content='X'
|
||||||
spacing='M'
|
spacing='M'
|
||||||
@ -169,6 +178,10 @@ const ComposeAttachments: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'content.root.footer.attachments.edit.accessibilityLabel',
|
||||||
|
{ attachment: index + 1 }
|
||||||
|
)}
|
||||||
type='icon'
|
type='icon'
|
||||||
content='Edit'
|
content='Edit'
|
||||||
spacing='M'
|
spacing='M'
|
||||||
@ -192,6 +205,10 @@ const ComposeAttachments: React.FC = () => {
|
|||||||
const listFooter = useMemo(
|
const listFooter = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
accessible
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'content.root.footer.attachments.upload.accessibilityLabel'
|
||||||
|
)}
|
||||||
style={[
|
style={[
|
||||||
styles.container,
|
styles.container,
|
||||||
{
|
{
|
||||||
@ -233,7 +250,7 @@ const ComposeAttachments: React.FC = () => {
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base} ref={accessibleRefAttachments} accessible>
|
||||||
<Pressable style={styles.sensitive} onPress={sensitiveOnPress}>
|
<Pressable style={styles.sensitive} onPress={sensitiveOnPress}>
|
||||||
<Icon
|
<Icon
|
||||||
name={composeState.attachments.sensitive ? 'CheckCircle' : 'Circle'}
|
name={composeState.attachments.sensitive ? 'CheckCircle' : 'Circle'}
|
||||||
|
@ -3,14 +3,30 @@ import haptics from '@components/haptics'
|
|||||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useContext, useMemo } from 'react'
|
import React, {
|
||||||
import { Pressable, SectionList, StyleSheet, Text, View } from 'react-native'
|
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 FastImage from 'react-native-fast-image'
|
||||||
import validUrl from 'valid-url'
|
import validUrl from 'valid-url'
|
||||||
import updateText from '../../updateText'
|
import updateText from '../../updateText'
|
||||||
import ComposeContext from '../../utils/createContext'
|
import ComposeContext from '../../utils/createContext'
|
||||||
|
|
||||||
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
|
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { reduceMotionEnabled } = useAccessibility()
|
const { reduceMotionEnabled } = useAccessibility()
|
||||||
|
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
@ -29,6 +45,12 @@ const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
|
|||||||
if (validUrl.isHttpsUri(uri)) {
|
if (validUrl.isHttpsUri(uri)) {
|
||||||
return (
|
return (
|
||||||
<FastImage
|
<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 }}
|
source={{ uri: reduceMotionEnabled ? emoji.static_url : emoji.url }}
|
||||||
style={styles.emoji}
|
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 { composeState } = useContext(ComposeContext)
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tagEmojis = findNodeHandle(accessibleRefEmojis.current)
|
||||||
|
if (composeState.emoji.active) {
|
||||||
|
tagEmojis && AccessibilityInfo.setAccessibilityFocus(tagEmojis)
|
||||||
|
}
|
||||||
|
}, [composeState.emoji.active])
|
||||||
|
|
||||||
const listHeader = useCallback(
|
const listHeader = useCallback(
|
||||||
({ section: { title } }) => (
|
({ section: { title } }) => (
|
||||||
<Text style={[styles.group, { color: theme.secondary }]}>{title}</Text>
|
<Text style={[styles.group, { color: theme.secondary }]}>{title}</Text>
|
||||||
@ -73,6 +106,8 @@ const ComposeEmojis: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<SectionList
|
<SectionList
|
||||||
|
accessible
|
||||||
|
ref={accessibleRefEmojis}
|
||||||
horizontal
|
horizontal
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
sections={composeState.emoji.emojis || []}
|
sections={composeState.emoji.emojis || []}
|
||||||
|
@ -48,6 +48,10 @@ const ComposePoll: React.FC = () => {
|
|||||||
color={theme.secondary}
|
color={theme.secondary}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
accessibilityLabel={t(
|
||||||
|
'content.root.footer.poll.option.placeholder.accessibilityLabel',
|
||||||
|
{ index: i + 1 }
|
||||||
|
)}
|
||||||
keyboardAppearance={mode}
|
keyboardAppearance={mode}
|
||||||
{...(i === 0 && firstRender && { autoFocus: true })}
|
{...(i === 0 && firstRender && { autoFocus: true })}
|
||||||
style={[
|
style={[
|
||||||
@ -80,6 +84,19 @@ const ComposePoll: React.FC = () => {
|
|||||||
<View style={styles.controlAmount}>
|
<View style={styles.controlAmount}>
|
||||||
<View style={styles.firstButton}>
|
<View style={styles.firstButton}>
|
||||||
<Button
|
<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={() => {
|
onPress={() => {
|
||||||
analytics('compose_poll_reduce_press')
|
analytics('compose_poll_reduce_press')
|
||||||
total > 2 &&
|
total > 2 &&
|
||||||
@ -95,6 +112,19 @@ const ComposePoll: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<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={() => {
|
onPress={() => {
|
||||||
analytics('compose_poll_increase_press')
|
analytics('compose_poll_increase_press')
|
||||||
total < 4 &&
|
total < 4 &&
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, Text, TextInput } from 'react-native'
|
import { StyleSheet, Text, TextInput } from 'react-native'
|
||||||
import formatText from '../../formatText'
|
import formatText from '../../formatText'
|
||||||
@ -45,7 +45,6 @@ const ComposeSpoilerInput: React.FC = () => {
|
|||||||
payload: { selection: { start, end } }
|
payload: { selection: { start, end } }
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
ref={composeState.textInputFocus.refs.spoiler}
|
|
||||||
scrollEnabled={false}
|
scrollEnabled={false}
|
||||||
onFocus={() =>
|
onFocus={() =>
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
|
@ -37,7 +37,7 @@ const composeInitialState: Omit<ComposeState, 'timestamp'> = {
|
|||||||
replyToStatus: undefined,
|
replyToStatus: undefined,
|
||||||
textInputFocus: {
|
textInputFocus: {
|
||||||
current: 'text',
|
current: 'text',
|
||||||
refs: { text: createRef(), spoiler: createRef() }
|
refs: { text: createRef() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
src/screens/Compose/utils/types.d.ts
vendored
2
src/screens/Compose/utils/types.d.ts
vendored
@ -63,7 +63,7 @@ export type ComposeState = {
|
|||||||
replyToStatus?: Mastodon.Status
|
replyToStatus?: Mastodon.Status
|
||||||
textInputFocus: {
|
textInputFocus: {
|
||||||
current: 'text' | 'spoiler'
|
current: 'text' | 'spoiler'
|
||||||
refs: { text: RefObject<TextInput>; spoiler: RefObject<TextInput> }
|
refs: { text: RefObject<TextInput> }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +98,10 @@ const ImageItem = ({
|
|||||||
setImageDimensions
|
setImageDimensions
|
||||||
})}
|
})}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
|
imageStyle={{
|
||||||
|
flex: 1,
|
||||||
|
resizeMode: 'stretch'
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -120,6 +120,8 @@ const HeaderComponent = React.memo(
|
|||||||
content={`${currentIndex + 1} / ${imageUrls.length}`}
|
content={`${currentIndex + 1} / ${imageUrls.length}`}
|
||||||
/>
|
/>
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
|
accessibilityLabel={t('content.actions.accessibilityLabel')}
|
||||||
|
accessibilityHint={t('content.actions.accessibilityHint')}
|
||||||
content='MoreHorizontal'
|
content='MoreHorizontal'
|
||||||
native={false}
|
native={false}
|
||||||
background
|
background
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import {
|
import {
|
||||||
@ -67,32 +68,28 @@ const ScreenTabs = React.memo(
|
|||||||
case 'Tab-Notifications':
|
case 'Tab-Notifications':
|
||||||
return <Icon name='Bell' size={size} color={color} />
|
return <Icon name='Bell' size={size} color={color} />
|
||||||
case 'Tab-Me':
|
case 'Tab-Me':
|
||||||
return instanceActive !== -1 ? (
|
return (
|
||||||
<Image
|
<GracefullyImage
|
||||||
source={{
|
key={instanceAccount?.avatarStatic}
|
||||||
uri: instanceAccount?.avatarStatic
|
uri={{ original: instanceAccount?.avatarStatic }}
|
||||||
|
dimension={{
|
||||||
|
width: size,
|
||||||
|
height: size
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
borderRadius: size,
|
borderRadius: size,
|
||||||
|
overflow: 'hidden',
|
||||||
borderWidth: focused ? 2 : 0,
|
borderWidth: focused ? 2 : 0,
|
||||||
borderColor: focused ? theme.secondary : color
|
borderColor: focused ? theme.secondary : color
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<Icon
|
|
||||||
name={focused ? 'Meh' : 'Smile'}
|
|
||||||
size={size}
|
|
||||||
color={!focused ? theme.secondary : color}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return <Icon name='AlertOctagon' size={size} color={color} />
|
return <Icon name='AlertOctagon' size={size} color={color} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[instanceAccount, instanceActive]
|
[instanceAccount?.avatarStatic, instanceActive]
|
||||||
)
|
)
|
||||||
const tabBarOptions = useMemo(
|
const tabBarOptions = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -37,6 +37,8 @@ const TabLocal = React.memo(
|
|||||||
}),
|
}),
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
|
accessibilityLabel={t('common.search.accessibilityLabel')}
|
||||||
|
accessibilityHint={t('common.search.accessibilityHint')}
|
||||||
content='Search'
|
content='Search'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('search_tap', { page: 'Local' })
|
analytics('search_tap', { page: 'Local' })
|
||||||
|
@ -11,7 +11,8 @@ import {
|
|||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import { groupBy } from 'lodash'
|
||||||
|
import React, { useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
import { ScrollView } from 'react-native-gesture-handler'
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
@ -20,10 +21,10 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
instance: Instance
|
instance: Instance
|
||||||
disabled?: boolean
|
selected?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountButton: React.FC<Props> = ({ instance, disabled = false }) => {
|
const AccountButton: React.FC<Props> = ({ instance, selected = false }) => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
@ -31,10 +32,10 @@ const AccountButton: React.FC<Props> = ({ instance, disabled = false }) => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
disabled={disabled}
|
selected={selected}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
content={`@${instance.account.acct}@${instance.uri}${
|
content={`@${instance.account.acct}@${instance.uri}${
|
||||||
disabled ? ' ✓' : ''
|
selected ? ' ✓' : ''
|
||||||
}`}
|
}`}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
haptics('Light')
|
haptics('Light')
|
||||||
@ -50,11 +51,17 @@ const AccountButton: React.FC<Props> = ({ instance, disabled = false }) => {
|
|||||||
const ScreenMeSwitchRoot: React.FC = () => {
|
const ScreenMeSwitchRoot: React.FC = () => {
|
||||||
const { t } = useTranslation('screenTabs')
|
const { t } = useTranslation('screenTabs')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const instances = useSelector(getInstances)
|
const instances = useSelector(getInstances, () => true)
|
||||||
const instanceActive = useSelector(getInstanceActive)
|
const instanceActive = useSelector(getInstanceActive, () => true)
|
||||||
|
|
||||||
|
const scrollViewRef = useRef<ScrollView>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView style={styles.base} keyboardShouldPersistTaps='always'>
|
<ScrollView
|
||||||
|
ref={scrollViewRef}
|
||||||
|
style={styles.base}
|
||||||
|
keyboardShouldPersistTaps='always'
|
||||||
|
>
|
||||||
<View style={[styles.firstSection, { borderBottomColor: theme.border }]}>
|
<View style={[styles.firstSection, { borderBottomColor: theme.border }]}>
|
||||||
<Text style={[styles.header, { color: theme.primaryDefault }]}>
|
<Text style={[styles.header, { color: theme.primaryDefault }]}>
|
||||||
{t('me.switch.existing')}
|
{t('me.switch.existing')}
|
||||||
@ -74,7 +81,7 @@ const ScreenMeSwitchRoot: React.FC = () => {
|
|||||||
<AccountButton
|
<AccountButton
|
||||||
key={index}
|
key={index}
|
||||||
instance={instance}
|
instance={instance}
|
||||||
disabled={
|
selected={
|
||||||
instance.url === localAccount.url &&
|
instance.url === localAccount.url &&
|
||||||
instance.token === localAccount.token &&
|
instance.token === localAccount.token &&
|
||||||
instance.account.id === localAccount.account.id
|
instance.account.id === localAccount.account.id
|
||||||
@ -90,7 +97,11 @@ const ScreenMeSwitchRoot: React.FC = () => {
|
|||||||
<Text style={[styles.header, { color: theme.primaryDefault }]}>
|
<Text style={[styles.header, { color: theme.primaryDefault }]}>
|
||||||
{t('me.switch.new')}
|
{t('me.switch.new')}
|
||||||
</Text>
|
</Text>
|
||||||
<ComponentInstance disableHeaderImage goBack />
|
<ComponentInstance
|
||||||
|
scrollViewRef={scrollViewRef}
|
||||||
|
disableHeaderImage
|
||||||
|
goBack
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)
|
)
|
||||||
|
@ -34,6 +34,8 @@ const TabNotifications = React.memo(
|
|||||||
}),
|
}),
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
|
accessibilityLabel={t('notifications.filter.accessibilityLabel')}
|
||||||
|
accessibilityHint={t('notifications.filter.accessibilityHint')}
|
||||||
content='Filter'
|
content='Filter'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('notificationsfilter_tap')
|
analytics('notificationsfilter_tap')
|
||||||
|
@ -62,6 +62,8 @@ const TabPublic = React.memo(
|
|||||||
),
|
),
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
|
accessibilityLabel={t('common.search.accessibilityLabel')}
|
||||||
|
accessibilityHint={t('common.search.accessibilityHint')}
|
||||||
content='Search'
|
content='Search'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('search_tap', { page: pages[segment].key })
|
analytics('search_tap', { page: pages[segment].key })
|
||||||
|
@ -6,6 +6,7 @@ import { useAccountQuery } from '@utils/queryHooks/account'
|
|||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useMemo, useReducer } from 'react'
|
import React, { useCallback, useEffect, useMemo, useReducer } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { useSharedValue } from 'react-native-reanimated'
|
import { useSharedValue } from 'react-native-reanimated'
|
||||||
import AccountAttachments from './Account/Attachments'
|
import AccountAttachments from './Account/Attachments'
|
||||||
@ -23,6 +24,7 @@ const TabSharedAccount: React.FC<SharedAccountProp> = ({
|
|||||||
},
|
},
|
||||||
navigation
|
navigation
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t, i18n } = useTranslation('screenTabs')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const { data } = useAccountQuery({ id: account.id })
|
const { data } = useAccountQuery({ id: account.id })
|
||||||
@ -38,6 +40,10 @@ const TabSharedAccount: React.FC<SharedAccountProp> = ({
|
|||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
|
accessibilityLabel={t('shared.account.actions.accessibilityLabel', {
|
||||||
|
user: data?.acct
|
||||||
|
})}
|
||||||
|
accessibilityHint={t('shared.account.actions.accessibilityHint')}
|
||||||
content='MoreHorizontal'
|
content='MoreHorizontal'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('bottomsheet_open_press', {
|
analytics('bottomsheet_open_press', {
|
||||||
@ -54,7 +60,7 @@ const TabSharedAccount: React.FC<SharedAccountProp> = ({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
return updateHeaderRight()
|
return updateHeaderRight()
|
||||||
}, [])
|
}, [i18n.language])
|
||||||
|
|
||||||
const onScroll = useCallback(({ nativeEvent }) => {
|
const onScroll = useCallback(({ nativeEvent }) => {
|
||||||
scrollY.value = nativeEvent.contentOffset.y
|
scrollY.value = nativeEvent.contentOffset.y
|
||||||
|
@ -163,6 +163,7 @@ const sharedScreens = (
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
accessibilityRole='search'
|
||||||
keyboardAppearance={mode}
|
keyboardAppearance={mode}
|
||||||
style={[
|
style={[
|
||||||
styles.textInput,
|
styles.textInput,
|
||||||
|
@ -3,46 +3,60 @@ import { AccessibilityInfo } from 'react-native'
|
|||||||
|
|
||||||
type ContextType = {
|
type ContextType = {
|
||||||
reduceMotionEnabled: boolean
|
reduceMotionEnabled: boolean
|
||||||
|
screenReaderEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccessibilityContext = createContext<ContextType>({
|
const AccessibilityContext = createContext<ContextType>({
|
||||||
reduceMotionEnabled: false
|
reduceMotionEnabled: false,
|
||||||
|
screenReaderEnabled: false
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useAccessibility = () => useContext(AccessibilityContext)
|
export const useAccessibility = () => useContext(AccessibilityContext)
|
||||||
|
|
||||||
const AccessibilityManager: React.FC = ({ children }) => {
|
const AccessibilityManager: React.FC = ({ children }) => {
|
||||||
const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false)
|
const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false)
|
||||||
|
const [screenReaderEnabled, setScreenReaderEnabled] = useState(false)
|
||||||
|
|
||||||
const handleReduceMotionChanged = (reduceMotionEnabled: boolean) =>
|
const handleReduceMotionChanged = (reduceMotionEnabled: boolean) =>
|
||||||
setReduceMotionEnabled(reduceMotionEnabled)
|
setReduceMotionEnabled(reduceMotionEnabled)
|
||||||
|
|
||||||
const loadReduceMotion = async () => {
|
const handleScreenReaderEnabled = (screenReaderEnabled: boolean) =>
|
||||||
|
setScreenReaderEnabled(screenReaderEnabled)
|
||||||
|
|
||||||
|
const loadAccessibilityInfo = async () => {
|
||||||
const reduceMotion = await AccessibilityInfo.isReduceMotionEnabled()
|
const reduceMotion = await AccessibilityInfo.isReduceMotionEnabled()
|
||||||
|
const screenReader = await AccessibilityInfo.isScreenReaderEnabled()
|
||||||
setReduceMotionEnabled(reduceMotion)
|
setReduceMotionEnabled(reduceMotion)
|
||||||
|
setScreenReaderEnabled(screenReader)
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadReduceMotion()
|
loadAccessibilityInfo()
|
||||||
|
|
||||||
AccessibilityInfo.addEventListener(
|
AccessibilityInfo.addEventListener(
|
||||||
'reduceMotionChanged',
|
'reduceMotionChanged',
|
||||||
handleReduceMotionChanged
|
handleReduceMotionChanged
|
||||||
)
|
)
|
||||||
|
AccessibilityInfo.addEventListener(
|
||||||
|
'screenReaderChanged',
|
||||||
|
handleScreenReaderEnabled
|
||||||
|
)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
AccessibilityInfo.removeEventListener(
|
AccessibilityInfo.removeEventListener(
|
||||||
'reduceMotionChanged',
|
'reduceMotionChanged',
|
||||||
handleReduceMotionChanged
|
handleReduceMotionChanged
|
||||||
)
|
)
|
||||||
|
AccessibilityInfo.removeEventListener(
|
||||||
|
'screenReaderChanged',
|
||||||
|
handleScreenReaderEnabled
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibilityContext.Provider
|
<AccessibilityContext.Provider
|
||||||
value={{
|
value={{ reduceMotionEnabled, screenReaderEnabled }}
|
||||||
reduceMotionEnabled
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</AccessibilityContext.Provider>
|
</AccessibilityContext.Provider>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user