mirror of
https://github.com/tooot-app/app
synced 2025-02-24 15:47:41 +01:00
Basic notification working
This commit is contained in:
parent
4eea2bf58c
commit
78898059cb
@ -118,6 +118,7 @@
|
||||
"react-navigation": "^4.4.3",
|
||||
"react-navigation-stack": "^2.10.2",
|
||||
"react-test-renderer": "^16.13.1",
|
||||
"typescript": "~4.1.3"
|
||||
"typescript": "~4.1.3",
|
||||
"uri-scheme": "^1.0.67"
|
||||
}
|
||||
}
|
2
src/@types/react-navigation.d.ts
vendored
2
src/@types/react-navigation.d.ts
vendored
@ -71,7 +71,7 @@ declare namespace Nav {
|
||||
'Tab-Local': undefined
|
||||
'Tab-Public': undefined
|
||||
'Tab-Compose': undefined
|
||||
'Tab-Notifications': { id?: Mastodon.Notification['id'] }
|
||||
'Tab-Notifications': undefined
|
||||
'Tab-Me': undefined
|
||||
}
|
||||
|
||||
|
@ -8,18 +8,17 @@ import ScreenAnnouncements from '@screens/Announcements'
|
||||
import ScreenCompose from '@screens/Compose'
|
||||
import ScreenImagesViewer from '@screens/ImagesViewer'
|
||||
import ScreenTabs from '@screens/Tabs'
|
||||
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
import { updatePreviousTab } from '@utils/slices/contextsSlice'
|
||||
import { connectInstancesPush } from '@utils/slices/instances/connectPush'
|
||||
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { themes } from '@utils/styles/themes'
|
||||
import * as Analytics from 'expo-firebase-analytics'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import { addScreenshotListener } from 'expo-screen-capture'
|
||||
import React, { createRef, useCallback, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Linking, Platform, StatusBar } from 'react-native'
|
||||
import { Alert, Platform, StatusBar } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
@ -27,35 +26,6 @@ import * as Sentry from 'sentry-expo'
|
||||
|
||||
const Stack = createNativeStackNavigator<Nav.RootStackParamList>()
|
||||
|
||||
const linking = {
|
||||
prefixes: ['tooot://', 'https://tooot.app'],
|
||||
config: {
|
||||
screens: {
|
||||
'Screen-Tabs': {
|
||||
screens: {
|
||||
'Tab-Notifications': 'push/:id'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
subscribe (listener: (arg0: string) => any) {
|
||||
const onReceiveURL = ({ url }: { url: string }) => listener(url)
|
||||
Linking.addEventListener('url', onReceiveURL)
|
||||
const subscription = Notifications.addNotificationResponseReceivedListener(
|
||||
response => {
|
||||
const url = response.notification.request.content.data.url
|
||||
console.log(url)
|
||||
url && typeof url === 'string' && listener(url)
|
||||
}
|
||||
)
|
||||
|
||||
return () => {
|
||||
Linking.removeEventListener('url', onReceiveURL)
|
||||
subscription.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
localCorrupt?: string
|
||||
}
|
||||
@ -87,6 +57,11 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
// }
|
||||
// }, [isConnected, firstRender])
|
||||
|
||||
// Update Expo Token to server
|
||||
useEffect(() => {
|
||||
dispatch(connectInstancesPush())
|
||||
}, [])
|
||||
|
||||
// Prevent screenshot alert
|
||||
useEffect(() => {
|
||||
const screenshotListener = addScreenshotListener(() =>
|
||||
@ -116,23 +91,6 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
return showLocalCorrect()
|
||||
}, [localCorrupt])
|
||||
|
||||
// On launch check if there is any unread announcements
|
||||
useAnnouncementQuery({
|
||||
showAll: false,
|
||||
options: {
|
||||
notifyOnChangeProps: [],
|
||||
select: announcements =>
|
||||
announcements.filter(announcement => !announcement.read),
|
||||
onSuccess: data => {
|
||||
if (data.length) {
|
||||
navigationRef.current?.navigate('Screen-Announcements', {
|
||||
showAll: false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Lazily update users's preferences, for e.g. composing default visibility
|
||||
useEffect(() => {
|
||||
if (instanceActive !== -1) {
|
||||
@ -175,7 +133,6 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
theme={themes[mode]}
|
||||
onReady={navigationContainerOnReady}
|
||||
onStateChange={navigationContainerOnStateChange}
|
||||
linking={linking}
|
||||
>
|
||||
<Stack.Navigator initialRouteName='Screen-Tabs'>
|
||||
<Stack.Screen
|
||||
|
@ -6,7 +6,10 @@ import * as WebBrowser from 'expo-web-browser'
|
||||
const openLink = async (url: string) => {
|
||||
switch (getSettingsBrowser(store.getState())) {
|
||||
case 'internal':
|
||||
await WebBrowser.openBrowserAsync(url)
|
||||
await WebBrowser.openBrowserAsync(url, {
|
||||
dismissButtonStyle: 'close',
|
||||
enableBarCollapsing: true
|
||||
})
|
||||
break
|
||||
case 'external':
|
||||
await Linking.openURL(url)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import useWebsocket from '@api/websocket'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
@ -13,10 +14,14 @@ import {
|
||||
getInstanceAccount,
|
||||
getInstanceActive,
|
||||
getInstanceNotification,
|
||||
getInstances,
|
||||
updateInstanceActive,
|
||||
updateInstanceNotification
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
@ -38,10 +43,97 @@ export type ScreenTabsProp = StackScreenProps<
|
||||
'Screen-Tabs'
|
||||
>
|
||||
|
||||
const convertNotificationToToot = (
|
||||
navigation: any,
|
||||
id: Mastodon.Notification['id']
|
||||
) => {
|
||||
apiInstance<Mastodon.Notification>({
|
||||
method: 'get',
|
||||
url: `notifications/${id}`
|
||||
}).then(({ body }) => {
|
||||
// @ts-ignore
|
||||
navigation.navigate('Tab-Notifications', {
|
||||
screen: 'Tab-Notifications-Root'
|
||||
})
|
||||
if (body.status) {
|
||||
// @ts-ignore
|
||||
navigation.navigate('Tab-Notifications', {
|
||||
screen: 'Tab-Shared-Toot',
|
||||
params: { toot: body.status }
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const Tab = createBottomTabNavigator<Nav.ScreenTabsStackParamList>()
|
||||
|
||||
const ScreenTabs = React.memo(
|
||||
({ navigation }: ScreenTabsProp) => {
|
||||
// Push notifications
|
||||
const instances = useSelector(
|
||||
getInstances,
|
||||
(prev, next) => prev.length === next.length
|
||||
)
|
||||
const lastNotificationResponse = Notifications.useLastNotificationResponse()
|
||||
useEffect(() => {
|
||||
const subscription = Notifications.addNotificationResponseReceivedListener(
|
||||
({ notification }) => {
|
||||
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]))
|
||||
}
|
||||
if (payloadData?.notification_id) {
|
||||
convertNotificationToToot(
|
||||
navigation,
|
||||
notification.request.content.data.notification_id as string
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
return () => subscription.remove()
|
||||
|
||||
// if (
|
||||
// lastNotificationResponse &&
|
||||
// lastNotificationResponse.actionIdentifier ===
|
||||
// Notifications.DEFAULT_ACTION_IDENTIFIER
|
||||
// ) {
|
||||
// const payloadData = lastNotificationResponse.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]))
|
||||
// }
|
||||
// if (payloadData?.notification_id) {
|
||||
// convertNotificationToToot(
|
||||
// navigation,
|
||||
// lastNotificationResponse.notification.request.content.data
|
||||
// .notification_id as string
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
}, [instances, lastNotificationResponse])
|
||||
|
||||
const { mode, theme } = useTheme()
|
||||
const dispatch = useDispatch()
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
|
@ -1,12 +1,16 @@
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
const ScreenMeBookmarks = React.memo(
|
||||
() => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Bookmarks' }]
|
||||
|
||||
return <Timeline queryKey={queryKey} />
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||
[]
|
||||
)
|
||||
return <Timeline queryKey={queryKey} customProps={{ renderItem }} />
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
@ -1,12 +1,17 @@
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
const ScreenMeFavourites = React.memo(
|
||||
() => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Favourites' }]
|
||||
const renderItem = useCallback(
|
||||
({ item }) => <TimelineDefault item={item} queryKey={queryKey} />,
|
||||
[]
|
||||
)
|
||||
|
||||
return <Timeline queryKey={queryKey} />
|
||||
return <Timeline queryKey={queryKey} customProps={{ renderItem }} />
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
@ -1,18 +1,17 @@
|
||||
import { MenuRow } from '@components/Menu'
|
||||
import TimelineEmpty from '@components/Timeline/Empty'
|
||||
import { StackScreenProps } from '@react-navigation/stack'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
const ScreenMeLists: React.FC<StackScreenProps<
|
||||
Nav.TabMeStackParamList,
|
||||
'Tab-Me-Switch'
|
||||
'Tab-Me-Lists'
|
||||
>> = ({ navigation }) => {
|
||||
const { status, data, refetch } = useListsQuery({})
|
||||
const { data } = useListsQuery({})
|
||||
|
||||
const children = useMemo(() => {
|
||||
if (status === 'success') {
|
||||
return data?.map((d: Mastodon.List, i: number) => (
|
||||
return (
|
||||
<>
|
||||
{data?.map((d: Mastodon.List, i: number) => (
|
||||
<MenuRow
|
||||
key={i}
|
||||
iconFront='List'
|
||||
@ -24,13 +23,9 @@ const ScreenMeLists: React.FC<StackScreenProps<
|
||||
})
|
||||
}
|
||||
/>
|
||||
))
|
||||
} else {
|
||||
return <TimelineEmpty status={status} refetch={refetch} />
|
||||
}
|
||||
}, [status])
|
||||
|
||||
return <>{children}</>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScreenMeLists
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@ -8,26 +9,54 @@ const Collections: React.FC = () => {
|
||||
const { t, i18n } = useTranslation('meRoot')
|
||||
const navigation = useNavigation()
|
||||
|
||||
const { data, isFetching } = useAnnouncementQuery({
|
||||
showAll: true
|
||||
const listsQuery = useListsQuery({
|
||||
options: {
|
||||
notifyOnChangeProps: []
|
||||
}
|
||||
})
|
||||
const rowLists = useMemo(() => {
|
||||
if (listsQuery.isSuccess && listsQuery.data?.length) {
|
||||
return (
|
||||
<MenuRow
|
||||
iconFront='List'
|
||||
iconBack='ChevronRight'
|
||||
title={t('content.collections.lists')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Lists')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}, [listsQuery.isSuccess, listsQuery.data, i18n.language])
|
||||
|
||||
const announcementContent = useMemo(() => {
|
||||
if (data) {
|
||||
if (data.length === 0) {
|
||||
return t('content.collections.announcements.content.empty')
|
||||
} else {
|
||||
const amount = data.filter(announcement => !announcement.read).length
|
||||
if (amount) {
|
||||
return t('content.collections.announcements.content.unread', {
|
||||
const announcementsQuery = useAnnouncementQuery({
|
||||
showAll: true,
|
||||
options: {
|
||||
notifyOnChangeProps: []
|
||||
}
|
||||
})
|
||||
const rowAnnouncements = useMemo(() => {
|
||||
if (announcementsQuery.isSuccess && announcementsQuery.data?.length) {
|
||||
const amount = announcementsQuery.data.filter(
|
||||
announcement => !announcement.read
|
||||
).length
|
||||
return (
|
||||
<MenuRow
|
||||
iconFront='Clipboard'
|
||||
iconBack='ChevronRight'
|
||||
title={t('content.collections.announcements.heading')}
|
||||
content={
|
||||
amount
|
||||
? t('content.collections.announcements.content.unread', {
|
||||
amount
|
||||
})
|
||||
} else {
|
||||
return t('content.collections.announcements.content.read')
|
||||
: t('content.collections.announcements.content.read')
|
||||
}
|
||||
onPress={() =>
|
||||
navigation.navigate('Screen-Announcements', { showAll: true })
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}, [data, i18n.language])
|
||||
}, [announcementsQuery.isSuccess, announcementsQuery.data, i18n.language])
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
@ -49,24 +78,8 @@ const Collections: React.FC = () => {
|
||||
title={t('content.collections.favourites')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Favourites')}
|
||||
/>
|
||||
<MenuRow
|
||||
iconFront='List'
|
||||
iconBack='ChevronRight'
|
||||
title={t('content.collections.lists')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Lists')}
|
||||
/>
|
||||
<MenuRow
|
||||
iconFront='Clipboard'
|
||||
iconBack={data && data.length === 0 ? undefined : 'ChevronRight'}
|
||||
title={t('content.collections.announcements.heading')}
|
||||
content={announcementContent}
|
||||
loading={isFetching}
|
||||
onPress={() =>
|
||||
data &&
|
||||
data.length &&
|
||||
navigation.navigate('Screen-Announcements', { showAll: true })
|
||||
}
|
||||
/>
|
||||
{rowLists}
|
||||
{rowAnnouncements}
|
||||
</MenuContainer>
|
||||
)
|
||||
}
|
||||
|
@ -43,32 +43,32 @@ const TabNotifications = React.memo(
|
||||
<Timeline
|
||||
queryKey={queryKey}
|
||||
customProps={{
|
||||
renderItem,
|
||||
viewabilityConfigCallbackPairs: [
|
||||
{
|
||||
onViewableItemsChanged: ({
|
||||
viewableItems
|
||||
}: {
|
||||
viewableItems: ViewToken[]
|
||||
}) => {
|
||||
if (
|
||||
navigation.isFocused() &&
|
||||
viewableItems.length &&
|
||||
viewableItems[0].index === 0
|
||||
) {
|
||||
dispatch(
|
||||
updateInstanceNotification({
|
||||
readTime: viewableItems[0].item.created_at
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
viewabilityConfig: {
|
||||
minimumViewTime: 100,
|
||||
itemVisiblePercentThreshold: 60
|
||||
}
|
||||
}
|
||||
]
|
||||
renderItem
|
||||
// viewabilityConfigCallbackPairs: [
|
||||
// {
|
||||
// onViewableItemsChanged: ({
|
||||
// viewableItems
|
||||
// }: {
|
||||
// viewableItems: ViewToken[]
|
||||
// }) => {
|
||||
// if (
|
||||
// navigation.isFocused() &&
|
||||
// viewableItems.length &&
|
||||
// viewableItems[0].index === 0
|
||||
// ) {
|
||||
// dispatch(
|
||||
// updateInstanceNotification({
|
||||
// readTime: viewableItems[0].item.created_at
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
// },
|
||||
// viewabilityConfig: {
|
||||
// minimumViewTime: 100,
|
||||
// itemVisiblePercentThreshold: 60
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
}}
|
||||
/>
|
||||
),
|
||||
|
34
src/utils/slices/instances/connectPush.ts
Normal file
34
src/utils/slices/instances/connectPush.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import apiGeneral from '@api/general'
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||
import { RootState } from '@root/store'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import { PUSH_SERVER } from '../instancesSlice'
|
||||
|
||||
export const connectInstancesPush = createAsyncThunk(
|
||||
'instances/connectPush',
|
||||
async (_, { 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()
|
||||
}
|
||||
}
|
||||
)
|
@ -10286,6 +10286,11 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
uri-scheme@^1.0.67:
|
||||
version "1.0.67"
|
||||
resolved "https://registry.yarnpkg.com/uri-scheme/-/uri-scheme-1.0.67.tgz#a18c8752489967783eb94784b9d08adaf124d9eb"
|
||||
integrity sha512-q0xH1d4w3fMaEJfpmA+mXN+L9fzV8bBZz96llWLG4TXO98vbMeXptDhvasQP30y/okwsQnO5gbVYJmeZW2OWIw==
|
||||
|
||||
urix@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||
|
Loading…
x
Reference in New Issue
Block a user