mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Added notifications filter
This commit is contained in:
		
							
								
								
									
										3
									
								
								src/@types/react-navigation.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/@types/react-navigation.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -12,6 +12,9 @@ declare namespace Nav { | |||||||
|           type: 'account' |           type: 'account' | ||||||
|           account: Mastodon.Account |           account: Mastodon.Account | ||||||
|         } |         } | ||||||
|  |       | { | ||||||
|  |           type: 'notifications_filter' | ||||||
|  |         } | ||||||
|     'Screen-Announcements': { showAll: boolean } |     'Screen-Announcements': { showAll: boolean } | ||||||
|     'Screen-Compose': |     'Screen-Compose': | ||||||
|       | { |       | { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ export type Params = { | |||||||
|   domain?: string |   domain?: string | ||||||
|   url: string |   url: string | ||||||
|   params?: { |   params?: { | ||||||
|     [key: string]: string | number | boolean |     [key: string]: string | number | boolean | string[] | number[] | boolean[] | ||||||
|   } |   } | ||||||
|   headers?: { [key: string]: string } |   headers?: { [key: string]: string } | ||||||
|   body?: FormData | Object |   body?: FormData | Object | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ export type Params = { | |||||||
|   version?: 'v1' | 'v2' |   version?: 'v1' | 'v2' | ||||||
|   url: string |   url: string | ||||||
|   params?: { |   params?: { | ||||||
|     [key: string]: string | number | boolean |     [key: string]: string | number | boolean | string[] | number[] | boolean[] | ||||||
|   } |   } | ||||||
|   headers?: { [key: string]: string } |   headers?: { [key: string]: string } | ||||||
|   body?: FormData |   body?: FormData | ||||||
|   | |||||||
| @@ -32,5 +32,6 @@ export default { | |||||||
|   componentRelativeTime: require('./components/relativeTime').default, |   componentRelativeTime: require('./components/relativeTime').default, | ||||||
|   componentTimeline: require('./components/timeline').default, |   componentTimeline: require('./components/timeline').default, | ||||||
|  |  | ||||||
|  |   screenActions: require('./screens/screenActions').default, | ||||||
|   screenImageViewer: require('./screens/screenImageViewer').default |   screenImageViewer: require('./screens/screenImageViewer').default | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ export default { | |||||||
|     localCorrupt: 'Login expired, please login again' |     localCorrupt: 'Login expired, please login again' | ||||||
|   }, |   }, | ||||||
|   buttons: { |   buttons: { | ||||||
|  |     apply: 'Apply', | ||||||
|     cancel: 'Cancel' |     cancel: 'Cancel' | ||||||
|   }, |   }, | ||||||
|   toastMessage: { |   toastMessage: { | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								src/i18n/en/screens/screenActions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/i18n/en/screens/screenActions.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | export default { | ||||||
|  |   content: { | ||||||
|  |     button: { | ||||||
|  |       apply: '$t(common:buttons.apply)', | ||||||
|  |       cancel: '$t(common:buttons.cancel)' | ||||||
|  |     }, | ||||||
|  |     notificationsFilter: { | ||||||
|  |       heading: 'Show notification types', | ||||||
|  |       content: { | ||||||
|  |         follow: '$t(meSettingsPush:content.follow.heading)', | ||||||
|  |         favourite: '$t(meSettingsPush:content.favourite.heading)', | ||||||
|  |         reblog: '$t(meSettingsPush:content.reblog.heading)', | ||||||
|  |         mention: '$t(meSettingsPush:content.mention.heading)', | ||||||
|  |         poll: '$t(meSettingsPush:content.poll.heading)', | ||||||
|  |         follow_request: 'Follow request' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -32,5 +32,6 @@ export default { | |||||||
|   componentRelativeTime: require('./components/relativeTime').default, |   componentRelativeTime: require('./components/relativeTime').default, | ||||||
|   componentTimeline: require('./components/timeline').default, |   componentTimeline: require('./components/timeline').default, | ||||||
|  |  | ||||||
|  |   screenActions: require('./screens/screenActions').default, | ||||||
|   screenImageViewer: require('./screens/screenImageViewer').default |   screenImageViewer: require('./screens/screenImageViewer').default | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ export default { | |||||||
|     localCorrupt: '登录已过期,请重新登录' |     localCorrupt: '登录已过期,请重新登录' | ||||||
|   }, |   }, | ||||||
|   buttons: { |   buttons: { | ||||||
|  |     apply: '应用', | ||||||
|     cancel: '取消' |     cancel: '取消' | ||||||
|   }, |   }, | ||||||
|   toastMessage: { |   toastMessage: { | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								src/i18n/zh-Hans/screens/screenActions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/i18n/zh-Hans/screens/screenActions.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | export default { | ||||||
|  |   content: { | ||||||
|  |     button: { | ||||||
|  |       apply: '$t(common:buttons.apply)', | ||||||
|  |       cancel: '$t(common:buttons.cancel)' | ||||||
|  |     }, | ||||||
|  |     notificationsFilter: { | ||||||
|  |       heading: '显示通知', | ||||||
|  |       content: { | ||||||
|  |         follow: '$t(meSettingsPush:content.follow.heading)', | ||||||
|  |         favourite: '$t(meSettingsPush:content.favourite.heading)', | ||||||
|  |         reblog: '$t(meSettingsPush:content.reblog.heading)', | ||||||
|  |         mention: '$t(meSettingsPush:content.mention.heading)', | ||||||
|  |         poll: '$t(meSettingsPush:content.poll.heading)', | ||||||
|  |         follow_request: '关注请求' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										94
									
								
								src/screens/Actions/NotificationsFilter.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/screens/Actions/NotificationsFilter.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | import Button from '@components/Button' | ||||||
|  | import MenuContainer from '@components/Menu/Container' | ||||||
|  | import MenuHeader from '@components/Menu/Header' | ||||||
|  | import MenuRow from '@components/Menu/Row' | ||||||
|  | import { useNavigation } from '@react-navigation/native' | ||||||
|  | import { | ||||||
|  |   getInstanceNotificationsFilter, | ||||||
|  |   updateInstanceNotificationsFilter | ||||||
|  | } from '@utils/slices/instancesSlice' | ||||||
|  | import { StyleConstants } from '@utils/styles/constants' | ||||||
|  | import React, { useMemo } from 'react' | ||||||
|  | import { StyleSheet } from 'react-native' | ||||||
|  | import { useTranslation } from 'react-i18next' | ||||||
|  | import { useDispatch, useSelector } from 'react-redux' | ||||||
|  | import { useQueryClient } from 'react-query' | ||||||
|  | import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | ||||||
|  |  | ||||||
|  | const ActionsNotificationsFilter: React.FC = () => { | ||||||
|  |   const navigation = useNavigation() | ||||||
|  |   const { t } = useTranslation('screenActions') | ||||||
|  |  | ||||||
|  |   const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Notifications' }] | ||||||
|  |   const queryClient = useQueryClient() | ||||||
|  |  | ||||||
|  |   const dispatch = useDispatch() | ||||||
|  |   const instanceNotificationsFilter = useSelector( | ||||||
|  |     getInstanceNotificationsFilter | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   if (!instanceNotificationsFilter) { | ||||||
|  |     navigation.goBack() | ||||||
|  |     return null | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const options = useMemo(() => { | ||||||
|  |     return ( | ||||||
|  |       instanceNotificationsFilter && | ||||||
|  |       ([ | ||||||
|  |         'follow', | ||||||
|  |         'favourite', | ||||||
|  |         'reblog', | ||||||
|  |         'mention', | ||||||
|  |         'poll', | ||||||
|  |         'follow_request' | ||||||
|  |       ] as [ | ||||||
|  |         'follow', | ||||||
|  |         'favourite', | ||||||
|  |         'reblog', | ||||||
|  |         'mention', | ||||||
|  |         'poll', | ||||||
|  |         'follow_request' | ||||||
|  |       ]).map(type => ( | ||||||
|  |         <MenuRow | ||||||
|  |           key={type} | ||||||
|  |           title={t(`content.notificationsFilter.content.${type}`)} | ||||||
|  |           switchValue={instanceNotificationsFilter[type]} | ||||||
|  |           switchOnValueChange={() => | ||||||
|  |             dispatch( | ||||||
|  |               updateInstanceNotificationsFilter({ | ||||||
|  |                 ...instanceNotificationsFilter, | ||||||
|  |                 [type]: !instanceNotificationsFilter[type] | ||||||
|  |               }) | ||||||
|  |             ) | ||||||
|  |           } | ||||||
|  |         /> | ||||||
|  |       )) | ||||||
|  |     ) | ||||||
|  |   }, [instanceNotificationsFilter]) | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       <MenuContainer> | ||||||
|  |         <MenuHeader heading={t(`content.notificationsFilter.heading`)} /> | ||||||
|  |         {options} | ||||||
|  |       </MenuContainer> | ||||||
|  |       <Button | ||||||
|  |         type='text' | ||||||
|  |         content={t('content.button.apply')} | ||||||
|  |         onPress={() => { | ||||||
|  |           queryClient.resetQueries(queryKey) | ||||||
|  |         }} | ||||||
|  |         style={styles.button} | ||||||
|  |       /> | ||||||
|  |     </> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const styles = StyleSheet.create({ | ||||||
|  |   button: { | ||||||
|  |     marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2 | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | export default ActionsNotificationsFilter | ||||||
| @@ -28,6 +28,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context' | |||||||
| import { useSelector } from 'react-redux' | import { useSelector } from 'react-redux' | ||||||
| import ActionsAccount from './Account' | import ActionsAccount from './Account' | ||||||
| import ActionsDomain from './Domain' | import ActionsDomain from './Domain' | ||||||
|  | import ActionsNotificationsFilter from './NotificationsFilter' | ||||||
| import ActionsShare from './Share' | import ActionsShare from './Share' | ||||||
| import ActionsStatus from './Status' | import ActionsStatus from './Status' | ||||||
|  |  | ||||||
| @@ -40,21 +41,21 @@ const ScreenActionsRoot = React.memo( | |||||||
|   ({ route: { params }, navigation }: ScreenAccountProp) => { |   ({ route: { params }, navigation }: ScreenAccountProp) => { | ||||||
|     const { t } = useTranslation() |     const { t } = useTranslation() | ||||||
|  |  | ||||||
|     const localAccount = useSelector( |     const instanceAccount = useSelector( | ||||||
|       getInstanceAccount, |       getInstanceAccount, | ||||||
|       (prev, next) => prev?.id === next?.id |       (prev, next) => prev?.id === next?.id | ||||||
|     ) |     ) | ||||||
|     let sameAccount = false |     let sameAccount = false | ||||||
|     switch (params.type) { |     switch (params.type) { | ||||||
|       case 'status': |       case 'status': | ||||||
|         sameAccount = localAccount?.id === params.status.account.id |         sameAccount = instanceAccount?.id === params.status.account.id | ||||||
|         break |         break | ||||||
|       case 'account': |       case 'account': | ||||||
|         sameAccount = localAccount?.id === params.account.id |         sameAccount = instanceAccount?.id === params.account.id | ||||||
|         break |         break | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const localDomain = useSelector(getInstanceUrl) |     const instanceDomain = useSelector(getInstanceUrl) | ||||||
|     let sameDomain = true |     let sameDomain = true | ||||||
|     let statusDomain: string |     let statusDomain: string | ||||||
|     switch (params.type) { |     switch (params.type) { | ||||||
| @@ -62,7 +63,7 @@ const ScreenActionsRoot = React.memo( | |||||||
|         statusDomain = params.status.uri |         statusDomain = params.status.uri | ||||||
|           ? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1] |           ? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1] | ||||||
|           : '' |           : '' | ||||||
|         sameDomain = localDomain === statusDomain |         sameDomain = instanceDomain === statusDomain | ||||||
|         break |         break | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -86,7 +87,6 @@ const ScreenActionsRoot = React.memo( | |||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|     const dismiss = useCallback(() => { |     const dismiss = useCallback(() => { | ||||||
|       panY.value = withTiming(DEFAULT_VALUE) |  | ||||||
|       navigation.goBack() |       navigation.goBack() | ||||||
|     }, []) |     }, []) | ||||||
|     const onGestureEvent = useAnimatedGestureHandler({ |     const onGestureEvent = useAnimatedGestureHandler({ | ||||||
| @@ -137,6 +137,14 @@ const ScreenActionsRoot = React.memo( | |||||||
|                 type={params.type} |                 type={params.type} | ||||||
|                 dismiss={dismiss} |                 dismiss={dismiss} | ||||||
|               /> |               /> | ||||||
|  |               <Button | ||||||
|  |                 type='text' | ||||||
|  |                 content={t('common:buttons.cancel')} | ||||||
|  |                 onPress={() => { | ||||||
|  |                   analytics('bottomsheet_acknowledge') | ||||||
|  |                 }} | ||||||
|  |                 style={styles.button} | ||||||
|  |               /> | ||||||
|             </> |             </> | ||||||
|           ) |           ) | ||||||
|         case 'account': |         case 'account': | ||||||
| @@ -150,8 +158,18 @@ const ScreenActionsRoot = React.memo( | |||||||
|                 type={params.type} |                 type={params.type} | ||||||
|                 dismiss={dismiss} |                 dismiss={dismiss} | ||||||
|               /> |               /> | ||||||
|  |               <Button | ||||||
|  |                 type='text' | ||||||
|  |                 content={t('common:buttons.cancel')} | ||||||
|  |                 onPress={() => { | ||||||
|  |                   analytics('bottomsheet_acknowledge') | ||||||
|  |                 }} | ||||||
|  |                 style={styles.button} | ||||||
|  |               /> | ||||||
|             </> |             </> | ||||||
|           ) |           ) | ||||||
|  |         case 'notifications_filter': | ||||||
|  |           return <ActionsNotificationsFilter /> | ||||||
|       } |       } | ||||||
|     }, []) |     }, []) | ||||||
|  |  | ||||||
| @@ -188,15 +206,6 @@ const ScreenActionsRoot = React.memo( | |||||||
|                   ]} |                   ]} | ||||||
|                 /> |                 /> | ||||||
|                 {actions} |                 {actions} | ||||||
|                 <Button |  | ||||||
|                   type='text' |  | ||||||
|                   content={t('common:buttons.cancel')} |  | ||||||
|                   onPress={() => { |  | ||||||
|                     analytics('bottomsheet_cancel') |  | ||||||
|                     // dismiss() |  | ||||||
|                   }} |  | ||||||
|                   style={styles.button} |  | ||||||
|                 /> |  | ||||||
|               </Animated.View> |               </Animated.View> | ||||||
|             </PanGestureHandler> |             </PanGestureHandler> | ||||||
|           </Animated.View> |           </Animated.View> | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ const Stack = createNativeStackNavigator<Nav.TabLocalStackParamList>() | |||||||
|  |  | ||||||
| const TabLocal = React.memo( | const TabLocal = React.memo( | ||||||
|   ({ navigation }: TabLocalProp) => { |   ({ navigation }: TabLocalProp) => { | ||||||
|     const { t } = useTranslation('local') |     const { t, i18n } = useTranslation('local') | ||||||
|  |  | ||||||
|     const screenOptions = useMemo( |     const screenOptions = useMemo( | ||||||
|       () => ({ |       () => ({ | ||||||
| @@ -48,7 +48,7 @@ const TabLocal = React.memo( | |||||||
|           /> |           /> | ||||||
|         ) |         ) | ||||||
|       }), |       }), | ||||||
|       [] |       [i18n.language] | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Following' }] |     const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Following' }] | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| import { HeaderCenter } from '@components/Header' | import analytics from '@components/analytics' | ||||||
|  | import { HeaderCenter, HeaderRight } from '@components/Header' | ||||||
| import Timeline from '@components/Timeline' | import Timeline from '@components/Timeline' | ||||||
| import TimelineNotifications from '@components/Timeline/Notifications' | import TimelineNotifications from '@components/Timeline/Notifications' | ||||||
|  | import { useNavigation } from '@react-navigation/native' | ||||||
| import sharedScreens from '@screens/Tabs/Shared/sharedScreens' | import sharedScreens from '@screens/Tabs/Shared/sharedScreens' | ||||||
| import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | ||||||
| import React, { useCallback, useMemo } from 'react' | import React, { useCallback, useMemo } from 'react' | ||||||
| @@ -12,9 +14,17 @@ const Stack = createNativeStackNavigator<Nav.TabNotificationsStackParamList>() | |||||||
|  |  | ||||||
| const TabNotifications = React.memo( | const TabNotifications = React.memo( | ||||||
|   () => { |   () => { | ||||||
|     const { t } = useTranslation() |     const navigation = useNavigation() | ||||||
|  |     const { t, i18n } = useTranslation() | ||||||
|  |  | ||||||
|     const screenOptions = useMemo( |     const screenOptions = useMemo( | ||||||
|  |       () => ({ | ||||||
|  |         headerHideShadow: true, | ||||||
|  |         headerTopInsetEnabled: false | ||||||
|  |       }), | ||||||
|  |       [] | ||||||
|  |     ) | ||||||
|  |     const screenOptionsRoot = useMemo( | ||||||
|       () => ({ |       () => ({ | ||||||
|         headerTitle: t('notifications:heading'), |         headerTitle: t('notifications:heading'), | ||||||
|         ...(Platform.OS === 'android' && { |         ...(Platform.OS === 'android' && { | ||||||
| @@ -22,10 +32,19 @@ const TabNotifications = React.memo( | |||||||
|             <HeaderCenter content={t('notifications:heading')} /> |             <HeaderCenter content={t('notifications:heading')} /> | ||||||
|           ) |           ) | ||||||
|         }), |         }), | ||||||
|         headerHideShadow: true, |         headerRight: () => ( | ||||||
|         headerTopInsetEnabled: false |           <HeaderRight | ||||||
|  |             content='Filter' | ||||||
|  |             onPress={() => { | ||||||
|  |               analytics('notificationsfilter_tap') | ||||||
|  |               navigation.navigate('Screen-Actions', { | ||||||
|  |                 type: 'notifications_filter' | ||||||
|  |               }) | ||||||
|  |             }} | ||||||
|  |           /> | ||||||
|  |         ) | ||||||
|       }), |       }), | ||||||
|       [] |       [i18n.language] | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Notifications' }] |     const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Notifications' }] | ||||||
| @@ -42,7 +61,11 @@ const TabNotifications = React.memo( | |||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
|       <Stack.Navigator screenOptions={screenOptions}> |       <Stack.Navigator screenOptions={screenOptions}> | ||||||
|         <Stack.Screen name='Tab-Notifications-Root' children={children} /> |         <Stack.Screen | ||||||
|  |           name='Tab-Notifications-Root' | ||||||
|  |           children={children} | ||||||
|  |           options={screenOptionsRoot} | ||||||
|  |         /> | ||||||
|         {sharedScreens(Stack as any)} |         {sharedScreens(Stack as any)} | ||||||
|       </Stack.Navigator> |       </Stack.Navigator> | ||||||
|     ) |     ) | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								src/store.ts
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								src/store.ts
									
									
									
									
									
								
							| @@ -5,7 +5,7 @@ import { | |||||||
|   configureStore, |   configureStore, | ||||||
|   getDefaultMiddleware |   getDefaultMiddleware | ||||||
| } from '@reduxjs/toolkit' | } from '@reduxjs/toolkit' | ||||||
| import { InstancesV3 } from '@utils/migrations/instances/v3' | import instancesMigration from '@utils/migrations/instances/migration' | ||||||
| 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' | ||||||
| @@ -21,40 +21,13 @@ 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, |   version: 5, | ||||||
|   // @ts-ignore |   // @ts-ignore | ||||||
|   migrate: createMigrate(instancesMigration) |   migrate: createMigrate(instancesMigration, { debug: true }) | ||||||
| } | } | ||||||
|  |  | ||||||
| const settingsPersistConfig = { | const settingsPersistConfig = { | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								src/utils/migrations/instances/migration.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/utils/migrations/instances/migration.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | import { InstanceV3 } from './v3' | ||||||
|  | import { InstanceV4 } from './v4' | ||||||
|  |  | ||||||
|  | const instancesMigration = { | ||||||
|  |   4: (state: InstanceV3) => { | ||||||
|  |     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 | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   5: (state: InstanceV4) => { | ||||||
|  |     // Migration is run on each start, don't know why | ||||||
|  |     // @ts-ignore | ||||||
|  |     if (state.instances.length && !state.instances[0].notifications_filter) { | ||||||
|  |       return { | ||||||
|  |         instances: state.instances.map(instance => { | ||||||
|  |           // @ts-ignore | ||||||
|  |           instance.notifications_filter = { | ||||||
|  |             follow: true, | ||||||
|  |             favourite: true, | ||||||
|  |             reblog: true, | ||||||
|  |             mention: true, | ||||||
|  |             poll: true, | ||||||
|  |             follow_request: true | ||||||
|  |           } | ||||||
|  |           return instance | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       return state | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default instancesMigration | ||||||
| @@ -21,7 +21,7 @@ type InstanceLocal = { | |||||||
|   drafts: any[] |   drafts: any[] | ||||||
| } | } | ||||||
|  |  | ||||||
| export type InstancesV3 = { | export type InstanceV3 = { | ||||||
|   local: { |   local: { | ||||||
|     activeIndex: number | null |     activeIndex: number | null | ||||||
|     instances: InstanceLocal[] |     instances: InstanceLocal[] | ||||||
|   | |||||||
							
								
								
									
										84
									
								
								src/utils/migrations/instances/v4.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/utils/migrations/instances/v4.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | import { ComposeStateDraft } from "@screens/Compose/utils/types" | ||||||
|  |  | ||||||
|  | type Instance = { | ||||||
|  |   active: boolean | ||||||
|  |   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 | ||||||
|  |   } | ||||||
|  |   push: | ||||||
|  |     | { | ||||||
|  |         global: { loading: boolean; value: boolean } | ||||||
|  |         decode: { loading: boolean; value: true } | ||||||
|  |         alerts: { | ||||||
|  |           follow: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['follow'] | ||||||
|  |           } | ||||||
|  |           favourite: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['favourite'] | ||||||
|  |           } | ||||||
|  |           reblog: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['reblog'] | ||||||
|  |           } | ||||||
|  |           mention: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['mention'] | ||||||
|  |           } | ||||||
|  |           poll: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['poll'] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         keys: { | ||||||
|  |           auth: string | ||||||
|  |           public: string | ||||||
|  |           private: string | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     | { | ||||||
|  |         global: { loading: boolean; value: boolean } | ||||||
|  |         decode: { loading: boolean; value: false } | ||||||
|  |         alerts: { | ||||||
|  |           follow: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['follow'] | ||||||
|  |           } | ||||||
|  |           favourite: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['favourite'] | ||||||
|  |           } | ||||||
|  |           reblog: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['reblog'] | ||||||
|  |           } | ||||||
|  |           mention: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['mention'] | ||||||
|  |           } | ||||||
|  |           poll: { | ||||||
|  |             loading: boolean | ||||||
|  |             value: Mastodon.PushSubscription['alerts']['poll'] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         keys: undefined | ||||||
|  |       } | ||||||
|  |   drafts: ComposeStateDraft[] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export type InstanceV4 = { | ||||||
|  |   instances: Instance[] | ||||||
|  | } | ||||||
| @@ -1,5 +1,10 @@ | |||||||
| import apiInstance from '@api/instance' | import apiInstance from '@api/instance' | ||||||
| import haptics from '@components/haptics' | import haptics from '@components/haptics' | ||||||
|  | import { store } from '@root/store' | ||||||
|  | import { | ||||||
|  |   getInstanceActive, | ||||||
|  |   getInstanceNotificationsFilter | ||||||
|  | } from '@utils/slices/instancesSlice' | ||||||
| import { AxiosError } from 'axios' | import { AxiosError } from 'axios' | ||||||
| import { uniqBy } from 'lodash' | import { uniqBy } from 'lodash' | ||||||
| import { | import { | ||||||
| @@ -59,10 +64,19 @@ const queryFunction = ({ | |||||||
|       }) |       }) | ||||||
|  |  | ||||||
|     case 'Notifications': |     case 'Notifications': | ||||||
|  |       const rootStore = store.getState() | ||||||
|  |       const notificationsFilter = getInstanceNotificationsFilter(rootStore) | ||||||
|       return apiInstance<Mastodon.Notification[]>({ |       return apiInstance<Mastodon.Notification[]>({ | ||||||
|         method: 'get', |         method: 'get', | ||||||
|         url: 'notifications', |         url: 'notifications', | ||||||
|         params |         params: { | ||||||
|  |           ...params, | ||||||
|  |           ...(notificationsFilter && { | ||||||
|  |             exclude_types: Object.keys(notificationsFilter) | ||||||
|  |               // @ts-ignore | ||||||
|  |               .filter(filter => notificationsFilter[filter] === false) | ||||||
|  |           }) | ||||||
|  |         } | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|     case 'Account_Default': |     case 'Account_Default': | ||||||
|   | |||||||
| @@ -70,6 +70,14 @@ const addInstance = createAsyncThunk( | |||||||
|           avatarStatic: avatar_static, |           avatarStatic: avatar_static, | ||||||
|           preferences |           preferences | ||||||
|         }, |         }, | ||||||
|  |         notifications_filter: { | ||||||
|  |           follow: true, | ||||||
|  |           favourite: true, | ||||||
|  |           reblog: true, | ||||||
|  |           mention: true, | ||||||
|  |           poll: true, | ||||||
|  |           follow_request: true | ||||||
|  |         }, | ||||||
|         push: { |         push: { | ||||||
|           global: { loading: false, value: false }, |           global: { loading: false, value: false }, | ||||||
|           decode: { loading: false, value: false }, |           decode: { loading: false, value: false }, | ||||||
|   | |||||||
| @@ -29,6 +29,14 @@ export type Instance = { | |||||||
|     avatarStatic: Mastodon.Account['avatar_static'] |     avatarStatic: Mastodon.Account['avatar_static'] | ||||||
|     preferences: Mastodon.Preferences |     preferences: Mastodon.Preferences | ||||||
|   } |   } | ||||||
|  |   notifications_filter: { | ||||||
|  |     follow: boolean | ||||||
|  |     favourite: boolean | ||||||
|  |     reblog: boolean | ||||||
|  |     mention: boolean | ||||||
|  |     poll: boolean | ||||||
|  |     follow_request: boolean | ||||||
|  |   } | ||||||
|   push: |   push: | ||||||
|     | { |     | { | ||||||
|         global: { loading: boolean; value: boolean } |         global: { loading: boolean; value: boolean } | ||||||
| @@ -55,13 +63,11 @@ export type Instance = { | |||||||
|             value: Mastodon.PushSubscription['alerts']['poll'] |             value: Mastodon.PushSubscription['alerts']['poll'] | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         keys: |         keys: { | ||||||
|           | { |  | ||||||
|           auth: string |           auth: string | ||||||
|           public: string |           public: string | ||||||
|           private: string |           private: string | ||||||
|         } |         } | ||||||
|           | undefined |  | ||||||
|       } |       } | ||||||
|     | { |     | { | ||||||
|         global: { loading: boolean; value: boolean } |         global: { loading: boolean; value: boolean } | ||||||
| @@ -127,6 +133,13 @@ const instancesSlice = createSlice({ | |||||||
|         ...action.payload |         ...action.payload | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     updateInstanceNotificationsFilter: ( | ||||||
|  |       { instances }, | ||||||
|  |       action: PayloadAction<Instance['notifications_filter']> | ||||||
|  |     ) => { | ||||||
|  |       const activeIndex = findInstanceActive(instances) | ||||||
|  |       instances[activeIndex].notifications_filter = action.payload | ||||||
|  |     }, | ||||||
|     updateInstanceDraft: ( |     updateInstanceDraft: ( | ||||||
|       { instances }, |       { instances }, | ||||||
|       action: PayloadAction<ComposeStateDraft> |       action: PayloadAction<ComposeStateDraft> | ||||||
| @@ -314,6 +327,15 @@ export const getInstanceAccount = ({ instances: { instances } }: RootState) => { | |||||||
|   return instanceActive !== -1 ? instances[instanceActive].account : null |   return instanceActive !== -1 ? instances[instanceActive].account : null | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export const getInstanceNotificationsFilter = ({ | ||||||
|  |   instances: { instances } | ||||||
|  | }: RootState) => { | ||||||
|  |   const instanceActive = findInstanceActive(instances) | ||||||
|  |   return instanceActive !== -1 | ||||||
|  |     ? instances[instanceActive].notifications_filter | ||||||
|  |     : null | ||||||
|  | } | ||||||
|  |  | ||||||
| export const getInstancePush = ({ instances: { instances } }: RootState) => { | export const getInstancePush = ({ instances: { instances } }: RootState) => { | ||||||
|   const instanceActive = findInstanceActive(instances) |   const instanceActive = findInstanceActive(instances) | ||||||
|   return instanceActive !== -1 ? instances[instanceActive].push : null |   return instanceActive !== -1 ? instances[instanceActive].push : null | ||||||
| @@ -327,6 +349,7 @@ export const getInstanceDrafts = ({ instances: { instances } }: RootState) => { | |||||||
| export const { | export const { | ||||||
|   updateInstanceActive, |   updateInstanceActive, | ||||||
|   updateInstanceAccount, |   updateInstanceAccount, | ||||||
|  |   updateInstanceNotificationsFilter, | ||||||
|   updateInstanceDraft, |   updateInstanceDraft, | ||||||
|   removeInstanceDraft, |   removeInstanceDraft, | ||||||
|   disableAllPushes |   disableAllPushes | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user