Refine notifications

https://github.com/tooot-app/app/issues/306

https://github.com/tooot-app/app/issues/305 This one uses the positive filtering that is added since v3.5, that a such a filter won't be shown as there is no way to check if a user is an admin or not and showing a useless option for majority users won't be a good experience.
This commit is contained in:
Zhiyuan Zheng 2022-05-28 19:24:08 +02:00
parent 5a23b73f69
commit 4398e520ed
24 changed files with 254 additions and 86 deletions

View File

@ -341,6 +341,7 @@ declare namespace Mastodon {
| 'favourite' | 'favourite'
| 'poll' | 'poll'
| 'status' | 'status'
| 'update'
created_at: string created_at: string
account: Account account: Account

View File

@ -1,8 +1,9 @@
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { useAppDispatch } from '@root/store' import { useAppDispatch } from '@root/store'
import { InstanceLatest } from '@utils/migrations/instances/migration'
import { TabMeStackNavigationProp } from '@utils/navigation/navigators' import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
import addInstance from '@utils/slices/instances/add' import addInstance from '@utils/slices/instances/add'
import { checkInstanceFeature, Instance } from '@utils/slices/instancesSlice' import { checkInstanceFeature } from '@utils/slices/instancesSlice'
import * as AuthSession from 'expo-auth-session' import * as AuthSession from 'expo-auth-session'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { useQueryClient } from 'react-query' import { useQueryClient } from 'react-query'
@ -12,7 +13,7 @@ export interface Props {
instanceDomain: string instanceDomain: string
// Domain can be different than uri // Domain can be different than uri
instance: Mastodon.Instance instance: Mastodon.Instance
appData: Instance['appData'] appData: InstanceLatest['appData']
goBack?: boolean goBack?: boolean
} }

View File

@ -34,7 +34,7 @@ const TimelineActioned = React.memo(
navigation.push('Tab-Shared-Account', { account }) navigation.push('Tab-Shared-Account', { account })
}, []) }, [])
const children = useMemo(() => { const children = () => {
switch (action) { switch (action) {
case 'pinned': case 'pinned':
return ( return (
@ -48,7 +48,6 @@ const TimelineActioned = React.memo(
{content(t('shared.actioned.pinned'))} {content(t('shared.actioned.pinned'))}
</> </>
) )
break
case 'favourite': case 'favourite':
return ( return (
<> <>
@ -63,7 +62,6 @@ const TimelineActioned = React.memo(
</Pressable> </Pressable>
</> </>
) )
break
case 'follow': case 'follow':
return ( return (
<> <>
@ -78,7 +76,6 @@ const TimelineActioned = React.memo(
</Pressable> </Pressable>
</> </>
) )
break
case 'follow_request': case 'follow_request':
return ( return (
<> <>
@ -93,7 +90,6 @@ const TimelineActioned = React.memo(
</Pressable> </Pressable>
</> </>
) )
break
case 'poll': case 'poll':
return ( return (
<> <>
@ -106,7 +102,6 @@ const TimelineActioned = React.memo(
{content(t('shared.actioned.poll'))} {content(t('shared.actioned.poll'))}
</> </>
) )
break
case 'reblog': case 'reblog':
return ( return (
<> <>
@ -125,7 +120,6 @@ const TimelineActioned = React.memo(
</Pressable> </Pressable>
</> </>
) )
break
case 'status': case 'status':
return ( return (
<> <>
@ -140,9 +134,22 @@ const TimelineActioned = React.memo(
</Pressable> </Pressable>
</> </>
) )
break case 'update':
return (
<>
<Icon
name='BarChart2'
size={StyleConstants.Font.Size.S}
color={iconColor}
style={styles.icon}
/>
{content(t('shared.actioned.update'))}
</>
)
default:
return <></>
} }
}, []) }
return ( return (
<View <View
@ -153,8 +160,9 @@ const TimelineActioned = React.memo(
paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S, paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S,
paddingRight: StyleConstants.Spacing.Global.PagePadding paddingRight: StyleConstants.Spacing.Global.PagePadding
}} }}
children={children} >
/> {children()}
</View>
) )
}, },
() => true () => true

View File

@ -8,5 +8,20 @@
"feature": "deprecate_auth_follow", "feature": "deprecate_auth_follow",
"version": 3.5, "version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0" "reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
},
{
"feature": "notification_type_status",
"version": 3.3,
"reference": "https://docs.joinmastodon.org/entities/notification/#required-attributes"
},
{
"feature": "notification_type_update",
"version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
},
{
"feature": "notification_types_positive_filter",
"version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
} }
] ]

View File

@ -29,7 +29,8 @@
"reblog": { "reblog": {
"default": "{{name}} boosted", "default": "{{name}} boosted",
"notification": "{{name}} boosted your toot" "notification": "{{name}} boosted your toot"
} },
"update": "Reblog has been edited"
}, },
"actions": { "actions": {
"reply": { "reply": {

View File

@ -8,11 +8,13 @@
"heading": "Show notification types", "heading": "Show notification types",
"content": { "content": {
"follow": "$t(screenTabs:me.push.follow.heading)", "follow": "$t(screenTabs:me.push.follow.heading)",
"follow_request": "Follow request",
"favourite": "$t(screenTabs:me.push.favourite.heading)", "favourite": "$t(screenTabs:me.push.favourite.heading)",
"reblog": "$t(screenTabs:me.push.reblog.heading)", "reblog": "$t(screenTabs:me.push.reblog.heading)",
"mention": "$t(screenTabs:me.push.mention.heading)", "mention": "$t(screenTabs:me.push.mention.heading)",
"poll": "$t(screenTabs:me.push.poll.heading)", "poll": "$t(screenTabs:me.push.poll.heading)",
"follow_request": "Follow request" "status": "Toot from subscribed users",
"update": "Reblog has been edited"
} }
} }
} }

View File

@ -4,6 +4,7 @@ import MenuHeader from '@components/Menu/Header'
import MenuRow from '@components/Menu/Row' import MenuRow from '@components/Menu/Row'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { import {
checkInstanceFeature,
getInstanceNotificationsFilter, getInstanceNotificationsFilter,
updateInstanceNotificationsFilter updateInstanceNotificationsFilter
} from '@utils/slices/instancesSlice' } from '@utils/slices/instancesSlice'
@ -33,42 +34,63 @@ const ActionsNotificationsFilter: React.FC = () => {
return null return null
} }
const hasTypeStatus = useSelector(
checkInstanceFeature('notification_type_status')
)
const hasTypeUpdate = useSelector(
checkInstanceFeature('notification_type_update')
)
const options = useMemo(() => { const options = useMemo(() => {
return ( return (
instanceNotificationsFilter && instanceNotificationsFilter &&
( (
[ [
'follow', 'follow',
'follow_request',
'favourite', 'favourite',
'reblog', 'reblog',
'mention', 'mention',
'poll', 'poll',
'follow_request' 'status',
'update'
] as [ ] as [
'follow', 'follow',
'follow_request',
'favourite', 'favourite',
'reblog', 'reblog',
'mention', 'mention',
'poll', 'poll',
'follow_request' 'status',
'update'
] ]
).map(type => ( )
<MenuRow .filter(type => {
key={type} switch (type) {
title={t(`content.notificationsFilter.content.${type}`)} case 'status':
switchValue={instanceNotificationsFilter[type]} return hasTypeStatus
switchOnValueChange={() => case 'update':
dispatch( return hasTypeUpdate
updateInstanceNotificationsFilter({ default:
...instanceNotificationsFilter, return true
[type]: !instanceNotificationsFilter[type]
})
)
} }
/> })
)) .map(type => (
<MenuRow
key={type}
title={t(`content.notificationsFilter.content.${type}`)}
switchValue={instanceNotificationsFilter[type]}
switchOnValueChange={() =>
dispatch(
updateInstanceNotificationsFilter({
...instanceNotificationsFilter,
[type]: !instanceNotificationsFilter[type]
})
)
}
/>
))
) )
}, [instanceNotificationsFilter]) }, [instanceNotificationsFilter, hasTypeStatus, hasTypeUpdate])
return ( return (
<> <>

View File

@ -5,11 +5,8 @@ import ComponentInstance from '@components/Instance'
import CustomText from '@components/Text' import CustomText from '@components/Text'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import initQuery from '@utils/initQuery' import initQuery from '@utils/initQuery'
import { import { InstanceLatest } from '@utils/migrations/instances/migration'
getInstanceActive, import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
getInstances,
Instance
} from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
@ -19,7 +16,7 @@ import { ScrollView } from 'react-native-gesture-handler'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
interface Props { interface Props {
instance: Instance instance: InstanceLatest
selected?: boolean selected?: boolean
} }

View File

@ -1,4 +1,3 @@
import { isRelease } from '@utils/checkEnvironment'
import * as Sentry from 'sentry-expo' import * as Sentry from 'sentry-expo'
import log from './log' import log from './log'
@ -7,7 +6,7 @@ const sentry = () => {
Sentry.init({ Sentry.init({
dsn: 'https://53348b60ff844d52886e90251b3a5f41@o917354.ingest.sentry.io/6410576', dsn: 'https://53348b60ff844d52886e90251b3a5f41@o917354.ingest.sentry.io/6410576',
enableInExpoDevelopment: false, enableInExpoDevelopment: false,
debug: !isRelease, // debug: !isRelease,
autoSessionTracking: true autoSessionTracking: true
}) })
} }

View File

@ -39,7 +39,7 @@ const instancesPersistConfig = {
key: 'instances', key: 'instances',
prefix, prefix,
storage: Platform.OS === 'ios' ? secureStorage : AsyncStorage, storage: Platform.OS === 'ios' ? secureStorage : AsyncStorage,
version: 9, version: 10,
// @ts-ignore // @ts-ignore
migrate: createMigrate(instancesMigration) migrate: createMigrate(instancesMigration)
} }

View File

@ -1,13 +1,14 @@
import queryClient from '@helpers/queryClient' import queryClient from '@helpers/queryClient'
import { store } from '@root/store' import { store } from '@root/store'
import { InstanceLatest } from './migrations/instances/migration'
// import { prefetchTimelineQuery } from './queryHooks/timeline' // import { prefetchTimelineQuery } from './queryHooks/timeline'
import { Instance, updateInstanceActive } from './slices/instancesSlice' import { updateInstanceActive } from './slices/instancesSlice'
const initQuery = async ({ const initQuery = async ({
instance, instance,
prefetch prefetch
}: { }: {
instance: Instance instance: InstanceLatest
prefetch?: { enabled: boolean; page?: 'Following' | 'LocalPublic' } prefetch?: { enabled: boolean; page?: 'Following' | 'LocalPublic' }
}) => { }) => {
store.dispatch(updateInstanceActive(instance)) store.dispatch(updateInstanceActive(instance))

View File

@ -5,6 +5,7 @@ import { InstanceV6 } from './v6'
import { InstanceV7 } from './v7' import { InstanceV7 } from './v7'
import { InstanceV8 } from './v8' import { InstanceV8 } from './v8'
import { InstanceV9 } from './v9' import { InstanceV9 } from './v9'
import { InstanceV10 } from './v10'
const instancesMigration = { const instancesMigration = {
4: (state: InstanceV3): InstanceV4 => { 4: (state: InstanceV3): InstanceV4 => {
@ -99,7 +100,23 @@ const instancesMigration = {
} }
}) })
} }
},
10: (state: { instances: InstanceV9[] }): { instances: InstanceV10[] } => {
return {
instances: state.instances.map(instance => {
return {
...instance,
notifications_filter: {
...instance.notifications_filter,
status: true,
update: true
}
}
})
}
} }
} }
export { InstanceV10 as InstanceLatest }
export default instancesMigration export default instancesMigration

View File

@ -0,0 +1,81 @@
import { ComposeStateDraft } from '@screens/Compose/utils/types'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
export type InstanceV10 = {
active: boolean
appData: {
clientId: string
clientSecret: string
}
url: string
token: string
uri: Mastodon.Instance['uri']
urls: Mastodon.Instance['urls']
account: {
id: Mastodon.Account['id']
acct: Mastodon.Account['acct']
avatarStatic: Mastodon.Account['avatar_static']
preferences: Mastodon.Preferences
}
version: string
configuration?: Mastodon.Instance['configuration']
filters: Mastodon.Filter[]
notifications_filter: {
follow: boolean
follow_request: boolean
favourite: boolean
reblog: boolean
mention: boolean
poll: boolean
status: boolean
update: boolean
}
push: {
global: { loading: boolean; value: boolean }
decode: { loading: boolean; value: boolean }
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 // legacy
private?: string // legacy
}
}
timelinesLookback?: {
[key: string]: {
queryKey: QueryKeyTimeline
ids: Mastodon.Status['id'][]
}
}
mePage: {
lists: { shown: boolean }
announcements: { shown: boolean; unread: number }
}
drafts: ComposeStateDraft[]
frequentEmojis: {
emoji: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>
score: number
count: number
lastUsed: number
}[]
}

View File

@ -4,7 +4,8 @@ import { displayMessage } from '@components/Message'
import navigationRef from '@helpers/navigationRef' import navigationRef from '@helpers/navigationRef'
import { useAppDispatch } from '@root/store' import { useAppDispatch } from '@root/store'
import { isDevelopment } from '@utils/checkEnvironment' import { isDevelopment } from '@utils/checkEnvironment'
import { disableAllPushes, Instance } from '@utils/slices/instancesSlice' import { InstanceLatest } from '@utils/migrations/instances/migration'
import { disableAllPushes } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import { useEffect } from 'react' import { useEffect } from 'react'
@ -12,7 +13,7 @@ import { TFunction } from 'react-i18next'
export interface Params { export interface Params {
t: TFunction<'screens'> t: TFunction<'screens'>
instances: Instance[] instances: InstanceLatest[]
} }
const pushUseConnect = ({ t, instances }: Params) => { const pushUseConnect = ({ t, instances }: Params) => {

View File

@ -1,14 +1,14 @@
import { displayMessage } from '@components/Message' import { displayMessage } from '@components/Message'
import queryClient from '@helpers/queryClient' import queryClient from '@helpers/queryClient'
import initQuery from '@utils/initQuery' import initQuery from '@utils/initQuery'
import { InstanceLatest } from '@utils/migrations/instances/migration'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { Instance } from '@utils/slices/instancesSlice'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import { useEffect } from 'react' import { useEffect } from 'react'
import pushUseNavigate from './useNavigate' import pushUseNavigate from './useNavigate'
export interface Params { export interface Params {
instances: Instance[] instances: InstanceLatest[]
} }
const pushUseReceive = ({ instances }: Params) => { const pushUseReceive = ({ instances }: Params) => {

View File

@ -1,13 +1,13 @@
import queryClient from '@helpers/queryClient' import queryClient from '@helpers/queryClient'
import initQuery from '@utils/initQuery' import initQuery from '@utils/initQuery'
import { InstanceLatest } from '@utils/migrations/instances/migration'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { Instance } from '@utils/slices/instancesSlice'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import { useEffect } from 'react' import { useEffect } from 'react'
import pushUseNavigate from './useNavigate' import pushUseNavigate from './useNavigate'
export interface Params { export interface Params {
instances: Instance[] instances: InstanceLatest[]
} }
const pushUseRespond = ({ instances }: Params) => { const pushUseRespond = ({ instances }: Params) => {

View File

@ -2,7 +2,10 @@ import apiInstance, { InstanceResponse } from '@api/instance'
import haptics from '@components/haptics' import haptics from '@components/haptics'
import queryClient from '@helpers/queryClient' import queryClient from '@helpers/queryClient'
import { store } from '@root/store' import { store } from '@root/store'
import { getInstanceNotificationsFilter } from '@utils/slices/instancesSlice' import {
checkInstanceFeature,
getInstanceNotificationsFilter
} from '@utils/slices/instancesSlice'
import { AxiosError } from 'axios' import { AxiosError } from 'axios'
import { uniqBy } from 'lodash' import { uniqBy } from 'lodash'
import { import {
@ -62,16 +65,26 @@ const queryFunction = async ({
case 'Notifications': case 'Notifications':
const rootStore = store.getState() const rootStore = store.getState()
const notificationsFilter = getInstanceNotificationsFilter(rootStore) const notificationsFilter = getInstanceNotificationsFilter(rootStore)
const usePositiveFilter = checkInstanceFeature(
'notification_types_positive_filter'
)(rootStore)
return apiInstance<Mastodon.Notification[]>({ return apiInstance<Mastodon.Notification[]>({
method: 'get', method: 'get',
url: 'notifications', url: 'notifications',
params: { params: {
...params, ...params,
...(notificationsFilter && { ...(notificationsFilter &&
exclude_types: Object.keys(notificationsFilter) (usePositiveFilter
// @ts-ignore ? {
.filter(filter => notificationsFilter[filter] === false) types: Object.keys(notificationsFilter)
}) // @ts-ignore
.filter(filter => notificationsFilter[filter] === true)
}
: {
exclude_types: Object.keys(notificationsFilter)
// @ts-ignore
.filter(filter => notificationsFilter[filter] === false)
}))
} }
}) })

View File

@ -1,7 +1,7 @@
import apiGeneral from '@api/general' import apiGeneral from '@api/general'
import { createAsyncThunk } from '@reduxjs/toolkit' import { createAsyncThunk } from '@reduxjs/toolkit'
import { RootState } from '@root/store' import { RootState } from '@root/store'
import { Instance } from '../instancesSlice' import { InstanceLatest } from '@utils/migrations/instances/migration'
const addInstance = createAsyncThunk( const addInstance = createAsyncThunk(
'instances/add', 'instances/add',
@ -11,11 +11,11 @@ const addInstance = createAsyncThunk(
instance, instance,
appData appData
}: { }: {
domain: Instance['url'] domain: InstanceLatest['url']
token: Instance['token'] token: InstanceLatest['token']
instance: Mastodon.Instance instance: Mastodon.Instance
appData: Instance['appData'] appData: InstanceLatest['appData']
}): Promise<{ type: 'add' | 'overwrite'; data: Instance }> => { }): Promise<{ type: 'add' | 'overwrite'; data: InstanceLatest }> => {
const { store } = require('@root/store') const { store } = require('@root/store')
const instances = (store.getState() as RootState).instances.instances const instances = (store.getState() as RootState).instances.instances
@ -81,11 +81,13 @@ const addInstance = createAsyncThunk(
filters, filters,
notifications_filter: { notifications_filter: {
follow: true, follow: true,
follow_request: true,
favourite: true, favourite: true,
reblog: true, reblog: true,
mention: true, mention: true,
poll: true, poll: true,
follow_request: true status: true,
update: true
}, },
push: { push: {
global: { loading: false, value: false }, global: { loading: false, value: false },

View File

@ -2,7 +2,8 @@ import apiInstance from '@api/instance'
import apiTooot, { TOOOT_API_DOMAIN } from '@api/tooot' import apiTooot, { TOOOT_API_DOMAIN } from '@api/tooot'
import i18n from '@root/i18n/i18n' import i18n from '@root/i18n/i18n'
import { RootState } from '@root/store' import { RootState } from '@root/store'
import { getInstance, Instance } from '@utils/slices/instancesSlice' import { InstanceLatest } from '@utils/migrations/instances/migration'
import { getInstance } from '@utils/slices/instancesSlice'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import * as Random from 'expo-random' import * as Random from 'expo-random'
import { Platform } from 'react-native' import { Platform } from 'react-native'
@ -34,7 +35,7 @@ const subscribe = async ({
const pushRegister = async ( const pushRegister = async (
state: RootState, state: RootState,
expoToken: string expoToken: string
): Promise<Instance['push']['keys']['auth']> => { ): Promise<InstanceLatest['push']['keys']['auth']> => {
const instance = getInstance(state) const instance = getInstance(state)
const instanceUrl = instance?.url const instanceUrl = instance?.url
const instanceUri = instance?.uri const instanceUri = instance?.uri

View File

@ -1,11 +1,11 @@
import { createAsyncThunk } from '@reduxjs/toolkit' import { createAsyncThunk } from '@reduxjs/toolkit'
import { InstanceLatest } from '@utils/migrations/instances/migration'
import * as AuthSession from 'expo-auth-session' import * as AuthSession from 'expo-auth-session'
import { Instance } from '../instancesSlice'
import { updateInstancePush } from './updatePush' import { updateInstancePush } from './updatePush'
const removeInstance = createAsyncThunk( const removeInstance = createAsyncThunk(
'instances/remove', 'instances/remove',
async (instance: Instance, { dispatch }): Promise<Instance> => { async (instance: InstanceLatest, { dispatch }): Promise<InstanceLatest> => {
if (instance.push.global.value) { if (instance.push.global.value) {
dispatch(updateInstancePush(false)) dispatch(updateInstancePush(false))
} }

View File

@ -1,8 +1,8 @@
import { createAsyncThunk } from '@reduxjs/toolkit' import { createAsyncThunk } from '@reduxjs/toolkit'
import { RootState } from '@root/store' import { RootState } from '@root/store'
import { isDevelopment } from '@utils/checkEnvironment' import { isDevelopment } from '@utils/checkEnvironment'
import { InstanceLatest } from '@utils/migrations/instances/migration'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import { Instance } from '../instancesSlice'
import pushRegister from './push/register' import pushRegister from './push/register'
import pushUnregister from './push/unregister' import pushUnregister from './push/unregister'
@ -11,7 +11,7 @@ export const updateInstancePush = createAsyncThunk(
async ( async (
disable: boolean, disable: boolean,
{ getState } { getState }
): Promise<Instance['push']['keys']['auth'] | undefined> => { ): Promise<InstanceLatest['push']['keys']['auth'] | undefined> => {
const state = getState() as RootState const state = getState() as RootState
const expoToken = isDevelopment const expoToken = isDevelopment
? 'DEVELOPMENT_TOKEN_1' ? 'DEVELOPMENT_TOKEN_1'

View File

@ -1,15 +1,15 @@
import apiInstance from '@api/instance' import apiInstance from '@api/instance'
import { createAsyncThunk } from '@reduxjs/toolkit' import { createAsyncThunk } from '@reduxjs/toolkit'
import { Instance } from '../instancesSlice' import { InstanceLatest } from '@utils/migrations/instances/migration'
export const updateInstancePushAlert = createAsyncThunk( export const updateInstancePushAlert = createAsyncThunk(
'instances/updatePushAlert', 'instances/updatePushAlert',
async ({ async ({
alerts alerts
}: { }: {
changed: keyof Instance['push']['alerts'] changed: keyof InstanceLatest['push']['alerts']
alerts: Instance['push']['alerts'] alerts: InstanceLatest['push']['alerts']
}): Promise<Instance['push']['alerts']> => { }): Promise<InstanceLatest['push']['alerts']> => {
const formData = new FormData() const formData = new FormData()
Object.keys(alerts).map(alert => Object.keys(alerts).map(alert =>
// @ts-ignore // @ts-ignore

View File

@ -3,9 +3,10 @@ import { createAsyncThunk } from '@reduxjs/toolkit'
import i18n from '@root/i18n/i18n' import i18n from '@root/i18n/i18n'
import { RootState } from '@root/store' import { RootState } from '@root/store'
import { isDevelopment } from '@utils/checkEnvironment' import { isDevelopment } from '@utils/checkEnvironment'
import { InstanceLatest } from '@utils/migrations/instances/migration'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import { Platform } from 'react-native' import { Platform } from 'react-native'
import { getInstance, Instance } from '../instancesSlice' import { getInstance } from '../instancesSlice'
import androidDefaults from './push/androidDefaults' import androidDefaults from './push/androidDefaults'
export const updateInstancePushDecode = createAsyncThunk( export const updateInstancePushDecode = createAsyncThunk(
@ -13,7 +14,7 @@ export const updateInstancePushDecode = createAsyncThunk(
async ( async (
disable: boolean, disable: boolean,
{ getState } { getState }
): Promise<{ disable: Instance['push']['decode']['value'] }> => { ): Promise<{ disable: InstanceLatest['push']['decode']['value'] }> => {
const state = getState() as RootState const state = getState() as RootState
const instance = getInstance(state) const instance = getInstance(state)
if (!instance?.url || !instance.account.id || !instance.push.keys) { if (!instance?.url || !instance.account.id || !instance.push.keys) {

View File

@ -3,7 +3,7 @@ import features from '@helpers/features'
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 { InstanceV9 } from '@utils/migrations/instances/v9' import { InstanceLatest } from '@utils/migrations/instances/migration'
import addInstance from './instances/add' import addInstance from './instances/add'
import { checkEmojis } from './instances/checkEmojis' import { checkEmojis } from './instances/checkEmojis'
import removeInstance from './instances/remove' import removeInstance from './instances/remove'
@ -14,24 +14,25 @@ import { updateInstancePush } from './instances/updatePush'
import { updateInstancePushAlert } from './instances/updatePushAlert' import { updateInstancePushAlert } from './instances/updatePushAlert'
import { updateInstancePushDecode } from './instances/updatePushDecode' import { updateInstancePushDecode } from './instances/updatePushDecode'
export type Instance = InstanceV9
export type InstancesState = { export type InstancesState = {
instances: Instance[] instances: InstanceLatest[]
} }
export const instancesInitialState: InstancesState = { export const instancesInitialState: InstancesState = {
instances: [] instances: []
} }
const findInstanceActive = (instances: Instance[]) => const findInstanceActive = (instances: InstanceLatest[]) =>
instances.findIndex(instance => instance.active) instances.findIndex(instance => instance.active)
const instancesSlice = createSlice({ const instancesSlice = createSlice({
name: 'instances', name: 'instances',
initialState: instancesInitialState, initialState: instancesInitialState,
reducers: { reducers: {
updateInstanceActive: ({ instances }, action: PayloadAction<Instance>) => { updateInstanceActive: (
{ instances },
action: PayloadAction<InstanceLatest>
) => {
instances = instances.map(instance => { instances = instances.map(instance => {
instance.active = instance.active =
instance.url === action.payload.url && instance.url === action.payload.url &&
@ -42,7 +43,9 @@ const instancesSlice = createSlice({
}, },
updateInstanceAccount: ( updateInstanceAccount: (
{ instances }, { instances },
action: PayloadAction<Pick<Instance['account'], 'acct' & 'avatarStatic'>> action: PayloadAction<
Pick<InstanceLatest['account'], 'acct' & 'avatarStatic'>
>
) => { ) => {
const activeIndex = findInstanceActive(instances) const activeIndex = findInstanceActive(instances)
instances[activeIndex].account = { instances[activeIndex].account = {
@ -52,7 +55,7 @@ const instancesSlice = createSlice({
}, },
updateInstanceNotificationsFilter: ( updateInstanceNotificationsFilter: (
{ instances }, { instances },
action: PayloadAction<Instance['notifications_filter']> action: PayloadAction<InstanceLatest['notifications_filter']>
) => { ) => {
const activeIndex = findInstanceActive(instances) const activeIndex = findInstanceActive(instances)
instances[activeIndex].notifications_filter = action.payload instances[activeIndex].notifications_filter = action.payload
@ -99,7 +102,7 @@ const instancesSlice = createSlice({
}, },
updateInstanceTimelineLookback: ( updateInstanceTimelineLookback: (
{ instances }, { instances },
action: PayloadAction<Instance['timelinesLookback']> action: PayloadAction<InstanceLatest['timelinesLookback']>
) => { ) => {
const activeIndex = findInstanceActive(instances) const activeIndex = findInstanceActive(instances)
instances[activeIndex] && instances[activeIndex] &&
@ -110,7 +113,7 @@ const instancesSlice = createSlice({
}, },
updateInstanceMePage: ( updateInstanceMePage: (
{ instances }, { instances },
action: PayloadAction<Partial<Instance['mePage']>> action: PayloadAction<Partial<InstanceLatest['mePage']>>
) => { ) => {
const activeIndex = findInstanceActive(instances) const activeIndex = findInstanceActive(instances)
instances[activeIndex].mePage = { instances[activeIndex].mePage = {
@ -120,10 +123,12 @@ const instancesSlice = createSlice({
}, },
countInstanceEmoji: ( countInstanceEmoji: (
{ instances }, { instances },
action: PayloadAction<Instance['frequentEmojis'][0]['emoji']> action: PayloadAction<InstanceLatest['frequentEmojis'][0]['emoji']>
) => { ) => {
const HALF_LIFE = 60 * 60 * 24 * 7 // 1 week const HALF_LIFE = 60 * 60 * 24 * 7 // 1 week
const calculateScore = (emoji: Instance['frequentEmojis'][0]): number => { const calculateScore = (
emoji: InstanceLatest['frequentEmojis'][0]
): number => {
var seconds = (new Date().getTime() - emoji.lastUsed) / 1000 var seconds = (new Date().getTime() - emoji.lastUsed) / 1000
var score = emoji.count + 1 var score = emoji.count + 1
var order = Math.log(Math.max(score, 1)) / Math.LN10 var order = Math.log(Math.max(score, 1)) / Math.LN10
@ -136,7 +141,7 @@ const instancesSlice = createSlice({
e.emoji.shortcode === action.payload.shortcode && e.emoji.shortcode === action.payload.shortcode &&
e.emoji.url === action.payload.url e.emoji.url === action.payload.url
) )
let newEmojisSort: Instance['frequentEmojis'] let newEmojisSort: InstanceLatest['frequentEmojis']
if (foundEmojiIndex > -1) { if (foundEmojiIndex > -1) {
newEmojisSort = instances[activeIndex].frequentEmojis newEmojisSort = instances[activeIndex].frequentEmojis
.map((e, i) => .map((e, i) =>