diff --git a/src/components/Message.tsx b/src/components/Message.tsx index 7ba3377f..db350f64 100644 --- a/src/components/Message.tsx +++ b/src/components/Message.tsx @@ -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, diff --git a/src/screens/Tabs/Me/Profile/Fields.tsx b/src/screens/Tabs/Me/Profile/Fields.tsx index f5e904e9..fc525ca0 100644 --- a/src/screens/Tabs/Me/Profile/Fields.tsx +++ b/src/screens/Tabs/Me/Profile/Fields.tsx @@ -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 { 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' - }) - }) }} /> ) diff --git a/src/screens/Tabs/Me/Profile/Name.tsx b/src/screens/Tabs/Me/Profile/Name.tsx index c0b96765..f81c877d 100644 --- a/src/screens/Tabs/Me/Profile/Name.tsx +++ b/src/screens/Tabs/Me/Profile/Name.tsx @@ -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 { - 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') + }) }} /> ) diff --git a/src/screens/Tabs/Me/Profile/Note.tsx b/src/screens/Tabs/Me/Profile/Note.tsx index 9ae06086..46df69b9 100644 --- a/src/screens/Tabs/Me/Profile/Note.tsx +++ b/src/screens/Tabs/Me/Profile/Note.tsx @@ -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 { - 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') + }) }} /> ) diff --git a/src/screens/Tabs/Me/Profile/Root.tsx b/src/screens/Tabs/Me/Profile/Root.tsx index a4caa510..99f3fafc 100644 --- a/src/screens/Tabs/Me/Profile/Root.tsx +++ b/src/screens/Tabs/Me/Profile/Root.tsx @@ -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 { 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 { - 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 ( diff --git a/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx b/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx index 6d9110b2..e81d1acb 100644 --- a/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx +++ b/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx @@ -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 = ({ 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 + }) }} /> ) diff --git a/src/utils/queryHooks/profile.ts b/src/utils/queryHooks/profile.ts index b68fcdb2..a02bb57d 100644 --- a/src/utils/queryHooks/profile.ts +++ b/src/utils/queryHooks/profile.ts @@ -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 = ({ 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 + 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) diff --git a/src/utils/queryHooks/translate.ts b/src/utils/queryHooks/translate.ts index beae5055..3142f64d 100644 --- a/src/utils/queryHooks/translate.ts +++ b/src/utils/queryHooks/translate.ts @@ -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 }