1
0
mirror of https://github.com/tooot-app/app synced 2025-04-24 23:18:47 +02:00

Added reduced motion

This commit is contained in:
Zhiyuan Zheng 2021-03-27 00:02:32 +01:00
parent d490cae955
commit b0b7a7567b
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
10 changed files with 127 additions and 47 deletions

View File

@ -7,6 +7,7 @@ import log from '@root/startup/log'
import netInfo from '@root/startup/netInfo' import netInfo from '@root/startup/netInfo'
import sentry from '@root/startup/sentry' import sentry from '@root/startup/sentry'
import { persistor, store } from '@root/store' import { persistor, store } from '@root/store'
import AccessibilityManager from '@utils/accessibility/AccessibilityManager'
import { getSettingsLanguage } from '@utils/slices/settingsSlice' import { getSettingsLanguage } from '@utils/slices/settingsSlice'
import ThemeManager from '@utils/styles/ThemeManager' import ThemeManager from '@utils/styles/ThemeManager'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
@ -90,9 +91,11 @@ const App: React.FC = () => {
i18n.changeLanguage(getSettingsLanguage(store.getState())) i18n.changeLanguage(getSettingsLanguage(store.getState()))
return ( return (
<ActionSheetProvider> <ActionSheetProvider>
<AccessibilityManager>
<ThemeManager> <ThemeManager>
<Screens localCorrupt={localCorrupt} /> <Screens localCorrupt={localCorrupt} />
</ThemeManager> </ThemeManager>
</AccessibilityManager>
</ActionSheetProvider> </ActionSheetProvider>
) )
} else { } else {

View File

@ -1,9 +1,11 @@
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { getSettingsFontsize } from '@utils/slices/settingsSlice' import { getSettingsFontsize } from '@utils/slices/settingsSlice'
import { StyleConstants } from '@utils/styles/constants' 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 { Image, StyleSheet, Text } from 'react-native' import { StyleSheet, Text } from 'react-native'
import FastImage from 'react-native-fast-image'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/) const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
@ -24,6 +26,8 @@ const ParseEmojis = React.memo(
adaptiveSize = false, adaptiveSize = false,
fontBold = false fontBold = false
}: Props) => { }: Props) => {
const { reduceMotionEnabled } = useAccessibility()
const adaptiveFontsize = useSelector(getSettingsFontsize) const adaptiveFontsize = useSelector(getSettingsFontsize)
const adaptedFontsize = adaptiveScale( const adaptedFontsize = adaptiveScale(
StyleConstants.Font.Size[size], StyleConstants.Font.Size[size],
@ -69,9 +73,13 @@ const ParseEmojis = React.memo(
<Text key={i}> <Text key={i}>
{/* When emoji starts a paragraph, lineHeight will break */} {/* When emoji starts a paragraph, lineHeight will break */}
{i === 0 ? <Text> </Text> : null} {i === 0 ? <Text> </Text> : null}
<Image <FastImage
key={adaptiveFontsize} key={adaptiveFontsize}
source={{ uri: emojis[emojiIndex].url }} source={{
uri: reduceMotionEnabled
? emojis[emojiIndex].static_url
: emojis[emojiIndex].url
}}
style={styles.image} style={styles.image}
/> />
</Text> </Text>

View File

@ -33,9 +33,10 @@ const TimelineAvatar = React.memo(
height: StyleConstants.Avatar.M height: StyleConstants.Avatar.M
}} }}
style={{ style={{
borderRadius: 4, borderRadius: StyleConstants.Avatar.M,
overflow: 'hidden', overflow: 'hidden',
marginRight: StyleConstants.Spacing.S marginRight: StyleConstants.Spacing.S,
backgroundColor: 'red'
}} }}
/> />
) )

View File

@ -6,6 +6,7 @@ import { ParseHTML } from '@components/Parse'
import RelativeTime from '@components/RelativeTime' import RelativeTime from '@components/RelativeTime'
import { BlurView } from '@react-native-community/blur' import { BlurView } from '@react-native-community/blur'
import { StackScreenProps } from '@react-navigation/stack' import { StackScreenProps } from '@react-navigation/stack'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { import {
useAnnouncementMutation, useAnnouncementMutation,
useAnnouncementQuery useAnnouncementQuery
@ -14,15 +15,9 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { import { Dimensions, Pressable, StyleSheet, Text, View } from 'react-native'
Dimensions,
Image,
Pressable,
StyleSheet,
Text,
View
} from 'react-native'
import { Circle } from 'react-native-animated-spinkit' import { Circle } from 'react-native-animated-spinkit'
import FastImage from 'react-native-fast-image'
import { FlatList, ScrollView } from 'react-native-gesture-handler' import { FlatList, ScrollView } from 'react-native-gesture-handler'
import { SafeAreaView } from 'react-native-safe-area-context' import { SafeAreaView } from 'react-native-safe-area-context'
@ -37,6 +32,7 @@ const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
}, },
navigation navigation
}) => { }) => {
const { reduceMotionEnabled } = useAccessibility()
const { mode, theme } = useTheme() const { mode, theme } = useTheme()
const [index, setIndex] = useState(0) const [index, setIndex] = useState(0)
const { t } = useTranslation('sharedAnnouncements') const { t } = useTranslation('sharedAnnouncements')
@ -102,7 +98,9 @@ const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
style={[ style={[
styles.reaction, styles.reaction,
{ {
borderColor: reaction.me ? theme.disabled : theme.primaryDefault, borderColor: reaction.me
? theme.disabled
: theme.primaryDefault,
backgroundColor: reaction.me backgroundColor: reaction.me
? theme.disabled ? theme.disabled
: theme.backgroundDefault : theme.backgroundDefault
@ -121,8 +119,12 @@ const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
}} }}
> >
{reaction.url ? ( {reaction.url ? (
<Image <FastImage
source={{ uri: reaction.url }} source={{
uri: reduceMotionEnabled
? reaction.static_url
: reaction.url
}}
style={[styles.reactionImage]} style={[styles.reactionImage]}
/> />
) : ( ) : (
@ -130,7 +132,10 @@ const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
)} )}
{reaction.count ? ( {reaction.count ? (
<Text <Text
style={[styles.reactionCount, { color: theme.primaryDefault }]} style={[
styles.reactionCount,
{ color: theme.primaryDefault }
]}
> >
{reaction.count} {reaction.count}
</Text> </Text>
@ -246,7 +251,8 @@ const ScreenAnnouncements: React.FC<ScreenAnnouncementsProp> = ({
styles.indicator, styles.indicator,
{ {
borderColor: theme.primaryDefault, borderColor: theme.primaryDefault,
backgroundColor: i === index ? theme.primaryDefault : undefined, backgroundColor:
i === index ? theme.primaryDefault : undefined,
marginLeft: marginLeft:
i === query.data.length ? 0 : StyleConstants.Spacing.S i === query.data.length ? 0 : StyleConstants.Spacing.S
} }

View File

@ -4,14 +4,8 @@ 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, { import React, { useCallback, useContext, useEffect, useMemo } from 'react'
useCallback, import { FlatList, StyleSheet, View } from 'react-native'
useContext,
useEffect,
useMemo,
useRef
} from 'react'
import { FlatList, Image, 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'
@ -20,24 +14,32 @@ import ComposeRootHeader from './Root/Header'
import ComposeRootSuggestion from './Root/Suggestion' import ComposeRootSuggestion from './Root/Suggestion'
import ComposeContext from './utils/createContext' import ComposeContext from './utils/createContext'
import ComposeDrafts from './Root/Drafts' import ComposeDrafts from './Root/Drafts'
import FastImage from 'react-native-fast-image'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
const prefetchEmojis = ( const prefetchEmojis = (
sortedEmojis: { title: string; data: Mastodon.Emoji[] }[] sortedEmojis: { title: string; data: Mastodon.Emoji[] }[],
reduceMotionEnabled: boolean
) => { ) => {
const prefetches: { uri: string }[] = []
let requestedIndex = 0 let requestedIndex = 0
sortedEmojis.forEach(sorted => { sortedEmojis.forEach(sorted => {
sorted.data.forEach(emoji => { sorted.data.forEach(emoji => {
if (requestedIndex > 40) { if (requestedIndex > 40) {
return return
} }
Image.prefetch(emoji.url) prefetches.push({
uri: reduceMotionEnabled ? emoji.static_url : emoji.url
})
requestedIndex++ requestedIndex++
}) })
}) })
FastImage.preload(prefetches)
} }
const ComposeRoot = React.memo( const ComposeRoot = React.memo(
() => { () => {
const { reduceMotionEnabled } = useAccessibility()
const { theme } = useTheme() const { theme } = useTheme()
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
@ -74,9 +76,9 @@ const ComposeRoot = React.memo(
type: 'emoji', type: 'emoji',
payload: { ...composeState.emoji, emojis: sortedEmojis } payload: { ...composeState.emoji, emojis: sortedEmojis }
}) })
prefetchEmojis(sortedEmojis) prefetchEmojis(sortedEmojis, reduceMotionEnabled)
} }
}, [emojisData]) }, [emojisData, reduceMotionEnabled])
const listEmpty = useMemo(() => { const listEmpty = useMemo(() => {
if (isFetching) { if (isFetching) {

View File

@ -3,18 +3,15 @@ import haptics from '@components/haptics'
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, { useCallback, useContext, useMemo } from 'react'
import { import { Pressable, SectionList, StyleSheet, Text, View } from 'react-native'
Image,
Pressable,
SectionList,
StyleSheet,
Text,
View
} from 'react-native'
import ComposeContext from '../../utils/createContext' import ComposeContext from '../../utils/createContext'
import updateText from '../../updateText' import updateText from '../../updateText'
import FastImage from 'react-native-fast-image'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => { const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
const { reduceMotionEnabled } = useAccessibility()
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('compose_emoji_add') analytics('compose_emoji_add')
@ -32,8 +29,8 @@ const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
}, [composeState]) }, [composeState])
const children = useMemo( const children = useMemo(
() => ( () => (
<Image <FastImage
source={{ uri: emoji.url, cache: 'force-cache' }} source={{ uri: reduceMotionEnabled ? emoji.static_url : emoji.url }}
style={styles.emoji} style={styles.emoji}
/> />
), ),
@ -81,6 +78,7 @@ const ComposeEmojis: React.FC = () => {
keyExtractor={item => item.shortcode} keyExtractor={item => item.shortcode}
renderSectionHeader={listHeader} renderSectionHeader={listHeader}
renderItem={listItem} renderItem={listItem}
windowSize={3}
/> />
</View> </View>
) )

View File

@ -1,3 +1,4 @@
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { Dimensions, Image } from 'react-native' import { Dimensions, Image } from 'react-native'
@ -9,14 +10,17 @@ export interface Props {
limitHeight?: boolean limitHeight?: boolean
} }
const AccountHeader: React.FC<Props> = ({ account, limitHeight = false }) => { const AccountHeader: React.FC<Props> = ({ account }) => {
const { accountState } = useContext(AccountContext) const { accountState } = useContext(AccountContext)
const { reduceMotionEnabled } = useAccessibility()
const { theme } = useTheme() const { theme } = useTheme()
const topInset = useSafeAreaInsets().top const topInset = useSafeAreaInsets().top
return ( return (
<Image <Image
source={{ uri: account?.header }} source={{
uri: reduceMotionEnabled ? account?.header_static : account?.header
}}
style={{ style={{
height: height:
Dimensions.get('screen').width * accountState.headerRatio + topInset, Dimensions.get('screen').width * accountState.headerRatio + topInset,

View File

@ -2,6 +2,7 @@ import analytics from '@components/analytics'
import GracefullyImage from '@components/GracefullyImage' import GracefullyImage from '@components/GracefullyImage'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack' import { StackNavigationProp } from '@react-navigation/stack'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import React from 'react' import React from 'react'
import { Pressable, StyleSheet } from 'react-native' import { Pressable, StyleSheet } from 'react-native'
@ -15,6 +16,7 @@ const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo }) => {
const navigation = useNavigation< const navigation = useNavigation<
StackNavigationProp<Nav.TabLocalStackParamList> StackNavigationProp<Nav.TabLocalStackParamList>
>() >()
const { reduceMotionEnabled } = useAccessibility()
return ( return (
<Pressable <Pressable
@ -28,7 +30,11 @@ const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo }) => {
<GracefullyImage <GracefullyImage
key={account?.avatar} key={account?.avatar}
style={styles.image} style={styles.image}
uri={{ original: account?.avatar }} uri={{
original: reduceMotionEnabled
? account?.avatar_static
: account?.avatar
}}
/> />
</Pressable> </Pressable>
) )

View File

@ -0,0 +1,52 @@
import React, { createContext, useContext, useEffect, useState } from 'react'
import { AccessibilityInfo } from 'react-native'
type ContextType = {
reduceMotionEnabled: boolean
}
const AccessibilityContext = createContext<ContextType>({
reduceMotionEnabled: false
})
export const useAccessibility = () => useContext(AccessibilityContext)
const AccessibilityManager: React.FC = ({ children }) => {
const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false)
const handleReduceMotionChanged = (reduceMotionEnabled: boolean) =>
setReduceMotionEnabled(reduceMotionEnabled)
const loadReduceMotion = async () => {
const reduceMotion = await AccessibilityInfo.isReduceMotionEnabled()
setReduceMotionEnabled(reduceMotion)
}
useEffect(() => {
loadReduceMotion()
AccessibilityInfo.addEventListener(
'reduceMotionChanged',
handleReduceMotionChanged
)
return () => {
AccessibilityInfo.removeEventListener(
'reduceMotionChanged',
handleReduceMotionChanged
)
}
}, [])
return (
<AccessibilityContext.Provider
value={{
reduceMotionEnabled
}}
>
{children}
</AccessibilityContext.Provider>
)
}
export default AccessibilityManager

View File

@ -11,7 +11,7 @@ type ContextType = {
setTheme: (theme: 'light' | 'dark') => void setTheme: (theme: 'light' | 'dark') => void
} }
export const ManageThemeContext = createContext<ContextType>({ const ManageThemeContext = createContext<ContextType>({
mode: 'light', mode: 'light',
theme: getTheme('light'), theme: getTheme('light'),
setTheme: () => {} setTheme: () => {}