diff --git a/app/products/calls/actions/calls.test.ts b/app/products/calls/actions/calls.test.ts index b2f2c77d9..5be7c259c 100644 --- a/app/products/calls/actions/calls.test.ts +++ b/app/products/calls/actions/calls.test.ts @@ -115,12 +115,42 @@ const addFakeCall = (serverUrl: string, channelId: string) => { const call: Call = { id: 'call', sessions: { - a23456abcdefghijklmnopqrs: {sessionId: 'a23456abcdefghijklmnopqrs', userId: 'xohi8cki9787fgiryne716u84o', muted: false, raisedHand: 0}, - a12345667890bcdefghijklmn1: {sessionId: 'a12345667890bcdefghijklmn1', userId: 'xohi8cki9787fgiryne716u84o', muted: true, raisedHand: 0}, - a12345667890bcdefghijklmn2: {sessionId: 'a12345667890bcdefghijklmn2', userId: 'xohi8cki9787fgiryne716u84o', muted: false, raisedHand: 0}, - a12345667890bcdefghijklmn3: {sessionId: 'a12345667890bcdefghijklmn3', userId: 'xohi8cki9787fgiryne716u84o', muted: true, raisedHand: 0}, - a12345667890bcdefghijklmn4: {sessionId: 'a12345667890bcdefghijklmn4', userId: 'xohi8cki9787fgiryne716u84o', muted: false, raisedHand: 0}, - a12345667890bcdefghijklmn5: {sessionId: 'a12345667890bcdefghijklmn5', userId: 'xohi8cki9787fgiryne716u84o', muted: true, raisedHand: 0}, + a23456abcdefghijklmnopqrs: { + sessionId: 'a23456abcdefghijklmnopqrs', + userId: 'xohi8cki9787fgiryne716u84o', + muted: false, + raisedHand: 0, + }, + a12345667890bcdefghijklmn1: { + sessionId: 'a12345667890bcdefghijklmn1', + userId: 'xohi8cki9787fgiryne716u84o', + muted: true, + raisedHand: 0, + }, + a12345667890bcdefghijklmn2: { + sessionId: 'a12345667890bcdefghijklmn2', + userId: 'xohi8cki9787fgiryne716u84o', + muted: false, + raisedHand: 0, + }, + a12345667890bcdefghijklmn3: { + sessionId: 'a12345667890bcdefghijklmn3', + userId: 'xohi8cki9787fgiryne716u84o', + muted: true, + raisedHand: 0, + }, + a12345667890bcdefghijklmn4: { + sessionId: 'a12345667890bcdefghijklmn4', + userId: 'xohi8cki9787fgiryne716u84o', + muted: false, + raisedHand: 0, + }, + a12345667890bcdefghijklmn5: { + sessionId: 'a12345667890bcdefghijklmn5', + userId: 'xohi8cki9787fgiryne716u84o', + muted: true, + raisedHand: 0, + }, }, channelId, startTime: (new Date()).getTime(), @@ -198,9 +228,9 @@ describe('Actions.Calls', () => { assert.equal(response!.data, 'channel-id'); assert.equal((result.current[1] as CurrentCall).channelId, 'channel-id'); - expect(newConnection).toBeCalled(); + expect(newConnection).toHaveBeenCalled(); expect(newConnection.mock.calls[0][1]).toBe('channel-id'); - expect(updateThreadFollowing).toBeCalled(); + expect(updateThreadFollowing).toHaveBeenCalled(); await act(async () => { CallsActions.leaveCall(); @@ -229,7 +259,7 @@ describe('Actions.Calls', () => { assert.equal(response!.data, 'channel-id'); assert.equal((result.current[1] as CurrentCall | null)?.channelId, 'channel-id'); - expect(getConnectionForTesting()!.disconnect).not.toBeCalled(); + expect(getConnectionForTesting()!.disconnect).not.toHaveBeenCalled(); const disconnectMock = getConnectionForTesting()!.disconnect; await act(async () => { @@ -239,7 +269,7 @@ describe('Actions.Calls', () => { myselfLeftCall(); }); - expect(disconnectMock).toBeCalled(); + expect(disconnectMock).toHaveBeenCalled(); expect(getConnectionForTesting()).toBe(null); assert.equal((result.current[1] as CurrentCall | null), null); }); @@ -270,7 +300,7 @@ describe('Actions.Calls', () => { CallsActions.muteMyself(); }); - expect(getConnectionForTesting()!.mute).toBeCalled(); + expect(getConnectionForTesting()!.mute).toHaveBeenCalled(); await act(async () => { CallsActions.leaveCall(); @@ -303,7 +333,7 @@ describe('Actions.Calls', () => { CallsActions.unmuteMyself(); }); - expect(getConnectionForTesting()!.unmute).toBeCalled(); + expect(getConnectionForTesting()!.unmute).toHaveBeenCalled(); await act(async () => { CallsActions.leaveCall(); @@ -319,7 +349,7 @@ describe('Actions.Calls', () => { await act(async () => { await CallsActions.loadCalls('server1', 'userId1'); }); - expect(mockClient.getCalls).toBeCalled(); + expect(mockClient.getCalls).toHaveBeenCalled(); assert.equal((result.current[0] as CallsState).calls['channel-1'].channelId, 'channel-1'); assert.equal((result.current[0] as CallsState).enabled['channel-1'], true); assert.equal((result.current[1] as ChannelsWithCalls)['channel-1'], true); @@ -347,7 +377,7 @@ describe('Actions.Calls', () => { await act(async () => { await CallsActions.loadCalls('server1', 'userId1'); }); - expect(mockClient.getCalls).toBeCalled(); + expect(mockClient.getCalls).toHaveBeenCalled(); assert.deepEqual((result.current[0] as CallsState), expectedCallsState); assert.deepEqual((result.current[1] as ChannelsWithCalls), {}); assert.equal((result.current[2] as CurrentCall | null), null); @@ -362,7 +392,7 @@ describe('Actions.Calls', () => { await act(async () => { await CallsActions.loadConfig('server1'); }); - expect(mockClient.getCallsConfig).toBeCalledWith(); + expect(mockClient.getCallsConfig).toHaveBeenCalledWith(); assert.equal(result.current.DefaultEnabled, true); assert.equal(result.current.AllowEnableCalls, true); }); @@ -374,7 +404,7 @@ describe('Actions.Calls', () => { await act(async () => { await CallsActions.enableChannelCalls('server1', 'channel-1', true); }); - expect(mockClient.enableChannelCalls).toBeCalledWith('channel-1', true); + expect(mockClient.enableChannelCalls).toHaveBeenCalledWith('channel-1', true); assert.equal(result.current.enabled['channel-1'], true); }); @@ -385,13 +415,13 @@ describe('Actions.Calls', () => { await act(async () => { await CallsActions.enableChannelCalls('server1', 'channel-1', true); }); - expect(mockClient.enableChannelCalls).toBeCalledWith('channel-1', true); + expect(mockClient.enableChannelCalls).toHaveBeenCalledWith('channel-1', true); assert.equal(result.current.enabled['channel-1'], true); mockClient.enableChannelCalls.mockReturnValueOnce({enabled: false}); await act(async () => { await CallsActions.enableChannelCalls('server1', 'channel-1', false); }); - expect(mockClient.enableChannelCalls).toBeCalledWith('channel-1', false); + expect(mockClient.enableChannelCalls).toHaveBeenCalledWith('channel-1', false); assert.equal(result.current.enabled['channel-1'], false); }); @@ -400,8 +430,8 @@ describe('Actions.Calls', () => { await CallsActions.startCallRecording('server1', 'channel-id'); }); - expect(mockClient.startCallRecording).toBeCalledWith('channel-id'); - expect(needsRecordingErrorAlert).toBeCalled(); + expect(mockClient.startCallRecording).toHaveBeenCalledWith('channel-id'); + expect(needsRecordingErrorAlert).toHaveBeenCalled(); }); it('stopCallRecording', async () => { @@ -409,9 +439,9 @@ describe('Actions.Calls', () => { await CallsActions.stopCallRecording('server1', 'channel-id'); }); - expect(mockClient.stopCallRecording).toBeCalledWith('channel-id'); - expect(needsRecordingErrorAlert).toBeCalled(); - expect(needsRecordingWillBePostedAlert).toBeCalled(); + expect(mockClient.stopCallRecording).toHaveBeenCalledWith('channel-id'); + expect(needsRecordingErrorAlert).toHaveBeenCalled(); + expect(needsRecordingWillBePostedAlert).toHaveBeenCalled(); }); it('dismissIncomingCall', async () => { @@ -419,7 +449,7 @@ describe('Actions.Calls', () => { await CallsActions.dismissIncomingCall('server1', 'channel-id'); }); - expect(mockClient.dismissCall).toBeCalledWith('channel-id'); + expect(mockClient.dismissCall).toHaveBeenCalledWith('channel-id'); }); it('userLeftChannelErr', async () => { @@ -446,20 +476,20 @@ describe('Actions.Calls', () => { assert.equal(response!.data, 'channel-id'); assert.equal((result.current[1] as CurrentCall).channelId, 'channel-id'); - expect(newConnection).toBeCalled(); + expect(newConnection).toHaveBeenCalled(); expect(newConnection.mock.calls[0][1]).toBe('channel-id'); - expect(updateThreadFollowing).toBeCalled(); + expect(updateThreadFollowing).toHaveBeenCalled(); await act(async () => { CallsActions.leaveCall(userLeftChannelErr); }); - expect(intl.formatMessage).toBeCalledWith({ + expect(intl.formatMessage).toHaveBeenCalledWith({ id: 'mobile.calls_user_left_channel_error_title', defaultMessage: 'You left the channel', }); - expect(intl.formatMessage).toBeCalledWith({ + expect(intl.formatMessage).toHaveBeenCalledWith({ id: 'mobile.calls_user_left_channel_error_message', defaultMessage: 'You have left the channel, and have been disconnected from the call.', }); @@ -489,20 +519,20 @@ describe('Actions.Calls', () => { assert.equal(response!.data, 'channel-id'); assert.equal((result.current[1] as CurrentCall).channelId, 'channel-id'); - expect(newConnection).toBeCalled(); + expect(newConnection).toHaveBeenCalled(); expect(newConnection.mock.calls[0][1]).toBe('channel-id'); - expect(updateThreadFollowing).toBeCalled(); + expect(updateThreadFollowing).toHaveBeenCalled(); await act(async () => { CallsActions.leaveCall(userRemovedFromChannelErr); }); - expect(intl.formatMessage).toBeCalledWith({ + expect(intl.formatMessage).toHaveBeenCalledWith({ id: 'mobile.calls_user_removed_from_channel_error_title', defaultMessage: 'You were removed from channel', }); - expect(intl.formatMessage).toBeCalledWith({ + expect(intl.formatMessage).toHaveBeenCalledWith({ id: 'mobile.calls_user_removed_from_channel_error_message', defaultMessage: 'You have been removed from the channel, and have been disconnected from the call.', }); @@ -532,14 +562,14 @@ describe('Actions.Calls', () => { assert.equal(response!.data, 'channel-id'); assert.equal((result.current[1] as CurrentCall).channelId, 'channel-id'); - expect(newConnection).toBeCalled(); + expect(newConnection).toHaveBeenCalled(); expect(newConnection.mock.calls[0][1]).toBe('channel-id'); - expect(updateThreadFollowing).toBeCalled(); + expect(updateThreadFollowing).toHaveBeenCalled(); await act(async () => { CallsActions.leaveCall(new Error('generic error')); }); - expect(errorAlert).toBeCalled(); + expect(errorAlert).toHaveBeenCalled(); }); }); diff --git a/app/products/calls/actions/calls.ts b/app/products/calls/actions/calls.ts index d4d1359fa..86a0507b4 100644 --- a/app/products/calls/actions/calls.ts +++ b/app/products/calls/actions/calls.ts @@ -363,7 +363,7 @@ export const canEndCall = async (serverUrl: string, channelId: string) => { return false; } - return isSystemAdmin(currentUser.roles) || currentUser.id === call.ownerId; + return isSystemAdmin(currentUser.roles) || currentUser.id === call.hostId; }; export const getEndCallMessage = async (serverUrl: string, channelId: string, currentUserId: string, intl: IntlShape) => { diff --git a/app/products/calls/components/calls_custom_message/calls_custom_message.tsx b/app/products/calls/components/calls_custom_message/calls_custom_message.tsx index 7ddde2133..63df36327 100644 --- a/app/products/calls/components/calls_custom_message/calls_custom_message.tsx +++ b/app/products/calls/components/calls_custom_message/calls_custom_message.tsx @@ -2,10 +2,11 @@ // See LICENSE.txt for license information. import moment from 'moment-timezone'; -import React from 'react'; +import React, {useCallback} from 'react'; import {useIntl} from 'react-intl'; import {Text, TouchableOpacity, View} from 'react-native'; +import {leaveCall} from '@calls/actions'; import {leaveAndJoinWithAlert, showLimitRestrictedAlert} from '@calls/alerts'; import CompassIcon from '@components/compass_icon'; import FormattedRelativeTime from '@components/formatted_relative_time'; @@ -15,7 +16,7 @@ import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; -import {displayUsername, getUserTimezone} from '@utils/user'; +import {getUserTimezone} from '@utils/user'; import type {LimitRestrictedInfo} from '@calls/observers'; import type PostModel from '@typings/database/models/servers/post'; @@ -24,9 +25,7 @@ import type UserModel from '@typings/database/models/servers/user'; type Props = { post: PostModel; currentUser?: UserModel; - author?: UserModel; isMilitaryTime: boolean; - teammateNameDisplay?: string; limitRestrictedInfo?: LimitRestrictedInfo; ccChannelId?: string; } @@ -41,61 +40,74 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { flexDirection: 'row', alignItems: 'center', color: changeOpacity(theme.centerChannelColor, 0.6), - paddingTop: 5, - paddingBottom: 5, + padding: 12, + marginBottom: 2, + gap: 8, + backgroundColor: theme.centerChannelBg, + borderWidth: 1, + borderColor: changeOpacity(theme.centerChannelColor, 0.12), + borderRadius: 4, + shadowColor: theme.centerChannelColor, + shadowOffset: { + width: 0, + height: 2, + }, + shadowRadius: 1, + shadowOpacity: 0.08, + elevation: 1, }, - messageText: { + message: { flex: 1, - paddingLeft: 12, - paddingRight: 4, + }, + text: { + color: theme.centerChannelColor, + ...typography('Heading', 200), + }, + timeText: { + color: changeOpacity(theme.centerChannelColor, 0.72), + ...typography('Body', 75), + }, + callIcon: { + padding: 10, + borderRadius: 20, + color: theme.buttonColor, + overflow: 'hidden', }, joinCallIcon: { - padding: 8, backgroundColor: theme.onlineIndicator, - borderRadius: 4, - color: theme.buttonColor, - overflow: 'hidden', }, phoneHangupIcon: { - padding: 8, - backgroundColor: changeOpacity(theme.centerChannelColor, 0.6), - borderRadius: 4, - color: theme.buttonColor, - overflow: 'hidden', + color: changeOpacity(theme.centerChannelColor, 0.72), + backgroundColor: changeOpacity(theme.centerChannelColor, 0.08), }, - joinCallButtonText: { - color: theme.buttonColor, - ...typography('Body', 75, 'SemiBold'), - }, - joinCallButtonTextRestricted: { - color: changeOpacity(theme.centerChannelColor, 0.32), - }, - joinCallButtonIcon: { - color: theme.buttonColor, - marginRight: 5, - }, - joinCallButtonIconRestricted: { - color: changeOpacity(theme.centerChannelColor, 0.32), - }, - startedText: { - color: theme.centerChannelColor, - ...typography('Body', 100, 'SemiBold'), - }, - joinCallButton: { + callButton: { flexDirection: 'row', - padding: 12, - backgroundColor: theme.onlineIndicator, + paddingHorizontal: 16, + paddingVertical: 10, + gap: 7, borderRadius: 4, alignItems: 'center', alignContent: 'center', }, + joinCallButton: { + backgroundColor: theme.onlineIndicator, + }, + leaveCallButton: { + backgroundColor: theme.dndIndicator, + }, + buttonText: { + color: theme.buttonColor, + ...typography('Body', 100, 'SemiBold'), + }, + buttonRestricted: { + color: changeOpacity(theme.centerChannelColor, 0.32), + }, + buttonIcon: { + color: theme.buttonColor, + }, joinCallButtonRestricted: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.08), }, - timeText: { - ...typography('Body', 75), - color: changeOpacity(theme.centerChannelColor, 0.64), - }, endCallInfo: { flexDirection: 'row', alignItems: 'center', @@ -109,10 +121,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { }; }); -export const CallsCustomMessage = ({ - post, currentUser, author, isMilitaryTime, teammateNameDisplay, - ccChannelId, limitRestrictedInfo, -}: Props) => { +export const CallsCustomMessage = ({post, currentUser, isMilitaryTime, ccChannelId, limitRestrictedInfo}: Props) => { const intl = useIntl(); const theme = useTheme(); const style = getStyleSheet(theme); @@ -122,18 +131,18 @@ export const CallsCustomMessage = ({ const alreadyInTheCall = Boolean(ccChannelId && ccChannelId === post.channelId); const isLimitRestricted = Boolean(limitRestrictedInfo?.limitRestricted); - const joinHandler = () => { - if (alreadyInTheCall) { - return; - } - + const joinHandler = useCallback(() => { if (isLimitRestricted) { showLimitRestrictedAlert(limitRestrictedInfo!, intl); return; } leaveAndJoinWithAlert(intl, serverUrl, post.channelId); - }; + }, [limitRestrictedInfo, intl, serverUrl, post.channelId]); + + const leaveHandler = useCallback(() => { + leaveCall(); + }, []); const title = post.props.title ? ( @@ -148,20 +157,20 @@ export const CallsCustomMessage = ({ - + {' '} {'•'} @@ -184,22 +193,54 @@ export const CallsCustomMessage = ({ ); } - const joinTextStyle = [style.joinCallButtonText, isLimitRestricted && style.joinCallButtonTextRestricted]; + const button = alreadyInTheCall ? ( + + + + + ) : ( + + + + + ); + return ( <> {title} - + - - - - { - alreadyInTheCall && - - } - { - !alreadyInTheCall && - - } - + {button} ); diff --git a/app/products/calls/components/calls_custom_message/index.ts b/app/products/calls/components/calls_custom_message/index.ts index 2547bf33f..6368f17b6 100644 --- a/app/products/calls/components/calls_custom_message/index.ts +++ b/app/products/calls/components/calls_custom_message/index.ts @@ -11,7 +11,7 @@ import {observeCurrentCall} from '@calls/state'; import {Preferences} from '@constants'; import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference'; import {queryDisplayNamePreferences} from '@queries/servers/preference'; -import {observeCurrentUser, observeTeammateNameDisplay, observeUser} from '@queries/servers/user'; +import {observeCurrentUser} from '@queries/servers/user'; import type {WithDatabaseArgs} from '@typings/database/database'; import type PostModel from '@typings/database/models/servers/post'; @@ -23,7 +23,6 @@ type OwnProps = { const enhanced = withObservables(['post'], ({serverUrl, post, database}: OwnProps & WithDatabaseArgs) => { const currentUser = observeCurrentUser(database); - const author = observeUser(database, post.userId); const isMilitaryTime = queryDisplayNamePreferences(database).observeWithColumns(['value']).pipe( switchMap( (preferences) => of$(getDisplayNamePreferenceAsBool(preferences, Preferences.USE_MILITARY_TIME)), @@ -34,7 +33,6 @@ const enhanced = withObservables(['post'], ({serverUrl, post, database}: OwnProp if (post.props.end_at) { return { currentUser, - author, isMilitaryTime, }; } @@ -46,9 +44,7 @@ const enhanced = withObservables(['post'], ({serverUrl, post, database}: OwnProp return { currentUser, - author, isMilitaryTime, - teammateNameDisplay: observeTeammateNameDisplay(database), limitRestrictedInfo: observeIsCallLimitRestricted(database, serverUrl, post.channelId), ccChannelId, }; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index dc7b077ed..b9a38edad 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -447,7 +447,6 @@ "mobile.calls_call_thread": "Call Thread", "mobile.calls_captions": "Captions", "mobile.calls_captions_turned_on": "Live captions turned on", - "mobile.calls_current_call": "Current call", "mobile.calls_disable": "Disable calls", "mobile.calls_dismiss": "Dismiss", "mobile.calls_enable": "Enable calls", @@ -474,6 +473,7 @@ "mobile.calls_host_transcription_title": "Recording and transcription has started", "mobile.calls_incoming_dm": "{name} is inviting you to a call", "mobile.calls_incoming_gm": "{name} is inviting you to a call with {num, plural, one {# other} other {# others}}", + "mobile.calls_join": "Join", "mobile.calls_join_call": "Join call", "mobile.calls_lasted": "Lasted {duration}", "mobile.calls_leave": "Leave", @@ -489,7 +489,6 @@ "mobile.calls_mute_others": "Mute others", "mobile.calls_mute_participant": "Mute participant", "mobile.calls_name_is_talking_postfix": "is talking...", - "mobile.calls_name_started_call": "{name} started a call", "mobile.calls_noone_talking": "No one is talking", "mobile.calls_not_available_msg": "Please contact your System Admin to enable the feature.", "mobile.calls_not_available_option": "(Not available)", @@ -529,6 +528,7 @@ "mobile.calls_speaker": "Speaker", "mobile.calls_start_call": "Start Call", "mobile.calls_start_call_exists": "A call is already ongoing in the channel.", + "mobile.calls_started_call": "Call started", "mobile.calls_stop_recording": "Stop Recording", "mobile.calls_stop_screenshare": "Stop screen share", "mobile.calls_tablet": "Tablet",