forked from Ivasoft/mattermost-mobile
Compare commits
26 Commits
release-1.
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7baf943038 | ||
|
|
8392bcb4f8 | ||
|
|
11136e7cf6 | ||
|
|
cd70d9067a | ||
|
|
d834c3042e | ||
|
|
14b1227c16 | ||
|
|
5b0b1e9af8 | ||
|
|
41b571f137 | ||
|
|
ee38dd39ea | ||
|
|
5e7f368583 | ||
|
|
c7586aa4d8 | ||
|
|
094621b99b | ||
|
|
c5d5e25426 | ||
|
|
901dae29e3 | ||
|
|
8f9de69b4d | ||
|
|
10faa0f37d | ||
|
|
3c40a6db30 | ||
|
|
7db68a5292 | ||
|
|
cb1773bda5 | ||
|
|
a10a21f152 | ||
|
|
bb655c8c60 | ||
|
|
c2de372504 | ||
|
|
23509cbb83 | ||
|
|
c74cd14713 | ||
|
|
1d299d02f0 | ||
|
|
0e1d0d78ae |
@@ -286,14 +286,6 @@ jobs:
|
||||
name: Post results to Mattermost
|
||||
command: go run ../security-automation-config/dependency-check/post_results.go
|
||||
|
||||
build-android-beta:
|
||||
executor: android
|
||||
steps:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-android-release:
|
||||
executor: android
|
||||
steps:
|
||||
@@ -333,14 +325,6 @@ jobs:
|
||||
- save:
|
||||
filename: "*.apk"
|
||||
|
||||
build-ios-beta:
|
||||
executor: ios
|
||||
steps:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
@@ -416,16 +400,6 @@ jobs:
|
||||
target: android
|
||||
file: "*.apk"
|
||||
|
||||
deploy-android-beta:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to Google Play"
|
||||
target: android
|
||||
file: "*.apk"
|
||||
|
||||
deploy-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
@@ -434,14 +408,6 @@ jobs:
|
||||
target: ios
|
||||
file: "*.ipa"
|
||||
|
||||
deploy-ios-beta:
|
||||
executor: ios
|
||||
steps:
|
||||
- deploy-to-store:
|
||||
task: "Deploy to TestFlight"
|
||||
target: ios
|
||||
file: "*.ipa"
|
||||
|
||||
github-release:
|
||||
executor:
|
||||
name: android
|
||||
@@ -485,27 +451,6 @@ workflows:
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-release-\d+$/
|
||||
|
||||
- build-android-beta:
|
||||
context: mattermost-mobile-android-beta
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-beta-\d+$/
|
||||
- deploy-android-beta:
|
||||
context: mattermost-mobile-android-beta
|
||||
requires:
|
||||
- build-android-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-beta-\d+$/
|
||||
|
||||
- build-ios-release:
|
||||
context: mattermost-mobile-ios-release
|
||||
requires:
|
||||
@@ -527,27 +472,6 @@ workflows:
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-release-\d+$/
|
||||
|
||||
- build-ios-beta:
|
||||
context: mattermost-mobile-ios-beta
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
- deploy-ios-beta:
|
||||
context: mattermost-mobile-ios-beta
|
||||
requires:
|
||||
- build-ios-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
|
||||
- build-android-pr:
|
||||
context: mattermost-mobile-android-pr
|
||||
requires:
|
||||
@@ -590,7 +514,6 @@ workflows:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
- /^build-ios-sim-\d+$/
|
||||
|
||||
- github-release:
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -14,7 +14,7 @@ env.d.ts
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
ios/build/*
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
@@ -43,6 +43,8 @@ ios/Pods
|
||||
local.properties
|
||||
*.iml
|
||||
android/app/bin
|
||||
android/app/build
|
||||
android/build
|
||||
.settings
|
||||
.project
|
||||
.classpath
|
||||
@@ -103,3 +105,8 @@ detox/detox_pixel_4_xl_api_30
|
||||
|
||||
#editor-settings
|
||||
.vscode
|
||||
.scannerwork
|
||||
|
||||
# Notice.txt generation
|
||||
!build/notice-file
|
||||
|
||||
|
||||
555
NOTICE.txt
555
NOTICE.txt
File diff suppressed because it is too large
Load Diff
@@ -131,8 +131,8 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 398
|
||||
versionName "1.52.0"
|
||||
versionCode 410
|
||||
versionName "1.54.0"
|
||||
multiDexEnabled = true
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -27,6 +27,7 @@ import {removeUserFromList} from '@mm-redux/utils/user_utils';
|
||||
import {batchLoadCalls} from '@mmproducts/calls/store/actions/calls';
|
||||
import {
|
||||
handleCallStarted,
|
||||
handleCallEnded,
|
||||
handleCallUserConnected,
|
||||
handleCallUserDisconnected,
|
||||
handleCallUserMuted,
|
||||
@@ -473,6 +474,8 @@ function handleEvent(msg: WebSocketMessage) {
|
||||
break;
|
||||
case WebsocketEvents.CALLS_CALL_START:
|
||||
return dispatch(handleCallStarted(msg));
|
||||
case WebsocketEvents.CALLS_CALL_END:
|
||||
return dispatch(handleCallEnded(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_ON:
|
||||
return dispatch(handleCallScreenOn(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_OFF:
|
||||
|
||||
@@ -63,6 +63,7 @@ export default class DraftInput extends PureComponent {
|
||||
channelMemberCountsByGroup: PropTypes.object,
|
||||
groupsWithAllowReference: PropTypes.object,
|
||||
addRecentUsedEmojisInMessage: PropTypes.func.isRequired,
|
||||
endCallAlert: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -296,6 +297,12 @@ export default class DraftInput extends PureComponent {
|
||||
const {intl} = this.context;
|
||||
const {channelId, executeCommand, rootId, userIsOutOfOffice, theme} = this.props;
|
||||
|
||||
if (msg.trim() === '/call end') {
|
||||
this.props.endCallAlert(channelId);
|
||||
|
||||
// NOTE: fallthrough because the server may want to handle the command as well
|
||||
}
|
||||
|
||||
const status = DraftUtils.getStatusFromSlashCommand(msg);
|
||||
if (userIsOutOfOffice && DraftUtils.isStatusSlashCommand(status)) {
|
||||
confirmOutOfOfficeDisabled(intl, status, this.updateStatus);
|
||||
|
||||
@@ -18,6 +18,7 @@ import {getAssociatedGroupsForReferenceMap} from '@mm-redux/selectors/entities/g
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {haveIChannelPermission} from '@mm-redux/selectors/entities/roles';
|
||||
import {getCurrentUserId, getStatusForUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {endCallAlert} from '@mmproducts/calls/store/actions/calls';
|
||||
import {isLandscape} from '@selectors/device';
|
||||
import {getCurrentChannelDraft, getThreadDraft} from '@selectors/views';
|
||||
|
||||
@@ -103,6 +104,7 @@ const mapDispatchToProps = {
|
||||
setStatus,
|
||||
getChannelMemberCountsByGroup,
|
||||
addRecentUsedEmojisInMessage,
|
||||
endCallAlert,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, null, {forwardRef: true})(PostDraft);
|
||||
|
||||
@@ -38,6 +38,7 @@ export default class RemoveMarkdown extends React.PureComponent {
|
||||
channelLink: Renderer.forwardChildren,
|
||||
emoji: this.renderNull,
|
||||
hashtag: Renderer.forwardChildren,
|
||||
latexinline: Renderer.forwardChildren,
|
||||
|
||||
paragraph: Renderer.forwardChildren,
|
||||
heading: Renderer.forwardChildren,
|
||||
|
||||
@@ -12,4 +12,8 @@ const RequiredServer = {
|
||||
|
||||
const PluginId = 'com.mattermost.calls';
|
||||
|
||||
export default {RequiredServer, RefreshConfigMillis, PluginId};
|
||||
// Used for case when cloud server is using Calls v0.5.3.
|
||||
// This can be removed when v0.5.4 is prepackaged in cloud.
|
||||
const DefaultCloudMaxParticipants = 8;
|
||||
|
||||
export default {RequiredServer, RefreshConfigMillis, PluginId, DefaultCloudMaxParticipants};
|
||||
|
||||
@@ -65,6 +65,7 @@ const WebsocketEvents = {
|
||||
CALLS_USER_VOICE_ON: `custom_${Calls.PluginId}_user_voice_on`,
|
||||
CALLS_USER_VOICE_OFF: `custom_${Calls.PluginId}_user_voice_off`,
|
||||
CALLS_CALL_START: `custom_${Calls.PluginId}_call_start`,
|
||||
CALLS_CALL_END: `custom_${Calls.PluginId}_call_end`,
|
||||
CALLS_SCREEN_ON: `custom_${Calls.PluginId}_user_screen_on`,
|
||||
CALLS_SCREEN_OFF: `custom_${Calls.PluginId}_user_screen_off`,
|
||||
CALLS_USER_RAISE_HAND: `custom_${Calls.PluginId}_user_raise_hand`,
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface ClientCallsMix {
|
||||
getCallsConfig: () => Promise<ServerConfig>;
|
||||
enableChannelCalls: (channelId: string) => Promise<ServerChannelState>;
|
||||
disableChannelCalls: (channelId: string) => Promise<ServerChannelState>;
|
||||
endCall: (channelId: string) => Promise<any>;
|
||||
}
|
||||
|
||||
const ClientCalls = (superclass: any) => class extends superclass {
|
||||
@@ -51,6 +52,20 @@ const ClientCalls = (superclass: any) => class extends superclass {
|
||||
{method: 'post', body: JSON.stringify({enabled: false})},
|
||||
);
|
||||
};
|
||||
|
||||
endCall = async (channelId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getCallsRoute()}/calls/${channelId}/end`,
|
||||
{method: 'post'},
|
||||
);
|
||||
};
|
||||
|
||||
genTURNCredentials = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getCallsRoute()}/turn-credentials`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientCalls;
|
||||
|
||||
@@ -57,31 +57,40 @@ exports[`CallMessage should match snapshot 1`] = `
|
||||
<TouchableOpacity
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignContent": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#339970",
|
||||
"borderRadius": 8,
|
||||
"flexDirection": "row",
|
||||
"padding": 12,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignContent": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#339970",
|
||||
"borderRadius": 8,
|
||||
"flexDirection": "row",
|
||||
"padding": 12,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<CompassIcon
|
||||
name="phone-outline"
|
||||
size={16}
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"marginRight": 5,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
"marginRight": 5,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Join call
|
||||
@@ -239,31 +248,40 @@ exports[`CallMessage should match snapshot for the call already in the current c
|
||||
<TouchableOpacity
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignContent": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#339970",
|
||||
"borderRadius": 8,
|
||||
"flexDirection": "row",
|
||||
"padding": 12,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignContent": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#339970",
|
||||
"borderRadius": 8,
|
||||
"flexDirection": "row",
|
||||
"padding": 12,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<CompassIcon
|
||||
name="phone-outline"
|
||||
size={16}
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"marginRight": 5,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
"marginRight": 5,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Current call
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import React, {useCallback} from 'react';
|
||||
import React from 'react';
|
||||
import {injectIntl, intlShape, IntlShape} from 'react-intl';
|
||||
import {View, TouchableOpacity, Text} from 'react-native';
|
||||
|
||||
@@ -32,6 +32,7 @@ type CallMessageProps = {
|
||||
currentChannelName: string;
|
||||
callChannelName: string;
|
||||
intl: typeof IntlShape;
|
||||
isLimitRestricted: boolean;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
@@ -66,10 +67,16 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
joinCallButtonText: {
|
||||
color: 'white',
|
||||
},
|
||||
joinCallButtonTextRestricted: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.32),
|
||||
},
|
||||
joinCallButtonIcon: {
|
||||
color: 'white',
|
||||
marginRight: 5,
|
||||
},
|
||||
joinCallButtonIconRestricted: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.32),
|
||||
},
|
||||
startedText: {
|
||||
color: theme.centerChannelColor,
|
||||
fontWeight: 'bold',
|
||||
@@ -82,6 +89,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
alignItems: 'center',
|
||||
alignContent: 'center',
|
||||
},
|
||||
joinCallButtonRestricted: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
},
|
||||
timeText: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
@@ -98,14 +108,28 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
};
|
||||
});
|
||||
|
||||
const CallMessage = ({post, user, teammateNameDisplay, confirmToJoin, alreadyInTheCall, theme, actions, userTimezone, isMilitaryTime, currentChannelName, callChannelName, intl}: CallMessageProps) => {
|
||||
const CallMessage = ({
|
||||
post,
|
||||
user,
|
||||
teammateNameDisplay,
|
||||
confirmToJoin,
|
||||
alreadyInTheCall,
|
||||
theme,
|
||||
actions,
|
||||
userTimezone,
|
||||
isMilitaryTime,
|
||||
currentChannelName,
|
||||
callChannelName,
|
||||
intl,
|
||||
isLimitRestricted,
|
||||
}: CallMessageProps) => {
|
||||
const style = getStyleSheet(theme);
|
||||
const joinHandler = useCallback(() => {
|
||||
if (alreadyInTheCall) {
|
||||
const joinHandler = () => {
|
||||
if (alreadyInTheCall || isLimitRestricted) {
|
||||
return;
|
||||
}
|
||||
leaveAndJoinWithAlert(intl, post.channel_id, callChannelName, currentChannelName, confirmToJoin, actions.joinCall);
|
||||
}, [post.channel_id, callChannelName, currentChannelName, confirmToJoin, actions.joinCall]);
|
||||
};
|
||||
|
||||
if (post.props.end_at) {
|
||||
return (
|
||||
@@ -142,6 +166,8 @@ const CallMessage = ({post, user, teammateNameDisplay, confirmToJoin, alreadyInT
|
||||
);
|
||||
}
|
||||
|
||||
const joinCallButtonText = alreadyInTheCall ? 'Current call' : 'Join call';
|
||||
|
||||
return (
|
||||
<View style={style.messageStyle}>
|
||||
<CompassIcon
|
||||
@@ -161,22 +187,17 @@ const CallMessage = ({post, user, teammateNameDisplay, confirmToJoin, alreadyInT
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={style.joinCallButton}
|
||||
style={[style.joinCallButton, isLimitRestricted && style.joinCallButtonRestricted]}
|
||||
onPress={joinHandler}
|
||||
>
|
||||
<CompassIcon
|
||||
name='phone-outline'
|
||||
size={16}
|
||||
style={style.joinCallButtonIcon}
|
||||
style={[style.joinCallButtonIcon, isLimitRestricted && style.joinCallButtonIconRestricted]}
|
||||
/>
|
||||
{alreadyInTheCall &&
|
||||
<Text
|
||||
style={style.joinCallButtonText}
|
||||
>{'Current call'}</Text>}
|
||||
{!alreadyInTheCall &&
|
||||
<Text
|
||||
style={style.joinCallButtonText}
|
||||
>{'Join call'}</Text>}
|
||||
<Text style={[style.joinCallButtonText, isLimitRestricted && style.joinCallButtonTextRestricted]}>
|
||||
{joinCallButtonText}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone';
|
||||
import {getUser, getCurrentUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getUserCurrentTimezone} from '@mm-redux/utils/timezone_utils';
|
||||
import {joinCall} from '@mmproducts/calls/store/actions/calls';
|
||||
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {getCalls, getCurrentCall, isLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
|
||||
import CallMessage from './call_message';
|
||||
|
||||
@@ -41,6 +41,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
userTimezone: enableTimezone ? getUserCurrentTimezone(currentUser.timezone) : undefined,
|
||||
currentChannelName: getChannel(state, post.channel_id)?.display_name,
|
||||
callChannelName: currentCall ? getChannel(state, currentCall.channelId)?.display_name : '',
|
||||
isLimitRestricted: isLimitRestricted(state, post.channel_id),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ 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 {getCalls, getCurrentCall, isLimitRestricted} 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';
|
||||
@@ -28,6 +28,7 @@ const StartCall = ({testID, theme, intl, joinCall}: Props) => {
|
||||
const currentCall = useSelector(getCurrentCall);
|
||||
const currentCallChannelId = currentCall?.channelId || '';
|
||||
const callChannelName = useSelector((state: GlobalState) => getChannel(state, currentCallChannelId)?.display_name) || '';
|
||||
const limitRestricted = useSelector(isLimitRestricted);
|
||||
|
||||
const confirmToJoin = Boolean(currentCall && currentCall.channelId !== currentChannel.id);
|
||||
const alreadyInTheCall = Boolean(currentCall && call && currentCall.channelId === call.channelId);
|
||||
@@ -39,7 +40,7 @@ const StartCall = ({testID, theme, intl, joinCall}: Props) => {
|
||||
const [tryLeaveAndJoin, msgPostfix] = useTryCallsFunction(leaveAndJoin);
|
||||
const handleStartCall = useCallback(preventDoubleTap(tryLeaveAndJoin), [tryLeaveAndJoin]);
|
||||
|
||||
if (alreadyInTheCall) {
|
||||
if (alreadyInTheCall || limitRestricted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,89 +1,100 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`JoinCall should match snapshot 1`] = `
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#3DB887",
|
||||
"flexDirection": "row",
|
||||
"height": 38,
|
||||
"justifyContent": "center",
|
||||
"padding": 5,
|
||||
"width": "100%",
|
||||
"backgroundColor": "#ffffff",
|
||||
}
|
||||
}
|
||||
>
|
||||
<CompassIcon
|
||||
name="phone-in-talk"
|
||||
size={16}
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"marginLeft": 10,
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "bold",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#3DB887",
|
||||
"flexDirection": "row",
|
||||
"height": 38,
|
||||
"justifyContent": "center",
|
||||
"padding": 5,
|
||||
"width": "100%",
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
Join Call
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"flex": 1,
|
||||
"fontWeight": "400",
|
||||
"marginLeft": 10,
|
||||
<CompassIcon
|
||||
name="phone-in-talk"
|
||||
size={16}
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"marginLeft": 10,
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedRelativeTime
|
||||
updateIntervalInSeconds={1}
|
||||
value={100}
|
||||
/>
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginRight": 5,
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "bold",
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(Avatars)
|
||||
breakAt={1}
|
||||
listTitle={
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.56)",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"paddingHorizontal": 16,
|
||||
"paddingVertical": 0,
|
||||
"top": 16,
|
||||
>
|
||||
Join Call
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"flex": 1,
|
||||
"fontWeight": "400",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedRelativeTime
|
||||
updateIntervalInSeconds={1}
|
||||
value={100}
|
||||
/>
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(Avatars)
|
||||
breakAt={1}
|
||||
listTitle={
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.56)",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"paddingHorizontal": 16,
|
||||
"paddingVertical": 0,
|
||||
"top": 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
Call participants
|
||||
</Text>
|
||||
}
|
||||
userIds={
|
||||
Array [
|
||||
"user-1-id",
|
||||
"user-2-id",
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
>
|
||||
Call participants
|
||||
</Text>
|
||||
}
|
||||
userIds={
|
||||
Array [
|
||||
"user-1-id",
|
||||
"user-2-id",
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
`;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {bindActionCreators, Dispatch} from 'redux';
|
||||
import {getChannel, getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {joinCall} from '@mmproducts/calls/store/actions/calls';
|
||||
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {getCalls, getCurrentCall, isLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
|
||||
import JoinCall from './join_call';
|
||||
|
||||
@@ -23,6 +23,7 @@ function mapStateToProps(state: GlobalState) {
|
||||
alreadyInTheCall: Boolean(currentCall && call && currentCall.channelId === call.channelId),
|
||||
currentChannelName: getChannel(state, currentChannelId)?.display_name,
|
||||
callChannelName: currentCall ? getChannel(state, currentCall.channelId)?.display_name : '',
|
||||
isLimitRestricted: isLimitRestricted(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ describe('JoinCall', () => {
|
||||
alreadyInTheCall: false,
|
||||
currentChannelName: 'Current Channel',
|
||||
callChannelName: 'Call Channel',
|
||||
isLimitRestricted: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
@@ -56,7 +57,7 @@ describe('JoinCall', () => {
|
||||
test('should join on click', () => {
|
||||
const joinCall = jest.fn();
|
||||
const props = {...baseProps, actions: {joinCall}};
|
||||
const wrapper = shallowWithIntl(<JoinCall {...props}/>).dive();
|
||||
const wrapper = shallowWithIntl(<JoinCall {...props}/>).dive().childAt(0);
|
||||
|
||||
wrapper.simulate('press');
|
||||
expect(Alert.alert).not.toHaveBeenCalled();
|
||||
@@ -66,7 +67,7 @@ describe('JoinCall', () => {
|
||||
test('should ask for confirmation on click', () => {
|
||||
const joinCall = jest.fn();
|
||||
const props = {...baseProps, confirmToJoin: true, actions: {joinCall}};
|
||||
const wrapper = shallowWithIntl(<JoinCall {...props}/>).dive();
|
||||
const wrapper = shallowWithIntl(<JoinCall {...props}/>).dive().childAt(0);
|
||||
|
||||
wrapper.simulate('press');
|
||||
expect(Alert.alert).toHaveBeenCalled();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo} from 'react';
|
||||
import React, {useEffect, useMemo} from 'react';
|
||||
import {injectIntl, IntlShape} from 'react-intl';
|
||||
import {View, Text, Pressable} from 'react-native';
|
||||
|
||||
@@ -26,49 +26,62 @@ type Props = {
|
||||
alreadyInTheCall: boolean;
|
||||
currentChannelName: string;
|
||||
callChannelName: string;
|
||||
isLimitRestricted: boolean;
|
||||
intl: typeof IntlShape;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#3DB887',
|
||||
width: '100%',
|
||||
padding: 5,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: JOIN_CALL_BAR_HEIGHT,
|
||||
},
|
||||
joinCallIcon: {
|
||||
color: theme.sidebarText,
|
||||
marginLeft: 10,
|
||||
marginRight: 5,
|
||||
},
|
||||
joinCall: {
|
||||
color: theme.sidebarText,
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
},
|
||||
started: {
|
||||
flex: 1,
|
||||
color: theme.sidebarText,
|
||||
fontWeight: '400',
|
||||
marginLeft: 10,
|
||||
},
|
||||
avatars: {
|
||||
marginRight: 5,
|
||||
},
|
||||
headerText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 0,
|
||||
top: 16,
|
||||
},
|
||||
};
|
||||
});
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
outerContainer: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
innerContainer: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#3DB887',
|
||||
width: '100%',
|
||||
padding: 5,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: JOIN_CALL_BAR_HEIGHT,
|
||||
},
|
||||
innerContainerRestricted: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.48),
|
||||
},
|
||||
joinCallIcon: {
|
||||
color: theme.sidebarText,
|
||||
marginLeft: 10,
|
||||
marginRight: 5,
|
||||
},
|
||||
joinCall: {
|
||||
color: theme.sidebarText,
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
},
|
||||
started: {
|
||||
flex: 1,
|
||||
color: theme.sidebarText,
|
||||
fontWeight: '400',
|
||||
marginLeft: 10,
|
||||
},
|
||||
limitReached: {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
textAlign: 'right',
|
||||
marginRight: 10,
|
||||
color: '#FFFFFFD6',
|
||||
fontWeight: '400',
|
||||
},
|
||||
avatars: {
|
||||
marginRight: 5,
|
||||
},
|
||||
headerText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 0,
|
||||
top: 16,
|
||||
},
|
||||
}));
|
||||
|
||||
const JoinCall = (props: Props) => {
|
||||
if (!props.call) {
|
||||
@@ -82,9 +95,12 @@ const JoinCall = (props: Props) => {
|
||||
};
|
||||
}, [props.call, props.alreadyInTheCall]);
|
||||
|
||||
const joinHandler = useCallback(() => {
|
||||
const joinHandler = () => {
|
||||
if (props.isLimitRestricted) {
|
||||
return;
|
||||
}
|
||||
leaveAndJoinWithAlert(props.intl, props.call.channelId, props.callChannelName, props.currentChannelName, props.confirmToJoin, props.actions.joinCall);
|
||||
}, [props.call.channelId, props.callChannelName, props.currentChannelName, props.confirmToJoin, props.actions.joinCall]);
|
||||
};
|
||||
|
||||
if (props.alreadyInTheCall) {
|
||||
return null;
|
||||
@@ -96,32 +112,39 @@ const JoinCall = (props: Props) => {
|
||||
}, [props.call.participants]);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={style.container}
|
||||
onPress={joinHandler}
|
||||
>
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
size={16}
|
||||
style={style.joinCallIcon}
|
||||
/>
|
||||
<Text style={style.joinCall}>{'Join Call'}</Text>
|
||||
<Text style={style.started}>
|
||||
<FormattedRelativeTime
|
||||
value={props.call.startTime}
|
||||
updateIntervalInSeconds={1}
|
||||
<View style={style.outerContainer}>
|
||||
<Pressable
|
||||
style={[style.innerContainer, props.isLimitRestricted && style.innerContainerRestricted]}
|
||||
onPress={joinHandler}
|
||||
>
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
size={16}
|
||||
style={style.joinCallIcon}
|
||||
/>
|
||||
</Text>
|
||||
<View style={style.avatars}>
|
||||
<Avatars
|
||||
userIds={userIds}
|
||||
breakAt={1}
|
||||
listTitle={
|
||||
<Text style={style.headerText}>{'Call participants'}</Text>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
<Text style={style.joinCall}>{'Join Call'}</Text>
|
||||
{props.isLimitRestricted ?
|
||||
<Text style={style.limitReached}>
|
||||
{'Participant limit reached'}
|
||||
</Text> :
|
||||
<Text style={style.started}>
|
||||
<FormattedRelativeTime
|
||||
value={props.call.startTime}
|
||||
updateIntervalInSeconds={1}
|
||||
/>
|
||||
</Text>
|
||||
}
|
||||
<View style={style.avatars}>
|
||||
<Avatars
|
||||
userIds={userIds}
|
||||
breakAt={1}
|
||||
listTitle={
|
||||
<Text style={style.headerText}>{'Call participants'}</Text>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
export default injectIntl(JoinCall);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import {deflate} from 'pako/lib/deflate.js';
|
||||
import {DeviceEventEmitter, EmitterSubscription} from 'react-native';
|
||||
import InCallManager from 'react-native-incall-manager';
|
||||
import {
|
||||
MediaStream,
|
||||
@@ -12,6 +13,8 @@ import {
|
||||
} from 'react-native-webrtc';
|
||||
|
||||
import {Client4} from '@client/rest';
|
||||
import {WebsocketEvents} from '@constants';
|
||||
import {ICEServersConfigs} from '@mmproducts/calls/store/types/calls';
|
||||
|
||||
import Peer from './simple-peer';
|
||||
import WebSocketClient from './websocket';
|
||||
@@ -20,12 +23,13 @@ export let client: any = null;
|
||||
|
||||
const websocketConnectTimeout = 3000;
|
||||
|
||||
export async function newClient(channelID: string, closeCb: () => void, setScreenShareURL: (url: string) => void) {
|
||||
export async function newClient(channelID: string, iceServers: ICEServersConfigs, closeCb: () => void, setScreenShareURL: (url: string) => void) {
|
||||
let peer: Peer | null = null;
|
||||
let stream: MediaStream;
|
||||
let voiceTrackAdded = false;
|
||||
let voiceTrack: MediaStreamTrack | null = null;
|
||||
let isClosed = false;
|
||||
let onCallEnd: EmitterSubscription | null = null;
|
||||
const streams: MediaStream[] = [];
|
||||
|
||||
try {
|
||||
@@ -40,13 +44,18 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
|
||||
console.log('Unable to get media device:', err); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
const ws = new WebSocketClient(Client4.getWebSocketUrl());
|
||||
const ws = new WebSocketClient(Client4.getWebSocketUrl(), Client4.getToken());
|
||||
|
||||
const disconnect = () => {
|
||||
if (!isClosed) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
if (onCallEnd) {
|
||||
onCallEnd.remove();
|
||||
onCallEnd = null;
|
||||
}
|
||||
|
||||
streams.forEach((s) => {
|
||||
s.getTracks().forEach((track: MediaStreamTrack) => {
|
||||
track.stop();
|
||||
@@ -65,6 +74,12 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
|
||||
}
|
||||
};
|
||||
|
||||
onCallEnd = DeviceEventEmitter.addListener(WebsocketEvents.CALLS_CALL_END, ({channelId}) => {
|
||||
if (channelId === channelID) {
|
||||
disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
const mute = () => {
|
||||
if (!peer) {
|
||||
return;
|
||||
@@ -119,16 +134,8 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
|
||||
});
|
||||
|
||||
ws.on('join', async () => {
|
||||
let config;
|
||||
try {
|
||||
config = await Client4.getCallsConfig();
|
||||
} catch (err) {
|
||||
console.log('ERROR FETCHING CALLS CONFIG:', err); // eslint-disable-line no-console
|
||||
return;
|
||||
}
|
||||
|
||||
InCallManager.start({media: 'audio'});
|
||||
peer = new Peer(null, config.ICEServers);
|
||||
peer = new Peer(null, iceServers);
|
||||
peer.on('signal', (data: any) => {
|
||||
if (data.type === 'offer' || data.type === 'answer') {
|
||||
ws.send('sdp', {
|
||||
|
||||
@@ -323,6 +323,16 @@ const CallScreen = (props: Props) => {
|
||||
setShowControlsInLandscape(!showControlsInLandscape);
|
||||
}, [showControlsInLandscape]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = DeviceEventEmitter.addListener(WebsocketEvents.CALLS_CALL_END, ({channelId}) => {
|
||||
if (channelId === props.call?.channelId) {
|
||||
popTopScreen();
|
||||
}
|
||||
});
|
||||
|
||||
return () => listener.remove();
|
||||
}, []);
|
||||
|
||||
if (!props.call) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
} from 'react-native-webrtc';
|
||||
import stream from 'readable-stream';
|
||||
|
||||
import {ICEServersConfigs} from '@mmproducts/calls/store/types/calls';
|
||||
|
||||
const queueMicrotask = (callback: any) => {
|
||||
Promise.resolve().then(callback).catch((e) => setTimeout(() => {
|
||||
throw e;
|
||||
@@ -94,7 +96,7 @@ export default class Peer extends stream.Duplex {
|
||||
private pc: RTCPeerConnection|null = null;
|
||||
private onFinishBound?: () => void;
|
||||
|
||||
constructor(localStream: MediaStream | null, iceServers?: string[]) {
|
||||
constructor(localStream: MediaStream | null, iceServers: ICEServersConfigs) {
|
||||
super({allowHalfOpen: false});
|
||||
|
||||
this.streams = localStream ? [localStream] : [];
|
||||
@@ -104,21 +106,10 @@ export default class Peer extends stream.Duplex {
|
||||
};
|
||||
|
||||
const connConfig = {
|
||||
iceServers: [
|
||||
{
|
||||
urls: [
|
||||
'stun:stun.l.google.com:19302',
|
||||
'stun:global.stun.twilio.com:3478',
|
||||
],
|
||||
},
|
||||
],
|
||||
iceServers,
|
||||
sdpSemantics: 'unified-plan',
|
||||
};
|
||||
|
||||
if (iceServers && iceServers.length > 0) {
|
||||
connConfig.iceServers[0].urls = iceServers;
|
||||
}
|
||||
|
||||
try {
|
||||
this.pc = new RTCPeerConnection(connConfig);
|
||||
} catch (err) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import keyMirror from '@mm-redux/utils/key_mirror';
|
||||
export default keyMirror({
|
||||
RECEIVED_CALLS: null,
|
||||
RECEIVED_CALL_STARTED: null,
|
||||
RECEIVED_CALL_ENDED: null,
|
||||
RECEIVED_CALL_FINISHED: null,
|
||||
RECEIVED_CHANNEL_CALL_ENABLED: null,
|
||||
RECEIVED_CHANNEL_CALL_DISABLED: null,
|
||||
|
||||
@@ -34,7 +34,10 @@ jest.mock('@client/rest', () => ({
|
||||
},
|
||||
]),
|
||||
getCallsConfig: jest.fn(() => ({
|
||||
ICEServers: ['mattermost.com'],
|
||||
ICEServersConfigs: [{
|
||||
urls: 'stun:stun1.example.com',
|
||||
},
|
||||
],
|
||||
AllowEnableCalls: true,
|
||||
DefaultEnabled: true,
|
||||
last_retrieved_at: 1234,
|
||||
@@ -117,11 +120,14 @@ describe('Actions.Calls', () => {
|
||||
expect(CallsActions.ws.disconnect).not.toBeCalled();
|
||||
const disconnectMock = CallsActions.ws.disconnect;
|
||||
await store.dispatch(CallsActions.leaveCall());
|
||||
|
||||
// ws.disconnect calls the callback, which is what sends the CallsTypes.RECEIVED_MYSELF_LEFT_CALL action.
|
||||
expect(disconnectMock).toBeCalled();
|
||||
await store.dispatch({type: CallsTypes.RECEIVED_MYSELF_LEFT_CALL});
|
||||
expect(CallsActions.ws).toBe(null);
|
||||
|
||||
result = store.getState().entities.calls.joined;
|
||||
assert.equal('', result);
|
||||
assert.equal(result, '');
|
||||
});
|
||||
|
||||
it('muteMyself', async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {intlShape} from 'react-intl';
|
||||
import {Alert} from 'react-native';
|
||||
import InCallManager from 'react-native-incall-manager';
|
||||
import {batch} from 'react-redux';
|
||||
|
||||
@@ -9,6 +10,10 @@ 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 {General} from '@mm-redux/constants';
|
||||
import {getCurrentChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getTeammateNameDisplaySetting} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getCurrentUserRoles, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {
|
||||
GenericAction,
|
||||
ActionFunc,
|
||||
@@ -17,10 +22,17 @@ import {
|
||||
ActionResult,
|
||||
} from '@mm-redux/types/actions';
|
||||
import {Dictionary} from '@mm-redux/types/utilities';
|
||||
import {displayUsername, isAdmin as checkIsAdmin} from '@mm-redux/utils/user_utils';
|
||||
import {newClient} from '@mmproducts/calls/connection';
|
||||
import CallsTypes from '@mmproducts/calls/store/action_types/calls';
|
||||
import {getConfig} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {
|
||||
getCallInCurrentChannel,
|
||||
getConfig,
|
||||
getICEServersConfigs,
|
||||
getNumCurrentConnectedParticipants,
|
||||
} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {Call, CallParticipant, DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
|
||||
import {getUserIdFromDM} from '@mmproducts/calls/utils';
|
||||
import {hasMicrophonePermission} from '@utils/permission';
|
||||
|
||||
export let ws: any = null;
|
||||
@@ -82,6 +94,7 @@ export function loadCalls(): ActionFunc {
|
||||
speakers: [],
|
||||
screenOn: channel.call.screen_sharing_id,
|
||||
threadId: channel.call.thread_id,
|
||||
creatorId: channel.call.creator_id,
|
||||
};
|
||||
}
|
||||
enabledChannels[channel.channel_id] = channel.enabled;
|
||||
@@ -189,7 +202,16 @@ export function joinCall(channelId: string, intl: typeof intlShape): ActionFunc
|
||||
dispatch(setSpeakerphoneOn(false));
|
||||
|
||||
try {
|
||||
ws = await newClient(channelId, () => null, setScreenShareURL);
|
||||
const state = getState();
|
||||
const iceConfigs = [...getICEServersConfigs(state)];
|
||||
if (getConfig(state).NeedsTURNCredentials) {
|
||||
iceConfigs.push(...await Client4.genTURNCredentials());
|
||||
}
|
||||
|
||||
ws = await newClient(channelId, iceConfigs, () => {
|
||||
dispatch(setSpeakerphoneOn(false));
|
||||
dispatch({type: CallsTypes.RECEIVED_MYSELF_LEFT_CALL});
|
||||
}, setScreenShareURL);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch, getState);
|
||||
dispatch(logError(error));
|
||||
@@ -212,13 +234,11 @@ export function joinCall(channelId: string, intl: typeof intlShape): ActionFunc
|
||||
}
|
||||
|
||||
export function leaveCall(): ActionFunc {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
return async () => {
|
||||
if (ws) {
|
||||
ws.disconnect();
|
||||
ws = null;
|
||||
}
|
||||
dispatch(setSpeakerphoneOn(false));
|
||||
dispatch({type: CallsTypes.RECEIVED_MYSELF_LEFT_CALL});
|
||||
return {};
|
||||
};
|
||||
}
|
||||
@@ -260,3 +280,51 @@ export function setSpeakerphoneOn(newState: boolean): GenericAction {
|
||||
data: newState,
|
||||
};
|
||||
}
|
||||
|
||||
export function endCallAlert(channelId: string): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const userId = getCurrentUserId(getState());
|
||||
const numParticipants = getNumCurrentConnectedParticipants(getState());
|
||||
const channel = getCurrentChannel(getState());
|
||||
const currentCall = getCallInCurrentChannel(getState());
|
||||
const roles = getCurrentUserRoles(getState());
|
||||
const isAdmin = checkIsAdmin(roles);
|
||||
|
||||
if (!isAdmin && userId !== currentCall?.creatorId) {
|
||||
Alert.alert('Error', 'You do not have permission to end the call. Please ask the call creator to end call.');
|
||||
return {};
|
||||
}
|
||||
|
||||
let msg = `Are you sure you want to end a call with ${numParticipants} participants in ${channel.display_name}?`;
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
const otherID = getUserIdFromDM(channel.name, getCurrentUserId(getState()));
|
||||
const otherUser = getUser(getState(), otherID);
|
||||
const nameDisplay = getTeammateNameDisplaySetting(getState());
|
||||
msg = `Are you sure you want to end a call with ${displayUsername(otherUser, nameDisplay)}?`;
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'End call',
|
||||
msg,
|
||||
[
|
||||
{
|
||||
text: 'Cancel',
|
||||
},
|
||||
{
|
||||
text: 'End call',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await Client4.endCall(channelId);
|
||||
} catch (e) {
|
||||
const err = e.message || 'unable to complete command, see server logs';
|
||||
Alert.alert('Error', `Error: ${err}`);
|
||||
}
|
||||
},
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,7 +50,15 @@ export function handleCallUserVoiceOff(msg: WebSocketMessage) {
|
||||
export function handleCallStarted(msg: WebSocketMessage): GenericAction {
|
||||
return {
|
||||
type: CallsTypes.RECEIVED_CALL_STARTED,
|
||||
data: {channelId: msg.data.channelID, startTime: msg.data.start_at, threadId: msg.data.thread_id, participants: {}},
|
||||
data: {channelId: msg.data.channelID, startTime: msg.data.start_at, threadId: msg.data.thread_id, participants: {}, creatorId: msg.data.creator_id},
|
||||
};
|
||||
}
|
||||
|
||||
export function handleCallEnded(msg: WebSocketMessage): GenericAction {
|
||||
DeviceEventEmitter.emit(WebsocketEvents.CALLS_CALL_END, {channelId: msg.broadcast.channel_id});
|
||||
return {
|
||||
type: CallsTypes.RECEIVED_CALL_ENDED,
|
||||
data: {channelId: msg.broadcast.channel_id},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,16 @@ describe('Reducers.calls.calls', () => {
|
||||
assert.deepEqual(state.calls, {'channel-2': call2});
|
||||
});
|
||||
|
||||
it('RECEIVED_CALL_ENDED', async () => {
|
||||
const initialState = {calls: {'channel-1': call1, 'channel-2': call2}};
|
||||
const testAction = {
|
||||
type: CallsTypes.RECEIVED_CALL_FINISHED,
|
||||
data: {channelId: 'channel-1'},
|
||||
};
|
||||
const state = callsReducer(initialState, testAction);
|
||||
assert.deepEqual(state.calls, {'channel-2': call2});
|
||||
});
|
||||
|
||||
it('RECEIVED_MUTE_USER_CALL', async () => {
|
||||
const initialState = {calls: {'channel-1': call1, 'channel-2': call2}};
|
||||
const testAction = {
|
||||
@@ -398,7 +408,7 @@ describe('Reducers.calls.config', () => {
|
||||
const testAction = {
|
||||
type: CallsTypes.RECEIVED_CONFIG,
|
||||
data: {
|
||||
ICEServers: ['google.com'],
|
||||
ICEServers: ['stun:stun.example.com'],
|
||||
AllowEnableCalls: true,
|
||||
DefaultEnabled: true,
|
||||
last_retrieved_at: 123,
|
||||
|
||||
@@ -53,6 +53,11 @@ function calls(state: Dictionary<Call> = {}, action: GenericAction) {
|
||||
nextState[newCall.channelId] = newCall;
|
||||
return nextState;
|
||||
}
|
||||
case CallsTypes.RECEIVED_CALL_ENDED: {
|
||||
const nextState = {...state};
|
||||
delete nextState[action.data.channelId];
|
||||
return nextState;
|
||||
}
|
||||
case CallsTypes.RECEIVED_CALL_FINISHED: {
|
||||
const newCall = action.data;
|
||||
const nextState = {...state};
|
||||
@@ -162,6 +167,12 @@ function joined(state = '', action: GenericAction) {
|
||||
case CallsTypes.RECEIVED_MYSELF_JOINED_CALL: {
|
||||
return action.data;
|
||||
}
|
||||
case CallsTypes.RECEIVED_CALL_ENDED: {
|
||||
if (action.data.channelId === state) {
|
||||
return '';
|
||||
}
|
||||
return state;
|
||||
}
|
||||
case CallsTypes.RECEIVED_MYSELF_LEFT_CALL: {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
import assert from 'assert';
|
||||
|
||||
import deepFreezeAndThrowOnMutation from '@mm-redux/utils/deep_freeze';
|
||||
import {DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
|
||||
|
||||
import * as Selectors from './calls';
|
||||
|
||||
describe('Selectors.Calls', () => {
|
||||
const call1 = {id: 'call1'};
|
||||
const call1 = {id: 'call1', participants: [{id: 'me'}]};
|
||||
const call2 = {id: 'call2'};
|
||||
const testState = deepFreezeAndThrowOnMutation({
|
||||
entities: {
|
||||
@@ -20,6 +21,10 @@ describe('Selectors.Calls', () => {
|
||||
joined: 'call1',
|
||||
enabled: {'channel-1': true, 'channel-2': false},
|
||||
screenShareURL: 'screenshare-url',
|
||||
config: DefaultServerConfig,
|
||||
},
|
||||
general: {
|
||||
license: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -77,4 +82,162 @@ describe('Selectors.Calls', () => {
|
||||
it('getScreenShareURL', () => {
|
||||
assert.equal(Selectors.getScreenShareURL(testState), 'screenshare-url');
|
||||
});
|
||||
|
||||
it('isLimitRestricted', () => {
|
||||
// Default, no limit
|
||||
assert.equal(Selectors.isLimitRestricted(testState, 'call1'), false);
|
||||
|
||||
let newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
config: {
|
||||
...testState.entities.calls.config,
|
||||
MaxCallParticipants: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Limit to 1 and one participant already in call.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), true);
|
||||
|
||||
// Limit to 1 but no call ongoing.
|
||||
assert.equal(Selectors.isLimitRestricted(newState), false);
|
||||
|
||||
newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
general: {
|
||||
license: {Cloud: 'true'},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// On cloud, no limit.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), false);
|
||||
|
||||
newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
config: {
|
||||
...testState.entities.calls.config,
|
||||
MaxCallParticipants: 1,
|
||||
},
|
||||
},
|
||||
general: {
|
||||
license: {Cloud: 'true'},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// On cloud, with limit.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), true);
|
||||
|
||||
const call = {id: 'call1',
|
||||
participants: [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
]};
|
||||
newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
calls: {call1: call},
|
||||
},
|
||||
general: {
|
||||
license: {Cloud: 'true'},
|
||||
},
|
||||
},
|
||||
};
|
||||
delete newState.entities.calls.config.MaxCallParticipants;
|
||||
|
||||
// On cloud, MaxCallParticipants missing, default should be used.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), false);
|
||||
|
||||
const newCall = {id: 'call1',
|
||||
participants: [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
]};
|
||||
newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
calls: {call1: newCall},
|
||||
},
|
||||
general: {
|
||||
license: {Cloud: 'true'},
|
||||
},
|
||||
},
|
||||
};
|
||||
delete newState.entities.calls.config.MaxCallParticipants;
|
||||
|
||||
// On cloud, MaxCallParticipants missing, default should be used.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), true);
|
||||
});
|
||||
|
||||
it('getICEServersConfigs', () => {
|
||||
assert.deepEqual(Selectors.getICEServersConfigs(testState), []);
|
||||
|
||||
// backwards compatible case, no ICEServersConfigs present.
|
||||
let newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
config: {
|
||||
...testState.entities.calls.config,
|
||||
ICEServers: ['stun:stun1.example.com'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
assert.deepEqual(Selectors.getICEServersConfigs(newState), [{urls: ['stun:stun1.example.com']}]);
|
||||
|
||||
// ICEServersConfigs defined case
|
||||
newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
config: {
|
||||
...testState.entities.calls.config,
|
||||
ICEServers: ['stun:stun1.example.com'],
|
||||
ICEServersConfigs: [
|
||||
{urls: 'stun:stun1.example.com'},
|
||||
{urls: 'turn:turn.example.com', username: 'username', credentail: 'password'},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
assert.deepEqual(Selectors.getICEServersConfigs(newState), [
|
||||
{urls: 'stun:stun1.example.com'},
|
||||
{urls: 'turn:turn.example.com', username: 'username', credentail: 'password'},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {Client4} from '@client/rest';
|
||||
import Calls from '@constants/calls';
|
||||
import {getCurrentChannelId} from '@mm-redux/selectors/entities/common';
|
||||
import {getServerVersion} from '@mm-redux/selectors/entities/general';
|
||||
import {getLicense, getServerVersion} from '@mm-redux/selectors/entities/general';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
||||
import {Call, ICEServersConfigs} from '@mmproducts/calls/store/types/calls';
|
||||
|
||||
export function getConfig(state: GlobalState) {
|
||||
return state.entities.calls.config;
|
||||
@@ -65,3 +68,71 @@ export function isSupportedServer(state: GlobalState) {
|
||||
export function isCallsPluginEnabled(state: GlobalState) {
|
||||
return state.entities.calls.pluginEnabled;
|
||||
}
|
||||
|
||||
export const getCallInCurrentChannel: (state: GlobalState) => Call | undefined = createSelector(
|
||||
getCurrentChannelId,
|
||||
getCalls,
|
||||
(currentChannelId, calls) => calls[currentChannelId],
|
||||
);
|
||||
|
||||
export const getNumCurrentConnectedParticipants: (state: GlobalState) => number = createSelector(
|
||||
getCurrentChannelId,
|
||||
getCalls,
|
||||
(currentChannelId, calls) => {
|
||||
const participants = calls[currentChannelId]?.participants;
|
||||
if (!participants) {
|
||||
return 0;
|
||||
}
|
||||
return Object.keys(participants).length || 0;
|
||||
},
|
||||
);
|
||||
|
||||
const isCloud: (state: GlobalState) => boolean = createSelector(
|
||||
getLicense,
|
||||
(license) => license?.Cloud === 'true',
|
||||
);
|
||||
|
||||
export const isLimitRestricted: (state: GlobalState, channelId?: string) => boolean = createSelector(
|
||||
isCloud,
|
||||
(state: GlobalState, channelId: string) => (channelId ? getCalls(state)[channelId] : getCallInCurrentChannel(state)),
|
||||
getConfig,
|
||||
(cloud, call, config) => {
|
||||
if (!call) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const numParticipants = Object.keys(call.participants || {}).length;
|
||||
|
||||
// TODO: The next block is used for case when cloud server is using Calls v0.5.3. This can be removed
|
||||
// when v0.5.4 is prepackaged in cloud. Then replace the max in the return statement with
|
||||
// config.MaxCallParticipants.
|
||||
let max = config.MaxCallParticipants;
|
||||
if (cloud && !max) {
|
||||
max = Calls.DefaultCloudMaxParticipants;
|
||||
}
|
||||
|
||||
return max !== 0 && numParticipants >= max;
|
||||
},
|
||||
);
|
||||
|
||||
export const getICEServersConfigs: (state: GlobalState) => ICEServersConfigs = createSelector(
|
||||
getConfig,
|
||||
(config) => {
|
||||
// if ICEServersConfigs is set, we can trust this to be complete and
|
||||
// coming from an updated API.
|
||||
if (config.ICEServersConfigs?.length > 0) {
|
||||
return config.ICEServersConfigs;
|
||||
}
|
||||
|
||||
// otherwise we revert to using the now deprecated field.
|
||||
if (config.ICEServers?.length > 0) {
|
||||
return [
|
||||
{
|
||||
urls: config.ICEServers,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ConfigurationParamWithUrls, ConfigurationParamWithUrl} from 'react-native-webrtc';
|
||||
|
||||
import {UserProfile} from '@mm-redux/types/users';
|
||||
import {Dictionary} from '@mm-redux/types/utilities';
|
||||
|
||||
@@ -15,12 +17,13 @@ export type CallsState = {
|
||||
}
|
||||
|
||||
export type Call = {
|
||||
participants: Dictionary<CallParticipant>;
|
||||
participants: Dictionary<CallParticipant>;
|
||||
channelId: string;
|
||||
startTime: number;
|
||||
speakers: string[];
|
||||
screenOn: string;
|
||||
threadId: string;
|
||||
creatorId: string;
|
||||
}
|
||||
|
||||
export type CallParticipant = {
|
||||
@@ -49,6 +52,7 @@ export type ServerCallState = {
|
||||
states: ServerUserState[];
|
||||
thread_id: string;
|
||||
screen_sharing_id: string;
|
||||
creator_id: string;
|
||||
}
|
||||
|
||||
export type VoiceEventData = {
|
||||
@@ -58,14 +62,24 @@ export type VoiceEventData = {
|
||||
|
||||
export type ServerConfig = {
|
||||
ICEServers: string[];
|
||||
ICEServersConfigs: ICEServersConfigs;
|
||||
AllowEnableCalls: boolean;
|
||||
DefaultEnabled: boolean;
|
||||
MaxCallParticipants: number;
|
||||
NeedsTURNCredentials: boolean;
|
||||
sku_short_name: string;
|
||||
last_retrieved_at: number;
|
||||
}
|
||||
|
||||
export const DefaultServerConfig = {
|
||||
ICEServers: [],
|
||||
ICEServersConfigs: [],
|
||||
AllowEnableCalls: false,
|
||||
DefaultEnabled: false,
|
||||
MaxCallParticipants: 0,
|
||||
NeedsTURNCredentials: false,
|
||||
sku_short_name: '',
|
||||
last_retrieved_at: 0,
|
||||
} as ServerConfig;
|
||||
|
||||
export type ICEServersConfigs = Array<ConfigurationParamWithUrls | ConfigurationParamWithUrl>;
|
||||
|
||||
@@ -48,3 +48,14 @@ const sortByState = (presenterID?: string) => {
|
||||
return 0;
|
||||
};
|
||||
};
|
||||
|
||||
export function getUserIdFromDM(dmName: string, currentUserId: string) {
|
||||
const ids = dmName.split('__');
|
||||
let otherUserId = '';
|
||||
if (ids[0] === currentUserId) {
|
||||
otherUserId = ids[1];
|
||||
} else {
|
||||
otherUserId = ids[0];
|
||||
}
|
||||
return otherUserId;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ import {encode} from '@msgpack/msgpack/dist';
|
||||
|
||||
export default class WebSocketClient extends EventEmitter {
|
||||
private ws: WebSocket | null;
|
||||
private seqNo = 0;
|
||||
private seqNo = 1;
|
||||
private connID = '';
|
||||
private eventPrefix = `custom_${Calls.PluginId}`;
|
||||
|
||||
constructor(connURL: string) {
|
||||
constructor(connURL: string, authToken: string) {
|
||||
super();
|
||||
this.ws = new WebSocket(connURL);
|
||||
|
||||
this.ws = new WebSocket(connURL, [], {headers: {authorization: `Bearer ${authToken}`}});
|
||||
|
||||
this.ws.onerror = (err) => {
|
||||
this.emit('error', err);
|
||||
@@ -73,6 +74,7 @@ export default class WebSocketClient extends EventEmitter {
|
||||
seq: this.seqNo++,
|
||||
data,
|
||||
};
|
||||
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
if (binary) {
|
||||
this.ws.send(encode(msg));
|
||||
@@ -87,7 +89,7 @@ export default class WebSocketClient extends EventEmitter {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
this.seqNo = 0;
|
||||
this.seqNo = 1;
|
||||
this.connID = '';
|
||||
this.emit('close');
|
||||
}
|
||||
|
||||
@@ -11,35 +11,54 @@
|
||||
"about.teamEditiont1": "Enterprise Edition",
|
||||
"about.title": "Sobre {appTitle}",
|
||||
"announcment_banner.dont_show_again": "Não mostrar novamente",
|
||||
"api.channel.add_guest.added": "{addedUsername} adicionado ao canal como convidado por {username}.",
|
||||
"api.channel.add_member.added": "{addedUsername} foi adicionado ao canal por {username}.",
|
||||
"api.channel.guest_join_channel.post_and_forget": "{username} entrou no canal como convidado.",
|
||||
"apps.error": "Erro: {error}",
|
||||
"apps.error.command.field_missing": "Campos obrigatórios ausentes: `{fieldName}`.",
|
||||
"apps.error.command.same_channel": "Canal repetido para o campo `{fieldName}`: `{option}`.",
|
||||
"apps.error.command.same_option": "Opção repetida para o campo `{fieldName}`: `{option}`.",
|
||||
"apps.error.command.same_user": "Usuário repetido para o campo `{fieldName}`: `{option}`.",
|
||||
"apps.error.command.unknown_channel": "Canal desconhecido para o campo `{fieldName}`: `{option}`.",
|
||||
"apps.error.command.unknown_option": "Opção desconhecida para o campo `{fieldName}`: `{option}`.",
|
||||
"apps.error.command.unknown_user": "Usuário desconhecido para o campo `{fieldName}`: `{opção}`.",
|
||||
"apps.error.form.no_form": "`form` não está definido.",
|
||||
"apps.error.form.no_lookup": "`procurar` não está definido.",
|
||||
"apps.error.form.no_source": "`fonte` não está definida.",
|
||||
"apps.error.form.no_submit": "`enviar ` não está definido",
|
||||
"apps.error.form.refresh": "Ocorreu um erro ao buscar os campos selecionados. Entre em contato com o desenvolvedor do aplicativo. Detalhes: {details}",
|
||||
"apps.error.form.refresh_no_refresh": "Atualização chamada no campo sem atualização.",
|
||||
"apps.error.form.submit.pretext": "Ocorreu um erro ao enviar o modal. Entre em contato com o desenvolvedor do aplicativo. Detalhes: {details}",
|
||||
"apps.error.lookup.error_preparing_request": "Erro ao preparar solicitação de pesquisa: {errorMessage}",
|
||||
"apps.error.malformed_binding": "Esta ligação não está formada corretamente. Entre em contato com o desenvolvedor do aplicativo.",
|
||||
"apps.error.parser": "Erro de análise: {error}",
|
||||
"apps.error.parser.empty_value": "valores vazios não são permitidos",
|
||||
"apps.error.parser.execute_non_leaf": "Você deve selecionar um subcomando.",
|
||||
"apps.error.parser.missing_binding": "Ligações de comando ausentes.",
|
||||
"apps.error.parser.missing_field_value": "Falta o valor do campo.",
|
||||
"apps.error.parser.missing_list_end": "Token de fechamento de lista esperado.",
|
||||
"apps.error.parser.missing_quote": "Aspas duplas correspondentes são esperadas antes do final da entrada.",
|
||||
"apps.error.parser.missing_source": "O formulário não tem envio nem fonte.",
|
||||
"apps.error.parser.missing_submit": "Nenhuma chamada de envio em forma ou vinculação.",
|
||||
"apps.error.parser.missing_tick": "Crase correspondente é esperada antes do final de entrada.",
|
||||
"apps.error.parser.multiple_equal": "Vários sinais `=` não são permitidos.",
|
||||
"apps.error.parser.no_argument_pos_x": "Incapaz de identificar o argumento.",
|
||||
"apps.error.parser.no_bindings": "Sem ligações de comando.",
|
||||
"apps.error.parser.no_form": "Nenhum formulário encontrado.",
|
||||
"apps.error.parser.no_match": "`{command}`: Nenhum comando correspondente encontrado neste espaço de trabalho.",
|
||||
"apps.error.parser.no_slash_start": "O comando deve começar com `/`.",
|
||||
"apps.error.parser.unexpected_character": "Caracter inesperado.",
|
||||
"apps.error.parser.unexpected_comma": "Vírgula inesperada.",
|
||||
"apps.error.parser.unexpected_error": "Erro inesperado.",
|
||||
"apps.error.parser.unexpected_flag": "O comando não aceita o sinalizador `{flagName}`.",
|
||||
"apps.error.parser.unexpected_squared_bracket": "Abertura de lista inesperada.",
|
||||
"apps.error.parser.unexpected_state": "Inacessível: Estado inesperado em matchBinding: `{state}`.",
|
||||
"apps.error.parser.unexpected_whitespace": "Inacessível: espaço em branco inesperado.",
|
||||
"apps.error.responses.form.no_form": "O tipo de resposta é `form`, mas nenhum formulário foi incluído na resposta.",
|
||||
"apps.error.responses.navigate.no_url": "O tipo de resposta é `navigate`, mas nenhuma url foi incluído na resposta.",
|
||||
"apps.error.responses.unexpected_error": "Recebeu um erro inesperado.",
|
||||
"apps.error.responses.unexpected_type": "O tipo de resposta do aplicativo não era esperado. Tipo de resposta: {type}.",
|
||||
"apps.error.responses.unknown_field_error": "Recebeu um erro para um campo desconhecido. Nome do campo: `{field}`. Erro: `{error}`.",
|
||||
"apps.error.responses.unknown_type": "O tipo de resposta do aplicativo não é compatível. Tipo de resposta: {type}.",
|
||||
"apps.error.unknown": "Ocorreu um erro desconhecido.",
|
||||
"apps.suggestion.dynamic.error": "Erro de seleção dinâmica",
|
||||
@@ -48,6 +67,9 @@
|
||||
"apps.suggestion.no_static": "Sem opções correspondentes.",
|
||||
"apps.suggestion.no_suggestion": "Sem sugestões correspondentes.",
|
||||
"archivedChannelMessage": "Você está vendo um **canal arquivado**. Novas mensagens não podem ser publicadas.",
|
||||
"camera_type.photo.option": "Capturar foto",
|
||||
"camera_type.title": "Escolha uma ação",
|
||||
"camera_type.video.option": "Record Video",
|
||||
"center_panel.archived.closeChannel": "Fechar Canal",
|
||||
"channel.channelHasGuests": "Este canal tem convidados",
|
||||
"channel.hasGuests": "Este grupo de mensagem tem convidados",
|
||||
@@ -67,6 +89,7 @@
|
||||
"channel_loader.someone": "Alguém",
|
||||
"channel_members_modal.remove": "Remover",
|
||||
"channel_modal.cancel": "Cancelar",
|
||||
"channel_modal.channelType": "Modelo",
|
||||
"channel_modal.descriptionHelp": "Descreva como este canal deve ser utilizado.",
|
||||
"channel_modal.header": "Cabeçalho",
|
||||
"channel_modal.headerEx": "Ex.: \"[Título do Link](http://example.com)\"",
|
||||
@@ -76,6 +99,8 @@
|
||||
"channel_modal.optional": "(opcional)",
|
||||
"channel_modal.purpose": "Propósito",
|
||||
"channel_modal.purposeEx": "Ex.: \"Um canal para arquivar bugs e melhorias\"",
|
||||
"channel_modal.type.private": "Canal privado",
|
||||
"channel_modal.type.public": "Canal público",
|
||||
"channel_notifications.ignoreChannelMentions.settings": "Ignorar @channel, @here, @all",
|
||||
"channel_notifications.muteChannel.settings": "Silenciar o canal",
|
||||
"channel_notifications.preference.all_activity": "Para todas as atividades",
|
||||
@@ -120,21 +145,76 @@
|
||||
"create_comment.addComment": "Adicionar um comentário...",
|
||||
"create_post.deactivated": "Você está vendo um canal arquivado com um usuário inativo.",
|
||||
"create_post.write": "Escrever para {channelDisplayName}",
|
||||
"custom_status.expiry.at": "no",
|
||||
"custom_status.expiry.until": "Até",
|
||||
"custom_status.expiry_dropdown.custom": "Personalizado",
|
||||
"custom_status.expiry_dropdown.date_and_time": "Data e hora",
|
||||
"custom_status.expiry_dropdown.dont_clear": "Não limpe",
|
||||
"custom_status.expiry_dropdown.four_hours": "4 horas",
|
||||
"custom_status.expiry_dropdown.one_hour": "1 hora",
|
||||
"custom_status.expiry_dropdown.thirty_minutes": "30 minutos",
|
||||
"custom_status.expiry_dropdown.this_week": "Essa semana",
|
||||
"custom_status.expiry_dropdown.today": "Hoje",
|
||||
"custom_status.expiry_time.today": "Hoje",
|
||||
"custom_status.expiry_time.tomorrow": "Amanhã",
|
||||
"custom_status.failure_message": "Falha ao atualizar o status. Tente novamente",
|
||||
"custom_status.set_status": "Definir um status",
|
||||
"custom_status.suggestions.in_a_meeting": "Em uma reunião",
|
||||
"custom_status.suggestions.on_a_vacation": "De férias",
|
||||
"custom_status.suggestions.out_for_lunch": "Almoçando",
|
||||
"custom_status.suggestions.out_sick": "Doente",
|
||||
"custom_status.suggestions.recent_title": "RECENTE",
|
||||
"custom_status.suggestions.title": "SUGESTÕES",
|
||||
"custom_status.suggestions.working_from_home": "Trabalhando em casa",
|
||||
"date_separator.today": "Hoje",
|
||||
"date_separator.yesterday": "Ontem",
|
||||
"edit_post.editPost": "Editar o post...",
|
||||
"edit_post.save": "Salvar",
|
||||
"emoji_picker.activities": "Atividades",
|
||||
"emoji_picker.animals-nature": "Animais e Natureza",
|
||||
"emoji_picker.custom": "Personalizado",
|
||||
"emoji_picker.flags": "Bandeiras",
|
||||
"emoji_picker.food-drink": "Comida e bebida",
|
||||
"emoji_picker.objects": "Objetos",
|
||||
"emoji_picker.people-body": "Pessoas e Corpo",
|
||||
"emoji_picker.recent": "Recente",
|
||||
"emoji_picker.searchResults": "Resultados da Pesquisa",
|
||||
"emoji_picker.smileys-emotion": "Sorrisos e Emoções",
|
||||
"emoji_picker.symbols": "Símbolos",
|
||||
"emoji_picker.travel-places": "Viagens e lugares",
|
||||
"emoji_skin.dark_skin_tone": "tom de pele escuro",
|
||||
"emoji_skin.default": "tom de pele padrão",
|
||||
"emoji_skin.light_skin_tone": "tom de pele claro",
|
||||
"emoji_skin.medium_dark_skin_tone": "pele morena escura",
|
||||
"emoji_skin.medium_light_skin_tone": "tom de pele claro médio",
|
||||
"emoji_skin.medium_skin_tone": "tom de pele médio",
|
||||
"file_upload.fileAbove": "Arquivos devem ter menos de {max}",
|
||||
"friendly_date.daysAgo": "{count} {count, plural, one {day} other {days}} atrás",
|
||||
"friendly_date.hoursAgo": "{count} {count, plural, one {hour} other {hours}} atrás",
|
||||
"friendly_date.minsAgo": "{count} {count, plural, one {min} other {mins}} atrás",
|
||||
"friendly_date.monthsAgo": "{count} {count, plural, one {month} other {months}} atrás",
|
||||
"friendly_date.now": "Agora",
|
||||
"friendly_date.yearsAgo": "{count} {count, plural, one {year} other {years}} atrás",
|
||||
"friendly_date.yesterday": "Ontem",
|
||||
"gallery.download_file": "Baixar arquivo",
|
||||
"gallery.footer.channel_name": "Compartilhado em {channelName}",
|
||||
"gallery.open_file": "Abrir arquivo",
|
||||
"gallery.unsuppored": "A visualização não é compatível com este tipo de arquivo",
|
||||
"get_post_link_modal.title": "Copiar Link",
|
||||
"global_threads.allThreads": "Todos os seus tópicos",
|
||||
"global_threads.emptyThreads.message": "Todos os tópicos nos quais você é mencionado ou dos quais participou serão exibidos aqui junto com todos os tópicos que você seguiu.",
|
||||
"global_threads.emptyThreads.title": "Nenhum tópico seguido ainda",
|
||||
"global_threads.emptyUnreads.message": "Parece que você está todo preso.",
|
||||
"global_threads.emptyUnreads.title": "Nenhum tópico não lido",
|
||||
"global_threads.markAllRead.cancel": "Cancelar",
|
||||
"global_threads.markAllRead.markRead": "Marcar como lido",
|
||||
"global_threads.markAllRead.message": "Isso limpará qualquer status não lido de todos os seus tópicos mostrados aqui",
|
||||
"global_threads.markAllRead.title": "Tem certeza de que deseja marcar todos os tópicos como lidos?",
|
||||
"global_threads.options.mark_as_read": "Marcar como Lido",
|
||||
"global_threads.options.open_in_channel": "Abrir no canal",
|
||||
"global_threads.options.title": "AÇÕES DE TÓPICO",
|
||||
"global_threads.options.unfollow": "Deixar de seguir o tópico",
|
||||
"global_threads.unreads": "Não lidos",
|
||||
"integrations.add": "Adicionar",
|
||||
"intro_messages.anyMember": " Qualquer membro pode participar e ler este canal.",
|
||||
"intro_messages.beginning": "Início do {name}",
|
||||
@@ -208,7 +288,7 @@
|
||||
"mobile.camera_photo_permission_denied_title": "{applicationName} gostaria de acessar sua camera",
|
||||
"mobile.camera_video_permission_denied_description": "Faça vídeos e envie-os para a sua instância do Mattermost ou salve-os no seu dispositivo. Abra Configurações para conceder ao Mattermost acesso de leitura e gravação à sua câmera.",
|
||||
"mobile.camera_video_permission_denied_title": "{applicationName} gostaria de acessar sua camera",
|
||||
"mobile.channel_drawer.search": "Ir para...",
|
||||
"mobile.channel_drawer.search": "Encontrar canal",
|
||||
"mobile.channel_info.alertMessageConvertChannel": "Quando você converte {displayName} em um canal privado, o histórico e os membros são preservados. Os arquivos compartilhados publicamente permanecem acessíveis a qualquer pessoa com o link. O ingresso a um canal privado é apenas por convite.\n\nA mudança é permanente e não pode ser desfeita.\n\nTem certeza de que deseja converter {displayName} em um canal privado?",
|
||||
"mobile.channel_info.alertMessageDeleteChannel": "Você tem certeza que quer arquivar o {term} {name}?",
|
||||
"mobile.channel_info.alertMessageLeaveChannel": "Você tem certeza que quer deixar o {term} {name}?",
|
||||
@@ -252,6 +332,9 @@
|
||||
"mobile.create_channel.public": "Novo Canal Público",
|
||||
"mobile.create_post.read_only": "Este canal é de apenas leitura",
|
||||
"mobile.custom_list.no_results": "Nenhum Resultado",
|
||||
"mobile.custom_status.choose_emoji": "Escolha um emoji",
|
||||
"mobile.custom_status.clear_after": "Limpar depois",
|
||||
"mobile.custom_status.modal_confirm": "Feito",
|
||||
"mobile.display_settings.sidebar": "Barra lateral",
|
||||
"mobile.display_settings.theme": "Tema",
|
||||
"mobile.document_preview.failed_description": "Um erro aconteceu durante a abertura do documento. Por favor verifique que você tem o visualizador para {fileType} instalado e tente novamente.\n",
|
||||
@@ -288,7 +371,7 @@
|
||||
"mobile.file_upload.disabled": "Os uploads de arquivos do celular estão desabilitados. Entre em contato com o Administrador do Sistema para obter mais detalhes.",
|
||||
"mobile.file_upload.disabled2": "Os envios de arquivos do celular estão desabilitados.",
|
||||
"mobile.file_upload.library": "Biblioteca de Fotos",
|
||||
"mobile.file_upload.max_warning": "Upload limitado ao máximo de 5 arquivos.",
|
||||
"mobile.file_upload.max_warning": "Uploads limitados a {count} máximo de arquivos.",
|
||||
"mobile.file_upload.unsupportedMimeType": "Somente imagens em BMP, JPG ou PNG podem ser usadas como imagem do perfil.",
|
||||
"mobile.file_upload.video": "Galeria de Videos",
|
||||
"mobile.files_paste.error_description": "Ocorreu um erro enquanto colava o arquivo. Por favor tente novamente.",
|
||||
@@ -296,9 +379,11 @@
|
||||
"mobile.files_paste.error_title": "Colar falhou",
|
||||
"mobile.flagged_posts.empty_description": "As mensagens salvas são visíveis apenas para você. Marque mensagens para acompanhamento ou salve algo para mais tarde mantendo uma mensagem pressionada e escolhendo Salvar no menu.",
|
||||
"mobile.flagged_posts.empty_title": "Nenhuma mensagem salva",
|
||||
"mobile.forms.select.done": "Feito",
|
||||
"mobile.gallery.title": "{index} de {total}",
|
||||
"mobile.general.error.title": "Erro",
|
||||
"mobile.help.title": "Ajuda",
|
||||
"mobile.interactive_dialog.defaultSubmit": "Enviar",
|
||||
"mobile.intro_messages.DM": "Este é o início do seu histórico de mensagens diretas com {teammate}. Mensagens diretas e arquivos compartilhados aqui não são mostrados para pessoas de fora desta área.",
|
||||
"mobile.intro_messages.default_message": "Este é o primeiro canal da equipe veja quando eles se registrarem - use para postar atualizações que todos devem saber.",
|
||||
"mobile.intro_messages.default_welcome": "Bem-vindo ao {name}!",
|
||||
@@ -330,6 +415,8 @@
|
||||
"mobile.message_length.message": "Sua mensagem atual é muito grande. Contagem corrente de caracters: {max}/{count}",
|
||||
"mobile.message_length.message_split_left": "Mensagem excede o limite de caracteres",
|
||||
"mobile.message_length.title": "Tamanho da Mensagem",
|
||||
"mobile.microphone_permission_denied_description": "Para participar desta chamada, abra Configurações para conceder ao Mattermost acesso ao seu microfone.",
|
||||
"mobile.microphone_permission_denied_title": "{applicationName} gostaria de acessar seu microfone",
|
||||
"mobile.more_dms.add_more": "Você pode adicionar mais {remaining, number} usuários",
|
||||
"mobile.more_dms.cannot_add_more": "Você não pode adicionar mais usuários",
|
||||
"mobile.more_dms.one_more": "Você não pode adicionar mais 1 usuário",
|
||||
@@ -349,6 +436,7 @@
|
||||
"mobile.notification_settings.email": "E-mail",
|
||||
"mobile.notification_settings.email.send": "ENVIAR NOTIFICAÇÕES POR EMAIL",
|
||||
"mobile.notification_settings.email_title": "Notificações por Email",
|
||||
"mobile.notification_settings.mentions": "Menções",
|
||||
"mobile.notification_settings.mentions.channelWide": "Menções de todo canal",
|
||||
"mobile.notification_settings.mentions.reply_title": "Enviar notificações de resposta para",
|
||||
"mobile.notification_settings.mentions.sensitiveName": "Seu primeiro nome em maiúsculas e minúsculas",
|
||||
@@ -359,6 +447,10 @@
|
||||
"mobile.notification_settings.modal_cancel": "CANCELAR",
|
||||
"mobile.notification_settings.modal_save": "SALVAR",
|
||||
"mobile.notification_settings.ooo_auto_responder": "Respostas Automáticas para Mensagens Diretas",
|
||||
"mobile.notification_settings.push_threads.description": "Notificar-me sobre todas as respostas aos tópicos que estou seguindo",
|
||||
"mobile.notification_settings.push_threads.info": "Quando ativado, qualquer resposta a um tópico que você está seguindo enviará uma notificação por push móvel.",
|
||||
"mobile.notification_settings.push_threads.title": "NOTIFICAÇÕES DE RESPOSTA DE TÓPICOS",
|
||||
"mobile.notification_settings.push_threads.title_android": "Notificações de resposta do tópico",
|
||||
"mobile.notification_settings.save_failed_description": "As configurações de notificação não conseguiram salvar devido a um problema de conexão. Por favor tente novamente.",
|
||||
"mobile.notification_settings.save_failed_title": "Problema de conexão",
|
||||
"mobile.notification_settings_mentions.keywords": "Palavras-chave",
|
||||
@@ -391,6 +483,8 @@
|
||||
"mobile.open_dm.error": "Não foi possível entrar nas mensagens diretas com {displayName}. Por favor verifique a sua conexão e tente novamente.",
|
||||
"mobile.open_gm.error": "Não foi possível abrir uma mensagem em grupo com esses usuários. Por favor verifique sua conexão e tente novamente.",
|
||||
"mobile.open_unknown_channel.error": "Não é possível entrar no canal. Por favor, limpe o cache e tente novamente.",
|
||||
"mobile.participants.header": "PARTICIPANTES DO TÓPICO",
|
||||
"mobile.participants.you": "(você)",
|
||||
"mobile.permission_denied_dismiss": "Não Permitir",
|
||||
"mobile.permission_denied_retry": "Configurações",
|
||||
"mobile.pinned_posts.empty_description": "Fixe mensagens importantes que sejam visíveis para todo o canal. Mantenha uma mensagem pressionada e escolha Fixar no canal para salvá-la aqui.",
|
||||
@@ -418,6 +512,8 @@
|
||||
"mobile.post_textbox.entire_channel.message": "Usando @all ou @channel você está enviando notificações para {totalMembers, number} {totalMembers, plural, one {pessoa} other {pessoas}}. Você tem certeza que quer fazer isso?",
|
||||
"mobile.post_textbox.entire_channel.message.with_timezones": "Usando @all ou @channel você está enviando notificações para {totalMembers, number} {totalMembers, plural, one {pessoa} other {pessoas}} em {timezones, number} {timezones, plural, one {fuso horário} other {fusos horários}}. Você tem certeza que quer fazer isso?",
|
||||
"mobile.post_textbox.entire_channel.title": "Confirmar o envio de notificações para o canal todo",
|
||||
"mobile.post_textbox.entire_channel_here.message": "Ao usar @here você está prestes a enviar notificações para {totalMembers, number} {totalMembers, plural, one {person} other {people}}. Você tem certeza de que quer fazer isso?",
|
||||
"mobile.post_textbox.entire_channel_here.message.with_timezones": "Ao usar @here, você está prestes a enviar notificações para {totalMembers, number} {totalMembers, plural, one {person} other {people}} em {timezones, number} {timezones, plural, one {timezone} other {timezones} }. Você tem certeza de que quer fazer isso?",
|
||||
"mobile.post_textbox.groups.title": "Confirmar envio de notificações para grupos",
|
||||
"mobile.post_textbox.multi_group.message.with_timezones": "Ao usar {mentions} e {finalMention}, você está prestes a enviar notificações para pelo menos {totalMembers} pessoas em {timezones, number} {timezones, plural, one {fuso horário} other {fusos horários}}. Você tem certeza de que quer fazer isso?",
|
||||
"mobile.post_textbox.multi_group.message.without_timezones": "Ao usar {mentions} e {finalMention}, você está prestes a enviar notificações para pelo menos {totalMembers} pessoas. Você tem certeza de que quer fazer isso?",
|
||||
@@ -448,7 +544,7 @@
|
||||
"mobile.reset_status.alert_cancel": "Cancelar",
|
||||
"mobile.reset_status.alert_ok": "Ok",
|
||||
"mobile.reset_status.title_ooo": "Desativar \"Fora Do Escritório\"?",
|
||||
"mobile.retry_message": "Atualização de mensagens falharam. Puxe para tentar novamente.",
|
||||
"mobile.retry_message": "Falha ao buscar mensagens. Toque aqui para tentar novamente.",
|
||||
"mobile.routes.channelInfo": "Informações",
|
||||
"mobile.routes.channelInfo.createdBy": "Criado por {creator} em ",
|
||||
"mobile.routes.channelInfo.delete_channel": "Arquivar Canal",
|
||||
@@ -460,6 +556,7 @@
|
||||
"mobile.routes.channel_members.action_message_confirm": "Você tem certeza que quer remover o membro selecionado do canal?",
|
||||
"mobile.routes.code": "{language} Código",
|
||||
"mobile.routes.code.noLanguage": "Código",
|
||||
"mobile.routes.custom_status": "Definir um status",
|
||||
"mobile.routes.edit_profile": "Editar Perfil",
|
||||
"mobile.routes.login": "Login",
|
||||
"mobile.routes.loginOptions": "Selecionador de Login",
|
||||
@@ -470,10 +567,13 @@
|
||||
"mobile.routes.sso": "Single Sign-On",
|
||||
"mobile.routes.table": "Tabela",
|
||||
"mobile.routes.thread": "Tópico {channelName}",
|
||||
"mobile.routes.thread_crt": "Tópico",
|
||||
"mobile.routes.thread_crt.in": "em {channelName}",
|
||||
"mobile.routes.thread_dm": "Tópico Mensagem Direta",
|
||||
"mobile.routes.user_profile": "Perfil",
|
||||
"mobile.routes.user_profile.edit": "Editar",
|
||||
"mobile.routes.user_profile.local_time": "HORA LOCAL",
|
||||
"mobile.routes.user_profile.organization": "ORGANIZAÇÃO",
|
||||
"mobile.routes.user_profile.send_message": "Enviar Mensagem",
|
||||
"mobile.search.after_modifier_description": "encontrar publicações após uma data específica",
|
||||
"mobile.search.before_modifier_description": "encontrar publicações antes de uma data específica",
|
||||
@@ -543,6 +643,7 @@
|
||||
"mobile.user_list.deactivated": "Desativado",
|
||||
"mobile.user_removed.message": "Você foi removido do canal.",
|
||||
"mobile.user_removed.title": "Removido de {channelName}",
|
||||
"mobile.video_playback.download": "Baixar video",
|
||||
"mobile.video_playback.failed_description": "Ocorreu um erro ao tentar reproduzir o video.\n",
|
||||
"mobile.video_playback.failed_title": "Erro ao realizar o playback do video",
|
||||
"mobile.youtube_playback_error.description": "Ocorreu um erro ao tentar executar um vídeo do YouTube.\nDetalhes: {details}",
|
||||
@@ -556,8 +657,10 @@
|
||||
"more_channels.dropdownTitle": "Exibir",
|
||||
"more_channels.noMore": "Não há mais canais para participar",
|
||||
"more_channels.publicChannels": "Canais Públicos",
|
||||
"more_channels.sharedChannels": "Canais compartilhados",
|
||||
"more_channels.showArchivedChannels": "Exibir: Canais Arquivados",
|
||||
"more_channels.showPublicChannels": "Exibir: Canais Públicos",
|
||||
"more_channels.showSharedChannels": "Mostrar: canais compartilhados",
|
||||
"more_channels.title": "Mais Canais",
|
||||
"msg_typing.areTyping": "{users} e {last} estão digitando...",
|
||||
"msg_typing.isTyping": "{user} está digitando...",
|
||||
@@ -606,6 +709,7 @@
|
||||
"sidebar.channels": "CANAIS PÚBLICOS",
|
||||
"sidebar.direct": "MENSAGENS DIRETAS",
|
||||
"sidebar.favorite": "CANAIS FAVORITOS",
|
||||
"sidebar.favorites": "Favoritos",
|
||||
"sidebar.pg": "CANAIS PRIVADOS",
|
||||
"sidebar.types.recent": "ATIVIDADE RECENTE",
|
||||
"sidebar.unreads": "Mais não lidos",
|
||||
@@ -628,12 +732,22 @@
|
||||
"suggestion.mention.morechannels": "Outros Canais",
|
||||
"suggestion.mention.nonmembers": "Não no Canal",
|
||||
"suggestion.mention.special": "Menções Especiais",
|
||||
"suggestion.mention.you": "(você)",
|
||||
"suggestion.mention.you": " (você)",
|
||||
"suggestion.search.direct": "Mensagens Diretas",
|
||||
"suggestion.search.private": "Canais Privados",
|
||||
"suggestion.search.public": "Canais Públicos",
|
||||
"terms_of_service.agreeButton": "Eu Concordo",
|
||||
"terms_of_service.api_error": "Não é possível concluir o pedido. Se esse problema persistir, entre em contato com o Administrador do Sistema.",
|
||||
"threads": "Tópicos",
|
||||
"threads.deleted": "Mensagem original excluída",
|
||||
"threads.follow": "Seguir",
|
||||
"threads.followMessage": "Seguir mensagem",
|
||||
"threads.followThread": "Seguir Tópico",
|
||||
"threads.following": "Seguindo",
|
||||
"threads.newReplies": "{count} novo {count, plural, one {reply} other {replies}}",
|
||||
"threads.replies": "{count} {count, plural, one {reply} other {replies}}",
|
||||
"threads.unfollowMessage": "Deixar de seguir a mensagem",
|
||||
"threads.unfollowThread": "Deixar de seguir o tópico",
|
||||
"user.settings.display.clockDisplay": "Exibição do Relógio",
|
||||
"user.settings.display.custom_theme": "Tema Personalizado",
|
||||
"user.settings.display.militaryClock": "Relógio de 24 horas (exemplo: 16:00)",
|
||||
@@ -652,6 +766,7 @@
|
||||
"user.settings.general.lastName": "Último Nome",
|
||||
"user.settings.general.nickname": "Apelido",
|
||||
"user.settings.general.position": "Cargo",
|
||||
"user.settings.general.status": "Status",
|
||||
"user.settings.general.username": "Usuário",
|
||||
"user.settings.modal.display": "Exibição",
|
||||
"user.settings.modal.notifications": "Notificações",
|
||||
@@ -663,6 +778,10 @@
|
||||
"user.settings.notifications.email.never": "Nunca",
|
||||
"user.settings.notifications.email.send": "Enviar notificações por email",
|
||||
"user.settings.notifications.emailInfo": "As notificações por email são enviadas para menções e mensagens diretas quando você estiver offline ou ausente por mais de 5 minutos.",
|
||||
"user.settings.notifications.email_threads.description": "Notifique-me sobre todas as respostas aos tópicos que estou seguindo.",
|
||||
"user.settings.notifications.email_threads.info": "Quando ativado, qualquer resposta a uma conversa que você está seguindo enviará uma notificação por e-mail.",
|
||||
"user.settings.notifications.email_threads.title": "NOTIFICAÇÕES DE RESPOSTA DE TÓPICOS",
|
||||
"user.settings.notifications.email_threads.title_android": "Notificações de resposta do tópico",
|
||||
"user.settings.notifications.never": "Nunca",
|
||||
"user.settings.notifications.onlyMentions": "Somente para menções e mensagens diretas",
|
||||
"user.settings.push_notification.away": "Ausente ou desconectado",
|
||||
|
||||
@@ -343,7 +343,7 @@
|
||||
"mobile.downloader.disabled_title": "Nedladdning inaktiverat",
|
||||
"mobile.downloader.failed_description": "Ett fel inträffade när filen skulle laddas ner. Kontrollera din anslutning till internet och försök igen.\n",
|
||||
"mobile.downloader.failed_title": "Nedladdning misslyckades",
|
||||
"mobile.drawer.teamsTitle": "Grupper",
|
||||
"mobile.drawer.teamsTitle": "Team",
|
||||
"mobile.edit_channel": "Spara",
|
||||
"mobile.edit_post.title": "Editerar meddelande",
|
||||
"mobile.edit_profile.remove_profile_photo": "Ta bort foto",
|
||||
@@ -561,8 +561,8 @@
|
||||
"mobile.routes.login": "Logga in",
|
||||
"mobile.routes.loginOptions": "Välj inloggningsmetod",
|
||||
"mobile.routes.mfa": "Flerfaktorsautentisering",
|
||||
"mobile.routes.selectChannel": "Ändra kanalnamn",
|
||||
"mobile.routes.selectTeam": "Välj grupp",
|
||||
"mobile.routes.selectChannel": "Välj kanal",
|
||||
"mobile.routes.selectTeam": "Välj team",
|
||||
"mobile.routes.settings": "Inställningar",
|
||||
"mobile.routes.sso": "Auto-inloggning",
|
||||
"mobile.routes.table": "Tabell",
|
||||
@@ -586,7 +586,7 @@
|
||||
"mobile.search.on_modifier_description": "för att hitta inlägg före ett visst datum",
|
||||
"mobile.search.recent_title": "Senaste sökningar",
|
||||
"mobile.select_team.guest_cant_join_team": "Ditt gästkonto finns inte i något team eller kanal. Kontakta en administratör.",
|
||||
"mobile.select_team.join_open": "Öppna grupper du kan gå med i",
|
||||
"mobile.select_team.join_open": "Öppna team du kan gå med i",
|
||||
"mobile.select_team.no_teams": "Det finns inget team tillgängligt för dig att ansluta till.",
|
||||
"mobile.server_link.error.text": "Länken kunde inte hittas på servern.",
|
||||
"mobile.server_link.error.title": "Länk-fel",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"channel_notifications.preference.global_default": "Genel varsayılan ({notifyLevel})",
|
||||
"channel_notifications.preference.header": "Bildirimler gönderimi",
|
||||
"channel_notifications.preference.never": "Asla",
|
||||
"channel_notifications.preference.only_mentions": "Yalnız anmalar ve doğrudan iletiler",
|
||||
"channel_notifications.preference.only_mentions": "Yalnızca anmalar ve doğrudan iletiler",
|
||||
"channel_notifications.preference.save_error": "Bildirim ayarı kaydedilemedi. Lütfen bağlantınızı denetleyip yeniden deneyin.",
|
||||
"combined_system_message.added_to_channel.many_expanded": "{users} ve {lastUser} {actor} tarafından **kanala eklendi**.",
|
||||
"combined_system_message.added_to_channel.one": "{firstUser} {actor} tarafından **kanala eklendi**.",
|
||||
@@ -202,8 +202,8 @@
|
||||
"gallery.unsuppored": "Bu dosya türü ön izlenemiyor",
|
||||
"get_post_link_modal.title": "Bağlantıyı kopyala",
|
||||
"global_threads.allThreads": "Tüm konularınız",
|
||||
"global_threads.emptyThreads.message": "Bahsettiğiniz veya katıldığınız tüm konular, takip ettiğiniz tüm konularla birlikte burada görüntülenir.",
|
||||
"global_threads.emptyThreads.title": "Takip edilen bir konu yok",
|
||||
"global_threads.emptyThreads.message": "Bahsettiğiniz veya katıldığınız tüm konular, izlediğiniz tüm konularla birlikte burada görüntülenir.",
|
||||
"global_threads.emptyThreads.title": "İzlenen bir konu yok",
|
||||
"global_threads.emptyUnreads.message": "Tüm konulara bakmışsınız gibi görünüyor.",
|
||||
"global_threads.emptyUnreads.title": "Okunmamış bir konu yok",
|
||||
"global_threads.markAllRead.cancel": "İptal",
|
||||
@@ -222,7 +222,7 @@
|
||||
"intro_messages.creatorPrivate": "{name} özel kanalı, {creator} tarafından {date} tarihinde başlatılmış.",
|
||||
"intro_messages.group_message": "Bu takım arkadaşlarınız ile doğrudan ileti geçmişinizin başlangıcı. Bu bölüm dışındaki kişiler burada paylaşılan doğrudan ileti ve dosyaları göremez.",
|
||||
"intro_messages.noCreator": "{date} tarihinde oluşturulmuş {name} kanalının başlangıcı.",
|
||||
"intro_messages.onlyInvited": " Bu özel kanalı yalnız çağrılmış üyeler görüntüleyebilir.",
|
||||
"intro_messages.onlyInvited": " Bu özel kanalı yalnızca çağrılmış üyeler görüntüleyebilir.",
|
||||
"last_users_message.added_to_channel.type": "{actor} tarafından **kanala eklendiniz**.",
|
||||
"last_users_message.added_to_team.type": "{actor} tarafından **takıma eklendiniz**.",
|
||||
"last_users_message.first": "{firstUser} ve ",
|
||||
@@ -289,7 +289,7 @@
|
||||
"mobile.camera_video_permission_denied_description": "Görüntü çekin ve Mattermost kopyanıza yükleyin ya da aygıtınıza kaydedin. Mattermost uygulamasına kameranızı okuma ve yazma izni vermek için ayarları açın.",
|
||||
"mobile.camera_video_permission_denied_title": "{applicationName} kameranıza erişmek istiyor",
|
||||
"mobile.channel_drawer.search": "Kanal ara",
|
||||
"mobile.channel_info.alertMessageConvertChannel": "{displayName} özel bir kanala dönüştürüldüğünde, geçmiş iletiler ve üyelikler korunur. Herkese açık olarak paylaşılmış dosyalara bağlantıya sahip olan herkes erişmeye devam edebilir. Özel kanallara yalnız çağrı ile üye olunabilir. \n \nBu değişiklik kalıcıdır ve geri alınamaz.\n\n{displayName} kanalını özel kanala dönüştürmek istediğinize emin misiniz?",
|
||||
"mobile.channel_info.alertMessageConvertChannel": "{displayName} özel bir kanala dönüştürüldüğünde, geçmiş iletiler ve üyelikler korunur. Herkese açık olarak paylaşılmış dosyalara bağlantıya sahip olan herkes erişmeye devam edebilir. Özel kanallara yalnızca çağrı ile üye olunabilir. \n \nBu değişiklik kalıcıdır ve geri alınamaz.\n\n{displayName} kanalını özel kanala dönüştürmek istediğinize emin misiniz?",
|
||||
"mobile.channel_info.alertMessageDeleteChannel": "{term} {name} kanalını arşivlemek istediğinize emin misiniz?",
|
||||
"mobile.channel_info.alertMessageLeaveChannel": "{term} {name} kanalından ayrılmak istediğinize emin misiniz?",
|
||||
"mobile.channel_info.alertMessageUnarchiveChannel": "{term} {name} kanalını arşivden çıkarmak istediğinize emin misiniz?",
|
||||
@@ -372,12 +372,12 @@
|
||||
"mobile.file_upload.disabled2": "Mobil uygulamadan dosya yüklemek devre dışı bırakılmış.",
|
||||
"mobile.file_upload.library": "Fotoğraf kitaplığı",
|
||||
"mobile.file_upload.max_warning": "En fazla {count} dosya yüklenebilir.",
|
||||
"mobile.file_upload.unsupportedMimeType": "Profil görseli olarak yalnız BMP, JPG ya da PNG dosyaları kullanılabilir.",
|
||||
"mobile.file_upload.unsupportedMimeType": "Profil görseli olarak yalnızca BMP, JPG ya da PNG dosyaları kullanılabilir.",
|
||||
"mobile.file_upload.video": "Görüntü kitaplığı",
|
||||
"mobile.files_paste.error_description": "Dosyalar yapıştırılırken bir sorun çıktı. Lütfen yeniden deneyin.",
|
||||
"mobile.files_paste.error_dismiss": "Yok say",
|
||||
"mobile.files_paste.error_title": "Yapıştırılamadı",
|
||||
"mobile.flagged_posts.empty_description": "Kaydedilmiş iletileri yalnız siz görebilirsiniz. İletileri takip etmek için işaretleyin ya da bir şeyi kaydetmek için ileti üzerine uzun basıp menüden Kaydet komutunu seçin.",
|
||||
"mobile.flagged_posts.empty_description": "Kaydedilmiş iletileri yalnızca siz görebilirsiniz. İletileri izlemek için işaretleyin ya da bir şeyi kaydetmek için ileti üzerine uzun basıp menüden Kaydet komutunu seçin.",
|
||||
"mobile.flagged_posts.empty_title": "Kaydedilmiş bir ileti yok",
|
||||
"mobile.forms.select.done": "Tamam",
|
||||
"mobile.gallery.title": "{index} / {total}",
|
||||
@@ -447,8 +447,8 @@
|
||||
"mobile.notification_settings.modal_cancel": "İPTAL",
|
||||
"mobile.notification_settings.modal_save": "KAYDET",
|
||||
"mobile.notification_settings.ooo_auto_responder": "Otomatik doğrudan ileti yanıtları",
|
||||
"mobile.notification_settings.push_threads.description": "Takip ettiğim konulara yazılan tüm yanıtlar ile ilgili bildirim gönderilsin",
|
||||
"mobile.notification_settings.push_threads.info": "Bu seçenek etkinleştirildiğinde, takip ettiğiniz bir konuya yazılan yanıtlar mobil anında bildirim olarak iletilir.",
|
||||
"mobile.notification_settings.push_threads.description": "İzlediğim konulara yazılan tüm yanıtlar ile ilgili bildirim gönderilsin",
|
||||
"mobile.notification_settings.push_threads.info": "Bu seçenek etkinleştirildiğinde, izlediğiniz bir konuya yazılan yanıtlar mobil uygulamada anında bildirim olarak iletilir.",
|
||||
"mobile.notification_settings.push_threads.title": "KONU YANITI BİLDİRİMLERİ",
|
||||
"mobile.notification_settings.push_threads.title_android": "Konu yanıtı bildirimleri",
|
||||
"mobile.notification_settings.save_failed_description": "Bir bağlantı sorunu nedeniyle bildirim ayarları kaydedilemedi. Lütfen yeniden deneyin.",
|
||||
@@ -740,10 +740,10 @@
|
||||
"terms_of_service.api_error": "İstek yerine getirilemedi. Sorun sürerse sistem yöneticiniz ile görüşün.",
|
||||
"threads": "Konular",
|
||||
"threads.deleted": "Özgün ileti silindi",
|
||||
"threads.follow": "Takip et",
|
||||
"threads.followMessage": "İletiyi takip et",
|
||||
"threads.followThread": "Konuyu takip et",
|
||||
"threads.following": "Takip ediliyor",
|
||||
"threads.follow": "İzle",
|
||||
"threads.followMessage": "İletiyi izle",
|
||||
"threads.followThread": "Konuyu izle",
|
||||
"threads.following": "İzleniyor",
|
||||
"threads.newReplies": "{count} yeni {count, plural, one {yanıt} other {yanıt}}",
|
||||
"threads.replies": "{count} {count, plural, one {yanıt} other {yanıt}}",
|
||||
"threads.unfollowMessage": "İletiyi takibi bırak",
|
||||
@@ -778,12 +778,12 @@
|
||||
"user.settings.notifications.email.never": "Asla",
|
||||
"user.settings.notifications.email.send": "E-posta bildirimleri gönderilsin",
|
||||
"user.settings.notifications.emailInfo": "5 dakikadan uzun süre uzak ya da çevrimdışı olduğunuzda anma ve doğrudan iletiler için e-posta bildirimi gönderilir.",
|
||||
"user.settings.notifications.email_threads.description": "Takip ettiğim konulara yazılan tüm yanıtlar ile ilgili bildirim gönderilsin.",
|
||||
"user.settings.notifications.email_threads.info": "Bu seçenek etkinleştirildiğinde, takip ettiğiniz bir konuya yazılan yanıtlar e-posta bildirimi olarak iletilir.",
|
||||
"user.settings.notifications.email_threads.description": "İzlediğim konulara yazılan tüm yanıtlar ile ilgili bildirim gönderilsin.",
|
||||
"user.settings.notifications.email_threads.info": "Bu seçenek etkinleştirildiğinde, izlediğiniz bir konuya yazılan yanıtlar e-posta bildirimi olarak iletilir.",
|
||||
"user.settings.notifications.email_threads.title": "KONU YANITI BİLDİRİMLERİ",
|
||||
"user.settings.notifications.email_threads.title_android": "Konu yanıtı bildirimleri",
|
||||
"user.settings.notifications.never": "Asla",
|
||||
"user.settings.notifications.onlyMentions": "Yalnız anmalar ve doğrudan iletiler için",
|
||||
"user.settings.notifications.onlyMentions": "Yalnızca anmalar ve doğrudan iletiler için",
|
||||
"user.settings.push_notification.away": "Uzakta ya da çevrimdışı",
|
||||
"user.settings.push_notification.disabled": "Anında bildirimler devre dışı bırakılmış",
|
||||
"user.settings.push_notification.disabled_long": "Anında bildirimler sistem yöneticiniz tarafından devre dışı bırakılmış.",
|
||||
|
||||
45
build/notice-file/Readme.md
Normal file
45
build/notice-file/Readme.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Notice.txt File Configuration
|
||||
|
||||
We are automatically generating Notice.txt by using first-level dependencies of the project. The related pipeline uses `config.yaml` stored in this folder.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Notice.txt will be always generated from first-level dependencies scanned from package.json files. For some cases it may be useful to define
|
||||
the dependency to add its licence to the Notice.txt even if it is not a first-level dependency or development dependency.
|
||||
|
||||
For instance, at the desktop project we need to add `wix`'s licence to the Notice.txt even if it is not a first-level dependency. To do that define
|
||||
all dependencies at `dependencies` array at the configuration. For any dependency at `dependencies` section, pipeline code will add their
|
||||
licenses to the Notice.txt file.
|
||||
|
||||
At the webapp project, `webpack` is used in `devDependencies` and it is requested because we are using some files generated by `webpack`. If there is any dependency
|
||||
referenced at `devDependencies` section, pipeline will add those dependency licences when they are referenced at `package.json` files.
|
||||
|
||||
Sample:
|
||||
|
||||
```
|
||||
title: "Mattermost Mobile"
|
||||
copyright: "© 2016-present Mattermost, Inc. All Rights Reserved. See LICENSE.txt for license information."
|
||||
description: "This document includes a list of open source components used in Mattermost Mobile, including those that have been modified."
|
||||
reviewers:
|
||||
- mattermost/release-managers
|
||||
- enahum
|
||||
search:
|
||||
- "package.json"
|
||||
dependencies:
|
||||
- "wix"
|
||||
devDependencies:
|
||||
- "webpack"
|
||||
```
|
||||
|
||||
| Field | Type | Purpose |
|
||||
| :-- | :-- | :-- |
|
||||
| title | string | Field content will be used as a title of the application. See first line of `NOTICE.txt` file. |
|
||||
| copyright | string | Field content will be used as a copyright message. See second line of `NOTICE.txt` file. |
|
||||
| description | string | Field content will be used as notice file description. See third line of `NOTICE.txt` file. |
|
||||
| reviewers | array of GitHub user/teams | Those will be automatically assigned to the PRs as reviewers. |
|
||||
| dependencies | array | If any dependency name mentioned, it will be automatically added even if it is not a first-level dependency. |
|
||||
| devDependencies | array | If any dependency name mentioned, it will be added when it is referenced in devDependency section. |
|
||||
| search | array | Pipeline will search for package.json files mentioned here. Globstar format is supported ie. `packages/**/package.json`. |
|
||||
|
||||
|
||||
14
build/notice-file/config.yaml
Normal file
14
build/notice-file/config.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
|
||||
title: "Mattermost Mobile"
|
||||
copyright: "© 2016-present Mattermost, Inc. All Rights Reserved. See LICENSE.txt for license information."
|
||||
description: "This document includes a list of open source components used in Mattermost Mobile, including those that have been modified."
|
||||
reviewers:
|
||||
- "mattermost/release-managers"
|
||||
- "enahum"
|
||||
search:
|
||||
- "package.json"
|
||||
dependencies: []
|
||||
devDependencies: []
|
||||
|
||||
...
|
||||
@@ -8,8 +8,8 @@ GEM
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.590.0)
|
||||
aws-sdk-core (3.131.1)
|
||||
aws-partitions (1.602.0)
|
||||
aws-sdk-core (3.131.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
@@ -56,8 +56,8 @@ GEM
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
@@ -66,7 +66,7 @@ GEM
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.206.1)
|
||||
fastlane (2.207.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -111,9 +111,9 @@ GEM
|
||||
fastlane-plugin-find_replace_string (0.1.0)
|
||||
fastlane-plugin-versioning_android (0.1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.21.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-core (0.5.0)
|
||||
google-apis-androidpublisher_v3 (0.23.0)
|
||||
google-apis-core (>= 0.6, < 2.a)
|
||||
google-apis-core (0.7.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@@ -122,19 +122,19 @@ GEM
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.10.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.7.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.14.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-iamcredentials_v1 (0.12.0)
|
||||
google-apis-core (>= 0.6, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.9.0)
|
||||
google-apis-core (>= 0.6, < 2.a)
|
||||
google-apis-storage_v1 (0.16.0)
|
||||
google-apis-core (>= 0.6, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.36.2)
|
||||
google-cloud-storage (1.37.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
@@ -142,7 +142,7 @@ GEM
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.1.3)
|
||||
googleauth (1.2.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
@@ -150,12 +150,12 @@ GEM
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.4)
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.1)
|
||||
json (2.6.2)
|
||||
jwt (2.3.0)
|
||||
jwt (2.4.1)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
@@ -183,9 +183,9 @@ GEM
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.16.1)
|
||||
signet (0.17.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.0)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
@@ -203,11 +203,11 @@ GEM
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.1)
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.21.0)
|
||||
xcodeproj (1.22.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
|
||||
@@ -911,7 +911,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 398;
|
||||
CURRENT_PROJECT_VERSION = 410;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -953,7 +953,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 398;
|
||||
CURRENT_PROJECT_VERSION = 410;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.52.0</string>
|
||||
<string>1.54.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -37,7 +37,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>398</string>
|
||||
<string>410</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.52.0</string>
|
||||
<string>1.54.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>398</string>
|
||||
<string>410</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.52.0</string>
|
||||
<string>1.54.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>398</string>
|
||||
<string>410</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@@ -733,7 +733,7 @@ SPEC CHECKSUMS:
|
||||
FBLazyVector: 244195e30d63d7f564c55da4410b9a24e8fbceaa
|
||||
FBReactNativeSpec: c94002c1d93da3658f4d5119c6994d19961e3d52
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
glog: 5337263514dd6f09803962437687240c5dc39aa4
|
||||
glog: 85ecdd10ee8d8ec362ef519a6a45ff9aa27b2e85
|
||||
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
|
||||
iosMath: f7a6cbadf9d836d2149c2a84c435b1effc244cba
|
||||
jail-monkey: 07b83767601a373db876e939b8dbf3f5eb15f073
|
||||
@@ -746,7 +746,7 @@ SPEC CHECKSUMS:
|
||||
Permission-Notifications: bb420c3d28328df24de1b476b41ed8249ccf2537
|
||||
Permission-PhotoLibrary: 7bec836dcdd04a0bfb200c314f1aae06d4476357
|
||||
Permission-PhotoLibraryAddOnly: 06fb0cdb1d35683b235ad8c464ef0ecc88859ea3
|
||||
RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9
|
||||
RCT-Folly: 803a9cfd78114b2ec0f140cfa6fa2a6bafb2d685
|
||||
RCTRequired: cd47794163052d2b8318c891a7a14fcfaccc75ab
|
||||
RCTTypeSafety: 393bb40b3e357b224cde53d3fec26813c52428b1
|
||||
RCTYouTube: a8bb45705622a6fc9decf64be04128d3658ed411
|
||||
@@ -824,4 +824,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 8214414d5676358401d8ad51dff19e7fd8c71b5c
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
COCOAPODS: 1.11.3
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mattermost-mobile",
|
||||
"version": "1.52.0",
|
||||
"version": "1.54.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mattermost-mobile",
|
||||
"version": "1.52.0",
|
||||
"version": "1.54.0",
|
||||
"description": "Mattermost Mobile with React Native",
|
||||
"repository": "git@github.com:mattermost/mattermost-mobile.git",
|
||||
"author": "Mattermost, Inc.",
|
||||
|
||||
2
types/modules/react-native-webrtc.d.ts
vendored
2
types/modules/react-native-webrtc.d.ts
vendored
@@ -168,7 +168,7 @@ declare module 'react-native-webrtc' {
|
||||
}
|
||||
|
||||
export interface RTCPeerConnectionConfiguration {
|
||||
iceServers: ConfigurationParamWithUrls[] | ConfigurationParamWithUrl[];
|
||||
iceServers: Array<ConfigurationParamWithUrls | ConfigurationParamWithUrl>;
|
||||
iceTransportPolicy?: 'all' | 'relay' | 'nohost' | 'none' | undefined;
|
||||
bundlePolicy?: 'balanced' | 'max-compat' | 'max-bundle' | undefined;
|
||||
rtcpMuxPolicy?: 'negotiate' | 'require' | undefined;
|
||||
|
||||
Reference in New Issue
Block a user