1
0
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:
Zhiyuan Zheng
2021-03-04 01:03:53 +01:00
parent a4a6e9316b
commit cc02626adb
17 changed files with 255 additions and 135 deletions

View 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
}
}

View 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

View 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

View File

@ -0,0 +1,58 @@
import { displayMessage } from '@components/Message'
import { NavigationContainerRef } from '@react-navigation/native'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { Instance, updateInstanceActive } from '@utils/slices/instancesSlice'
import * as Notifications from 'expo-notifications'
import { findIndex } from 'lodash'
import { useEffect } from 'react'
import { QueryClient } from 'react-query'
import { useDispatch } from 'react-redux'
import pushUseNavigate from './useNavigate'
export interface Params {
navigationRef: React.RefObject<NavigationContainerRef>
queryClient: QueryClient
instances: Instance[]
}
const pushUseReceive = ({ navigationRef, queryClient, instances }: Params) => {
const dispatch = useDispatch()
return useEffect(() => {
const subscription = Notifications.addNotificationReceivedListener(
notification => {
const queryKey: QueryKeyTimeline = [
'Timeline',
{ page: 'Notifications' }
]
queryClient.invalidateQueries(queryKey)
const payloadData = notification.request.content.data as {
notification_id?: string
instanceUrl: string
accountId: string
}
const notificationIndex = findIndex(
instances,
instance =>
instance.url === payloadData.instanceUrl &&
instance.account.id === payloadData.accountId
)
displayMessage({
duration: 'long',
message: notification.request.content.title!,
description: notification.request.content.body!,
onPress: () => {
if (notificationIndex !== -1) {
dispatch(updateInstanceActive(instances[notificationIndex]))
}
pushUseNavigate(navigationRef, payloadData.notification_id)
}
})
}
)
return () => subscription.remove()
}, [instances])
}
export default pushUseReceive

View File

@ -0,0 +1,54 @@
import { NavigationContainerRef } from '@react-navigation/native'
import { Dispatch } from '@reduxjs/toolkit'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { Instance, updateInstanceActive } from '@utils/slices/instancesSlice'
import * as Notifications from 'expo-notifications'
import { findIndex } from 'lodash'
import { useEffect } from 'react'
import { QueryClient } from 'react-query'
import pushUseNavigate from './useNavigate'
export interface Params {
navigationRef: React.RefObject<NavigationContainerRef>
queryClient: QueryClient
instances: Instance[]
dispatch: Dispatch<any>
}
const pushUseRespond = ({
navigationRef,
queryClient,
instances,
dispatch
}: Params) => {
return useEffect(() => {
const subscription = Notifications.addNotificationResponseReceivedListener(
({ notification }) => {
const queryKey: QueryKeyTimeline = [
'Timeline',
{ page: 'Notifications' }
]
queryClient.invalidateQueries(queryKey)
const payloadData = notification.request.content.data as {
notification_id?: string
instanceUrl: string
accountId: string
}
const notificationIndex = findIndex(
instances,
instance =>
instance.url === payloadData.instanceUrl &&
instance.account.id === payloadData.accountId
)
if (notificationIndex !== -1) {
dispatch(updateInstanceActive(instances[notificationIndex]))
}
pushUseNavigate(navigationRef, payloadData.notification_id)
}
)
return () => subscription.remove()
}, [instances])
}
export default pushUseRespond

View File

@ -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()
}
}
)

View File

@ -1,11 +1,9 @@
import analytics from '@components/analytics'
import { displayMessage } from '@components/Message'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { RootState } from '@root/store'
import { ComposeStateDraft } from '@screens/Compose/utils/types'
import { findIndex } from 'lodash'
import addInstance from './instances/add'
import { connectInstancesPush } from './instances/connectPush'
import removeInstance from './instances/remove'
import { updateAccountPreferences } from './instances/updateAccountPreferences'
import { updateInstancePush } from './instances/updatePush'
@ -150,6 +148,13 @@ const instancesSlice = createSlice({
instances[activeIndex].drafts = instances[activeIndex].drafts?.filter(
draft => draft.timestamp !== action.payload
)
},
disableAllPushes: ({ instances }) => {
instances = instances.map(instance => {
let newInstance = instance
newInstance.push.global.value = false
return newInstance
})
}
},
extraReducers: builder => {
@ -266,22 +271,6 @@ const instancesSlice = createSlice({
action.meta.arg.changed
].loading = true
})
// If Expo token does not exist on the server, disable all existing ones
.addCase(connectInstancesPush.rejected, (state, action) => {
state.instances = state.instances.map(instance => {
let newInstance = instance
newInstance.push.global.value = false
displayMessage({
mode: action.meta.arg.mode,
type: 'error',
autoHide: false,
message: action.meta.arg.t('meSettingsPush:error.message'),
description: action.meta.arg.t('meSettingsPush:error.description')
})
return newInstance
})
})
}
})
@ -337,7 +326,8 @@ export const {
updateInstanceActive,
updateInstanceAccount,
updateInstanceDraft,
removeInstanceDraft
removeInstanceDraft,
disableAllPushes
} = instancesSlice.actions
export default instancesSlice.reducer