Added gif preference

https://github.com/mastodon/mastodon/pull/22706
This commit is contained in:
xmflsct 2023-01-24 00:36:51 +01:00
parent 47d5b02468
commit f1b162a020
12 changed files with 233 additions and 139 deletions

View File

@ -452,6 +452,7 @@ declare namespace Mastodon {
'posting:default:language'?: string 'posting:default:language'?: string
'reading:expand:media'?: 'default' | 'show_all' | 'hide_all' 'reading:expand:media'?: 'default' | 'show_all' | 'hide_all'
'reading:expand:spoilers'?: boolean 'reading:expand:spoilers'?: boolean
'reading:autoplay:gifs'?: boolean
} }
type PushSubscription = { type PushSubscription = {

View File

@ -1,17 +1,19 @@
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import React from 'react' import React from 'react'
import { View } from 'react-native' import { View, ViewStyle } from 'react-native'
export interface Props { export interface Props {
style?: ViewStyle
children: React.ReactNode children: React.ReactNode
} }
const MenuContainer: React.FC<Props> = ({ children }) => { const MenuContainer: React.FC<Props> = ({ style, children }) => {
return ( return (
<View <View
style={{ style={{
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding, paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
marginBottom: StyleConstants.Spacing.Global.PagePadding marginBottom: StyleConstants.Spacing.Global.PagePadding,
...style
}} }}
> >
{children} {children}

View File

@ -13,7 +13,6 @@ 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 { useTranslation } from 'react-i18next'
import { View } from 'react-native'
export interface Props { export interface Props {
id: Mastodon.Account['id'] id: Mastodon.Account['id']
@ -127,7 +126,7 @@ const RelationshipOutgoing: React.FC<Props> = ({ id }: Props) => {
const isPageNotifications = name === 'Tab-Notifications-Root' const isPageNotifications = name === 'Tab-Notifications-Root'
return ( return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <>
{!isPageNotifications && canFollowNotify && query.data?.following ? ( {!isPageNotifications && canFollowNotify && query.data?.following ? (
<Button <Button
type='icon' type='icon'
@ -155,7 +154,7 @@ const RelationshipOutgoing: React.FC<Props> = ({ id }: Props) => {
loading={query.isLoading || mutation.isLoading} loading={query.isLoading || mutation.isLoading}
disabled={query.isError || query.data?.blocked_by} disabled={query.isError || query.data?.blocked_by}
/> />
</View> </>
) )
} }

View File

@ -1,6 +1,6 @@
import Button from '@components/Button' import Button from '@components/Button'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { useGlobalStorage } from '@utils/storage/actions' import { useAccountStorage, useGlobalStorage } from '@utils/storage/actions'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av' import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av'
import { Platform } from 'expo-modules-core' import { Platform } from 'expo-modules-core'
@ -28,6 +28,11 @@ const AttachmentVideo: React.FC<Props> = ({
}) => { }) => {
const { reduceMotionEnabled } = useAccessibility() const { reduceMotionEnabled } = useAccessibility()
const [autoplayGifv] = useGlobalStorage.boolean('app.auto_play_gifv') const [autoplayGifv] = useGlobalStorage.boolean('app.auto_play_gifv')
const [preferences] = useAccountStorage.object('preferences')
const shouldAutoplayGifv =
preferences?.['reading:autoplay:gifs'] !== undefined
? preferences['reading:autoplay:gifs']
: autoplayGifv
const videoPlayer = useRef<Video>(null) const videoPlayer = useRef<Video>(null)
const [videoLoading, setVideoLoading] = useState(false) const [videoLoading, setVideoLoading] = useState(false)
@ -63,7 +68,7 @@ const AttachmentVideo: React.FC<Props> = ({
resizeMode={videoResizeMode} resizeMode={videoResizeMode}
{...(gifv {...(gifv
? { ? {
shouldPlay: reduceMotionEnabled || !autoplayGifv ? false : true, shouldPlay: reduceMotionEnabled || !shouldAutoplayGifv ? false : true,
isMuted: true, isMuted: true,
isLooping: true, isLooping: true,
source: { uri: video.url } source: { uri: video.url }
@ -82,7 +87,7 @@ const AttachmentVideo: React.FC<Props> = ({
Platform.OS === 'android' && Platform.OS === 'android' &&
(await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT)) (await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT))
Platform.OS === 'android' && setVideoResizeMode(ResizeMode.COVER) Platform.OS === 'android' && setVideoResizeMode(ResizeMode.COVER)
if (gifv && !reduceMotionEnabled && autoplayGifv) { if (gifv && !reduceMotionEnabled && shouldAutoplayGifv) {
videoPlayer.current?.playAsync() videoPlayer.current?.playAsync()
} else { } else {
videoPlayer.current?.pauseAsync() videoPlayer.current?.pauseAsync()
@ -116,7 +121,7 @@ const AttachmentVideo: React.FC<Props> = ({
video.blurhash ? ( video.blurhash ? (
<Blurhash blurhash={video.blurhash} style={{ width: '100%', height: '100%' }} /> <Blurhash blurhash={video.blurhash} style={{ width: '100%', height: '100%' }} />
) : null ) : null
) : !gifv || (gifv && (reduceMotionEnabled || !autoplayGifv)) ? ( ) : !gifv || (gifv && (reduceMotionEnabled || !shouldAutoplayGifv)) ? (
<Button <Button
round round
overlay overlay
@ -129,7 +134,7 @@ const AttachmentVideo: React.FC<Props> = ({
) : null} ) : null}
<AttachmentAltText sensitiveShown={sensitiveShown} text={video.description} /> <AttachmentAltText sensitiveShown={sensitiveShown} text={video.description} />
</Pressable> </Pressable>
{gifv && !autoplayGifv ? ( {gifv && !shouldAutoplayGifv ? (
<Button <Button
style={{ style={{
position: 'absolute', position: 'absolute',

View File

@ -69,6 +69,9 @@
"push": { "push": {
"name": "Push Notification" "name": "Push Notification"
}, },
"preferences": {
"name": "Preferences"
},
"profile": { "profile": {
"name": "Edit Profile" "name": "Edit Profile"
}, },
@ -84,9 +87,6 @@
"settings": { "settings": {
"name": "App Settings" "name": "App Settings"
}, },
"webSettings": {
"name": "More Account Settings"
},
"switch": { "switch": {
"name": "Switch Account" "name": "Switch Account"
} }
@ -125,6 +125,37 @@
"message": "This action is not recoverable." "message": "This action is not recoverable."
} }
}, },
"preferences": {
"visibility": {
"title": "Posting visibility",
"options": {
"public": "Public",
"unlisted": "Unlisted",
"private": "Followers only"
}
},
"sensitive": {
"title": "Posting media sensitive"
},
"media": {
"title": "Media display",
"options": {
"default": "Hide media marked as sensitive",
"show_all": "Always show media",
"hide_all": "Always hide media"
}
},
"spoilers": {
"title": "Auto expand toots with content warning",
},
"autoplay_gifs": {
"title": "Autoplay GIF in toots"
},
"web_only": {
"title": "Update settings",
"description": "Settings below can only be updated using the web UI"
}
},
"profile": { "profile": {
"feedback": { "feedback": {
"succeed": "{{type}} updated", "succeed": "{{type}} updated",
@ -150,17 +181,6 @@
"total_one": "{{count}} field", "total_one": "{{count}} field",
"total_other": "{{count}} fields" "total_other": "{{count}} fields"
}, },
"visibility": {
"title": "Posting Visibility",
"options": {
"public": "Public",
"unlisted": "Unlisted",
"private": "Followers only"
}
},
"sensitive": {
"title": "Posting Media Sensitive"
},
"lock": { "lock": {
"title": "Lock Account", "title": "Lock Account",
"description": "Requires you to manually approve followers" "description": "Requires you to manually approve followers"

View File

@ -0,0 +1,158 @@
import { MenuContainer, MenuRow } from '@components/Menu'
import { Message } from '@components/Message'
import { useActionSheet } from '@expo/react-native-action-sheet'
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
import browserPackage from '@utils/helpers/browserPackage'
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
import { useProfileMutation } from '@utils/queryHooks/profile'
import { getAccountStorage } from '@utils/storage/actions'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import * as WebBrowser from 'expo-web-browser'
import React, { useRef } from 'react'
import { useTranslation } from 'react-i18next'
import FlashMessage from 'react-native-flash-message'
import { ScrollView } from 'react-native-gesture-handler'
const TabMePreferences: React.FC<TabMeProfileStackScreenProps<'Tab-Me-Profile-Root'>> = () => {
const { colors } = useTheme()
const { t } = useTranslation(['common', 'screenTabs'])
const { showActionSheetWithOptions } = useActionSheet()
const { mutateAsync } = useProfileMutation()
const { data, isFetching, refetch } = usePreferencesQuery()
const messageRef = useRef<FlashMessage>(null)
return (
<ScrollView>
<MenuContainer>
{data?.['posting:default:visibility'] !== 'direct' ? (
<MenuRow
title={t('screenTabs:me.preferences.visibility.title')}
content={
data?.['posting:default:visibility']
? t(
`screenTabs:me.preferences.visibility.options.${data['posting:default:visibility']}`
)
: undefined
}
loading={isFetching}
iconBack='ChevronRight'
onPress={() =>
showActionSheetWithOptions(
{
title: t('screenTabs:me.preferences.visibility.title'),
options: [
t('screenTabs:me.preferences.visibility.options.public'),
t('screenTabs:me.preferences.visibility.options.unlisted'),
t('screenTabs:me.preferences.visibility.options.private'),
t('common:buttons.cancel')
],
cancelButtonIndex: 3,
...androidActionSheetStyles(colors)
},
async buttonIndex => {
switch (buttonIndex) {
case 0:
case 1:
case 2:
const indexVisibilityMapping = ['public', 'unlisted', 'private'] as [
'public',
'unlisted',
'private'
]
if (
data?.['posting:default:visibility'] &&
data['posting:default:visibility'] !== indexVisibilityMapping[buttonIndex]
) {
mutateAsync({
messageRef,
message: {
text: 'me.profile.root.visibility.title',
succeed: false,
failed: true
},
type: 'source[privacy]',
data: indexVisibilityMapping[buttonIndex]
}).then(() => refetch())
}
break
}
}
)
}
/>
) : null}
<MenuRow
title={t('screenTabs:me.preferences.sensitive.title')}
switchValue={data?.['posting:default:sensitive']}
switchOnValueChange={() =>
mutateAsync({
messageRef,
message: {
text: 'me.profile.root.sensitive.title',
succeed: false,
failed: true
},
type: 'source[sensitive]',
data:
data?.['posting:default:sensitive'] === undefined
? true
: !data['posting:default:sensitive']
}).then(() => refetch())
}
loading={isFetching}
/>
</MenuContainer>
<MenuContainer style={{ marginTop: StyleConstants.Spacing.L }}>
<MenuRow
iconBack='ExternalLink'
title={t('screenTabs:me.preferences.web_only.title')}
description={t('screenTabs:me.preferences.web_only.description')}
onPress={async () =>
WebBrowser.openAuthSessionAsync(
`https://${getAccountStorage.string('auth.domain')}/settings/preferences`,
'tooot://tooot',
{
...(await browserPackage()),
dismissButtonStyle: 'done',
readerMode: false
}
).then(() => refetch())
}
/>
<MenuRow
title={t('screenTabs:me.preferences.media.title')}
content={
data?.['reading:expand:media']
? t(`screenTabs:me.preferences.media.options.${data['reading:expand:media']}`)
: undefined
}
loading={isFetching}
/>
<MenuRow
title={t('screenTabs:me.preferences.spoilers.title')}
switchValue={data?.['reading:expand:spoilers'] || false}
switchDisabled={true}
loading={isFetching}
/>
{data?.['reading:autoplay:gifs'] !== undefined ? (
<MenuRow
title={t('screenTabs:me.preferences.autoplay_gifs.title')}
switchValue={data['reading:autoplay:gifs'] || false}
switchDisabled={true}
loading={isFetching}
/>
) : null}
</MenuContainer>
<Message ref={messageRef} />
</ScrollView>
)
}
export default TabMePreferences

View File

@ -1,11 +1,6 @@
import { MenuContainer, MenuRow } from '@components/Menu' import { MenuContainer, MenuRow } from '@components/Menu'
import { useActionSheet } from '@expo/react-native-action-sheet'
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators' import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
import { queryClient } from '@utils/queryHooks'
import { QueryKeyPreferences } from '@utils/queryHooks/preferences'
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile' import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { RefObject } from 'react' import React, { RefObject } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import FlashMessage from 'react-native-flash-message' import FlashMessage from 'react-native-flash-message'
@ -17,19 +12,11 @@ const TabMeProfileRoot: React.FC<
messageRef: RefObject<FlashMessage> messageRef: RefObject<FlashMessage>
} }
> = ({ messageRef, navigation }) => { > = ({ messageRef, navigation }) => {
const { colors } = useTheme()
const { t } = useTranslation(['common', 'screenTabs']) const { t } = useTranslation(['common', 'screenTabs'])
const { showActionSheetWithOptions } = useActionSheet()
const { data, isFetching } = useProfileQuery() const { data, isFetching } = useProfileQuery()
const { mutateAsync } = useProfileMutation() const { mutateAsync } = useProfileMutation()
const refetchPreferences = () => {
const queryKeyPreferences: QueryKeyPreferences = ['Preferences']
queryClient.refetchQueries(queryKeyPreferences)
}
return ( return (
<ScrollView> <ScrollView>
<MenuContainer> <MenuContainer>
@ -77,77 +64,6 @@ const TabMeProfileRoot: React.FC<
}} }}
/> />
</MenuContainer> </MenuContainer>
<MenuContainer>
{data?.source.privacy !== 'direct' ? (
<MenuRow
title={t('screenTabs:me.profile.root.visibility.title')}
content={
data?.source.privacy
? t(`screenTabs:me.profile.root.visibility.options.${data.source.privacy}`)
: undefined
}
loading={isFetching}
iconBack='ChevronRight'
onPress={() =>
showActionSheetWithOptions(
{
title: t('screenTabs:me.profile.root.visibility.title'),
options: [
t('screenTabs:me.profile.root.visibility.options.public'),
t('screenTabs:me.profile.root.visibility.options.unlisted'),
t('screenTabs:me.profile.root.visibility.options.private'),
t('common:buttons.cancel')
],
cancelButtonIndex: 3,
...androidActionSheetStyles(colors)
},
async buttonIndex => {
switch (buttonIndex) {
case 0:
case 1:
case 2:
const indexVisibilityMapping = ['public', 'unlisted', 'private'] as [
'public',
'unlisted',
'private'
]
if (data?.source.privacy !== indexVisibilityMapping[buttonIndex]) {
mutateAsync({
messageRef,
message: {
text: 'me.profile.root.visibility.title',
succeed: false,
failed: true
},
type: 'source[privacy]',
data: indexVisibilityMapping[buttonIndex]
}).then(() => refetchPreferences())
}
break
}
}
)
}
/>
) : null}
<MenuRow
title={t('screenTabs:me.profile.root.sensitive.title')}
switchValue={data?.source.sensitive}
switchOnValueChange={() =>
mutateAsync({
messageRef,
message: {
text: 'me.profile.root.sensitive.title',
succeed: false,
failed: true
},
type: 'source[sensitive]',
data: data?.source.sensitive === undefined ? true : !data.source.sensitive
}).then(() => refetchPreferences())
}
loading={isFetching}
/>
</MenuContainer>
<MenuContainer> <MenuContainer>
<MenuRow <MenuRow
title={t('screenTabs:me.profile.root.lock.title')} title={t('screenTabs:me.profile.root.lock.title')}

View File

@ -1,8 +1,5 @@
import { MenuContainer, MenuRow } from '@components/Menu' import { MenuContainer, MenuRow } from '@components/Menu'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import browserPackage from '@utils/helpers/browserPackage'
import { getAccountStorage, useGlobalStorage } from '@utils/storage/actions'
import * as WebBrowser from 'expo-web-browser'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -10,8 +7,6 @@ const Settings: React.FC = () => {
const { t } = useTranslation('screenTabs') const { t } = useTranslation('screenTabs')
const navigation = useNavigation<any>() const navigation = useNavigation<any>()
const [accountActive] = useGlobalStorage.string('account.active')
return ( return (
<MenuContainer> <MenuContainer>
<MenuRow <MenuRow
@ -20,24 +15,6 @@ const Settings: React.FC = () => {
title={t('me.stacks.settings.name')} title={t('me.stacks.settings.name')}
onPress={() => navigation.navigate('Tab-Me-Settings')} onPress={() => navigation.navigate('Tab-Me-Settings')}
/> />
{accountActive ? (
<MenuRow
iconFront='Sliders'
iconBack='ExternalLink'
title={t('me.stacks.webSettings.name')}
onPress={async () =>
WebBrowser.openAuthSessionAsync(
`https://${getAccountStorage.string('auth.domain')}/settings/preferences`,
'tooot://tooot',
{
...(await browserPackage()),
dismissButtonStyle: 'done',
readerMode: false
}
)
}
/>
) : null}
</MenuContainer> </MenuContainer>
) )
} }

View File

@ -12,6 +12,7 @@ import TabMeList from './List'
import TabMeListAccounts from './List/Accounts' import TabMeListAccounts from './List/Accounts'
import TabMeListEdit from './List/Edit' import TabMeListEdit from './List/Edit'
import TabMeListList from './List/List' import TabMeListList from './List/List'
import TabMePreferences from './Preferences'
import TabMeProfile from './Profile' import TabMeProfile from './Profile'
import TabMePush from './Push' import TabMePush from './Push'
import TabMeRoot from './Root' import TabMeRoot from './Root'
@ -100,13 +101,19 @@ const TabMe: React.FC = () => {
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} /> headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
})} })}
/> />
<Stack.Screen
name='Tab-Me-Preferences'
component={TabMePreferences}
options={({ navigation }: any) => ({
presentation: 'modal',
title: t('me.stacks.preferences.name'),
headerLeft: () => <HeaderLeft content='ChevronDown' onPress={() => navigation.pop(1)} />
})}
/>
<Stack.Screen <Stack.Screen
name='Tab-Me-Profile' name='Tab-Me-Profile'
component={TabMeProfile} component={TabMeProfile}
options={{ options={{ headerShown: false, presentation: 'modal' }}
headerShown: false,
presentation: 'modal'
}}
/> />
<Stack.Screen <Stack.Screen
name='Tab-Me-Push' name='Tab-Me-Push'

View File

@ -42,6 +42,14 @@ const AccountInformationActions: React.FC = () => {
content={t('me.stacks.profile.name')} content={t('me.stacks.profile.name')}
onPress={() => navigation.navigate('Tab-Me-Profile')} onPress={() => navigation.navigate('Tab-Me-Profile')}
/> />
<Button
round
type='icon'
disabled={account === undefined}
content='Settings'
style={{ marginLeft: StyleConstants.Spacing.S }}
onPress={() => navigation.navigate('Tab-Me-Preferences')}
/>
</View> </View>
) )
} }
@ -61,7 +69,7 @@ const AccountInformationActions: React.FC = () => {
round round
type='icon' type='icon'
content='AtSign' content='AtSign'
style={{ marginRight: StyleConstants.Spacing.S }} style={{ flex: 1, marginRight: StyleConstants.Spacing.S }}
onPress={() => {}} onPress={() => {}}
/> />
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
@ -119,7 +127,7 @@ const styles = StyleSheet.create({
base: { base: {
alignSelf: 'flex-end', alignSelf: 'flex-end',
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'stretch'
} }
}) })

View File

@ -157,6 +157,7 @@ export type TabMeStackParamList = {
key: string // To update title after successful mutation key: string // To update title after successful mutation
} }
'Tab-Me-List-List': undefined 'Tab-Me-List-List': undefined
'Tab-Me-Preferences': undefined
'Tab-Me-Profile': undefined 'Tab-Me-Profile': undefined
'Tab-Me-Push': undefined 'Tab-Me-Push': undefined
'Tab-Me-Settings': undefined 'Tab-Me-Settings': undefined

View File

@ -19,7 +19,7 @@ const usePreferencesQuery = (params?: {
...params?.options, ...params?.options,
staleTime: Infinity, staleTime: Infinity,
cacheTime: Infinity, cacheTime: Infinity,
initialData: getAccountStorage.object('preferences'), placeholderData: getAccountStorage.object('preferences'),
onSuccess: data => setAccountStorage([{ key: 'preferences', value: data }]) onSuccess: data => setAccountStorage([{ key: 'preferences', value: data }])
}) })
} }