1
0
mirror of https://github.com/tooot-app/app synced 2025-04-15 18:57:39 +02:00

Merge branch 'main' into candidate

This commit is contained in:
Zhiyuan Zheng 2021-05-26 23:30:29 +02:00
commit 98cc328570
8 changed files with 162 additions and 264 deletions

View File

@ -80,7 +80,7 @@ const displayMessage = ({
}) })
} else { } else {
showMessage({ showMessage({
duration: type === 'error' ? 5000 : duration === 'short' ? 1500 : 3000, duration: type === 'error' ? 3500 : duration === 'short' ? 1500 : 2500,
autoHide, autoHide,
message, message,
description, description,

View File

@ -1,6 +1,5 @@
import { HeaderLeft, HeaderRight } from '@components/Header' import { HeaderLeft, HeaderRight } from '@components/Header'
import Input from '@components/Input' import Input from '@components/Input'
import { displayMessage } from '@components/Message'
import { StackScreenProps } from '@react-navigation/stack' import { StackScreenProps } from '@react-navigation/stack'
import { useProfileMutation } from '@utils/queryHooks/profile' import { useProfileMutation } from '@utils/queryHooks/profile'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
@ -79,33 +78,20 @@ const TabMeProfileFields: React.FC<StackScreenProps<
content='Save' content='Save'
onPress={async () => { onPress={async () => {
mutateAsync({ mutateAsync({
mode,
messageRef,
message: {
text: 'me.profile.root.note.title',
succeed: true,
failed: true
},
type: 'fields_attributes', type: 'fields_attributes',
data: newFields data: newFields
.filter(field => field.name.length && field.value.length) .filter(field => field.name.length && field.value.length)
.map(field => ({ name: field.name, value: field.value })) .map(field => ({ name: field.name, value: field.value }))
}).then(() => {
navigation.navigate('Tab-Me-Profile-Root')
}) })
.then(() => {
navigation.navigate('Tab-Me-Profile-Root')
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.succeed', {
type: t('me.profile.root.note.title')
}),
mode,
type: 'success'
})
})
.catch(err => {
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.failed', {
type: t('me.profile.root.note.title')
}),
...(err && { description: err }),
mode,
type: 'error'
})
})
}} }}
/> />
) )

View File

@ -1,6 +1,5 @@
import { HeaderLeft, HeaderRight } from '@components/Header' import { HeaderLeft, HeaderRight } from '@components/Header'
import Input from '@components/Input' import Input from '@components/Input'
import { displayMessage } from '@components/Message'
import { StackScreenProps } from '@react-navigation/stack' import { StackScreenProps } from '@react-navigation/stack'
import { useProfileMutation } from '@utils/queryHooks/profile' import { useProfileMutation } from '@utils/queryHooks/profile'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
@ -65,29 +64,19 @@ const TabMeProfileName: React.FC<StackScreenProps<
loading={status === 'loading'} loading={status === 'loading'}
content='Save' content='Save'
onPress={async () => { onPress={async () => {
mutateAsync({ type: 'display_name', data: displayName }) mutateAsync({
.then(() => { mode,
navigation.navigate('Tab-Me-Profile-Root') messageRef,
displayMessage({ message: {
ref: messageRef, text: 'me.profile.root.name.title',
message: t('me.profile.feedback.succeed', { succeed: true,
type: t('me.profile.root.name.title') failed: true
}), },
mode, type: 'display_name',
type: 'success' data: displayName
}) }).then(() => {
}) navigation.navigate('Tab-Me-Profile-Root')
.catch(err => { })
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.failed', {
type: t('me.profile.root.name.title')
}),
...(err && { description: err }),
mode,
type: 'error'
})
})
}} }}
/> />
) )

View File

@ -1,6 +1,5 @@
import { HeaderLeft, HeaderRight } from '@components/Header' import { HeaderLeft, HeaderRight } from '@components/Header'
import Input from '@components/Input' import Input from '@components/Input'
import { displayMessage } from '@components/Message'
import { StackScreenProps } from '@react-navigation/stack' import { StackScreenProps } from '@react-navigation/stack'
import { useProfileMutation } from '@utils/queryHooks/profile' import { useProfileMutation } from '@utils/queryHooks/profile'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
@ -65,29 +64,19 @@ const TabMeProfileNote: React.FC<StackScreenProps<
loading={status === 'loading'} loading={status === 'loading'}
content='Save' content='Save'
onPress={async () => { onPress={async () => {
mutateAsync({ type: 'note', data: newNote }) mutateAsync({
.then(() => { mode,
navigation.navigate('Tab-Me-Profile-Root') messageRef,
displayMessage({ message: {
ref: messageRef, text: 'me.profile.root.note.title',
message: t('me.profile.feedback.succeed', { succeed: true,
type: t('me.profile.root.note.title') failed: true
}), },
mode, type: 'note',
type: 'success' data: newNote
}) }).then(() => {
}) navigation.navigate('Tab-Me-Profile-Root')
.catch(err => { })
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.failed', {
type: t('me.profile.root.note.title')
}),
...(err && { description: err }),
mode,
type: 'error'
})
})
}} }}
/> />
) )

View File

@ -1,6 +1,5 @@
import analytics from '@components/analytics' import analytics from '@components/analytics'
import { MenuContainer, MenuRow } from '@components/Menu' import { MenuContainer, MenuRow } from '@components/Menu'
import { displayMessage } from '@components/Message'
import { useActionSheet } from '@expo/react-native-action-sheet' import { useActionSheet } from '@expo/react-native-action-sheet'
import { StackScreenProps } from '@react-navigation/stack' import { StackScreenProps } from '@react-navigation/stack'
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile' import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
@ -41,67 +40,32 @@ const TabMeProfileRoot: React.FC<StackScreenProps<
async buttonIndex => { async buttonIndex => {
switch (buttonIndex) { switch (buttonIndex) {
case 0: case 0:
analytics('me_profile_visibility', {
current: t(
`me.profile.root.visibility.options.${data?.source.privacy}`
),
new: 'public'
})
mutateAsync({ type: 'source[privacy]', data: 'public' })
.then(() => dispatch(updateAccountPreferences()))
.catch(err =>
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.failed', {
type: t('me.profile.root.visibility.title')
}),
...(err && { description: err }),
mode,
type: 'error'
})
)
break
case 1: case 1:
analytics('me_profile_visibility', {
current: t(
`me.profile.root.visibility.options.${data?.source.privacy}`
),
new: 'unlisted'
})
mutateAsync({ type: 'source[privacy]', data: 'unlisted' })
.then(() => dispatch(updateAccountPreferences()))
.catch(err =>
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.failed', {
type: t('me.profile.root.visibility.title')
}),
...(err && { description: err }),
mode,
type: 'error'
})
)
break
case 2: case 2:
analytics('me_profile_visibility', { const indexVisibilityMapping = [
current: t( 'public',
`me.profile.root.visibility.options.${data?.source.privacy}` 'unlisted',
), 'private'
new: 'unlisted' ] as ['public', 'unlisted', 'private']
}) if (data?.source.privacy !== indexVisibilityMapping[buttonIndex]) {
mutateAsync({ type: 'source[privacy]', data: 'private' }) analytics('me_profile_visibility', {
.then(() => dispatch(updateAccountPreferences())) current: t(
.catch(err => `me.profile.root.visibility.options.${data?.source.privacy}`
displayMessage({ ),
ref: messageRef, new: indexVisibilityMapping[buttonIndex]
message: t('me.profile.feedback.failed', { })
type: t('me.profile.root.visibility.title') mutateAsync({
}), mode,
...(err && { description: err }), messageRef,
mode, message: {
type: 'error' text: 'me.profile.root.visibility.title',
}) succeed: false,
) failed: true
},
type: 'source[privacy]',
data: indexVisibilityMapping[buttonIndex]
}).then(() => dispatch(updateAccountPreferences()))
}
break break
} }
} }
@ -109,118 +73,57 @@ const TabMeProfileRoot: React.FC<StackScreenProps<
}, [data?.source.privacy]) }, [data?.source.privacy])
const onPressSensitive = useCallback(() => { const onPressSensitive = useCallback(() => {
if (data?.source.sensitive === undefined) { analytics('me_profile_sensitive', {
analytics('me_profile_sensitive', { current: data?.source.sensitive,
current: undefined, new: data?.source.sensitive === undefined ? true : !data.source.sensitive
new: true })
}) mutateAsync({
mutateAsync({ type: 'source[sensitive]', data: true }) mode,
.then(() => dispatch(updateAccountPreferences())) messageRef,
.catch(err => message: {
displayMessage({ text: 'me.profile.root.sensitive.title',
ref: messageRef, succeed: false,
message: t('me.profile.feedback.failed', { failed: true
type: t('me.profile.root.sensitive.title') },
}), type: 'source[sensitive]',
...(err && { description: err }), data: data?.source.sensitive === undefined ? true : !data.source.sensitive
mode, }).then(() => dispatch(updateAccountPreferences()))
type: 'error'
})
)
} else {
analytics('me_profile_sensitive', {
current: data.source.sensitive,
new: !data.source.sensitive
})
mutateAsync({
type: 'source[sensitive]',
data: !data.source.sensitive
})
.then(() => dispatch(updateAccountPreferences()))
.catch(err =>
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.failed', {
type: t('me.profile.root.sensitive.title')
}),
...(err && { description: err }),
mode,
type: 'error'
})
)
}
}, [data?.source.sensitive]) }, [data?.source.sensitive])
const onPressLock = useCallback(() => { const onPressLock = useCallback(() => {
if (data?.locked === undefined) { analytics('me_profile_lock', {
analytics('me_profile_lock', { current: data?.locked,
current: undefined, new: data?.locked === undefined ? true : !data.locked
new: true })
}) mutateAsync({
mutateAsync({ type: 'locked', data: true }).catch(err => mode,
displayMessage({ messageRef,
ref: messageRef, message: {
message: t('me.profile.feedback.failed', { text: 'me.profile.root.lock.title',
type: t('me.profile.root.lock.title') succeed: false,
}), failed: true
...(err && { description: err }), },
mode, type: 'locked',
type: 'error' data: data?.locked === undefined ? true : !data.locked
}) })
)
} else {
analytics('me_profile_lock', {
current: data.locked,
new: !data.locked
})
mutateAsync({ type: 'locked', data: !data.locked }).catch(err =>
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.failed', {
type: t('me.profile.root.lock.title')
}),
...(err && { description: err }),
mode,
type: 'error'
})
)
}
}, [data?.locked]) }, [data?.locked])
const onPressBot = useCallback(() => { const onPressBot = useCallback(() => {
if (data?.bot === undefined) { analytics('me_profile_bot', {
analytics('me_profile_bot', { current: data?.bot,
current: undefined, new: data?.bot === undefined ? true : !data.bot
new: true })
}) mutateAsync({
mutateAsync({ type: 'bot', data: true }).catch(err => mode,
displayMessage({ messageRef,
ref: messageRef, message: {
message: t('me.profile.feedback.failed', { text: 'me.profile.root.bot.title',
type: t('me.profile.root.bot.title') succeed: false,
}), failed: true
...(err && { description: err }), },
mode, type: 'bot',
type: 'error' data: data?.bot === undefined ? true : !data.bot
}) })
)
} else {
analytics('me_profile_bot', {
current: data.bot,
new: !data.bot
})
mutateAsync({ type: 'bot', data: !data?.bot }).catch(err =>
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.failed', {
type: t('me.profile.root.bot.title')
}),
...(err && { description: err }),
mode,
type: 'error'
})
)
}
}, [data?.bot]) }, [data?.bot])
return ( return (

View File

@ -1,6 +1,5 @@
import mediaSelector from '@components/mediaSelector' import mediaSelector from '@components/mediaSelector'
import { MenuRow } from '@components/Menu' import { MenuRow } from '@components/Menu'
import { displayMessage } from '@components/Message'
import { useActionSheet } from '@expo/react-native-action-sheet' import { useActionSheet } from '@expo/react-native-action-sheet'
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile' import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
@ -35,29 +34,17 @@ const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
mediaTypes: ImagePicker.MediaTypeOptions.Images, mediaTypes: ImagePicker.MediaTypeOptions.Images,
resize: { width: 400, height: 400 } resize: { width: 400, height: 400 }
}) })
mutation mutation.mutate({
.mutateAsync({ type, data: image.uri }) mode,
.then(() => messageRef,
displayMessage({ message: {
ref: messageRef, text: `me.profile.root.${type}.title`,
message: t('me.profile.feedback.succeed', { succeed: true,
type: t(`me.profile.root.${type}.title`) failed: true
}), },
mode, type,
type: 'success' data: image.uri
}) })
)
.catch(err =>
displayMessage({
ref: messageRef,
message: t('me.profile.feedback.failed', {
type: t(`me.profile.root.${type}.title`)
}),
...(err && { description: err }),
mode,
type: 'error'
})
)
}} }}
/> />
) )

View File

@ -1,6 +1,11 @@
import apiInstance from '@api/instance' import apiInstance from '@api/instance'
import haptics from '@components/haptics'
import { displayMessage } from '@components/Message'
import queryClient from '@helpers/queryClient' import queryClient from '@helpers/queryClient'
import { AxiosError } from 'axios' import { AxiosError } from 'axios'
import i18next from 'i18next'
import { RefObject } from 'react'
import FlashMessage from 'react-native-flash-message'
import { useMutation, useQuery, UseQueryOptions } from 'react-query' import { useMutation, useQuery, UseQueryOptions } from 'react-query'
type AccountWithSource = Mastodon.Account & type AccountWithSource = Mastodon.Account &
@ -24,7 +29,7 @@ const useProfileQuery = <TData = AccountWithSource>({
return useQuery(queryKey, queryFunction, options) return useQuery(queryKey, queryFunction, options)
} }
type MutationVarsProfile = type MutationVarsProfileBase =
| { type: 'display_name'; data: string } | { type: 'display_name'; data: string }
| { type: 'note'; data: string } | { type: 'note'; data: string }
| { type: 'avatar'; data: string } | { type: 'avatar'; data: string }
@ -44,6 +49,16 @@ type MutationVarsProfile =
data: { name: string; value: string }[] data: { name: string; value: string }[]
} }
type MutationVarsProfile = MutationVarsProfileBase & {
mode: 'light' | 'dark'
messageRef: RefObject<FlashMessage>
message: {
text: string
succeed: boolean
failed: boolean
}
}
const mutationFunction = async ({ type, data }: MutationVarsProfile) => { const mutationFunction = async ({ type, data }: MutationVarsProfile) => {
const formData = new FormData() const formData = new FormData()
if (type === 'fields_attributes') { if (type === 'fields_attributes') {
@ -107,8 +122,35 @@ const useProfileMutation = () => {
return oldData return oldData
}, },
onError: (_, variables, context) => { onError: (err, variables, context) => {
queryClient.setQueryData(queryKey, context) queryClient.setQueryData(queryKey, context)
haptics('Error')
if (variables.message.failed) {
displayMessage({
ref: variables.messageRef,
message: i18next.t('screenTabs:me.profile.feedback.failed', {
type: i18next.t(`screenTabs:${variables.message.text}`)
}),
...(err && { description: err.message }),
mode: variables.mode,
type: 'error'
})
}
},
onSuccess: (_, variables) => {
if (variables.message.succeed) {
haptics('Success')
displayMessage({
ref: variables.messageRef,
message: i18next.t('screenTabs:me.profile.feedback.succeed', {
type: i18next.t(`screenTabs:${variables.message.text}`)
}),
mode: variables.mode,
type: 'success'
})
} else {
haptics('Light')
}
}, },
onSettled: () => { onSettled: () => {
queryClient.invalidateQueries(queryKey) queryClient.invalidateQueries(queryKey)

View File

@ -1,4 +1,5 @@
import apiGeneral from '@api/general' import apiGeneral from '@api/general'
import haptics from '@components/haptics'
import { AxiosError } from 'axios' import { AxiosError } from 'axios'
import { Buffer } from 'buffer' import { Buffer } from 'buffer'
import Constants from 'expo-constants' import Constants from 'expo-constants'
@ -47,6 +48,7 @@ const queryFunction = async ({ queryKey }: { queryKey: QueryKeyTranslate }) => {
url: `v1/translate/${uriEncoded}/${target}`, url: `v1/translate/${uriEncoded}/${target}`,
headers: { key, original } headers: { key, original }
}) })
haptics('Light')
return res.body return res.body
} }