feat (Android): mobile ringer (#5286)
This commit is contained in:
parent
bd17ee55bf
commit
31ed940426
Binary file not shown.
|
@ -97,10 +97,14 @@ public class CustomPushNotification extends PushNotification {
|
||||||
bundle.putString("senderId", hasSender ? loadedEjson.sender._id : "1");
|
bundle.putString("senderId", hasSender ? loadedEjson.sender._id : "1");
|
||||||
bundle.putString("avatarUri", loadedEjson.getAvatarUri());
|
bundle.putString("avatarUri", loadedEjson.getAvatarUri());
|
||||||
|
|
||||||
|
if (loadedEjson.notificationType.equals("videoconf")) {
|
||||||
|
notifyReceivedToJS();
|
||||||
|
} else {
|
||||||
notificationMessages.get(notId).add(bundle);
|
notificationMessages.get(notId).add(bundle);
|
||||||
postNotification(Integer.parseInt(notId));
|
postNotification(Integer.parseInt(notId));
|
||||||
notifyReceivedToJS();
|
notifyReceivedToJS();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOpened() {
|
public void onOpened() {
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DI
|
||||||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
export const LOGOUT = 'LOGOUT'; // logout is always success
|
||||||
export const DELETE_ACCOUNT = 'DELETE_ACCOUNT';
|
export const DELETE_ACCOUNT = 'DELETE_ACCOUNT';
|
||||||
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||||
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
|
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN', 'OPEN_VIDEO_CONF']);
|
||||||
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
|
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
|
||||||
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
|
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
|
||||||
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'CLEAR']);
|
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'CLEAR']);
|
||||||
|
|
|
@ -22,3 +22,10 @@ export function deepLinkingOpen(params: Partial<IParams>): IDeepLinkingOpen {
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deepLinkingClickCallPush(params: any): IDeepLinkingOpen {
|
||||||
|
return {
|
||||||
|
type: DEEP_LINKING.OPEN_VIDEO_CONF,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ export enum ERingerSounds {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Ringer = React.memo(({ ringer }: { ringer: ERingerSounds }) => {
|
const Ringer = React.memo(({ ringer }: { ringer: ERingerSounds }) => {
|
||||||
console.log('Ringer', ringer);
|
|
||||||
|
|
||||||
const sound = useRef<Audio.Sound | null>(null);
|
const sound = useRef<Audio.Sound | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
|
@ -749,6 +749,7 @@
|
||||||
"Continue": "Continue",
|
"Continue": "Continue",
|
||||||
"Message_has_been_shared": "Message has been shared",
|
"Message_has_been_shared": "Message has been shared",
|
||||||
"No_channels_in_team": "No Channels on this team",
|
"No_channels_in_team": "No Channels on this team",
|
||||||
|
"conference_call": "Conference call",
|
||||||
"Room_not_found": "Room not found",
|
"Room_not_found": "Room not found",
|
||||||
"The_room_does_not_exist": "The room does not exist or you may not have access permission",
|
"The_room_does_not_exist": "The room does not exist or you may not have access permission",
|
||||||
"Supported_versions_expired_title": "{{workspace_name}} is running an unsupported version of Rocket.Chat",
|
"Supported_versions_expired_title": "{{workspace_name}} is running an unsupported version of Rocket.Chat",
|
||||||
|
@ -758,5 +759,6 @@
|
||||||
"The_user_will_be_able_to_type_in_roomName": "The user will be able to type in {{roomName}}",
|
"The_user_will_be_able_to_type_in_roomName": "The user will be able to type in {{roomName}}",
|
||||||
"Enable_writing_in_room": "Enable writing in room",
|
"Enable_writing_in_room": "Enable writing in room",
|
||||||
"Disable_writing_in_room": "Disable writing in room",
|
"Disable_writing_in_room": "Disable writing in room",
|
||||||
"Pinned_a_message": "Pinned a message:"
|
"Pinned_a_message": "Pinned a message:",
|
||||||
|
"Missed_call": "Missed call"
|
||||||
}
|
}
|
||||||
|
|
|
@ -747,7 +747,6 @@
|
||||||
"decline": "Recusar",
|
"decline": "Recusar",
|
||||||
"accept": "Aceitar",
|
"accept": "Aceitar",
|
||||||
"Incoming_call_from": "Chamada recebida de",
|
"Incoming_call_from": "Chamada recebida de",
|
||||||
"Call_started": "Chamada Iniciada",
|
|
||||||
"Room_not_found": "Sala não encontrada",
|
"Room_not_found": "Sala não encontrada",
|
||||||
"The_room_does_not_exist": "A sala não existe ou você pode não ter permissão de acesso",
|
"The_room_does_not_exist": "A sala não existe ou você pode não ter permissão de acesso",
|
||||||
"Call_started": "Chamada iniciada",
|
"Call_started": "Chamada iniciada",
|
||||||
|
@ -757,5 +756,6 @@
|
||||||
"The_user_wont_be_able_to_type_in_roomName": "O usuário não poderá digitar em {{roomName}}",
|
"The_user_wont_be_able_to_type_in_roomName": "O usuário não poderá digitar em {{roomName}}",
|
||||||
"The_user_will_be_able_to_type_in_roomName": "O usuário poderá digitar em {{roomName}}",
|
"The_user_will_be_able_to_type_in_roomName": "O usuário poderá digitar em {{roomName}}",
|
||||||
"Enable_writing_in_room": "Permitir escrita na sala",
|
"Enable_writing_in_room": "Permitir escrita na sala",
|
||||||
"Disable_writing_in_room": "Desabilitar escrita na sala"
|
"Disable_writing_in_room": "Desabilitar escrita na sala",
|
||||||
|
"Missed_call": "Chamada perdida"
|
||||||
}
|
}
|
|
@ -1,30 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Dimensions, Linking } from 'react-native';
|
import { Dimensions, Linking } from 'react-native';
|
||||||
import { initialWindowMetrics, SafeAreaProvider } from 'react-native-safe-area-context';
|
|
||||||
import RNScreens from 'react-native-screens';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||||
import Orientation from 'react-native-orientation-locker';
|
import Orientation from 'react-native-orientation-locker';
|
||||||
|
import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context';
|
||||||
|
import RNScreens from 'react-native-screens';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import AppContainer from './AppContainer';
|
||||||
import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app';
|
import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app';
|
||||||
import { deepLinkingOpen } from './actions/deepLinking';
|
import { deepLinkingOpen } from './actions/deepLinking';
|
||||||
import AppContainer from './AppContainer';
|
|
||||||
import { ActionSheetProvider } from './containers/ActionSheet';
|
import { ActionSheetProvider } from './containers/ActionSheet';
|
||||||
import InAppNotification from './containers/InAppNotification';
|
import InAppNotification from './containers/InAppNotification';
|
||||||
|
import Loading from './containers/Loading';
|
||||||
import Toast from './containers/Toast';
|
import Toast from './containers/Toast';
|
||||||
import TwoFactor from './containers/TwoFactor';
|
import TwoFactor from './containers/TwoFactor';
|
||||||
import Loading from './containers/Loading';
|
|
||||||
import { IThemePreference } from './definitions/ITheme';
|
import { IThemePreference } from './definitions/ITheme';
|
||||||
import { DimensionsContext } from './dimensions';
|
import { DimensionsContext } from './dimensions';
|
||||||
import { colors, isFDroidBuild, MIN_WIDTH_MASTER_DETAIL_LAYOUT, themes } from './lib/constants';
|
import { MIN_WIDTH_MASTER_DETAIL_LAYOUT, colors, isFDroidBuild, themes } from './lib/constants';
|
||||||
import { getAllowAnalyticsEvents, getAllowCrashReport } from './lib/methods';
|
import { getAllowAnalyticsEvents, getAllowCrashReport } from './lib/methods';
|
||||||
import parseQuery from './lib/methods/helpers/parseQuery';
|
|
||||||
import { initializePushNotifications, onNotification } from './lib/notifications';
|
|
||||||
import store from './lib/store';
|
|
||||||
import { initStore } from './lib/store/auxStore';
|
|
||||||
import { ThemeContext, TSupportedThemes } from './theme';
|
|
||||||
import { debounce, isTablet } from './lib/methods/helpers';
|
import { debounce, isTablet } from './lib/methods/helpers';
|
||||||
import { toggleAnalyticsEventsReport, toggleCrashErrorsReport } from './lib/methods/helpers/log';
|
import { toggleAnalyticsEventsReport, toggleCrashErrorsReport } from './lib/methods/helpers/log';
|
||||||
|
import parseQuery from './lib/methods/helpers/parseQuery';
|
||||||
import {
|
import {
|
||||||
getTheme,
|
getTheme,
|
||||||
initialTheme,
|
initialTheme,
|
||||||
|
@ -33,6 +29,11 @@ import {
|
||||||
subscribeTheme,
|
subscribeTheme,
|
||||||
unsubscribeTheme
|
unsubscribeTheme
|
||||||
} from './lib/methods/helpers/theme';
|
} from './lib/methods/helpers/theme';
|
||||||
|
import { initializePushNotifications, onNotification } from './lib/notifications';
|
||||||
|
import { getInitialNotification } from './lib/notifications/videoConf/getInitialNotification';
|
||||||
|
import store from './lib/store';
|
||||||
|
import { initStore } from './lib/store/auxStore';
|
||||||
|
import { TSupportedThemes, ThemeContext } from './theme';
|
||||||
import ChangePasscodeView from './views/ChangePasscodeView';
|
import ChangePasscodeView from './views/ChangePasscodeView';
|
||||||
import ScreenLockedView from './views/ScreenLockedView';
|
import ScreenLockedView from './views/ScreenLockedView';
|
||||||
|
|
||||||
|
@ -126,6 +127,8 @@ export default class Root extends React.Component<{}, IState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await getInitialNotification();
|
||||||
|
|
||||||
// Open app from deep linking
|
// Open app from deep linking
|
||||||
const deepLinking = await Linking.getInitialURL();
|
const deepLinking = await Linking.getInitialURL();
|
||||||
const parsedDeepLinkingURL = parseDeepLinking(deepLinking!);
|
const parsedDeepLinkingURL = parseDeepLinking(deepLinking!);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
export const BACKGROUND_PUSH_COLOR = '#F5455C';
|
||||||
|
|
||||||
export const STATUS_COLORS: any = {
|
export const STATUS_COLORS: any = {
|
||||||
online: '#2de0a5',
|
online: '#2de0a5',
|
||||||
busy: '#f5455c',
|
busy: '#f5455c',
|
||||||
|
|
|
@ -118,3 +118,5 @@ export const goRoom = async ({
|
||||||
|
|
||||||
return navigate({ item, isMasterDetail, popToRoot, ...props });
|
return navigate({ item, isMasterDetail, popToRoot, ...props });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const navigateToRoom = navigate;
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const handleAndroidBltPermission = async (): Promise<void> => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const videoConfJoin = async (callId: string, cam?: boolean, mic?: boolean): Promise<void> => {
|
export const videoConfJoin = async (callId: string, cam?: boolean, mic?: boolean, fromPush?: boolean): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const result = await Services.videoConferenceJoin(callId, cam, mic);
|
const result = await Services.videoConferenceJoin(callId, cam, mic);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
@ -38,7 +38,11 @@ export const videoConfJoin = async (callId: string, cam?: boolean, mic?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (fromPush) {
|
||||||
|
showErrorAlert(i18n.t('Missed_call'));
|
||||||
|
} else {
|
||||||
showErrorAlert(i18n.t('error-init-video-conf'));
|
showErrorAlert(i18n.t('error-init-video-conf'));
|
||||||
|
}
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import EJSON from 'ejson';
|
import EJSON from 'ejson';
|
||||||
|
|
||||||
import { store } from '../store/auxStore';
|
|
||||||
import { deepLinkingOpen } from '../../actions/deepLinking';
|
import { deepLinkingOpen } from '../../actions/deepLinking';
|
||||||
import { isFDroidBuild } from '../constants';
|
|
||||||
import { deviceToken, pushNotificationConfigure, setNotificationsBadgeCount, removeAllNotifications } from './push';
|
|
||||||
import { INotification, SubscriptionType } from '../../definitions';
|
import { INotification, SubscriptionType } from '../../definitions';
|
||||||
|
import { isFDroidBuild } from '../constants';
|
||||||
|
import { store } from '../store/auxStore';
|
||||||
|
import { deviceToken, pushNotificationConfigure, removeAllNotifications, setNotificationsBadgeCount } from './push';
|
||||||
|
|
||||||
interface IEjson {
|
interface IEjson {
|
||||||
rid: string;
|
rid: string;
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
import notifee, { AndroidCategory, AndroidImportance, AndroidVisibility, Event } from '@notifee/react-native';
|
||||||
|
import messaging from '@react-native-firebase/messaging';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
import ejson from 'ejson';
|
||||||
|
|
||||||
|
import { deepLinkingClickCallPush } from '../../../actions/deepLinking';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import { BACKGROUND_PUSH_COLOR } from '../../constants';
|
||||||
|
import { store } from '../../store/auxStore';
|
||||||
|
|
||||||
|
const VIDEO_CONF_CHANNEL = 'video-conf-call';
|
||||||
|
const VIDEO_CONF_TYPE = 'videoconf';
|
||||||
|
|
||||||
|
interface Caller {
|
||||||
|
_id?: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NotificationData {
|
||||||
|
notificationType?: string;
|
||||||
|
status?: number;
|
||||||
|
rid?: string;
|
||||||
|
caller?: Caller;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createChannel = () =>
|
||||||
|
notifee.createChannel({
|
||||||
|
id: VIDEO_CONF_CHANNEL,
|
||||||
|
name: 'Video Call',
|
||||||
|
lights: true,
|
||||||
|
vibration: true,
|
||||||
|
importance: AndroidImportance.HIGH,
|
||||||
|
sound: 'ringtone'
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBackgroundEvent = async (event: Event) => {
|
||||||
|
const { pressAction, notification } = event.detail;
|
||||||
|
const notificationData = notification?.data;
|
||||||
|
if (
|
||||||
|
typeof notificationData?.caller === 'object' &&
|
||||||
|
(notificationData.caller as Caller)?._id &&
|
||||||
|
(event.type === 1 || event.type === 2)
|
||||||
|
) {
|
||||||
|
if (store?.getState()?.app.ready) {
|
||||||
|
store.dispatch(deepLinkingClickCallPush({ ...notificationData, event: pressAction?.id }));
|
||||||
|
} else {
|
||||||
|
AsyncStorage.setItem('pushNotification', JSON.stringify({ ...notificationData, event: pressAction?.id }));
|
||||||
|
}
|
||||||
|
await notifee.cancelNotification(
|
||||||
|
`${notificationData.rid}${(notificationData.caller as Caller)._id}`.replace(/[^A-Za-z0-9]/g, '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const backgroundNotificationHandler = () => {
|
||||||
|
notifee.onBackgroundEvent(handleBackgroundEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayVideoConferenceNotification = async (notification: NotificationData) => {
|
||||||
|
const id = `${notification.rid}${notification.caller?._id}`.replace(/[^A-Za-z0-9]/g, '');
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
title: i18n.t('accept'),
|
||||||
|
pressAction: {
|
||||||
|
id: 'accept',
|
||||||
|
launchActivity: 'default'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18n.t('decline'),
|
||||||
|
pressAction: {
|
||||||
|
id: 'decline',
|
||||||
|
launchActivity: 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
await notifee.displayNotification({
|
||||||
|
id,
|
||||||
|
title: i18n.t('conference_call'),
|
||||||
|
body: `${i18n.t('Incoming_call_from')} ${notification.caller?.name}`,
|
||||||
|
data: notification as { [key: string]: string | number | object },
|
||||||
|
android: {
|
||||||
|
channelId: VIDEO_CONF_CHANNEL,
|
||||||
|
category: AndroidCategory.CALL,
|
||||||
|
visibility: AndroidVisibility.PUBLIC,
|
||||||
|
importance: AndroidImportance.HIGH,
|
||||||
|
smallIcon: 'ic_notification',
|
||||||
|
color: BACKGROUND_PUSH_COLOR,
|
||||||
|
actions,
|
||||||
|
lightUpScreen: true,
|
||||||
|
loopSound: true,
|
||||||
|
sound: 'ringtone',
|
||||||
|
autoCancel: false,
|
||||||
|
ongoing: true,
|
||||||
|
pressAction: {
|
||||||
|
id: 'default',
|
||||||
|
launchActivity: 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setBackgroundNotificationHandler = () => {
|
||||||
|
createChannel();
|
||||||
|
messaging().setBackgroundMessageHandler(async message => {
|
||||||
|
const notification: NotificationData = ejson.parse(message?.data?.ejson as string);
|
||||||
|
if (notification?.notificationType === VIDEO_CONF_TYPE) {
|
||||||
|
if (notification.status === 0) {
|
||||||
|
await displayVideoConferenceNotification(notification);
|
||||||
|
} else if (notification.status === 4) {
|
||||||
|
const id = `${notification.rid}${notification.caller?._id}`.replace(/[^A-Za-z0-9]/g, '');
|
||||||
|
await notifee.cancelNotification(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setBackgroundNotificationHandler();
|
||||||
|
backgroundNotificationHandler();
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { deepLinkingClickCallPush } from '../../../actions/deepLinking';
|
||||||
|
import { isAndroid } from '../../methods/helpers';
|
||||||
|
import { store } from '../../store/auxStore';
|
||||||
|
|
||||||
|
export const getInitialNotification = async (): Promise<void> => {
|
||||||
|
if (isAndroid) {
|
||||||
|
const notifee = require('@notifee/react-native').default;
|
||||||
|
const initialNotification = await notifee.getInitialNotification();
|
||||||
|
if (initialNotification?.notification?.data?.notificationType === 'videoconf') {
|
||||||
|
store.dispatch(
|
||||||
|
deepLinkingClickCallPush({ ...initialNotification?.notification?.data, event: initialNotification?.pressAction?.id })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,20 +1,21 @@
|
||||||
import { all, delay, put, select, take, takeLatest } from 'redux-saga/effects';
|
import { all, call, delay, put, select, take, takeLatest } from 'redux-saga/effects';
|
||||||
|
|
||||||
import UserPreferences from '../lib/methods/userPreferences';
|
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { selectServerRequest, serverInitAdd } from '../actions/server';
|
|
||||||
import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks';
|
|
||||||
import database from '../lib/database';
|
|
||||||
import EventEmitter from '../lib/methods/helpers/events';
|
|
||||||
import { appInit, appStart } from '../actions/app';
|
import { appInit, appStart } from '../actions/app';
|
||||||
import { localAuthenticate } from '../lib/methods/helpers/localAuthentication';
|
import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks';
|
||||||
import { goRoom } from '../lib/methods/helpers/goRoom';
|
|
||||||
import { getUidDirectMessage } from '../lib/methods/helpers';
|
|
||||||
import { loginRequest } from '../actions/login';
|
import { loginRequest } from '../actions/login';
|
||||||
import log from '../lib/methods/helpers/log';
|
import { selectServerRequest, serverInitAdd } from '../actions/server';
|
||||||
import { RootEnum } from '../definitions';
|
import { RootEnum } from '../definitions';
|
||||||
import { CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
|
import { CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
|
||||||
|
import database from '../lib/database';
|
||||||
import { canOpenRoom, getServerInfo } from '../lib/methods';
|
import { canOpenRoom, getServerInfo } from '../lib/methods';
|
||||||
|
import { getUidDirectMessage } from '../lib/methods/helpers';
|
||||||
|
import EventEmitter from '../lib/methods/helpers/events';
|
||||||
|
import { goRoom, navigateToRoom } from '../lib/methods/helpers/goRoom';
|
||||||
|
import { localAuthenticate } from '../lib/methods/helpers/localAuthentication';
|
||||||
|
import log from '../lib/methods/helpers/log';
|
||||||
|
import UserPreferences from '../lib/methods/userPreferences';
|
||||||
|
import { videoConfJoin } from '../lib/methods/videoConf';
|
||||||
import { Services } from '../lib/services';
|
import { Services } from '../lib/services';
|
||||||
|
|
||||||
const roomTypes = {
|
const roomTypes = {
|
||||||
|
@ -168,7 +169,90 @@ const handleOpen = function* handleOpen({ params }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleNavigateCallRoom = function* handleNavigateCallRoom({ params }) {
|
||||||
|
yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
|
||||||
|
const db = database.active;
|
||||||
|
const subsCollection = db.get('subscriptions');
|
||||||
|
const room = yield subsCollection.find(params.rid);
|
||||||
|
if (room) {
|
||||||
|
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
||||||
|
yield navigateToRoom({ item: room, isMasterDetail, popToRoot: true });
|
||||||
|
const uid = params.caller._id;
|
||||||
|
const { rid, callId, event } = params;
|
||||||
|
if (event === 'accept') {
|
||||||
|
yield call(Services.notifyUser, `${uid}/video-conference`, {
|
||||||
|
action: 'accepted',
|
||||||
|
params: { uid, rid, callId }
|
||||||
|
});
|
||||||
|
yield videoConfJoin(callId, true, false, true);
|
||||||
|
} else if (event === 'decline') {
|
||||||
|
yield call(Services.notifyUser, `${uid}/video-conference`, {
|
||||||
|
action: 'rejected',
|
||||||
|
params: { uid, rid, callId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickCallPush = function* handleOpen({ params }) {
|
||||||
|
const serversDB = database.servers;
|
||||||
|
const serversCollection = serversDB.get('servers');
|
||||||
|
|
||||||
|
let { host } = params;
|
||||||
|
|
||||||
|
if (host.slice(-1) === '/') {
|
||||||
|
host = host.slice(0, host.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [server, user] = yield all([
|
||||||
|
UserPreferences.getString(CURRENT_SERVER),
|
||||||
|
UserPreferences.getString(`${TOKEN_KEY}-${host}`)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (server === host && user) {
|
||||||
|
const connected = yield select(state => state.server.connected);
|
||||||
|
if (!connected) {
|
||||||
|
yield localAuthenticate(host);
|
||||||
|
yield put(selectServerRequest(host));
|
||||||
|
yield take(types.LOGIN.SUCCESS);
|
||||||
|
}
|
||||||
|
yield handleNavigateCallRoom({ params });
|
||||||
|
} else {
|
||||||
|
// search if deep link's server already exists
|
||||||
|
try {
|
||||||
|
const hostServerRecord = yield serversCollection.find(host);
|
||||||
|
if (hostServerRecord && user) {
|
||||||
|
yield localAuthenticate(host);
|
||||||
|
yield put(selectServerRequest(host, hostServerRecord.version, true, true));
|
||||||
|
yield take(types.LOGIN.SUCCESS);
|
||||||
|
yield handleNavigateCallRoom({ params });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing?
|
||||||
|
}
|
||||||
|
// if deep link is from a different server
|
||||||
|
const result = yield Services.getServerInfo(host);
|
||||||
|
if (!result.success) {
|
||||||
|
// Fallback to prevent the app from being stuck on splash screen
|
||||||
|
yield fallbackNavigation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
||||||
|
yield put(serverInitAdd(server));
|
||||||
|
yield delay(1000);
|
||||||
|
EventEmitter.emit('NewServer', { server: host });
|
||||||
|
if (params.token) {
|
||||||
|
yield take(types.SERVER.SELECT_SUCCESS);
|
||||||
|
yield put(loginRequest({ resume: params.token }, true));
|
||||||
|
yield take(types.LOGIN.SUCCESS);
|
||||||
|
yield handleNavigateCallRoom({ params });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.DEEP_LINKING.OPEN, handleOpen);
|
yield takeLatest(types.DEEP_LINKING.OPEN, handleOpen);
|
||||||
|
yield takeLatest(types.DEEP_LINKING.OPEN_VIDEO_CONF, handleClickCallPush);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { put, takeLatest } from 'redux-saga/effects';
|
import { call, put, takeLatest } from 'redux-saga/effects';
|
||||||
import RNBootSplash from 'react-native-bootsplash';
|
import RNBootSplash from 'react-native-bootsplash';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
|
||||||
import { BIOMETRY_ENABLED_KEY, CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
|
import { BIOMETRY_ENABLED_KEY, CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
|
||||||
import UserPreferences from '../lib/methods/userPreferences';
|
import UserPreferences from '../lib/methods/userPreferences';
|
||||||
|
@ -12,6 +13,7 @@ import { localAuthenticate } from '../lib/methods/helpers/localAuthentication';
|
||||||
import { appReady, appStart } from '../actions/app';
|
import { appReady, appStart } from '../actions/app';
|
||||||
import { RootEnum } from '../definitions';
|
import { RootEnum } from '../definitions';
|
||||||
import { getSortPreferences } from '../lib/methods';
|
import { getSortPreferences } from '../lib/methods';
|
||||||
|
import { deepLinkingClickCallPush } from '../actions/deepLinking';
|
||||||
|
|
||||||
export const initLocalSettings = function* initLocalSettings() {
|
export const initLocalSettings = function* initLocalSettings() {
|
||||||
const sortPreferences = getSortPreferences();
|
const sortPreferences = getSortPreferences();
|
||||||
|
@ -70,6 +72,11 @@ const restore = function* restore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put(appReady({}));
|
yield put(appReady({}));
|
||||||
|
const pushNotification = yield call(AsyncStorage.getItem, 'pushNotification');
|
||||||
|
if (pushNotification) {
|
||||||
|
const pushNotification = yield call(AsyncStorage.removeItem, 'pushNotification');
|
||||||
|
yield call(deepLinkingClickCallPush, JSON.parse(pushNotification));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
||||||
|
|
7
index.js
7
index.js
|
@ -3,6 +3,8 @@ import 'react-native-console-time-polyfill';
|
||||||
import { AppRegistry } from 'react-native';
|
import { AppRegistry } from 'react-native';
|
||||||
|
|
||||||
import { name as appName, share as shareName } from './app.json';
|
import { name as appName, share as shareName } from './app.json';
|
||||||
|
import { isFDroidBuild } from './app/lib/constants';
|
||||||
|
import { isAndroid } from './app/lib/methods/helpers';
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
require('./app/ReactotronConfig');
|
require('./app/ReactotronConfig');
|
||||||
|
@ -18,6 +20,11 @@ if (__DEV__) {
|
||||||
console.info = () => {};
|
console.info = () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isFDroidBuild && isAndroid) {
|
||||||
|
require('./app/lib/notifications/videoConf/backgroundNotificationHandler');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
AppRegistry.registerComponent(appName, () => require('./app/index').default);
|
AppRegistry.registerComponent(appName, () => require('./app/index').default);
|
||||||
AppRegistry.registerComponent(shareName, () => require('./app/share').default);
|
AppRegistry.registerComponent(shareName, () => require('./app/share').default);
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
"@codler/react-native-keyboard-aware-scroll-view": "^2.0.1",
|
"@codler/react-native-keyboard-aware-scroll-view": "^2.0.1",
|
||||||
"@gorhom/bottom-sheet": "^4.3.1",
|
"@gorhom/bottom-sheet": "^4.3.1",
|
||||||
"@hookform/resolvers": "^2.9.10",
|
"@hookform/resolvers": "^2.9.10",
|
||||||
|
"@notifee/react-native": "^7.8.0",
|
||||||
"@nozbe/watermelondb": "0.23.0",
|
"@nozbe/watermelondb": "0.23.0",
|
||||||
"@react-native-async-storage/async-storage": "^1.17.11",
|
"@react-native-async-storage/async-storage": "^1.17.11",
|
||||||
"@react-native-camera-roll/camera-roll": "5.6.1",
|
"@react-native-camera-roll/camera-roll": "5.6.1",
|
||||||
|
@ -46,6 +47,7 @@
|
||||||
"@react-native-firebase/analytics": "^14.11.0",
|
"@react-native-firebase/analytics": "^14.11.0",
|
||||||
"@react-native-firebase/app": "^14.11.0",
|
"@react-native-firebase/app": "^14.11.0",
|
||||||
"@react-native-firebase/crashlytics": "^14.11.0",
|
"@react-native-firebase/crashlytics": "^14.11.0",
|
||||||
|
"@react-native-firebase/messaging": "^18.5.0",
|
||||||
"@react-native-masked-view/masked-view": "^0.2.8",
|
"@react-native-masked-view/masked-view": "^0.2.8",
|
||||||
"@react-navigation/drawer": "6.4.1",
|
"@react-navigation/drawer": "6.4.1",
|
||||||
"@react-navigation/elements": "^1.3.6",
|
"@react-navigation/elements": "^1.3.6",
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
--- a/node_modules/@notifee/react-native/android/src/main/java/io/invertase/notifee/NotifeeApiModule.java
|
||||||
|
+++ b/node_modules/@notifee/react-native/android/src/main/java/io/invertase/notifee/NotifeeApiModule.java
|
||||||
|
@@ -238,7 +238,7 @@ public class NotifeeApiModule extends ReactContextBaseJavaModule implements Perm
|
||||||
|
@ReactMethod
|
||||||
|
public void requestPermission(Promise promise) {
|
||||||
|
// For Android 12 and below, we return the notification settings
|
||||||
|
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
+ if (Build.VERSION.SDK_INT < 33) {
|
||||||
|
Notifee.getInstance()
|
||||||
|
.getNotificationSettings(
|
||||||
|
(e, aBundle) -> NotifeeReactUtils.promiseResolver(promise, e, aBundle));
|
||||||
|
@@ -265,7 +265,7 @@ public class NotifeeApiModule extends ReactContextBaseJavaModule implements Perm
|
||||||
|
(e, aBundle) -> NotifeeReactUtils.promiseResolver(promise, e, aBundle));
|
||||||
|
|
||||||
|
activity.requestPermissions(
|
||||||
|
- new String[] {Manifest.permission.POST_NOTIFICATIONS},
|
||||||
|
+ new String[] {"android.permission.POST_NOTIFICATIONS"},
|
||||||
|
Notifee.REQUEST_CODE_NOTIFICATION_PERMISSION,
|
||||||
|
this);
|
||||||
|
}
|
|
@ -14,6 +14,16 @@ module.exports = {
|
||||||
platforms: {
|
platforms: {
|
||||||
android: null
|
android: null
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'@react-native-firebase/messaging': {
|
||||||
|
platforms: {
|
||||||
|
ios: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'@notifee/react-native': {
|
||||||
|
platforms: {
|
||||||
|
ios: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -5153,6 +5153,11 @@
|
||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@notifee/react-native@^7.8.0":
|
||||||
|
version "7.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-7.8.0.tgz#2990883753990f3585aa0cb5becc5cbdbcd87a43"
|
||||||
|
integrity sha512-sx8h62U4FrR4pqlbN1rkgPsdamDt9Tad0zgfO6VtP6rNJq/78k8nxUnh0xIX3WPDcCV8KAzdYCE7+UNvhF1CpQ==
|
||||||
|
|
||||||
"@nozbe/simdjson@0.9.6-fix2":
|
"@nozbe/simdjson@0.9.6-fix2":
|
||||||
version "0.9.6-fix2"
|
version "0.9.6-fix2"
|
||||||
resolved "https://registry.yarnpkg.com/@nozbe/simdjson/-/simdjson-0.9.6-fix2.tgz#00d1c8ec76bfac25c022b07511c8fff4568b2973"
|
resolved "https://registry.yarnpkg.com/@nozbe/simdjson/-/simdjson-0.9.6-fix2.tgz#00d1c8ec76bfac25c022b07511c8fff4568b2973"
|
||||||
|
@ -5488,6 +5493,11 @@
|
||||||
"@expo/config-plugins" "^4.1.5"
|
"@expo/config-plugins" "^4.1.5"
|
||||||
stacktrace-js "^2.0.0"
|
stacktrace-js "^2.0.0"
|
||||||
|
|
||||||
|
"@react-native-firebase/messaging@^18.5.0":
|
||||||
|
version "18.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-18.5.0.tgz#2a80b25816470e9843682e031a3a113566067ce6"
|
||||||
|
integrity sha512-y1FApYxBMcygmbWBqUPFC+fCfvx6Yf6TdZewun7kPwx+S+tkYzoKx1IsXtxOXtqyJjCNEYirjFgNrs5SSd02zA==
|
||||||
|
|
||||||
"@react-native-masked-view/masked-view@^0.2.8":
|
"@react-native-masked-view/masked-view@^0.2.8":
|
||||||
version "0.2.8"
|
version "0.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.2.8.tgz#34405a4361882dae7c81b1b771fe9f5fbd545a97"
|
resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.2.8.tgz#34405a4361882dae7c81b1b771fe9f5fbd545a97"
|
||||||
|
|
Loading…
Reference in New Issue