Unverified Commit d524ccdb authored by Diego Mello's avatar Diego Mello Committed by GitHub
Browse files

[RELEASE] Merge beta into master (#1174)

parent 494890ad
......@@ -116,10 +116,26 @@ jobs:
steps:
- checkout
- run:
name: Install Node 8
command: |
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
source ~/.nvm/nvm.sh
# https://github.com/creationix/nvm/issues/1394
set +e
nvm install 8
echo 'export PATH="/home/circleci/.nvm/versions/node/v8.16.0/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
- restore_cache:
name: Restore NPM cache
key: node-modules-{{ checksum "yarn.lock" }}
- run:
name: Install React Native CLI
command: |
npm i -g react-native-cli
- run:
name: Install NPM modules
command: |
......@@ -148,12 +164,32 @@ jobs:
fi
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
- run:
name: Set Google Services
command: |
cd android/app
cp google-services.prod.json google-services.json
working_directory: android/app
- run:
name: Upload sourcemaps to Bugsnag
command: |
if [[ $BUGSNAG_KEY ]]; then
yarn generate-source-maps-android
curl https://upload.bugsnag.com/react-native-source-map \
-F apiKey=$BUGSNAG_KEY \
-F appVersionCode=$CIRCLE_BUILD_NUM \
-F dev=false \
-F platform=android \
-F sourceMap=@android-release.bundle.map \
-F bundle=@android-release.bundle
fi
- run:
name: Config variables
command: |
echo -e "export default { BUGSNAG_API_KEY: '$BUGSNAG_KEY' };" > ./config.js
- run:
name: Build Android App
......@@ -215,6 +251,7 @@ jobs:
- run:
name: Install NPM modules
command: |
yarn global add react-native react-native-cli
yarn
- run:
......@@ -229,11 +266,26 @@ jobs:
cp GoogleService-Info.prod.plist GoogleService-Info.plist
working_directory: ios
- run:
name: Upload sourcemaps to Bugsnag
command: |
if [[ $BUGSNAG_KEY ]]; then
yarn generate-source-maps-ios
curl https://upload.bugsnag.com/react-native-source-map \
-F apiKey=$BUGSNAG_KEY \
-F appBundleVersion=$CIRCLE_BUILD_NUM \
-F dev=false \
-F platform=ios \
-F sourceMap=@ios-release.bundle.map \
-F bundle=@ios-release.bundle
fi
- run:
name: Fastlane Build
no_output_timeout: 1200
command: |
agvtool new-version -all $CIRCLE_BUILD_NUM
/usr/libexec/PlistBuddy -c "Set BugsnagAPIKey $BUGSNAG_KEY" ./RocketChatRN/Info.plist
if [[ $MATCH_KEYCHAIN_NAME ]]; then
bundle exec fastlane ios release
......@@ -288,7 +340,7 @@ jobs:
- run:
name: Fastlane Tesflight Upload
command: |
bundle exec fastlane pilot upload --ipa ios/RocketChatRN.ipa --changelog "$(sh ../.circleci/changelog.sh)"
bundle exec fastlane ios beta
working_directory: ios
- save_cache:
......
apply plugin: "com.android.application"
apply plugin: "io.fabric"
apply plugin: "com.google.firebase.firebase-perf"
apply plugin: 'com.bugsnag.android.gradle'
import com.android.build.OutputFile
......@@ -135,8 +136,9 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "1.18.0"
versionName "1.19.0"
vectorDrawables.useSupportLibrary = true
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
}
signingConfigs {
......
......@@ -5,7 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
......@@ -52,6 +52,9 @@
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<meta-data
android:name="com.bugsnag.android.API_KEY"
android:value="${BugsnagAPIKey}" />
</application>
</manifest>
......@@ -7,6 +7,7 @@ import org.unimodules.core.interfaces.Package;
public class BasePackageList {
public List<Package> getPackageList() {
return Arrays.<Package>asList(
new expo.modules.av.AVPackage(),
new expo.modules.constants.ConstantsPackage(),
new expo.modules.filesystem.FileSystemPackage(),
new expo.modules.haptics.HapticsPackage(),
......
......@@ -24,6 +24,7 @@ buildscript {
classpath 'com.google.gms:google-services:4.2.0'
classpath 'io.fabric.tools:gradle:1.28.1'
classpath 'com.google.firebase:perf-plugin:1.2.1'
classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.+'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
......@@ -50,16 +51,16 @@ allprojects {
}
}
// subprojects { subproject ->
// afterEvaluate {
// if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
// android {
// compileSdkVersion 28
// buildToolsVersion "28.0.3"
// defaultConfig {
// targetSdkVersion 28
// }
// }
// }
// }
// }
\ No newline at end of file
// subprojects { subproject ->
// afterEvaluate {
// if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
// android {
// compileSdkVersion 28
// buildToolsVersion "28.0.3"
// defaultConfig {
// targetSdkVersion 28
// }
// }
// }
// }
// }
......@@ -22,3 +22,4 @@ org.gradle.jvmargs=-Xmx2048M -XX\:MaxHeapSize\=32g
android.useAndroidX=true
android.enableJetifier=true
VERSIONCODE=999999999
BugsnagAPIKey=""
......@@ -18,4 +18,5 @@ if (__DEV__) {
Reactotron.clear();
console.warn = Reactotron.log;
console.log = Reactotron.log;
console.disableYellowBox = true;
}
......@@ -74,3 +74,4 @@ export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
export const NOTIFICATION = createRequestTypes('NOTIFICATION', ['RECEIVED', 'REMOVE']);
export const TOGGLE_MARKDOWN = 'TOGGLE_MARKDOWN';
export const TOGGLE_CRASH_REPORT = 'TOGGLE_CRASH_REPORT';
import * as types from './actionsTypes';
export function toggleCrashReport(value) {
return {
type: types.TOGGLE_CRASH_REPORT,
payload: value
};
}
......@@ -23,10 +23,11 @@ export function selectServerFailure() {
};
}
export function serverRequest(server) {
export function serverRequest(server, certificate = null) {
return {
type: SERVER.REQUEST,
server
server,
certificate
};
}
......
import React from 'react';
import React, { useState } from 'react';
import {
View, Text, TouchableWithoutFeedback, ActivityIndicator, StyleSheet, SafeAreaView
} from 'react-native';
......@@ -6,7 +6,7 @@ import FastImage from 'react-native-fast-image';
import PropTypes from 'prop-types';
import Modal from 'react-native-modal';
import ImageViewer from 'react-native-image-zoom-viewer';
import VideoPlayer from 'react-native-video-controls';
import { Video } from 'expo-av';
import sharedStyles from '../views/Styles';
import { COLOR_WHITE } from '../constants/colors';
......@@ -38,6 +38,18 @@ const styles = StyleSheet.create({
},
indicator: {
flex: 1
},
video: {
flex: 1
},
loading: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
}
});
......@@ -72,15 +84,26 @@ const ModalContent = React.memo(({
);
}
if (attachment && attachment.video_url) {
const [loading, setLoading] = useState(true);
const uri = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl);
return (
<SafeAreaView style={styles.safeArea}>
<VideoPlayer
<>
<Video
source={{ uri }}
onBack={onClose}
disableVolume
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode='cover'
shouldPlay
isLooping={false}
style={styles.video}
useNativeControls
onReadyForDisplay={() => setLoading(false)}
onLoadStart={() => setLoading(true)}
onError={console.log}
/>
</SafeAreaView>
{ loading ? <ActivityIndicator size='large' style={styles.loading} /> : null }
</>
);
}
return null;
......@@ -95,11 +118,11 @@ const FileModal = React.memo(({
onBackdropPress={onClose}
onBackButtonPress={onClose}
onSwipeComplete={onClose}
swipeDirection={['up', 'left', 'right', 'down']}
swipeDirection={['up', 'down']}
>
<ModalContent attachment={attachment} onClose={onClose} user={user} baseUrl={baseUrl} />
</Modal>
), (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible);
), (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible && prevProps.loading === nextProps.loading);
FileModal.propTypes = {
isVisible: PropTypes.bool,
......
......@@ -310,8 +310,8 @@ class MessageActions extends React.Component {
try {
await RocketChat.reportMessage(actionMessage._id);
Alert.alert(I18n.t('Message_Reported'));
} catch (err) {
log('err_report_message', err);
} catch (e) {
log(e);
}
}
......@@ -327,8 +327,8 @@ class MessageActions extends React.Component {
if (!translatedMessage) {
await RocketChat.translateMessage(actionMessage, room.autoTranslateLanguage);
}
} catch (err) {
log('err_toggle_translation', err);
} catch (e) {
log(e);
}
}
......
......@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import moment from 'moment';
import { connect } from 'react-redux';
import Markdown from '../message/Markdown';
import Markdown from '../markdown';
import { getCustomEmoji } from '../message/utils';
import { CustomIcon } from '../../lib/Icons';
import sharedStyles from '../../views/Styles';
......@@ -50,6 +50,7 @@ const styles = StyleSheet.create({
class ReplyPreview extends Component {
static propTypes = {
useMarkdown: PropTypes.bool,
message: PropTypes.object.isRequired,
Message_TimeFormat: PropTypes.string.isRequired,
close: PropTypes.func.isRequired,
......@@ -68,7 +69,7 @@ class ReplyPreview extends Component {
render() {
const {
message, Message_TimeFormat, baseUrl, username
message, Message_TimeFormat, baseUrl, username, useMarkdown
} = this.props;
const time = moment(message.ts).format(Message_TimeFormat);
return (
......@@ -78,7 +79,7 @@ class ReplyPreview extends Component {
<Text style={styles.username}>{message.u.username}</Text>
<Text style={styles.time}>{time}</Text>
</View>
<Markdown msg={message.msg} baseUrl={baseUrl} username={username} getCustomEmoji={getCustomEmoji} numberOfLines={1} />
<Markdown msg={message.msg} baseUrl={baseUrl} username={username} getCustomEmoji={getCustomEmoji} numberOfLines={1} useMarkdown={useMarkdown} />
</View>
<CustomIcon name='cross' color={COLOR_TEXT_DESCRIPTION} size={20} style={styles.close} onPress={this.close} />
</View>
......@@ -87,6 +88,7 @@ class ReplyPreview extends Component {
}
const mapStateToProps = state => ({
useMarkdown: state.markdown.useMarkdown,
Message_TimeFormat: state.settings.Message_TimeFormat,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
});
......
......@@ -293,7 +293,7 @@ class MessageBox extends Component {
try {
RocketChat.executeCommandPreview(command, params, rid, item);
} catch (e) {
log('onPressCommandPreview', e);
log(e);
}
}
......@@ -362,7 +362,7 @@ class MessageBox extends Component {
try {
database.create('users', user, true);
} catch (e) {
log('err_create_users', e);
log(e);
}
});
});
......@@ -468,7 +468,7 @@ class MessageBox extends Component {
this.setState({ commandPreview: preview.items });
} catch (e) {
this.showCommandPreview = false;
log('command Preview', e);
log(e);
}
}
......@@ -504,7 +504,7 @@ class MessageBox extends Component {
try {
await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user);
} catch (e) {
log('err_send_media_message', e);
log(e);
}
}
......@@ -513,7 +513,7 @@ class MessageBox extends Component {
const image = await ImagePicker.openCamera(this.imagePickerConfig);
this.showUploadModal(image);
} catch (e) {
log('err_take_photo', e);
log(e);
}
}
......@@ -522,7 +522,7 @@ class MessageBox extends Component {
const video = await ImagePicker.openCamera(this.videoPickerConfig);
this.showUploadModal(video);
} catch (e) {
log('err_take_video', e);
log(e);
}
}
......@@ -531,7 +531,7 @@ class MessageBox extends Component {
const image = await ImagePicker.openPicker(this.libraryPickerConfig);
this.showUploadModal(image);
} catch (e) {
log('err_choose_from_library', e);
log(e);
}
}
......@@ -546,9 +546,9 @@ class MessageBox extends Component {
mime: res.type,
path: res.uri
});
} catch (error) {
if (!DocumentPicker.isCancel(error)) {
log('chooseFile', error);
} catch (e) {
if (!DocumentPicker.isCancel(e)) {
log(e);
}
}
}
......@@ -618,7 +618,7 @@ class MessageBox extends Component {
if (e && e.error === 'error-file-too-large') {
return Alert.alert(I18n.t(e.error));
}
log('err_finish_audio_message', e);
log(e);
}
}
}
......@@ -655,7 +655,7 @@ class MessageBox extends Component {
const messageWithoutCommand = message.substr(message.indexOf(' ') + 1);
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand);
} catch (e) {
log('slashCommand', e);
log(e);
}
this.clearInput();
return;
......
import React from 'react';
import PropTypes from 'prop-types';
import { Text } from 'react-native';
import styles from './styles';
const AtMention = React.memo(({
mention, mentions, username, navToRoomInfo
}) => {
let mentionStyle = styles.mention;
if (mention === 'all' || mention === 'here') {
mentionStyle = {
...mentionStyle,
...styles.mentionAll
};
} else if (mention === username) {
mentionStyle = {
...mentionStyle,
...styles.mentionLoggedUser
};
}
const handlePress = () => {
if (mentions && mentions.length && mentions.findIndex(m => m.username === mention) !== -1) {
const index = mentions.findIndex(m => m.username === mention);
const navParam = {
t: 'd',
rid: mentions[index]._id
};
navToRoomInfo(navParam);
}
};
return (
<Text
style={mentionStyle}
onPress={handlePress}
>
{`@${ mention }`}
</Text>
);
});
AtMention.propTypes = {
mention: PropTypes.string,
username: PropTypes.string,
navToRoomInfo: PropTypes.func,
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
};
export default AtMention;
import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import styles from './styles';
const BlockQuote = React.memo(({ children }) => (
<View style={styles.container}>
<View style={styles.quote} />
<View style={styles.childContainer}>
{children}
</View>
</View>
));
BlockQuote.propTypes = {
children: PropTypes.node.isRequired
};
export default BlockQuote;
import React from 'react';
import PropTypes from 'prop-types';
import { Text } from 'react-native';
import { emojify } from 'react-emojione';
import CustomEmoji from '../EmojiPicker/CustomEmoji';
import styles from './styles';
const Emoji = React.memo(({
emojiName, literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl
}) => {
const emojiUnicode = emojify(literal, { output: 'unicode' });
const emoji = getCustomEmoji && getCustomEmoji(emojiName);
if (emoji) {
return (
<CustomEmoji
baseUrl={baseUrl}
style={isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji}
emoji={emoji}
/>
);
}
return <Text style={isMessageContainsOnlyEmoji ? styles.textBig : styles.text}>{emojiUnicode}</Text>;
});
Emoji.propTypes = {
emojiName: PropTypes.string,
literal: PropTypes.string,
isMessageContainsOnlyEmoji: PropTypes.bool,
getCustomEmoji: PropTypes.func,
baseUrl: PropTypes.string
};
export default Emoji;
import PropTypes from 'prop-types';
import React from 'react';
import { Text } from 'react-native';
import styles from './styles';
const Hashtag = React.memo(({
hashtag, channels, navToRoomInfo
}) => {
const handlePress = () => {
const index = channels.findIndex(channel => channel.name === hashtag);
const navParam = {
t: 'c',
rid: channels[index]._id
};
navToRoomInfo(navParam);
};
if (channels && channels.length && channels.findIndex(channel => channel.name === hashtag) !== -1) {
return (
<Text
style={styles.mention}
onPress={handlePress}
>
{`#${ hashtag }`}
</Text>
);
}
return `#${ hashtag }`;
});
Hashtag.propTypes = {
hashtag: PropTypes.string,
navToRoomInfo: PropTypes.func,