Improve push experience
@ -1,8 +1,5 @@
|
|||||||
<manifest
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:tools="http://schemas.android.com/tools" package="com.xmflsct.app.tooot">
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="com.xmflsct.app.tooot"
|
|
||||||
>
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||||
@ -12,30 +9,17 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<application
|
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:requestLegacyExternalStorage="true">
|
||||||
android:name=".MainApplication"
|
<!-- [Custom] Expo Notifications -->
|
||||||
android:label="@string/app_name"
|
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/ic_stat_notifications" />
|
||||||
android:icon="@mipmap/ic_launcher"
|
<!-- [Custom] End Expo Notifications -->
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:theme="@style/AppTheme"
|
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
>
|
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@xmflsct/tooot"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@xmflsct/tooot"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="${expoSDK}"/>
|
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="${expoSDK}"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_RELEASE_CHANNEL" android:value="${releaseChannel}"/>
|
<meta-data android:name="expo.modules.updates.EXPO_RELEASE_CHANNEL" android:value="${releaseChannel}"/>
|
||||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
|
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||||
<activity
|
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="portrait">
|
||||||
android:name=".MainActivity"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:windowSoftInputMode="adjustResize"
|
|
||||||
android:theme="@style/Theme.App.SplashScreen"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
>
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
@ -24,6 +24,7 @@ public class BasePackageList {
|
|||||||
new expo.modules.lineargradient.LinearGradientPackage(),
|
new expo.modules.lineargradient.LinearGradientPackage(),
|
||||||
new expo.modules.localization.LocalizationPackage(),
|
new expo.modules.localization.LocalizationPackage(),
|
||||||
new expo.modules.location.LocationPackage(),
|
new expo.modules.location.LocationPackage(),
|
||||||
|
new expo.modules.notifications.NotificationsPackage(),
|
||||||
new expo.modules.permissions.PermissionsPackage(),
|
new expo.modules.permissions.PermissionsPackage(),
|
||||||
new expo.modules.screencapture.ScreenCapturePackage(),
|
new expo.modules.screencapture.ScreenCapturePackage(),
|
||||||
new expo.modules.securestore.SecureStorePackage(),
|
new expo.modules.securestore.SecureStorePackage(),
|
||||||
|
BIN
android/app/src/main/res/drawable-hdpi/ic_stat_notifications.png
Normal file
After Width: | Height: | Size: 577 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_stat_notifications.png
Normal file
After Width: | Height: | Size: 379 B |
After Width: | Height: | Size: 892 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.5 KiB |
14
src/App.tsx
@ -10,9 +10,10 @@ import sentry from '@root/startup/sentry'
|
|||||||
import { persistor, store } from '@root/store'
|
import { persistor, store } from '@root/store'
|
||||||
import { getSettingsLanguage } from '@utils/slices/settingsSlice'
|
import { getSettingsLanguage } from '@utils/slices/settingsSlice'
|
||||||
import ThemeManager from '@utils/styles/ThemeManager'
|
import ThemeManager from '@utils/styles/ThemeManager'
|
||||||
|
import * as Notifications from 'expo-notifications'
|
||||||
import * as SplashScreen from 'expo-splash-screen'
|
import * as SplashScreen from 'expo-splash-screen'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { LogBox, Platform } from 'react-native'
|
import { AppState, LogBox, Platform } from 'react-native'
|
||||||
import { enableScreens } from 'react-native-screens'
|
import { enableScreens } from 'react-native-screens'
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query'
|
import { QueryClient, QueryClientProvider } from 'react-query'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
@ -39,6 +40,17 @@ const App: React.FC = () => {
|
|||||||
log('log', 'App', 'rendering App')
|
log('log', 'App', 'rendering App')
|
||||||
const [localCorrupt, setLocalCorrupt] = useState<string>()
|
const [localCorrupt, setLocalCorrupt] = useState<string>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
AppState.addEventListener('change', () => {
|
||||||
|
Notifications.setBadgeCountAsync(0)
|
||||||
|
Notifications.dismissAllNotificationsAsync()
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
AppState.removeEventListener('change', () => {})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const delaySplash = async () => {
|
const delaySplash = async () => {
|
||||||
log('log', 'App', 'delay splash')
|
log('log', 'App', 'delay splash')
|
||||||
|
@ -109,14 +109,11 @@ const GracefullyImage = React.memo(
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
(prev, next) => {
|
(prev, next) =>
|
||||||
let skipUpdate = true
|
prev.hidden === next.hidden &&
|
||||||
skipUpdate = prev.hidden === next.hidden
|
prev.uri.preview === next.uri.preview &&
|
||||||
skipUpdate = prev.uri.preview === next.uri.preview
|
prev.uri.original === next.uri.original &&
|
||||||
skipUpdate = prev.uri.original === next.uri.original
|
prev.uri.remote === next.uri.remote
|
||||||
skipUpdate = prev.uri.remote === next.uri.remote
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -3,9 +3,9 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { ColorDefinitions } from '@utils/styles/themes'
|
import { ColorDefinitions } from '@utils/styles/themes'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { StyleSheet, Switch, Text, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
import { Flow } from 'react-native-animated-spinkit'
|
import { Flow } from 'react-native-animated-spinkit'
|
||||||
import { State, TapGestureHandler } from 'react-native-gesture-handler'
|
import { State, Switch, TapGestureHandler } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
iconFront?: any
|
iconFront?: any
|
||||||
|
@ -14,6 +14,9 @@ export default {
|
|||||||
description:
|
description:
|
||||||
'经由tooot服务器中转的通知消息已被加密,但可以允许tooot服务器解密并转发消息。tooot消息服务器源码开源,且不留存任何日志。'
|
'经由tooot服务器中转的通知消息已被加密,但可以允许tooot服务器解密并转发消息。tooot消息服务器源码开源,且不留存任何日志。'
|
||||||
},
|
},
|
||||||
|
default: {
|
||||||
|
heading: '默认通知' // Android notification channel name only
|
||||||
|
},
|
||||||
follow: {
|
follow: {
|
||||||
heading: '新关注者'
|
heading: '新关注者'
|
||||||
},
|
},
|
||||||
@ -24,10 +27,10 @@ export default {
|
|||||||
heading: '嘟文被转嘟'
|
heading: '嘟文被转嘟'
|
||||||
},
|
},
|
||||||
mention: {
|
mention: {
|
||||||
heading: '提及你'
|
heading: '嘟文提及你'
|
||||||
},
|
},
|
||||||
poll: {
|
poll: {
|
||||||
heading: '投票'
|
heading: '投票更新'
|
||||||
},
|
},
|
||||||
howitworks: '了解通知消息转发如何工作'
|
howitworks: '了解通知消息转发如何工作'
|
||||||
},
|
},
|
||||||
|
@ -319,7 +319,7 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
|
|||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={styles.base}
|
style={styles.base}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
keyboardVerticalOffset={Platform.OS === 'android' ? 63 : 0}
|
keyboardVerticalOffset={Platform.OS === 'android' ? 30 : 0}
|
||||||
>
|
>
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={styles.base}
|
style={styles.base}
|
||||||
|
@ -95,7 +95,7 @@ const ComposeAttachments: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
snapToOffsets.length >
|
snapToOffsets.length >
|
||||||
(prevOffsets.current ? prevOffsets.current?.length : 0)
|
(prevOffsets.current ? prevOffsets.current.length : 0)
|
||||||
) {
|
) {
|
||||||
flatListRef.current?.scrollToOffset({
|
flatListRef.current?.scrollToOffset({
|
||||||
offset:
|
offset:
|
||||||
|
@ -18,9 +18,7 @@ const ComposeRootHeader: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{instanceActive !== -1 &&
|
{instanceActive !== -1 && localInstances.length > 1 && (
|
||||||
localInstances.length &&
|
|
||||||
localInstances.length > 1 && (
|
|
||||||
<View style={styles.postingAs}>
|
<View style={styles.postingAs}>
|
||||||
<ComposePostingAs />
|
<ComposePostingAs />
|
||||||
</View>
|
</View>
|
||||||
|
@ -1,25 +1,15 @@
|
|||||||
import apiInstance from '@api/instance'
|
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { displayMessage } from '@components/Message'
|
|
||||||
import {
|
import {
|
||||||
BottomTabNavigationOptions,
|
BottomTabNavigationOptions,
|
||||||
createBottomTabNavigator
|
createBottomTabNavigator
|
||||||
} from '@react-navigation/bottom-tabs'
|
} from '@react-navigation/bottom-tabs'
|
||||||
import { NavigatorScreenParams } from '@react-navigation/native'
|
import { NavigatorScreenParams } from '@react-navigation/native'
|
||||||
import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack'
|
import { StackScreenProps } from '@react-navigation/stack'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|
||||||
import { getPreviousTab } from '@utils/slices/contextsSlice'
|
import { getPreviousTab } from '@utils/slices/contextsSlice'
|
||||||
import {
|
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||||
getInstanceAccount,
|
|
||||||
getInstanceActive,
|
|
||||||
getInstances,
|
|
||||||
updateInstanceActive
|
|
||||||
} from '@utils/slices/instancesSlice'
|
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import * as Notifications from 'expo-notifications'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { findIndex } from 'lodash'
|
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
@ -28,6 +18,8 @@ import TabLocal from './Tabs/Local'
|
|||||||
import TabMe from './Tabs/Me'
|
import TabMe from './Tabs/Me'
|
||||||
import TabNotifications from './Tabs/Notifications'
|
import TabNotifications from './Tabs/Notifications'
|
||||||
import TabPublic from './Tabs/Public'
|
import TabPublic from './Tabs/Public'
|
||||||
|
import pushReceive from './Tabs/utils/pushReceive'
|
||||||
|
import pushRespond from './Tabs/utils/pushRespond'
|
||||||
|
|
||||||
export type ScreenTabsParamList = {
|
export type ScreenTabsParamList = {
|
||||||
'Tab-Local': NavigatorScreenParams<Nav.TabLocalStackParamList>
|
'Tab-Local': NavigatorScreenParams<Nav.TabLocalStackParamList>
|
||||||
@ -42,108 +34,23 @@ export type ScreenTabsProp = StackScreenProps<
|
|||||||
'Screen-Tabs'
|
'Screen-Tabs'
|
||||||
>
|
>
|
||||||
|
|
||||||
const convertNotificationToToot = (
|
|
||||||
navigation: StackNavigationProp<Nav.RootStackParamList, 'Screen-Tabs'>,
|
|
||||||
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 Tab = createBottomTabNavigator<Nav.ScreenTabsStackParamList>()
|
||||||
|
|
||||||
const ScreenTabs = React.memo(
|
const ScreenTabs = React.memo(
|
||||||
({ navigation }: ScreenTabsProp) => {
|
({ navigation }: ScreenTabsProp) => {
|
||||||
// Push notifications
|
const { mode, theme } = useTheme()
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const instanceActive = useSelector(getInstanceActive)
|
||||||
const instances = useSelector(
|
const instances = useSelector(
|
||||||
getInstances,
|
getInstances,
|
||||||
(prev, next) => prev.length === next.length
|
(prev, next) => prev.length === next.length
|
||||||
)
|
)
|
||||||
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(
|
pushReceive({ navigation, queryClient, instances })
|
||||||
instances,
|
pushRespond({ navigation, queryClient, instances, dispatch })
|
||||||
instance =>
|
|
||||||
instance.url === payloadData.instanceUrl &&
|
|
||||||
instance.account.id === payloadData.accountId
|
|
||||||
)
|
|
||||||
if (notificationIndex !== -1 && payloadData.notification_id) {
|
|
||||||
displayMessage({
|
|
||||||
duration: 'long',
|
|
||||||
message: notification.request.content.title!,
|
|
||||||
description: notification.request.content.body!,
|
|
||||||
onPress: () =>
|
|
||||||
convertNotificationToToot(
|
|
||||||
navigation,
|
|
||||||
// @ts-ignore Typescript is wrong
|
|
||||||
payloadData.notification_id
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return () => subscription.remove()
|
|
||||||
}, [instances])
|
|
||||||
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, payloadData.notification_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return () => subscription.remove()
|
|
||||||
}, [instances])
|
|
||||||
|
|
||||||
const { mode, theme } = useTheme()
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const instanceActive = useSelector(getInstanceActive)
|
|
||||||
const localAccount = useSelector(
|
|
||||||
getInstanceAccount,
|
|
||||||
(prev, next) => prev?.avatarStatic === next?.avatarStatic
|
|
||||||
)
|
|
||||||
|
|
||||||
const screenOptions = useCallback(
|
const screenOptions = useCallback(
|
||||||
({ route }): BottomTabNavigationOptions => ({
|
({ route }): BottomTabNavigationOptions => ({
|
||||||
@ -169,7 +76,9 @@ const ScreenTabs = React.memo(
|
|||||||
case 'Tab-Me':
|
case 'Tab-Me':
|
||||||
return instanceActive !== -1 ? (
|
return instanceActive !== -1 ? (
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: localAccount?.avatarStatic }}
|
source={{
|
||||||
|
uri: instances[instanceActive].account.avatarStatic
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
@ -190,7 +99,7 @@ const ScreenTabs = React.memo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[instanceActive, localAccount?.avatarStatic]
|
[instances, instanceActive]
|
||||||
)
|
)
|
||||||
const tabBarOptions = useMemo(
|
const tabBarOptions = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -17,28 +17,24 @@ import { AppState, Linking } from 'react-native'
|
|||||||
const ScreenMeSettingsPush: React.FC = () => {
|
const ScreenMeSettingsPush: React.FC = () => {
|
||||||
const { t } = useTranslation('meSettingsPush')
|
const { t } = useTranslation('meSettingsPush')
|
||||||
|
|
||||||
const [appStateVisible, setAppStateVisible] = useState(AppState.currentState)
|
const dispatch = useDispatch()
|
||||||
useEffect(() => {
|
const instancePush = useSelector(getInstancePush)
|
||||||
AppState.addEventListener('change', state => setAppStateVisible(state))
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
AppState.removeEventListener('change', state => setAppStateVisible(state))
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
const [pushEnabled, setPushEnabled] = useState<boolean>()
|
const [pushEnabled, setPushEnabled] = useState<boolean>()
|
||||||
const [pushCanAskAgain, setPushCanAskAgain] = useState<boolean>()
|
const [pushCanAskAgain, setPushCanAskAgain] = useState<boolean>()
|
||||||
useEffect(() => {
|
|
||||||
const checkPush = async () => {
|
const checkPush = async () => {
|
||||||
const settings = await Notifications.getPermissionsAsync()
|
const settings = await Notifications.getPermissionsAsync()
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
setPushEnabled(settings.granted)
|
setPushEnabled(settings.granted)
|
||||||
setPushCanAskAgain(settings.canAskAgain)
|
setPushCanAskAgain(settings.canAskAgain)
|
||||||
}
|
}
|
||||||
|
useEffect(() => {
|
||||||
checkPush()
|
checkPush()
|
||||||
}, [appStateVisible])
|
AppState.addEventListener('change', () => checkPush())
|
||||||
|
return () => {
|
||||||
const dispatch = useDispatch()
|
AppState.removeEventListener('change', () => {})
|
||||||
const instancePush = useSelector(getInstancePush)
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const isLoading = instancePush?.global.loading || instancePush?.decode.loading
|
const isLoading = instancePush?.global.loading || instancePush?.decode.loading
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ const Collections: React.FC = () => {
|
|||||||
onPress={() => navigation.navigate('Tab-Me-Lists')}
|
onPress={() => navigation.navigate('Tab-Me-Lists')}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}, [listsQuery.isSuccess, listsQuery.data, i18n.language])
|
}, [listsQuery.isSuccess, listsQuery.data, i18n.language])
|
||||||
|
|
||||||
@ -55,6 +57,8 @@ const Collections: React.FC = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}, [announcementsQuery.isSuccess, announcementsQuery.data, i18n.language])
|
}, [announcementsQuery.isSuccess, announcementsQuery.data, i18n.language])
|
||||||
|
|
||||||
|
@ -4,9 +4,11 @@ import { MenuContainer, MenuRow } from '@components/Menu'
|
|||||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import i18n from '@root/i18n/i18n'
|
import i18n from '@root/i18n/i18n'
|
||||||
|
import androidDefaults from '@utils/slices/instances/push/androidDefaults'
|
||||||
import {
|
import {
|
||||||
getInstanceActive,
|
getInstanceActive,
|
||||||
getInstancePush
|
getInstancePush,
|
||||||
|
getInstances
|
||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import {
|
import {
|
||||||
changeBrowser,
|
changeBrowser,
|
||||||
@ -17,8 +19,10 @@ import {
|
|||||||
getSettingsBrowser
|
getSettingsBrowser
|
||||||
} from '@utils/slices/settingsSlice'
|
} from '@utils/slices/settingsSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import * as Notifications from 'expo-notifications'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
const SettingsApp: React.FC = () => {
|
const SettingsApp: React.FC = () => {
|
||||||
@ -28,6 +32,7 @@ const SettingsApp: React.FC = () => {
|
|||||||
const { setTheme } = useTheme()
|
const { setTheme } = useTheme()
|
||||||
const { t } = useTranslation('meSettings')
|
const { t } = useTranslation('meSettings')
|
||||||
|
|
||||||
|
const instances = useSelector(getInstances, () => true)
|
||||||
const instanceActive = useSelector(getInstanceActive)
|
const instanceActive = useSelector(getInstanceActive)
|
||||||
const settingsLanguage = useSelector(getSettingsLanguage)
|
const settingsLanguage = useSelector(getSettingsLanguage)
|
||||||
const settingsTheme = useSelector(getSettingsTheme)
|
const settingsTheme = useSelector(getSettingsTheme)
|
||||||
@ -80,9 +85,68 @@ const SettingsApp: React.FC = () => {
|
|||||||
new: availableLanguages[buttonIndex]
|
new: availableLanguages[buttonIndex]
|
||||||
})
|
})
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
dispatch(changeLanguage(availableLanguages[buttonIndex]))
|
dispatch(changeLanguage(availableLanguages[buttonIndex]))
|
||||||
i18n.changeLanguage(availableLanguages[buttonIndex])
|
i18n.changeLanguage(availableLanguages[buttonIndex])
|
||||||
|
|
||||||
|
// Update Android notification channel language
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
instances.forEach(instance => {
|
||||||
|
const accountFull = `@${instance.account.acct}@${instance.uri}`
|
||||||
|
if (instance.push.decode.value === false) {
|
||||||
|
Notifications.setNotificationChannelAsync(
|
||||||
|
`${accountFull}_default`,
|
||||||
|
{
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('meSettingsPush:content.default.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Notifications.setNotificationChannelAsync(
|
||||||
|
`${accountFull}_follow`,
|
||||||
|
{
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('meSettingsPush:content.follow.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Notifications.setNotificationChannelAsync(
|
||||||
|
`${accountFull}_favourite`,
|
||||||
|
{
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('meSettingsPush:content.favourite.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Notifications.setNotificationChannelAsync(
|
||||||
|
`${accountFull}_reblog`,
|
||||||
|
{
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('meSettingsPush:content.reblog.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Notifications.setNotificationChannelAsync(
|
||||||
|
`${accountFull}_mention`,
|
||||||
|
{
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('meSettingsPush:content.mention.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Notifications.setNotificationChannelAsync(
|
||||||
|
`${accountFull}_poll`,
|
||||||
|
{
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('meSettingsPush:content.poll.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ const SettingsDev: React.FC = () => {
|
|||||||
color: theme.primary
|
color: theme.primary
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{instances[instanceActive].token}
|
{instances[instanceActive]?.token}
|
||||||
</Text>
|
</Text>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title={'Local active index'}
|
title={'Local active index'}
|
||||||
|
@ -2,7 +2,7 @@ import { HeaderCenter, HeaderLeft } from '@components/Header'
|
|||||||
import { StackScreenProps } from '@react-navigation/stack'
|
import { StackScreenProps } from '@react-navigation/stack'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Platform, StyleSheet } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
import ScreenMeSwitchRoot from './Switch/Root'
|
import ScreenMeSwitchRoot from './Switch/Root'
|
||||||
|
|
||||||
@ -37,6 +37,4 @@ const ScreenMeSwitch: React.FC<StackScreenProps<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({})
|
|
||||||
|
|
||||||
export default ScreenMeSwitch
|
export default ScreenMeSwitch
|
||||||
|
@ -92,7 +92,11 @@ const styles = StyleSheet.create({
|
|||||||
|
|
||||||
export default React.memo(AccountInformation, (prev, next) => {
|
export default React.memo(AccountInformation, (prev, next) => {
|
||||||
let skipUpdate = true
|
let skipUpdate = true
|
||||||
skipUpdate = prev.account?.id === next.account?.id
|
if (prev.account?.id !== next.account?.id) {
|
||||||
skipUpdate = prev.account?.acct === next.account?.acct
|
skipUpdate = false
|
||||||
|
}
|
||||||
|
if (prev.account?.acct === next.account?.acct) {
|
||||||
|
skipUpdate = false
|
||||||
|
}
|
||||||
return skipUpdate
|
return skipUpdate
|
||||||
})
|
})
|
||||||
|
@ -33,7 +33,7 @@ const AccountInformationCreated: React.FC<Props> = ({ account }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('content.created_at', {
|
{t('content.created_at', {
|
||||||
date: new Date(account?.created_at || '').toLocaleDateString(
|
date: new Date(account.created_at || '').toLocaleDateString(
|
||||||
i18n.language,
|
i18n.language,
|
||||||
{
|
{
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
|
@ -73,7 +73,7 @@ const AccountInformationStats: React.FC<Props> = ({ account, myInfo }) => {
|
|||||||
<Text
|
<Text
|
||||||
style={[styles.stat, { color: theme.primary, textAlign: 'center' }]}
|
style={[styles.stat, { color: theme.primary, textAlign: 'center' }]}
|
||||||
children={t('content.summary.followers_count', {
|
children={t('content.summary.followers_count', {
|
||||||
count: account?.followers_count || 0
|
count: account.followers_count || 0
|
||||||
})}
|
})}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('account_stats_followers_press', {
|
analytics('account_stats_followers_press', {
|
||||||
|
31
src/screens/Tabs/utils/pushNavigate.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import apiInstance from '@api/instance'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
|
|
||||||
|
const pushNavigate = (
|
||||||
|
navigation: StackNavigationProp<Nav.RootStackParamList, 'Screen-Tabs'>,
|
||||||
|
id?: Mastodon.Notification['id']
|
||||||
|
) => {
|
||||||
|
// @ts-ignore
|
||||||
|
navigation.navigate('Tab-Notifications', {
|
||||||
|
screen: 'Tab-Notifications-Root'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiInstance<Mastodon.Notification>({
|
||||||
|
method: 'get',
|
||||||
|
url: `notifications/${id}`
|
||||||
|
}).then(({ body }) => {
|
||||||
|
if (body.status) {
|
||||||
|
// @ts-ignore
|
||||||
|
navigation.navigate('Tab-Notifications', {
|
||||||
|
screen: 'Tab-Shared-Toot',
|
||||||
|
params: { toot: body.status }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default pushNavigate
|
58
src/screens/Tabs/utils/pushReceive.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { displayMessage } from '@components/Message'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
|
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 pushNavigate from './pushNavigate'
|
||||||
|
|
||||||
|
export interface Params {
|
||||||
|
navigation: StackNavigationProp<Nav.RootStackParamList, 'Screen-Tabs'>
|
||||||
|
queryClient: QueryClient
|
||||||
|
instances: Instance[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushReceive = ({ navigation, 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]))
|
||||||
|
}
|
||||||
|
pushNavigate(navigation, payloadData.notification_id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return () => subscription.remove()
|
||||||
|
}, [instances])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default pushReceive
|
54
src/screens/Tabs/utils/pushRespond.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
|
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 pushNavigate from './pushNavigate'
|
||||||
|
|
||||||
|
export interface Params {
|
||||||
|
navigation: StackNavigationProp<Nav.RootStackParamList, 'Screen-Tabs'>
|
||||||
|
queryClient: QueryClient
|
||||||
|
instances: Instance[]
|
||||||
|
dispatch: Dispatch<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushRespond = ({
|
||||||
|
navigation,
|
||||||
|
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]))
|
||||||
|
}
|
||||||
|
pushNavigate(navigation, payloadData.notification_id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return () => subscription.remove()
|
||||||
|
}, [instances])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default pushRespond
|
@ -10,8 +10,6 @@ const push = () => {
|
|||||||
shouldSetBadge: false
|
shouldSetBadge: false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Notifications.setBadgeCountAsync(0)
|
|
||||||
Notifications.dismissAllNotificationsAsync()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default push
|
export default push
|
||||||
|
11
src/utils/slices/instances/push/androidDefaults.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as Notifications from 'expo-notifications'
|
||||||
|
|
||||||
|
const androidDefaults = {
|
||||||
|
importance: Notifications.AndroidImportance.DEFAULT,
|
||||||
|
bypassDnd: false,
|
||||||
|
showBadge: true,
|
||||||
|
enableLights: true,
|
||||||
|
enableVibrate: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export default androidDefaults
|
@ -1,5 +1,6 @@
|
|||||||
import apiGeneral from '@api/general'
|
import apiGeneral from '@api/general'
|
||||||
import apiInstance from '@api/instance'
|
import apiInstance from '@api/instance'
|
||||||
|
import i18n from '@root/i18n/i18n'
|
||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import {
|
import {
|
||||||
getInstance,
|
getInstance,
|
||||||
@ -8,6 +9,7 @@ import {
|
|||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
|
import androidDefaults from './androidDefaults'
|
||||||
|
|
||||||
const register1 = async ({
|
const register1 = async ({
|
||||||
expoToken,
|
expoToken,
|
||||||
@ -66,17 +68,6 @@ const pushRegister = async (
|
|||||||
return Promise.reject()
|
return Promise.reject()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { status: existingStatus } = await Notifications.getPermissionsAsync()
|
|
||||||
let finalStatus = existingStatus
|
|
||||||
if (existingStatus !== 'granted') {
|
|
||||||
const { status } = await Notifications.requestPermissionsAsync()
|
|
||||||
finalStatus = status
|
|
||||||
}
|
|
||||||
if (finalStatus !== 'granted') {
|
|
||||||
alert('Failed to get push token for push notification!')
|
|
||||||
return Promise.reject()
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountId = instanceAccount.id
|
const accountId = instanceAccount.id
|
||||||
const accountFull = `@${instanceAccount.acct}@${instanceUri}`
|
const accountFull = `@${instanceAccount.acct}@${instanceUri}`
|
||||||
const serverRes = await register1({
|
const serverRes = await register1({
|
||||||
@ -111,25 +102,45 @@ const pushRegister = async (
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
Notifications.setNotificationChannelAsync('follow', {
|
Notifications.setNotificationChannelGroupAsync(accountFull, {
|
||||||
name: 'Follow',
|
name: accountFull,
|
||||||
importance: Notifications.AndroidImportance.DEFAULT
|
...androidDefaults
|
||||||
|
}).then(group => {
|
||||||
|
if (group) {
|
||||||
|
if (instancePush.decode.value === false) {
|
||||||
|
Notifications.setNotificationChannelAsync(`${group.id}_default`, {
|
||||||
|
groupId: group.id,
|
||||||
|
name: i18n.t('meSettingsPush:content.default.heading'),
|
||||||
|
...androidDefaults
|
||||||
})
|
})
|
||||||
Notifications.setNotificationChannelAsync('favourite', {
|
} else {
|
||||||
name: 'Favourite',
|
Notifications.setNotificationChannelAsync(`${group.id}_follow`, {
|
||||||
importance: Notifications.AndroidImportance.DEFAULT
|
groupId: group.id,
|
||||||
|
name: i18n.t('meSettingsPush:content.follow.heading'),
|
||||||
|
...androidDefaults
|
||||||
})
|
})
|
||||||
Notifications.setNotificationChannelAsync('reblog', {
|
Notifications.setNotificationChannelAsync(`${group.id}_favourite`, {
|
||||||
name: 'Reblog',
|
groupId: group.id,
|
||||||
importance: Notifications.AndroidImportance.DEFAULT
|
name: i18n.t('meSettingsPush:content.favourite.heading'),
|
||||||
|
...androidDefaults
|
||||||
})
|
})
|
||||||
Notifications.setNotificationChannelAsync('mention', {
|
Notifications.setNotificationChannelAsync(`${group.id}_reblog`, {
|
||||||
name: 'Mention',
|
groupId: group.id,
|
||||||
importance: Notifications.AndroidImportance.DEFAULT
|
name: i18n.t('meSettingsPush:content.reblog.heading'),
|
||||||
|
...androidDefaults
|
||||||
})
|
})
|
||||||
Notifications.setNotificationChannelAsync('poll', {
|
Notifications.setNotificationChannelAsync(`${group.id}_mention`, {
|
||||||
name: 'Poll',
|
groupId: group.id,
|
||||||
importance: Notifications.AndroidImportance.DEFAULT
|
name: i18n.t('meSettingsPush:content.mention.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
Notifications.setNotificationChannelAsync(`${group.id}_poll`, {
|
||||||
|
groupId: group.id,
|
||||||
|
name: i18n.t('meSettingsPush:content.poll.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,13 @@ import apiGeneral from '@api/general'
|
|||||||
import apiInstance from '@api/instance'
|
import apiInstance from '@api/instance'
|
||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import { getInstance, PUSH_SERVER } from '@utils/slices/instancesSlice'
|
import { getInstance, PUSH_SERVER } from '@utils/slices/instancesSlice'
|
||||||
|
import * as Notifications from 'expo-notifications'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
|
|
||||||
const pushUnregister = async (state: RootState, expoToken: string) => {
|
const pushUnregister = async (state: RootState, expoToken: string) => {
|
||||||
const instance = getInstance(state)
|
const instance = getInstance(state)
|
||||||
|
const instanceUri = instance?.uri
|
||||||
|
const instanceAccount = instance?.account
|
||||||
|
|
||||||
if (!instance?.url || !instance.account.id) {
|
if (!instance?.url || !instance.account.id) {
|
||||||
return Promise.reject()
|
return Promise.reject()
|
||||||
@ -26,6 +30,11 @@ const pushUnregister = async (state: RootState, expoToken: string) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
const accountFull = `@${instanceAccount?.acct}@${instanceUri}`
|
||||||
|
Notifications.deleteNotificationChannelGroupAsync(accountFull)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import apiGeneral from '@api/general'
|
import apiGeneral from '@api/general'
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit'
|
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||||
|
import i18n from '@root/i18n/i18n'
|
||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
import { getInstance, Instance, PUSH_SERVER } from '../instancesSlice'
|
import { getInstance, Instance, PUSH_SERVER } from '../instancesSlice'
|
||||||
|
import androidDefaults from './push/androidDefaults'
|
||||||
|
|
||||||
export const updateInstancePushDecode = createAsyncThunk(
|
export const updateInstancePushDecode = createAsyncThunk(
|
||||||
'instances/updatePushDecode',
|
'instances/updatePushDecode',
|
||||||
async (
|
async (
|
||||||
disalbe: boolean,
|
disable: boolean,
|
||||||
{ getState }
|
{ getState }
|
||||||
): Promise<Instance['push']['decode']['value']> => {
|
): Promise<Instance['push']['decode']['value']> => {
|
||||||
const state = getState() as RootState
|
const state = getState() as RootState
|
||||||
@ -30,10 +33,61 @@ export const updateInstancePushDecode = createAsyncThunk(
|
|||||||
expoToken,
|
expoToken,
|
||||||
instanceUrl: instance.url,
|
instanceUrl: instance.url,
|
||||||
accountId: instance.account.id,
|
accountId: instance.account.id,
|
||||||
...(disalbe && { keys: instance.push.keys })
|
...(disable && { keys: instance.push.keys })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.resolve(disalbe)
|
if (Platform.OS === 'android') {
|
||||||
|
const accountFull = `@${instance.account.acct}@${instance.uri}`
|
||||||
|
switch (disable) {
|
||||||
|
case true:
|
||||||
|
Notifications.deleteNotificationChannelAsync(`${accountFull}_default`)
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_follow`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: i18n.t('meSettingsPush:content.follow.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
Notifications.setNotificationChannelAsync(
|
||||||
|
`${accountFull}_favourite`,
|
||||||
|
{
|
||||||
|
groupId: accountFull,
|
||||||
|
name: i18n.t('meSettingsPush:content.favourite.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_reblog`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: i18n.t('meSettingsPush:content.reblog.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_mention`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: i18n.t('meSettingsPush:content.mention.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_poll`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: i18n.t('meSettingsPush:content.poll.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case false:
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_default`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: i18n.t('meSettingsPush:content.default.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
Notifications.deleteNotificationChannelAsync(`${accountFull}_follow`)
|
||||||
|
Notifications.deleteNotificationChannelAsync(
|
||||||
|
`${accountFull}_favourite`
|
||||||
|
)
|
||||||
|
Notifications.deleteNotificationChannelAsync(`${accountFull}_reblog`)
|
||||||
|
Notifications.deleteNotificationChannelAsync(`${accountFull}_mention`)
|
||||||
|
Notifications.deleteNotificationChannelAsync(`${accountFull}_poll`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(disable)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
|||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import { ComposeStateDraft } from '@screens/Compose/utils/types'
|
import { ComposeStateDraft } from '@screens/Compose/utils/types'
|
||||||
import { findIndex } from 'lodash'
|
import { findIndex } from 'lodash'
|
||||||
import { Appearance } from 'react-native'
|
|
||||||
import addInstance from './instances/add'
|
import addInstance from './instances/add'
|
||||||
import { connectInstancesPush } from './instances/connectPush'
|
import { connectInstancesPush } from './instances/connectPush'
|
||||||
import removeInstance from './instances/remove'
|
import removeInstance from './instances/remove'
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
|
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
import { RootState } from '@root/store'
|
import i18n from '@root/i18n/i18n'
|
||||||
|
import { RootState, store } from '@root/store'
|
||||||
import * as Analytics from 'expo-firebase-analytics'
|
import * as Analytics from 'expo-firebase-analytics'
|
||||||
import * as Localization from 'expo-localization'
|
import * as Localization from 'expo-localization'
|
||||||
|
import * as Notifications from 'expo-notifications'
|
||||||
import { pickBy } from 'lodash'
|
import { pickBy } from 'lodash'
|
||||||
|
import androidDefaults from './instances/push/androidDefaults'
|
||||||
|
import { getInstances } from './instancesSlice'
|
||||||
|
|
||||||
enum availableLanguages {
|
enum availableLanguages {
|
||||||
'zh-Hans',
|
'zh-Hans',
|
||||||
|