mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-20 12:17:47 +00:00
[RN] add support for inviting participants during a call on mobile
* Button conditionally shown based on if the feature is enabled and available * Hooks for launching the invite UI (delegates to the native layer) * Hooks for using the search and dial out checks from the native layer (calls back into JS) * Hooks for handling sending invites and passing any failures back to the native layer * Android and iOS handling for those hooks Author: Ryan Peck <rpeck@atlassian.com> Author: Eric Brynsvold <ebrynsvold@atlassian.com>
This commit is contained in:
committed by
Saúl Ibarra Corretgé
parent
4e36127dc7
commit
f64c13d4b7
46
react/features/mobile/invite-search/actionTypes.js
Normal file
46
react/features/mobile/invite-search/actionTypes.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* The type of redux action to set InviteSearch's event subscriptions.
|
||||
*
|
||||
* {
|
||||
* type: _SET_INVITE_SEARCH_SUBSCRIPTIONS,
|
||||
* subscriptions: Array|undefined
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _SET_INVITE_SEARCH_SUBSCRIPTIONS
|
||||
= Symbol('_SET_INVITE_SEARCH_SUBSCRIPTIONS');
|
||||
|
||||
|
||||
/**
|
||||
* The type of the action which signals a request to launch the native invite
|
||||
* dialog.
|
||||
*
|
||||
* {
|
||||
* type: LAUNCH_NATIVE_INVITE
|
||||
* }
|
||||
*/
|
||||
export const LAUNCH_NATIVE_INVITE = Symbol('LAUNCH_NATIVE_INVITE');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that native invites were sent
|
||||
* successfully.
|
||||
*
|
||||
* {
|
||||
* type: SEND_INVITE_SUCCESS,
|
||||
* inviteScope: string
|
||||
* }
|
||||
*/
|
||||
export const SEND_INVITE_SUCCESS = Symbol('SEND_INVITE_SUCCESS');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that native invites failed to send
|
||||
* successfully.
|
||||
*
|
||||
* {
|
||||
* type: SEND_INVITE_FAILURE,
|
||||
* items: Array<*>,
|
||||
* inviteScope: string
|
||||
* }
|
||||
*/
|
||||
export const SEND_INVITE_FAILURE = Symbol('SEND_INVITE_FAILURE');
|
||||
50
react/features/mobile/invite-search/actions.js
Normal file
50
react/features/mobile/invite-search/actions.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
LAUNCH_NATIVE_INVITE,
|
||||
SEND_INVITE_SUCCESS,
|
||||
SEND_INVITE_FAILURE
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Launches the native invite dialog.
|
||||
*
|
||||
* @returns {{
|
||||
* type: LAUNCH_NATIVE_INVITE
|
||||
* }}
|
||||
*/
|
||||
export function launchNativeInvite() {
|
||||
return {
|
||||
type: LAUNCH_NATIVE_INVITE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that all native invites were sent successfully.
|
||||
*
|
||||
* @param {string} inviteScope - Scope identifier for the invite success. This
|
||||
* is used to look up relevant information on the native side.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function sendInviteSuccess(inviteScope: string) {
|
||||
return {
|
||||
type: SEND_INVITE_SUCCESS,
|
||||
inviteScope
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that some native invites failed to send successfully.
|
||||
*
|
||||
* @param {Array<*>} items - Invite items that failed to send.
|
||||
* @param {string} inviteScope - Scope identifier for the invite failure. This
|
||||
* is used to look up relevant information on the native side.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function sendInviteFailure(items: Array<*>, inviteScope: string) {
|
||||
return {
|
||||
type: SEND_INVITE_FAILURE,
|
||||
items,
|
||||
inviteScope
|
||||
};
|
||||
}
|
||||
5
react/features/mobile/invite-search/index.js
Normal file
5
react/features/mobile/invite-search/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
|
||||
import './reducer';
|
||||
import './middleware';
|
||||
233
react/features/mobile/invite-search/middleware.js
Normal file
233
react/features/mobile/invite-search/middleware.js
Normal file
@@ -0,0 +1,233 @@
|
||||
/* @flow */
|
||||
|
||||
import i18next from 'i18next';
|
||||
import { NativeModules, NativeEventEmitter } from 'react-native';
|
||||
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
|
||||
import { getInviteURL } from '../../base/connection';
|
||||
import {
|
||||
getInviteResultsForQuery,
|
||||
isAddPeopleEnabled,
|
||||
isDialOutEnabled,
|
||||
sendInvitesForItems
|
||||
} from '../../invite';
|
||||
import { inviteVideoRooms } from '../../videosipgw';
|
||||
|
||||
import { sendInviteSuccess, sendInviteFailure } from './actions';
|
||||
import {
|
||||
_SET_INVITE_SEARCH_SUBSCRIPTIONS,
|
||||
LAUNCH_NATIVE_INVITE,
|
||||
SEND_INVITE_SUCCESS,
|
||||
SEND_INVITE_FAILURE
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Middleware that captures Redux actions and uses the InviteSearch module to
|
||||
* turn them into native events so the application knows about them.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case APP_WILL_MOUNT:
|
||||
return _appWillMount(store, next, action);
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
store.dispatch({
|
||||
type: _SET_INVITE_SEARCH_SUBSCRIPTIONS,
|
||||
subscriptions: undefined
|
||||
});
|
||||
break;
|
||||
|
||||
case LAUNCH_NATIVE_INVITE:
|
||||
launchNativeInvite(store);
|
||||
break;
|
||||
|
||||
case SEND_INVITE_SUCCESS:
|
||||
onSendInviteSuccess(action);
|
||||
break;
|
||||
|
||||
case SEND_INVITE_FAILURE:
|
||||
onSendInviteFailure(action);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies the feature jwt that the action {@link APP_WILL_MOUNT} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
|
||||
* being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*}
|
||||
*/
|
||||
function _appWillMount({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const emitter = new NativeEventEmitter(NativeModules.InviteSearch);
|
||||
|
||||
const context = {
|
||||
dispatch,
|
||||
getState
|
||||
};
|
||||
const subscriptions = [
|
||||
emitter.addListener(
|
||||
'performQueryAction',
|
||||
_onPerformQueryAction,
|
||||
context),
|
||||
emitter.addListener(
|
||||
'performSubmitInviteAction',
|
||||
_onPerformSubmitInviteAction,
|
||||
context)
|
||||
];
|
||||
|
||||
dispatch({
|
||||
type: _SET_INVITE_SEARCH_SUBSCRIPTIONS,
|
||||
subscriptions
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the native counterpart of InviteSearch to launch a native.
|
||||
* invite search.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function launchNativeInvite(store: { getState: Function }) {
|
||||
// The JavaScript App needs to provide uniquely identifying information
|
||||
// to the native module so that the latter may match the former
|
||||
// to the native JitsiMeetView which hosts it.
|
||||
const { app } = store.getState()['features/app'];
|
||||
|
||||
if (app) {
|
||||
const { externalAPIScope } = app.props;
|
||||
|
||||
if (externalAPIScope) {
|
||||
NativeModules.InviteSearch.launchNativeInvite(externalAPIScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a notification to the native counterpart of InviteSearch that all
|
||||
* invites were sent successfully.
|
||||
*
|
||||
* @param {Object} action - The redux action {@code SEND_INVITE_SUCCESS} which
|
||||
* is being dispatched.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onSendInviteSuccess({ inviteScope }) {
|
||||
NativeModules.InviteSearch.inviteSucceeded(inviteScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a notification to the native counterpart of InviteSearch that some
|
||||
* invite items failed to send successfully.
|
||||
*
|
||||
* @param {Object} action - The redux action {@code SEND_INVITE_FAILURE} which
|
||||
* is being dispatched.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onSendInviteFailure({ items, inviteScope }) {
|
||||
NativeModules.InviteSearch.inviteFailedForItems(items, inviteScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles InviteSearch's event {@code performQueryAction}.
|
||||
*
|
||||
* @param {Object} event - The details of the InviteSearch event
|
||||
* {@code performQueryAction}.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onPerformQueryAction({ query, inviteScope }) {
|
||||
const { getState } = this; // eslint-disable-line no-invalid-this
|
||||
|
||||
const state = getState();
|
||||
|
||||
const {
|
||||
dialOutAuthUrl,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl
|
||||
} = state['features/base/config'];
|
||||
|
||||
const options = {
|
||||
dialOutAuthUrl,
|
||||
enableAddPeople: isAddPeopleEnabled(state),
|
||||
enableDialOut: isDialOutEnabled(state),
|
||||
jwt: state['features/base/jwt'].jwt,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl
|
||||
};
|
||||
|
||||
getInviteResultsForQuery(query, options)
|
||||
.catch(() => [])
|
||||
.then(results => {
|
||||
const translatedResults = results.map(result => {
|
||||
if (result.type === 'phone') {
|
||||
result.title = i18next.t('addPeople.telephone', {
|
||||
number: result.number
|
||||
});
|
||||
|
||||
if (result.showCountryCodeReminder) {
|
||||
result.subtitle = i18next.t(
|
||||
'addPeople.countryReminder'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}).filter(result => result.type !== 'phone' || result.allowed);
|
||||
|
||||
NativeModules.InviteSearch.receivedResults(
|
||||
translatedResults,
|
||||
query,
|
||||
inviteScope);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles InviteSearch's event {@code performSubmitInviteAction}.
|
||||
*
|
||||
* @param {Object} event - The details of the InviteSearch event.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onPerformSubmitInviteAction({ selectedItems, inviteScope }) {
|
||||
const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
|
||||
const state = getState();
|
||||
const { conference } = state['features/base/conference'];
|
||||
const {
|
||||
inviteServiceUrl
|
||||
} = state['features/base/config'];
|
||||
const options = {
|
||||
conference,
|
||||
inviteServiceUrl,
|
||||
inviteUrl: getInviteURL(state),
|
||||
inviteVideoRooms,
|
||||
jwt: state['features/base/jwt'].jwt
|
||||
};
|
||||
|
||||
sendInvitesForItems(selectedItems, options)
|
||||
.then(invitesLeftToSend => {
|
||||
if (invitesLeftToSend.length) {
|
||||
dispatch(sendInviteFailure(invitesLeftToSend, inviteScope));
|
||||
} else {
|
||||
dispatch(sendInviteSuccess(inviteScope));
|
||||
}
|
||||
});
|
||||
}
|
||||
14
react/features/mobile/invite-search/reducer.js
Normal file
14
react/features/mobile/invite-search/reducer.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { assign, ReducerRegistry } from '../../base/redux';
|
||||
|
||||
import { _SET_INVITE_SEARCH_SUBSCRIPTIONS } from './actionTypes';
|
||||
|
||||
ReducerRegistry.register(
|
||||
'features/invite-search',
|
||||
(state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case _SET_INVITE_SEARCH_SUBSCRIPTIONS:
|
||||
return assign(state, 'subscriptions', action.subscriptions);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
Reference in New Issue
Block a user