From 31a3e87963523a956364aede09f7bc4b9d62c643 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Fri, 3 Jun 2022 21:25:20 +0200 Subject: [PATCH] Allow push count badge --- src/App.tsx | 15 +- src/screens/Tabs.tsx | 2 +- src/screens/Tabs/Me/Push.tsx | 9 +- src/screens/Tabs/Me/Root/Update.tsx | 2 +- src/store.ts | 4 +- src/utils/push/useConnect.ts | 137 ++++++++++-------- src/utils/slices/appSlice.ts | 71 +++++++++ src/utils/slices/instances/push/register.ts | 3 +- src/utils/slices/instances/updatePush.ts | 14 +- .../slices/instances/updatePushDecode.ts | 13 +- src/utils/slices/versionSlice.ts | 43 ------ 11 files changed, 166 insertions(+), 147 deletions(-) create mode 100644 src/utils/slices/appSlice.ts delete mode 100644 src/utils/slices/versionSlice.ts diff --git a/src/App.tsx b/src/App.tsx index d0344016..496f2c71 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,10 +17,9 @@ import { } from '@utils/slices/settingsSlice' import ThemeManager from '@utils/styles/ThemeManager' import 'expo-asset' -import * as Notifications from 'expo-notifications' import * as SplashScreen from 'expo-splash-screen' import React, { useCallback, useEffect, useState } from 'react' -import { AppState, LogBox, Platform } from 'react-native' +import { LogBox, Platform } from 'react-native' import { GestureHandlerRootView } from 'react-native-gesture-handler' import 'react-native-image-keyboard' import { enableFreeze } from 'react-native-screens' @@ -44,18 +43,6 @@ const App: React.FC = () => { log('log', 'App', 'rendering App') const [localCorrupt, setLocalCorrupt] = useState() - const appStateEffect = useCallback(() => { - Notifications.setBadgeCountAsync(0) - Notifications.dismissAllNotificationsAsync() - }, []) - useEffect(() => { - const appStateListener = AppState.addEventListener('change', appStateEffect) - - return () => { - appStateListener.remove() - } - }, []) - useEffect(() => { const delaySplash = async () => { log('log', 'App', 'delay splash') diff --git a/src/screens/Tabs.tsx b/src/screens/Tabs.tsx index 48f1cf6a..0c18965e 100644 --- a/src/screens/Tabs.tsx +++ b/src/screens/Tabs.tsx @@ -18,7 +18,7 @@ import { import { getVersionUpdate, retriveVersionLatest -} from '@utils/slices/versionSlice' +} from '@utils/slices/appSlice' import { useTheme } from '@utils/styles/ThemeManager' import React, { useCallback, useEffect, useMemo } from 'react' import { Platform } from 'react-native' diff --git a/src/screens/Tabs/Me/Push.tsx b/src/screens/Tabs/Me/Push.tsx index e49cb86e..9e739527 100644 --- a/src/screens/Tabs/Me/Push.tsx +++ b/src/screens/Tabs/Me/Push.tsx @@ -5,6 +5,7 @@ import { MenuContainer, MenuRow } from '@components/Menu' import CustomText from '@components/Text' import { useAppDispatch } from '@root/store' import { isDevelopment } from '@utils/checkEnvironment' +import { getExpoToken } from '@utils/slices/appSlice' import { updateInstancePush } from '@utils/slices/instances/updatePush' import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert' import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode' @@ -45,16 +46,12 @@ const TabMePush: React.FC = () => { setPushEnabled(settings.granted) setPushCanAskAgain(settings.canAskAgain) } + const expoToken = useSelector(getExpoToken) useEffect(() => { if (isDevelopment) { setPushAvailable(true) } else { - Notifications.getExpoPushTokenAsync({ - experienceId: '@xmflsct/tooot', - applicationId: 'com.xmflsct.app.tooot' - }) - .then(data => setPushAvailable(!!data)) - .catch(() => setPushAvailable(false)) + setPushAvailable(!!expoToken) } checkPush() diff --git a/src/screens/Tabs/Me/Root/Update.tsx b/src/screens/Tabs/Me/Root/Update.tsx index 2d2c2d9b..e1d9f6d2 100644 --- a/src/screens/Tabs/Me/Root/Update.tsx +++ b/src/screens/Tabs/Me/Root/Update.tsx @@ -1,5 +1,5 @@ import { MenuContainer, MenuRow } from '@components/Menu' -import { getVersionUpdate } from '@utils/slices/versionSlice' +import { getVersionUpdate } from '@utils/slices/appSlice' import React from 'react' import { useTranslation } from 'react-i18next' import { Linking, Platform } from 'react-native' diff --git a/src/store.ts b/src/store.ts index 81a06675..06942508 100644 --- a/src/store.ts +++ b/src/store.ts @@ -4,10 +4,10 @@ import { AnyAction, configureStore, Reducer } from '@reduxjs/toolkit' import contextsMigration from '@utils/migrations/contexts/migration' import instancesMigration from '@utils/migrations/instances/migration' import settingsMigration from '@utils/migrations/settings/migration' +import appSlice from '@utils/slices/appSlice' import contextsSlice, { ContextsState } from '@utils/slices/contextsSlice' import instancesSlice, { InstancesState } from '@utils/slices/instancesSlice' import settingsSlice, { SettingsState } from '@utils/slices/settingsSlice' -import versionSlice from '@utils/slices/versionSlice' import { Platform } from 'react-native' import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' import { @@ -67,7 +67,7 @@ const store = configureStore({ SettingsState, AnyAction >, - version: versionSlice + app: appSlice }, middleware: getDefaultMiddleware => getDefaultMiddleware({ diff --git a/src/utils/push/useConnect.ts b/src/utils/push/useConnect.ts index 6096f4a9..9b6453dd 100644 --- a/src/utils/push/useConnect.ts +++ b/src/utils/push/useConnect.ts @@ -3,13 +3,15 @@ import apiTooot from '@api/tooot' import { displayMessage } from '@components/Message' import navigationRef from '@helpers/navigationRef' import { useAppDispatch } from '@root/store' -import { isDevelopment } from '@utils/checkEnvironment' import { InstanceLatest } from '@utils/migrations/instances/migration' +import { getExpoToken, retriveExpoToken } from '@utils/slices/appSlice' import { disableAllPushes } from '@utils/slices/instancesSlice' import { useTheme } from '@utils/styles/ThemeManager' import * as Notifications from 'expo-notifications' import { useEffect } from 'react' import { TFunction } from 'react-i18next' +import { AppState } from 'react-native' +import { useSelector } from 'react-redux' export interface Params { t: TFunction<'screens'> @@ -19,69 +21,84 @@ export interface Params { const pushUseConnect = ({ t, instances }: Params) => { const dispatch = useAppDispatch() const { theme } = useTheme() + useEffect(() => { + dispatch(retriveExpoToken()) + }, []) + + const expoToken = useSelector(getExpoToken) + + const connect = () => { + apiTooot({ + method: 'get', + url: `push/connect/${expoToken}`, + sentry: true + }).catch(error => { + if (error?.status == 404) { + displayMessage({ + theme, + type: 'error', + duration: 'long', + message: t('pushError.message'), + description: t('pushError.description'), + onPress: () => { + navigationRef.navigate('Screen-Tabs', { + screen: 'Tab-Me', + params: { + screen: 'Tab-Me-Root' + } + }) + navigationRef.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) + + useEffect(() => { + const appStateListener = AppState.addEventListener('change', state => { + console.log('changing state to', state) + if (expoToken && pushEnabled.length && state === 'active') { + Notifications.getBadgeCountAsync().then(count => { + if (count > 0) { + Notifications.setBadgeCountAsync(0) + connect() + } + }) + } + }) + + return () => { + appStateListener.remove() + } + }, [expoToken, pushEnabled.length]) return useEffect(() => { - const connect = async () => { - const expoToken = isDevelopment - ? 'DEVELOPMENT_TOKEN_1' - : ( - await Notifications.getExpoPushTokenAsync({ - experienceId: '@xmflsct/tooot', - applicationId: 'com.xmflsct.app.tooot' - }) - ).data - - apiTooot({ - method: 'get', - url: `push/connect/${expoToken}`, - sentry: true - }).catch(error => { - if (error?.status == 404) { - displayMessage({ - theme, - type: 'error', - duration: 'long', - message: t('pushError.message'), - description: t('pushError.description'), - onPress: () => { - navigationRef.navigate('Screen-Tabs', { - screen: 'Tab-Me', - params: { - screen: 'Tab-Me-Root' - } - }) - navigationRef.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) { + if (expoToken && pushEnabled.length) { connect() } - }, [instances]) + }, [expoToken, pushEnabled.length]) } export default pushUseConnect diff --git a/src/utils/slices/appSlice.ts b/src/utils/slices/appSlice.ts new file mode 100644 index 00000000..dc793133 --- /dev/null +++ b/src/utils/slices/appSlice.ts @@ -0,0 +1,71 @@ +import apiGeneral from '@api/general' +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' +import { RootState } from '@root/store' +import { isDevelopment } from '@utils/checkEnvironment' +import Constants from 'expo-constants' +import * as Notifications from 'expo-notifications' + +export const retriveExpoToken = createAsyncThunk( + 'app/expoToken', + async (): Promise => { + if (isDevelopment) { + return 'DEVELOPMENT_TOKEN_1' + } + + const res = await Notifications.getExpoPushTokenAsync({ + experienceId: '@xmflsct/tooot', + applicationId: 'com.xmflsct.app.tooot' + }) + return res.data + } +) + +export const retriveVersionLatest = createAsyncThunk( + 'app/versionUpdate', + async (): Promise => { + const res = await apiGeneral<{ latest: string }>({ + method: 'get', + domain: 'tooot.app', + url: 'version.json' + }) + return res.body.latest + } +) + +export type AppState = { + expoToken?: string + versionUpdate: boolean +} + +export const appInitialState: AppState = { + expoToken: undefined, + versionUpdate: false +} + +const appSlice = createSlice({ + name: 'app', + initialState: appInitialState, + reducers: {}, + extraReducers: builder => { + builder + .addCase(retriveExpoToken.fulfilled, (state, action) => { + if (action.payload) { + state.expoToken = action.payload + } + }) + .addCase(retriveVersionLatest.fulfilled, (state, action) => { + if (action.payload && Constants.manifest?.version) { + if ( + parseFloat(action.payload) > parseFloat(Constants.manifest.version) + ) { + state.versionUpdate = true + } + } + }) + } +}) + +export const getExpoToken = (state: RootState) => state.app.expoToken +export const getVersionUpdate = (state: RootState) => state.app.versionUpdate + +export default appSlice.reducer diff --git a/src/utils/slices/instances/push/register.ts b/src/utils/slices/instances/push/register.ts index ff43c968..fafad15d 100644 --- a/src/utils/slices/instances/push/register.ts +++ b/src/utils/slices/instances/push/register.ts @@ -48,8 +48,9 @@ const pushRegister = async ( const accountId = instanceAccount.id const accountFull = `@${instanceAccount.acct}@${instanceUri}` + const randomPath = (Math.random() + 1).toString(36).substring(2) - const endpoint = `https://${TOOOT_API_DOMAIN}/push/send/${expoToken}/${instanceUrl}/${accountId}` + const endpoint = `https://${TOOOT_API_DOMAIN}/push/send/${expoToken}/${instanceUrl}/${accountId}/${randomPath}` const auth = base64.encodeFromByteArray(Random.getRandomBytes(16)) const alerts = instancePush.alerts diff --git a/src/utils/slices/instances/updatePush.ts b/src/utils/slices/instances/updatePush.ts index 9d11c546..7a04f9eb 100644 --- a/src/utils/slices/instances/updatePush.ts +++ b/src/utils/slices/instances/updatePush.ts @@ -1,8 +1,6 @@ import { createAsyncThunk } from '@reduxjs/toolkit' import { RootState } from '@root/store' -import { isDevelopment } from '@utils/checkEnvironment' import { InstanceLatest } from '@utils/migrations/instances/migration' -import * as Notifications from 'expo-notifications' import pushRegister from './push/register' import pushUnregister from './push/unregister' @@ -13,14 +11,10 @@ export const updateInstancePush = createAsyncThunk( { getState } ): Promise => { const state = getState() as RootState - const expoToken = isDevelopment - ? 'DEVELOPMENT_TOKEN_1' - : ( - await Notifications.getExpoPushTokenAsync({ - experienceId: '@xmflsct/tooot', - applicationId: 'com.xmflsct.app.tooot' - }) - ).data + const expoToken = state.app.expoToken + if (!expoToken) { + return Promise.reject() + } if (disable) { return await pushRegister(state, expoToken) diff --git a/src/utils/slices/instances/updatePushDecode.ts b/src/utils/slices/instances/updatePushDecode.ts index 3525d170..ac215263 100644 --- a/src/utils/slices/instances/updatePushDecode.ts +++ b/src/utils/slices/instances/updatePushDecode.ts @@ -2,7 +2,6 @@ import apiTooot from '@api/tooot' import { createAsyncThunk } from '@reduxjs/toolkit' import i18n from '@root/i18n/i18n' import { RootState } from '@root/store' -import { isDevelopment } from '@utils/checkEnvironment' import { InstanceLatest } from '@utils/migrations/instances/migration' import * as Notifications from 'expo-notifications' import { Platform } from 'react-native' @@ -21,14 +20,10 @@ export const updateInstancePushDecode = createAsyncThunk( return Promise.reject() } - const expoToken = isDevelopment - ? 'DEVELOPMENT_TOKEN_1' - : ( - await Notifications.getExpoPushTokenAsync({ - experienceId: '@xmflsct/tooot', - applicationId: 'com.xmflsct.app.tooot' - }) - ).data + const expoToken = state.app.expoToken + if (!expoToken) { + return Promise.reject() + } await apiTooot({ method: 'put', diff --git a/src/utils/slices/versionSlice.ts b/src/utils/slices/versionSlice.ts deleted file mode 100644 index 620e6f10..00000000 --- a/src/utils/slices/versionSlice.ts +++ /dev/null @@ -1,43 +0,0 @@ -import apiGeneral from '@api/general' -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' -import { RootState } from '@root/store' -import Constants from 'expo-constants' - -export const retriveVersionLatest = createAsyncThunk( - 'version/latest', - async () => { - const res = await apiGeneral<{ latest: string }>({ - method: 'get', - domain: 'tooot.app', - url: 'version.json' - }) - return res.body.latest - } -) - -export type VersionState = { - update: boolean -} - -export const versionInitialState = { - update: false -} - -const versionSlice = createSlice({ - name: 'version', - initialState: versionInitialState, - reducers: {}, - extraReducers: builder => { - builder.addCase(retriveVersionLatest.fulfilled, (state, action) => { - if (action.payload && Constants.manifest?.version) { - if (parseFloat(action.payload) > parseFloat(Constants.manifest.version)) { - state.update = true - } - } - }) - } -}) - -export const getVersionUpdate = (state: RootState) => state.version.update - -export default versionSlice.reducer