diff --git a/react/features/share-room/actionTypes.js b/react/features/share-room/actionTypes.js new file mode 100644 index 000000000..83151550a --- /dev/null +++ b/react/features/share-room/actionTypes.js @@ -0,0 +1,24 @@ +import { Symbol } from '../base/react'; + +/** + * The type of (redux) action which begins the UI procedure to share the current + * conference/room URL. + * + * { + * type: BEGIN_SHARE_ROOM, + * roomURL: string + * } + */ +export const BEGIN_SHARE_ROOM = Symbol('BEGIN_SHARE_ROOM'); + +/** + * The type of (redux) action which ends the UI procedure to share a specific + * conference/room URL. + * + * { + * type: END_SHARE_ROOM, + * roomURL: string, + * shared: boolean + * } + */ +export const END_SHARE_ROOM = Symbol('END_SHARE_ROOM'); diff --git a/react/features/share-room/actions.js b/react/features/share-room/actions.js new file mode 100644 index 000000000..bee3f2362 --- /dev/null +++ b/react/features/share-room/actions.js @@ -0,0 +1,65 @@ +/* @flow */ + +import { BEGIN_SHARE_ROOM, END_SHARE_ROOM } from './actionTypes'; + +/** + * Begins the UI procedure to share the URL for the current conference/room. + * + * @param {string} roomURL - The URL of the room to share. + * @public + * @returns {Function} + */ +export function beginShareRoom(roomURL: ?string): Function { + return (dispatch, getState) => { + if (!roomURL) { + const { conference, room } = getState()['features/base/conference']; + + // eslint-disable-next-line no-param-reassign + roomURL = _getRoomURL(conference, room); + } + + if (roomURL) { + dispatch({ + type: BEGIN_SHARE_ROOM, + roomURL + }); + } + }; +} + +/** + * Ends the UI procedure to share a specific conference/room URL. + * + * @param {string} roomURL - The URL of the conference/room which was shared. + * @param {boolean} shared - True if the URL was shared successfully; false, + * otherwise. + * @public + * @returns {{ + * type: END_SHARE_ROOM, + * roomURL: string, + * shared: boolean + * }} + */ +export function endShareRoom(roomURL: string, shared: boolean): Object { + return { + type: END_SHARE_ROOM, + roomURL, + shared + }; +} + +/** + * Gets the public URL of a conference/room. + * + * @param {JitsiConference} conference - The JitsiConference to share the URL + * of. + * @param {string} room - The name of the room to share the URL of. + * @private + * @returns {string|null} + */ +function _getRoomURL(conference, room) { + return ( + conference + && room + && `https://${conference.connection.options.hosts.domain}/${room}`); +} diff --git a/react/features/share-room/index.js b/react/features/share-room/index.js new file mode 100644 index 000000000..e72b2584a --- /dev/null +++ b/react/features/share-room/index.js @@ -0,0 +1,4 @@ +export * from './actions'; +export * from './actionTypes'; + +import './middleware'; diff --git a/react/features/share-room/middleware.js b/react/features/share-room/middleware.js new file mode 100644 index 000000000..d18321741 --- /dev/null +++ b/react/features/share-room/middleware.js @@ -0,0 +1,63 @@ +/* @flow */ + +import { Share } from 'react-native'; + +import { MiddlewareRegistry } from '../base/redux'; + +import { endShareRoom } from './actions'; +import { BEGIN_SHARE_ROOM } from './actionTypes'; + +/** + * Middleware that captures room URL sharing actions and starts the sharing + * process. + * + * @param {Store} store - Redux store. + * @returns {Function} + */ +MiddlewareRegistry.register(store => next => action => { + switch (action.type) { + case BEGIN_SHARE_ROOM: + _shareRoom(action.roomURL, store.dispatch); + break; + } + + return next(action); +}); + +/** + * Open the native sheet for sharing a specific conference/room URL. + * + * @param {string} roomURL - The URL of the conference/room to be shared. + * @param {Dispatch} dispatch - The Redux dispatch function. + * @private + * @returns {void} + */ +function _shareRoom(roomURL: string, dispatch: Function) { + // TODO The following display/human-readable strings were submitted for + // review before i18n was introduces in react/. However, I reviewed it + // afterwards. Translate the display/human-readable strings. + const message = `Click the following link to join the meeting: ${roomURL}`; + const title = 'Jitsi Meet Conference'; + const onFulfilled + = (shared: boolean) => dispatch(endShareRoom(roomURL, shared)); + + Share.share( + /* content */ { + message, + title + }, + /* options */ { + dialogTitle: title, // Android + subject: title // iOS + }) + .then( + /* onFulfilled */ value => { + onFulfilled(value.action === Share.sharedAction); + }, + /* onRejected */ reason => { + console.error( + `Failed to share conference/room URL ${roomURL}:`, + reason); + onFulfilled(false); + }); +} diff --git a/react/features/toolbox/components/Toolbox.native.js b/react/features/toolbox/components/Toolbox.native.js index 2d51af8ff..316173695 100644 --- a/react/features/toolbox/components/Toolbox.native.js +++ b/react/features/toolbox/components/Toolbox.native.js @@ -7,6 +7,7 @@ import { MEDIA_TYPE, toggleCameraFacingMode } from '../../base/media'; import { Container } from '../../base/react'; import { ColorPalette } from '../../base/styles'; import { beginRoomLockRequest } from '../../room-lock'; +import { beginShareRoom } from '../../share-room'; import { abstractMapDispatchToProps, @@ -45,6 +46,11 @@ class Toolbox extends Component { */ _onRoomLock: React.PropTypes.func, + /** + * Begins the UI procedure to share the conference/room URL. + */ + _onShareRoom: React.PropTypes.func, + /** * Handler for toggle audio. */ @@ -211,6 +217,12 @@ class Toolbox extends Component { onClick = { this.props._onToggleAudioOnly } style = { style } underlayColor = { underlayColor } /> + ); @@ -257,6 +269,17 @@ function _mapDispatchToProps(dispatch) { return dispatch(beginRoomLockRequest()); }, + /** + * Begins the UI procedure to share the conference/room URL. + * + * @private + * @returns {void} Dispatched action. + * @type {Function} + */ + _onShareRoom() { + return dispatch(beginShareRoom()); + }, + /** * Toggles the audio-only flag of the conference. *