MM-42821 - Calls mobile: Default calls doesn't work on mobile (#6107) (#6120)

* use call config when checking for Enable/Disable and StartCall permissions

* store config, refresh after 20 minutes

* load config on startup

* batch load calls and load config on startup/reconnect

(cherry picked from commit 10479d1d35)

Co-authored-by: Christopher Poile <cpoile@gmail.com>
This commit is contained in:
Mattermost Build 2022-04-01 16:29:51 +02:00 committed by GitHub
parent b13f07a66a
commit e16e89bb00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 366 additions and 814 deletions

View file

@ -24,7 +24,7 @@ import {TeamMembership} from '@mm-redux/types/teams';
import {WebSocketMessage} from '@mm-redux/types/websocket';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {removeUserFromList} from '@mm-redux/utils/user_utils';
import {loadCalls} from '@mmproducts/calls/store/actions/calls';
import {batchLoadCalls} from '@mmproducts/calls/store/actions/calls';
import {
handleCallStarted,
handleCallUserConnected,
@ -171,7 +171,7 @@ export function doReconnect(now: number) {
if (!me.error) {
if (isSupportedServerCalls) {
dispatch(loadCalls());
dispatch(batchLoadCalls(true));
}
const roles = [];

View file

@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const RefreshConfigMillis = 20 * 60 * 1000; // Refresh config after 20 minutes
const RequiredServer = {
FULL_VERSION: '6.3.0',
MAJOR_VERSION: 6,
@ -8,4 +10,4 @@ const RequiredServer = {
PATCH_VERSION: 0,
};
export default {RequiredServer};
export default {RequiredServer, RefreshConfigMillis};

View file

@ -1,11 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ServerChannelState} from '@mmproducts/calls/store/types/calls';
import type {ServerChannelState, ServerConfig} from '@mmproducts/calls/store/types/calls';
export interface ClientCallsMix {
getEnabled: () => Promise<Boolean>;
getCalls: () => Promise<ServerChannelState[]>;
getCallsConfig: () => Promise<ServerConfig>;
enableChannelCalls: (channelId: string) => Promise<ServerChannelState>;
disableChannelCalls: (channelId: string) => Promise<ServerChannelState>;
}
@ -34,7 +35,7 @@ const ClientCalls = (superclass: any) => class extends superclass {
return this.doFetch(
`${this.getCallsRoute()}/config`,
{method: 'get'},
);
) as ServerConfig;
};
enableChannelCalls = async (channelId: string) => {

View file

@ -1,149 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EnableDisableCalls should match snapshot if calls are disabled 1`] = `
<React.Fragment>
<Separator
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Enable Calls"
icon="phone-outline"
rightArrow={false}
shouldRender={true}
testID="test-id"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
togglable={false}
/>
</React.Fragment>
`;
exports[`EnableDisableCalls should match snapshot if calls are enabled 1`] = `
<React.Fragment>
<Separator
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Disable Calls"
icon="phone-outline"
rightArrow={false}
shouldRender={true}
testID="test-id"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
togglable={false}
/>
</React.Fragment>
`;

View file

@ -0,0 +1,39 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Theme} from '@mm-redux/types/theme';
import EnableDisableCalls from '@mmproducts/calls/components/channel_info/enable_disable_calls';
import StartCall from '@mmproducts/calls/components/channel_info/start_call';
import {useCallsChannelSettings} from '@mmproducts/calls/hooks';
type Props = {
theme: Theme;
joinCall: (channelId: string) => void;
}
const CallsChannelInfo = ({theme, joinCall}: Props) => {
const [enabled, canEnableDisable] = useCallsChannelSettings();
return (
<>
{enabled &&
<StartCall
testID='channel_info.start_call.action'
theme={theme}
joinCall={joinCall}
/>
}
{canEnableDisable &&
<EnableDisableCalls
testID='channel_info.start_call.action'
theme={theme}
enabled={enabled}
/>
}
</>
);
};
export default CallsChannelInfo;

View file

@ -2,30 +2,35 @@
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {useSelector} from 'react-redux';
import {getCurrentChannel} from '@mm-redux/selectors/entities/channels';
import {Theme} from '@mm-redux/types/theme';
import {useTryCallsFunction} from '@mmproducts/calls/hooks';
import {disableChannelCalls, enableChannelCalls} from '@mmproducts/calls/store/actions/calls';
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
import Separator from '@screens/channel_info/separator';
import {preventDoubleTap} from '@utils/tap';
type Props = {
testID?: string;
testID: string;
theme: Theme;
onPress: (channelId: string) => void;
canEnableDisableCalls: boolean;
enabled: boolean;
}
const EnableDisableCalls = (props: Props) => {
const {testID, canEnableDisableCalls, theme, onPress, enabled} = props;
const EnableDisableCalls = ({testID, theme, enabled}: Props) => {
const currentChannel = useSelector(getCurrentChannel);
const [tryOnPress, msgPostfix] = useTryCallsFunction(onPress);
const handleEnableDisableCalls = useCallback(preventDoubleTap(tryOnPress), [tryOnPress]);
const toggleCalls = useCallback(() => {
if (enabled) {
disableChannelCalls(currentChannel.id);
} else {
enableChannelCalls(currentChannel.id);
}
}, [enabled, currentChannel.id]);
if (!canEnableDisableCalls) {
return null;
}
const [tryOnPress, msgPostfix] = useTryCallsFunction(toggleCalls);
const handleEnableDisableCalls = preventDoubleTap(tryOnPress);
return (
<>

View file

@ -3,43 +3,42 @@
import React, {useCallback} from 'react';
import {injectIntl, IntlShape} from 'react-intl';
import {useSelector} from 'react-redux';
import {getChannel, getCurrentChannel} from '@mm-redux/selectors/entities/channels';
import {GlobalState} from '@mm-redux/types/store';
import {Theme} from '@mm-redux/types/theme';
import leaveAndJoinWithAlert from '@mmproducts/calls/components/leave_and_join_alert';
import {useTryCallsFunction} from '@mmproducts/calls/hooks';
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
import Separator from '@screens/channel_info/separator';
import {preventDoubleTap} from '@utils/tap';
type Props = {
actions: {
joinCall: (channelId: string) => any;
};
testID?: string;
testID: string;
theme: Theme;
currentChannelId: string;
currentChannelName: string;
callChannelName: string;
confirmToJoin: boolean;
alreadyInTheCall: boolean;
canStartCall: boolean;
ongoingCall: boolean;
intl: typeof IntlShape;
joinCall: (channelId: string) => void;
}
const StartCall = (props: Props) => {
const {testID, canStartCall, theme, actions, currentChannelId, callChannelName, currentChannelName, confirmToJoin, alreadyInTheCall, ongoingCall, intl} = props;
const StartCall = ({testID, theme, intl, joinCall}: Props) => {
const currentChannel = useSelector(getCurrentChannel);
const call = useSelector(getCalls)[currentChannel.id];
const currentCall = useSelector(getCurrentCall);
const currentCallChannelId = currentCall?.channelId || '';
const callChannelName = useSelector((state: GlobalState) => getChannel(state, currentCallChannelId)?.display_name) || '';
const confirmToJoin = Boolean(currentCall && currentCall.channelId !== currentChannel.id);
const alreadyInTheCall = Boolean(currentCall && call && currentCall.channelId === call.channelId);
const ongoingCall = Boolean(call);
const leaveAndJoin = useCallback(() => {
leaveAndJoinWithAlert(intl, currentChannelId, callChannelName, currentChannelName, confirmToJoin, actions.joinCall);
}, [intl, currentChannelId, callChannelName, currentChannelName, confirmToJoin, actions.joinCall]);
leaveAndJoinWithAlert(intl, currentChannel.id, callChannelName, currentChannel.display_name, confirmToJoin, joinCall);
}, [intl, currentChannel.id, callChannelName, currentChannel.display_name, confirmToJoin, joinCall]);
const [tryLeaveAndJoin, msgPostfix] = useTryCallsFunction(leaveAndJoin);
const handleStartCall = useCallback(preventDoubleTap(tryLeaveAndJoin), [tryLeaveAndJoin]);
if (!canStartCall) {
return null;
}
if (alreadyInTheCall) {
return null;
}

View file

@ -1,39 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import Preferences from '@mm-redux/constants/preferences';
import EnableDisableCalls from './enable_disable_calls';
describe('EnableDisableCalls', () => {
const baseProps = {
testID: 'test-id',
theme: Preferences.THEMES.denim,
onPress: jest.fn(),
canEnableDisableCalls: true,
enabled: false,
};
test('should match snapshot if calls are disabled', () => {
const wrapper = shallow(<EnableDisableCalls {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should match snapshot if calls are enabled', () => {
const props = {...baseProps, enabled: true};
const wrapper = shallow(<EnableDisableCalls {...props}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should be null if you can not enable/disable calls', () => {
const props = {...baseProps, canEnableDisableCalls: false};
const wrapper = shallow(<EnableDisableCalls {...props}/>);
expect(wrapper.getElement()).toBeNull();
});
});

View file

@ -1,149 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StartCall should match snapshot 1`] = `
<React.Fragment>
<Separator
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Start Call"
icon="phone-in-talk"
rightArrow={false}
shouldRender={true}
testID="test-id"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
togglable={false}
/>
</React.Fragment>
`;
exports[`StartCall should match snapshot when there is already an ongoing call in the channel 1`] = `
<React.Fragment>
<Separator
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Join Ongoing Call"
icon="phone-in-talk"
rightArrow={false}
shouldRender={true}
testID="test-id"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
togglable={false}
/>
</React.Fragment>
`;

View file

@ -1,34 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators, Dispatch} from 'redux';
import {getChannel, getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
import {joinCall} from '@mmproducts/calls/store/actions/calls';
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
import StartCall from './start_call';
import type {GlobalState} from '@mm-redux/types/store';
function mapStateToProps(state: GlobalState) {
const currentChannelId = getCurrentChannelId(state);
const call = getCalls(state)[currentChannelId];
const currentCall = getCurrentCall(state);
return {
confirmToJoin: Boolean(currentCall && currentCall.channelId !== currentChannelId),
alreadyInTheCall: Boolean(currentCall && call && currentCall.channelId === call.channelId),
callChannelName: currentCall ? getChannel(state, currentCall.channelId)?.display_name : '',
ongoingCall: Boolean(call),
};
}
function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators({
joinCall,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(StartCall);

View file

@ -1,134 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import nock from 'nock';
import React from 'react';
import {Alert, TouchableHighlight} from 'react-native';
import {Client4} from '@client/rest';
import Preferences from '@mm-redux/constants/preferences';
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
import {shallowWithIntl} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';
import StartCall from './start_call';
describe('StartCall', () => {
beforeAll(async () => {
await TestHelper.initBasic(Client4);
});
afterAll(async () => {
await TestHelper.tearDown();
});
const baseProps = {
actions: {
joinCall: jest.fn(),
},
testID: 'test-id',
theme: Preferences.THEMES.denim,
currentChannelId: 'channel-id',
currentChannelName: 'Channel Name',
canStartCall: true,
callChannelName: 'Call channel name',
confirmToJoin: false,
alreadyInTheCall: false,
ongoingCall: false,
};
test('should match snapshot', () => {
const wrapper = shallowWithIntl(<StartCall {...baseProps}/>).dive();
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should match snapshot when there is already an ongoing call in the channel', () => {
const props = {...baseProps, ongoingCall: true};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should be null when you are already in the channel call', () => {
const props = {...baseProps, alreadyInTheCall: true};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
expect(wrapper.getElement()).toBeNull();
});
test('should be null if you can not start a call', () => {
const props = {...baseProps, canStartCall: false};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
expect(wrapper.getElement()).toBeNull();
});
test('should start on click when calls is enabled', async () => {
nock(Client4.getCallsRoute()).
get('/version').
times(2).
reply(200, {version: 1, build: 2});
const joinCall = jest.fn();
const props = {...baseProps, actions: {joinCall}};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
wrapper.find(ChannelInfoRow).dive().find(TouchableHighlight).simulate('press');
// This is so that the awaited call within ClientCalls in products/calls/client/rest.ts has
// a chance to be completed by nock:
await Client4.doFetch(
`${Client4.getUrl()}/plugins/com.mattermost.calls/version`,
{method: 'get'},
);
expect(Alert.alert).not.toHaveBeenCalled();
expect(props.actions.joinCall).toHaveBeenCalled();
});
test('should not start on click and should show alert when calls is not enabled', async () => {
nock(Client4.getCallsRoute()).
get('/version').
times(2).
reply(404);
const joinCall = jest.fn();
const props = {...baseProps, actions: {joinCall}};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
wrapper.find(ChannelInfoRow).dive().find(TouchableHighlight).simulate('press');
// This is so that the awaited call within ClientCalls in products/calls/client/rest.ts has
// a chance to be completed by nock:
try {
await Client4.doFetch(
`${Client4.getUrl()}/plugins/com.mattermost.calls/version`,
{method: 'get'},
);
} catch (e) {
// expected
}
expect(Alert.alert).toHaveBeenCalled();
expect(props.actions.joinCall).not.toHaveBeenCalled();
});
test('should ask for confirmation on click', async () => {
nock(Client4.getCallsRoute()).
get('/version').
times(2).
reply(200, {version: 1, build: 2});
const joinCall = jest.fn();
const props = {...baseProps, confirmToJoin: true, actions: {joinCall}};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
wrapper.find(ChannelInfoRow).dive().find(TouchableHighlight).simulate('press');
// This is so that the awaited call within ClientCalls in products/calls/client/rest.ts has
// a chance to be completed by nock:
await Client4.doFetch(
`${Client4.getUrl()}/plugins/com.mattermost.calls/version`,
{method: 'get'},
);
expect(Alert.alert).toHaveBeenCalled();
expect(props.actions.joinCall).not.toHaveBeenCalled();
});
});

View file

@ -1,10 +1,17 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useState} from 'react';
import {useEffect, useState} from 'react';
import {Alert} from 'react-native';
import {useDispatch, useSelector} from 'react-redux';
import {Client4} from '@client/rest';
import {General} from '@mm-redux/constants';
import {getCurrentChannel} from '@mm-redux/selectors/entities/channels';
import {getCurrentUserRoles} from '@mm-redux/selectors/entities/users';
import {isAdmin as checkIsAdmin, isChannelAdmin as checkIsChannelAdmin} from '@mm-redux/utils/user_utils';
import {loadConfig} from '@mmproducts/calls/store/actions/calls';
import {getConfig, isCallsExplicitlyDisabled, isCallsExplicitlyEnabled} from '@mmproducts/calls/store/selectors/calls';
// Check if calls is enabled. If it is, then run fn; if it isn't, show an alert and set
// msgPostfix to ' (Not Available)'.
@ -33,3 +40,31 @@ export const useTryCallsFunction = (fn: (channelId: string) => void) => {
return [tryFn, msgPostfix];
};
export const useCallsChannelSettings = () => {
const dispatch = useDispatch();
const config = useSelector(getConfig);
const currentChannel = useSelector(getCurrentChannel);
const explicitlyDisabled = useSelector(isCallsExplicitlyDisabled);
const explicitlyEnabled = useSelector(isCallsExplicitlyEnabled);
const roles = useSelector(getCurrentUserRoles);
useEffect(() => {
dispatch(loadConfig());
}, []);
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
const isGroupMessage = currentChannel.type === General.GM_CHANNEL;
const isAdmin = checkIsAdmin(roles);
const isChannelAdmin = isAdmin || checkIsChannelAdmin(roles);
const enabled = (explicitlyEnabled || (!explicitlyDisabled && config.DefaultEnabled));
let canEnableDisable;
if (config.AllowEnableCalls) {
canEnableDisable = isDirectMessage || isGroupMessage || isChannelAdmin;
} else {
canEnableDisable = isAdmin;
}
return [enabled, canEnableDisable];
};

View file

@ -23,4 +23,5 @@ export default keyMirror({
RECEIVED_UNRAISE_HAND: null,
SET_SCREENSHARE_URL: null,
SET_SPEAKERPHONE: null,
RECEIVED_CONFIG: null,
});

View file

@ -31,6 +31,12 @@ jest.mock('@client/rest', () => ({
enabled: true,
},
]),
getCallsConfig: jest.fn(() => ({
ICEServers: ['mattermost.com'],
AllowEnableCalls: true,
DefaultEnabled: true,
last_retrieved_at: 1234,
})),
enableChannelCalls: jest.fn(() => null),
disableChannelCalls: jest.fn(() => null),
},
@ -72,6 +78,7 @@ describe('Actions.Calls', () => {
newClient.mockClear();
Client4.setUrl.mockClear();
Client4.getCalls.mockClear();
Client4.getCallsConfig.mockClear();
Client4.enableChannelCalls.mockClear();
Client4.disableChannelCalls.mockClear();
store = await configureStore();
@ -123,12 +130,33 @@ describe('Actions.Calls', () => {
});
it('loadCalls', async () => {
await store.dispatch(CallsActions.loadCalls());
await store.dispatch(await store.dispatch(CallsActions.loadCalls()));
expect(Client4.getCalls).toBeCalledWith();
assert.equal(store.getState().entities.calls.calls['channel-1'].channelId, 'channel-1');
assert.equal(store.getState().entities.calls.enabled['channel-1'], true);
});
it('loadConfig', async () => {
await store.dispatch(await store.dispatch(CallsActions.loadConfig()));
expect(Client4.getCallsConfig).toBeCalledWith();
assert.equal(store.getState().entities.calls.config.DefaultEnabled, true);
assert.equal(store.getState().entities.calls.config.AllowEnableCalls, true);
});
it('batchLoadConfig', async () => {
await store.dispatch(CallsActions.batchLoadCalls());
expect(Client4.getCallsConfig).toBeCalledWith();
expect(Client4.getCalls).toBeCalledWith();
// For some reason the above await is not working. This helps us:
await store.dispatch(CallsActions.enableChannelCalls('channel-1'));
assert.equal(store.getState().entities.calls.config.DefaultEnabled, true);
assert.equal(store.getState().entities.calls.config.AllowEnableCalls, true);
assert.equal(store.getState().entities.calls.calls['channel-1'].channelId, 'channel-1');
assert.equal(store.getState().entities.calls.enabled['channel-1'], true);
});
it('enableChannelCalls', async () => {
assert.equal(store.getState().entities.calls.enabled['channel-1'], undefined);
await store.dispatch(CallsActions.enableChannelCalls('channel-1'));

View file

@ -4,26 +4,56 @@
import InCallManager from 'react-native-incall-manager';
import {Client4} from '@client/rest';
import Calls from '@constants/calls';
import {logError} from '@mm-redux/actions/errors';
import {forceLogoutIfNecessary} from '@mm-redux/actions/helpers';
import {GenericAction, ActionFunc, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
import {GenericAction, ActionFunc, DispatchFunc, GetStateFunc, batchActions} from '@mm-redux/types/actions';
import {Dictionary} from '@mm-redux/types/utilities';
import {newClient} from '@mmproducts/calls/connection';
import CallsTypes from '@mmproducts/calls/store/action_types/calls';
import type {Call, CallParticipant} from '@mmproducts/calls/store/types/calls';
import {getConfig} from '@mmproducts/calls/store/selectors/calls';
import {Call, CallParticipant, DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
export let ws: any = null;
export function loadConfig(force = false): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc): Promise<GenericAction> => {
if (!force) {
if ((Date.now() - getConfig(getState()).last_retrieved_at) < Calls.RefreshConfigMillis) {
return {} as GenericAction;
}
}
let data;
try {
data = await Client4.getCallsConfig();
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
// Reset the config to the default (off) since it looks like Calls is not enabled.
return {
type: CallsTypes.RECEIVED_CONFIG,
data: {...DefaultServerConfig, last_retrieved_at: Date.now()},
};
}
return {
type: CallsTypes.RECEIVED_CONFIG,
data: {...data, last_retrieved_at: Date.now()},
};
};
}
export function loadCalls(): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
return async (dispatch: DispatchFunc, getState: GetStateFunc): Promise<GenericAction> => {
let resp = [];
try {
resp = await Client4.getCalls();
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
return {} as GenericAction;
}
const callsResults: Dictionary<Call> = {};
@ -54,9 +84,18 @@ export function loadCalls(): ActionFunc {
enabled: enabledChannels,
};
dispatch({type: CallsTypes.RECEIVED_CALLS, data});
return {type: CallsTypes.RECEIVED_CALLS, data};
};
}
return {data};
export function batchLoadCalls(forceConfig = false): ActionFunc {
return async (dispatch: DispatchFunc) => {
const promises = [dispatch(loadConfig(forceConfig)), dispatch(loadCalls())];
Promise.all(promises).then((actions: Array<Awaited<GenericAction>>) => {
dispatch(batchActions(actions, 'BATCH_LOAD_CALLS'));
});
return {};
};
}

View file

@ -4,6 +4,7 @@
import assert from 'assert';
import CallsTypes from '@mmproducts/calls/store/action_types/calls';
import {DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
import callsReducer from './calls';
@ -390,3 +391,20 @@ describe('Reducers.calls.screenShareURL', () => {
assert.deepEqual(state.screenShareURL, 'new-url');
});
});
describe('Reducers.calls.config', () => {
it('RECEIVED_CONFIG', async () => {
const initialState = {config: DefaultServerConfig};
const testAction = {
type: CallsTypes.RECEIVED_CONFIG,
data: {
ICEServers: ['google.com'],
AllowEnableCalls: true,
DefaultEnabled: true,
last_retrieved_at: 123,
},
};
const state = callsReducer(initialState, testAction);
assert.deepEqual(state.config, testAction.data);
});
});

View file

@ -5,7 +5,7 @@ import {combineReducers} from 'redux';
import {GenericAction} from '@mm-redux/types/actions';
import {Dictionary} from '@mm-redux/types/utilities';
import CallsTypes from '@mmproducts/calls/store/action_types/calls';
import {Call} from '@mmproducts/calls/store/types/calls';
import {Call, DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
function calls(state: Dictionary<Call> = {}, action: GenericAction) {
switch (action.type) {
@ -147,6 +147,16 @@ function calls(state: Dictionary<Call> = {}, action: GenericAction) {
}
}
function config(state = DefaultServerConfig, action: GenericAction) {
switch (action.type) {
case CallsTypes.RECEIVED_CONFIG: {
return action.data;
}
default:
return state;
}
}
function joined(state = '', action: GenericAction) {
switch (action.type) {
case CallsTypes.RECEIVED_MYSELF_JOINED_CALL: {
@ -212,4 +222,5 @@ export default combineReducers({
joined,
screenShareURL,
speakerphoneOn,
config,
});

View file

@ -55,7 +55,7 @@ describe('Selectors.Calls', () => {
});
it('isCallsEnabled', () => {
assert.equal(Selectors.isCallsEnabled(testState), true);
assert.equal(Selectors.isCallsExplicitlyEnabled(testState), true);
let newState = {
...testState,
entities: {
@ -63,7 +63,7 @@ describe('Selectors.Calls', () => {
channels: {currentChannelId: 'channel-2'},
},
};
assert.equal(Selectors.isCallsEnabled(newState), false);
assert.equal(Selectors.isCallsExplicitlyEnabled(newState), false);
newState = {
...testState,
entities: {
@ -71,7 +71,7 @@ describe('Selectors.Calls', () => {
channels: {currentChannelId: 'not-valid-channel'},
},
};
assert.equal(Selectors.isCallsEnabled(newState), false);
assert.equal(Selectors.isCallsExplicitlyEnabled(newState), false);
});
it('getScreenShareURL', () => {

View file

@ -8,6 +8,10 @@ import {getServerVersion} from '@mm-redux/selectors/entities/general';
import {GlobalState} from '@mm-redux/types/store';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
export function getConfig(state: GlobalState) {
return state.entities.calls.config;
}
export function getCalls(state: GlobalState) {
return state.entities.calls.calls;
}
@ -20,8 +24,20 @@ export function getCurrentCall(state: GlobalState) {
return state.entities.calls.calls[currentCall];
}
export function isCallsEnabled(state: GlobalState) {
return Boolean(state.entities.calls.enabled[getCurrentChannelId(state)]);
// isCallsExplicitlyEnabled returns true if and only if calls has been explicitly enabled in the current channel
// Both this and isCallsExplicitlyDisabled can be false if the channel has never had a call in it.
export function isCallsExplicitlyEnabled(state: GlobalState) {
const currentChannelId = getCurrentChannelId(state);
const enabledDict = state.entities.calls.enabled;
return enabledDict.hasOwnProperty(currentChannelId) && enabledDict[currentChannelId];
}
// isCallsExplicitlyEnabled returns true if and only if calls has been explicitly disabled in the current channel
// Both this and isCallsExplicitlyEnabled can be false if the channel has never had a call in it.
export function isCallsExplicitlyDisabled(state: GlobalState) {
const currentChannelId = getCurrentChannelId(state);
const enabledDict = state.entities.calls.enabled;
return enabledDict.hasOwnProperty(currentChannelId) && !enabledDict[currentChannelId];
}
export function getScreenShareURL(state: GlobalState) {

View file

@ -10,6 +10,7 @@ export type CallsState = {
joined: string;
screenShareURL: string;
speakerphoneOn: boolean;
config: ServerConfig;
}
export type Call = {
@ -53,3 +54,17 @@ export type VoiceEventData = {
channelId: string;
userId: string;
}
export type ServerConfig = {
ICEServers: string[];
AllowEnableCalls: boolean;
DefaultEnabled: boolean;
last_retrieved_at: number;
}
export const DefaultServerConfig = {
ICEServers: [],
AllowEnableCalls: false,
DefaultEnabled: false,
last_retrieved_at: 0,
} as ServerConfig;

View file

@ -24,7 +24,7 @@ export default class ChannelBase extends PureComponent {
loadChannelsForTeam: PropTypes.func.isRequired,
selectDefaultTeam: PropTypes.func.isRequired,
selectInitialChannel: PropTypes.func.isRequired,
loadCalls: PropTypes.func.isRequired,
batchLoadCalls: PropTypes.func.isRequired,
}).isRequired,
componentId: PropTypes.string.isRequired,
currentChannelId: PropTypes.string,
@ -106,7 +106,7 @@ export default class ChannelBase extends PureComponent {
}
if (this.props.isSupportedServerCalls) {
this.props.actions.loadCalls();
this.props.actions.batchLoadCalls();
}
}

View file

@ -17,7 +17,7 @@ import {getCurrentTeam} from '@mm-redux/selectors/entities/teams';
import {getCurrentUserId, getCurrentUserRoles, shouldShowTermsOfService} from '@mm-redux/selectors/entities/users';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import {isSystemAdmin as checkIsSystemAdmin} from '@mm-redux/utils/user_utils';
import {loadCalls} from '@mmproducts/calls/store/actions/calls';
import {batchLoadCalls} from '@mmproducts/calls/store/actions/calls';
import {isSupportedServer as isSupportedServerForCalls} from '@mmproducts/calls/store/selectors/calls';
import {getViewingGlobalThreads} from '@selectors/threads';
@ -68,7 +68,7 @@ function mapDispatchToProps(dispatch) {
loadChannelsForTeam,
selectDefaultTeam,
selectInitialChannel,
loadCalls,
batchLoadCalls,
}, dispatch),
};
}

View file

@ -1117,79 +1117,38 @@ exports[`channelInfo should match snapshot on calls supported and calls disabled
}
}
/>
<React.Fragment>
<Connect(InjectIntl(StartCall))
canStartCall={false}
currentChannelId="1234"
currentChannelName="Channel Name"
joinCall={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
<CallsChannelInfo
joinCall={[Function]}
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
/>
<EnableDisableCalls
canEnableDisableCalls={true}
enabled={false}
onPress={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
</React.Fragment>
}
/>
<Connect(InjectIntl(Component))
theme={
Object {
@ -1804,79 +1763,38 @@ exports[`channelInfo should match snapshot on calls supported and calls enabled
}
}
/>
<React.Fragment>
<Connect(InjectIntl(StartCall))
canStartCall={true}
currentChannelId="1234"
currentChannelName="Channel Name"
joinCall={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
<CallsChannelInfo
joinCall={[Function]}
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
/>
<EnableDisableCalls
canEnableDisableCalls={true}
enabled={true}
onPress={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
</React.Fragment>
}
/>
<Connect(InjectIntl(Component))
theme={
Object {
@ -2491,79 +2409,38 @@ exports[`channelInfo should match snapshot on calls supported, user is not admin
}
}
/>
<React.Fragment>
<Connect(InjectIntl(StartCall))
canStartCall={false}
currentChannelId="1234"
currentChannelName="Channel Name"
joinCall={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
<CallsChannelInfo
joinCall={[Function]}
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
/>
<EnableDisableCalls
canEnableDisableCalls={false}
enabled={false}
onPress={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
</React.Fragment>
}
/>
<Connect(InjectIntl(Component))
theme={
Object {

View file

@ -13,8 +13,7 @@ import {SafeAreaView} from 'react-native-safe-area-context';
import {dismissModal} from '@actions/navigation';
import StatusBar from '@components/status_bar';
import EnableDisableCalls from '@mmproducts/calls/components/enable_disable_calls';
import StartCall from '@mmproducts/calls/components/start_call';
import CallsChannelInfo from '@mmproducts/calls/components/channel_info/calls_channel_info';
import {alertErrorWithFallback} from '@utils/general';
import {t} from '@utils/i18n';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@ -51,15 +50,12 @@ export default class ChannelInfo extends PureComponent {
currentUserId: PropTypes.string,
isTeammateGuest: PropTypes.bool.isRequired,
isDirectMessage: PropTypes.bool.isRequired,
isGroupMessage: PropTypes.bool.isRequired,
teammateId: PropTypes.string,
theme: PropTypes.object.isRequired,
customStatus: PropTypes.object,
isCustomStatusEnabled: PropTypes.bool.isRequired,
isCustomStatusExpired: PropTypes.bool.isRequired,
isCustomStatusExpirySupported: PropTypes.bool.isRequired,
isCallsEnabled: PropTypes.bool.isRequired,
isChannelAdmin: PropTypes.bool.isRequired,
isSupportedServerCalls: PropTypes.bool.isRequired,
};
@ -93,19 +89,11 @@ export default class ChannelInfo extends PureComponent {
dismissModal();
};
startCallHandler = (channelId) => {
joinCallHandler = (channelId) => {
this.props.actions.joinCall(channelId);
this.close();
};
toggleCalls = () => {
if (this.props.isCallsEnabled) {
this.props.actions.disableChannelCalls(this.props.currentChannel.id);
} else {
this.props.actions.enableChannelCalls(this.props.currentChannel.id);
}
};
permalinkBadTeam = () => {
const {intl} = this.context;
const message = {
@ -117,7 +105,7 @@ export default class ChannelInfo extends PureComponent {
};
actionsRows = (channelIsArchived) => {
const {currentChannel, currentUserId, isDirectMessage, isGroupMessage, theme, isCallsEnabled, isSupportedServerCalls, isChannelAdmin} = this.props;
const {currentChannel, currentUserId, isDirectMessage, theme, isSupportedServerCalls} = this.props;
if (channelIsArchived) {
return (
@ -179,23 +167,11 @@ export default class ChannelInfo extends PureComponent {
theme={theme}
/>
{isSupportedServerCalls &&
<>
<StartCall
testID='channel_info.start_call.action'
theme={theme}
currentChannelId={currentChannel.id}
currentChannelName={currentChannel.display_name}
joinCall={this.startCallHandler}
canStartCall={isCallsEnabled}
/>
<EnableDisableCalls
testID='channel_info.start_call.action'
theme={theme}
onPress={this.toggleCalls}
canEnableDisableCalls={isDirectMessage || isGroupMessage || isChannelAdmin}
enabled={isCallsEnabled}
/>
</>}
<CallsChannelInfo
theme={theme}
joinCall={this.joinCallHandler}
/>
}
<Bindings
theme={theme}
/>

View file

@ -10,11 +10,11 @@ import {getCustomEmojisInText} from '@mm-redux/actions/emojis';
import {General} from '@mm-redux/constants';
import {getCurrentChannel, getCurrentChannelStats} from '@mm-redux/selectors/entities/channels';
import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
import {getCurrentUserRoles, getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
import {getUserIdFromChannelName} from '@mm-redux/utils/channel_utils';
import {isAdmin as checkIsAdmin, isChannelAdmin as checkIsChannelAdmin, displayUsername} from '@mm-redux/utils/user_utils';
import {displayUsername} from '@mm-redux/utils/user_utils';
import {joinCall, enableChannelCalls, disableChannelCalls} from '@mmproducts/calls/store/actions/calls';
import {isCallsEnabled, isSupportedServer} from '@mmproducts/calls/store/selectors/calls';
import {isSupportedServer} from '@mmproducts/calls/store/selectors/calls';
import {makeGetCustomStatus, isCustomStatusEnabled, isCustomStatusExpired, isCustomStatusExpirySupported} from '@selectors/custom_status';
import {isGuest} from '@utils/users';
@ -38,8 +38,6 @@ function makeMapStateToProps() {
let customStatus;
let customStatusExpired = true;
let customStatusExpirySupported = false;
const roles = getCurrentUserRoles(state) || '';
const isChannelAdmin = checkIsAdmin(roles) || checkIsChannelAdmin(roles);
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
if (isDirectMessage) {
@ -70,16 +68,13 @@ function makeMapStateToProps() {
currentUserId,
isTeammateGuest,
isDirectMessage,
isGroupMessage,
teammateId,
theme: getTheme(state),
customStatus,
isCustomStatusEnabled: customStatusEnabled,
isCustomStatusExpired: customStatusExpired,
isCustomStatusExpirySupported: customStatusExpirySupported,
isCallsEnabled: isCallsEnabled(state),
isSupportedServerCalls,
isChannelAdmin,
};
};
}