diff --git a/src/Screens.tsx b/src/Screens.tsx index c88da00b..f5686550 100644 --- a/src/Screens.tsx +++ b/src/Screens.tsx @@ -39,7 +39,7 @@ export interface Props { } const Screens: React.FC = ({ localCorrupt }) => { - const { i18n, t } = useTranslation('screens') + const { t } = useTranslation('screens') const dispatch = useAppDispatch() const instanceActive = useSelector(getInstanceActive) const { colors, theme } = useTheme() @@ -70,8 +70,7 @@ const Screens: React.FC = ({ localCorrupt }) => { displayMessage({ message: t('localCorrupt.message'), description: localCorrupt.length ? localCorrupt : undefined, - type: 'error', - theme + type: 'danger' }) // @ts-ignore navigationRef.navigate('Screen-Tabs', { @@ -183,8 +182,7 @@ const Screens: React.FC = ({ localCorrupt }) => { message: t('shareError.imageNotSupported', { type: mime.split('/')[1] }), - type: 'error', - theme + type: 'danger' }) return } @@ -196,8 +194,7 @@ const Screens: React.FC = ({ localCorrupt }) => { message: t('shareError.videoNotSupported', { type: mime.split('/')[1] }), - type: 'error', - theme + type: 'danger' }) return } diff --git a/src/components/Message.tsx b/src/components/Message.tsx index 85e6e62c..9b8343fa 100644 --- a/src/components/Message.tsx +++ b/src/components/Message.tsx @@ -1,10 +1,9 @@ import Icon from '@components/Icon' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' -import { getColors, Theme } from '@utils/styles/themes' import React, { RefObject } from 'react' import { AccessibilityInfo } from 'react-native' -import FlashMessage, { hideMessage, showMessage } from 'react-native-flash-message' +import FlashMessage, { MessageType, showMessage } from 'react-native-flash-message' import { useSafeAreaInsets } from 'react-native-safe-area-context' import haptics from './haptics' @@ -15,107 +14,80 @@ const displayMessage = ({ message, description, onPress, - theme, type -}: - | { - ref?: RefObject - duration?: 'short' | 'long' - autoHide?: boolean - message: string - description?: string - onPress?: () => void - theme?: undefined - type?: undefined - } - | { - ref?: RefObject - duration?: 'short' | 'long' - autoHide?: boolean - message: string - description?: string - onPress?: () => void - theme: Theme - type: 'success' | 'error' | 'warning' - }) => { +}: { + ref?: RefObject + duration?: 'short' | 'long' + autoHide?: boolean + message: string + description?: string + onPress?: () => void + type?: MessageType +}) => { AccessibilityInfo.announceForAccessibility(message + '.' + description) - enum iconMapping { - success = 'CheckCircle', - error = 'XCircle', - warning = 'AlertCircle' - } - enum colorMapping { - success = 'blue', - error = 'red', - warning = 'secondary' - } - - if (type && type === 'error') { + if (type && type === 'danger') { haptics('Error') } if (ref) { ref.current?.showMessage({ - duration: type === 'error' ? 8000 : duration === 'short' ? 3000 : 5000, + duration: type === 'danger' ? 8000 : duration === 'short' ? 3000 : 5000, autoHide, message, description, onPress, - ...(theme && - type && { - renderFlashMessageIcon: () => { - return ( - - ) - } - }) + type }) } else { showMessage({ - duration: type === 'error' ? 8000 : duration === 'short' ? 3000 : 5000, + duration: type === 'danger' ? 8000 : duration === 'short' ? 3000 : 5000, autoHide, message, description, onPress, - ...(theme && - type && { - renderFlashMessageIcon: () => { - return ( - - ) - } - }) + type }) } } -const removeMessage = () => { - // if (ref) { - // ref.current?.hideMessage() - // } else { - hideMessage() - // } -} - const Message = React.forwardRef((_, ref) => { const { colors, theme } = useTheme() const insets = useSafeAreaInsets() + enum iconMapping { + success = 'CheckCircle', + danger = 'XCircle', + warning = 'AlertCircle', + none = '', + default = '', + info = '', + auto = '' + } + enum colorMapping { + success = 'blue', + danger = 'red', + warning = 'secondary', + none = 'secondary', + default = 'secondary', + info = 'secondary', + auto = 'secondary' + } + return ( { + return typeof type === 'string' && ['success', 'danger', 'warning'].includes(type) ? ( + + ) : null + }} position='top' floating style={{ @@ -142,4 +114,4 @@ const Message = React.forwardRef((_, ref) => { ) }) -export { Message, displayMessage, removeMessage } +export { Message, displayMessage } diff --git a/src/components/contextMenu/account.ts b/src/components/contextMenu/account.ts index 34b52dce..44526902 100644 --- a/src/components/contextMenu/account.ts +++ b/src/components/contextMenu/account.ts @@ -14,7 +14,6 @@ import { useTimelineMutation } from '@utils/queryHooks/timeline' import { getInstanceAccount } from '@utils/slices/instancesSlice' -import { useTheme } from '@utils/styles/ThemeManager' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { Platform } from 'react-native' @@ -38,7 +37,6 @@ const menuAccount = ({ const navigation = useNavigation>() - const { theme } = useTheme() const { t } = useTranslation('componentContextMenu') const menus: ContextMenu[][] = [[]] @@ -60,7 +58,6 @@ const menuAccount = ({ queryClient.refetchQueries(['Relationship', { id: account.id }]) const theParams = params as MutationVarsTimelineUpdateAccountProperty displayMessage({ - theme, type: 'success', message: t('common:message.success.message', { function: t(`account.${theParams.payload.property}.action`, { @@ -74,8 +71,7 @@ const menuAccount = ({ onError: (err: any, params) => { const theParams = params as MutationVarsTimelineUpdateAccountProperty displayMessage({ - theme, - type: 'error', + type: 'danger', message: t('common:message.error.message', { function: t(`account.${theParams.payload.property}.action`, { ...(theParams.payload.property !== 'reports' && { @@ -109,8 +105,7 @@ const menuAccount = ({ }, onError: (err: any, { payload: { action } }) => { displayMessage({ - theme, - type: 'error', + type: 'danger', message: t('common:message.error.message', { function: t(`${action}.function`) }), diff --git a/src/components/contextMenu/instance.ts b/src/components/contextMenu/instance.ts index b152779a..4dca7faf 100644 --- a/src/components/contextMenu/instance.ts +++ b/src/components/contextMenu/instance.ts @@ -1,7 +1,6 @@ import { displayMessage } from '@components/Message' import { QueryKeyTimeline, useTimelineMutation } from '@utils/queryHooks/timeline' import { getInstanceUrl } from '@utils/slices/instancesSlice' -import { useTheme } from '@utils/styles/ThemeManager' import { useTranslation } from 'react-i18next' import { Alert } from 'react-native' import { useQueryClient } from 'react-query' @@ -18,14 +17,12 @@ const menuInstance = ({ }): ContextMenu[][] => { if (!status || !queryKey) return [] - const { theme } = useTheme() const { t } = useTranslation('componentContextMenu') const queryClient = useQueryClient() const mutation = useTimelineMutation({ onSettled: () => { displayMessage({ - theme, type: 'success', message: t('common:message.success.message', { function: t(`instance.block.action`, { instance }) diff --git a/src/components/contextMenu/share.ts b/src/components/contextMenu/share.ts index 961bc091..8d907040 100644 --- a/src/components/contextMenu/share.ts +++ b/src/components/contextMenu/share.ts @@ -1,6 +1,5 @@ import { displayMessage } from '@components/Message' import Clipboard from '@react-native-clipboard/clipboard' -import { useTheme } from '@utils/styles/ThemeManager' import { useTranslation } from 'react-i18next' import { Platform, Share } from 'react-native' @@ -22,7 +21,6 @@ const menuShare = ( ): ContextMenu[][] => { if (params.type === 'status' && params.visibility === 'direct') return [] - const { theme } = useTheme() const { t } = useTranslation('componentContextMenu') const menus: ContextMenu[][] = [[]] @@ -56,11 +54,7 @@ const menuShare = ( item: { onSelect: () => { Clipboard.setString(params.copiableContent?.current.content || '') - displayMessage({ - theme, - type: 'success', - message: t(`copy.succeed`) - }) + displayMessage({ type: 'success', message: t(`copy.succeed`) }) }, disabled: false, destructive: false, diff --git a/src/i18n/en/screens/tabs.json b/src/i18n/en/screens/tabs.json index 8f49b544..b286a094 100644 --- a/src/i18n/en/screens/tabs.json +++ b/src/i18n/en/screens/tabs.json @@ -178,6 +178,10 @@ "direct": "Enable push notification", "settings": "Enable in settings" }, + "missingServerKey": { + "message": "Server misconfigured for push", + "description": "Please contact your server admin to configure push support" + }, "global": { "heading": "Enable for {{acct}}", "description": "Messages are routed through tooot's server" diff --git a/src/screens/Actions/Account.tsx b/src/screens/Actions/Account.tsx deleted file mode 100644 index 8071a0ed..00000000 --- a/src/screens/Actions/Account.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu' -import { displayMessage } from '@components/Message' -import { - MutationVarsTimelineUpdateAccountProperty, - QueryKeyTimeline, - useTimelineMutation -} from '@utils/queryHooks/timeline' -import { useTheme } from '@utils/styles/ThemeManager' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { useQueryClient } from 'react-query' - -export interface Props { - queryKey?: QueryKeyTimeline - rootQueryKey?: QueryKeyTimeline - account: Mastodon.Account - dismiss: () => void -} - -const ActionsAccount: React.FC = ({ - queryKey, - rootQueryKey, - account, - dismiss -}) => { - const { theme } = useTheme() - const { t } = useTranslation('componentTimeline') - - const queryClient = useQueryClient() - const mutation = useTimelineMutation({ - onSuccess: (_, params) => { - const theParams = params as MutationVarsTimelineUpdateAccountProperty - displayMessage({ - theme, - type: 'success', - message: t('common:message.success.message', { - function: t( - `shared.header.actions.account.${theParams.payload.property}.function`, - { - acct: account.acct - } - ) - }) - }) - }, - onError: (err: any, params) => { - const theParams = params as MutationVarsTimelineUpdateAccountProperty - displayMessage({ - theme, - type: 'error', - message: t('common:message.error.message', { - function: t( - `shared.header.actions.account.${theParams.payload.property}.function` - ) - }), - ...(err.status && - typeof err.status === 'number' && - err.data && - err.data.error && - typeof err.data.error === 'string' && { - description: err.data.error - }) - }) - }, - onSettled: () => { - queryKey && queryClient.invalidateQueries(queryKey) - rootQueryKey && queryClient.invalidateQueries(rootQueryKey) - } - }) - - return ( - - - { - dismiss() - mutation.mutate({ - type: 'updateAccountProperty', - queryKey, - id: account.id, - payload: { property: 'mute' } - }) - }} - iconFront='EyeOff' - title={t('shared.header.actions.account.mute.button', { - acct: account.acct - })} - /> - { - dismiss() - mutation.mutate({ - type: 'updateAccountProperty', - queryKey, - id: account.id, - payload: { property: 'block' } - }) - }} - iconFront='XCircle' - title={t('shared.header.actions.account.block.button', { - acct: account.acct - })} - /> - { - dismiss() - mutation.mutate({ - type: 'updateAccountProperty', - queryKey, - id: account.id, - payload: { property: 'reports' } - }) - }} - iconFront='Flag' - title={t('shared.header.actions.account.reports.button', { - acct: account.acct - })} - /> - - ) -} - -export default ActionsAccount diff --git a/src/screens/Actions/Domain.tsx b/src/screens/Actions/Domain.tsx deleted file mode 100644 index 009987e0..00000000 --- a/src/screens/Actions/Domain.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import MenuContainer from '@components/Menu/Container' -import MenuHeader from '@components/Menu/Header' -import MenuRow from '@components/Menu/Row' -import { displayMessage } from '@components/Message' -import { QueryKeyTimeline, useTimelineMutation } from '@utils/queryHooks/timeline' -import { useTheme } from '@utils/styles/ThemeManager' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { Alert } from 'react-native' -import { useQueryClient } from 'react-query' - -export interface Props { - queryKey: QueryKeyTimeline - rootQueryKey?: QueryKeyTimeline - domain: string - dismiss: () => void -} - -const ActionsDomain: React.FC = ({ queryKey, rootQueryKey, domain, dismiss }) => { - const { theme } = useTheme() - const { t } = useTranslation('componentTimeline') - const queryClient = useQueryClient() - const mutation = useTimelineMutation({ - onSettled: () => { - displayMessage({ - theme, - type: 'success', - message: t('common:message.success.message', { - function: t(`shared.header.actions.domain.block.function`) - }) - }) - queryClient.invalidateQueries(queryKey) - rootQueryKey && queryClient.invalidateQueries(rootQueryKey) - } - }) - - return ( - - - - Alert.alert( - t('shared.header.actions.domain.alert.title', { domain }), - t('shared.header.actions.domain.alert.message'), - [ - { - text: t('shared.header.actions.domain.alert.buttons.confirm'), - style: 'destructive', - onPress: () => { - dismiss() - mutation.mutate({ - type: 'domainBlock', - queryKey, - domain: domain - }) - } - }, - { - text: t('shared.header.actions.domain.alert.buttons.cancel'), - style: 'default' - } - ] - ) - } - iconFront='CloudOff' - title={t(`shared.header.actions.domain.block.button`, { - domain - })} - /> - - ) -} - -export default ActionsDomain diff --git a/src/screens/Actions/Share.tsx b/src/screens/Actions/Share.tsx deleted file mode 100644 index 6a8f7308..00000000 --- a/src/screens/Actions/Share.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import MenuContainer from '@components/Menu/Container' -import MenuHeader from '@components/Menu/Header' -import MenuRow from '@components/Menu/Row' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { Platform, Share } from 'react-native' - -export interface Props { - type: 'status' | 'account' - url: string - dismiss: () => void -} - -const ActionsShare: React.FC = ({ type, url, dismiss }) => { - const { t } = useTranslation('componentTimeline') - - return ( - - - { - switch (Platform.OS) { - case 'ios': - await Share.share({ - url - }) - break - case 'android': - await Share.share({ - message: url - }) - break - } - dismiss() - }} - /> - - ) -} - -export default ActionsShare diff --git a/src/screens/Actions/Status.tsx b/src/screens/Actions/Status.tsx deleted file mode 100644 index 751a9c6e..00000000 --- a/src/screens/Actions/Status.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import React from 'react' -import { useTranslation } from 'react-i18next' -import { Alert } from 'react-native' -import { useQueryClient } from 'react-query' -import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu' -import { - MutationVarsTimelineUpdateStatusProperty, - QueryKeyTimeline, - useTimelineMutation -} from '@utils/queryHooks/timeline' -import { displayMessage } from '@components/Message' -import { useTheme } from '@utils/styles/ThemeManager' -import apiInstance from '@api/instance' -import { NativeStackNavigationProp } from '@react-navigation/native-stack' -import { RootStackParamList } from '@utils/navigation/navigators' -import { useSelector } from 'react-redux' -import { checkInstanceFeature } from '@utils/slices/instancesSlice' - -export interface Props { - navigation: NativeStackNavigationProp - queryKey: QueryKeyTimeline - rootQueryKey?: QueryKeyTimeline - status: Mastodon.Status - dismiss: () => void -} - -const ActionsStatus: React.FC = ({ - navigation, - queryKey, - rootQueryKey, - status, - dismiss -}) => { - const { theme } = useTheme() - const { t } = useTranslation('componentTimeline') - - const queryClient = useQueryClient() - const mutation = useTimelineMutation({ - onMutate: true, - onError: (err: any, params, oldData) => { - const theFunction = (params as MutationVarsTimelineUpdateStatusProperty).payload - ? (params as MutationVarsTimelineUpdateStatusProperty).payload.property - : 'delete' - displayMessage({ - theme, - type: 'error', - message: t('common:message.error.message', { - function: t(`shared.header.actions.status.${theFunction}.function`) - }), - ...(err?.status && - typeof err.status === 'number' && - err.data && - err.data.error && - typeof err.data.error === 'string' && { - description: err.data.error - }) - }) - queryClient.setQueryData(queryKey, oldData) - } - }) - - const canEditPost = useSelector(checkInstanceFeature('edit_post')) - - return ( - - - {canEditPost ? ( - { - let replyToStatus: Mastodon.Status | undefined = undefined - if (status.in_reply_to_id) { - replyToStatus = await apiInstance({ - method: 'get', - url: `statuses/${status.in_reply_to_id}` - }).then(res => res.body) - } - apiInstance<{ - id: Mastodon.Status['id'] - text: NonNullable - spoiler_text: Mastodon.Status['spoiler_text'] - }>({ - method: 'get', - url: `statuses/${status.id}/source` - }).then(res => { - dismiss() - navigation.navigate('Screen-Compose', { - type: 'edit', - incomingStatus: { - ...status, - text: res.body.text, - spoiler_text: res.body.spoiler_text - }, - ...(replyToStatus && { replyToStatus }), - queryKey, - rootQueryKey - }) - }) - }} - iconFront='Edit3' - title={t('shared.header.actions.status.edit.button')} - /> - ) : null} - { - Alert.alert( - t('shared.header.actions.status.delete.alert.title'), - t('shared.header.actions.status.delete.alert.message'), - [ - { - text: t('shared.header.actions.status.delete.alert.buttons.confirm'), - style: 'destructive', - onPress: async () => { - dismiss() - mutation.mutate({ - type: 'deleteItem', - source: 'statuses', - queryKey, - rootQueryKey, - id: status.id - }) - } - }, - { - text: t('shared.header.actions.status.delete.alert.buttons.cancel'), - style: 'default' - } - ] - ) - }} - iconFront='Trash' - title={t('shared.header.actions.status.delete.button')} - /> - { - Alert.alert( - t('shared.header.actions.status.deleteEdit.alert.title'), - t('shared.header.actions.status.deleteEdit.alert.message'), - [ - { - text: t('shared.header.actions.status.deleteEdit.alert.buttons.confirm'), - style: 'destructive', - onPress: async () => { - let replyToStatus: Mastodon.Status | undefined = undefined - if (status.in_reply_to_id) { - replyToStatus = await apiInstance({ - method: 'get', - url: `statuses/${status.in_reply_to_id}` - }).then(res => res.body) - } - mutation - .mutateAsync({ - type: 'deleteItem', - source: 'statuses', - queryKey, - id: status.id - }) - .then(res => { - dismiss() - navigation.navigate('Screen-Compose', { - type: 'deleteEdit', - incomingStatus: res.body as Mastodon.Status, - ...(replyToStatus && { replyToStatus }), - queryKey - }) - }) - } - }, - { - text: t('shared.header.actions.status.deleteEdit.alert.buttons.cancel'), - style: 'default' - } - ] - ) - }} - iconFront='Edit' - title={t('shared.header.actions.status.deleteEdit.button')} - /> - { - dismiss() - mutation.mutate({ - type: 'updateStatusProperty', - queryKey, - rootQueryKey, - id: status.id, - payload: { - property: 'muted', - currentValue: status.muted, - propertyCount: undefined, - countValue: undefined - } - }) - }} - iconFront='VolumeX' - title={ - status.muted - ? t('shared.header.actions.status.mute.button.negative') - : t('shared.header.actions.status.mute.button.positive') - } - /> - {/* Also note that reblogs cannot be pinned. */} - {(status.visibility === 'public' || status.visibility === 'unlisted') && ( - { - dismiss() - mutation.mutate({ - type: 'updateStatusProperty', - queryKey, - rootQueryKey, - id: status.id, - payload: { - property: 'pinned', - currentValue: status.pinned, - propertyCount: undefined, - countValue: undefined - } - }) - }} - iconFront='Anchor' - title={ - status.pinned - ? t('shared.header.actions.status.pin.button.negative') - : t('shared.header.actions.status.pin.button.positive') - } - /> - )} - - ) -} - -export default ActionsStatus diff --git a/src/screens/ImageViewer/save.ts b/src/screens/ImageViewer/save.ts index 4ec00792..4fbeffef 100644 --- a/src/screens/ImageViewer/save.ts +++ b/src/screens/ImageViewer/save.ts @@ -2,22 +2,19 @@ import haptics from '@components/haptics' import { displayMessage } from '@components/Message' import { CameraRoll } from '@react-native-camera-roll/camera-roll' import { RootStackParamList } from '@utils/navigation/navigators' -import { Theme } from '@utils/styles/themes' import * as FileSystem from 'expo-file-system' import i18next from 'i18next' import { PermissionsAndroid, Platform } from 'react-native' type CommonProps = { - theme: Theme image: RootStackParamList['Screen-ImagesViewer']['imageUrls'][0] } -const saveIos = async ({ theme, image }: CommonProps) => { +const saveIos = async ({ image }: CommonProps) => { CameraRoll.save(image.url) .then(() => { haptics('Success') displayMessage({ - theme, type: 'success', message: i18next.t('screenImageViewer:content.save.succeed') }) @@ -28,7 +25,6 @@ const saveIos = async ({ theme, image }: CommonProps) => { .then(() => { haptics('Success') displayMessage({ - theme, type: 'success', message: i18next.t('screenImageViewer:content.save.succeed') }) @@ -36,32 +32,31 @@ const saveIos = async ({ theme, image }: CommonProps) => { .catch(() => { haptics('Error') displayMessage({ - theme, - type: 'error', + type: 'danger', message: i18next.t('screenImageViewer:content.save.failed') }) }) } else { haptics('Error') displayMessage({ - theme, - type: 'error', + type: 'danger', message: i18next.t('screenImageViewer:content.save.failed') }) } }) } -const saveAndroid = async ({ theme, image }: CommonProps) => { +const saveAndroid = async ({ image }: CommonProps) => { const fileUri: string = `${FileSystem.documentDirectory}${image.id}.jpg` - const downloadedFile: FileSystem.FileSystemDownloadResult = - await FileSystem.downloadAsync(image.url, fileUri) + const downloadedFile: FileSystem.FileSystemDownloadResult = await FileSystem.downloadAsync( + image.url, + fileUri + ) if (downloadedFile.status != 200) { haptics('Error') displayMessage({ - theme, - type: 'error', + type: 'danger', message: i18next.t('screenImageViewer:content.save.failed') }) return @@ -75,8 +70,7 @@ const saveAndroid = async ({ theme, image }: CommonProps) => { if (status !== 'granted') { haptics('Error') displayMessage({ - theme, - type: 'error', + type: 'danger', message: i18next.t('screenImageViewer:content.save.failed') }) return @@ -87,7 +81,6 @@ const saveAndroid = async ({ theme, image }: CommonProps) => { .then(() => { haptics('Success') displayMessage({ - theme, type: 'success', message: i18next.t('screenImageViewer:content.save.succeed') }) @@ -95,8 +88,7 @@ const saveAndroid = async ({ theme, image }: CommonProps) => { .catch(() => { haptics('Error') displayMessage({ - theme, - type: 'error', + type: 'danger', message: i18next.t('screenImageViewer:content.save.failed') }) }) diff --git a/src/screens/ImagesViewer.tsx b/src/screens/ImagesViewer.tsx index dff8754f..eb99187f 100644 --- a/src/screens/ImagesViewer.tsx +++ b/src/screens/ImagesViewer.tsx @@ -40,7 +40,7 @@ const ScreenImagesViewer = ({ const insets = useSafeAreaInsets() - const { mode, theme } = useTheme() + const { mode } = useTheme() const { t } = useTranslation('screenImageViewer') const initialIndex = imageUrls.findIndex(image => image.id === id) @@ -61,7 +61,7 @@ const ScreenImagesViewer = ({ async buttonIndex => { switch (buttonIndex) { case 0: - saveImage({ theme, image: imageUrls[currentIndex] }) + saveImage({ image: imageUrls[currentIndex] }) break case 1: switch (Platform.OS) { @@ -188,7 +188,7 @@ const ScreenImagesViewer = ({ async buttonIndex => { switch (buttonIndex) { case 0: - saveImage({ theme, image: imageUrls[currentIndex] }) + saveImage({ image: imageUrls[currentIndex] }) break case 1: switch (Platform.OS) { diff --git a/src/screens/Tabs/Me.tsx b/src/screens/Tabs/Me.tsx index bf8c5a14..650dbff9 100644 --- a/src/screens/Tabs/Me.tsx +++ b/src/screens/Tabs/Me.tsx @@ -129,15 +129,11 @@ const TabMe = React.memo( name='Tab-Me-Push' component={TabMePush} options={({ navigation }) => ({ - presentation: 'modal', - headerShown: true, title: t('me.stacks.push.name'), ...(Platform.OS === 'android' && { headerCenter: () => }), - headerLeft: () => ( - navigation.goBack()} /> - ) + headerLeft: () => navigation.goBack()} /> })} /> > }, onError: () => { displayMessage({ - theme, - type: 'error', + type: 'danger', message: t('common:message.error.message', { function: t('me.listAccounts.error') }) diff --git a/src/screens/Tabs/Me/List/Edit.tsx b/src/screens/Tabs/Me/List/Edit.tsx index dda4e3e4..70635d53 100644 --- a/src/screens/Tabs/Me/List/Edit.tsx +++ b/src/screens/Tabs/Me/List/Edit.tsx @@ -41,8 +41,7 @@ const TabMeListEdit: React.FC> = ({ onError: () => { displayMessage({ ref: messageRef, - theme, - type: 'error', + type: 'danger', message: t('common:message.error.message', { function: params.type === 'add' ? t('me.stacks.listAdd.name') : t('me.stacks.listEdit.name') diff --git a/src/screens/Tabs/Me/List/index.tsx b/src/screens/Tabs/Me/List/index.tsx index 464a8d5c..2ec2c3e1 100644 --- a/src/screens/Tabs/Me/List/index.tsx +++ b/src/screens/Tabs/Me/List/index.tsx @@ -17,7 +17,7 @@ const TabMeList: React.FC> = ({ navigation, route: { key, params } }) => { - const { colors, theme } = useTheme() + const { colors } = useTheme() const { t } = useTranslation('screenTabs') const queryKey: QueryKeyTimeline = ['Timeline', { page: 'List', list: params.id }] @@ -30,8 +30,7 @@ const TabMeList: React.FC> = ({ }, onError: () => { displayMessage({ - theme, - type: 'error', + type: 'danger', message: t('common:message.error.message', { function: t('me.listDelete.heading') }) diff --git a/src/screens/Tabs/Me/Profile/Fields.tsx b/src/screens/Tabs/Me/Profile/Fields.tsx index 52cc1b4a..6f7a78bf 100644 --- a/src/screens/Tabs/Me/Profile/Fields.tsx +++ b/src/screens/Tabs/Me/Profile/Fields.tsx @@ -121,7 +121,6 @@ const TabMeProfileFields: React.FC< content='Save' onPress={async () => { mutateAsync({ - theme, messageRef, message: { text: 'me.profile.root.note.title', diff --git a/src/screens/Tabs/Me/Profile/Name.tsx b/src/screens/Tabs/Me/Profile/Name.tsx index e2add288..81dce1d7 100644 --- a/src/screens/Tabs/Me/Profile/Name.tsx +++ b/src/screens/Tabs/Me/Profile/Name.tsx @@ -75,7 +75,6 @@ const TabMeProfileName: React.FC< content='Save' onPress={async () => { mutateAsync({ - theme, messageRef, message: { text: 'me.profile.root.name.title', diff --git a/src/screens/Tabs/Me/Profile/Note.tsx b/src/screens/Tabs/Me/Profile/Note.tsx index f3cc5407..7fa08dbf 100644 --- a/src/screens/Tabs/Me/Profile/Note.tsx +++ b/src/screens/Tabs/Me/Profile/Note.tsx @@ -75,7 +75,6 @@ const TabMeProfileNote: React.FC< content='Save' onPress={async () => { mutateAsync({ - theme, messageRef, message: { text: 'me.profile.root.note.title', diff --git a/src/screens/Tabs/Me/Profile/Root.tsx b/src/screens/Tabs/Me/Profile/Root.tsx index d66aa704..a4e48780 100644 --- a/src/screens/Tabs/Me/Profile/Root.tsx +++ b/src/screens/Tabs/Me/Profile/Root.tsx @@ -5,7 +5,7 @@ import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators' import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile' import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences' import { useTheme } from '@utils/styles/ThemeManager' -import React, { RefObject, useCallback } from 'react' +import React, { RefObject } from 'react' import { useTranslation } from 'react-i18next' import FlashMessage from 'react-native-flash-message' import { ScrollView } from 'react-native-gesture-handler' @@ -16,7 +16,7 @@ const TabMeProfileRoot: React.FC< messageRef: RefObject } > = ({ messageRef, navigation }) => { - const { mode, theme } = useTheme() + const { mode } = useTheme() const { t } = useTranslation('screenTabs') const { showActionSheetWithOptions } = useActionSheet() @@ -25,90 +25,6 @@ const TabMeProfileRoot: React.FC< const { mutateAsync } = useProfileMutation() const dispatch = useAppDispatch() - const onPressVisibility = useCallback(() => { - showActionSheetWithOptions( - { - title: t('me.profile.root.visibility.title'), - options: [ - t('me.profile.root.visibility.options.public'), - t('me.profile.root.visibility.options.unlisted'), - t('me.profile.root.visibility.options.private'), - t('common:buttons.cancel') - ], - cancelButtonIndex: 3, - userInterfaceStyle: mode - }, - 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({ - theme, - messageRef, - message: { - text: 'me.profile.root.visibility.title', - succeed: false, - failed: true - }, - type: 'source[privacy]', - data: indexVisibilityMapping[buttonIndex] - }).then(() => dispatch(updateAccountPreferences())) - } - break - } - } - ) - }, [theme, data?.source?.privacy]) - - const onPressSensitive = useCallback(() => { - mutateAsync({ - theme, - 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(() => { - mutateAsync({ - theme, - messageRef, - message: { - text: 'me.profile.root.lock.title', - succeed: false, - failed: true - }, - type: 'locked', - data: data?.locked === undefined ? true : !data.locked - }) - }, [theme, data?.locked]) - - const onPressBot = useCallback(() => { - mutateAsync({ - theme, - messageRef, - message: { - text: 'me.profile.root.bot.title', - succeed: false, - failed: true - }, - type: 'bot', - data: data?.bot === undefined ? true : !data.bot - }) - }, [theme, data?.bot]) - return ( @@ -166,12 +82,62 @@ const TabMeProfileRoot: React.FC< } loading={isLoading} iconBack='ChevronRight' - onPress={onPressVisibility} + onPress={() => + showActionSheetWithOptions( + { + title: t('me.profile.root.visibility.title'), + options: [ + t('me.profile.root.visibility.options.public'), + t('me.profile.root.visibility.options.unlisted'), + t('me.profile.root.visibility.options.private'), + t('common:buttons.cancel') + ], + cancelButtonIndex: 3, + userInterfaceStyle: mode + }, + 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(() => dispatch(updateAccountPreferences())) + } + break + } + } + ) + } /> + 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(() => dispatch(updateAccountPreferences())) + } loading={isLoading} /> @@ -180,14 +146,36 @@ const TabMeProfileRoot: React.FC< title={t('me.profile.root.lock.title')} description={t('me.profile.root.lock.description')} switchValue={data?.locked} - switchOnValueChange={onPressLock} + switchOnValueChange={() => + mutateAsync({ + messageRef, + message: { + text: 'me.profile.root.lock.title', + succeed: false, + failed: true + }, + type: 'locked', + data: data?.locked === undefined ? true : !data.locked + }) + } loading={isLoading} /> + mutateAsync({ + messageRef, + message: { + text: 'me.profile.root.bot.title', + succeed: false, + failed: true + }, + type: 'bot', + data: data?.bot === undefined ? true : !data.bot + }) + } loading={isLoading} /> diff --git a/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx b/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx index 19b8caec..f6879a2c 100644 --- a/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx +++ b/src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx @@ -3,7 +3,6 @@ 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' import React, { RefObject } from 'react' import { useTranslation } from 'react-i18next' import FlashMessage from 'react-native-flash-message' @@ -14,7 +13,6 @@ export interface Props { } const ProfileAvatarHeader: React.FC = ({ type, messageRef }) => { - const { theme } = useTheme() const { t } = useTranslation('screenTabs') const { showActionSheetWithOptions } = useActionSheet() @@ -40,7 +38,6 @@ const ProfileAvatarHeader: React.FC = ({ type, messageRef }) => { }) if (image[0].uri) { mutation.mutate({ - theme, messageRef, message: { text: `me.profile.root.${type}.title`, @@ -54,8 +51,7 @@ const ProfileAvatarHeader: React.FC = ({ type, messageRef }) => { displayMessage({ ref: messageRef, message: t('screenTabs:me.profile.mediaSelectionFailed'), - theme: theme, - type: 'error' + type: 'danger' }) } }} diff --git a/src/utils/push/useConnect.ts b/src/utils/push/useConnect.ts index 1d470e69..bb117b7d 100644 --- a/src/utils/push/useConnect.ts +++ b/src/utils/push/useConnect.ts @@ -7,7 +7,6 @@ import { useAppDispatch } from '@root/store' import * as Sentry from '@sentry/react-native' import { getExpoToken, retrieveExpoToken } from '@utils/slices/appSlice' import { disableAllPushes, getInstances } from '@utils/slices/instancesSlice' -import { useTheme } from '@utils/styles/ThemeManager' import * as Notifications from 'expo-notifications' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' @@ -16,7 +15,6 @@ import { useSelector } from 'react-redux' const pushUseConnect = () => { const { t } = useTranslation('screens') - const { theme } = useTheme() const dispatch = useAppDispatch() useEffect(() => { @@ -39,8 +37,7 @@ const pushUseConnect = () => { Notifications.setBadgeCountAsync(0) if (error?.status == 404) { displayMessage({ - theme, - type: 'error', + type: 'danger', duration: 'long', message: t('pushError.message'), description: t('pushError.description'), diff --git a/src/utils/queryHooks/profile.ts b/src/utils/queryHooks/profile.ts index 5158d9ce..bb00c6d5 100644 --- a/src/utils/queryHooks/profile.ts +++ b/src/utils/queryHooks/profile.ts @@ -2,15 +2,13 @@ import apiInstance from '@api/instance' import haptics from '@components/haptics' import { displayMessage } from '@components/Message' import queryClient from '@helpers/queryClient' -import { Theme } from '@utils/styles/themes' 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 & - Required> +type AccountWithSource = Mastodon.Account & Required> type QueryKeyProfile = ['Profile'] const queryKey: QueryKeyProfile = ['Profile'] @@ -52,7 +50,6 @@ type MutationVarsProfileBase = } type MutationVarsProfile = MutationVarsProfileBase & { - theme: Theme messageRef: RefObject message: { text: string @@ -92,73 +89,70 @@ const mutationFunction = async ({ type, data }: MutationVarsProfile) => { } const useProfileMutation = () => { - return useMutation< - { body: AccountWithSource }, - AxiosError, - MutationVarsProfile - >(mutationFunction, { - onMutate: async variables => { - await queryClient.cancelQueries(queryKey) + return useMutation<{ body: AccountWithSource }, AxiosError, MutationVarsProfile>( + mutationFunction, + { + onMutate: async variables => { + await queryClient.cancelQueries(queryKey) - const oldData = queryClient.getQueryData(queryKey) + const oldData = queryClient.getQueryData(queryKey) - queryClient.setQueryData(queryKey, old => { - if (old) { - switch (variables.type) { - case 'source[privacy]': - return { - ...old, - source: { ...old.source, privacy: variables.data } - } - case 'source[sensitive]': - return { - ...old, - source: { ...old.source, sensitive: variables.data } - } - case 'locked': - return { ...old, locked: variables.data } - case 'bot': - return { ...old, bot: variables.data } - default: - return old + queryClient.setQueryData(queryKey, old => { + if (old) { + switch (variables.type) { + case 'source[privacy]': + return { + ...old, + source: { ...old.source, privacy: variables.data } + } + case 'source[sensitive]': + return { + ...old, + source: { ...old.source, sensitive: variables.data } + } + case 'locked': + return { ...old, locked: variables.data } + case 'bot': + return { ...old, bot: variables.data } + default: + return old + } } - } - }) + }) - return oldData - }, - 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 }), - theme: variables.theme, - type: 'error' - }) + return oldData + }, + 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 }), + type: 'danger' + }) + } + }, + 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}`) + }), + type: 'success' + }) + } + }, + onSettled: () => { + queryClient.invalidateQueries(queryKey) } - }, - 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}`) - }), - theme: variables.theme, - type: 'success' - }) - } - }, - onSettled: () => { - queryClient.invalidateQueries(queryKey) } - }) + ) } export { useProfileQuery, useProfileMutation } diff --git a/src/utils/slices/instances/push/register.ts b/src/utils/slices/instances/push/register.ts index dd82032c..976b0d29 100644 --- a/src/utils/slices/instances/push/register.ts +++ b/src/utils/slices/instances/push/register.ts @@ -1,12 +1,15 @@ import apiInstance from '@api/instance' import apiTooot, { TOOOT_API_DOMAIN } from '@api/tooot' +import { displayMessage } from '@components/Message' import i18n from '@root/i18n/i18n' import { RootState } from '@root/store' import * as Sentry from '@sentry/react-native' import { InstanceLatest } from '@utils/migrations/instances/migration' import { getInstance } from '@utils/slices/instancesSlice' +import { Theme } from '@utils/styles/themes' import * as Notifications from 'expo-notifications' import * as Random from 'expo-random' +import i18next from 'i18next' import { Platform } from 'react-native' import base64 from 'react-native-base64' import androidDefaults from './androidDefaults' @@ -74,6 +77,12 @@ const pushRegister = async ( }) if (!res.body.server_key?.length) { + displayMessage({ + type: 'danger', + duration: 'long', + message: i18next.t('screenTabs:me.push.missingServerKey.message'), + description: i18next.t('screenTabs:me.push.missingServerKey.description') + }) Sentry.setContext('Push server key', { instance: instanceUri, resBody: res.body