mirror of
https://github.com/tooot-app/app
synced 2025-01-30 10:14:53 +01:00
With unread notifications
This commit is contained in:
parent
4461cd1fa4
commit
b5f267e6bf
@ -67,6 +67,7 @@
|
|||||||
"@types/react-redux": "^7.1.12",
|
"@types/react-redux": "^7.1.12",
|
||||||
"@welldone-software/why-did-you-render": "^6.0.3",
|
"@welldone-software/why-did-you-render": "^6.0.3",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
|
"chalk": "^4.1.0",
|
||||||
"typescript": "~4.0.0"
|
"typescript": "~4.0.0"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
} from '@react-navigation/native'
|
} from '@react-navigation/native'
|
||||||
import { enableScreens } from 'react-native-screens'
|
import { enableScreens } from 'react-native-screens'
|
||||||
|
|
||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useEffect, useMemo, useRef } from 'react'
|
||||||
import { StatusBar } from 'react-native'
|
import { StatusBar } from 'react-native'
|
||||||
import Toast from 'react-native-toast-message'
|
import Toast from 'react-native-toast-message'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
@ -23,12 +23,15 @@ import { toast, toastConfig } from '@components/toast'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import {
|
import {
|
||||||
|
getLocalNotification,
|
||||||
getLocalUrl,
|
getLocalUrl,
|
||||||
updateLocalAccountPreferences
|
updateLocalAccountPreferences,
|
||||||
|
updateNotification
|
||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import { useQuery } from 'react-query'
|
import { useInfiniteQuery, useQuery } from 'react-query'
|
||||||
import { announcementFetch } from './utils/fetches/announcementsFetch'
|
import { announcementFetch } from './utils/fetches/announcementsFetch'
|
||||||
import client from './api/client'
|
import client from './api/client'
|
||||||
|
import { timelineFetch } from './utils/fetches/timelineFetch'
|
||||||
|
|
||||||
enableScreens()
|
enableScreens()
|
||||||
const Tab = createBottomTabNavigator<RootStackParamList>()
|
const Tab = createBottomTabNavigator<RootStackParamList>()
|
||||||
@ -55,6 +58,7 @@ export const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
dark = 'light-content'
|
dark = 'light-content'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On launch display login credentials corrupt information
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const showLocalCorrect = localCorrupt
|
const showLocalCorrect = localCorrupt
|
||||||
? toast({
|
? toast({
|
||||||
@ -67,27 +71,66 @@ export const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
return showLocalCorrect
|
return showLocalCorrect
|
||||||
}, [localCorrupt])
|
}, [localCorrupt])
|
||||||
|
|
||||||
|
// On launch check if there is any unread announcements
|
||||||
|
const navigationRef = useRef<NavigationContainerRef>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
localInstance &&
|
||||||
|
client({
|
||||||
|
method: 'get',
|
||||||
|
instance: 'local',
|
||||||
|
url: `announcements`
|
||||||
|
})
|
||||||
|
.then(({ body }: { body?: Mastodon.Announcement[] }) => {
|
||||||
|
if (body?.filter(announcement => !announcement.read).length) {
|
||||||
|
navigationRef.current?.navigate('Screen-Shared-Announcements')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// On launch check if there is any unread noficiations
|
||||||
|
const queryNotification = useInfiniteQuery(
|
||||||
|
['Notifications', {}] as QueryKey.Timeline,
|
||||||
|
timelineFetch,
|
||||||
|
{ enabled: localInstance ? true : false, cacheTime: 1000 * 30 }
|
||||||
|
)
|
||||||
|
const prevNotification = useSelector(getLocalNotification)
|
||||||
|
useEffect(() => {
|
||||||
|
if (queryNotification.data?.pages) {
|
||||||
|
const flattenData = queryNotification.data.pages.flatMap(d => [
|
||||||
|
...d?.toots
|
||||||
|
])
|
||||||
|
const latestNotificationTime = flattenData.length
|
||||||
|
? flattenData[0].created_at
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (!prevNotification || !prevNotification.latestTime) {
|
||||||
|
dispatch(
|
||||||
|
updateNotification({
|
||||||
|
unread: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
latestNotificationTime &&
|
||||||
|
new Date(prevNotification.latestTime) < new Date(latestNotificationTime)
|
||||||
|
) {
|
||||||
|
dispatch(
|
||||||
|
updateNotification({
|
||||||
|
unread: true,
|
||||||
|
latestTime: latestNotificationTime
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [queryNotification.data?.pages])
|
||||||
|
|
||||||
|
// Lazily update users's preferences, for e.g. composing default visibility
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (localInstance) {
|
if (localInstance) {
|
||||||
dispatch(updateLocalAccountPreferences())
|
dispatch(updateLocalAccountPreferences())
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const navigationRef = useRef<NavigationContainerRef>(null)
|
|
||||||
useEffect(() => {
|
|
||||||
client({
|
|
||||||
method: 'get',
|
|
||||||
instance: 'local',
|
|
||||||
url: `announcements`
|
|
||||||
})
|
|
||||||
.then(({ body }: { body?: Mastodon.Announcement[] }) => {
|
|
||||||
if (body?.filter(announcement => !announcement.read).length) {
|
|
||||||
navigationRef.current?.navigate('Screen-Shared-Announcements')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StatusBar barStyle={barStyle[mode]} />
|
<StatusBar barStyle={barStyle[mode]} />
|
||||||
@ -147,7 +190,7 @@ export const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
<Tab.Screen name='Screen-Public' component={ScreenPublic} />
|
<Tab.Screen name='Screen-Public' component={ScreenPublic} />
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name='Screen-Post'
|
name='Screen-Post'
|
||||||
listeners={({ navigation, route }) => ({
|
listeners={({ navigation }) => ({
|
||||||
tabPress: e => {
|
tabPress: e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
localInstance &&
|
localInstance &&
|
||||||
@ -162,6 +205,10 @@ export const Index: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name='Screen-Notifications'
|
name='Screen-Notifications'
|
||||||
component={ScreenNotifications}
|
component={ScreenNotifications}
|
||||||
|
options={{
|
||||||
|
tabBarBadge: prevNotification.unread ? '' : undefined,
|
||||||
|
tabBarBadgeStyle: { transform: [{ scale: 0.5 }] }
|
||||||
|
}}
|
||||||
listeners={{
|
listeners={{
|
||||||
tabPress: e => {
|
tabPress: e => {
|
||||||
if (!localInstance) {
|
if (!localInstance) {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import chalk from 'chalk'
|
||||||
import { store, RootState } from '@root/store'
|
import { store, RootState } from '@root/store'
|
||||||
|
|
||||||
|
const ctx = new chalk.Instance({ level: 3 })
|
||||||
|
|
||||||
const client = async ({
|
const client = async ({
|
||||||
method,
|
method,
|
||||||
instance,
|
instance,
|
||||||
@ -25,13 +28,13 @@ const client = async ({
|
|||||||
onUploadProgress?: (progressEvent: any) => void
|
onUploadProgress?: (progressEvent: any) => void
|
||||||
}): Promise<any> => {
|
}): Promise<any> => {
|
||||||
console.log(
|
console.log(
|
||||||
'API call:',
|
ctx.bgGreen.bold(' API ') +
|
||||||
'Method ->',
|
' ' +
|
||||||
method,
|
method +
|
||||||
'Endpoint ->',
|
ctx.green(' -> ') +
|
||||||
url,
|
`/${url}` +
|
||||||
'Params ->',
|
(params ? ctx.green(' -> ') : ''),
|
||||||
params
|
params ? params : ''
|
||||||
)
|
)
|
||||||
const state: RootState['instances'] = store.getState().instances
|
const state: RootState['instances'] = store.getState().instances
|
||||||
const domain =
|
const domain =
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
|
|
||||||
import Timeline from '@components/Timelines/Timeline'
|
import Timeline from '@components/Timelines/Timeline'
|
||||||
import sharedScreens from '@screens/Shared/sharedScreens'
|
import sharedScreens from '@screens/Shared/sharedScreens'
|
||||||
import { useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { updateNotification } from '@root/utils/slices/instancesSlice'
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator()
|
const Stack = createNativeStackNavigator()
|
||||||
|
|
||||||
@ -15,6 +16,12 @@ const ScreenNotifications: React.FC = () => {
|
|||||||
(state: RootState) => state.instances.local.url
|
(state: RootState) => state.instances.local.url
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Clear notification unread, but keep the time
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(updateNotification({ unread: false }))
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{ headerTitle: t('notifications:heading') }}
|
screenOptions={{ headerTitle: t('notifications:heading') }}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import client from '@api/client'
|
import client from '@api/client'
|
||||||
@ -11,13 +11,17 @@ export type InstancesState = {
|
|||||||
id: Mastodon.Account['id'] | undefined
|
id: Mastodon.Account['id'] | undefined
|
||||||
preferences: Mastodon.Preferences
|
preferences: Mastodon.Preferences
|
||||||
}
|
}
|
||||||
|
notification: {
|
||||||
|
unread: boolean
|
||||||
|
latestTime?: Mastodon.Notification['created_at']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
remote: {
|
remote: {
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialStateLocal = {
|
const initialStateLocal: InstancesState['local'] = {
|
||||||
url: undefined,
|
url: undefined,
|
||||||
token: undefined,
|
token: undefined,
|
||||||
account: {
|
account: {
|
||||||
@ -29,6 +33,10 @@ const initialStateLocal = {
|
|||||||
'reading:expand:media': undefined,
|
'reading:expand:media': undefined,
|
||||||
'reading:expand:spoilers': undefined
|
'reading:expand:spoilers': undefined
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
notification: {
|
||||||
|
unread: false,
|
||||||
|
latestTime: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +102,15 @@ const instancesSlice = createSlice({
|
|||||||
reducers: {
|
reducers: {
|
||||||
resetLocal: state => {
|
resetLocal: state => {
|
||||||
state.local = initialStateLocal
|
state.local = initialStateLocal
|
||||||
|
},
|
||||||
|
updateNotification: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<Partial<InstancesState['local']['notification']>>
|
||||||
|
) => {
|
||||||
|
state.local.notification = {
|
||||||
|
...state.local.notification,
|
||||||
|
...action.payload
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
extraReducers: builder => {
|
extraReducers: builder => {
|
||||||
@ -109,12 +126,14 @@ const instancesSlice = createSlice({
|
|||||||
|
|
||||||
export const getLocalUrl = (state: RootState) => state.instances.local.url
|
export const getLocalUrl = (state: RootState) => state.instances.local.url
|
||||||
export const getLocalToken = (state: RootState) => state.instances.local.token
|
export const getLocalToken = (state: RootState) => state.instances.local.token
|
||||||
|
export const getLocalNotification = (state: RootState) =>
|
||||||
|
state.instances.local.notification
|
||||||
export const getRemoteUrl = (state: RootState) => state.instances.remote.url
|
export const getRemoteUrl = (state: RootState) => state.instances.remote.url
|
||||||
export const getLocalAccountId = (state: RootState) =>
|
export const getLocalAccountId = (state: RootState) =>
|
||||||
state.instances.local.account.id
|
state.instances.local.account.id
|
||||||
export const getLocalAccountPreferences = (state: RootState) =>
|
export const getLocalAccountPreferences = (state: RootState) =>
|
||||||
state.instances.local.account.preferences
|
state.instances.local.account.preferences
|
||||||
|
|
||||||
export const { resetLocal } = instancesSlice.actions
|
export const { resetLocal, updateNotification } = instancesSlice.actions
|
||||||
|
|
||||||
export default instancesSlice.reducer
|
export default instancesSlice.reducer
|
||||||
|
@ -2107,7 +2107,7 @@ chalk@^3.0.0:
|
|||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
chalk@^4.0.0:
|
chalk@^4.0.0, chalk@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
||||||
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
|
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user