mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Ready for push feature
This commit is contained in:
		| @@ -8,7 +8,7 @@ ensure_env_vars( | |||||||
| VERSIONS = read_json( json_path: "./package.json" )[:versions] | VERSIONS = read_json( json_path: "./package.json" )[:versions] | ||||||
| ENVIRONMENT = ENV["TOOOT_ENVIRONMENT"] | ENVIRONMENT = ENV["TOOOT_ENVIRONMENT"] | ||||||
| VERSION = "#{VERSIONS[:major]}.#{VERSIONS[:minor]}" | VERSION = "#{VERSIONS[:major]}.#{VERSIONS[:minor]}" | ||||||
| RELEASE_CHANNEL = "#{VERSIONS[:major]}-#{ENVIRONMENT}" | RELEASE_CHANNEL = "#{VERSIONS[:major]}-#{VERSIONS[:minor]}-#{ENVIRONMENT}" | ||||||
| BUILD_NUMBER = ENV["GITHUB_RUN_NUMBER"] | BUILD_NUMBER = ENV["GITHUB_RUN_NUMBER"] | ||||||
| GITHUB_REPO = "tooot-app/app" | GITHUB_REPO = "tooot-app/app" | ||||||
| case ENVIRONMENT | case ENVIRONMENT | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| { | { | ||||||
|   "name": "tooot", |   "name": "tooot", | ||||||
|   "versions": { |   "versions": { | ||||||
|     "native": "210201", |     "native": "210304", | ||||||
|     "major": 0, |     "major": 0, | ||||||
|     "minor": 5, |     "minor": 6, | ||||||
|     "patch": 0, |     "patch": 0, | ||||||
|     "expo": "40.0.0" |     "expo": "40.0.0" | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -8,10 +8,12 @@ import ScreenAnnouncements from '@screens/Announcements' | |||||||
| import ScreenCompose from '@screens/Compose' | import ScreenCompose from '@screens/Compose' | ||||||
| import ScreenImagesViewer from '@screens/ImagesViewer' | import ScreenImagesViewer from '@screens/ImagesViewer' | ||||||
| import ScreenTabs from '@screens/Tabs' | import ScreenTabs from '@screens/Tabs' | ||||||
|  | import pushUseConnect from '@utils/push/useConnect' | ||||||
|  | import pushUseReceive from '@utils/push/useReceive' | ||||||
|  | import pushUseRespond from '@utils/push/useRespond' | ||||||
| import { updatePreviousTab } from '@utils/slices/contextsSlice' | import { updatePreviousTab } from '@utils/slices/contextsSlice' | ||||||
| import { connectInstancesPush } from '@utils/slices/instances/connectPush' |  | ||||||
| import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences' | import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences' | ||||||
| import { getInstanceActive } from '@utils/slices/instancesSlice' | import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice' | ||||||
| import { useTheme } from '@utils/styles/ThemeManager' | import { useTheme } from '@utils/styles/ThemeManager' | ||||||
| import { themes } from '@utils/styles/themes' | import { themes } from '@utils/styles/themes' | ||||||
| import * as Analytics from 'expo-firebase-analytics' | import * as Analytics from 'expo-firebase-analytics' | ||||||
| @@ -20,6 +22,7 @@ import React, { createRef, useCallback, useEffect, useRef } from 'react' | |||||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||||
| import { Alert, Platform, StatusBar } from 'react-native' | import { Alert, Platform, StatusBar } from 'react-native' | ||||||
| import { createNativeStackNavigator } from 'react-native-screens/native-stack' | import { createNativeStackNavigator } from 'react-native-screens/native-stack' | ||||||
|  | import { useQueryClient } from 'react-query' | ||||||
| import { useDispatch, useSelector } from 'react-redux' | import { useDispatch, useSelector } from 'react-redux' | ||||||
| import * as Sentry from 'sentry-expo' | import * as Sentry from 'sentry-expo' | ||||||
|  |  | ||||||
| @@ -56,10 +59,15 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => { | |||||||
|   //   } |   //   } | ||||||
|   // }, [isConnected, firstRender]) |   // }, [isConnected, firstRender]) | ||||||
|  |  | ||||||
|   // Update Expo Token to server |   // Push hooks | ||||||
|   useEffect(() => { |   const instances = useSelector( | ||||||
|     dispatch(connectInstancesPush({ mode, t })) |     getInstances, | ||||||
|   }, []) |     (prev, next) => prev.length === next.length | ||||||
|  |   ) | ||||||
|  |   const queryClient = useQueryClient() | ||||||
|  |   pushUseConnect({ navigationRef, mode, t, instances, dispatch }) | ||||||
|  |   pushUseReceive({ navigationRef, queryClient, instances }) | ||||||
|  |   pushUseRespond({ navigationRef, queryClient, instances, dispatch }) | ||||||
|  |  | ||||||
|   // Prevent screenshot alert |   // Prevent screenshot alert | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|   | |||||||
| @@ -56,8 +56,7 @@ const displayMessage = ({ | |||||||
|     onPress, |     onPress, | ||||||
|     ...(mode && |     ...(mode && | ||||||
|       type && { |       type && { | ||||||
|         renderFlashMessageIcon: props => { |         renderFlashMessageIcon: () => { | ||||||
|           console.log(props) |  | ||||||
|           return ( |           return ( | ||||||
|             <Icon |             <Icon | ||||||
|               name={iconMapping[type]} |               name={iconMapping[type]} | ||||||
| @@ -92,7 +91,7 @@ const Message = React.memo( | |||||||
|           ...StyleConstants.FontStyle.M, |           ...StyleConstants.FontStyle.M, | ||||||
|           fontWeight: StyleConstants.Font.Weight.Bold |           fontWeight: StyleConstants.Font.Weight.Bold | ||||||
|         }} |         }} | ||||||
|         textStyle={{ color: theme.primary, ...StyleConstants.FontStyle.M }} |         textStyle={{ color: theme.primary, ...StyleConstants.FontStyle.S }} | ||||||
|         // @ts-ignore |         // @ts-ignore | ||||||
|         textProps={{ numberOfLines: 2 }} |         textProps={{ numberOfLines: 2 }} | ||||||
|       /> |       /> | ||||||
|   | |||||||
| @@ -209,7 +209,6 @@ const TimelineActions: React.FC<Props> = ({ | |||||||
|               : iconColorAction(status.reblogged) |               : iconColorAction(status.reblogged) | ||||||
|           } |           } | ||||||
|           size={StyleConstants.Font.Size.L} |           size={StyleConstants.Font.Size.L} | ||||||
|           strokeWidth={status.reblogged ? 3 : undefined} |  | ||||||
|         /> |         /> | ||||||
|         {status.reblogs_count > 0 && ( |         {status.reblogs_count > 0 && ( | ||||||
|           <Text |           <Text | ||||||
| @@ -233,7 +232,6 @@ const TimelineActions: React.FC<Props> = ({ | |||||||
|           name='Heart' |           name='Heart' | ||||||
|           color={iconColorAction(status.favourited)} |           color={iconColorAction(status.favourited)} | ||||||
|           size={StyleConstants.Font.Size.L} |           size={StyleConstants.Font.Size.L} | ||||||
|           strokeWidth={status.favourited ? 3 : undefined} |  | ||||||
|         /> |         /> | ||||||
|         {status.favourites_count > 0 && ( |         {status.favourites_count > 0 && ( | ||||||
|           <Text |           <Text | ||||||
| @@ -257,7 +255,6 @@ const TimelineActions: React.FC<Props> = ({ | |||||||
|         name='Bookmark' |         name='Bookmark' | ||||||
|         color={iconColorAction(status.bookmarked)} |         color={iconColorAction(status.bookmarked)} | ||||||
|         size={StyleConstants.Font.Size.L} |         size={StyleConstants.Font.Size.L} | ||||||
|         strokeWidth={status.bookmarked ? 3 : undefined} |  | ||||||
|       /> |       /> | ||||||
|     ), |     ), | ||||||
|     [status.bookmarked] |     [status.bookmarked] | ||||||
|   | |||||||
| @@ -1,6 +1,10 @@ | |||||||
| export default { | export default { | ||||||
|   heading: 'Push Notification', |   heading: 'Push Notification', | ||||||
|   content: { |   content: { | ||||||
|  |     enable: { | ||||||
|  |       direct: 'Enable push notification', | ||||||
|  |       settings: 'Enable in settings' | ||||||
|  |     }, | ||||||
|     global: { |     global: { | ||||||
|       heading: 'Enable push notification', |       heading: 'Enable push notification', | ||||||
|       description: "Messages are routed through tooot's server" |       description: "Messages are routed through tooot's server" | ||||||
| @@ -10,6 +14,9 @@ export default { | |||||||
|       description: |       description: | ||||||
|         "Messages routed through tooot's server are encrypted, but you can choose to decode the message on the server. Our server source code is open source, and no log policy." |         "Messages routed through tooot's server are encrypted, but you can choose to decode the message on the server. Our server source code is open source, and no log policy." | ||||||
|     }, |     }, | ||||||
|  |     default: { | ||||||
|  |       heading: 'Default' // Android notification channel name only | ||||||
|  |     }, | ||||||
|     follow: { |     follow: { | ||||||
|       heading: 'New follower' |       heading: 'New follower' | ||||||
|     }, |     }, | ||||||
| @@ -26,5 +33,9 @@ export default { | |||||||
|       heading: 'Poll updates' |       heading: 'Poll updates' | ||||||
|     }, |     }, | ||||||
|     howitworks: 'Learn how routing works' |     howitworks: 'Learn how routing works' | ||||||
|  |   }, | ||||||
|  |   error: { | ||||||
|  |     message: 'Push service error', | ||||||
|  |     description: 'Please re-enable push notification in settings' | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ export default { | |||||||
|     howitworks: '了解通知消息转发如何工作' |     howitworks: '了解通知消息转发如何工作' | ||||||
|   }, |   }, | ||||||
|   error: { |   error: { | ||||||
|     message: '推送服务器错误', |     message: '推送服务错误', | ||||||
|     description: '请在设置中重新尝试启用推送通知' |     description: '请在设置中重新尝试启用推送通知' | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,19 +7,19 @@ import { | |||||||
| import { NavigatorScreenParams } from '@react-navigation/native' | import { NavigatorScreenParams } from '@react-navigation/native' | ||||||
| import { StackScreenProps } from '@react-navigation/stack' | import { StackScreenProps } from '@react-navigation/stack' | ||||||
| import { getPreviousTab } from '@utils/slices/contextsSlice' | import { getPreviousTab } from '@utils/slices/contextsSlice' | ||||||
| import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice' | import { | ||||||
|  |   getInstanceAccount, | ||||||
|  |   getInstanceActive | ||||||
|  | } from '@utils/slices/instancesSlice' | ||||||
| import { useTheme } from '@utils/styles/ThemeManager' | import { useTheme } from '@utils/styles/ThemeManager' | ||||||
| import React, { useCallback, useMemo } from 'react' | import React, { useCallback, useMemo } from 'react' | ||||||
| import { Platform } from 'react-native' | import { Platform } from 'react-native' | ||||||
| import FastImage from 'react-native-fast-image' | import FastImage from 'react-native-fast-image' | ||||||
| import { useQueryClient } from 'react-query' | import { useSelector } from 'react-redux' | ||||||
| import { useDispatch, useSelector } from 'react-redux' |  | ||||||
| import TabLocal from './Tabs/Local' | import TabLocal from './Tabs/Local' | ||||||
| import TabMe from './Tabs/Me' | import TabMe from './Tabs/Me' | ||||||
| import TabNotifications from './Tabs/Notifications' | import TabNotifications from './Tabs/Notifications' | ||||||
| import TabPublic from './Tabs/Public' | import TabPublic from './Tabs/Public' | ||||||
| import pushReceive from './Tabs/utils/pushReceive' |  | ||||||
| import pushRespond from './Tabs/utils/pushRespond' |  | ||||||
|  |  | ||||||
| export type ScreenTabsParamList = { | export type ScreenTabsParamList = { | ||||||
|   'Tab-Local': NavigatorScreenParams<Nav.TabLocalStackParamList> |   'Tab-Local': NavigatorScreenParams<Nav.TabLocalStackParamList> | ||||||
| @@ -40,18 +40,12 @@ const ScreenTabs = React.memo( | |||||||
|   ({ navigation }: ScreenTabsProp) => { |   ({ navigation }: ScreenTabsProp) => { | ||||||
|     const { mode, theme } = useTheme() |     const { mode, theme } = useTheme() | ||||||
|  |  | ||||||
|     const queryClient = useQueryClient() |  | ||||||
|  |  | ||||||
|     const dispatch = useDispatch() |  | ||||||
|     const instanceActive = useSelector(getInstanceActive) |     const instanceActive = useSelector(getInstanceActive) | ||||||
|     const instances = useSelector( |     const instanceAccount = useSelector( | ||||||
|       getInstances, |       getInstanceAccount, | ||||||
|       (prev, next) => prev.length === next.length |       (prev, next) => prev?.avatarStatic === next?.avatarStatic | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     pushReceive({ navigation, queryClient, instances }) |  | ||||||
|     pushRespond({ navigation, queryClient, instances, dispatch }) |  | ||||||
|  |  | ||||||
|     const screenOptions = useCallback( |     const screenOptions = useCallback( | ||||||
|       ({ route }): BottomTabNavigationOptions => ({ |       ({ route }): BottomTabNavigationOptions => ({ | ||||||
|         tabBarVisible: instanceActive !== -1, |         tabBarVisible: instanceActive !== -1, | ||||||
| @@ -77,7 +71,7 @@ const ScreenTabs = React.memo( | |||||||
|               return instanceActive !== -1 ? ( |               return instanceActive !== -1 ? ( | ||||||
|                 <FastImage |                 <FastImage | ||||||
|                   source={{ |                   source={{ | ||||||
|                     uri: instances[instanceActive].account.avatarStatic |                     uri: instanceAccount?.avatarStatic | ||||||
|                   }} |                   }} | ||||||
|                   style={{ |                   style={{ | ||||||
|                     width: size, |                     width: size, | ||||||
| @@ -99,7 +93,7 @@ const ScreenTabs = React.memo( | |||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       }), |       }), | ||||||
|       [instances, instanceActive] |       [instanceAccount, instanceActive] | ||||||
|     ) |     ) | ||||||
|     const tabBarOptions = useMemo( |     const tabBarOptions = useMemo( | ||||||
|       () => ({ |       () => ({ | ||||||
|   | |||||||
| @@ -1,31 +0,0 @@ | |||||||
| import apiInstance from '@api/instance' |  | ||||||
| import { StackNavigationProp } from '@react-navigation/stack' |  | ||||||
|  |  | ||||||
| const pushNavigate = ( |  | ||||||
|   navigation: StackNavigationProp<Nav.RootStackParamList, 'Screen-Tabs'>, |  | ||||||
|   id?: Mastodon.Notification['id'] |  | ||||||
| ) => { |  | ||||||
|   // @ts-ignore |  | ||||||
|   navigation.navigate('Tab-Notifications', { |  | ||||||
|     screen: 'Tab-Notifications-Root' |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   if (!id) { |  | ||||||
|     return |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   apiInstance<Mastodon.Notification>({ |  | ||||||
|     method: 'get', |  | ||||||
|     url: `notifications/${id}` |  | ||||||
|   }).then(({ body }) => { |  | ||||||
|     if (body.status) { |  | ||||||
|       // @ts-ignore |  | ||||||
|       navigation.navigate('Tab-Notifications', { |  | ||||||
|         screen: 'Tab-Shared-Toot', |  | ||||||
|         params: { toot: body.status } |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
|   }) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export default pushNavigate |  | ||||||
							
								
								
									
										35
									
								
								src/store.ts
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/store.ts
									
									
									
									
									
								
							| @@ -5,10 +5,11 @@ import { | |||||||
|   configureStore, |   configureStore, | ||||||
|   getDefaultMiddleware |   getDefaultMiddleware | ||||||
| } from '@reduxjs/toolkit' | } from '@reduxjs/toolkit' | ||||||
|  | import { InstancesV3 } from '@utils/migrations/instances/v3' | ||||||
| import contextsSlice from '@utils/slices/contextsSlice' | import contextsSlice from '@utils/slices/contextsSlice' | ||||||
| import instancesSlice from '@utils/slices/instancesSlice' | import instancesSlice from '@utils/slices/instancesSlice' | ||||||
| import settingsSlice from '@utils/slices/settingsSlice' | import settingsSlice from '@utils/slices/settingsSlice' | ||||||
| import { persistReducer, persistStore } from 'redux-persist' | import { createMigrate, persistReducer, persistStore } from 'redux-persist' | ||||||
|  |  | ||||||
| const secureStorage = createSecureStore() | const secureStorage = createSecureStore() | ||||||
|  |  | ||||||
| @@ -20,10 +21,40 @@ const contextsPersistConfig = { | |||||||
|   storage: AsyncStorage |   storage: AsyncStorage | ||||||
| } | } | ||||||
|  |  | ||||||
|  | const instancesMigration = { | ||||||
|  |   4: (state: InstancesV3) => { | ||||||
|  |     return { | ||||||
|  |       instances: state.local.instances.map((instance, index) => { | ||||||
|  |         // @ts-ignore | ||||||
|  |         delete instance.notification | ||||||
|  |         return { | ||||||
|  |           ...instance, | ||||||
|  |           active: state.local.activeIndex === index, | ||||||
|  |           push: { | ||||||
|  |             global: { loading: false, value: false }, | ||||||
|  |             decode: { loading: false, value: false }, | ||||||
|  |             alerts: { | ||||||
|  |               follow: { loading: false, value: true }, | ||||||
|  |               favourite: { loading: false, value: true }, | ||||||
|  |               reblog: { loading: false, value: true }, | ||||||
|  |               mention: { loading: false, value: true }, | ||||||
|  |               poll: { loading: false, value: true } | ||||||
|  |             }, | ||||||
|  |             keys: undefined | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| const instancesPersistConfig = { | const instancesPersistConfig = { | ||||||
|   key: 'instances', |   key: 'instances', | ||||||
|   prefix, |   prefix, | ||||||
|   storage: secureStorage |   storage: secureStorage, | ||||||
|  |   version: 4, | ||||||
|  |   // @ts-ignore | ||||||
|  |   migrate: createMigrate(instancesMigration) | ||||||
| } | } | ||||||
|  |  | ||||||
| const settingsPersistConfig = { | const settingsPersistConfig = { | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								src/utils/migrations/instances/v3.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/utils/migrations/instances/v3.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | type InstanceLocal = { | ||||||
|  |   appData: { | ||||||
|  |     clientId: string | ||||||
|  |     clientSecret: string | ||||||
|  |   } | ||||||
|  |   url: string | ||||||
|  |   token: string | ||||||
|  |   uri: Mastodon.Instance['uri'] | ||||||
|  |   urls: Mastodon.Instance['urls'] | ||||||
|  |   max_toot_chars: number | ||||||
|  |   account: { | ||||||
|  |     id: Mastodon.Account['id'] | ||||||
|  |     acct: Mastodon.Account['acct'] | ||||||
|  |     avatarStatic: Mastodon.Account['avatar_static'] | ||||||
|  |     preferences: Mastodon.Preferences | ||||||
|  |   } | ||||||
|  |   notification: { | ||||||
|  |     readTime?: Mastodon.Notification['created_at'] | ||||||
|  |     latestTime?: Mastodon.Notification['created_at'] | ||||||
|  |   } | ||||||
|  |   drafts: any[] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export type InstancesV3 = { | ||||||
|  |   local: { | ||||||
|  |     activeIndex: number | null | ||||||
|  |     instances: InstanceLocal[] | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   remote: { | ||||||
|  |     url: string | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										91
									
								
								src/utils/push/useConnect.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/utils/push/useConnect.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | import apiGeneral from '@api/general' | ||||||
|  | import { displayMessage } from '@components/Message' | ||||||
|  | import { NavigationContainerRef } from '@react-navigation/native' | ||||||
|  | import { Dispatch } from '@reduxjs/toolkit' | ||||||
|  | import { | ||||||
|  |   disableAllPushes, | ||||||
|  |   Instance, | ||||||
|  |   PUSH_SERVER | ||||||
|  | } from '@utils/slices/instancesSlice' | ||||||
|  | import * as Notifications from 'expo-notifications' | ||||||
|  | import { useEffect } from 'react' | ||||||
|  | import { TFunction } from 'react-i18next' | ||||||
|  |  | ||||||
|  | export interface Params { | ||||||
|  |   navigationRef: React.RefObject<NavigationContainerRef> | ||||||
|  |   mode: 'light' | 'dark' | ||||||
|  |   t: TFunction<'common'> | ||||||
|  |   instances: Instance[] | ||||||
|  |   dispatch: Dispatch<any> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const pushUseConnect = ({ | ||||||
|  |   navigationRef, | ||||||
|  |   mode, | ||||||
|  |   t, | ||||||
|  |   instances, | ||||||
|  |   dispatch | ||||||
|  | }: Params) => { | ||||||
|  |   return useEffect(() => { | ||||||
|  |     const connect = async () => { | ||||||
|  |       const expoToken = ( | ||||||
|  |         await Notifications.getExpoPushTokenAsync({ | ||||||
|  |           experienceId: '@xmflsct/tooot' | ||||||
|  |         }) | ||||||
|  |       ).data | ||||||
|  |  | ||||||
|  |       apiGeneral({ | ||||||
|  |         method: 'post', | ||||||
|  |         domain: PUSH_SERVER, | ||||||
|  |         url: 'v1/connect', | ||||||
|  |         body: { | ||||||
|  |           expoToken | ||||||
|  |         } | ||||||
|  |       }).catch(() => { | ||||||
|  |         displayMessage({ | ||||||
|  |           mode, | ||||||
|  |           type: 'error', | ||||||
|  |           duration: 'long', | ||||||
|  |           message: t('meSettingsPush:error.message'), | ||||||
|  |           description: t('meSettingsPush:error.description'), | ||||||
|  |           onPress: () => { | ||||||
|  |             navigationRef.current?.navigate('Screen-Tabs', { | ||||||
|  |               screen: 'Tab-Me', | ||||||
|  |               params: { | ||||||
|  |                 screen: 'Tab-Me-Root' | ||||||
|  |               } | ||||||
|  |             }) | ||||||
|  |             navigationRef.current?.navigate('Screen-Tabs', { | ||||||
|  |               screen: 'Tab-Me', | ||||||
|  |               params: { | ||||||
|  |                 screen: 'Tab-Me-Settings' | ||||||
|  |               } | ||||||
|  |             }) | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |         dispatch(disableAllPushes()) | ||||||
|  |  | ||||||
|  |         instances.forEach(instance => { | ||||||
|  |           if (instance.push.global.value) { | ||||||
|  |             apiGeneral<{}>({ | ||||||
|  |               method: 'delete', | ||||||
|  |               domain: instance.url, | ||||||
|  |               url: 'api/v1/push/subscription', | ||||||
|  |               headers: { | ||||||
|  |                 Authorization: `Bearer ${instance.token}` | ||||||
|  |               } | ||||||
|  |             }).catch(() => console.log('error!!!')) | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const pushEnabled = instances.filter(instance => instance.push.global.value) | ||||||
|  |     if (pushEnabled.length) { | ||||||
|  |       connect() | ||||||
|  |     } | ||||||
|  |   }, [instances]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default pushUseConnect | ||||||
							
								
								
									
										35
									
								
								src/utils/push/useNavigate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/utils/push/useNavigate.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | import apiInstance from '@api/instance' | ||||||
|  | import { NavigationContainerRef } from '@react-navigation/native' | ||||||
|  |  | ||||||
|  | const pushUseNavigate = ( | ||||||
|  |   navigationRef: React.RefObject<NavigationContainerRef>, | ||||||
|  |   id?: Mastodon.Notification['id'] | ||||||
|  | ) => { | ||||||
|  |   navigationRef.current?.navigate('Screen-Tabs', { | ||||||
|  |     screen: 'Tab-Notifications', | ||||||
|  |     params: { | ||||||
|  |       screen: 'Tab-Notifications-Root' | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   if (!id) { | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   apiInstance<Mastodon.Notification>({ | ||||||
|  |     method: 'get', | ||||||
|  |     url: `notifications/${id}` | ||||||
|  |   }).then(({ body }) => { | ||||||
|  |     if (body.status) { | ||||||
|  |       navigationRef.current?.navigate('Screen-Tabs', { | ||||||
|  |         screen: 'Tab-Notifications', | ||||||
|  |         params: { | ||||||
|  |           screen: 'Tab-Shared-Toot', | ||||||
|  |           params: { toot: body.status } | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default pushUseNavigate | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { displayMessage } from '@components/Message' | import { displayMessage } from '@components/Message' | ||||||
| import { StackNavigationProp } from '@react-navigation/stack' | import { NavigationContainerRef } from '@react-navigation/native' | ||||||
| import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | ||||||
| import { Instance, updateInstanceActive } from '@utils/slices/instancesSlice' | import { Instance, updateInstanceActive } from '@utils/slices/instancesSlice' | ||||||
| import * as Notifications from 'expo-notifications' | import * as Notifications from 'expo-notifications' | ||||||
| @@ -7,15 +7,15 @@ import { findIndex } from 'lodash' | |||||||
| import { useEffect } from 'react' | import { useEffect } from 'react' | ||||||
| import { QueryClient } from 'react-query' | import { QueryClient } from 'react-query' | ||||||
| import { useDispatch } from 'react-redux' | import { useDispatch } from 'react-redux' | ||||||
| import pushNavigate from './pushNavigate' | import pushUseNavigate from './useNavigate' | ||||||
| 
 | 
 | ||||||
| export interface Params { | export interface Params { | ||||||
|   navigation: StackNavigationProp<Nav.RootStackParamList, 'Screen-Tabs'> |   navigationRef: React.RefObject<NavigationContainerRef> | ||||||
|   queryClient: QueryClient |   queryClient: QueryClient | ||||||
|   instances: Instance[] |   instances: Instance[] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const pushReceive = ({ navigation, queryClient, instances }: Params) => { | const pushUseReceive = ({ navigationRef, queryClient, instances }: Params) => { | ||||||
|   const dispatch = useDispatch() |   const dispatch = useDispatch() | ||||||
| 
 | 
 | ||||||
|   return useEffect(() => { |   return useEffect(() => { | ||||||
| @@ -46,7 +46,7 @@ const pushReceive = ({ navigation, queryClient, instances }: Params) => { | |||||||
|             if (notificationIndex !== -1) { |             if (notificationIndex !== -1) { | ||||||
|               dispatch(updateInstanceActive(instances[notificationIndex])) |               dispatch(updateInstanceActive(instances[notificationIndex])) | ||||||
|             } |             } | ||||||
|             pushNavigate(navigation, payloadData.notification_id) |             pushUseNavigate(navigationRef, payloadData.notification_id) | ||||||
|           } |           } | ||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
| @@ -55,4 +55,4 @@ const pushReceive = ({ navigation, queryClient, instances }: Params) => { | |||||||
|   }, [instances]) |   }, [instances]) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default pushReceive | export default pushUseReceive | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { StackNavigationProp } from '@react-navigation/stack' | import { NavigationContainerRef } from '@react-navigation/native' | ||||||
| import { Dispatch } from '@reduxjs/toolkit' | import { Dispatch } from '@reduxjs/toolkit' | ||||||
| import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | ||||||
| import { Instance, updateInstanceActive } from '@utils/slices/instancesSlice' | import { Instance, updateInstanceActive } from '@utils/slices/instancesSlice' | ||||||
| @@ -6,17 +6,17 @@ import * as Notifications from 'expo-notifications' | |||||||
| import { findIndex } from 'lodash' | import { findIndex } from 'lodash' | ||||||
| import { useEffect } from 'react' | import { useEffect } from 'react' | ||||||
| import { QueryClient } from 'react-query' | import { QueryClient } from 'react-query' | ||||||
| import pushNavigate from './pushNavigate' | import pushUseNavigate from './useNavigate' | ||||||
| 
 | 
 | ||||||
| export interface Params { | export interface Params { | ||||||
|   navigation: StackNavigationProp<Nav.RootStackParamList, 'Screen-Tabs'> |   navigationRef: React.RefObject<NavigationContainerRef> | ||||||
|   queryClient: QueryClient |   queryClient: QueryClient | ||||||
|   instances: Instance[] |   instances: Instance[] | ||||||
|   dispatch: Dispatch<any> |   dispatch: Dispatch<any> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const pushRespond = ({ | const pushUseRespond = ({ | ||||||
|   navigation, |   navigationRef, | ||||||
|   queryClient, |   queryClient, | ||||||
|   instances, |   instances, | ||||||
|   dispatch |   dispatch | ||||||
| @@ -44,11 +44,11 @@ const pushRespond = ({ | |||||||
|         if (notificationIndex !== -1) { |         if (notificationIndex !== -1) { | ||||||
|           dispatch(updateInstanceActive(instances[notificationIndex])) |           dispatch(updateInstanceActive(instances[notificationIndex])) | ||||||
|         } |         } | ||||||
|         pushNavigate(navigation, payloadData.notification_id) |         pushUseNavigate(navigationRef, payloadData.notification_id) | ||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
|     return () => subscription.remove() |     return () => subscription.remove() | ||||||
|   }, [instances]) |   }, [instances]) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default pushRespond | export default pushUseRespond | ||||||
| @@ -1,38 +0,0 @@ | |||||||
| 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 ( |  | ||||||
|     { 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 |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     if (pushEnabled.length) { |  | ||||||
|       const expoToken = ( |  | ||||||
|         await Notifications.getExpoPushTokenAsync({ |  | ||||||
|           experienceId: '@xmflsct/tooot' |  | ||||||
|         }) |  | ||||||
|       ).data |  | ||||||
|  |  | ||||||
|       return apiGeneral({ |  | ||||||
|         method: 'post', |  | ||||||
|         domain: PUSH_SERVER, |  | ||||||
|         url: 'v1/connect', |  | ||||||
|         body: { |  | ||||||
|           expoToken |  | ||||||
|         } |  | ||||||
|       }) |  | ||||||
|     } else { |  | ||||||
|       return Promise.resolve() |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| ) |  | ||||||
| @@ -1,11 +1,9 @@ | |||||||
| import analytics from '@components/analytics' | import analytics from '@components/analytics' | ||||||
| import { displayMessage } from '@components/Message' |  | ||||||
| import { createSlice, PayloadAction } from '@reduxjs/toolkit' | import { createSlice, PayloadAction } from '@reduxjs/toolkit' | ||||||
| import { RootState } from '@root/store' | import { RootState } from '@root/store' | ||||||
| import { ComposeStateDraft } from '@screens/Compose/utils/types' | import { ComposeStateDraft } from '@screens/Compose/utils/types' | ||||||
| import { findIndex } from 'lodash' | import { findIndex } from 'lodash' | ||||||
| import addInstance from './instances/add' | import addInstance from './instances/add' | ||||||
| import { connectInstancesPush } from './instances/connectPush' |  | ||||||
| import removeInstance from './instances/remove' | import removeInstance from './instances/remove' | ||||||
| import { updateAccountPreferences } from './instances/updateAccountPreferences' | import { updateAccountPreferences } from './instances/updateAccountPreferences' | ||||||
| import { updateInstancePush } from './instances/updatePush' | import { updateInstancePush } from './instances/updatePush' | ||||||
| @@ -150,6 +148,13 @@ const instancesSlice = createSlice({ | |||||||
|       instances[activeIndex].drafts = instances[activeIndex].drafts?.filter( |       instances[activeIndex].drafts = instances[activeIndex].drafts?.filter( | ||||||
|         draft => draft.timestamp !== action.payload |         draft => draft.timestamp !== action.payload | ||||||
|       ) |       ) | ||||||
|  |     }, | ||||||
|  |     disableAllPushes: ({ instances }) => { | ||||||
|  |       instances = instances.map(instance => { | ||||||
|  |         let newInstance = instance | ||||||
|  |         newInstance.push.global.value = false | ||||||
|  |         return newInstance | ||||||
|  |       }) | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   extraReducers: builder => { |   extraReducers: builder => { | ||||||
| @@ -266,22 +271,6 @@ const instancesSlice = createSlice({ | |||||||
|           action.meta.arg.changed |           action.meta.arg.changed | ||||||
|         ].loading = true |         ].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 |  | ||||||
|         }) |  | ||||||
|       }) |  | ||||||
|   } |   } | ||||||
| }) | }) | ||||||
|  |  | ||||||
| @@ -337,7 +326,8 @@ export const { | |||||||
|   updateInstanceActive, |   updateInstanceActive, | ||||||
|   updateInstanceAccount, |   updateInstanceAccount, | ||||||
|   updateInstanceDraft, |   updateInstanceDraft, | ||||||
|   removeInstanceDraft |   removeInstanceDraft, | ||||||
|  |   disableAllPushes | ||||||
| } = instancesSlice.actions | } = instancesSlice.actions | ||||||
|  |  | ||||||
| export default instancesSlice.reducer | export default instancesSlice.reducer | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user