2017-10-24 08:40:39 +00:00
|
|
|
// @flow
|
|
|
|
|
|
|
|
import _ from 'lodash';
|
|
|
|
import React, { Component } from 'react';
|
2018-05-07 20:55:17 +00:00
|
|
|
import { NativeModules, Text, TouchableHighlight, View } from 'react-native';
|
2017-10-24 08:40:39 +00:00
|
|
|
|
2019-05-22 09:25:22 +00:00
|
|
|
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
2018-05-07 20:55:17 +00:00
|
|
|
import { hideDialog, BottomSheet } from '../../../base/dialog';
|
2017-11-20 11:13:42 +00:00
|
|
|
import { translate } from '../../../base/i18n';
|
2018-05-07 20:55:17 +00:00
|
|
|
import { Icon } from '../../../base/font-icons';
|
2019-03-21 16:38:29 +00:00
|
|
|
import { connect } from '../../../base/redux';
|
2019-05-22 09:25:22 +00:00
|
|
|
import { ColorPalette, type StyleType } from '../../../base/styles';
|
2018-05-07 20:55:17 +00:00
|
|
|
|
2018-12-18 11:18:54 +00:00
|
|
|
import styles from './styles';
|
2017-10-24 08:40:39 +00:00
|
|
|
|
2017-11-14 20:18:16 +00:00
|
|
|
/**
|
2018-05-07 20:55:17 +00:00
|
|
|
* Type definition for a single entry in the device list.
|
|
|
|
*/
|
|
|
|
type Device = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Name of the icon which will be rendered on the right.
|
|
|
|
*/
|
|
|
|
iconName: string,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* True if the element is selected (will be highlighted in blue),
|
|
|
|
* false otherwise.
|
|
|
|
*/
|
|
|
|
selected: boolean,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Text which will be rendered in the row.
|
|
|
|
*/
|
|
|
|
text: string,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Device type.
|
|
|
|
*/
|
|
|
|
type: string
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@code AudioRoutePickerDialog}'s React {@code Component} prop types.
|
2017-11-14 20:18:16 +00:00
|
|
|
*/
|
|
|
|
type Props = {
|
|
|
|
|
2019-05-22 09:25:22 +00:00
|
|
|
/**
|
|
|
|
* Style of the bottom sheet feature.
|
|
|
|
*/
|
|
|
|
_bottomSheetStyles: StyleType,
|
|
|
|
|
2017-11-14 20:18:16 +00:00
|
|
|
/**
|
|
|
|
* Used for hiding the dialog when the selection was completed.
|
|
|
|
*/
|
2017-11-20 11:13:42 +00:00
|
|
|
dispatch: Function,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Invoked to obtain translated strings.
|
|
|
|
*/
|
|
|
|
t: Function
|
2017-11-14 20:18:16 +00:00
|
|
|
};
|
|
|
|
|
2018-05-07 20:55:17 +00:00
|
|
|
/**
|
|
|
|
* {@code AudioRoutePickerDialog}'s React {@code Component} state types.
|
|
|
|
*/
|
2017-11-14 20:18:16 +00:00
|
|
|
type State = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of available devices.
|
|
|
|
*/
|
2018-05-07 20:55:17 +00:00
|
|
|
devices: Array<Device>
|
2017-11-14 20:18:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const { AudioMode } = NativeModules;
|
2017-10-24 08:40:39 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Maps each device type to a display name and icon.
|
|
|
|
*/
|
|
|
|
const deviceInfoMap = {
|
|
|
|
BLUETOOTH: {
|
|
|
|
iconName: 'bluetooth',
|
2017-11-20 11:13:42 +00:00
|
|
|
text: 'audioDevices.bluetooth',
|
2017-10-24 08:40:39 +00:00
|
|
|
type: 'BLUETOOTH'
|
|
|
|
},
|
|
|
|
EARPIECE: {
|
|
|
|
iconName: 'phone-talk',
|
2017-11-20 11:13:42 +00:00
|
|
|
text: 'audioDevices.phone',
|
2017-10-24 08:40:39 +00:00
|
|
|
type: 'EARPIECE'
|
|
|
|
},
|
|
|
|
HEADPHONES: {
|
|
|
|
iconName: 'headset',
|
2017-11-20 11:13:42 +00:00
|
|
|
text: 'audioDevices.headphones',
|
2017-10-24 08:40:39 +00:00
|
|
|
type: 'HEADPHONES'
|
|
|
|
},
|
|
|
|
SPEAKER: {
|
|
|
|
iconName: 'volume',
|
2017-11-20 11:13:42 +00:00
|
|
|
text: 'audioDevices.speaker',
|
2017-10-24 08:40:39 +00:00
|
|
|
type: 'SPEAKER'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-11-14 20:18:16 +00:00
|
|
|
* The exported React {@code Component}. {@code AudioRoutePickerDialog} is
|
|
|
|
* exported only if the {@code AudioMode} module has the capability to get / set
|
2017-10-24 08:40:39 +00:00
|
|
|
* audio devices.
|
|
|
|
*/
|
2017-11-14 20:18:16 +00:00
|
|
|
let AudioRoutePickerDialog_;
|
2017-10-24 08:40:39 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements a React {@code Component} which prompts the user when a password
|
|
|
|
* is required to join a conference.
|
|
|
|
*/
|
|
|
|
class AudioRoutePickerDialog extends Component<Props, State> {
|
|
|
|
state = {
|
2017-11-14 20:18:16 +00:00
|
|
|
/**
|
|
|
|
* Available audio devices, it will be set in
|
2018-11-01 17:32:11 +00:00
|
|
|
* {@link #componentDidMount()}.
|
2017-11-14 20:18:16 +00:00
|
|
|
*/
|
2017-10-24 08:40:39 +00:00
|
|
|
devices: []
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes a new {@code PasswordRequiredPrompt} instance.
|
|
|
|
*
|
|
|
|
* @param {Props} props - The read-only React {@code Component} props with
|
|
|
|
* which the new instance is to be initialized.
|
|
|
|
*/
|
2018-05-07 20:55:17 +00:00
|
|
|
constructor(props: Props) {
|
2017-10-24 08:40:39 +00:00
|
|
|
super(props);
|
|
|
|
|
|
|
|
// Bind event handlers so they are only bound once per instance.
|
|
|
|
this._onCancel = this._onCancel.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-11-14 20:18:16 +00:00
|
|
|
* Initializes the device list by querying {@code AudioMode}.
|
2017-10-24 08:40:39 +00:00
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2018-11-01 17:32:11 +00:00
|
|
|
componentDidMount() {
|
2017-10-24 08:40:39 +00:00
|
|
|
AudioMode.getAudioDevices().then(({ devices, selected }) => {
|
|
|
|
const audioDevices = [];
|
|
|
|
|
|
|
|
if (devices) {
|
|
|
|
for (const device of devices) {
|
2017-11-20 11:13:42 +00:00
|
|
|
if (deviceInfoMap[device]) {
|
|
|
|
const info = Object.assign({}, deviceInfoMap[device]);
|
2017-10-24 08:40:39 +00:00
|
|
|
|
|
|
|
info.selected = device === selected;
|
2017-11-20 11:13:42 +00:00
|
|
|
info.text = this.props.t(info.text);
|
2017-10-24 08:40:39 +00:00
|
|
|
audioDevices.push(info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (audioDevices) {
|
2017-11-14 20:18:16 +00:00
|
|
|
// Make sure devices is alphabetically sorted.
|
|
|
|
this.setState({
|
|
|
|
devices: _.sortBy(audioDevices, 'text')
|
|
|
|
});
|
2017-10-24 08:40:39 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dispatches a redux action to hide this sheet.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_hide() {
|
2017-11-14 20:18:16 +00:00
|
|
|
this.props.dispatch(hideDialog(AudioRoutePickerDialog_));
|
2017-10-24 08:40:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_onCancel: () => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cancels the dialog by hiding it.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_onCancel() {
|
|
|
|
this._hide();
|
|
|
|
}
|
|
|
|
|
2018-05-07 20:55:17 +00:00
|
|
|
_onSelectDeviceFn: (Device) => Function;
|
2017-10-24 08:40:39 +00:00
|
|
|
|
|
|
|
/**
|
2018-05-07 20:55:17 +00:00
|
|
|
* Builds and returns a function which handles the selection of a device
|
|
|
|
* on the sheet. The selected device will be used by {@code AudioMode}.
|
2017-10-24 08:40:39 +00:00
|
|
|
*
|
2018-05-07 20:55:17 +00:00
|
|
|
* @param {Device} device - Object representing the selected device.
|
2017-10-24 08:40:39 +00:00
|
|
|
* @private
|
2018-05-07 20:55:17 +00:00
|
|
|
* @returns {Function}
|
2017-10-24 08:40:39 +00:00
|
|
|
*/
|
2018-05-07 20:55:17 +00:00
|
|
|
_onSelectDeviceFn(device: Device) {
|
|
|
|
return () => {
|
|
|
|
this._hide();
|
|
|
|
AudioMode.setAudioDevice(device.type);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Renders a single device.
|
|
|
|
*
|
|
|
|
* @param {Device} device - Object representing a single device.
|
|
|
|
* @private
|
|
|
|
* @returns {ReactElement}
|
|
|
|
*/
|
|
|
|
_renderDevice(device: Device) {
|
2019-05-22 09:25:22 +00:00
|
|
|
const { _bottomSheetStyles } = this.props;
|
2018-05-07 20:55:17 +00:00
|
|
|
const { iconName, selected, text } = device;
|
|
|
|
const selectedStyle = selected ? styles.selectedText : {};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<TouchableHighlight
|
|
|
|
key = { device.type }
|
|
|
|
onPress = { this._onSelectDeviceFn(device) }
|
2018-12-18 11:18:54 +00:00
|
|
|
underlayColor = { ColorPalette.overflowMenuItemUnderlay } >
|
2018-05-07 20:55:17 +00:00
|
|
|
<View style = { styles.deviceRow } >
|
|
|
|
<Icon
|
|
|
|
name = { iconName }
|
2019-05-22 09:25:22 +00:00
|
|
|
style = { [ styles.deviceIcon, _bottomSheetStyles.iconStyle, selectedStyle ] } />
|
|
|
|
<Text style = { [ styles.deviceText, _bottomSheetStyles.labelStyle, selectedStyle ] } >
|
2018-05-07 20:55:17 +00:00
|
|
|
{ text }
|
|
|
|
</Text>
|
|
|
|
</View>
|
|
|
|
</TouchableHighlight>
|
|
|
|
);
|
2017-10-24 08:40:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements React's {@link Component#render()}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
* @returns {ReactElement}
|
|
|
|
*/
|
|
|
|
render() {
|
2017-11-14 20:18:16 +00:00
|
|
|
const { devices } = this.state;
|
|
|
|
|
|
|
|
if (!devices.length) {
|
2017-10-24 08:40:39 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2018-05-07 20:55:17 +00:00
|
|
|
<BottomSheet onCancel = { this._onCancel }>
|
|
|
|
{ this.state.devices.map(this._renderDevice, this) }
|
|
|
|
</BottomSheet>
|
2017-10-24 08:40:39 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-22 09:25:22 +00:00
|
|
|
/**
|
|
|
|
* Maps part of the Redux state to the props of this component.
|
|
|
|
*
|
|
|
|
* @param {Object} state - The Redux state.
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
|
|
|
function _mapStateToProps(state) {
|
|
|
|
return {
|
|
|
|
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet')
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-10-24 08:40:39 +00:00
|
|
|
// Only export the dialog if we have support for getting / setting audio devices
|
|
|
|
// in AudioMode.
|
|
|
|
if (AudioMode.getAudioDevices && AudioMode.setAudioDevice) {
|
2019-05-22 09:25:22 +00:00
|
|
|
AudioRoutePickerDialog_ = translate(connect(_mapStateToProps)(AudioRoutePickerDialog));
|
2017-10-24 08:40:39 +00:00
|
|
|
}
|
|
|
|
|
2017-11-14 20:18:16 +00:00
|
|
|
export default AudioRoutePickerDialog_;
|