Allow push count badge

This commit is contained in:
Zhiyuan Zheng 2022-06-03 21:25:20 +02:00
parent 9b109ba1bb
commit 31a3e87963
11 changed files with 166 additions and 147 deletions

View File

@ -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<string>()
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')

View File

@ -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'

View File

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

View File

@ -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'

View File

@ -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({

View File

@ -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

View File

@ -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<string> => {
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<string> => {
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

View File

@ -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

View File

@ -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<InstanceLatest['push']['keys']['auth'] | undefined> => {
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)

View File

@ -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',

View File

@ -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