Optimisations and haptics

This commit is contained in:
Zhiyuan Zheng 2021-05-26 23:30:15 +02:00
parent 926bce8f58
commit 8a56e3a4f0
8 changed files with 162 additions and 264 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,11 @@
import apiInstance from '@api/instance'
import haptics from '@components/haptics'
import { displayMessage } from '@components/Message'
import queryClient from '@helpers/queryClient'
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'
type AccountWithSource = Mastodon.Account &
@ -24,7 +29,7 @@ const useProfileQuery = <TData = AccountWithSource>({
return useQuery(queryKey, queryFunction, options)
}
type MutationVarsProfile =
type MutationVarsProfileBase =
| { type: 'display_name'; data: string }
| { type: 'note'; data: string }
| { type: 'avatar'; data: string }
@ -44,6 +49,16 @@ type MutationVarsProfile =
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 formData = new FormData()
if (type === 'fields_attributes') {
@ -107,8 +122,35 @@ const useProfileMutation = () => {
return oldData
},
onError: (_, variables, context) => {
onError: (err, variables, 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: () => {
queryClient.invalidateQueries(queryKey)

View File

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