forked from Ivasoft/mattermost-mobile
Compare commits
42 Commits
release-1.
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa5ecaf417 | ||
|
|
65cad4c562 | ||
|
|
290e65ac57 | ||
|
|
20a2078377 | ||
|
|
8ace8c6212 | ||
|
|
5b858e2f06 | ||
|
|
a9064aca0f | ||
|
|
521435748e | ||
|
|
001c105ff7 | ||
|
|
603caaf125 | ||
|
|
069774231c | ||
|
|
be5ff217da | ||
|
|
8004a906cf | ||
|
|
46581bb6d2 | ||
|
|
9f92824dae | ||
|
|
f530c227b2 | ||
|
|
90f6fd6cb8 | ||
|
|
b37811d8e1 | ||
|
|
dc81742404 | ||
|
|
f005a60a8c | ||
|
|
9d357a0333 | ||
|
|
e100ffc7dd | ||
|
|
94995eb0b9 | ||
|
|
0b49403e92 | ||
|
|
4dfecea7d1 | ||
|
|
ada8d4863b | ||
|
|
ac2e8dc02f | ||
|
|
3b6ca6fdb8 | ||
|
|
26c4777365 | ||
|
|
820b3f73e6 | ||
|
|
344de147bd | ||
|
|
92cfb3ddef | ||
|
|
8fd6c86705 | ||
|
|
8f7d852cb1 | ||
|
|
b36b395a0b | ||
|
|
0115610152 | ||
|
|
db144dc5a7 | ||
|
|
3427e917f4 | ||
|
|
b8844c13b7 | ||
|
|
0bc2d43448 | ||
|
|
f79c33099e | ||
|
|
57d148bbf9 |
23
.circleci/config.yml
Normal file
23
.circleci/config.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
version: 2.1
|
||||
|
||||
|
||||
jobs:
|
||||
test:
|
||||
working_directory: ~/mattermost-mobile
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
steps:
|
||||
- checkout
|
||||
- run: |
|
||||
echo assets/base/config.json
|
||||
cat assets/base/config.json
|
||||
# Avoid installing pods
|
||||
touch .podinstall
|
||||
# Run tests
|
||||
make test || exit 1
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
pr-test:
|
||||
jobs:
|
||||
- test
|
||||
@@ -123,8 +123,8 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
|
||||
versionCode 221
|
||||
versionName "1.22.0"
|
||||
versionName "1.23.1"
|
||||
versionCode 234
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
@@ -69,7 +70,10 @@ public class MattermostManagedModule extends ReactContextBaseJavaModule {
|
||||
@ReactMethod
|
||||
// Close the current activity and open the security settings.
|
||||
public void goToSecuritySettings() {
|
||||
getReactApplicationContext().startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS));
|
||||
Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
getReactApplicationContext().startActivity(intent);
|
||||
|
||||
getCurrentActivity().finish();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
@@ -37,6 +38,13 @@ public class ReceiptDelivery {
|
||||
if (map != null) {
|
||||
String token = map.getString("password");
|
||||
String serverUrl = map.getString("service");
|
||||
if (serverUrl.isEmpty()) {
|
||||
String[] credentials = token.split(",[ ]*");
|
||||
if (credentials.length == 2) {
|
||||
token = credentials[0];
|
||||
serverUrl = credentials[1];
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("ReactNative", String.format("Send receipt delivery ACK=%s TYPE=%s to URL=%s with TOKEN=%s", ackId, type, serverUrl, token));
|
||||
execute(serverUrl, token, ackId, type);
|
||||
@@ -64,21 +72,24 @@ public class ReceiptDelivery {
|
||||
return;
|
||||
}
|
||||
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
RequestBody body = RequestBody.create(JSON, json.toString());
|
||||
Request request = new Request.Builder()
|
||||
.header("Authorization", String.format("Bearer %s", token))
|
||||
.header("Content-Type", "application/json")
|
||||
.url(String.format("%s/api/v4/notifications/ack", serverUrl.replaceAll("/$", "")))
|
||||
.post(body)
|
||||
.build();
|
||||
final HttpUrl url = HttpUrl.parse(
|
||||
String.format("%s/api/v4/notifications/ack", serverUrl.replaceAll("/$", "")));
|
||||
if (url != null) {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
RequestBody body = RequestBody.create(JSON, json.toString());
|
||||
Request request = new Request.Builder()
|
||||
.header("Authorization", String.format("Bearer %s", token))
|
||||
.header("Content-Type", "application/json")
|
||||
.url(url)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
try {
|
||||
client.newCall(request).execute();
|
||||
} catch (Exception e) {
|
||||
Log.e("ReactNative", "Receipt delivery failed to send");
|
||||
try {
|
||||
client.newCall(request).execute();
|
||||
} catch (Exception e) {
|
||||
Log.e("ReactNative", "Receipt delivery failed to send");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Platform, View} from 'react-native';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
@@ -23,11 +24,13 @@ export default class BotTag extends PureComponent {
|
||||
const style = createStyleSheet(this.props.theme);
|
||||
|
||||
return (
|
||||
<FormattedText
|
||||
id='post_info.bot'
|
||||
defaultMessage='BOT'
|
||||
style={style.bot}
|
||||
/>
|
||||
<View style={style.bot}>
|
||||
<FormattedText
|
||||
id='post_info.bot'
|
||||
defaultMessage='BOT'
|
||||
style={style.botText}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -39,12 +42,20 @@ const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.15),
|
||||
borderRadius: 2,
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
marginRight: 5,
|
||||
marginLeft: 5,
|
||||
marginRight: 2,
|
||||
marginBottom: 1,
|
||||
...Platform.select({
|
||||
android: {
|
||||
marginBottom: 0,
|
||||
},
|
||||
}),
|
||||
marginLeft: 2,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
botText: {
|
||||
fontSize: 10,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -241,12 +241,16 @@ export default class CombinedSystemMessage extends React.PureComponent {
|
||||
getUsernamesByIds = (userIds = []) => {
|
||||
const {currentUserId, currentUsername} = this.props;
|
||||
const allUsernames = this.getAllUsernames();
|
||||
|
||||
const {formatMessage} = this.context.intl;
|
||||
const someone = formatMessage({id: t('channel_loader.someone'), defaultMessage: 'Someone'});
|
||||
|
||||
const usernames = userIds.
|
||||
filter((userId) => {
|
||||
return userId !== currentUserId && userId !== currentUsername;
|
||||
}).
|
||||
map((userId) => {
|
||||
return `@${allUsernames[userId]}`;
|
||||
return allUsernames[userId] ? `@${allUsernames[userId]}` : someone;
|
||||
}).filter((username) => {
|
||||
return username && username !== '';
|
||||
});
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/emoji_picker/EmojiPicker should match snapshot 1`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior="padding"
|
||||
enabled={true}
|
||||
keyboardVerticalOffset={107}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"paddingVertical": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<SearchBarIos
|
||||
autoCapitalize="none"
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={true}
|
||||
cancelTitle="Cancel"
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
leftComponent={null}
|
||||
onAnimationComplete={[Function]}
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onFocus={[Function]}
|
||||
onSearchButtonPress={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
searchIconCollapsedMargin={10}
|
||||
searchIconExpandedMargin={10}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.8)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<SectionList
|
||||
ListFooterComponent={[Function]}
|
||||
data={Array []}
|
||||
disableVirtualization={false}
|
||||
getItemLayout={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
keyboardShouldPersistTaps="always"
|
||||
maxToRenderPerBatch={10}
|
||||
onEndReached={[Function]}
|
||||
onEndReachedThreshold={0}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollToIndexFailed={[Function]}
|
||||
pageSize={30}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={[Function]}
|
||||
renderSectionHeader={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
sections={Array []}
|
||||
showsVerticalScrollIndicator={false}
|
||||
stickySectionHeadersEnabled={true}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"marginBottom": 35,
|
||||
},
|
||||
Object {
|
||||
"width": 370,
|
||||
},
|
||||
]
|
||||
}
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={21}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"height": 35,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.1)",
|
||||
"borderTopColor": "rgba(61,60,64,0.3)",
|
||||
"borderTopWidth": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "space-between",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Connect(SafeAreaIos)>
|
||||
`;
|
||||
@@ -35,6 +35,10 @@ const SECTION_MARGIN = 15;
|
||||
const SECTION_HEADER_HEIGHT = 28;
|
||||
const EMOJIS_PER_PAGE = 200;
|
||||
|
||||
export function filterEmojiSearchInput(searchText) {
|
||||
return searchText.toLowerCase().replace(/^:|:$/g, '');
|
||||
}
|
||||
|
||||
export default class EmojiPicker extends PureComponent {
|
||||
static propTypes = {
|
||||
customEmojisEnabled: PropTypes.bool.isRequired,
|
||||
@@ -87,9 +91,9 @@ export default class EmojiPicker extends PureComponent {
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let rebuildEmojis = false;
|
||||
this.rebuildEmojis = false;
|
||||
if (this.props.deviceWidth !== nextProps.deviceWidth) {
|
||||
rebuildEmojis = true;
|
||||
this.rebuildEmojis = true;
|
||||
|
||||
if (this.refs.search_bar) {
|
||||
this.refs.search_bar.blur();
|
||||
@@ -97,14 +101,16 @@ export default class EmojiPicker extends PureComponent {
|
||||
}
|
||||
|
||||
if (this.props.emojis !== nextProps.emojis) {
|
||||
rebuildEmojis = true;
|
||||
this.rebuildEmojis = true;
|
||||
this.setRebuiltEmojis();
|
||||
}
|
||||
}
|
||||
|
||||
if (rebuildEmojis) {
|
||||
const emojis = this.renderableEmojis(this.props.emojisBySection, nextProps.deviceWidth);
|
||||
this.setState({
|
||||
emojis,
|
||||
});
|
||||
setRebuiltEmojis = (searchBarAnimationComplete = true) => {
|
||||
if (this.rebuildEmojis && searchBarAnimationComplete) {
|
||||
this.rebuildEmojis = false;
|
||||
const emojis = this.renderableEmojis(this.props.emojisBySection, this.props.deviceWidth);
|
||||
this.setState({emojis});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,24 +166,25 @@ export default class EmojiPicker extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
changeSearchTerm = (text) => {
|
||||
changeSearchTerm = (rawText) => {
|
||||
const searchTerm = filterEmojiSearchInput(rawText);
|
||||
const nextState = {
|
||||
searchTerm: text,
|
||||
searchTerm: rawText,
|
||||
};
|
||||
|
||||
if (!text) {
|
||||
nextState.currentSectionIndex = 0;
|
||||
}
|
||||
|
||||
this.setState(nextState);
|
||||
|
||||
if (!searchTerm) {
|
||||
nextState.currentSectionIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.searchTermTimeout);
|
||||
const timeout = text ? 100 : 0;
|
||||
const timeout = searchTerm ? 100 : 0;
|
||||
this.searchTermTimeout = setTimeout(async () => {
|
||||
if (isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
|
||||
await this.props.actions.searchCustomEmojis(text);
|
||||
await this.props.actions.searchCustomEmojis(searchTerm);
|
||||
}
|
||||
const filteredEmojis = this.searchEmojis(text);
|
||||
const filteredEmojis = this.searchEmojis(searchTerm);
|
||||
this.setState({
|
||||
filteredEmojis,
|
||||
});
|
||||
@@ -476,6 +483,7 @@ export default class EmojiPicker extends PureComponent {
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={searchTerm}
|
||||
onAnimationComplete={this.setRebuiltEmojis}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.container}>
|
||||
|
||||
115
app/components/emoji_picker/emoji_picker.test.js
Normal file
115
app/components/emoji_picker/emoji_picker.test.js
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
import EmojiPicker, {filterEmojiSearchInput} from './emoji_picker.js';
|
||||
|
||||
describe('components/emoji_picker/EmojiPicker', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
getCustomEmojis: jest.fn(),
|
||||
incrementEmojiPickerPage: jest.fn(),
|
||||
searchCustomEmojis: jest.fn(),
|
||||
},
|
||||
customEmojisEnabled: false,
|
||||
customEmojiPage: 200,
|
||||
deviceWidth: 400,
|
||||
emojis: [],
|
||||
emojisBySection: [],
|
||||
fuse: {},
|
||||
isLandscape: false,
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
const testCases = [
|
||||
{input: 'smile', output: 'smile'},
|
||||
{input: 'SMILE', output: 'smile'},
|
||||
{input: ':smile', output: 'smile'},
|
||||
{input: ':SMILE', output: 'smile'},
|
||||
{input: 'smile:', output: 'smile'},
|
||||
{input: 'SMILE:', output: 'smile'},
|
||||
{input: ':smile:', output: 'smile'},
|
||||
{input: ':SMILE:', output: 'smile'},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
test(`'${testCase.input}' should return '${testCase.output}'`, () => {
|
||||
expect(filterEmojiSearchInput(testCase.input)).toEqual(testCase.output);
|
||||
});
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should set rebuildEmojis to true when deviceWidth changes', () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
expect(instance.rebuildEmojis).toBe(undefined);
|
||||
|
||||
const newDeviceWidth = baseProps.deviceWidth * 2;
|
||||
wrapper.setProps({deviceWidth: newDeviceWidth});
|
||||
|
||||
expect(instance.rebuildEmojis).toBe(true);
|
||||
});
|
||||
|
||||
test('should rebuild emojis emojis when emojis change', () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
const renderableEmojis = jest.spyOn(instance, 'renderableEmojis');
|
||||
|
||||
expect(instance.rebuildEmojis).toBe(undefined);
|
||||
|
||||
const newEmojis = [{}];
|
||||
wrapper.setProps({emojis: newEmojis});
|
||||
|
||||
expect(renderableEmojis).toHaveBeenCalledWith(baseProps.emojisBySection, baseProps.deviceWidth);
|
||||
});
|
||||
|
||||
test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
instance.setState = jest.fn();
|
||||
instance.renderableEmojis = jest.spyOn(instance, 'renderableEmojis');
|
||||
|
||||
instance.rebuildEmojis = true;
|
||||
const searchBarAnimationComplete = true;
|
||||
const setRebuiltEmojis = jest.spyOn(instance, 'setRebuiltEmojis');
|
||||
setRebuiltEmojis(searchBarAnimationComplete);
|
||||
|
||||
expect(instance.setState).toHaveBeenCalledWith({emojis: []});
|
||||
expect(instance.rebuildEmojis).toBe(false);
|
||||
});
|
||||
|
||||
test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
instance.setState = jest.fn();
|
||||
|
||||
instance.rebuildEmojis = false;
|
||||
const searchBarAnimationComplete = true;
|
||||
const setRebuiltEmojis = jest.spyOn(instance, 'setRebuiltEmojis');
|
||||
setRebuiltEmojis(searchBarAnimationComplete);
|
||||
|
||||
expect(instance.setState).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
instance.setState = jest.fn();
|
||||
|
||||
instance.rebuildEmojis = true;
|
||||
const searchBarAnimationComplete = false;
|
||||
const setRebuiltEmojis = jest.spyOn(instance, 'setRebuiltEmojis');
|
||||
setRebuiltEmojis(searchBarAnimationComplete);
|
||||
|
||||
expect(instance.setState).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AttachmentImage it matches snapshot 1`] = `
|
||||
<TouchableWithoutFeedback
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginTop": 5,
|
||||
},
|
||||
Object {
|
||||
"width": 38,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "rgba(61,60,64,0.1)",
|
||||
"borderRadius": 2,
|
||||
"borderWidth": 1,
|
||||
"flex": 1,
|
||||
"padding": 5,
|
||||
},
|
||||
Object {
|
||||
"height": 28,
|
||||
"width": 28,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Connect(ProgressiveImage)
|
||||
imageUri="https://images.com/image.png"
|
||||
resizeMode="contain"
|
||||
style={
|
||||
Object {
|
||||
"height": 28,
|
||||
"width": 28,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
`;
|
||||
@@ -48,6 +48,12 @@ export default class AttachmentImage extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.imageUrl && (prevProps.imageUrl !== this.props.imageUrl)) {
|
||||
ImageCacheManager.cache(null, this.props.imageUrl, this.setImageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
handlePreviewImage = () => {
|
||||
const {actions, imageUrl} = this.props;
|
||||
const {
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ImageCacheManager from 'app/utils/image_cache_manager';
|
||||
import {Image} from 'react-native';
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
const originalCacheFn = ImageCacheManager.cache;
|
||||
const originalGetSizeFn = Image.getSize;
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import AttachmentImage from './attachment_image';
|
||||
|
||||
describe('AttachmentImage', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
showModalOverCurrentContext: jest.fn(),
|
||||
},
|
||||
deviceHeight: 256,
|
||||
deviceWidth: 128,
|
||||
imageMetadata: {width: 32, height: 32},
|
||||
imageUrl: 'https://images.com/image.png',
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
Image.getSize = originalGetSizeFn;
|
||||
ImageCacheManager.cache = originalCacheFn;
|
||||
});
|
||||
|
||||
test('it matches snapshot', () => {
|
||||
const cacheFn = jest.fn((_, url, callback) => {
|
||||
callback(url);
|
||||
});
|
||||
ImageCacheManager.cache = cacheFn;
|
||||
|
||||
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it sets state based on props', () => {
|
||||
const cacheFn = jest.fn((_, url, callback) => {
|
||||
callback(url);
|
||||
});
|
||||
ImageCacheManager.cache = cacheFn;
|
||||
|
||||
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
|
||||
|
||||
const state = wrapper.state();
|
||||
expect(state.hasImage).toBe(true);
|
||||
expect(state.imageUri).toBe('https://images.com/image.png');
|
||||
expect(state.originalWidth).toBe(32);
|
||||
expect(cacheFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('it does not render image if no imageUrl is provided', () => {
|
||||
const cacheFn = jest.fn((_, url, callback) => {
|
||||
callback(url);
|
||||
});
|
||||
ImageCacheManager.cache = cacheFn;
|
||||
|
||||
const props = {...baseProps, imageUrl: null, imageMetadata: null};
|
||||
const wrapper = shallow(<AttachmentImage {...props}/>);
|
||||
|
||||
const state = wrapper.state();
|
||||
expect(state.hasImage).toBe(false);
|
||||
expect(state.imageUri).toBe(null);
|
||||
expect(cacheFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('it calls Image.getSize if metadata is not present', () => {
|
||||
const cacheFn = jest.fn((_, url, callback) => {
|
||||
callback(url);
|
||||
});
|
||||
const getSizeFn = jest.fn((_, callback) => {
|
||||
callback(64, 64);
|
||||
});
|
||||
ImageCacheManager.cache = cacheFn;
|
||||
Image.getSize = getSizeFn;
|
||||
|
||||
const props = {...baseProps, imageMetadata: null};
|
||||
const wrapper = shallow(<AttachmentImage {...props}/>);
|
||||
|
||||
const state = wrapper.state();
|
||||
expect(state.hasImage).toBe(true);
|
||||
expect(state.imageUri).toBe('https://images.com/image.png');
|
||||
expect(state.originalWidth).toBe(64);
|
||||
expect(cacheFn).toHaveBeenCalled();
|
||||
expect(getSizeFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('it updates image when imageUrl prop changes', () => {
|
||||
const cacheFn = jest.fn((_, url, callback) => {
|
||||
callback(url);
|
||||
});
|
||||
ImageCacheManager.cache = cacheFn;
|
||||
|
||||
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
|
||||
|
||||
wrapper.setProps({
|
||||
imageUrl: 'https://someothersite.com/picture.png',
|
||||
imageMetadata: {
|
||||
width: 96,
|
||||
height: 96,
|
||||
},
|
||||
});
|
||||
|
||||
const state = wrapper.state();
|
||||
expect(state.hasImage).toBe(true);
|
||||
expect(state.imageUri).toBe('https://someothersite.com/picture.png');
|
||||
expect(state.originalWidth).toBe(96);
|
||||
expect(cacheFn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('it does not update image when an unrelated prop changes', () => {
|
||||
const cacheFn = jest.fn((_, url, callback) => {
|
||||
callback(url);
|
||||
});
|
||||
ImageCacheManager.cache = cacheFn;
|
||||
|
||||
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
|
||||
|
||||
wrapper.setProps({
|
||||
theme: {...Preferences.THEMES.default},
|
||||
});
|
||||
|
||||
const state = wrapper.state();
|
||||
expect(state.hasImage).toBe(true);
|
||||
expect(state.imageUri).toBe('https://images.com/image.png');
|
||||
expect(state.originalWidth).toBe(32);
|
||||
expect(cacheFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -134,6 +134,10 @@ export default class PostList extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
getItemCount = () => {
|
||||
return this.props.postIds.length;
|
||||
};
|
||||
|
||||
handleClosePermalink = () => {
|
||||
const {actions} = this.props;
|
||||
actions.selectFocusedPostId('');
|
||||
@@ -295,23 +299,39 @@ export default class PostList extends PureComponent {
|
||||
|
||||
scrollToBottom = () => {
|
||||
setTimeout(() => {
|
||||
if (this.flatListRef && this.flatListRef.current) {
|
||||
if (this.flatListRef.current) {
|
||||
this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
|
||||
}
|
||||
}, 250);
|
||||
};
|
||||
|
||||
scrollToInitialIndexIfNeeded = (index) => {
|
||||
if (!this.hasDoneInitialScroll && this.flatListRef?.current) {
|
||||
this.hasDoneInitialScroll = true;
|
||||
this.animationFrameInitialIndex = requestAnimationFrame(() => {
|
||||
this.flatListRef.current.scrollToIndex({
|
||||
animated: false,
|
||||
index,
|
||||
viewOffset: 0,
|
||||
viewPosition: 1, // 0 is at bottom
|
||||
});
|
||||
});
|
||||
flatListScrollToIndex = (index) => {
|
||||
this.flatListRef.current.scrollToIndex({
|
||||
animated: false,
|
||||
index,
|
||||
viewOffset: 0,
|
||||
viewPosition: 1, // 0 is at bottom
|
||||
});
|
||||
}
|
||||
|
||||
scrollToIndex = (index) => {
|
||||
this.animationFrameInitialIndex = requestAnimationFrame(() => {
|
||||
if (this.flatListRef.current && index > 0 && index <= this.getItemCount()) {
|
||||
this.flatListScrollToIndex(index);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
scrollToInitialIndexIfNeeded = (index, count = 0) => {
|
||||
if (!this.hasDoneInitialScroll) {
|
||||
if (index > 0 && index <= this.getItemCount()) {
|
||||
this.hasDoneInitialScroll = true;
|
||||
this.scrollToIndex(index);
|
||||
} else if (count < 3) {
|
||||
setTimeout(() => {
|
||||
this.scrollToInitialIndexIfNeeded(index, count + 1);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -55,4 +55,32 @@ describe('PostList', () => {
|
||||
expect(baseProps.actions.handleSelectChannelByName).toHaveBeenCalled();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call flatListScrollToIndex only when ref is set and index is in range', () => {
|
||||
jest.spyOn(global, 'requestAnimationFrame').mockImplementation((cb) => cb());
|
||||
|
||||
const instance = wrapper.instance();
|
||||
const flatListScrollToIndex = jest.spyOn(instance, 'flatListScrollToIndex');
|
||||
const indexInRange = baseProps.postIds.length;
|
||||
const indexOutOfRange = [-1, indexInRange + 1];
|
||||
|
||||
instance.flatListRef = {
|
||||
current: null,
|
||||
};
|
||||
instance.scrollToIndex(indexInRange);
|
||||
expect(flatListScrollToIndex).not.toHaveBeenCalled();
|
||||
|
||||
instance.flatListRef = {
|
||||
current: {
|
||||
scrollToIndex: jest.fn(),
|
||||
},
|
||||
};
|
||||
for (const index of indexOutOfRange) {
|
||||
instance.scrollToIndex(index);
|
||||
expect(flatListScrollToIndex).not.toHaveBeenCalled();
|
||||
}
|
||||
|
||||
instance.scrollToIndex(indexInRange);
|
||||
expect(flatListScrollToIndex).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Alert} from 'react-native';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
@@ -13,6 +14,12 @@ import PostTextbox from './post_textbox.ios';
|
||||
|
||||
jest.mock('NativeEventEmitter');
|
||||
|
||||
jest.mock('Alert', () => {
|
||||
return {
|
||||
alert: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('PostTextBox', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
@@ -88,6 +95,21 @@ describe('PostTextBox', () => {
|
||||
expect(baseProps.actions.handlePostDraftChanged).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should not send multiple alerts when message is too long', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<PostTextbox {...baseProps}/>
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
const longString = [...Array(baseProps.maxMessageLength + 2).keys()].map(() => Math.random().toString(36).slice(0, 1)).join('');
|
||||
|
||||
instance.handleTextChange(longString);
|
||||
instance.handleTextChange(longString.slice(1));
|
||||
|
||||
expect(Alert.alert).toBeCalled();
|
||||
expect(Alert.alert).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('send button', () => {
|
||||
test('should initially disable and hide the send button', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
|
||||
@@ -94,6 +94,7 @@ export default class PostTextBoxBase extends PureComponent {
|
||||
sendingMessage: false,
|
||||
top: 0,
|
||||
value: props.value,
|
||||
longMessageAlertShown: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -171,19 +172,25 @@ export default class PostTextBoxBase extends PureComponent {
|
||||
const valueLength = value.trim().length;
|
||||
|
||||
if (valueLength > maxMessageLength) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.message_length.title',
|
||||
defaultMessage: 'Message Length',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.message_length.message',
|
||||
defaultMessage: 'Your current message is too long. Current character count: {max}/{count}',
|
||||
}, {
|
||||
max: maxMessageLength,
|
||||
count: valueLength,
|
||||
})
|
||||
);
|
||||
// Check if component is already aware message is too long
|
||||
if (!this.state.longMessageAlertShown) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.message_length.title',
|
||||
defaultMessage: 'Message Length',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.message_length.message',
|
||||
defaultMessage: 'Your current message is too long. Current character count: {max}/{count}',
|
||||
}, {
|
||||
max: maxMessageLength,
|
||||
count: valueLength,
|
||||
})
|
||||
);
|
||||
this.setState({longMessageAlertShown: true});
|
||||
}
|
||||
} else if (this.state.longMessageAlertShown) {
|
||||
this.setState({longMessageAlertShown: false});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import EvilIcon from 'react-native-vector-icons/EvilIcons';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
|
||||
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
|
||||
const AnimatedIonIcon = Animated.createAnimatedComponent(IonIcon);
|
||||
@@ -77,6 +78,7 @@ export default class Search extends Component {
|
||||
shadowVisible: PropTypes.bool,
|
||||
leftComponent: PropTypes.element,
|
||||
inputCollapsedMargin: PropTypes.number,
|
||||
onAnimationComplete: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -101,6 +103,7 @@ export default class Search extends Component {
|
||||
value: '',
|
||||
leftComponent: null,
|
||||
inputCollapsedMargin: 10,
|
||||
onAnimationComplete: emptyFunction,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -244,7 +247,7 @@ export default class Search extends Component {
|
||||
Animated.timing(
|
||||
this.inputFocusWidthAnimated,
|
||||
{
|
||||
toValue: this.contentWidth - 70,
|
||||
toValue: this.contentWidth - 90,
|
||||
duration: 200,
|
||||
}
|
||||
),
|
||||
@@ -265,7 +268,7 @@ export default class Search extends Component {
|
||||
Animated.timing(
|
||||
this.btnCancelAnimated,
|
||||
{
|
||||
toValue: this.state.leftComponentWidth ? 15 - this.state.leftComponentWidth : 10,
|
||||
toValue: this.state.leftComponentWidth ? 15 - this.state.leftComponentWidth : 5,
|
||||
duration: 200,
|
||||
}
|
||||
),
|
||||
@@ -369,7 +372,7 @@ export default class Search extends Component {
|
||||
useNativeDriver: true,
|
||||
}
|
||||
),
|
||||
]).start();
|
||||
]).start(({finished}) => this.props.onAnimationComplete(finished));
|
||||
this.shadowHeight = this.props.shadowOffsetHeightCollapsed;
|
||||
resolve();
|
||||
});
|
||||
@@ -544,17 +547,18 @@ const styles = StyleSheet.create({
|
||||
position: 'absolute',
|
||||
paddingLeft: 1,
|
||||
paddingTop: 3,
|
||||
right: 65,
|
||||
right: 80,
|
||||
width: 25,
|
||||
},
|
||||
iconDeleteDefault: {
|
||||
color: 'grey',
|
||||
},
|
||||
cancelButton: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'transparent',
|
||||
width: 60,
|
||||
minWidth: 75,
|
||||
height: 50,
|
||||
},
|
||||
cancelButtonText: {
|
||||
|
||||
@@ -153,6 +153,7 @@ export default class ChannelsList extends PureComponent {
|
||||
onShowTeams={onShowTeams}
|
||||
/>
|
||||
)}
|
||||
positionRightDelete={5}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -101,7 +101,7 @@ class FilteredList extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
searchProfiles(term);
|
||||
searchProfiles(term, {allow_inactive: true});
|
||||
searchChannels(currentTeam.id, term);
|
||||
}, General.SEARCH_TIMEOUT_MILLISECONDS);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ import {getTheme, getFavoritesPreferences, getSidebarPreferences} from 'mattermo
|
||||
import {showCreateOption} from 'mattermost-redux/utils/channel_utils';
|
||||
import {memoizeResult} from 'mattermost-redux/utils/helpers';
|
||||
import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getConfig, getLicense, hasNewPermissions} from 'mattermost-redux/selectors/entities/general';
|
||||
import {haveITeamPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||
import Permissions from 'mattermost-redux/constants/permissions';
|
||||
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
|
||||
@@ -53,16 +55,20 @@ function mapStateToProps(state) {
|
||||
sidebarPrefs.favorite_at_top === 'true' && favoriteChannelIds.length,
|
||||
));
|
||||
|
||||
let canJoinPublicChannels = true;
|
||||
if (hasNewPermissions(state)) {
|
||||
canJoinPublicChannels = haveITeamPermission(state, {
|
||||
team: currentTeamId,
|
||||
permission: Permissions.JOIN_PUBLIC_CHANNELS,
|
||||
});
|
||||
}
|
||||
const canCreatePublicChannels = showCreateOption(state, config, license, currentTeamId, General.OPEN_CHANNEL, isAdmin, isSystemAdmin);
|
||||
const canCreatePrivateChannels = showCreateOption(state, config, license, currentTeamId, General.PRIVATE_CHANNEL, isAdmin, isSystemAdmin);
|
||||
|
||||
return {
|
||||
canCreatePrivateChannels: showCreateOption(
|
||||
state,
|
||||
config,
|
||||
license,
|
||||
currentTeamId,
|
||||
General.PRIVATE_CHANNEL,
|
||||
isAdmin,
|
||||
isSystemAdmin
|
||||
),
|
||||
canJoinPublicChannels,
|
||||
canCreatePrivateChannels,
|
||||
canCreatePublicChannels,
|
||||
favoriteChannelIds,
|
||||
theme: getTheme(state),
|
||||
unreadChannelIds,
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Dimensions,
|
||||
findNodeHandle,
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
Platform,
|
||||
@@ -23,6 +24,7 @@ import ChannelItem from 'app/components/sidebars/main/channels_list/channel_item
|
||||
import {DeviceTypes, ListTypes} from 'app/constants';
|
||||
import {SidebarSectionTypes} from 'app/constants/view';
|
||||
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
@@ -39,7 +41,9 @@ export default class List extends PureComponent {
|
||||
actions: PropTypes.shape({
|
||||
showModal: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
canJoinPublicChannels: PropTypes.bool.isRequired,
|
||||
canCreatePrivateChannels: PropTypes.bool.isRequired,
|
||||
canCreatePublicChannels: PropTypes.bool.isRequired,
|
||||
favoriteChannelIds: PropTypes.array.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
unreadChannelIds: PropTypes.array.isRequired,
|
||||
@@ -56,6 +60,8 @@ export default class List extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.combinedActionsRef = React.createRef();
|
||||
|
||||
this.state = {
|
||||
sections: this.buildSections(props),
|
||||
showIndicator: false,
|
||||
@@ -164,72 +170,51 @@ export default class List extends PureComponent {
|
||||
};
|
||||
|
||||
showCreateChannelOptions = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {
|
||||
canJoinPublicChannels,
|
||||
canCreatePrivateChannels,
|
||||
actions,
|
||||
canCreatePublicChannels,
|
||||
} = this.props;
|
||||
|
||||
const items = [];
|
||||
const moreChannels = {
|
||||
action: this.goToMoreChannels,
|
||||
text: {
|
||||
id: 'more_channels.title',
|
||||
defaultMessage: 'More Channels',
|
||||
},
|
||||
};
|
||||
const createPublicChannel = {
|
||||
action: this.goToCreatePublicChannel,
|
||||
text: {
|
||||
id: 'mobile.create_channel.public',
|
||||
defaultMessage: 'New Public Channel',
|
||||
},
|
||||
};
|
||||
const createPrivateChannel = {
|
||||
action: this.goToCreatePrivateChannel,
|
||||
text: {
|
||||
id: 'mobile.create_channel.private',
|
||||
defaultMessage: 'New Private Channel',
|
||||
},
|
||||
};
|
||||
const newConversation = {
|
||||
action: this.goToDirectMessages,
|
||||
text: {
|
||||
id: 'mobile.more_dms.title',
|
||||
defaultMessage: 'New Conversation',
|
||||
},
|
||||
};
|
||||
const moreChannelsText = formatMessage({id: 'more_channels.title', defaultMessage: 'More Channels'});
|
||||
const newPublicChannelText = formatMessage({id: 'mobile.create_channel.public', defaultMessage: 'New Public Channel'});
|
||||
const newPrivateChannelText = formatMessage({id: 'mobile.create_channel.private', defaultMessage: 'New Private Channel'});
|
||||
const newDirectChannelText = formatMessage({id: 'mobile.more_dms.title', defaultMessage: 'New Conversation'});
|
||||
const cancelText = formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'});
|
||||
const options = [];
|
||||
const actions = [];
|
||||
|
||||
items.push(moreChannels, createPublicChannel);
|
||||
if (canCreatePrivateChannels) {
|
||||
items.push(createPrivateChannel);
|
||||
if (canJoinPublicChannels) {
|
||||
actions.push(this.goToMoreChannels);
|
||||
options.push(moreChannelsText);
|
||||
}
|
||||
items.push(newConversation);
|
||||
|
||||
const screen = 'OptionsModal';
|
||||
const title = '';
|
||||
const passProps = {
|
||||
items,
|
||||
};
|
||||
const options = {
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
layout: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
animations: {
|
||||
showModal: {
|
||||
enable: false,
|
||||
},
|
||||
dismissModal: {
|
||||
enable: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
if (canCreatePublicChannels) {
|
||||
actions.push(this.goToCreatePublicChannel);
|
||||
options.push(newPublicChannelText);
|
||||
}
|
||||
|
||||
actions.showModal(screen, title, passProps, options);
|
||||
if (canCreatePrivateChannels) {
|
||||
actions.push(this.goToCreatePrivateChannel);
|
||||
options.push(newPrivateChannelText);
|
||||
}
|
||||
|
||||
actions.push(this.goToDirectMessages);
|
||||
options.push(newDirectChannelText);
|
||||
options.push(cancelText);
|
||||
|
||||
const cancelButtonIndex = options.length - 1;
|
||||
|
||||
BottomSheet.showBottomSheetWithOptions({
|
||||
anchor: this.combinedActionsRef?.current ? findNodeHandle(this.combinedActionsRef.current) : null,
|
||||
options,
|
||||
cancelButtonIndex,
|
||||
}, (value) => {
|
||||
if (value !== cancelButtonIndex) {
|
||||
actions[value]();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
goToCreatePublicChannel = preventDoubleTap(() => {
|
||||
@@ -303,7 +288,7 @@ export default class List extends PureComponent {
|
||||
this.setState({width: width - 40});
|
||||
};
|
||||
|
||||
renderSectionAction = (styles, action) => {
|
||||
renderSectionAction = (styles, action, anchor) => {
|
||||
const {theme} = this.props;
|
||||
return (
|
||||
<TouchableHighlight
|
||||
@@ -313,6 +298,7 @@ export default class List extends PureComponent {
|
||||
>
|
||||
<MaterialIcon
|
||||
name='add'
|
||||
ref={anchor ? this.combinedActionsRef : null}
|
||||
style={styles.action}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
@@ -351,6 +337,8 @@ export default class List extends PureComponent {
|
||||
topSeparator,
|
||||
} = section;
|
||||
|
||||
const anchor = (id === 'sidebar.types.recent' || id === 'mobile.channel_list.channels');
|
||||
|
||||
return (
|
||||
<View>
|
||||
{topSeparator && this.renderSectionSeparator()}
|
||||
@@ -358,7 +346,7 @@ export default class List extends PureComponent {
|
||||
<Text style={styles.title}>
|
||||
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
{action && this.renderSectionAction(styles, action)}
|
||||
{action && this.renderSectionAction(styles, action, anchor)}
|
||||
</View>
|
||||
{bottomSeparator && this.renderSectionSeparator()}
|
||||
</View>
|
||||
|
||||
@@ -105,7 +105,7 @@ export default class ChannelSidebar extends Component {
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {currentTeamId, deviceWidth, isLandscape, teamsCount} = this.props;
|
||||
const {currentTeamId, deviceWidth, isLandscape, teamsCount, theme} = this.props;
|
||||
const {openDrawerOffset, isSplitView, permanentSidebar, show, searching} = this.state;
|
||||
|
||||
if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== show || nextState.searching !== searching) {
|
||||
@@ -115,6 +115,7 @@ export default class ChannelSidebar extends Component {
|
||||
return nextProps.currentTeamId !== currentTeamId ||
|
||||
nextProps.isLandscape !== isLandscape || nextProps.deviceWidth !== deviceWidth ||
|
||||
nextProps.teamsCount !== teamsCount ||
|
||||
nextProps.theme !== theme ||
|
||||
nextState.isSplitView !== isSplitView ||
|
||||
nextState.permanentSidebar !== permanentSidebar;
|
||||
}
|
||||
|
||||
@@ -57,5 +57,28 @@ describe('MainSidebar', () => {
|
||||
await wrapper.instance().handlePermanentSidebar();
|
||||
|
||||
expect(wrapper.state('permanentSidebar')).toBeDefined();
|
||||
|
||||
// Reset to false for subsequent tests
|
||||
DeviceTypes.IS_TABLET = false;
|
||||
});
|
||||
|
||||
test('should re-render when the theme changes', () => {
|
||||
const theme = Preferences.THEMES.default;
|
||||
const newTheme = Preferences.THEMES.organization;
|
||||
const props = {
|
||||
...baseProps,
|
||||
theme,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<MainSidebar {...props}/>
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.render = jest.fn();
|
||||
|
||||
expect(instance.render).toHaveBeenCalledTimes(0);
|
||||
wrapper.setProps({theme: newTheme});
|
||||
expect(instance.render).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,17 +101,22 @@ function loadTranslation(locale) {
|
||||
}
|
||||
|
||||
if (momentData) {
|
||||
moment.updateLocale(locale, momentData);
|
||||
moment.updateLocale(locale.toLowerCase(), momentData);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('NO Translation found', e); //eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
export function resetMomentLocale() {
|
||||
moment.locale(DEFAULT_LOCALE);
|
||||
}
|
||||
|
||||
export function getTranslations(locale) {
|
||||
if (!TRANSLATIONS[locale]) {
|
||||
loadTranslation(locale);
|
||||
}
|
||||
|
||||
return TRANSLATIONS[locale] || TRANSLATIONS[DEFAULT_LOCALE];
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ class EMMProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setPerformingAuthentication(false);
|
||||
this.performingAuthentication = false;
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -134,7 +134,7 @@ class EMMProvider {
|
||||
};
|
||||
|
||||
showNotSecuredAlert = (translations) => {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise(async (resolve) => {
|
||||
const options = [];
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
@@ -152,9 +152,22 @@ class EMMProvider {
|
||||
style: 'cancel',
|
||||
});
|
||||
|
||||
let message;
|
||||
if (Platform.OS === 'ios') {
|
||||
const {supportsFaceId} = await mattermostManaged.supportsFaceId();
|
||||
|
||||
if (supportsFaceId) {
|
||||
message = translations[t('mobile.managed.not_secured.ios')];
|
||||
} else {
|
||||
message = translations[t('mobile.managed.not_secured.ios.touchId')];
|
||||
}
|
||||
} else {
|
||||
message = translations[t('mobile.managed.not_secured.android')];
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
translations[t('mobile.managed.blocked_by')].replace('{vendor}', this.vendor),
|
||||
Platform.OS === 'ios' ? translations[t('mobile.managed.not_secured.ios')] : translations[t('mobile.managed.not_secured.android')],
|
||||
message,
|
||||
options,
|
||||
{cancelable: false, onDismiss: resolve},
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ import {selectDefaultChannel} from 'app/actions/views/channel';
|
||||
import {showOverlay} from 'app/actions/navigation';
|
||||
import {loadConfigAndLicense, setDeepLinkURL, startDataCleanup} from 'app/actions/views/root';
|
||||
import {NavigationTypes, ViewTypes} from 'app/constants';
|
||||
import {getTranslations} from 'app/i18n';
|
||||
import {getTranslations, resetMomentLocale} from 'app/i18n';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
@@ -40,7 +40,6 @@ class GlobalEventHandler {
|
||||
EventEmitter.on(General.SERVER_VERSION_CHANGED, this.onServerVersionChanged);
|
||||
EventEmitter.on(General.CONFIG_CHANGED, this.onServerConfigChanged);
|
||||
EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, this.onSwitchToDefaultChannel);
|
||||
this.turnOnInAppNotificationHandling();
|
||||
Dimensions.addEventListener('change', this.onOrientationChange);
|
||||
AppState.addEventListener('change', this.onAppStateChange);
|
||||
Linking.addEventListener('url', this.onDeepLink);
|
||||
@@ -77,6 +76,10 @@ class GlobalEventHandler {
|
||||
this.store = opts.store;
|
||||
this.launchApp = opts.launchApp;
|
||||
|
||||
// onAppStateChange may be called by the AppState listener before we
|
||||
// configure the global event handler so we manually call it here
|
||||
this.onAppStateChange('active');
|
||||
|
||||
const window = Dimensions.get('window');
|
||||
this.onOrientationChange({window});
|
||||
|
||||
@@ -113,12 +116,12 @@ class GlobalEventHandler {
|
||||
|
||||
if (this.store) {
|
||||
this.store.dispatch(setAppState(isActive));
|
||||
}
|
||||
|
||||
if (isActive && emmProvider.previousAppState === 'background') {
|
||||
this.appActive();
|
||||
} else if (isBackground) {
|
||||
this.appInactive();
|
||||
if (isActive && (!emmProvider.enabled || emmProvider.previousAppState === 'background')) {
|
||||
this.appActive();
|
||||
} else if (isBackground) {
|
||||
this.appInactive();
|
||||
}
|
||||
}
|
||||
|
||||
emmProvider.previousAppState = appState;
|
||||
@@ -142,6 +145,7 @@ class GlobalEventHandler {
|
||||
this.store.dispatch(setServerVersion(''));
|
||||
deleteFileCache();
|
||||
removeAppCredentials();
|
||||
resetMomentLocale();
|
||||
|
||||
PushNotifications.clearNotifications();
|
||||
|
||||
@@ -181,7 +185,8 @@ class GlobalEventHandler {
|
||||
}
|
||||
|
||||
if (this.launchApp) {
|
||||
this.launchApp();
|
||||
const credentials = await getAppCredentials();
|
||||
this.launchApp(credentials);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import thunk from 'redux-thunk';
|
||||
|
||||
import intitialState from 'app/initial_state';
|
||||
import PushNotification from 'app/push_notifications';
|
||||
import * as I18n from 'app/i18n';
|
||||
|
||||
import GlobalEventHandler from './global_event_handler';
|
||||
|
||||
@@ -15,6 +16,12 @@ jest.mock('app/init/credentials', () => ({
|
||||
removeAppCredentials: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('app/utils/error_handling', () => ({
|
||||
default: {
|
||||
initializeErrorHandling: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('react-native-notifications', () => ({
|
||||
addEventListener: jest.fn(),
|
||||
cancelAllLocalNotifications: jest.fn(),
|
||||
@@ -29,10 +36,54 @@ GlobalEventHandler.store = store;
|
||||
|
||||
// TODO: Add Android test as part of https://mattermost.atlassian.net/browse/MM-17110
|
||||
describe('GlobalEventHandler', () => {
|
||||
it('should clear notifications on logout', async () => {
|
||||
it('should clear notifications and reset moment locale on logout', async () => {
|
||||
const clearNotifications = jest.spyOn(PushNotification, 'clearNotifications');
|
||||
const resetMomentLocale = jest.spyOn(I18n, 'resetMomentLocale');
|
||||
|
||||
await GlobalEventHandler.onLogout();
|
||||
expect(clearNotifications).toHaveBeenCalled();
|
||||
expect(resetMomentLocale).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should call onAppStateChange after configuration', () => {
|
||||
const onAppStateChange = jest.spyOn(GlobalEventHandler, 'onAppStateChange');
|
||||
|
||||
GlobalEventHandler.configure({store});
|
||||
expect(GlobalEventHandler.store).not.toBeNull();
|
||||
expect(onAppStateChange).toHaveBeenCalledWith('active');
|
||||
});
|
||||
|
||||
it('should handle onAppStateChange to active if the store set', () => {
|
||||
const appActive = jest.spyOn(GlobalEventHandler, 'appActive');
|
||||
const appInactive = jest.spyOn(GlobalEventHandler, 'appInactive');
|
||||
expect(GlobalEventHandler.store).not.toBeNull();
|
||||
|
||||
GlobalEventHandler.onAppStateChange('active');
|
||||
expect(appActive).toHaveBeenCalled();
|
||||
expect(appInactive).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle onAppStateChange to background if the store set', () => {
|
||||
const appActive = jest.spyOn(GlobalEventHandler, 'appActive');
|
||||
const appInactive = jest.spyOn(GlobalEventHandler, 'appInactive');
|
||||
expect(GlobalEventHandler.store).not.toBeNull();
|
||||
|
||||
GlobalEventHandler.onAppStateChange('background');
|
||||
expect(appActive).not.toHaveBeenCalled();
|
||||
expect(appInactive).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not handle onAppStateChange if the store is not set', () => {
|
||||
const appActive = jest.spyOn(GlobalEventHandler, 'appActive');
|
||||
const appInactive = jest.spyOn(GlobalEventHandler, 'appInactive');
|
||||
GlobalEventHandler.store = null;
|
||||
|
||||
GlobalEventHandler.onAppStateChange('active');
|
||||
expect(appActive).not.toHaveBeenCalled();
|
||||
expect(appInactive).not.toHaveBeenCalled();
|
||||
|
||||
GlobalEventHandler.onAppStateChange('background');
|
||||
expect(appActive).not.toHaveBeenCalled();
|
||||
expect(appInactive).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,8 +26,9 @@ const sharedExtensionStarted = Platform.OS === 'android' && MattermostShare.isOp
|
||||
export const store = configureStore(initialState);
|
||||
|
||||
const init = async () => {
|
||||
const credentials = await getAppCredentials();
|
||||
if (EphemeralStore.appStarted) {
|
||||
launchApp();
|
||||
launchApp(credentials);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,17 +45,16 @@ const init = async () => {
|
||||
}
|
||||
|
||||
if (!EphemeralStore.appStarted) {
|
||||
launchAppAndAuthenticateIfNeeded();
|
||||
launchAppAndAuthenticateIfNeeded(credentials);
|
||||
}
|
||||
};
|
||||
|
||||
const launchApp = async () => {
|
||||
const launchApp = async (credentials) => {
|
||||
telemetry.start([
|
||||
'start:select_server_screen',
|
||||
'start:channel_screen',
|
||||
]);
|
||||
|
||||
const credentials = await getAppCredentials();
|
||||
if (credentials) {
|
||||
store.dispatch(loadMe());
|
||||
store.dispatch(resetToChannel({skipMetrics: true}));
|
||||
@@ -66,9 +66,9 @@ const launchApp = async () => {
|
||||
EphemeralStore.appStarted = true;
|
||||
};
|
||||
|
||||
const launchAppAndAuthenticateIfNeeded = async () => {
|
||||
const launchAppAndAuthenticateIfNeeded = async (credentials) => {
|
||||
await emmProvider.handleManagedConfig(store);
|
||||
await launchApp();
|
||||
await launchApp(credentials);
|
||||
|
||||
if (emmProvider.enabled) {
|
||||
if (emmProvider.jailbreakProtection) {
|
||||
|
||||
@@ -66,5 +66,6 @@ export default {
|
||||
|
||||
return JailMonkey.trustFall();
|
||||
},
|
||||
supportsFaceId: async () => false,
|
||||
quitApp: MattermostManaged.quitApp,
|
||||
};
|
||||
|
||||
@@ -68,5 +68,6 @@ export default {
|
||||
|
||||
return JailMonkey.trustFall();
|
||||
},
|
||||
supportsFaceId: MattermostManaged.supportsFaceId,
|
||||
quitApp: MattermostManaged.quitApp,
|
||||
};
|
||||
|
||||
@@ -127,7 +127,10 @@ class PushNotification {
|
||||
...notification.getData(),
|
||||
message: notification.getMessage(),
|
||||
};
|
||||
this.handleNotification(info, false, userInteraction);
|
||||
|
||||
if (!userInteraction) {
|
||||
this.handleNotification(info, false, userInteraction);
|
||||
}
|
||||
};
|
||||
|
||||
onNotificationReceivedForeground = (notification) => {
|
||||
|
||||
@@ -72,6 +72,7 @@ export default class ChannelAddMembers extends PureComponent {
|
||||
enalbed: false,
|
||||
id: 'add-members',
|
||||
text: context.intl.formatMessage({id: 'integrations.add', defaultMessage: 'Add'}),
|
||||
color: props.theme.sidebarHeaderTextColor,
|
||||
showAsAction: 'always',
|
||||
};
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ export default class ChannelMembers extends PureComponent {
|
||||
};
|
||||
|
||||
this.removeButton = {
|
||||
color: props.theme.sidebarHeaderTextColor,
|
||||
enabled: false,
|
||||
id: 'remove-members',
|
||||
showAsAction: 'always',
|
||||
|
||||
@@ -258,7 +258,6 @@ export default class EditProfile extends PureComponent {
|
||||
handleRemoveProfileImage = () => {
|
||||
this.setState({profileImageRemove: true});
|
||||
this.emitCanUpdateAccount(true);
|
||||
this.props.actions.dismissModal();
|
||||
}
|
||||
|
||||
uploadProfileImage = async () => {
|
||||
|
||||
@@ -69,7 +69,5 @@ describe('edit_profile', () => {
|
||||
expect(wrapper.state('profileImageRemove')).toEqual(true);
|
||||
expect(instance.emitCanUpdateAccount).toHaveBeenCalledTimes(1);
|
||||
expect(instance.emitCanUpdateAccount).toBeCalledWith(true);
|
||||
|
||||
expect(baseProps.actions.dismissModal).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -234,6 +234,8 @@ exports[`ImagePreview should match snapshot 1`] = `
|
||||
</AnimatedComponent>
|
||||
`;
|
||||
|
||||
exports[`ImagePreview should match snapshot and not renderDownloadButton for local files 1`] = `null`;
|
||||
|
||||
exports[`ImagePreview should match snapshot, renderDownloadButton 1`] = `
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.2}
|
||||
|
||||
@@ -282,19 +282,16 @@ export default class Downloader extends PureComponent {
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
progress: 100,
|
||||
}, () => {
|
||||
// need to wait a bit for the progress circle UI to update to the give progress
|
||||
setTimeout(async () => {
|
||||
if (this.state.didCancel) {
|
||||
try {
|
||||
await RNFetchBlob.fs.unlink(path);
|
||||
} finally {
|
||||
this.props.onDownloadCancel();
|
||||
}
|
||||
} else {
|
||||
this.props.onDownloadSuccess();
|
||||
}, async () => {
|
||||
if (this.state.didCancel) {
|
||||
try {
|
||||
await RNFetchBlob.fs.unlink(path);
|
||||
} finally {
|
||||
this.downloadDidCancel();
|
||||
}
|
||||
}, 2000);
|
||||
} else {
|
||||
this.props.onDownloadSuccess();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -306,7 +303,9 @@ export default class Downloader extends PureComponent {
|
||||
} catch (error) {
|
||||
// cancellation throws so we need to catch
|
||||
if (downloadPath) {
|
||||
RNFetchBlob.fs.unlink(getLocalFilePathFromFile(downloadPath, file));
|
||||
RNFetchBlob.fs.unlink(getLocalFilePathFromFile(downloadPath, file)).catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
if (error.message !== 'cancelled' && this.mounted) {
|
||||
this.showDownloadFailedAlert();
|
||||
|
||||
@@ -227,6 +227,11 @@ export default class ImagePreview extends PureComponent {
|
||||
const {canDownloadFiles} = this.props;
|
||||
const file = this.getCurrentFile();
|
||||
|
||||
if (file?.data?.localPath) {
|
||||
// we already have the file locally we don't need to download it
|
||||
return null;
|
||||
}
|
||||
|
||||
if (file) {
|
||||
let icon;
|
||||
let action = emptyFunction;
|
||||
|
||||
@@ -67,6 +67,21 @@ describe('ImagePreview', () => {
|
||||
expect(wrapper.instance().renderDownloadButton()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot and not renderDownloadButton for local files', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
files: [{caption: 'Caption 1', source: 'source', data: {localPath: 'path'}}],
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<ImagePreview {...props}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
expect(wrapper.instance().renderDownloadButton()).toMatchSnapshot();
|
||||
expect(wrapper.instance().renderDownloadButton()).toBeNull();
|
||||
});
|
||||
|
||||
test('should match state on handleChangeImage', () => {
|
||||
const wrapper = shallow(
|
||||
<ImagePreview {...baseProps}/>,
|
||||
|
||||
@@ -66,6 +66,7 @@ export default class MoreChannels extends PureComponent {
|
||||
};
|
||||
|
||||
this.rightButton = {
|
||||
color: props.theme.sidebarHeaderTextColor,
|
||||
id: 'create-pub-channel',
|
||||
text: context.intl.formatMessage({id: 'mobile.create_channel', defaultMessage: 'Create'}),
|
||||
showAsAction: 'always',
|
||||
|
||||
@@ -325,10 +325,11 @@ export default class MoreDirectMessages extends PureComponent {
|
||||
};
|
||||
|
||||
updateNavigationButtons = (startEnabled, context = this.context) => {
|
||||
const {actions, componentId} = this.props;
|
||||
const {actions, componentId, theme} = this.props;
|
||||
const {formatMessage} = context.intl;
|
||||
actions.setButtons(componentId, {
|
||||
rightButtons: [{
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
id: START_BUTTON,
|
||||
text: formatMessage({id: 'mobile.more_dms.start', defaultMessage: 'Start'}),
|
||||
showAsAction: 'always',
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class OptionsModalList extends PureComponent {
|
||||
if (typeof action === 'function') {
|
||||
action();
|
||||
}
|
||||
}, 100);
|
||||
}, 250);
|
||||
});
|
||||
|
||||
renderOptions = () => {
|
||||
|
||||
@@ -226,11 +226,9 @@ class SSO extends PureComponent {
|
||||
startInLoadingState={true}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
onShouldStartLoadWithRequest={() => true}
|
||||
renderLoading={this.renderLoading}
|
||||
injectedJavaScript={jsCode}
|
||||
onLoadEnd={this.onLoadEnd}
|
||||
onMessage={messagingEnabled ? this.onMessage : null}
|
||||
useWebKit={this.useWebkit}
|
||||
useSharedProcessPool={true}
|
||||
cacheEnabled={true}
|
||||
/>
|
||||
|
||||
@@ -83,6 +83,7 @@ export default class ImageCacheManager {
|
||||
} catch (e) {
|
||||
RNFetchBlob.fs.unlink(pathWithPrefix);
|
||||
notifyAll(uri, uri);
|
||||
unsubscribe(uri);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -43,9 +43,13 @@ class PushNotificationUtils {
|
||||
};
|
||||
|
||||
loadFromNotification = async (notification) => {
|
||||
// Set appStartedFromPushNotification to avoid channel screen to call selectInitialChannel
|
||||
EphemeralStore.appStartedFromPushNotification = true;
|
||||
await this.store.dispatch(loadFromPushNotification(notification, true));
|
||||
|
||||
if (!EphemeralStore.appStartedFromPushNotification) {
|
||||
// if we have a componentId means that the app is already initialized
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
if (componentId) {
|
||||
EventEmitter.emit('close_channel_drawer');
|
||||
EventEmitter.emit('close_settings_sidebar');
|
||||
|
||||
@@ -190,11 +194,11 @@ class PushNotificationUtils {
|
||||
};
|
||||
|
||||
unsubscribeFromStore = this.store.subscribe(waitForHydration);
|
||||
}
|
||||
};
|
||||
|
||||
getNotification = () => {
|
||||
return PushNotifications.getNotification();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new PushNotificationUtils();
|
||||
|
||||
@@ -155,17 +155,34 @@ function capture(captureFunc, store) {
|
||||
|
||||
// Don't contact Sentry if we're connected to a server with diagnostics disabled. Note that this will
|
||||
// still log if we're not connected to any server.
|
||||
if (config.EnableDiagnostics != null && config.EnableDiagnostics !== 'true') {
|
||||
if (config && config.EnableDiagnostics != null && config.EnableDiagnostics !== 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
Sentry.setUserContext(getUserContext(state));
|
||||
Sentry.setExtraContext(getExtraContext(state));
|
||||
Sentry.setTagsContext(getBuildTags(state));
|
||||
let hasUserContext = false;
|
||||
const userContext = getUserContext(state);
|
||||
if (Object.keys(userContext).length) {
|
||||
hasUserContext = true;
|
||||
Sentry.setUserContext(userContext);
|
||||
}
|
||||
|
||||
console.warn('Capturing with Sentry at ' + getDsn() + '...'); // eslint-disable-line no-console
|
||||
const extraContext = getExtraContext(state);
|
||||
if (Object.keys(extraContext).length) {
|
||||
Sentry.setExtraContext(extraContext);
|
||||
}
|
||||
|
||||
captureFunc();
|
||||
const buildTags = getBuildTags(state);
|
||||
if (Object.keys(buildTags).length) {
|
||||
Sentry.setTagsContext(buildTags);
|
||||
}
|
||||
|
||||
if (hasUserContext) {
|
||||
console.warn('Capturing with Sentry at ' + getDsn() + '...'); // eslint-disable-line no-console
|
||||
|
||||
captureFunc();
|
||||
} else {
|
||||
console.warn('No user context, skipping capture'); // eslint-disable-line no-console
|
||||
}
|
||||
} catch (e) {
|
||||
// Don't want this to get into an infinite loop again...
|
||||
console.warn('Exception occured while sending to Sentry'); // eslint-disable-line no-console
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Dokumente & Daten löschen",
|
||||
"mobile.advanced_settings.timezone": "Zeitzone",
|
||||
"mobile.advanced_settings.title": "Erweiterte Einstellungen",
|
||||
"mobile.alert_dialog.alertCancel": "Abbrechen",
|
||||
"mobile.android.camera_permission_denied_description": "Um Fotos und Videos mit ihrer Kamera aufzunehmen, ändern Sie bitte ihre Berechtigungseinstellungen.",
|
||||
"mobile.android.camera_permission_denied_title": "Kamerazugrif wird benötigt",
|
||||
"mobile.android.permission_denied_dismiss": "Verwerfen",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Geräten mit Jailbreak wird von {vendor} nicht vertraut, bitte beenden Sie die App.",
|
||||
"mobile.managed.not_secured.android": "Dieses Gerät muss mit einer Bildschirmsperre gesichert werden, um Mattermost verwenden zu können.",
|
||||
"mobile.managed.not_secured.ios": "Dieses Gerät muss mit einem Passcode gesichert werden, um Mattermost verwenden zu können.\n \nGehen Sie zu Einstellungen > Face ID & Passwort.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Dieses Gerät muss mit einem Passcode gesichert werden, um Mattermost zu verwenden.\n \nGehen Sie zu Einstellungen > Touch ID & Passwort.",
|
||||
"mobile.managed.secured_by": "Gesichert durch {vendor}",
|
||||
"mobile.managed.settings": "Zu Einstellungen gehen",
|
||||
"mobile.markdown.code.copy_code": "Code kopieren",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Markiert",
|
||||
"mobile.post_pre_header.pinned": "Angeheftet",
|
||||
"mobile.post_pre_header.pinned_flagged": "Angeheftet und markiert",
|
||||
"mobile.post_textbox.empty.message": "Sie versuchen, eine leere Nachricht zu senden.\nBitte stellen Sie sicher, dass die Nachricht Text oder eine angehängte Datei enthält.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Leere Nachricht",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Einige Anhänge konnten nicht auf den Server hochgeladen werden. Sind Sie sicher, dass sie die Nachricht abschicken wollen?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Anhang Fehler",
|
||||
"mobile.post.cancel": "Abbrechen",
|
||||
|
||||
@@ -278,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Jailbroken devices are not trusted by {vendor}, please exit the app.",
|
||||
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
|
||||
"mobile.managed.not_secured.ios.touchId": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Touch ID & Passcode.",
|
||||
"mobile.managed.secured_by": "Secured by {vendor}",
|
||||
"mobile.managed.settings": "Go to settings",
|
||||
"mobile.markdown.code.copy_code": "Copy Code",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Eliminar Documentos & Datos",
|
||||
"mobile.advanced_settings.timezone": "Zona horaria",
|
||||
"mobile.advanced_settings.title": "Configuración Avanzada",
|
||||
"mobile.alert_dialog.alertCancel": "Cancelar",
|
||||
"mobile.android.camera_permission_denied_description": "Para tomar fotos y videos con la cámara, por favor, cambia la configuración de permisos.",
|
||||
"mobile.android.camera_permission_denied_title": "Acceso a la cámara es necesario",
|
||||
"mobile.android.permission_denied_dismiss": "Cerrar",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "{vendor} no confía en los dispositivos con jailbreak, por favor, salga de la aplicación.",
|
||||
"mobile.managed.not_secured.android": "Este dispositivo debe estar asegurado con un bloqueo de pantalla para utilizar Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "Este dispositivo debe estar protegido con un código de acceso para utilizar Mattermost.\n\nVaya a Configuración > Identificación Facial y clave de acceso.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Este dispositivo debe estar protegido con un código de acceso para utilizar Mattermost.\n\nVaya a Configuración > Identificación Táctil y clave de acceso.",
|
||||
"mobile.managed.secured_by": "Asegurado por {vendor}",
|
||||
"mobile.managed.settings": "Ir a configuración",
|
||||
"mobile.markdown.code.copy_code": "Copiar código",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Marcado",
|
||||
"mobile.post_pre_header.pinned": "Anclado",
|
||||
"mobile.post_pre_header.pinned_flagged": "Anclado y Marcado",
|
||||
"mobile.post_textbox.empty.message": "Estas intentando enviar un mensaje vacío.\nPor favor asegurate de estar enviando un mensaje con texto o con al menos un archivo adjunto.",
|
||||
"mobile.post_textbox.empty.ok": "Aceptar",
|
||||
"mobile.post_textbox.empty.title": "Mensaje vacío",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Algunos archivos adjuntos no se han subido al servidor. ¿Quieres publicar el mensaje?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Error Adjuntando",
|
||||
"mobile.post.cancel": "Cancelar",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Supprimer les documents et les données",
|
||||
"mobile.advanced_settings.timezone": "Fuseau horaire",
|
||||
"mobile.advanced_settings.title": "Paramètres avancés",
|
||||
"mobile.alert_dialog.alertCancel": "Annuler",
|
||||
"mobile.android.camera_permission_denied_description": "Pour prendre des photos et des vidéos avec votre appareil photo, veuillez modifier vos paramètres d'autorisation.",
|
||||
"mobile.android.camera_permission_denied_title": "L'accès à la caméra est requis",
|
||||
"mobile.android.permission_denied_dismiss": "Rejeter",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Les dispositifs jailbreakés ne sont pas approuvés par {vendor}, veuillez quitter l'application.",
|
||||
"mobile.managed.not_secured.android": "Cet appareil doit être sécurisé avec un verrouillage d'écran pour pouvoir utiliser Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "Cet appareil doit être sécurisé avec un code pour pouvoir utiliser Mattermost.\n\nAllez dans Réglages > Face ID et code.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Cet appareil doit être sécurisé avec un code pour pouvoir utiliser Mattermost.\n\nAllez dans Réglages > Touch ID et code.",
|
||||
"mobile.managed.secured_by": "Sécurisé par {vendor}",
|
||||
"mobile.managed.settings": "Aller dans les paramètres",
|
||||
"mobile.markdown.code.copy_code": "Copier le code",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Marqué d'un indicateur",
|
||||
"mobile.post_pre_header.pinned": "Épinglé",
|
||||
"mobile.post_pre_header.pinned_flagged": "Épinglé et marqué d'un indicateur",
|
||||
"mobile.post_textbox.empty.message": "Vous êtes en train d'envoyer un message vide.\nVeuillez-vous assurer d'avoir spécifié un message ou d'avoir joint au moins un fichier.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Message vide",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Certains fichiers joints n'ont pas pu être envoyés au serveur. Voulez-vous vraiment envoyer votre message ?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Erreur de pièces jointes",
|
||||
"mobile.post.cancel": "Annuler",
|
||||
@@ -535,7 +534,7 @@
|
||||
"user.settings.general.email": "E-mail",
|
||||
"user.settings.general.emailCantUpdate": "L'adresse e-mail ne peut être mise à jour qu'en utilisant un navigateur web ou l'application de bureau.",
|
||||
"user.settings.general.emailGitlabCantUpdate": "La connexion s'effectue par Gitlab. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email}.",
|
||||
"user.settings.general.emailGoogleCantUpdate": "La connexion s'effectue par Gitlab. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email} .",
|
||||
"user.settings.general.emailGoogleCantUpdate": "La connexion s'effectue par Google. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email} .",
|
||||
"user.settings.general.emailHelp2": "L'envoi d'e-mails a été désactivé par votre administrateur système. Aucun e-mail de notification ne peut être envoyé.",
|
||||
"user.settings.general.emailLdapCantUpdate": "La connexion s'effectue par AD/LDAP. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email}.",
|
||||
"user.settings.general.emailOffice365CantUpdate": "La connexion s'effectue par Office 365. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications par e-mail est {email} .",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Cancella Documenti e Dati",
|
||||
"mobile.advanced_settings.timezone": "Fuso orario",
|
||||
"mobile.advanced_settings.title": "Impostazioni Avanzate",
|
||||
"mobile.alert_dialog.alertCancel": "Annulla",
|
||||
"mobile.android.camera_permission_denied_description": "Per scattare foto e girare video con la videocamera, modificare i permessi nelle impostazioni.",
|
||||
"mobile.android.camera_permission_denied_title": "L'accesso alla videocamera è richiesto",
|
||||
"mobile.android.permission_denied_dismiss": "Annulla",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Dispositivi con jailbreak considerati non sicuri da {vendor}, per favore uscire dall'app.",
|
||||
"mobile.managed.not_secured.android": "Questo dispositivo deve essere protetto con un blocco schermo per utilizzare Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "Questo dispositivo deve essere protetto da un codice per utilizzare Mattermost.\n\nAndare in Impostazioni > Face ID & Codice.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Questo dispositivo deve essere protetto da un codice per utilizzare Mattermost.\n\nAndare in Impostazioni > Touch ID & Codice.",
|
||||
"mobile.managed.secured_by": "Verificato da {vendor}",
|
||||
"mobile.managed.settings": "Vai alle impostazioni",
|
||||
"mobile.markdown.code.copy_code": "Copia codice",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Contrassegnato",
|
||||
"mobile.post_pre_header.pinned": "Bloccato",
|
||||
"mobile.post_pre_header.pinned_flagged": "Bloccato e Contrassegnato",
|
||||
"mobile.post_textbox.empty.message": "Stai cercando di inviare un messaggio vuoto.\nAssicurati di aver scritto del testo o di aver allegato almeno un file.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Messaggio Vuoto",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Impossibile caricare alcuni allegati, sicuro di voler pubblicare il messaggio?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Allegati non caricati",
|
||||
"mobile.post.cancel": "Annulla",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "ドキュメントとデータの削除",
|
||||
"mobile.advanced_settings.timezone": "タイムゾーン",
|
||||
"mobile.advanced_settings.title": "詳細設定",
|
||||
"mobile.alert_dialog.alertCancel": "キャンセル",
|
||||
"mobile.android.camera_permission_denied_description": "カメラで写真やビデオを撮影するために権限設定を変更してください。",
|
||||
"mobile.android.camera_permission_denied_title": "カメラへのアクセスを要求しています",
|
||||
"mobile.android.permission_denied_dismiss": "破棄",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Jailbrokenデバイスは{vendor}から信頼されていません。アプリを終了してください。",
|
||||
"mobile.managed.not_secured.android": "このデバイスでMattermostを使うにはスクリーンロックを設定する必要があります。",
|
||||
"mobile.managed.not_secured.ios": "このデバイスでMattermostを使うにはパスコードを設定する必要があります。\n\n設定 > Face IDとパスコード から設定してください。",
|
||||
"mobile.managed.not_secured.ios.touchId": "このデバイスでMattermostを使うにはパスコードを設定する必要があります。\n\n設定 > Touch IDとパスコード から設定してください。",
|
||||
"mobile.managed.secured_by": "{vendor}により保護されました",
|
||||
"mobile.managed.settings": "設定へ移動する",
|
||||
"mobile.markdown.code.copy_code": "コードのコピー",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "フラグ済み",
|
||||
"mobile.post_pre_header.pinned": "ピン留め済み",
|
||||
"mobile.post_pre_header.pinned_flagged": "ピン留めとフラグ済み",
|
||||
"mobile.post_textbox.empty.message": "空のメッセージを送信しようとしています。\n何かメッセージか少なくとも一つの添付ファイルがあることを確認してください。",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "空のメッセージ",
|
||||
"mobile.post_textbox.uploadFailedDesc": "サーバーへの添付ファイルのアップロードが失敗しました。メッセージを投稿しますか?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "添付エラー",
|
||||
"mobile.post.cancel": "キャンセル",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "데이터 및 캐시삭제",
|
||||
"mobile.advanced_settings.timezone": "표준 시간대",
|
||||
"mobile.advanced_settings.title": "고급 설정",
|
||||
"mobile.alert_dialog.alertCancel": "취소",
|
||||
"mobile.android.camera_permission_denied_description": "사진이나 비디오를 촬영 하려면, 권한 설정을 변경해 주세요.",
|
||||
"mobile.android.camera_permission_denied_title": "카메라 접근이 필요합니다.",
|
||||
"mobile.android.permission_denied_dismiss": "Dismiss",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Jailbroken devices are not trusted by {vendor}, please exit the app.",
|
||||
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
|
||||
"mobile.managed.not_secured.ios.touchId": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Touch ID & Passcode.",
|
||||
"mobile.managed.secured_by": "Secured by {vendor}",
|
||||
"mobile.managed.settings": "Go to settings",
|
||||
"mobile.markdown.code.copy_code": "Copy Code",
|
||||
@@ -313,7 +315,7 @@
|
||||
"mobile.notification_settings_mobile.vibrate": "Vibrate",
|
||||
"mobile.notification_settings.auto_responder_short": "Automatic Replies",
|
||||
"mobile.notification_settings.auto_responder.default_message": "Hello, I am out of office and unable to respond to messages.",
|
||||
"mobile.notification_settings.auto_responder.enabled": "Enabled",
|
||||
"mobile.notification_settings.auto_responder.enabled": "활성화",
|
||||
"mobile.notification_settings.auto_responder.footer_message": "개인 메시지에 대한 응답을 자동으로 전송되는 사용자 지정 메시지를 설정합니다. 공개, 비공개 채널의 멘션은 자동 응답 기능이 실행되지 않습니다. 자동 응답을 사용하면 상태가 부재중으로 설정되고 이메일 알림, 푸쉬 알림은 해제됩니다.",
|
||||
"mobile.notification_settings.auto_responder.message_placeholder": "메시지",
|
||||
"mobile.notification_settings.auto_responder.message_title": "CUSTOM MESSAGE",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Flagged",
|
||||
"mobile.post_pre_header.pinned": "공지사항",
|
||||
"mobile.post_pre_header.pinned_flagged": "Pinned and Flagged",
|
||||
"mobile.post_textbox.empty.message": "You are trying to send an empty message.\nPlease make sure you have a message or at least one attached file.",
|
||||
"mobile.post_textbox.empty.ok": "확인",
|
||||
"mobile.post_textbox.empty.title": "Empty Message",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Some attachments failed to upload to the server. Are you sure you want to post the message?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Attachment failure",
|
||||
"mobile.post.cancel": "취소",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Delete Documents & Data",
|
||||
"mobile.advanced_settings.timezone": "Timezone",
|
||||
"mobile.advanced_settings.title": "Geavanceerde instellingen",
|
||||
"mobile.alert_dialog.alertCancel": "Annuleren",
|
||||
"mobile.android.camera_permission_denied_description": "To take photos and videos with your camera, please change your permission settings.",
|
||||
"mobile.android.camera_permission_denied_title": "Camera access is required",
|
||||
"mobile.android.permission_denied_dismiss": "Dismiss",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Jailbroken devices are not trusted by {vendor}, please exit the app.",
|
||||
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
|
||||
"mobile.managed.not_secured.ios.touchId": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Touch ID & Passcode.",
|
||||
"mobile.managed.secured_by": "Secured by {vendor}",
|
||||
"mobile.managed.settings": "Go to settings",
|
||||
"mobile.markdown.code.copy_code": "Copy Code",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Flagged",
|
||||
"mobile.post_pre_header.pinned": "Pinned",
|
||||
"mobile.post_pre_header.pinned_flagged": "Pinned and Flagged",
|
||||
"mobile.post_textbox.empty.message": "You are trying to send an empty message.\nPlease make sure you have a message or at least one attached file.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Empty Message",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Some attachments failed to upload to the server. Are you sure you want to post the message?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Attachment failure",
|
||||
"mobile.post.cancel": "Annuleren",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Usuń dokumenty i dane",
|
||||
"mobile.advanced_settings.timezone": "Strefa czasowa",
|
||||
"mobile.advanced_settings.title": "Zaawansowane Ustawienia",
|
||||
"mobile.alert_dialog.alertCancel": "Anuluj",
|
||||
"mobile.android.camera_permission_denied_description": "Aby robić zdjęcia i filmy używając aparatu, proszę zmień swoje ustawienia uprawnień.",
|
||||
"mobile.android.camera_permission_denied_title": "Wymagane są uprawnienia Aparatu",
|
||||
"mobile.android.permission_denied_dismiss": "Odrzuć",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Urządzenia z Jailbreakiem nie są zaufane przez {vendor}, proszę wyjść z aplikacji.",
|
||||
"mobile.managed.not_secured.android": "To urządzenie musi zostać zabezpieczone blokadą ekranu, aby użyć Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "To urządzenie musi zostać zabezpieczone hasłem, aby używać Mattermost.\n\nIdz do Ustawienia > Identyfikacja twarzy i hasło.",
|
||||
"mobile.managed.not_secured.ios.touchId": "To urządzenie musi zostać zabezpieczone hasłem, aby używać Mattermost.\n\nPrzejdź do Ustawienia > Touch ID i Hasło.",
|
||||
"mobile.managed.secured_by": "Zabezpieczony przez {vendor}",
|
||||
"mobile.managed.settings": "Idz do ustawień",
|
||||
"mobile.markdown.code.copy_code": "Skopiuj Kod",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Oflagowany",
|
||||
"mobile.post_pre_header.pinned": "Przypięty",
|
||||
"mobile.post_pre_header.pinned_flagged": "Przypięty i oflagowany",
|
||||
"mobile.post_textbox.empty.message": "Próbujesz wysłać pustą wiadomość.\nUpewnij się, że masz wiadomość lub co najmniej jeden załączony plik.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Pusta Wiadomość",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Niektórych załączników nie udało się przesłać na serwer. Czy na pewno chcesz opublikować wiadomość?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Błąd załącznika",
|
||||
"mobile.post.cancel": "Anuluj",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Remoer Documentos & Dados",
|
||||
"mobile.advanced_settings.timezone": "Fuso horário",
|
||||
"mobile.advanced_settings.title": "Configurações Avançadas",
|
||||
"mobile.alert_dialog.alertCancel": "Cancelar",
|
||||
"mobile.android.camera_permission_denied_description": "Para fazer fotos e vides com sua camera, por favor altere as permissões.",
|
||||
"mobile.android.camera_permission_denied_title": "Acesso a camera é necessário",
|
||||
"mobile.android.permission_denied_dismiss": "Ignorar",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Os dispositivos com Jailbroken não são confiáveis para {vendor}, por favor saia do aplicativo.",
|
||||
"mobile.managed.not_secured.android": "Este dispositivo deve ser protegido com um bloqueio de tela para usar o Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "Este dispositivo deve ser protegido por uma senha para usar o Mattermost.\n\nVá para Configurações > Face ID & Passcode.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Este dispositivo deve ser protegido por uma senha para usar o Mattermost.\n\nVá em Ajustes > Touch ID & Código.",
|
||||
"mobile.managed.secured_by": "Garantido por {vendor}",
|
||||
"mobile.managed.settings": "Vá para configurações",
|
||||
"mobile.markdown.code.copy_code": "Copiar Código",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Marcado",
|
||||
"mobile.post_pre_header.pinned": "Fixado",
|
||||
"mobile.post_pre_header.pinned_flagged": "Fixado e Marcado",
|
||||
"mobile.post_textbox.empty.message": "Você está tentando enviar uma mensagem vazia.\nCertifique-se de ter uma mensagem ou pelo menos um arquivo em anexo.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Mensagem Vazia",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Alguns anexos não foram enviados para o servidor. Tem certeza de que deseja publicar a mensagem?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Falha no anexo",
|
||||
"mobile.post.cancel": "Cancelar",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Ștergeți documentele și datele",
|
||||
"mobile.advanced_settings.timezone": "Fus orar",
|
||||
"mobile.advanced_settings.title": "Configurări Avansate Şablon",
|
||||
"mobile.alert_dialog.alertCancel": "Anulare",
|
||||
"mobile.android.camera_permission_denied_description": "Pentru a face fotografii și videoclipuri cu camera foto, modificați setările de permisiune.",
|
||||
"mobile.android.camera_permission_denied_title": "Accesul la cameră este necesar",
|
||||
"mobile.android.permission_denied_dismiss": "Elimină",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Dispozitivele jailbroken nu au încredere în {vendor}, închideți aplicația.",
|
||||
"mobile.managed.not_secured.android": "Acest dispozitiv trebuie să fie asigurat cu un dispozitiv de blocare a ecranului pentru a utiliza Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "Acest dispozitiv trebuie să fie securizat cu un cod de acces pentru a utiliza Mattermost.\n\nAccesați Setări > ID-ul feței și codul de acces.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Acest dispozitiv trebuie să fie securizat cu un cod de acces pentru a utiliza Mattermost.\n \nAccesați Setări > ID-ul feței și codul de acces.",
|
||||
"mobile.managed.secured_by": "Securizat de {vendor}",
|
||||
"mobile.managed.settings": "Mergi la Setări",
|
||||
"mobile.markdown.code.copy_code": "Copiați codul",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Marcat",
|
||||
"mobile.post_pre_header.pinned": "Fixat",
|
||||
"mobile.post_pre_header.pinned_flagged": "Fixat și marcat",
|
||||
"mobile.post_textbox.empty.message": "Încercați să trimiteți un mesaj gol.\nAsigurați-vă că aveți un mesaj sau cel puțin un fișier atașat.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Mesaj gol",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Unele atașări nu au putut fi încărcate pe server. Sigur doriți să încărcați mesajul?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Eșec atașament",
|
||||
"mobile.post.cancel": "Anulare",
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"intro_messages.group_message": "Начало истории групповых сообщений с участниками. Размещённые здесь сообщения и файлы не видны за пределами этой области.",
|
||||
"intro_messages.noCreator": "Канал: {name}, созданный {date}",
|
||||
"intro_messages.onlyInvited": " Только приглашенные пользователи могут видеть этот приватный канал.",
|
||||
"last_users_message.added_to_channel.type": "**добавлены на канал**. Кем: {actor}.",
|
||||
"last_users_message.added_to_channel.type": "{actor} добавил **added to the channel**.",
|
||||
"last_users_message.added_to_team.type": "были **добавлены в команду** пользователем {actor}.",
|
||||
"last_users_message.first": "{firstUser} и ",
|
||||
"last_users_message.joined_channel.type": "**выполнен вход на канал**",
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Удалить файловый кэш",
|
||||
"mobile.advanced_settings.timezone": "Часовой пояс",
|
||||
"mobile.advanced_settings.title": "Дополнительные параметры",
|
||||
"mobile.alert_dialog.alertCancel": "Отмена",
|
||||
"mobile.android.camera_permission_denied_description": "Чтобы делать фото и видео, вам нужно разрешить доступ к камере.",
|
||||
"mobile.android.camera_permission_denied_title": "Требуется доступ к камере",
|
||||
"mobile.android.permission_denied_dismiss": "Отказать",
|
||||
@@ -254,7 +255,7 @@
|
||||
"mobile.file_upload.camera_video": "Снять видео",
|
||||
"mobile.file_upload.library": "Библиотека изображений",
|
||||
"mobile.file_upload.max_warning": "Вы можете загрузить не более 5 файлов за раз.",
|
||||
"mobile.file_upload.unsupportedMimeType": "Only files of the following MIME type can be uploaded:\n{mimeTypes}",
|
||||
"mobile.file_upload.unsupportedMimeType": "Можно загружать только файлы следующего типа:\n{mimeTypes}",
|
||||
"mobile.file_upload.video": "Библиотека видео",
|
||||
"mobile.flagged_posts.empty_description": "Флаги - один из способов, пометки сообщений для последующей деятельности. Ваши флаги не могут быть просмотрены другими пользователями.",
|
||||
"mobile.flagged_posts.empty_title": "Отмеченные сообщения",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Устройства с джейлбрейком не являются доверенными {vendor}, выйдите из приложения.",
|
||||
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
|
||||
"mobile.managed.not_secured.ios.touchId": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Touch ID & Passcode.",
|
||||
"mobile.managed.secured_by": "Защищено {vendor}",
|
||||
"mobile.managed.settings": "Go to settings",
|
||||
"mobile.markdown.code.copy_code": "Скопировать код",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Отмеченные",
|
||||
"mobile.post_pre_header.pinned": "Прикреплённые",
|
||||
"mobile.post_pre_header.pinned_flagged": "Прикреплённые и отмеченные",
|
||||
"mobile.post_textbox.empty.message": "Вы пытаетесь отправить пустое сообщение.\nПожалуйста, убедитесь, что вы что-то написали или приложили хотя-бы один файл.",
|
||||
"mobile.post_textbox.empty.ok": "Ок",
|
||||
"mobile.post_textbox.empty.title": "Пустое сообщение",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Некоторые вложения не были загружены на сервер. Вы уверены, что хотите отправить сообщение?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Ошибка загрузки",
|
||||
"mobile.post.cancel": "Отмена",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Belge ve Verileri Sil",
|
||||
"mobile.advanced_settings.timezone": "Saat Dilimi",
|
||||
"mobile.advanced_settings.title": "Gelişmiş Ayarlar",
|
||||
"mobile.alert_dialog.alertCancel": "İptal",
|
||||
"mobile.android.camera_permission_denied_description": "Kameranız ile fotoğraf ve görüntü çekebilmek için izin ayarlarınızı değiştirmelisiniz.",
|
||||
"mobile.android.camera_permission_denied_title": "Kameraya erişim izni gereklidir",
|
||||
"mobile.android.permission_denied_dismiss": "Kapat",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Jailbreak uygulanmış aygıtlara {vendor} tarafından güvenilmiyor, lütfen uygulamadan çıkın.",
|
||||
"mobile.managed.not_secured.android": "Bu aygıtta Mattermost kullanılabilmesi için ekran kilidi güvenliğinin etkinleştirilmesi gerekir.",
|
||||
"mobile.managed.not_secured.ios": "Bu aygıtta Mattermost kullanılabilmesi için parola güvenliğinin etkinleştirilmesi gerekir.\n\nAyarlar > Yüz Tanıma ve Parola bölümünden ayarlayabilirsiniz.",
|
||||
"mobile.managed.not_secured.ios.touchId": "Bu aygıtta Mattermost kullanılabilmesi için parola güvenliğinin etkinleştirilmesi gerekir.\n\nAyarlar > Touch ID ve Parola bölümünden ayarlayabilirsiniz.",
|
||||
"mobile.managed.secured_by": "{vendor} tarafından korunuyor",
|
||||
"mobile.managed.settings": "Ayarlara git",
|
||||
"mobile.markdown.code.copy_code": "Kodu Kopyala",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "İşaretlenmiş",
|
||||
"mobile.post_pre_header.pinned": "Sabitlenmiş",
|
||||
"mobile.post_pre_header.pinned_flagged": "Sabitlenmiş ve İşaretlenmiş",
|
||||
"mobile.post_textbox.empty.message": "Boş bir ileti göndermeye çalışıyorsunuz.\nLütfen bir ileti yazdığınızdan ya da bir dosya eklediğinizden emin olun.",
|
||||
"mobile.post_textbox.empty.ok": "Tamam",
|
||||
"mobile.post_textbox.empty.title": "Boş İleti",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Bazı ek dosyaları sunucuya yüklenemedi. İletiyi göndermek istediğinize emin misiniz?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Ek dosya yüklenemedi",
|
||||
"mobile.post.cancel": "İptal",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "Delete Documents & Data",
|
||||
"mobile.advanced_settings.timezone": "Часовий пояс",
|
||||
"mobile.advanced_settings.title": "Додаткові параметри ",
|
||||
"mobile.alert_dialog.alertCancel": "Відміна",
|
||||
"mobile.android.camera_permission_denied_description": "Щоб зробити фотографії та відеозаписи з вашою камерою, будь-ласка, змініть налаштування вашого дозволу.",
|
||||
"mobile.android.camera_permission_denied_title": "Потрібен доступ до камери ",
|
||||
"mobile.android.permission_denied_dismiss": "Відхилити",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "Пристрої з Jailbroken не є довіреними {vendor}, вийдіть з програми.",
|
||||
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
|
||||
"mobile.managed.not_secured.ios.touchId": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Touch ID & Passcode.",
|
||||
"mobile.managed.secured_by": "Захищено {vendor}",
|
||||
"mobile.managed.settings": "Go to settings",
|
||||
"mobile.markdown.code.copy_code": "Копія коду",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "Позначено",
|
||||
"mobile.post_pre_header.pinned": "Прикріплено",
|
||||
"mobile.post_pre_header.pinned_flagged": "Прикріплений і позначений",
|
||||
"mobile.post_textbox.empty.message": "Ви намагаєтеся надіслати порожнє повідомлення.\nБудь-ласка, переконайтеся, що у вас є повідомлення або щонайменше один прикріплений файл.",
|
||||
"mobile.post_textbox.empty.ok": "OK",
|
||||
"mobile.post_textbox.empty.title": "Порожнє повідомлення",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Деякі вкладення не змогли завантажити на сервер. Ви впевнені, що хочете опублікувати це повідомлення? ",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Вкладення несправності",
|
||||
"mobile.post.cancel": "Відміна ",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "删除文档及数据",
|
||||
"mobile.advanced_settings.timezone": "时区",
|
||||
"mobile.advanced_settings.title": "高级设置",
|
||||
"mobile.alert_dialog.alertCancel": "取消",
|
||||
"mobile.android.camera_permission_denied_description": "请更改您的权限设定以拍照摄影。",
|
||||
"mobile.android.camera_permission_denied_title": "需要摄像头权限",
|
||||
"mobile.android.permission_denied_dismiss": "解除",
|
||||
@@ -159,8 +160,8 @@
|
||||
"mobile.channel_info.alertTitleDeleteChannel": "归档 {term}",
|
||||
"mobile.channel_info.alertTitleLeaveChannel": "离开 {term}",
|
||||
"mobile.channel_info.alertYes": "是",
|
||||
"mobile.channel_info.copy_header": "Copy Header",
|
||||
"mobile.channel_info.copy_purpose": "Copy Purpose",
|
||||
"mobile.channel_info.copy_header": "复制标题",
|
||||
"mobile.channel_info.copy_purpose": "复制用途",
|
||||
"mobile.channel_info.delete_failed": "我们无法归档频道 {displayName}。请检查您的网络连接再尝试。",
|
||||
"mobile.channel_info.edit": "编辑频道",
|
||||
"mobile.channel_info.privateChannel": "私有频道",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "越狱的设备不被 {vendor} 信任,请退出应用。",
|
||||
"mobile.managed.not_secured.android": "此设备必须开启屏幕锁才能使用 Mattermost。",
|
||||
"mobile.managed.not_secured.ios": "此设备必须开启密码锁才能使用 Mattermost。\n\n前往设置 > 面容 ID 与密码。",
|
||||
"mobile.managed.not_secured.ios.touchId": "此设备必须开启密码锁才能使用 Mattermost。\n\n前往设置 > 面容 ID 与密码。",
|
||||
"mobile.managed.secured_by": "被 {vendor} 安全保护",
|
||||
"mobile.managed.settings": "前往设置",
|
||||
"mobile.markdown.code.copy_code": "复制代码",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "已标记",
|
||||
"mobile.post_pre_header.pinned": "已置顶",
|
||||
"mobile.post_pre_header.pinned_flagged": "已被置顶及标记",
|
||||
"mobile.post_textbox.empty.message": "您正在尝试发送空白消息。\n请确认有消息或至少附加一个附件。",
|
||||
"mobile.post_textbox.empty.ok": "确定",
|
||||
"mobile.post_textbox.empty.title": "空消息",
|
||||
"mobile.post_textbox.uploadFailedDesc": "一些附件上传失败,您确定发送此消息?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "附件失败",
|
||||
"mobile.post.cancel": "取消",
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
"mobile.advanced_settings.delete_title": "刪除文件與資料",
|
||||
"mobile.advanced_settings.timezone": "時區",
|
||||
"mobile.advanced_settings.title": "進階設定",
|
||||
"mobile.alert_dialog.alertCancel": "取消",
|
||||
"mobile.android.camera_permission_denied_description": "請變更權限設定以用相機拍照攝影。",
|
||||
"mobile.android.camera_permission_denied_title": "需要存取相機",
|
||||
"mobile.android.permission_denied_dismiss": "解除",
|
||||
@@ -277,6 +278,7 @@
|
||||
"mobile.managed.jailbreak": "{vendor} 不信任越獄後的裝置,請關閉應用程式。",
|
||||
"mobile.managed.not_secured.android": "這裝置必須要設定螢幕鎖以使用 Mattermost",
|
||||
"mobile.managed.not_secured.ios": "這裝置必須要設定密碼以使用 Mattermost前往 設定 > Face ID 與密碼",
|
||||
"mobile.managed.not_secured.ios.touchId": "這裝置必須要設定密碼以使用 Mattermost。前往 設定 > Touch ID 與密碼",
|
||||
"mobile.managed.secured_by": "受到 {vendor} 保護",
|
||||
"mobile.managed.settings": "前往設定",
|
||||
"mobile.markdown.code.copy_code": "複製代碼",
|
||||
@@ -351,9 +353,6 @@
|
||||
"mobile.post_pre_header.flagged": "已被標記",
|
||||
"mobile.post_pre_header.pinned": "已釘選",
|
||||
"mobile.post_pre_header.pinned_flagged": "被釘選及標記",
|
||||
"mobile.post_textbox.empty.message": "正在嘗試發送空白訊息。\n請確定有訊息或是至少有附加 1 個檔案。",
|
||||
"mobile.post_textbox.empty.ok": "確定",
|
||||
"mobile.post_textbox.empty.title": "空白訊息",
|
||||
"mobile.post_textbox.uploadFailedDesc": "部份附加檔案上傳時失敗,請再次確定要發布此訊息?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "附加檔案失敗",
|
||||
"mobile.post.cancel": "取消",
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
7F240ADB220E089300637665 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F240ADA220E089300637665 /* Item.swift */; };
|
||||
7F240ADD220E094A00637665 /* TeamsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F240ADC220E094A00637665 /* TeamsViewController.swift */; };
|
||||
7F2691A11EE1DC6A007574FE /* libRNNotifications.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F2691A01EE1DC51007574FE /* libRNNotifications.a */; };
|
||||
7F26C1CC219C463D00FEB42D /* libRNCWebView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F26C1CB219C463300FEB42D /* libRNCWebView.a */; };
|
||||
7F292A711E8AB73400A450A3 /* SplashScreenResource in Resources */ = {isa = PBXBuildFile; fileRef = 7F292A701E8AB73400A450A3 /* SplashScreenResource */; };
|
||||
7F292AA61E8ABB1100A450A3 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F292AA41E8ABB1100A450A3 /* LaunchScreen.xib */; };
|
||||
7F292AA71E8ABB1100A450A3 /* splash.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F292AA51E8ABB1100A450A3 /* splash.png */; };
|
||||
@@ -93,6 +92,7 @@
|
||||
7FABE04622137F5C00D0F595 /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; };
|
||||
7FABE0562213884700D0F595 /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; };
|
||||
7FBB5E9B1E1F5A4B000DE18A /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; };
|
||||
7FD1DD0823273C6000E0D948 /* libRNCWebView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F26C1CB219C463300FEB42D /* libRNCWebView.a */; };
|
||||
7FDB92B11F706F58006CDFD1 /* libRNImagePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDB92A71F706F45006CDFD1 /* libRNImagePicker.a */; };
|
||||
7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB10971F6101710039A015 /* BlurAppScreen.m */; };
|
||||
7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109A1F61019C0039A015 /* MattermostManaged.m */; };
|
||||
@@ -945,7 +945,7 @@
|
||||
7FDB92B11F706F58006CDFD1 /* libRNImagePicker.a in Frameworks */,
|
||||
7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */,
|
||||
7F43D5DE1F6BF96A001FC614 /* libRCTYouTube.a in Frameworks */,
|
||||
7F26C1CC219C463D00FEB42D /* libRNCWebView.a in Frameworks */,
|
||||
7FD1DD0823273C6000E0D948 /* libRNCWebView.a in Frameworks */,
|
||||
7F88A04422270FD400DC5DE2 /* libRNSentry.a in Frameworks */,
|
||||
7F43D6061F6BF9EB001FC614 /* libPods-Mattermost.a in Frameworks */,
|
||||
A9B746D2CFA9CEBFB8AE2B5B /* libPods-Mattermost.a in Frameworks */,
|
||||
@@ -2785,7 +2785,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 221;
|
||||
CURRENT_PROJECT_VERSION = 234;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -2845,7 +2845,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 221;
|
||||
CURRENT_PROJECT_VERSION = 234;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.22.0</string>
|
||||
<string>1.23.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -34,7 +34,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>221</string>
|
||||
<string>234</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
@@ -56,6 +56,8 @@
|
||||
</dict>
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>Let Mattermost access your Media files</string>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>Share post data <span class="x x-first x-last">across</span> devices with Mattermost</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>Share post data accross devices with Mattermost</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import "MattermostManaged.h"
|
||||
#import <LocalAuthentication/LocalAuthentication.h>
|
||||
#import <UploadAttachments/MMMConstants.h>
|
||||
|
||||
@implementation MattermostManaged {
|
||||
@@ -167,4 +168,17 @@ RCT_EXPORT_METHOD(quitApp)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(supportsFaceId:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
LAContext *context = [[LAContext alloc] init];
|
||||
NSError *error;
|
||||
|
||||
// context.biometryType is initialized when canEvaluatePolicy, regardless of the error or return value
|
||||
[context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error];
|
||||
|
||||
resolve(@{
|
||||
@"supportsFaceId": @(context.biometryType == LABiometryTypeFaceID && @available(iOS 11.0, *))
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.22.0</string>
|
||||
<string>1.23.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>221</string>
|
||||
<string>234</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
||||
@@ -47,9 +47,16 @@ class ShareViewController: SLComposeServiceViewController {
|
||||
var error: NSError?
|
||||
if !context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
|
||||
if let error = error, error.code == kLAErrorPasscodeNotSet {
|
||||
var message = "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Touch ID & Passcode."
|
||||
if #available(iOS 11.0, *) {
|
||||
if (context.biometryType == LABiometryType.faceID) {
|
||||
message = "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode."
|
||||
}
|
||||
}
|
||||
|
||||
self.showErrorMessage(
|
||||
title: "",
|
||||
message: "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
|
||||
message: message,
|
||||
VC: self
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.22.0</string>
|
||||
<string>1.23.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>221</string>
|
||||
<string>234</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.22.0</string>
|
||||
<string>1.23.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>221</string>
|
||||
<string>234</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@@ -174,16 +174,7 @@ RCT_EXPORT_METHOD(
|
||||
WKHTTPCookieStore *cookieStore = [[WKWebsiteDataStore defaultDataStore] httpCookieStore];
|
||||
[cookieStore getAllCookies:^(NSArray<NSHTTPCookie *> *allCookies) {
|
||||
for(NSHTTPCookie *currentCookie in allCookies) {
|
||||
// Uses the NSHTTPCookie directly has no effect, nor deleted the cookie nor thrown an error.
|
||||
// Create a new cookie with the given values and delete this one do the work.
|
||||
NSMutableDictionary<NSHTTPCookiePropertyKey, id> *cookieData = [NSMutableDictionary dictionary];
|
||||
[cookieData setValue:currentCookie.name forKey:NSHTTPCookieName];
|
||||
[cookieData setValue:currentCookie.value forKey:NSHTTPCookieValue];
|
||||
[cookieData setValue:currentCookie.domain forKey:NSHTTPCookieDomain];
|
||||
[cookieData setValue:currentCookie.path forKey:NSHTTPCookiePath];
|
||||
|
||||
NSHTTPCookie *newCookie = [NSHTTPCookie cookieWithProperties:cookieData];
|
||||
[cookieStore deleteCookie:newCookie completionHandler:^{}];
|
||||
[cookieStore deleteCookie:currentCookie completionHandler:^{}];
|
||||
}
|
||||
resolve(nil);
|
||||
}];
|
||||
|
||||
218
package-lock.json
generated
218
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mattermost-mobile",
|
||||
"version": "1.22.0",
|
||||
"version": "1.23.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -904,9 +904,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-async-to-generator": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz",
|
||||
"integrity": "sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==",
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz",
|
||||
"integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==",
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.0.0",
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
@@ -1119,12 +1119,105 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-object-super": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz",
|
||||
"integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==",
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz",
|
||||
"integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
"@babel/helper-replace-supers": "^7.1.0"
|
||||
"@babel/helper-replace-supers": "^7.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
|
||||
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz",
|
||||
"integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.5.5",
|
||||
"jsesc": "^2.5.1",
|
||||
"lodash": "^4.17.13",
|
||||
"source-map": "^0.5.0",
|
||||
"trim-right": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz",
|
||||
"integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.5.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-replace-supers": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz",
|
||||
"integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==",
|
||||
"requires": {
|
||||
"@babel/helper-member-expression-to-functions": "^7.5.5",
|
||||
"@babel/helper-optimise-call-expression": "^7.0.0",
|
||||
"@babel/traverse": "^7.5.5",
|
||||
"@babel/types": "^7.5.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
|
||||
"integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.4.4"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz",
|
||||
"integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g=="
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz",
|
||||
"integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
"@babel/generator": "^7.5.5",
|
||||
"@babel/helper-function-name": "^7.1.0",
|
||||
"@babel/helper-split-export-declaration": "^7.4.4",
|
||||
"@babel/parser": "^7.5.5",
|
||||
"@babel/types": "^7.5.5",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.13"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz",
|
||||
"integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==",
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-parameters": {
|
||||
@@ -3550,9 +3643,9 @@
|
||||
"integrity": "sha512-Aksg16keqrxaluFRZwmo8O8ppP9TFylyCEwBElmxeZ+a6DQAvyMn5nS3n+lgSpkYsrwU2ZGVjDluhkjtBrkEqQ=="
|
||||
},
|
||||
"@react-native-community/cli": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-1.10.0.tgz",
|
||||
"integrity": "sha512-48tIWsMKhwbDsKhe5tYcsspsAy7aR3J/yRjdsVh+M2qkKEASe66Xbhiw5RK2nhfzd1IdOdlIxNMiC+9uad6NMQ==",
|
||||
"version": "1.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-1.11.2.tgz",
|
||||
"integrity": "sha512-5NuYd30f5PCTrGUbZLnusZKv5nfTWvTDTRa/3Q4vwdMnUQrhm9sZXWGQ5CnFoQ7cE58EAqhj6/ShXeJF3DZ9uQ==",
|
||||
"requires": {
|
||||
"chalk": "^1.1.1",
|
||||
"commander": "^2.19.0",
|
||||
@@ -3618,9 +3711,9 @@
|
||||
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
||||
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3681,9 +3774,9 @@
|
||||
}
|
||||
},
|
||||
"@sentry/cli": {
|
||||
"version": "1.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.43.0.tgz",
|
||||
"integrity": "sha512-NnJTT0DjzZB8+CxaisVEEExtns5eHkIFy1nqbM8tzNZ6hREshGf5kFZPPTmHairvaVMk1KWa7w8EqNl7jg3KUA==",
|
||||
"version": "1.47.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.47.1.tgz",
|
||||
"integrity": "sha512-WijaRu1lb99OL6rHee6uOSb1wDyNCbrWcTJoRCuZD83K2fw3U58p68nli/y8CoMwQ55Mrg6CgtY8pmBiuseG0A==",
|
||||
"requires": {
|
||||
"fs-copy-file-sync": "^1.1.1",
|
||||
"https-proxy-agent": "^2.2.1",
|
||||
@@ -3763,9 +3856,9 @@
|
||||
}
|
||||
},
|
||||
"external-editor": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz",
|
||||
"integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
|
||||
"requires": {
|
||||
"chardet": "^0.7.0",
|
||||
"iconv-lite": "^0.4.24",
|
||||
@@ -3781,9 +3874,9 @@
|
||||
}
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz",
|
||||
"integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==",
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
|
||||
"integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
|
||||
"requires": {
|
||||
"ansi-escapes": "^3.2.0",
|
||||
"chalk": "^2.4.2",
|
||||
@@ -3791,7 +3884,7 @@
|
||||
"cli-width": "^2.0.0",
|
||||
"external-editor": "^3.0.3",
|
||||
"figures": "^2.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash": "^4.17.12",
|
||||
"mute-stream": "0.0.7",
|
||||
"run-async": "^2.2.0",
|
||||
"rxjs": "^6.4.0",
|
||||
@@ -3856,9 +3949,9 @@
|
||||
}
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
|
||||
"integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
|
||||
"integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
|
||||
"requires": {
|
||||
"p-try": "^2.0.0"
|
||||
}
|
||||
@@ -4084,9 +4177,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
|
||||
"integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
|
||||
"requires": {
|
||||
"es6-promisify": "^5.0.0"
|
||||
}
|
||||
@@ -6216,9 +6309,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz",
|
||||
"integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q=="
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
||||
},
|
||||
"es6-promisify": {
|
||||
"version": "5.0.0",
|
||||
@@ -6512,10 +6605,13 @@
|
||||
}
|
||||
},
|
||||
"eslint-utils": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz",
|
||||
"integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==",
|
||||
"dev": true
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
|
||||
"integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-visitor-keys": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"eslint-visitor-keys": {
|
||||
"version": "1.0.0",
|
||||
@@ -7108,9 +7204,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
||||
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -8600,11 +8696,11 @@
|
||||
}
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
|
||||
"integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz",
|
||||
"integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==",
|
||||
"requires": {
|
||||
"agent-base": "^4.1.0",
|
||||
"agent-base": "^4.3.0",
|
||||
"debug": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -8617,9 +8713,9 @@
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -15255,9 +15351,9 @@
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raven-js": {
|
||||
"version": "3.27.1",
|
||||
"resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.27.1.tgz",
|
||||
"integrity": "sha512-r/9CwSbaGfBFjo4hGR45DAmrukUKkQ4HdMu80PlVLDY1t8f9b4aaZzTsFegaafu7EGhEYougWDJ9/IcTdYdLXQ=="
|
||||
"version": "3.27.2",
|
||||
"resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.27.2.tgz",
|
||||
"integrity": "sha512-mFWQcXnhRFEQe5HeFroPaEghlnqy7F5E2J3Fsab189ondqUzcjwSVi7el7F36cr6PvQYXoZ1P2F5CSF2/azeMQ=="
|
||||
},
|
||||
"react": {
|
||||
"version": "16.8.6",
|
||||
@@ -15292,9 +15388,9 @@
|
||||
"integrity": "sha512-WUSQJ4P/wWcusaH+zZmbECOk7H5N2pOIl0vzheeornkIMhu+qrNdGFm0bDZLCb0hSF0jf/kH1SgkNGfBdTc4wA=="
|
||||
},
|
||||
"react-devtools-core": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-3.6.1.tgz",
|
||||
"integrity": "sha512-I/LSX+tpeTrGKaF1wXSfJ/kP+6iaP2JfshEjW8LtQBdz6c6HhzOJtjZXhqOUrAdysuey8M1/JgPY1flSVVt8Ig==",
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-3.6.3.tgz",
|
||||
"integrity": "sha512-+P+eFy/yo8Z/UH9J0DqHZuUM5+RI2wl249TNvMx3J2jpUomLQa4Zxl56GEotGfw3PIP1eI+hVf1s53FlUONStQ==",
|
||||
"requires": {
|
||||
"shell-quote": "^1.6.1",
|
||||
"ws": "^3.3.1"
|
||||
@@ -15423,9 +15519,9 @@
|
||||
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
||||
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -15624,9 +15720,9 @@
|
||||
"integrity": "sha512-fzCW5SiYP6qCZyDHebaElHonIFr8NFrZK9JDkxFLnpxMJih4d+HQ4rHyOs0Z4Gb/FjyCVbRH7RtEnjeQ0XffMg=="
|
||||
},
|
||||
"react-native-sentry": {
|
||||
"version": "0.43.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-sentry/-/react-native-sentry-0.43.1.tgz",
|
||||
"integrity": "sha512-oDWPLlXBbTFOEzqqxGihFO3DoBkEiQCQKgkF4av07aMdW+dKJxagwFjQW4vkoclNdK3w0Nfjc8kd7I7loDAcnA==",
|
||||
"version": "0.43.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-sentry/-/react-native-sentry-0.43.2.tgz",
|
||||
"integrity": "sha512-MDrMkt/ntyQhhn+b3EVUuB/lXvQbSLvCOpkAvioNOzddoyo2R6i3rVZ3aZmPN1oK3O9GFE4tSa0M+eTHni7AaQ==",
|
||||
"requires": {
|
||||
"@sentry/wizard": "^0.13.0",
|
||||
"raven-js": "^3.27.1"
|
||||
@@ -15868,8 +15964,8 @@
|
||||
}
|
||||
},
|
||||
"react-native-webview": {
|
||||
"version": "github:mattermost/react-native-webview#34e1dbef70413134ddda85df863c962f24b8826e",
|
||||
"from": "github:mattermost/react-native-webview#34e1dbef70413134ddda85df863c962f24b8826e",
|
||||
"version": "github:mattermost/react-native-webview#b5e22940a613869d3999feac9451ee65352f4fbe",
|
||||
"from": "github:mattermost/react-native-webview#b5e22940a613869d3999feac9451ee65352f4fbe",
|
||||
"requires": {
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"invariant": "2.2.4"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mattermost-mobile",
|
||||
"version": "1.22.0",
|
||||
"version": "1.23.1",
|
||||
"description": "Mattermost Mobile with React Native",
|
||||
"repository": "git@github.com:mattermost/mattermost-mobile.git",
|
||||
"author": "Mattermost, Inc.",
|
||||
@@ -50,13 +50,13 @@
|
||||
"react-native-permissions": "1.1.1",
|
||||
"react-native-safe-area": "0.5.1",
|
||||
"react-native-section-list-get-item-layout": "2.2.3",
|
||||
"react-native-sentry": "0.43.1",
|
||||
"react-native-sentry": "0.43.2",
|
||||
"react-native-slider": "0.11.0",
|
||||
"react-native-status-bar-size": "0.3.3",
|
||||
"react-native-svg": "9.4.0",
|
||||
"react-native-vector-icons": "6.4.2",
|
||||
"react-native-video": "4.4.1",
|
||||
"react-native-webview": "github:mattermost/react-native-webview#34e1dbef70413134ddda85df863c962f24b8826e",
|
||||
"react-native-webview": "github:mattermost/react-native-webview#b5e22940a613869d3999feac9451ee65352f4fbe",
|
||||
"react-native-youtube": "github:mattermost/react-native-youtube#3f395b620ae4e05a3f1c6bdeef3a1158e78daadc",
|
||||
"react-navigation": "3.9.1",
|
||||
"react-redux": "7.0.3",
|
||||
@@ -147,4 +147,4 @@
|
||||
"node_modules/(?!react-native|jail-monkey)"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,9 @@ jest.mock('NativeModules', () => {
|
||||
addEventListener: jest.fn(),
|
||||
getCurrentState: jest.fn().mockResolvedValue({isConnected: true}),
|
||||
},
|
||||
StatusBarManager: {
|
||||
getHeight: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
jest.mock('NativeEventEmitter');
|
||||
|
||||
Reference in New Issue
Block a user