mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Push error tolerance
This commit is contained in:
		| @@ -58,7 +58,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => { | ||||
|  | ||||
|   // Update Expo Token to server | ||||
|   useEffect(() => { | ||||
|     dispatch(connectInstancesPush()) | ||||
|     dispatch(connectInstancesPush({ mode, t })) | ||||
|   }, []) | ||||
|  | ||||
|   // Prevent screenshot alert | ||||
|   | ||||
| @@ -1,13 +1,18 @@ | ||||
| export default { | ||||
|   heading: '推送通知', | ||||
|   content: { | ||||
|     enable: { | ||||
|       direct: '启用tooot推送通知', | ||||
|       settings: '去系统设置启用' | ||||
|     }, | ||||
|     global: { | ||||
|       heading: '启用通知', | ||||
|       description: '通知消息将经由tooot服务器转发' | ||||
|     }, | ||||
|     decode: { | ||||
|       heading: '显示通知内容', | ||||
|       description: '经由tooot服务器中转的通知消息已被加密,但可以允许tooot服务器解密并转发消息。tooot消息服务器源码开源,且不留存任何日志。' | ||||
|       description: | ||||
|         '经由tooot服务器中转的通知消息已被加密,但可以允许tooot服务器解密并转发消息。tooot消息服务器源码开源,且不留存任何日志。' | ||||
|     }, | ||||
|     follow: { | ||||
|       heading: '新关注者' | ||||
| @@ -25,5 +30,9 @@ export default { | ||||
|       heading: '投票' | ||||
|     }, | ||||
|     howitworks: '了解通知消息转发如何工作' | ||||
|   }, | ||||
|   error: { | ||||
|     message: '推送服务器错误', | ||||
|     description: '请在设置中重新尝试启用推送通知' | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -4,13 +4,39 @@ import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert | ||||
| import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode' | ||||
| import { getInstancePush } from '@utils/slices/instancesSlice' | ||||
| import * as WebBrowser from 'expo-web-browser' | ||||
| import React, { useMemo } from 'react' | ||||
| import * as Notifications from 'expo-notifications' | ||||
| import React, { useEffect, useMemo, useState } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { ScrollView } from 'react-native-gesture-handler' | ||||
| import { useDispatch, useSelector } from 'react-redux' | ||||
| import layoutAnimation from '@utils/styles/layoutAnimation' | ||||
| import Button from '@components/Button' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { AppState, Linking } from 'react-native' | ||||
|  | ||||
| const ScreenMeSettingsPush: React.FC = () => { | ||||
|   const { t } = useTranslation('meSettingsPush') | ||||
|  | ||||
|   const [appStateVisible, setAppStateVisible] = useState(AppState.currentState) | ||||
|   useEffect(() => { | ||||
|     AppState.addEventListener('change', state => setAppStateVisible(state)) | ||||
|  | ||||
|     return () => { | ||||
|       AppState.removeEventListener('change', state => setAppStateVisible(state)) | ||||
|     } | ||||
|   }, []) | ||||
|   const [pushEnabled, setPushEnabled] = useState<boolean>() | ||||
|   const [pushCanAskAgain, setPushCanAskAgain] = useState<boolean>() | ||||
|   useEffect(() => { | ||||
|     const checkPush = async () => { | ||||
|       const settings = await Notifications.getPermissionsAsync() | ||||
|       layoutAnimation() | ||||
|       setPushEnabled(settings.granted) | ||||
|       setPushCanAskAgain(settings.canAskAgain) | ||||
|     } | ||||
|     checkPush() | ||||
|   }, [appStateVisible]) | ||||
|  | ||||
|   const dispatch = useDispatch() | ||||
|   const instancePush = useSelector(getInstancePush) | ||||
|  | ||||
| @@ -28,7 +54,9 @@ const ScreenMeSettingsPush: React.FC = () => { | ||||
|           <MenuRow | ||||
|             key={alert} | ||||
|             title={t(`content.${alert}.heading`)} | ||||
|             switchDisabled={!instancePush.global.value || isLoading} | ||||
|             switchDisabled={ | ||||
|               !pushEnabled || !instancePush.global.value || isLoading | ||||
|             } | ||||
|             switchValue={instancePush?.alerts[alert].value} | ||||
|             switchOnValueChange={() => | ||||
|               dispatch( | ||||
| @@ -51,13 +79,40 @@ const ScreenMeSettingsPush: React.FC = () => { | ||||
|  | ||||
|   return ( | ||||
|     <ScrollView> | ||||
|       {pushEnabled === false ? ( | ||||
|         <MenuContainer> | ||||
|           <Button | ||||
|             type='text' | ||||
|             content={ | ||||
|               pushCanAskAgain | ||||
|                 ? t('content.enable.direct') | ||||
|                 : t('content.enable.settings') | ||||
|             } | ||||
|             style={{ | ||||
|               marginTop: StyleConstants.Spacing.Global.PagePadding, | ||||
|               marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2 | ||||
|             }} | ||||
|             onPress={async () => { | ||||
|               if (pushCanAskAgain) { | ||||
|                 const result = await Notifications.requestPermissionsAsync() | ||||
|                 setPushEnabled(result.granted) | ||||
|                 setPushCanAskAgain(result.canAskAgain) | ||||
|               } else { | ||||
|                 Linking.openURL('app-settings:') | ||||
|               } | ||||
|             }} | ||||
|           /> | ||||
|         </MenuContainer> | ||||
|       ) : null} | ||||
|       <MenuContainer> | ||||
|         <MenuRow | ||||
|           title={t('content.global.heading')} | ||||
|           description={t('content.global.description')} | ||||
|           loading={instancePush?.global.loading} | ||||
|           switchDisabled={isLoading} | ||||
|           switchValue={instancePush?.global.value} | ||||
|           switchDisabled={!pushEnabled || isLoading} | ||||
|           switchValue={ | ||||
|             pushEnabled === false ? false : instancePush?.global.value | ||||
|           } | ||||
|           switchOnValueChange={() => | ||||
|             dispatch(updateInstancePush(!instancePush?.global.value)) | ||||
|           } | ||||
| @@ -68,7 +123,9 @@ const ScreenMeSettingsPush: React.FC = () => { | ||||
|           title={t('content.decode.heading')} | ||||
|           description={t('content.decode.description')} | ||||
|           loading={instancePush?.decode.loading} | ||||
|           switchDisabled={!instancePush?.global.value || isLoading} | ||||
|           switchDisabled={ | ||||
|             !pushEnabled || !instancePush?.global.value || isLoading | ||||
|           } | ||||
|           switchValue={instancePush?.decode.value} | ||||
|           switchOnValueChange={() => | ||||
|             dispatch(updateInstancePushDecode(!instancePush?.decode.value)) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import Button from '@components/Button' | ||||
| import haptics from '@root/components/haptics' | ||||
| import removeInstance from '@utils/slices/instances/remove' | ||||
| import { getInstance, getInstanceActive } from '@utils/slices/instancesSlice' | ||||
| import { getInstance } from '@utils/slices/instancesSlice' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import React from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
|   | ||||
| @@ -2,11 +2,15 @@ import apiGeneral from '@api/general' | ||||
| import { createAsyncThunk } from '@reduxjs/toolkit' | ||||
| import { RootState } from '@root/store' | ||||
| import * as Notifications from 'expo-notifications' | ||||
| import { TFunction } from 'react-i18next' | ||||
| import { PUSH_SERVER } from '../instancesSlice' | ||||
|  | ||||
| export const connectInstancesPush = createAsyncThunk( | ||||
|   'instances/connectPush', | ||||
|   async (_, { getState }): Promise<any> => { | ||||
|   async ( | ||||
|     { mode, t }: { mode: 'light' | 'dark'; t: TFunction<'common'> }, | ||||
|     { getState } | ||||
|   ): Promise<any> => { | ||||
|     const state = getState() as RootState | ||||
|     const pushEnabled = state.instances.instances.filter( | ||||
|       instance => instance.push.global.value | ||||
|   | ||||
| @@ -110,16 +110,30 @@ const pushRegister = async ( | ||||
|     removeKeys: instancePush.decode.value === false | ||||
|   }) | ||||
|  | ||||
|   return Promise.resolve(serverRes.body.keys) | ||||
|   if (Platform.OS === 'android') { | ||||
|     Notifications.setNotificationChannelAsync('follow', { | ||||
|       name: 'Follow', | ||||
|       importance: Notifications.AndroidImportance.DEFAULT | ||||
|     }) | ||||
|     Notifications.setNotificationChannelAsync('favourite', { | ||||
|       name: 'Favourite', | ||||
|       importance: Notifications.AndroidImportance.DEFAULT | ||||
|     }) | ||||
|     Notifications.setNotificationChannelAsync('reblog', { | ||||
|       name: 'Reblog', | ||||
|       importance: Notifications.AndroidImportance.DEFAULT | ||||
|     }) | ||||
|     Notifications.setNotificationChannelAsync('mention', { | ||||
|       name: 'Mention', | ||||
|       importance: Notifications.AndroidImportance.DEFAULT | ||||
|     }) | ||||
|     Notifications.setNotificationChannelAsync('poll', { | ||||
|       name: 'Poll', | ||||
|       importance: Notifications.AndroidImportance.DEFAULT | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // if (Platform.OS === 'android') { | ||||
|   //   Notifications.setNotificationChannelAsync('default', { | ||||
|   //     name: 'default', | ||||
|   //     importance: Notifications.AndroidImportance.MAX, | ||||
|   //     vibrationPattern: [0, 250, 250, 250], | ||||
|   //     lightColor: '#FF231F7C' | ||||
|   //   }) | ||||
|   // } | ||||
|   return Promise.resolve(serverRes.body.keys) | ||||
| } | ||||
|  | ||||
| export default pushRegister | ||||
|   | ||||
| @@ -1,9 +1,12 @@ | ||||
| import analytics from '@components/analytics' | ||||
| import { displayMessage } from '@components/Message' | ||||
| import { createSlice, PayloadAction } from '@reduxjs/toolkit' | ||||
| import { RootState } from '@root/store' | ||||
| import { ComposeStateDraft } from '@screens/Compose/utils/types' | ||||
| import { findIndex } from 'lodash' | ||||
| import { Appearance } from 'react-native' | ||||
| import addInstance from './instances/add' | ||||
| import { connectInstancesPush } from './instances/connectPush' | ||||
| import removeInstance from './instances/remove' | ||||
| import { updateAccountPreferences } from './instances/updateAccountPreferences' | ||||
| import { updateInstancePush } from './instances/updatePush' | ||||
| @@ -264,6 +267,22 @@ const instancesSlice = createSlice({ | ||||
|           action.meta.arg.changed | ||||
|         ].loading = true | ||||
|       }) | ||||
|  | ||||
|       // If Expo token does not exist on the server, disable all existing ones | ||||
|       .addCase(connectInstancesPush.rejected, (state, action) => { | ||||
|         state.instances = state.instances.map(instance => { | ||||
|           let newInstance = instance | ||||
|           newInstance.push.global.value = false | ||||
|           displayMessage({ | ||||
|             mode: action.meta.arg.mode, | ||||
|             type: 'error', | ||||
|             autoHide: false, | ||||
|             message: action.meta.arg.t('meSettingsPush:error.message'), | ||||
|             description: action.meta.arg.t('meSettingsPush:error.description') | ||||
|           }) | ||||
|           return newInstance | ||||
|         }) | ||||
|       }) | ||||
|   } | ||||
| }) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user