[RELEASE] Merge beta into master (#1174)
This commit is contained in:
parent
494890ad84
commit
d524ccdb72
|
@ -116,10 +116,26 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- 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:
|
- restore_cache:
|
||||||
name: Restore NPM cache
|
name: Restore NPM cache
|
||||||
key: node-modules-{{ checksum "yarn.lock" }}
|
key: node-modules-{{ checksum "yarn.lock" }}
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Install React Native CLI
|
||||||
|
command: |
|
||||||
|
npm i -g react-native-cli
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Install NPM modules
|
name: Install NPM modules
|
||||||
command: |
|
command: |
|
||||||
|
@ -148,12 +164,32 @@ jobs:
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
|
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
|
||||||
|
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Set Google Services
|
name: Set Google Services
|
||||||
command: |
|
command: |
|
||||||
cd android/app
|
|
||||||
cp google-services.prod.json google-services.json
|
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:
|
- run:
|
||||||
name: Build Android App
|
name: Build Android App
|
||||||
|
@ -215,6 +251,7 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Install NPM modules
|
name: Install NPM modules
|
||||||
command: |
|
command: |
|
||||||
|
yarn global add react-native react-native-cli
|
||||||
yarn
|
yarn
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
|
@ -229,11 +266,26 @@ jobs:
|
||||||
cp GoogleService-Info.prod.plist GoogleService-Info.plist
|
cp GoogleService-Info.prod.plist GoogleService-Info.plist
|
||||||
working_directory: ios
|
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:
|
- run:
|
||||||
name: Fastlane Build
|
name: Fastlane Build
|
||||||
no_output_timeout: 1200
|
no_output_timeout: 1200
|
||||||
command: |
|
command: |
|
||||||
agvtool new-version -all $CIRCLE_BUILD_NUM
|
agvtool new-version -all $CIRCLE_BUILD_NUM
|
||||||
|
/usr/libexec/PlistBuddy -c "Set BugsnagAPIKey $BUGSNAG_KEY" ./RocketChatRN/Info.plist
|
||||||
|
|
||||||
if [[ $MATCH_KEYCHAIN_NAME ]]; then
|
if [[ $MATCH_KEYCHAIN_NAME ]]; then
|
||||||
bundle exec fastlane ios release
|
bundle exec fastlane ios release
|
||||||
|
@ -288,7 +340,7 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Fastlane Tesflight Upload
|
name: Fastlane Tesflight Upload
|
||||||
command: |
|
command: |
|
||||||
bundle exec fastlane pilot upload --ipa ios/RocketChatRN.ipa --changelog "$(sh ../.circleci/changelog.sh)"
|
bundle exec fastlane ios beta
|
||||||
working_directory: ios
|
working_directory: ios
|
||||||
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,7 @@
|
||||||
apply plugin: "com.android.application"
|
apply plugin: "com.android.application"
|
||||||
apply plugin: "io.fabric"
|
apply plugin: "io.fabric"
|
||||||
apply plugin: "com.google.firebase.firebase-perf"
|
apply plugin: "com.google.firebase.firebase-perf"
|
||||||
|
apply plugin: 'com.bugsnag.android.gradle'
|
||||||
|
|
||||||
import com.android.build.OutputFile
|
import com.android.build.OutputFile
|
||||||
|
|
||||||
|
@ -135,8 +136,9 @@ android {
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "1.18.0"
|
versionName "1.19.0"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<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.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
||||||
|
@ -52,6 +52,9 @@
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="*/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<meta-data
|
||||||
|
android:name="com.bugsnag.android.API_KEY"
|
||||||
|
android:value="${BugsnagAPIKey}" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import org.unimodules.core.interfaces.Package;
|
||||||
public class BasePackageList {
|
public class BasePackageList {
|
||||||
public List<Package> getPackageList() {
|
public List<Package> getPackageList() {
|
||||||
return Arrays.<Package>asList(
|
return Arrays.<Package>asList(
|
||||||
|
new expo.modules.av.AVPackage(),
|
||||||
new expo.modules.constants.ConstantsPackage(),
|
new expo.modules.constants.ConstantsPackage(),
|
||||||
new expo.modules.filesystem.FileSystemPackage(),
|
new expo.modules.filesystem.FileSystemPackage(),
|
||||||
new expo.modules.haptics.HapticsPackage(),
|
new expo.modules.haptics.HapticsPackage(),
|
||||||
|
|
|
@ -24,6 +24,7 @@ buildscript {
|
||||||
classpath 'com.google.gms:google-services:4.2.0'
|
classpath 'com.google.gms:google-services:4.2.0'
|
||||||
classpath 'io.fabric.tools:gradle:1.28.1'
|
classpath 'io.fabric.tools:gradle:1.28.1'
|
||||||
classpath 'com.google.firebase:perf-plugin:1.2.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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@ -50,16 +51,16 @@ allprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// subprojects { subproject ->
|
// subprojects { subproject ->
|
||||||
// afterEvaluate {
|
// afterEvaluate {
|
||||||
// if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
|
// if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
|
||||||
// android {
|
// android {
|
||||||
// compileSdkVersion 28
|
// compileSdkVersion 28
|
||||||
// buildToolsVersion "28.0.3"
|
// buildToolsVersion "28.0.3"
|
||||||
// defaultConfig {
|
// defaultConfig {
|
||||||
// targetSdkVersion 28
|
// targetSdkVersion 28
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
|
@ -22,3 +22,4 @@ org.gradle.jvmargs=-Xmx2048M -XX\:MaxHeapSize\=32g
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
VERSIONCODE=999999999
|
VERSIONCODE=999999999
|
||||||
|
BugsnagAPIKey=""
|
||||||
|
|
|
@ -18,4 +18,5 @@ if (__DEV__) {
|
||||||
Reactotron.clear();
|
Reactotron.clear();
|
||||||
console.warn = Reactotron.log;
|
console.warn = Reactotron.log;
|
||||||
console.log = 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 SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
|
||||||
export const NOTIFICATION = createRequestTypes('NOTIFICATION', ['RECEIVED', 'REMOVE']);
|
export const NOTIFICATION = createRequestTypes('NOTIFICATION', ['RECEIVED', 'REMOVE']);
|
||||||
export const TOGGLE_MARKDOWN = 'TOGGLE_MARKDOWN';
|
export const TOGGLE_MARKDOWN = 'TOGGLE_MARKDOWN';
|
||||||
|
export const TOGGLE_CRASH_REPORT = 'TOGGLE_CRASH_REPORT';
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
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 {
|
return {
|
||||||
type: SERVER.REQUEST,
|
type: SERVER.REQUEST,
|
||||||
server
|
server,
|
||||||
|
certificate
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
View, Text, TouchableWithoutFeedback, ActivityIndicator, StyleSheet, SafeAreaView
|
View, Text, TouchableWithoutFeedback, ActivityIndicator, StyleSheet, SafeAreaView
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
@ -6,7 +6,7 @@ import FastImage from 'react-native-fast-image';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import ImageViewer from 'react-native-image-zoom-viewer';
|
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 sharedStyles from '../views/Styles';
|
||||||
import { COLOR_WHITE } from '../constants/colors';
|
import { COLOR_WHITE } from '../constants/colors';
|
||||||
|
@ -38,6 +38,18 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
indicator: {
|
indicator: {
|
||||||
flex: 1
|
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) {
|
if (attachment && attachment.video_url) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const uri = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl);
|
const uri = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl);
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.safeArea}>
|
<>
|
||||||
<VideoPlayer
|
<Video
|
||||||
source={{ uri }}
|
source={{ uri }}
|
||||||
onBack={onClose}
|
rate={1.0}
|
||||||
disableVolume
|
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;
|
return null;
|
||||||
|
@ -95,11 +118,11 @@ const FileModal = React.memo(({
|
||||||
onBackdropPress={onClose}
|
onBackdropPress={onClose}
|
||||||
onBackButtonPress={onClose}
|
onBackButtonPress={onClose}
|
||||||
onSwipeComplete={onClose}
|
onSwipeComplete={onClose}
|
||||||
swipeDirection={['up', 'left', 'right', 'down']}
|
swipeDirection={['up', 'down']}
|
||||||
>
|
>
|
||||||
<ModalContent attachment={attachment} onClose={onClose} user={user} baseUrl={baseUrl} />
|
<ModalContent attachment={attachment} onClose={onClose} user={user} baseUrl={baseUrl} />
|
||||||
</Modal>
|
</Modal>
|
||||||
), (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible);
|
), (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible && prevProps.loading === nextProps.loading);
|
||||||
|
|
||||||
FileModal.propTypes = {
|
FileModal.propTypes = {
|
||||||
isVisible: PropTypes.bool,
|
isVisible: PropTypes.bool,
|
||||||
|
|
|
@ -310,8 +310,8 @@ class MessageActions extends React.Component {
|
||||||
try {
|
try {
|
||||||
await RocketChat.reportMessage(actionMessage._id);
|
await RocketChat.reportMessage(actionMessage._id);
|
||||||
Alert.alert(I18n.t('Message_Reported'));
|
Alert.alert(I18n.t('Message_Reported'));
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
log('err_report_message', err);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,8 +327,8 @@ class MessageActions extends React.Component {
|
||||||
if (!translatedMessage) {
|
if (!translatedMessage) {
|
||||||
await RocketChat.translateMessage(actionMessage, room.autoTranslateLanguage);
|
await RocketChat.translateMessage(actionMessage, room.autoTranslateLanguage);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
log('err_toggle_translation', err);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import Markdown from '../message/Markdown';
|
import Markdown from '../markdown';
|
||||||
import { getCustomEmoji } from '../message/utils';
|
import { getCustomEmoji } from '../message/utils';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
@ -50,6 +50,7 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
class ReplyPreview extends Component {
|
class ReplyPreview extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
useMarkdown: PropTypes.bool,
|
||||||
message: PropTypes.object.isRequired,
|
message: PropTypes.object.isRequired,
|
||||||
Message_TimeFormat: PropTypes.string.isRequired,
|
Message_TimeFormat: PropTypes.string.isRequired,
|
||||||
close: PropTypes.func.isRequired,
|
close: PropTypes.func.isRequired,
|
||||||
|
@ -68,7 +69,7 @@ class ReplyPreview extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
message, Message_TimeFormat, baseUrl, username
|
message, Message_TimeFormat, baseUrl, username, useMarkdown
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const time = moment(message.ts).format(Message_TimeFormat);
|
const time = moment(message.ts).format(Message_TimeFormat);
|
||||||
return (
|
return (
|
||||||
|
@ -78,7 +79,7 @@ class ReplyPreview extends Component {
|
||||||
<Text style={styles.username}>{message.u.username}</Text>
|
<Text style={styles.username}>{message.u.username}</Text>
|
||||||
<Text style={styles.time}>{time}</Text>
|
<Text style={styles.time}>{time}</Text>
|
||||||
</View>
|
</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>
|
</View>
|
||||||
<CustomIcon name='cross' color={COLOR_TEXT_DESCRIPTION} size={20} style={styles.close} onPress={this.close} />
|
<CustomIcon name='cross' color={COLOR_TEXT_DESCRIPTION} size={20} style={styles.close} onPress={this.close} />
|
||||||
</View>
|
</View>
|
||||||
|
@ -87,6 +88,7 @@ class ReplyPreview extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
useMarkdown: state.markdown.useMarkdown,
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
});
|
});
|
||||||
|
|
|
@ -293,7 +293,7 @@ class MessageBox extends Component {
|
||||||
try {
|
try {
|
||||||
RocketChat.executeCommandPreview(command, params, rid, item);
|
RocketChat.executeCommandPreview(command, params, rid, item);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('onPressCommandPreview', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,7 +362,7 @@ class MessageBox extends Component {
|
||||||
try {
|
try {
|
||||||
database.create('users', user, true);
|
database.create('users', user, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_create_users', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -468,7 +468,7 @@ class MessageBox extends Component {
|
||||||
this.setState({ commandPreview: preview.items });
|
this.setState({ commandPreview: preview.items });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.showCommandPreview = false;
|
this.showCommandPreview = false;
|
||||||
log('command Preview', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,7 +504,7 @@ class MessageBox extends Component {
|
||||||
try {
|
try {
|
||||||
await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user);
|
await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user);
|
||||||
} catch (e) {
|
} 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);
|
const image = await ImagePicker.openCamera(this.imagePickerConfig);
|
||||||
this.showUploadModal(image);
|
this.showUploadModal(image);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_take_photo', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,7 +522,7 @@ class MessageBox extends Component {
|
||||||
const video = await ImagePicker.openCamera(this.videoPickerConfig);
|
const video = await ImagePicker.openCamera(this.videoPickerConfig);
|
||||||
this.showUploadModal(video);
|
this.showUploadModal(video);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_take_video', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,7 +531,7 @@ class MessageBox extends Component {
|
||||||
const image = await ImagePicker.openPicker(this.libraryPickerConfig);
|
const image = await ImagePicker.openPicker(this.libraryPickerConfig);
|
||||||
this.showUploadModal(image);
|
this.showUploadModal(image);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_choose_from_library', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,9 +546,9 @@ class MessageBox extends Component {
|
||||||
mime: res.type,
|
mime: res.type,
|
||||||
path: res.uri
|
path: res.uri
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
if (!DocumentPicker.isCancel(error)) {
|
if (!DocumentPicker.isCancel(e)) {
|
||||||
log('chooseFile', error);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -618,7 +618,7 @@ class MessageBox extends Component {
|
||||||
if (e && e.error === 'error-file-too-large') {
|
if (e && e.error === 'error-file-too-large') {
|
||||||
return Alert.alert(I18n.t(e.error));
|
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);
|
const messageWithoutCommand = message.substr(message.indexOf(' ') + 1);
|
||||||
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand);
|
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('slashCommand', e);
|
log(e);
|
||||||
}
|
}
|
||||||
this.clearInput();
|
this.clearInput();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
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;
|
|
@ -0,0 +1,20 @@
|
||||||
|
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;
|
|
@ -0,0 +1,35 @@
|
||||||
|
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;
|
|
@ -0,0 +1,38 @@
|
||||||
|
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,
|
||||||
|
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Hashtag;
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
import openLink from '../../utils/openLink';
|
||||||
|
|
||||||
|
const Link = React.memo(({
|
||||||
|
children, link
|
||||||
|
}) => {
|
||||||
|
const handlePress = () => {
|
||||||
|
if (!link) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openLink(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
const childLength = React.Children.toArray(children).filter(o => o).length;
|
||||||
|
|
||||||
|
// if you have a [](https://rocket.chat) render https://rocket.chat
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
onPress={handlePress}
|
||||||
|
style={styles.link}
|
||||||
|
>
|
||||||
|
{ childLength !== 0 ? children : link }
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Link.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
link: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Link;
|
|
@ -0,0 +1,39 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const List = React.memo(({
|
||||||
|
children, ordered, start, tight
|
||||||
|
}) => {
|
||||||
|
let bulletWidth = 15;
|
||||||
|
|
||||||
|
if (ordered) {
|
||||||
|
const lastNumber = (start + children.length) - 1;
|
||||||
|
bulletWidth = (9 * lastNumber.toString().length) + 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _children = React.Children.map(children, (child, index) => React.cloneElement(child, {
|
||||||
|
bulletWidth,
|
||||||
|
ordered,
|
||||||
|
tight,
|
||||||
|
index: start + index
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{_children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
List.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
ordered: PropTypes.bool,
|
||||||
|
start: PropTypes.number,
|
||||||
|
tight: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
List.defaultProps = {
|
||||||
|
start: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
export default List;
|
|
@ -0,0 +1,60 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
|
const style = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-start'
|
||||||
|
},
|
||||||
|
bullet: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
marginRight: 5
|
||||||
|
},
|
||||||
|
contents: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ListItem = React.memo(({
|
||||||
|
children, level, bulletWidth, continue: _continue, ordered, index
|
||||||
|
}) => {
|
||||||
|
let bullet;
|
||||||
|
if (_continue) {
|
||||||
|
bullet = '';
|
||||||
|
} else if (ordered) {
|
||||||
|
bullet = `${ index }.`;
|
||||||
|
} else if (level % 2 === 0) {
|
||||||
|
bullet = '◦';
|
||||||
|
} else {
|
||||||
|
bullet = '•';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={style.container}>
|
||||||
|
<View style={[{ width: bulletWidth }, style.bullet]}>
|
||||||
|
<Text>
|
||||||
|
{bullet}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={style.contents}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ListItem.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
bulletWidth: PropTypes.number,
|
||||||
|
level: PropTypes.number,
|
||||||
|
ordered: PropTypes.bool,
|
||||||
|
continue: PropTypes.bool,
|
||||||
|
index: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListItem;
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { PropTypes } from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
Text
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
|
import { CELL_WIDTH } from './TableCell';
|
||||||
|
import styles from './styles';
|
||||||
|
import Navigation from '../../lib/Navigation';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
const MAX_HEIGHT = 300;
|
||||||
|
|
||||||
|
const Table = React.memo(({
|
||||||
|
children, numColumns
|
||||||
|
}) => {
|
||||||
|
const getTableWidth = () => numColumns * CELL_WIDTH;
|
||||||
|
|
||||||
|
const renderRows = (drawExtraBorders = true) => {
|
||||||
|
const tableStyle = [styles.table];
|
||||||
|
if (drawExtraBorders) {
|
||||||
|
tableStyle.push(styles.tableExtraBorders);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = React.Children.toArray(children);
|
||||||
|
rows[rows.length - 1] = React.cloneElement(rows[rows.length - 1], {
|
||||||
|
isLastRow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={tableStyle}>
|
||||||
|
{rows}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPress = () => Navigation.navigate('TableView', { renderRows, tableWidth: getTableWidth() });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={onPress}>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={{ width: getTableWidth() }}
|
||||||
|
scrollEnabled={false}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
style={[styles.containerTable, { maxWidth: getTableWidth(), maxHeight: MAX_HEIGHT }]}
|
||||||
|
>
|
||||||
|
{renderRows(false)}
|
||||||
|
</ScrollView>
|
||||||
|
<Text style={styles.textInfo}>{I18n.t('Full_table')}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Table.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
numColumns: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Table;
|
|
@ -0,0 +1,39 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
export const CELL_WIDTH = 100;
|
||||||
|
|
||||||
|
const TableCell = React.memo(({
|
||||||
|
isLastCell, align, children
|
||||||
|
}) => {
|
||||||
|
const cellStyle = [styles.cell];
|
||||||
|
if (!isLastCell) {
|
||||||
|
cellStyle.push(styles.cellRightBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
let textStyle = null;
|
||||||
|
if (align === 'center') {
|
||||||
|
textStyle = styles.alignCenter;
|
||||||
|
} else if (align === 'right') {
|
||||||
|
textStyle = styles.alignRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[...cellStyle, { width: CELL_WIDTH }]}>
|
||||||
|
<Text style={textStyle}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TableCell.propTypes = {
|
||||||
|
align: PropTypes.oneOf(['', 'left', 'center', 'right']),
|
||||||
|
children: PropTypes.node,
|
||||||
|
isLastCell: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableCell;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const TableRow = React.memo(({
|
||||||
|
isLastRow, children: _children
|
||||||
|
}) => {
|
||||||
|
const rowStyle = [styles.row];
|
||||||
|
if (!isLastRow) {
|
||||||
|
rowStyle.push(styles.rowBottomBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = React.Children.toArray(_children);
|
||||||
|
children[children.length - 1] = React.cloneElement(children[children.length - 1], {
|
||||||
|
isLastCell: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return <View style={rowStyle}>{children}</View>;
|
||||||
|
});
|
||||||
|
|
||||||
|
TableRow.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
isLastRow: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableRow;
|
|
@ -0,0 +1,295 @@
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import { View, Text, Image } from 'react-native';
|
||||||
|
import { Parser, Node } from 'commonmark';
|
||||||
|
import Renderer from 'commonmark-react-renderer';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
import MarkdownLink from './Link';
|
||||||
|
import MarkdownList from './List';
|
||||||
|
import MarkdownListItem from './ListItem';
|
||||||
|
import MarkdownAtMention from './AtMention';
|
||||||
|
import MarkdownHashtag from './Hashtag';
|
||||||
|
import MarkdownBlockQuote from './BlockQuote';
|
||||||
|
import MarkdownEmoji from './Emoji';
|
||||||
|
import MarkdownTable from './Table';
|
||||||
|
import MarkdownTableRow from './TableRow';
|
||||||
|
import MarkdownTableCell from './TableCell';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
// Support <http://link|Text>
|
||||||
|
const formatText = text => text.replace(
|
||||||
|
new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'),
|
||||||
|
(match, url, title) => `[${ title }](${ url })`
|
||||||
|
);
|
||||||
|
|
||||||
|
const emojiRanges = [
|
||||||
|
'\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]', // unicode emoji from https://www.regextester.com/106421
|
||||||
|
':.{1,40}:', // custom emoji
|
||||||
|
' |\n' // allow spaces and line breaks
|
||||||
|
].join('|');
|
||||||
|
|
||||||
|
const removeAllEmoji = str => str.replace(new RegExp(emojiRanges, 'g'), '');
|
||||||
|
|
||||||
|
const isOnlyEmoji = str => !removeAllEmoji(str).length;
|
||||||
|
|
||||||
|
const removeOneEmoji = str => str.replace(new RegExp(emojiRanges), '');
|
||||||
|
|
||||||
|
const emojiCount = (str) => {
|
||||||
|
let oldLength = 0;
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
while (oldLength !== str.length) {
|
||||||
|
oldLength = str.length;
|
||||||
|
str = removeOneEmoji(str);
|
||||||
|
if (oldLength !== str.length) {
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return counter;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Markdown extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
msg: PropTypes.string,
|
||||||
|
getCustomEmoji: PropTypes.func,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
|
username: PropTypes.string,
|
||||||
|
tmid: PropTypes.string,
|
||||||
|
isEdited: PropTypes.bool,
|
||||||
|
numberOfLines: PropTypes.number,
|
||||||
|
useMarkdown: PropTypes.bool,
|
||||||
|
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
|
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
|
navToRoomInfo: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.parser = this.createParser();
|
||||||
|
this.renderer = this.createRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
createParser = () => new Parser();
|
||||||
|
|
||||||
|
createRenderer = () => new Renderer({
|
||||||
|
renderers: {
|
||||||
|
text: this.renderText,
|
||||||
|
|
||||||
|
emph: Renderer.forwardChildren,
|
||||||
|
strong: Renderer.forwardChildren,
|
||||||
|
del: Renderer.forwardChildren,
|
||||||
|
code: this.renderCodeInline,
|
||||||
|
link: this.renderLink,
|
||||||
|
image: this.renderImage,
|
||||||
|
atMention: this.renderAtMention,
|
||||||
|
emoji: this.renderEmoji,
|
||||||
|
hashtag: this.renderHashtag,
|
||||||
|
|
||||||
|
paragraph: this.renderParagraph,
|
||||||
|
heading: this.renderHeading,
|
||||||
|
codeBlock: this.renderCodeBlock,
|
||||||
|
blockQuote: this.renderBlockQuote,
|
||||||
|
|
||||||
|
list: this.renderList,
|
||||||
|
item: this.renderListItem,
|
||||||
|
|
||||||
|
hardBreak: this.renderBreak,
|
||||||
|
thematicBreak: this.renderBreak,
|
||||||
|
softBreak: this.renderBreak,
|
||||||
|
|
||||||
|
htmlBlock: this.renderText,
|
||||||
|
htmlInline: this.renderText,
|
||||||
|
|
||||||
|
table: this.renderTable,
|
||||||
|
table_row: this.renderTableRow,
|
||||||
|
table_cell: this.renderTableCell,
|
||||||
|
|
||||||
|
editedIndicator: this.renderEditedIndicator
|
||||||
|
},
|
||||||
|
renderParagraphsInLists: true
|
||||||
|
});
|
||||||
|
|
||||||
|
editedMessage = (ast) => {
|
||||||
|
const { isEdited } = this.props;
|
||||||
|
if (isEdited) {
|
||||||
|
const editIndicatorNode = new Node('edited_indicator');
|
||||||
|
if (ast.lastChild && ['heading', 'paragraph'].includes(ast.lastChild.type)) {
|
||||||
|
ast.lastChild.appendChild(editIndicatorNode);
|
||||||
|
} else {
|
||||||
|
const node = new Node('paragraph');
|
||||||
|
node.appendChild(editIndicatorNode);
|
||||||
|
|
||||||
|
ast.appendChild(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderText = ({ context, literal }) => {
|
||||||
|
const { numberOfLines } = this.props;
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
this.isMessageContainsOnlyEmoji ? styles.textBig : styles.text,
|
||||||
|
...context.map(type => styles[type])
|
||||||
|
]}
|
||||||
|
numberOfLines={numberOfLines}
|
||||||
|
>
|
||||||
|
{literal}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCodeInline = ({ literal }) => <Text style={styles.codeInline}>{literal}</Text>;
|
||||||
|
|
||||||
|
renderCodeBlock = ({ literal }) => <Text style={styles.codeBlock}>{literal}</Text>;
|
||||||
|
|
||||||
|
renderBreak = () => {
|
||||||
|
const { tmid } = this.props;
|
||||||
|
return <Text>{tmid ? ' ' : '\n'}</Text>;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderParagraph = ({ children }) => {
|
||||||
|
const { numberOfLines } = this.props;
|
||||||
|
|
||||||
|
if (!children || children.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.block}>
|
||||||
|
<Text numberOfLines={numberOfLines}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderLink = ({ children, href }) => (
|
||||||
|
<MarkdownLink link={href}>
|
||||||
|
{children}
|
||||||
|
</MarkdownLink>
|
||||||
|
);
|
||||||
|
|
||||||
|
renderHashtag = ({ hashtag }) => {
|
||||||
|
const { channels, navToRoomInfo } = this.props;
|
||||||
|
return (
|
||||||
|
<MarkdownHashtag
|
||||||
|
hashtag={hashtag}
|
||||||
|
channels={channels}
|
||||||
|
navToRoomInfo={navToRoomInfo}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAtMention = ({ mentionName }) => {
|
||||||
|
const { username, mentions, navToRoomInfo } = this.props;
|
||||||
|
return (
|
||||||
|
<MarkdownAtMention
|
||||||
|
mentions={mentions}
|
||||||
|
mention={mentionName}
|
||||||
|
username={username}
|
||||||
|
navToRoomInfo={navToRoomInfo}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEmoji = ({ emojiName, literal }) => {
|
||||||
|
const { getCustomEmoji, baseUrl } = this.props;
|
||||||
|
return (
|
||||||
|
<MarkdownEmoji
|
||||||
|
emojiName={emojiName}
|
||||||
|
literal={literal}
|
||||||
|
isMessageContainsOnlyEmoji={this.isMessageContainsOnlyEmoji}
|
||||||
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderImage = ({ src }) => <Image style={styles.inlineImage} source={{ uri: src }} />;
|
||||||
|
|
||||||
|
renderEditedIndicator = () => <Text style={styles.edited}> ({I18n.t('edited')})</Text>;
|
||||||
|
|
||||||
|
renderHeading = ({ children, level }) => {
|
||||||
|
const textStyle = styles[`heading${ level }Text`];
|
||||||
|
return (
|
||||||
|
<Text style={textStyle}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderList = ({
|
||||||
|
children, start, tight, type
|
||||||
|
}) => (
|
||||||
|
<MarkdownList
|
||||||
|
ordered={type !== 'bullet'}
|
||||||
|
start={start}
|
||||||
|
tight={tight}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</MarkdownList>
|
||||||
|
);
|
||||||
|
|
||||||
|
renderListItem = ({
|
||||||
|
children, context, ...otherProps
|
||||||
|
}) => {
|
||||||
|
const level = context.filter(type => type === 'list').length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MarkdownListItem
|
||||||
|
level={level}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</MarkdownListItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderBlockQuote = ({ children }) => (
|
||||||
|
<MarkdownBlockQuote>
|
||||||
|
{children}
|
||||||
|
</MarkdownBlockQuote>
|
||||||
|
);
|
||||||
|
|
||||||
|
renderTable = ({ children, numColumns }) => (
|
||||||
|
<MarkdownTable numColumns={numColumns}>
|
||||||
|
{children}
|
||||||
|
</MarkdownTable>
|
||||||
|
);
|
||||||
|
|
||||||
|
renderTableRow = args => <MarkdownTableRow {...args} />;
|
||||||
|
|
||||||
|
renderTableCell = args => <MarkdownTableCell {...args} />;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
msg, useMarkdown = true, numberOfLines
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!msg) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let m = formatText(msg);
|
||||||
|
|
||||||
|
// Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test'
|
||||||
|
// Return: 'Test'
|
||||||
|
m = m.replace(/^\[([\s]]*)\]\(([^)]*)\)\s/, '').trim();
|
||||||
|
|
||||||
|
if (!useMarkdown) {
|
||||||
|
return <Text style={styles.text} numberOfLines={numberOfLines}>{m}</Text>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ast = this.parser.parse(m);
|
||||||
|
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
|
||||||
|
|
||||||
|
this.editedMessage(ast);
|
||||||
|
|
||||||
|
return this.renderer.render(ast);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
import { StyleSheet, Platform } from 'react-native';
|
||||||
|
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import {
|
||||||
|
COLOR_BORDER, COLOR_PRIMARY, COLOR_WHITE, COLOR_BACKGROUND_CONTAINER
|
||||||
|
} from '../../constants/colors';
|
||||||
|
|
||||||
|
const codeFontFamily = Platform.select({
|
||||||
|
ios: { fontFamily: 'Courier New' },
|
||||||
|
android: { fontFamily: 'monospace' }
|
||||||
|
});
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
childContainer: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
block: {
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap'
|
||||||
|
},
|
||||||
|
emph: {
|
||||||
|
fontStyle: 'italic'
|
||||||
|
},
|
||||||
|
strong: {
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
del: {
|
||||||
|
textDecorationLine: 'line-through'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fontSize: 16,
|
||||||
|
...sharedStyles.textColorNormal,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
textInfo: {
|
||||||
|
fontStyle: 'italic',
|
||||||
|
fontSize: 16,
|
||||||
|
...sharedStyles.textColorDescription,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
textBig: {
|
||||||
|
fontSize: 30,
|
||||||
|
...sharedStyles.textColorNormal,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
customEmoji: {
|
||||||
|
width: 20,
|
||||||
|
height: 20
|
||||||
|
},
|
||||||
|
customEmojiBig: {
|
||||||
|
width: 30,
|
||||||
|
height: 30
|
||||||
|
},
|
||||||
|
temp: { opacity: 0.3 },
|
||||||
|
mention: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#0072FE',
|
||||||
|
padding: 5,
|
||||||
|
...sharedStyles.textMedium,
|
||||||
|
backgroundColor: '#E8F2FF'
|
||||||
|
},
|
||||||
|
mentionLoggedUser: {
|
||||||
|
color: COLOR_WHITE,
|
||||||
|
backgroundColor: COLOR_PRIMARY
|
||||||
|
},
|
||||||
|
mentionAll: {
|
||||||
|
color: COLOR_WHITE,
|
||||||
|
backgroundColor: '#FF5B5A'
|
||||||
|
},
|
||||||
|
paragraph: {
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: 0,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'flex-start'
|
||||||
|
},
|
||||||
|
inlineImage: {
|
||||||
|
width: 300,
|
||||||
|
height: 300,
|
||||||
|
resizeMode: 'contain'
|
||||||
|
},
|
||||||
|
codeInline: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
...codeFontFamily,
|
||||||
|
borderWidth: 1,
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
codeBlock: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
...codeFontFamily,
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
||||||
|
borderColor: COLOR_BORDER,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: 4
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: COLOR_PRIMARY,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
edited: {
|
||||||
|
fontSize: 14,
|
||||||
|
...sharedStyles.textColorDescription,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
heading1: {
|
||||||
|
...sharedStyles.textBold,
|
||||||
|
fontSize: 24
|
||||||
|
},
|
||||||
|
heading2: {
|
||||||
|
...sharedStyles.textBold,
|
||||||
|
fontSize: 22
|
||||||
|
},
|
||||||
|
heading3: {
|
||||||
|
...sharedStyles.textSemibold,
|
||||||
|
fontSize: 20
|
||||||
|
},
|
||||||
|
heading4: {
|
||||||
|
...sharedStyles.textSemibold,
|
||||||
|
fontSize: 18
|
||||||
|
},
|
||||||
|
heading5: {
|
||||||
|
...sharedStyles.textMedium,
|
||||||
|
fontSize: 16
|
||||||
|
},
|
||||||
|
heading6: {
|
||||||
|
...sharedStyles.textMedium,
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
quote: {
|
||||||
|
height: '100%',
|
||||||
|
width: 2,
|
||||||
|
backgroundColor: COLOR_BORDER,
|
||||||
|
marginRight: 5
|
||||||
|
},
|
||||||
|
touchableTable: {
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
containerTable: {
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderColor: COLOR_BORDER,
|
||||||
|
borderRightWidth: 1
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
borderColor: COLOR_BORDER,
|
||||||
|
borderLeftWidth: 1,
|
||||||
|
borderTopWidth: 1
|
||||||
|
},
|
||||||
|
tableExtraBorders: {
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderRightWidth: 1
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
rowBottomBorder: {
|
||||||
|
borderColor: COLOR_BORDER,
|
||||||
|
borderBottomWidth: 1
|
||||||
|
},
|
||||||
|
cell: {
|
||||||
|
borderColor: COLOR_BORDER,
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
paddingHorizontal: 13,
|
||||||
|
paddingVertical: 6
|
||||||
|
},
|
||||||
|
cellRightBorder: {
|
||||||
|
borderRightWidth: 1
|
||||||
|
},
|
||||||
|
alignCenter: {
|
||||||
|
textAlign: 'center'
|
||||||
|
},
|
||||||
|
alignRight: {
|
||||||
|
textAlign: 'right'
|
||||||
|
}
|
||||||
|
});
|
|
@ -9,7 +9,7 @@ import moment from 'moment';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import Markdown from './Markdown';
|
import Markdown from '../markdown';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { COLOR_BACKGROUND_CONTAINER, COLOR_BORDER, COLOR_PRIMARY } from '../../constants/colors';
|
import { COLOR_BACKGROUND_CONTAINER, COLOR_BORDER, COLOR_PRIMARY } from '../../constants/colors';
|
||||||
|
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Markdown from './Markdown';
|
import Markdown from '../markdown';
|
||||||
import { getInfoMessage } from './utils';
|
import { getInfoMessage } from './utils';
|
||||||
|
|
||||||
const Content = React.memo((props) => {
|
const Content = React.memo((props) => {
|
||||||
|
@ -21,13 +21,15 @@ const Content = React.memo((props) => {
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={props.msg}
|
msg={props.msg}
|
||||||
baseUrl={props.baseUrl}
|
baseUrl={props.baseUrl}
|
||||||
|
getCustomEmoji={props.getCustomEmoji}
|
||||||
username={props.user.username}
|
username={props.user.username}
|
||||||
isEdited={props.isEdited}
|
isEdited={props.isEdited}
|
||||||
mentions={props.mentions}
|
|
||||||
channels={props.channels}
|
|
||||||
numberOfLines={props.tmid ? 1 : 0}
|
numberOfLines={props.tmid ? 1 : 0}
|
||||||
getCustomEmoji={props.getCustomEmoji}
|
channels={props.channels}
|
||||||
useMarkdown={props.useMarkdown}
|
mentions={props.mentions}
|
||||||
|
useMarkdown={props.useMarkdown && !props.tmid}
|
||||||
|
navToRoomInfo={props.navToRoomInfo}
|
||||||
|
tmid={props.tmid}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,15 +44,16 @@ const Content = React.memo((props) => {
|
||||||
Content.propTypes = {
|
Content.propTypes = {
|
||||||
isTemp: PropTypes.bool,
|
isTemp: PropTypes.bool,
|
||||||
isInfo: PropTypes.bool,
|
isInfo: PropTypes.bool,
|
||||||
isEdited: PropTypes.bool,
|
|
||||||
useMarkdown: PropTypes.bool,
|
|
||||||
tmid: PropTypes.string,
|
tmid: PropTypes.string,
|
||||||
msg: PropTypes.string,
|
msg: PropTypes.string,
|
||||||
|
isEdited: PropTypes.bool,
|
||||||
|
useMarkdown: PropTypes.bool,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
getCustomEmoji: PropTypes.func,
|
||||||
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
getCustomEmoji: PropTypes.func
|
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||||
|
navToRoomInfo: PropTypes.func
|
||||||
};
|
};
|
||||||
Content.displayName = 'MessageContent';
|
Content.displayName = 'MessageContent';
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import FastImage from 'react-native-fast-image';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import Markdown from './Markdown';
|
import Markdown from '../markdown';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { formatAttachmentUrl } from '../../lib/utils';
|
import { formatAttachmentUrl } from '../../lib/utils';
|
||||||
|
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Text, Image } from 'react-native';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { emojify } from 'react-emojione';
|
|
||||||
import MarkdownRenderer, { PluginContainer } from 'react-native-markdown-renderer';
|
|
||||||
import MarkdownFlowdock from 'markdown-it-flowdock';
|
|
||||||
|
|
||||||
import styles from './styles';
|
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
|
||||||
import MarkdownEmojiPlugin from './MarkdownEmojiPlugin';
|
|
||||||
import I18n from '../../i18n';
|
|
||||||
|
|
||||||
const EmojiPlugin = new PluginContainer(MarkdownEmojiPlugin);
|
|
||||||
const MentionsPlugin = new PluginContainer(MarkdownFlowdock);
|
|
||||||
const plugins = [EmojiPlugin, MentionsPlugin];
|
|
||||||
|
|
||||||
// Support <http://link|Text>
|
|
||||||
const formatText = text => text.replace(
|
|
||||||
new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'),
|
|
||||||
(match, url, title) => `[${ title }](${ url })`
|
|
||||||
);
|
|
||||||
|
|
||||||
const emojiRanges = [
|
|
||||||
'\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]', // unicode emoji from https://www.regextester.com/106421
|
|
||||||
':.{1,40}:', // custom emoji
|
|
||||||
' |\n' // allow spaces and line breaks
|
|
||||||
].join('|');
|
|
||||||
|
|
||||||
const removeAllEmoji = str => str.replace(new RegExp(emojiRanges, 'g'), '');
|
|
||||||
|
|
||||||
const isOnlyEmoji = str => !removeAllEmoji(str).length;
|
|
||||||
|
|
||||||
const removeOneEmoji = str => str.replace(new RegExp(emojiRanges), '');
|
|
||||||
|
|
||||||
const emojiCount = (str) => {
|
|
||||||
let oldLength = 0;
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
while (oldLength !== str.length) {
|
|
||||||
oldLength = str.length;
|
|
||||||
str = removeOneEmoji(str);
|
|
||||||
if (oldLength !== str.length) {
|
|
||||||
counter += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return counter;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Markdown = React.memo(({
|
|
||||||
msg, style, rules, baseUrl, username, isEdited, numberOfLines, mentions, channels, getCustomEmoji, useMarkdown = true
|
|
||||||
}) => {
|
|
||||||
if (!msg) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let m = formatText(msg);
|
|
||||||
if (m) {
|
|
||||||
m = emojify(m, { output: 'unicode' });
|
|
||||||
}
|
|
||||||
m = m.replace(/^\[([^\]]*)\]\(([^)]*)\)\s/, '').trim();
|
|
||||||
if (numberOfLines > 0) {
|
|
||||||
m = m.replace(/[\n]+/g, '\n').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!useMarkdown) {
|
|
||||||
return <Text style={styles.text} numberOfLines={numberOfLines}>{m}</Text>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MarkdownRenderer
|
|
||||||
rules={{
|
|
||||||
paragraph: (node, children) => (
|
|
||||||
<Text key={node.key} style={styles.paragraph} numberOfLines={numberOfLines}>
|
|
||||||
{children}
|
|
||||||
{isEdited ? <Text style={styles.edited}> ({I18n.t('edited')})</Text> : null}
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
mention: (node) => {
|
|
||||||
const { content, key } = node;
|
|
||||||
let mentionStyle = styles.mention;
|
|
||||||
if (content === 'all' || content === 'here') {
|
|
||||||
mentionStyle = {
|
|
||||||
...mentionStyle,
|
|
||||||
...styles.mentionAll
|
|
||||||
};
|
|
||||||
} else if (content === username) {
|
|
||||||
mentionStyle = {
|
|
||||||
...mentionStyle,
|
|
||||||
...styles.mentionLoggedUser
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (mentions && mentions.length && mentions.findIndex(mention => mention.username === content) !== -1) {
|
|
||||||
return (
|
|
||||||
<Text style={mentionStyle} key={key}>
|
|
||||||
{content}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return `@${ content }`;
|
|
||||||
},
|
|
||||||
hashtag: (node) => {
|
|
||||||
const { content, key } = node;
|
|
||||||
if (channels && channels.length && channels.findIndex(channel => channel.name === content) !== -1) {
|
|
||||||
return (
|
|
||||||
<Text key={key} style={styles.mention}>
|
|
||||||
#{content}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return `#${ content }`;
|
|
||||||
},
|
|
||||||
emoji: (node) => {
|
|
||||||
if (node.children && node.children.length && node.children[0].content) {
|
|
||||||
const { content } = node.children[0];
|
|
||||||
const emoji = getCustomEmoji && getCustomEmoji(content);
|
|
||||||
if (emoji) {
|
|
||||||
return (
|
|
||||||
<CustomEmoji
|
|
||||||
key={node.key}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
style={isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji}
|
|
||||||
emoji={emoji}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <Text key={node.key}>:{content}:</Text>;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
hardbreak: () => null,
|
|
||||||
blocklink: () => null,
|
|
||||||
image: node => (
|
|
||||||
<Image key={node.key} style={styles.inlineImage} source={{ uri: node.attributes.src }} />
|
|
||||||
),
|
|
||||||
...rules
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
paragraph: styles.paragraph,
|
|
||||||
text: isMessageContainsOnlyEmoji ? styles.textBig : styles.text,
|
|
||||||
codeInline: styles.codeInline,
|
|
||||||
codeBlock: styles.codeBlock,
|
|
||||||
link: styles.link,
|
|
||||||
...style
|
|
||||||
}}
|
|
||||||
plugins={plugins}
|
|
||||||
>{m}
|
|
||||||
</MarkdownRenderer>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Markdown.propTypes = {
|
|
||||||
msg: PropTypes.string,
|
|
||||||
username: PropTypes.string,
|
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
style: PropTypes.any,
|
|
||||||
rules: PropTypes.object,
|
|
||||||
isEdited: PropTypes.bool,
|
|
||||||
numberOfLines: PropTypes.number,
|
|
||||||
useMarkdown: PropTypes.bool,
|
|
||||||
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
|
||||||
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
|
||||||
getCustomEmoji: PropTypes.func
|
|
||||||
};
|
|
||||||
Markdown.displayName = 'MessageMarkdown';
|
|
||||||
|
|
||||||
export default Markdown;
|
|
|
@ -1,78 +0,0 @@
|
||||||
export default function(md) {
|
|
||||||
function tokenize(state, silent) {
|
|
||||||
let token;
|
|
||||||
const start = state.pos;
|
|
||||||
const marker = state.src.charCodeAt(start);
|
|
||||||
|
|
||||||
if (silent) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// :
|
|
||||||
if (marker !== 58) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scanned = state.scanDelims(state.pos, true);
|
|
||||||
const len = scanned.length;
|
|
||||||
const ch = String.fromCharCode(marker);
|
|
||||||
|
|
||||||
for (let i = 0; i < len; i += 1) {
|
|
||||||
token = state.push('text', '', 0);
|
|
||||||
token.content = ch;
|
|
||||||
|
|
||||||
state.delimiters.push({
|
|
||||||
marker,
|
|
||||||
jump: i,
|
|
||||||
token: state.tokens.length - 1,
|
|
||||||
level: state.level,
|
|
||||||
end: -1,
|
|
||||||
open: scanned.can_open,
|
|
||||||
close: scanned.can_close
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
state.pos += scanned.length;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function postProcess(state) {
|
|
||||||
let startDelim;
|
|
||||||
let endDelim;
|
|
||||||
let token;
|
|
||||||
const { delimiters } = state;
|
|
||||||
const max = delimiters.length;
|
|
||||||
|
|
||||||
for (let i = 0; i < max; i += 1) {
|
|
||||||
startDelim = delimiters[i];
|
|
||||||
|
|
||||||
// :
|
|
||||||
if (startDelim.marker !== 58) {
|
|
||||||
continue; // eslint-disable-line
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startDelim.end === -1) {
|
|
||||||
continue; // eslint-disable-line
|
|
||||||
}
|
|
||||||
|
|
||||||
endDelim = delimiters[startDelim.end];
|
|
||||||
|
|
||||||
token = state.tokens[startDelim.token];
|
|
||||||
token.type = 'emoji_open';
|
|
||||||
token.tag = 'emoji';
|
|
||||||
token.nesting = 1;
|
|
||||||
token.markup = ':';
|
|
||||||
token.content = '';
|
|
||||||
|
|
||||||
token = state.tokens[endDelim.token];
|
|
||||||
token.type = 'emoji_close';
|
|
||||||
token.tag = 'emoji';
|
|
||||||
token.nesting = -1;
|
|
||||||
token.markup = ':';
|
|
||||||
token.content = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
md.inline.ruler.before('emphasis', 'emoji', tokenize);
|
|
||||||
md.inline.ruler2.before('emphasis', 'emoji', postProcess);
|
|
||||||
}
|
|
|
@ -1,24 +1,34 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { TouchableOpacity } from 'react-native';
|
||||||
|
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const MessageAvatar = React.memo(({
|
const MessageAvatar = React.memo(({
|
||||||
isHeader, avatar, author, baseUrl, user, small
|
isHeader, avatar, author, baseUrl, user, small, navToRoomInfo
|
||||||
}) => {
|
}) => {
|
||||||
if (isHeader) {
|
if (isHeader) {
|
||||||
|
const navParam = {
|
||||||
|
t: 'd',
|
||||||
|
rid: author._id
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<TouchableOpacity
|
||||||
style={small ? styles.avatarSmall : styles.avatar}
|
onPress={() => navToRoomInfo(navParam)}
|
||||||
text={avatar ? '' : author.username}
|
disabled={author._id === user.id}
|
||||||
size={small ? 20 : 36}
|
>
|
||||||
borderRadius={small ? 2 : 4}
|
<Avatar
|
||||||
avatar={avatar}
|
style={small ? styles.avatarSmall : styles.avatar}
|
||||||
baseUrl={baseUrl}
|
text={avatar ? '' : author.username}
|
||||||
userId={user.id}
|
size={small ? 20 : 36}
|
||||||
token={user.token}
|
borderRadius={small ? 2 : 4}
|
||||||
/>
|
avatar={avatar}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
userId={user.id}
|
||||||
|
token={user.token}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -30,7 +40,8 @@ MessageAvatar.propTypes = {
|
||||||
author: PropTypes.obj,
|
author: PropTypes.obj,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
user: PropTypes.obj,
|
user: PropTypes.obj,
|
||||||
small: PropTypes.bool
|
small: PropTypes.bool,
|
||||||
|
navToRoomInfo: PropTypes.func
|
||||||
};
|
};
|
||||||
MessageAvatar.displayName = 'MessageAvatar';
|
MessageAvatar.displayName = 'MessageAvatar';
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import moment from 'moment';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
import isEqual from 'deep-equal';
|
import isEqual from 'deep-equal';
|
||||||
|
|
||||||
import Markdown from './Markdown';
|
import Markdown from '../markdown';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { COLOR_BACKGROUND_CONTAINER, COLOR_BORDER } from '../../constants/colors';
|
import { COLOR_BACKGROUND_CONTAINER, COLOR_BORDER } from '../../constants/colors';
|
||||||
|
|
|
@ -4,13 +4,13 @@ import { StyleSheet } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
import isEqual from 'deep-equal';
|
import isEqual from 'deep-equal';
|
||||||
|
|
||||||
import Markdown from './Markdown';
|
import Markdown from '../markdown';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { formatAttachmentUrl } from '../../lib/utils';
|
import { formatAttachmentUrl } from '../../lib/utils';
|
||||||
|
|
||||||
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/webm', 'video/3gp', 'video/mkv'])];
|
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
|
||||||
const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1;
|
const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
|
@ -39,7 +39,8 @@ export default class MessageContainer extends React.Component {
|
||||||
toggleReactionPicker: PropTypes.func,
|
toggleReactionPicker: PropTypes.func,
|
||||||
fetchThreadName: PropTypes.func,
|
fetchThreadName: PropTypes.func,
|
||||||
onOpenFileModal: PropTypes.func,
|
onOpenFileModal: PropTypes.func,
|
||||||
onReactionLongPress: PropTypes.func
|
onReactionLongPress: PropTypes.func,
|
||||||
|
navToRoomInfo: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -199,7 +200,7 @@ export default class MessageContainer extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage
|
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
_id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage
|
_id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage
|
||||||
|
@ -263,6 +264,7 @@ export default class MessageContainer extends React.Component {
|
||||||
onDiscussionPress={this.onDiscussionPress}
|
onDiscussionPress={this.onDiscussionPress}
|
||||||
onOpenFileModal={onOpenFileModal}
|
onOpenFileModal={onOpenFileModal}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
navToRoomInfo={navToRoomInfo}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
import { StyleSheet, Platform } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import {
|
import {
|
||||||
COLOR_BORDER, COLOR_PRIMARY, COLOR_WHITE, COLOR_BACKGROUND_CONTAINER
|
COLOR_BORDER, COLOR_PRIMARY, COLOR_WHITE
|
||||||
} from '../../constants/colors';
|
} from '../../constants/colors';
|
||||||
|
|
||||||
const codeFontFamily = Platform.select({
|
|
||||||
ios: { fontFamily: 'Courier New' },
|
|
||||||
android: { fontFamily: 'monospace' }
|
|
||||||
});
|
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
root: {
|
root: {
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
|
@ -34,30 +29,6 @@ export default StyleSheet.create({
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
// flex: 1
|
// flex: 1
|
||||||
},
|
},
|
||||||
text: {
|
|
||||||
fontSize: 16,
|
|
||||||
...sharedStyles.textColorNormal,
|
|
||||||
...sharedStyles.textRegular
|
|
||||||
},
|
|
||||||
textBig: {
|
|
||||||
fontSize: 30,
|
|
||||||
...sharedStyles.textColorNormal,
|
|
||||||
...sharedStyles.textRegular
|
|
||||||
},
|
|
||||||
textInfo: {
|
|
||||||
fontStyle: 'italic',
|
|
||||||
fontSize: 16,
|
|
||||||
...sharedStyles.textColorDescription,
|
|
||||||
...sharedStyles.textRegular
|
|
||||||
},
|
|
||||||
customEmoji: {
|
|
||||||
width: 20,
|
|
||||||
height: 20
|
|
||||||
},
|
|
||||||
customEmojiBig: {
|
|
||||||
width: 30,
|
|
||||||
height: 30
|
|
||||||
},
|
|
||||||
temp: { opacity: 0.3 },
|
temp: { opacity: 0.3 },
|
||||||
marginTop: {
|
marginTop: {
|
||||||
marginTop: 6
|
marginTop: 6
|
||||||
|
@ -143,28 +114,6 @@ export default StyleSheet.create({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
...sharedStyles.textMedium
|
...sharedStyles.textMedium
|
||||||
},
|
},
|
||||||
mention: {
|
|
||||||
...sharedStyles.textMedium,
|
|
||||||
color: '#0072FE',
|
|
||||||
padding: 5,
|
|
||||||
backgroundColor: '#E8F2FF'
|
|
||||||
},
|
|
||||||
mentionLoggedUser: {
|
|
||||||
color: COLOR_WHITE,
|
|
||||||
backgroundColor: COLOR_PRIMARY
|
|
||||||
},
|
|
||||||
mentionAll: {
|
|
||||||
color: COLOR_WHITE,
|
|
||||||
backgroundColor: '#FF5B5A'
|
|
||||||
},
|
|
||||||
paragraph: {
|
|
||||||
marginTop: 0,
|
|
||||||
marginBottom: 0,
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
justifyContent: 'flex-start'
|
|
||||||
},
|
|
||||||
imageContainer: {
|
imageContainer: {
|
||||||
// flex: 1,
|
// flex: 1,
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
@ -186,29 +135,15 @@ export default StyleSheet.create({
|
||||||
height: 300,
|
height: 300,
|
||||||
resizeMode: 'contain'
|
resizeMode: 'contain'
|
||||||
},
|
},
|
||||||
edited: {
|
text: {
|
||||||
fontSize: 14,
|
fontSize: 16,
|
||||||
...sharedStyles.textColorDescription,
|
...sharedStyles.textColorNormal,
|
||||||
...sharedStyles.textRegular
|
...sharedStyles.textRegular
|
||||||
},
|
},
|
||||||
codeInline: {
|
textInfo: {
|
||||||
...sharedStyles.textRegular,
|
fontStyle: 'italic',
|
||||||
...codeFontFamily,
|
fontSize: 16,
|
||||||
borderWidth: 1,
|
...sharedStyles.textColorDescription,
|
||||||
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
|
||||||
borderRadius: 4
|
|
||||||
},
|
|
||||||
codeBlock: {
|
|
||||||
...sharedStyles.textRegular,
|
|
||||||
...codeFontFamily,
|
|
||||||
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
|
||||||
borderColor: COLOR_BORDER,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 4,
|
|
||||||
padding: 4
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
color: COLOR_PRIMARY,
|
|
||||||
...sharedStyles.textRegular
|
...sharedStyles.textRegular
|
||||||
},
|
},
|
||||||
startedDiscussion: {
|
startedDiscussion: {
|
||||||
|
|
|
@ -87,17 +87,20 @@ export default {
|
||||||
alerts: 'alerts',
|
alerts: 'alerts',
|
||||||
All_users_in_the_channel_can_write_new_messages: 'All users in the channel can write new messages',
|
All_users_in_the_channel_can_write_new_messages: 'All users in the channel can write new messages',
|
||||||
All: 'All',
|
All: 'All',
|
||||||
|
All_Messages: 'All Messages',
|
||||||
Allow_Reactions: 'Allow Reactions',
|
Allow_Reactions: 'Allow Reactions',
|
||||||
Alphabetical: 'Alphabetical',
|
Alphabetical: 'Alphabetical',
|
||||||
and_more: 'and more',
|
and_more: 'and more',
|
||||||
and: 'and',
|
and: 'and',
|
||||||
announcement: 'announcement',
|
announcement: 'announcement',
|
||||||
Announcement: 'Announcement',
|
Announcement: 'Announcement',
|
||||||
|
Apply_Your_Certificate: 'Apply Your Certificate',
|
||||||
ARCHIVE: 'ARCHIVE',
|
ARCHIVE: 'ARCHIVE',
|
||||||
archive: 'archive',
|
archive: 'archive',
|
||||||
are_typing: 'are typing',
|
are_typing: 'are typing',
|
||||||
Are_you_sure_question_mark: 'Are you sure?',
|
Are_you_sure_question_mark: 'Are you sure?',
|
||||||
Are_you_sure_you_want_to_leave_the_room: 'Are you sure you want to leave the room {{room}}?',
|
Are_you_sure_you_want_to_leave_the_room: 'Are you sure you want to leave the room {{room}}?',
|
||||||
|
Audio: 'Audio',
|
||||||
Authenticating: 'Authenticating',
|
Authenticating: 'Authenticating',
|
||||||
Auto_Translate: 'Auto-Translate',
|
Auto_Translate: 'Auto-Translate',
|
||||||
Avatar_changed_successfully: 'Avatar changed successfully!',
|
Avatar_changed_successfully: 'Avatar changed successfully!',
|
||||||
|
@ -135,28 +138,34 @@ export default {
|
||||||
Copied_to_clipboard: 'Copied to clipboard!',
|
Copied_to_clipboard: 'Copied to clipboard!',
|
||||||
Copy: 'Copy',
|
Copy: 'Copy',
|
||||||
Permalink: 'Permalink',
|
Permalink: 'Permalink',
|
||||||
|
Certificate_password: 'Certificate Password',
|
||||||
|
Whats_the_password_for_your_certificate: 'What\'s the password for your certificate?',
|
||||||
Create_account: 'Create an account',
|
Create_account: 'Create an account',
|
||||||
Create_Channel: 'Create Channel',
|
Create_Channel: 'Create Channel',
|
||||||
Created_snippet: 'Created a snippet',
|
Created_snippet: 'Created a snippet',
|
||||||
Create_a_new_workspace: 'Create a new workspace',
|
Create_a_new_workspace: 'Create a new workspace',
|
||||||
Create: 'Create',
|
Create: 'Create',
|
||||||
|
Default: 'Default',
|
||||||
Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.',
|
Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.',
|
||||||
delete: 'delete',
|
delete: 'delete',
|
||||||
Delete: 'Delete',
|
Delete: 'Delete',
|
||||||
DELETE: 'DELETE',
|
DELETE: 'DELETE',
|
||||||
description: 'description',
|
description: 'description',
|
||||||
Description: 'Description',
|
Description: 'Description',
|
||||||
|
DESKTOP_OPTIONS: 'DESKTOP OPTIONS',
|
||||||
Directory: 'Directory',
|
Directory: 'Directory',
|
||||||
Direct_Messages: 'Direct Messages',
|
Direct_Messages: 'Direct Messages',
|
||||||
Disable_notifications: 'Disable notifications',
|
Disable_notifications: 'Disable notifications',
|
||||||
Discussions: 'Discussions',
|
Discussions: 'Discussions',
|
||||||
Dont_Have_An_Account: 'Don\'t have an account?',
|
Dont_Have_An_Account: 'Don\'t have an account?',
|
||||||
|
Do_you_have_a_certificate: 'Do you have a certificate?',
|
||||||
Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
|
Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
|
||||||
edit: 'edit',
|
edit: 'edit',
|
||||||
edited: 'edited',
|
edited: 'edited',
|
||||||
Edit: 'Edit',
|
Edit: 'Edit',
|
||||||
Email_or_password_field_is_empty: 'Email or password field is empty',
|
Email_or_password_field_is_empty: 'Email or password field is empty',
|
||||||
Email: 'Email',
|
Email: 'Email',
|
||||||
|
EMAIL: 'EMAIL',
|
||||||
email: 'e-mail',
|
email: 'e-mail',
|
||||||
Enable_Auto_Translate: 'Enable Auto-Translate',
|
Enable_Auto_Translate: 'Enable Auto-Translate',
|
||||||
Enable_markdown: 'Enable markdown',
|
Enable_markdown: 'Enable markdown',
|
||||||
|
@ -176,12 +185,15 @@ export default {
|
||||||
Forgot_password_If_this_email_is_registered: 'If this email is registered, we\'ll send instructions on how to reset your password. If you do not receive an email shortly, please come back and try again.',
|
Forgot_password_If_this_email_is_registered: 'If this email is registered, we\'ll send instructions on how to reset your password. If you do not receive an email shortly, please come back and try again.',
|
||||||
Forgot_password: 'Forgot password',
|
Forgot_password: 'Forgot password',
|
||||||
Forgot_Password: 'Forgot Password',
|
Forgot_Password: 'Forgot Password',
|
||||||
|
Full_table: 'Click to see full table',
|
||||||
Group_by_favorites: 'Group favorites',
|
Group_by_favorites: 'Group favorites',
|
||||||
Group_by_type: 'Group by type',
|
Group_by_type: 'Group by type',
|
||||||
Hide: 'Hide',
|
Hide: 'Hide',
|
||||||
Has_joined_the_channel: 'Has joined the channel',
|
Has_joined_the_channel: 'Has joined the channel',
|
||||||
Has_joined_the_conversation: 'Has joined the conversation',
|
Has_joined_the_conversation: 'Has joined the conversation',
|
||||||
Has_left_the_channel: 'Has left the channel',
|
Has_left_the_channel: 'Has left the channel',
|
||||||
|
IN_APP_AND_DESKTOP: 'IN-APP AND DESKTOP',
|
||||||
|
In_App_and_Desktop_Alert_info: 'Displays a banner at the top of the screen when app is open, and displays a notification on desktop',
|
||||||
Invisible: 'Invisible',
|
Invisible: 'Invisible',
|
||||||
Invite: 'Invite',
|
Invite: 'Invite',
|
||||||
is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance',
|
is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance',
|
||||||
|
@ -243,9 +255,13 @@ export default {
|
||||||
No_Reactions: 'No Reactions',
|
No_Reactions: 'No Reactions',
|
||||||
No_Read_Receipts: 'No Read Receipts',
|
No_Read_Receipts: 'No Read Receipts',
|
||||||
Not_logged: 'Not logged',
|
Not_logged: 'Not logged',
|
||||||
|
Nothing: 'Nothing',
|
||||||
Nothing_to_save: 'Nothing to save!',
|
Nothing_to_save: 'Nothing to save!',
|
||||||
Notify_active_in_this_room: 'Notify active users in this room',
|
Notify_active_in_this_room: 'Notify active users in this room',
|
||||||
Notify_all_in_this_room: 'Notify all in this room',
|
Notify_all_in_this_room: 'Notify all in this room',
|
||||||
|
Notifications: 'Notifications',
|
||||||
|
Notification_Duration: 'Notification Duration',
|
||||||
|
Notification_Preferences: 'Notification Preferences',
|
||||||
Offline: 'Offline',
|
Offline: 'Offline',
|
||||||
Oops: 'Oops!',
|
Oops: 'Oops!',
|
||||||
Online: 'Online',
|
Online: 'Online',
|
||||||
|
@ -269,6 +285,8 @@ export default {
|
||||||
Profile: 'Profile',
|
Profile: 'Profile',
|
||||||
Public_Channel: 'Public Channel',
|
Public_Channel: 'Public Channel',
|
||||||
Public: 'Public',
|
Public: 'Public',
|
||||||
|
PUSH_NOTIFICATIONS: 'PUSH NOTIFICATIONS',
|
||||||
|
Push_Notifications_Alert_Info: 'These notifications are delivered to you when the app is not open',
|
||||||
Quote: 'Quote',
|
Quote: 'Quote',
|
||||||
Reactions_are_disabled: 'Reactions are disabled',
|
Reactions_are_disabled: 'Reactions are disabled',
|
||||||
Reactions_are_enabled: 'Reactions are enabled',
|
Reactions_are_enabled: 'Reactions are enabled',
|
||||||
|
@ -277,6 +295,8 @@ export default {
|
||||||
Read_Only_Channel: 'Read Only Channel',
|
Read_Only_Channel: 'Read Only Channel',
|
||||||
Read_Only: 'Read Only',
|
Read_Only: 'Read Only',
|
||||||
Read_Receipt: 'Read Receipt',
|
Read_Receipt: 'Read Receipt',
|
||||||
|
Receive_Group_Mentions: 'Receive Group Mentions',
|
||||||
|
Receive_Group_Mentions_Info: 'Receive @all and @here mentions',
|
||||||
Register: 'Register',
|
Register: 'Register',
|
||||||
Repeat_Password: 'Repeat Password',
|
Repeat_Password: 'Repeat Password',
|
||||||
Replied_on: 'Replied on:',
|
Replied_on: 'Replied on:',
|
||||||
|
@ -284,6 +304,8 @@ export default {
|
||||||
reply: 'reply',
|
reply: 'reply',
|
||||||
Reply: 'Reply',
|
Reply: 'Reply',
|
||||||
Report: 'Report',
|
Report: 'Report',
|
||||||
|
Receive_Notification: 'Receive Notification',
|
||||||
|
Receive_notifications_from: 'Receive notifications from {{name}}',
|
||||||
Resend: 'Resend',
|
Resend: 'Resend',
|
||||||
Reset_password: 'Reset password',
|
Reset_password: 'Reset password',
|
||||||
resetting_password: 'resetting password',
|
resetting_password: 'resetting password',
|
||||||
|
@ -310,6 +332,7 @@ export default {
|
||||||
Search_by: 'Search by',
|
Search_by: 'Search by',
|
||||||
Search_global_users: 'Search for global users',
|
Search_global_users: 'Search for global users',
|
||||||
Search_global_users_description: 'If you turn-on, you can search for any user from others companies or servers.',
|
Search_global_users_description: 'If you turn-on, you can search for any user from others companies or servers.',
|
||||||
|
Seconds: '{{second}} seconds',
|
||||||
Select_Avatar: 'Select Avatar',
|
Select_Avatar: 'Select Avatar',
|
||||||
Select_Server: 'Select Server',
|
Select_Server: 'Select Server',
|
||||||
Select_Users: 'Select Users',
|
Select_Users: 'Select Users',
|
||||||
|
@ -327,10 +350,13 @@ export default {
|
||||||
Settings_succesfully_changed: 'Settings succesfully changed!',
|
Settings_succesfully_changed: 'Settings succesfully changed!',
|
||||||
Share: 'Share',
|
Share: 'Share',
|
||||||
Share_this_app: 'Share this app',
|
Share_this_app: 'Share this app',
|
||||||
|
Show_Unread_Counter: 'Show Unread Counter',
|
||||||
|
Show_Unread_Counter_Info: 'Unread counter is displayed as a badge on the right of the channel, in the list',
|
||||||
Sign_in_your_server: 'Sign in your server',
|
Sign_in_your_server: 'Sign in your server',
|
||||||
Sign_Up: 'Sign Up',
|
Sign_Up: 'Sign Up',
|
||||||
Some_field_is_invalid_or_empty: 'Some field is invalid or empty',
|
Some_field_is_invalid_or_empty: 'Some field is invalid or empty',
|
||||||
Sorting_by: 'Sorting by {{key}}',
|
Sorting_by: 'Sorting by {{key}}',
|
||||||
|
Sound: 'Sound',
|
||||||
Star_room: 'Star room',
|
Star_room: 'Star room',
|
||||||
Star: 'Star',
|
Star: 'Star',
|
||||||
Starred_Messages: 'Starred Messages',
|
Starred_Messages: 'Starred Messages',
|
||||||
|
@ -339,6 +365,7 @@ export default {
|
||||||
Start_of_conversation: 'Start of conversation',
|
Start_of_conversation: 'Start of conversation',
|
||||||
Started_discussion: 'Started a discussion:',
|
Started_discussion: 'Started a discussion:',
|
||||||
Submit: 'Submit',
|
Submit: 'Submit',
|
||||||
|
Table: 'Table',
|
||||||
Take_a_photo: 'Take a photo',
|
Take_a_photo: 'Take a photo',
|
||||||
Take_a_video: 'Take a video',
|
Take_a_video: 'Take a video',
|
||||||
tap_to_change_status: 'tap to change status',
|
tap_to_change_status: 'tap to change status',
|
||||||
|
@ -404,8 +431,9 @@ export default {
|
||||||
you: 'you',
|
you: 'you',
|
||||||
You: 'You',
|
You: 'You',
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
||||||
|
Your_certificate: 'Your Certificate',
|
||||||
Version_no: 'Version: {{version}}',
|
Version_no: 'Version: {{version}}',
|
||||||
You_will_not_be_able_to_recover_this_message: 'You will not be able to recover this message!',
|
You_will_not_be_able_to_recover_this_message: 'You will not be able to recover this message!',
|
||||||
Change_Language: 'Change Language',
|
Change_Language: 'Change Language',
|
||||||
Crash_report_disclaimer: 'We never track the content of your chats. The crash report only contains relevant information for us in order '
|
Crash_report_disclaimer: 'We never track the content of your chats. The crash report only contains relevant information for us in order to identify problems and fix it.'
|
||||||
};
|
};
|
||||||
|
|
|
@ -178,6 +178,7 @@ export default {
|
||||||
Forgot_password_If_this_email_is_registered: 'Se este e-mail estiver cadastrado, enviaremos instruções sobre como redefinir sua senha. Se você não receber um e-mail em breve, volte e tente novamente.',
|
Forgot_password_If_this_email_is_registered: 'Se este e-mail estiver cadastrado, enviaremos instruções sobre como redefinir sua senha. Se você não receber um e-mail em breve, volte e tente novamente.',
|
||||||
Forgot_password: 'Esqueci minha senha',
|
Forgot_password: 'Esqueci minha senha',
|
||||||
Forgot_Password: 'Esqueci minha senha',
|
Forgot_Password: 'Esqueci minha senha',
|
||||||
|
Full_table: 'Clique para ver a tabela completa',
|
||||||
Group_by_favorites: 'Agrupar favoritos',
|
Group_by_favorites: 'Agrupar favoritos',
|
||||||
Group_by_type: 'Agrupar por tipo',
|
Group_by_type: 'Agrupar por tipo',
|
||||||
Has_joined_the_channel: 'Entrou no canal',
|
Has_joined_the_channel: 'Entrou no canal',
|
||||||
|
@ -326,6 +327,7 @@ export default {
|
||||||
Start_of_conversation: 'Início da conversa',
|
Start_of_conversation: 'Início da conversa',
|
||||||
Started_discussion: 'Iniciou uma discussão:',
|
Started_discussion: 'Iniciou uma discussão:',
|
||||||
Submit: 'Enviar',
|
Submit: 'Enviar',
|
||||||
|
Table: 'Tabela',
|
||||||
Take_a_photo: 'Tirar uma foto',
|
Take_a_photo: 'Tirar uma foto',
|
||||||
Take_a_video: 'Gravar um vídeo',
|
Take_a_video: 'Gravar um vídeo',
|
||||||
Terms_of_Service: ' Termos de Serviço ',
|
Terms_of_Service: ' Termos de Serviço ',
|
||||||
|
|
20
app/index.js
20
app/index.js
|
@ -16,7 +16,9 @@ import { initializePushNotifications, onNotification } from './notifications/pus
|
||||||
import store from './lib/createStore';
|
import store from './lib/createStore';
|
||||||
import NotificationBadge from './notifications/inApp';
|
import NotificationBadge from './notifications/inApp';
|
||||||
import { defaultHeader, onNavigationStateChange } from './utils/navigation';
|
import { defaultHeader, onNavigationStateChange } from './utils/navigation';
|
||||||
|
import { loggerConfig, analytics } from './utils/log';
|
||||||
import Toast from './containers/Toast';
|
import Toast from './containers/Toast';
|
||||||
|
import RocketChat from './lib/rocketchat';
|
||||||
|
|
||||||
useScreens();
|
useScreens();
|
||||||
|
|
||||||
|
@ -119,6 +121,12 @@ const ChatsStack = createStackNavigator({
|
||||||
},
|
},
|
||||||
DirectoryView: {
|
DirectoryView: {
|
||||||
getScreen: () => require('./views/DirectoryView').default
|
getScreen: () => require('./views/DirectoryView').default
|
||||||
|
},
|
||||||
|
TableView: {
|
||||||
|
getScreen: () => require('./views/TableView').default
|
||||||
|
},
|
||||||
|
NotificationPrefView: {
|
||||||
|
getScreen: () => require('./views/NotificationPreferencesView').default
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
defaultNavigationOptions: defaultHeader
|
defaultNavigationOptions: defaultHeader
|
||||||
|
@ -256,6 +264,7 @@ export default class Root extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.init();
|
this.init();
|
||||||
|
this.initCrashReport();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -285,6 +294,17 @@ export default class Root extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initCrashReport = () => {
|
||||||
|
RocketChat.getAllowCrashReport()
|
||||||
|
.then((allowCrashReport) => {
|
||||||
|
if (!allowCrashReport) {
|
||||||
|
loggerConfig.autoNotify = false;
|
||||||
|
loggerConfig.registerBeforeSendCallback(() => false);
|
||||||
|
analytics().setAnalyticsCollectionEnabled(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
|
|
@ -68,7 +68,7 @@ export default function() {
|
||||||
database.delete(emojiRecord);
|
database.delete(emojiRecord);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_emojis_delete', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ export default function() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_custom_emojis', e);
|
log(e);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@ const create = (permissions) => {
|
||||||
try {
|
try {
|
||||||
database.create('permissions', permission, true);
|
database.create('permissions', permission, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_permissions_create', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ export default function() {
|
||||||
database.delete(permission);
|
database.delete(permission);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_permissions_delete', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ export default function() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_permissions', e);
|
log(e);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,14 +21,14 @@ export default function() {
|
||||||
try {
|
try {
|
||||||
database.create('roles', role, true);
|
database.create('roles', role, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_roles_create', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_roles', e);
|
log(e);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ function updateServer(param) {
|
||||||
try {
|
try {
|
||||||
database.databases.serversDB.create('servers', { id: reduxStore.getState().server.server, ...param }, true);
|
database.databases.serversDB.create('servers', { id: reduxStore.getState().server.server, ...param }, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_settings_update_server', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ export default async function() {
|
||||||
try {
|
try {
|
||||||
database.create('settings', { ...setting, _updatedAt: new Date() }, true);
|
database.create('settings', { ...setting, _updatedAt: new Date() }, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_settings_create', e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setting._id === 'Site_Name') {
|
if (setting._id === 'Site_Name') {
|
||||||
|
@ -61,6 +61,6 @@ export default async function() {
|
||||||
updateServer.call(this, { iconURL });
|
updateServer.call(this, { iconURL });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_settings', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default function() {
|
||||||
const result = await this.sdk.get('commands.list');
|
const result = await this.sdk.get('commands.list');
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
log('getSlashCommand fetch', result);
|
console.log(result);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,14 +22,14 @@ export default function() {
|
||||||
try {
|
try {
|
||||||
database.create('slashCommand', command, true);
|
database.create('slashCommand', command, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('get_slash_command', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_slash_command', e);
|
log(e);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,12 +34,6 @@ export const merge = (subscription, room) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subscription.mobilePushNotifications === 'nothing') {
|
|
||||||
subscription.notifications = true;
|
|
||||||
} else {
|
|
||||||
subscription.notifications = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!subscription.name) {
|
if (!subscription.name) {
|
||||||
subscription.name = subscription.fname;
|
subscription.name = subscription.fname;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ async function load({ rid: roomId, latest, t }) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return data.messages;
|
return data.messages;
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.log(error);
|
log(e);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export default function loadMessagesForRoom(...args) {
|
||||||
database.create('threadMessages', message, true);
|
database.create('threadMessages', message, true);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_load_messages_for_room_create', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
return resolve(data);
|
return resolve(data);
|
||||||
|
@ -61,7 +61,7 @@ export default function loadMessagesForRoom(...args) {
|
||||||
return resolve([]);
|
return resolve([]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_load_messages_for_room', e);
|
log(e);
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default function loadMissedMessages(...args) {
|
||||||
database.create('threadMessages', message, true);
|
database.create('threadMessages', message, true);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_load_missed_messages_create', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -65,14 +65,14 @@ export default function loadMissedMessages(...args) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_load_missed_messages_delete', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_load_missed_messages', e);
|
log(e);
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default function loadThreadMessages({ tmid, offset = 0 }) {
|
||||||
message.rid = tmid;
|
message.rid = tmid;
|
||||||
database.create('threadMessages', message, true);
|
database.create('threadMessages', message, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_load_thread_messages_create', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
return resolve(data);
|
return resolve(data);
|
||||||
|
@ -43,7 +43,7 @@ export default function loadThreadMessages({ tmid, offset = 0 }) {
|
||||||
return resolve([]);
|
return resolve([]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_load_thread_messages', e);
|
log(e);
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,6 +18,6 @@ export default async function readMessages(rid) {
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_read_messages', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ export function cancelUpload(path) {
|
||||||
try {
|
try {
|
||||||
database.delete(upload);
|
database.delete(upload);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_send_file_message_delete_upload', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
delete uploadQueue[path];
|
delete uploadQueue[path];
|
||||||
|
@ -45,7 +45,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
||||||
try {
|
try {
|
||||||
database.create('uploads', fileInfo, true);
|
database.create('uploads', fileInfo, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return log('err_send_file_message_create_upload_1', e);
|
return log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
||||||
try {
|
try {
|
||||||
database.create('uploads', fileInfo, true);
|
database.create('uploads', fileInfo, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return log('err_send_file_message_create_upload_2', e);
|
return log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -90,7 +90,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
||||||
resolve(response);
|
resolve(response);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
log('err_send_file_message_delete_upload', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,30 +100,30 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
||||||
database.create('uploads', fileInfo, true);
|
database.create('uploads', fileInfo, true);
|
||||||
const response = JSON.parse(xhr.response);
|
const response = JSON.parse(xhr.response);
|
||||||
reject(response);
|
reject(response);
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
reject(err);
|
reject(e);
|
||||||
log('err_send_file_message_create_upload_3', err);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = (e) => {
|
xhr.onerror = (error) => {
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
fileInfo.error = true;
|
fileInfo.error = true;
|
||||||
try {
|
try {
|
||||||
database.create('uploads', fileInfo, true);
|
database.create('uploads', fileInfo, true);
|
||||||
|
reject(error);
|
||||||
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
} catch (err) {
|
log(e);
|
||||||
reject(err);
|
|
||||||
log('err_send_file_message_create_upload_3', err);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
log('err_send_file_message_create_upload_4', err);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,6 @@ export default async function(rid, msg, tmid, user) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_send_message', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,8 @@ export default function subscribeRoom({ rid }) {
|
||||||
clearTimeout(typingTimeouts[username]);
|
clearTimeout(typingTimeouts[username]);
|
||||||
typingTimeouts[username] = null;
|
typingTimeouts[username] = null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_remove_user_typing', error);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,8 +60,8 @@ export default function subscribeRoom({ rid }) {
|
||||||
typingTimeouts[username] = setTimeout(() => {
|
typingTimeouts[username] = setTimeout(() => {
|
||||||
removeUserTyping(username);
|
removeUserTyping(username);
|
||||||
}, 10000);
|
}, 10000);
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_add_user_typing', error);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -172,7 +172,7 @@ export default function subscribeRoom({ rid }) {
|
||||||
try {
|
try {
|
||||||
promises = this.sdk.subscribeRoom(rid);
|
promises = this.sdk.subscribeRoom(rid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_subscribe_room', e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -44,7 +44,7 @@ export default function subscribeRooms() {
|
||||||
database.delete(subscription);
|
database.delete(subscription);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_stream_msg_received_sub_removed', e);
|
log(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const rooms = database.objects('rooms').filtered('_id == $0', data.rid);
|
const rooms = database.objects('rooms').filtered('_id == $0', data.rid);
|
||||||
|
@ -55,7 +55,7 @@ export default function subscribeRooms() {
|
||||||
database.delete(rooms);
|
database.delete(rooms);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_stream_msg_received_sub_updated', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ export default function subscribeRooms() {
|
||||||
database.create('subscriptions', tmp, true);
|
database.create('subscriptions', tmp, true);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_stream_msg_received_room_updated', e);
|
log(e);
|
||||||
}
|
}
|
||||||
} else if (type === 'inserted') {
|
} else if (type === 'inserted') {
|
||||||
try {
|
try {
|
||||||
|
@ -76,7 +76,7 @@ export default function subscribeRooms() {
|
||||||
database.create('rooms', data, true);
|
database.create('rooms', data, true);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_stream_msg_received_room_inserted', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ export default function subscribeRooms() {
|
||||||
database.create('messages', message, true);
|
database.create('messages', message, true);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_stream_msg_received_message', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ export default function subscribeRooms() {
|
||||||
stop: () => stop()
|
stop: () => stop()
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_subscribe_rooms', e);
|
log(e);
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,14 +95,23 @@ const subscriptionSchema = {
|
||||||
reactWhenReadOnly: { type: 'bool', optional: true },
|
reactWhenReadOnly: { type: 'bool', optional: true },
|
||||||
archived: { type: 'bool', optional: true },
|
archived: { type: 'bool', optional: true },
|
||||||
joinCodeRequired: { type: 'bool', optional: true },
|
joinCodeRequired: { type: 'bool', optional: true },
|
||||||
notifications: { type: 'bool', optional: true },
|
|
||||||
muted: 'string[]',
|
muted: 'string[]',
|
||||||
broadcast: { type: 'bool', optional: true },
|
broadcast: { type: 'bool', optional: true },
|
||||||
prid: { type: 'string', optional: true },
|
prid: { type: 'string', optional: true },
|
||||||
draftMessage: { type: 'string', optional: true },
|
draftMessage: { type: 'string', optional: true },
|
||||||
lastThreadSync: 'date?',
|
lastThreadSync: 'date?',
|
||||||
autoTranslate: 'bool?',
|
autoTranslate: 'bool?',
|
||||||
autoTranslateLanguage: 'string?'
|
autoTranslateLanguage: 'string?',
|
||||||
|
// Notifications
|
||||||
|
emailNotifications: { type: 'string', default: 'default' },
|
||||||
|
disableNotifications: { type: 'bool', default: false },
|
||||||
|
muteGroupMentions: { type: 'bool', default: false },
|
||||||
|
hideUnreadStatus: { type: 'bool', default: false },
|
||||||
|
audioNotifications: { type: 'string', default: 'default' },
|
||||||
|
desktopNotifications: { type: 'string', default: 'default' },
|
||||||
|
audioNotificationValue: { type: 'string', default: '0 Default' },
|
||||||
|
desktopNotificationDuration: { type: 'int', default: 0 },
|
||||||
|
mobilePushNotifications: { type: 'string', default: 'default' }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -474,7 +483,7 @@ class DB {
|
||||||
return this.databases.activeDB = new Realm({
|
return this.databases.activeDB = new Realm({
|
||||||
path: `${ RNRealmPath.realmPath }${ path }.realm`,
|
path: `${ RNRealmPath.realmPath }${ path }.realm`,
|
||||||
schema,
|
schema,
|
||||||
schemaVersion: 13,
|
schemaVersion: 14,
|
||||||
migration: (oldRealm, newRealm) => {
|
migration: (oldRealm, newRealm) => {
|
||||||
if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 13) {
|
if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 13) {
|
||||||
const newSubs = newRealm.objects('subscriptions');
|
const newSubs = newRealm.objects('subscriptions');
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { AsyncStorage, InteractionManager } from 'react-native';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||||
import RNUserDefaults from 'rn-user-defaults';
|
import RNUserDefaults from 'rn-user-defaults';
|
||||||
|
import * as FileSystem from 'expo-file-system';
|
||||||
|
|
||||||
import reduxStore from './createStore';
|
import reduxStore from './createStore';
|
||||||
import defaultSettings from '../constants/settings';
|
import defaultSettings from '../constants/settings';
|
||||||
|
@ -9,6 +10,7 @@ import messagesStatus from '../constants/messagesStatus';
|
||||||
import database from './realm';
|
import database from './realm';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import { isIOS, getBundleId } from '../utils/deviceInfo';
|
import { isIOS, getBundleId } from '../utils/deviceInfo';
|
||||||
|
import { extractHostname } from '../utils/server';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setUser, setLoginServices, loginRequest, loginFailure, logout
|
setUser, setLoginServices, loginRequest, loginFailure, logout
|
||||||
|
@ -45,6 +47,7 @@ import { SERVERS, SERVER_URL } from '../constants/userDefaults';
|
||||||
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||||
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
|
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
|
||||||
export const MARKDOWN_KEY = 'RC_MARKDOWN_KEY';
|
export const MARKDOWN_KEY = 'RC_MARKDOWN_KEY';
|
||||||
|
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
|
||||||
const returnAnArray = obj => obj || [];
|
const returnAnArray = obj => obj || [];
|
||||||
const MIN_ROCKETCHAT_VERSION = '0.70.0';
|
const MIN_ROCKETCHAT_VERSION = '0.70.0';
|
||||||
|
|
||||||
|
@ -52,7 +55,12 @@ const STATUSES = ['offline', 'online', 'away', 'busy'];
|
||||||
|
|
||||||
const RocketChat = {
|
const RocketChat = {
|
||||||
TOKEN_KEY,
|
TOKEN_KEY,
|
||||||
subscribeRooms,
|
async subscribeRooms() {
|
||||||
|
if (this.roomsSub) {
|
||||||
|
this.roomsSub.stop();
|
||||||
|
}
|
||||||
|
this.roomsSub = await subscribeRooms.call(this);
|
||||||
|
},
|
||||||
subscribeRoom,
|
subscribeRoom,
|
||||||
canOpenRoom,
|
canOpenRoom,
|
||||||
createChannel({
|
createChannel({
|
||||||
|
@ -85,7 +93,7 @@ const RocketChat = {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_server_info', e);
|
log(e);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -358,6 +366,12 @@ const RocketChat = {
|
||||||
try {
|
try {
|
||||||
const servers = await RNUserDefaults.objectForKey(SERVERS);
|
const servers = await RNUserDefaults.objectForKey(SERVERS);
|
||||||
await RNUserDefaults.setObjectForKey(SERVERS, servers && servers.filter(srv => srv[SERVER_URL] !== server));
|
await RNUserDefaults.setObjectForKey(SERVERS, servers && servers.filter(srv => srv[SERVER_URL] !== server));
|
||||||
|
// clear certificate for server - SSL Pinning
|
||||||
|
const certificate = await RNUserDefaults.objectForKey(extractHostname(server));
|
||||||
|
if (certificate && certificate.path) {
|
||||||
|
await RNUserDefaults.clear(extractHostname(server));
|
||||||
|
await FileSystem.deleteAsync(certificate.path);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('logout_rn_user_defaults', error);
|
console.log('logout_rn_user_defaults', error);
|
||||||
}
|
}
|
||||||
|
@ -433,7 +447,7 @@ const RocketChat = {
|
||||||
database.create('messages', message, true);
|
database.create('messages', message, true);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_resend_message', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -564,7 +578,7 @@ const RocketChat = {
|
||||||
try {
|
try {
|
||||||
room = await RocketChat.getRoom(message.rid);
|
room = await RocketChat.getRoom(message.rid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_permalink', e);
|
log(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { server } = reduxStore.getState().server;
|
const { server } = reduxStore.getState().server;
|
||||||
|
@ -640,6 +654,10 @@ const RocketChat = {
|
||||||
// RC 0.48.0
|
// RC 0.48.0
|
||||||
return this.sdk.get('users.info', { userId });
|
return this.sdk.get('users.info', { userId });
|
||||||
},
|
},
|
||||||
|
getRoomInfo(roomId) {
|
||||||
|
// RC 0.72.0
|
||||||
|
return this.sdk.get('rooms.info', { roomId });
|
||||||
|
},
|
||||||
getRoomMemberId(rid, currentUserId) {
|
getRoomMemberId(rid, currentUserId) {
|
||||||
if (rid === `${ currentUserId }${ currentUserId }`) {
|
if (rid === `${ currentUserId }${ currentUserId }`) {
|
||||||
return currentUserId;
|
return currentUserId;
|
||||||
|
@ -761,6 +779,13 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
return JSON.parse(useMarkdown);
|
return JSON.parse(useMarkdown);
|
||||||
},
|
},
|
||||||
|
async getAllowCrashReport() {
|
||||||
|
const allowCrashReport = await AsyncStorage.getItem(CRASH_REPORT_KEY);
|
||||||
|
if (allowCrashReport === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return JSON.parse(allowCrashReport);
|
||||||
|
},
|
||||||
async getSortPreferences() {
|
async getSortPreferences() {
|
||||||
const prefs = await RNUserDefaults.objectForKey(SORT_PREFS_KEY);
|
const prefs = await RNUserDefaults.objectForKey(SORT_PREFS_KEY);
|
||||||
return prefs;
|
return prefs;
|
||||||
|
@ -802,9 +827,11 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_determineAuthType(services) {
|
_determineAuthType(services) {
|
||||||
const { name, custom, service } = services;
|
const {
|
||||||
|
name, custom, showButton = true, service
|
||||||
|
} = services;
|
||||||
|
|
||||||
if (custom) {
|
if (custom && showButton) {
|
||||||
return 'oauth_custom';
|
return 'oauth_custom';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -957,8 +984,8 @@ const RocketChat = {
|
||||||
const autoTranslatePermission = database.objectForPrimaryKey('permissions', 'auto-translate');
|
const autoTranslatePermission = database.objectForPrimaryKey('permissions', 'auto-translate');
|
||||||
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || [];
|
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || [];
|
||||||
return autoTranslatePermission.roles.some(role => userRoles.includes(role));
|
return autoTranslatePermission.roles.some(role => userRoles.includes(role));
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_can_auto_translate', error);
|
log(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,7 +13,7 @@ const formatMsg = ({
|
||||||
if (!showLastMessage) {
|
if (!showLastMessage) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (!lastMessage) {
|
if (!lastMessage || lastMessage.pinned) {
|
||||||
return I18n.t('No_Message');
|
return I18n.t('No_Message');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { View, Text } from 'react-native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import log from '../../utils/log';
|
|
||||||
import Check from '../../containers/Check';
|
import Check from '../../containers/Check';
|
||||||
import styles, { ROW_HEIGHT } from './styles';
|
import styles, { ROW_HEIGHT } from './styles';
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@ const ServerItem = React.memo(({
|
||||||
}}
|
}}
|
||||||
defaultSource={{ uri: 'logo' }}
|
defaultSource={{ uri: 'logo' }}
|
||||||
style={styles.serverIcon}
|
style={styles.serverIcon}
|
||||||
onError={() => log('err_loading_server_icon')}
|
onError={() => console.log('err_loading_server_icon')}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { TOGGLE_CRASH_REPORT } from '../actions/actionsTypes';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
allowCrashReport: false
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case TOGGLE_CRASH_REPORT:
|
||||||
|
return {
|
||||||
|
allowCrashReport: action.payload
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -12,6 +12,7 @@ import sortPreferences from './sortPreferences';
|
||||||
import notification from './notification';
|
import notification from './notification';
|
||||||
import markdown from './markdown';
|
import markdown from './markdown';
|
||||||
import share from './share';
|
import share from './share';
|
||||||
|
import crashReport from './crashReport';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
|
@ -26,5 +27,6 @@ export default combineReducers({
|
||||||
sortPreferences,
|
sortPreferences,
|
||||||
notification,
|
notification,
|
||||||
markdown,
|
markdown,
|
||||||
share
|
share,
|
||||||
|
crashReport
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ import * as actions from '../actions';
|
||||||
import { selectServerRequest } from '../actions/server';
|
import { selectServerRequest } from '../actions/server';
|
||||||
import { setAllPreferences } from '../actions/sortPreferences';
|
import { setAllPreferences } from '../actions/sortPreferences';
|
||||||
import { toggleMarkdown } from '../actions/markdown';
|
import { toggleMarkdown } from '../actions/markdown';
|
||||||
|
import { toggleCrashReport } from '../actions/crashReport';
|
||||||
import { APP } from '../actions/actionsTypes';
|
import { APP } from '../actions/actionsTypes';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
|
@ -46,7 +47,7 @@ const restore = function* restore() {
|
||||||
serversDB.create('servers', serverInfo, true);
|
serversDB.create('servers', serverInfo, true);
|
||||||
await RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ serverInfo.id }`, serverItem[USER_ID]);
|
await RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ serverInfo.id }`, serverItem[USER_ID]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_create_servers', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -66,6 +67,9 @@ const restore = function* restore() {
|
||||||
const useMarkdown = yield RocketChat.getUseMarkdown();
|
const useMarkdown = yield RocketChat.getUseMarkdown();
|
||||||
yield put(toggleMarkdown(useMarkdown));
|
yield put(toggleMarkdown(useMarkdown));
|
||||||
|
|
||||||
|
const allowCrashReport = yield RocketChat.getAllowCrashReport();
|
||||||
|
yield put(toggleCrashReport(allowCrashReport));
|
||||||
|
|
||||||
if (!token || !server) {
|
if (!token || !server) {
|
||||||
yield all([
|
yield all([
|
||||||
RNUserDefaults.clear(RocketChat.TOKEN_KEY),
|
RNUserDefaults.clear(RocketChat.TOKEN_KEY),
|
||||||
|
@ -79,7 +83,7 @@ const restore = function* restore() {
|
||||||
|
|
||||||
yield put(actions.appReady({}));
|
yield put(actions.appReady({}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_restore', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
try {
|
try {
|
||||||
serversDB.create('user', user, true);
|
serversDB.create('user', user, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_set_user_token', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
yield put(appStart('inside'));
|
yield put(appStart('inside'));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_handle_login_success', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ const handleLogout = function* handleLogout() {
|
||||||
yield put(appStart('outside'));
|
yield put(appStart('outside'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(appStart('outside'));
|
yield put(appStart('outside'));
|
||||||
log('err_handle_logout', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -78,7 +78,7 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
|
||||||
yield delay(500);
|
yield delay(500);
|
||||||
yield put(replyInit(message, false));
|
yield put(replyInit(message, false));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_reply_broadcast', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ const watchUserTyping = function* watchUserTyping({ rid, status }) {
|
||||||
yield RocketChat.emitTyping(rid, false);
|
yield RocketChat.emitTyping(rid, false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_watch_user_typing', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
put, select, race, take, fork, cancel, takeLatest, delay
|
put, select, race, take, fork, cancel, delay
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
import { BACKGROUND, INACTIVE } from 'redux-enhancer-react-native-appstate';
|
import { BACKGROUND, INACTIVE } from 'redux-enhancer-react-native-appstate';
|
||||||
|
|
||||||
|
@ -10,18 +10,9 @@ import log from '../utils/log';
|
||||||
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
|
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
|
||||||
let roomsSub;
|
|
||||||
|
|
||||||
const removeSub = function removeSub() {
|
|
||||||
if (roomsSub && roomsSub.stop) {
|
|
||||||
roomsSub.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRoomsRequest = function* handleRoomsRequest() {
|
const handleRoomsRequest = function* handleRoomsRequest() {
|
||||||
try {
|
try {
|
||||||
removeSub();
|
yield RocketChat.subscribeRooms();
|
||||||
roomsSub = yield RocketChat.subscribeRooms();
|
|
||||||
const newRoomsUpdatedAt = new Date();
|
const newRoomsUpdatedAt = new Date();
|
||||||
const server = yield select(state => state.server.server);
|
const server = yield select(state => state.server.server);
|
||||||
const [serverRecord] = database.databases.serversDB.objects('servers').filtered('id = $0', server);
|
const [serverRecord] = database.databases.serversDB.objects('servers').filtered('id = $0', server);
|
||||||
|
@ -33,8 +24,8 @@ const handleRoomsRequest = function* handleRoomsRequest() {
|
||||||
subscriptions.forEach((subscription) => {
|
subscriptions.forEach((subscription) => {
|
||||||
try {
|
try {
|
||||||
database.create('subscriptions', subscription, true);
|
database.create('subscriptions', subscription, true);
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_rooms_request_create_sub', error);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,23 +33,18 @@ const handleRoomsRequest = function* handleRoomsRequest() {
|
||||||
try {
|
try {
|
||||||
database.databases.serversDB.create('servers', { id: server, roomsUpdatedAt: newRoomsUpdatedAt }, true);
|
database.databases.serversDB.create('servers', { id: server, roomsUpdatedAt: newRoomsUpdatedAt }, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_rooms_request_update', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
yield put(roomsSuccess());
|
yield put(roomsSuccess());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(roomsFailure(e));
|
yield put(roomsFailure(e));
|
||||||
log('err_rooms_request', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = function handleLogout() {
|
|
||||||
removeSub();
|
|
||||||
};
|
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.LOGOUT, handleLogout);
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const params = yield take(types.ROOMS.REQUEST);
|
const params = yield take(types.ROOMS.REQUEST);
|
||||||
const isAuthenticated = yield select(state => state.login.isAuthenticated);
|
const isAuthenticated = yield select(state => state.login.isAuthenticated);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { setUser } from '../actions/login';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/realm';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
|
import { extractHostname } from '../utils/server';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { SERVERS, TOKEN, SERVER_URL } from '../constants/userDefaults';
|
import { SERVERS, TOKEN, SERVER_URL } from '../constants/userDefaults';
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||||
|
|
||||||
return serverInfo;
|
return serverInfo;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_server_info', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -73,25 +74,30 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
||||||
yield put(selectServerSuccess(server, (serverInfo && serverInfo.version) || version));
|
yield put(selectServerSuccess(server, (serverInfo && serverInfo.version) || version));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(selectServerFailure());
|
yield put(selectServerFailure());
|
||||||
log('err_select_server', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleServerRequest = function* handleServerRequest({ server }) {
|
const handleServerRequest = function* handleServerRequest({ server, certificate }) {
|
||||||
try {
|
try {
|
||||||
const serverInfo = yield getServerInfo({ server });
|
if (certificate) {
|
||||||
|
yield RNUserDefaults.setObjectForKey(extractHostname(server), certificate);
|
||||||
const loginServicesLength = yield RocketChat.getLoginServices(server);
|
|
||||||
if (loginServicesLength === 0) {
|
|
||||||
Navigation.navigate('LoginView');
|
|
||||||
} else {
|
|
||||||
Navigation.navigate('LoginSignupView');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put(selectServerRequest(server, serverInfo.version, false));
|
const serverInfo = yield getServerInfo({ server });
|
||||||
|
|
||||||
|
if (serverInfo) {
|
||||||
|
const loginServicesLength = yield RocketChat.getLoginServices(server);
|
||||||
|
if (loginServicesLength === 0) {
|
||||||
|
Navigation.navigate('LoginView');
|
||||||
|
} else {
|
||||||
|
Navigation.navigate('LoginSignupView');
|
||||||
|
}
|
||||||
|
yield put(selectServerRequest(server, serverInfo.version, false));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(serverFailure());
|
yield put(serverFailure());
|
||||||
log('err_server_request', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() {
|
||||||
setBadgeCount();
|
setBadgeCount();
|
||||||
return yield RocketChat.setUserPresenceOnline();
|
return yield RocketChat.setUserPresenceOnline();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_app_has_come_back_to_foreground', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ const appHasComeBackToBackground = function* appHasComeBackToBackground() {
|
||||||
try {
|
try {
|
||||||
return yield RocketChat.setUserPresenceAway();
|
return yield RocketChat.setUserPresenceAway();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_app_has_come_back_to_background', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ class EventEmitter {
|
||||||
try {
|
try {
|
||||||
listener.apply(this, args);
|
listener.apply(this, args);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_emit', e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
|
import { Client } from 'bugsnag-react-native';
|
||||||
import firebase from 'react-native-firebase';
|
import firebase from 'react-native-firebase';
|
||||||
|
import config from '../../config';
|
||||||
|
|
||||||
export default (event, error) => {
|
const bugsnag = new Client(config.BUGSNAG_API_KEY);
|
||||||
if (typeof error !== 'object') {
|
|
||||||
error = { error };
|
export const { analytics } = firebase;
|
||||||
}
|
export const loggerConfig = bugsnag.config;
|
||||||
firebase.analytics().logEvent(event);
|
export const { leaveBreadcrumb } = bugsnag;
|
||||||
if (__DEV__) {
|
|
||||||
console.warn(event, error);
|
export default (e) => {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
bugsnag.notify(e);
|
||||||
|
} else {
|
||||||
|
console.log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import firebase from 'react-native-firebase';
|
import { analytics, leaveBreadcrumb } from './log';
|
||||||
|
|
||||||
import { HEADER_BACKGROUND, HEADER_TITLE, HEADER_BACK } from '../constants/colors';
|
import { HEADER_BACKGROUND, HEADER_TITLE, HEADER_BACK } from '../constants/colors';
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ export const onNavigationStateChange = (prevState, currentState) => {
|
||||||
const prevScreen = getActiveRouteName(prevState);
|
const prevScreen = getActiveRouteName(prevState);
|
||||||
|
|
||||||
if (prevScreen !== currentScreen) {
|
if (prevScreen !== currentScreen) {
|
||||||
firebase.analytics().setCurrentScreen(currentScreen);
|
analytics().setCurrentScreen(currentScreen);
|
||||||
|
leaveBreadcrumb(currentScreen, { type: 'navigation' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
Extract hostname from url
|
||||||
|
url = 'https://open.rocket.chat/method'
|
||||||
|
hostname = 'open.rocket.chat'
|
||||||
|
*/
|
||||||
|
export const extractHostname = (url) => {
|
||||||
|
let hostname;
|
||||||
|
|
||||||
|
if (url.indexOf('//') > -1) {
|
||||||
|
[,, hostname] = url.split('/');
|
||||||
|
} else {
|
||||||
|
[hostname] = url.split('/');
|
||||||
|
}
|
||||||
|
[hostname] = hostname.split(':');
|
||||||
|
[hostname] = hostname.split('?');
|
||||||
|
|
||||||
|
return hostname;
|
||||||
|
};
|
|
@ -99,8 +99,8 @@ class DirectoryView extends React.Component {
|
||||||
} else {
|
} else {
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_load_directory', error);
|
log(e);
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
}
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
|
@ -109,7 +109,7 @@ class LanguageView extends React.Component {
|
||||||
this.setState({ saving: false });
|
this.setState({ saving: false });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('saving_preferences') }));
|
showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('saving_preferences') }));
|
||||||
log('err_save_user_preferences', e);
|
log(e);
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import firebase from 'react-native-firebase';
|
import { analytics } from '../utils/log';
|
||||||
|
|
||||||
import KeyboardView from '../presentation/KeyboardView';
|
import KeyboardView from '../presentation/KeyboardView';
|
||||||
import TextInput from '../containers/TextInput';
|
import TextInput from '../containers/TextInput';
|
||||||
|
@ -156,7 +156,7 @@ class LoginView extends React.Component {
|
||||||
const { loginRequest } = this.props;
|
const { loginRequest } = this.props;
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
loginRequest({ user, password, code });
|
loginRequest({ user, password, code });
|
||||||
firebase.analytics().logEvent('login');
|
analytics().logEvent('login');
|
||||||
}
|
}
|
||||||
|
|
||||||
register = () => {
|
register = () => {
|
||||||
|
|
|
@ -35,7 +35,8 @@ class MessagesView extends React.Component {
|
||||||
loading: false,
|
loading: false,
|
||||||
messages: [],
|
messages: [],
|
||||||
selectedAttachment: {},
|
selectedAttachment: {},
|
||||||
photoModalVisible: false
|
photoModalVisible: false,
|
||||||
|
fileLoading: true
|
||||||
};
|
};
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
this.t = props.navigation.getParam('t');
|
this.t = props.navigation.getParam('t');
|
||||||
|
@ -47,7 +48,9 @@ class MessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const { loading, messages, photoModalVisible } = this.state;
|
const {
|
||||||
|
loading, messages, photoModalVisible, fileLoading
|
||||||
|
} = this.state;
|
||||||
if (nextState.loading !== loading) {
|
if (nextState.loading !== loading) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +60,10 @@ class MessagesView extends React.Component {
|
||||||
if (!equal(nextState.messages, messages)) {
|
if (!equal(nextState.messages, messages)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (fileLoading !== nextState.fileLoading) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,6 +232,10 @@ class MessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFileLoading = (fileLoading) => {
|
||||||
|
this.setState({ fileLoading });
|
||||||
|
}
|
||||||
|
|
||||||
renderEmpty = () => (
|
renderEmpty = () => (
|
||||||
<View style={styles.listEmptyContainer} testID={this.content.testID}>
|
<View style={styles.listEmptyContainer} testID={this.content.testID}>
|
||||||
<Text style={styles.noDataFound}>{this.content.noDataMsg}</Text>
|
<Text style={styles.noDataFound}>{this.content.noDataMsg}</Text>
|
||||||
|
@ -235,7 +246,7 @@ class MessagesView extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
messages, loading, selectedAttachment, photoModalVisible
|
messages, loading, selectedAttachment, photoModalVisible, fileLoading
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { user, baseUrl } = this.props;
|
const { user, baseUrl } = this.props;
|
||||||
|
|
||||||
|
@ -260,6 +271,8 @@ class MessagesView extends React.Component {
|
||||||
onClose={this.onCloseFileModal}
|
onClose={this.onCloseFileModal}
|
||||||
user={user}
|
user={user}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
|
loading={fileLoading}
|
||||||
|
setLoading={this.setFileLoading}
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Text, ScrollView, Keyboard, Image, StyleSheet, TouchableOpacity
|
Text, ScrollView, Keyboard, Image, StyleSheet, TouchableOpacity, View, Alert, LayoutAnimation
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
import * as FileSystem from 'expo-file-system';
|
||||||
|
import DocumentPicker from 'react-native-document-picker';
|
||||||
|
import ActionSheet from 'react-native-action-sheet';
|
||||||
|
import isEqual from 'deep-equal';
|
||||||
|
|
||||||
import { serverRequest } from '../actions/server';
|
import { serverRequest } from '../actions/server';
|
||||||
import sharedStyles from './Styles';
|
import sharedStyles from './Styles';
|
||||||
|
@ -18,6 +22,7 @@ import { isIOS, isNotch } from '../utils/deviceInfo';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from '../lib/Icons';
|
||||||
import StatusBar from '../containers/StatusBar';
|
import StatusBar from '../containers/StatusBar';
|
||||||
import { COLOR_PRIMARY } from '../constants/colors';
|
import { COLOR_PRIMARY } from '../constants/colors';
|
||||||
|
import log from '../utils/log';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
image: {
|
image: {
|
||||||
|
@ -41,6 +46,22 @@ const styles = StyleSheet.create({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
paddingHorizontal: 9,
|
paddingHorizontal: 9,
|
||||||
left: 15
|
left: 15
|
||||||
|
},
|
||||||
|
certificatePicker: {
|
||||||
|
flex: 1,
|
||||||
|
marginTop: 40,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
chooseCertificateTitle: {
|
||||||
|
fontSize: 15,
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
...sharedStyles.textColorDescription
|
||||||
|
},
|
||||||
|
chooseCertificate: {
|
||||||
|
fontSize: 15,
|
||||||
|
...sharedStyles.textSemibold,
|
||||||
|
...sharedStyles.textColorHeaderBack
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -61,9 +82,19 @@ class NewServerView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const server = props.navigation.getParam('server');
|
const server = props.navigation.getParam('server');
|
||||||
|
|
||||||
|
// Cancel
|
||||||
|
this.options = [I18n.t('Cancel')];
|
||||||
|
this.CANCEL_INDEX = 0;
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
this.options.push(I18n.t('Delete'));
|
||||||
|
this.DELETE_INDEX = 1;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
text: server || '',
|
text: server || '',
|
||||||
autoFocus: !server
|
autoFocus: !server,
|
||||||
|
certificate: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,11 +107,14 @@ class NewServerView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const { text } = this.state;
|
const { text, certificate } = this.state;
|
||||||
const { connecting } = this.props;
|
const { connecting } = this.props;
|
||||||
if (nextState.text !== text) {
|
if (nextState.text !== text) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!isEqual(nextState.certificate, certificate)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (nextProps.connecting !== connecting) {
|
if (nextProps.connecting !== connecting) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -91,13 +125,51 @@ class NewServerView extends React.Component {
|
||||||
this.setState({ text });
|
this.setState({ text });
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = () => {
|
submit = async() => {
|
||||||
const { text } = this.state;
|
const { text, certificate } = this.state;
|
||||||
const { connectServer } = this.props;
|
const { connectServer } = this.props;
|
||||||
|
let cert = null;
|
||||||
|
|
||||||
|
if (certificate) {
|
||||||
|
const certificatePath = `${ FileSystem.documentDirectory }/${ certificate.name }`;
|
||||||
|
try {
|
||||||
|
await FileSystem.copyAsync({ from: certificate.path, to: certificatePath });
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
cert = {
|
||||||
|
path: this.uriToPath(certificatePath), // file:// isn't allowed by obj-C
|
||||||
|
password: certificate.password
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
connectServer(this.completeUrl(text));
|
connectServer(this.completeUrl(text), cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chooseCertificate = async() => {
|
||||||
|
try {
|
||||||
|
const res = await DocumentPicker.pick({
|
||||||
|
type: ['com.rsa.pkcs-12']
|
||||||
|
});
|
||||||
|
const { uri: path, name } = res;
|
||||||
|
Alert.prompt(
|
||||||
|
I18n.t('Certificate_password'),
|
||||||
|
I18n.t('Whats_the_password_for_your_certificate'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'OK',
|
||||||
|
onPress: password => this.saveCertificate({ path, name, password })
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'secure-text',
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!DocumentPicker.isCancel(e)) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +192,25 @@ class NewServerView extends React.Component {
|
||||||
return url.replace(/\/+$/, '');
|
return url.replace(/\/+$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uriToPath = uri => uri.replace('file://', '');
|
||||||
|
|
||||||
|
saveCertificate = (certificate) => {
|
||||||
|
LayoutAnimation.easeInEaseOut();
|
||||||
|
this.setState({ certificate });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDelete = () => this.setState({ certificate: null }); // We not need delete file from DocumentPicker because it is a temp file
|
||||||
|
|
||||||
|
showActionSheet = () => {
|
||||||
|
ActionSheet.showActionSheetWithOptions({
|
||||||
|
options: this.options,
|
||||||
|
cancelButtonIndex: this.CANCEL_INDEX,
|
||||||
|
destructiveButtonIndex: this.DELETE_INDEX
|
||||||
|
}, (actionIndex) => {
|
||||||
|
if (actionIndex === this.DELETE_INDEX) { this.handleDelete(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderBack = () => {
|
renderBack = () => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
|
|
||||||
|
@ -142,6 +233,18 @@ class NewServerView extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderCertificatePicker = () => {
|
||||||
|
const { certificate } = this.state;
|
||||||
|
return (
|
||||||
|
<View style={styles.certificatePicker}>
|
||||||
|
<Text style={styles.chooseCertificateTitle}>{certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')}</Text>
|
||||||
|
<TouchableOpacity onPress={certificate ? this.showActionSheet : this.chooseCertificate} testID='new-server-choose-certificate'>
|
||||||
|
<Text style={styles.chooseCertificate}>{certificate ? certificate.name : I18n.t('Apply_Your_Certificate')}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { connecting } = this.props;
|
const { connecting } = this.props;
|
||||||
const { text, autoFocus } = this.state;
|
const { text, autoFocus } = this.state;
|
||||||
|
@ -175,6 +278,7 @@ class NewServerView extends React.Component {
|
||||||
loading={connecting}
|
loading={connecting}
|
||||||
testID='new-server-view-button'
|
testID='new-server-view-button'
|
||||||
/>
|
/>
|
||||||
|
{ isIOS ? this.renderCertificatePicker() : null }
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
{this.renderBack()}
|
{this.renderBack()}
|
||||||
|
@ -188,7 +292,7 @@ const mapStateToProps = state => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
connectServer: server => dispatch(serverRequest(server))
|
connectServer: (server, certificate) => dispatch(serverRequest(server, certificate))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(NewServerView);
|
export default connect(mapStateToProps, mapDispatchToProps)(NewServerView);
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
View, ScrollView, Switch, Text
|
||||||
|
} from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import RNPickerSelect from 'react-native-picker-select';
|
||||||
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
|
||||||
|
import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
||||||
|
import StatusBar from '../../containers/StatusBar';
|
||||||
|
import ListItem from '../../containers/ListItem';
|
||||||
|
import Separator from '../../containers/Separator';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
import styles from './styles';
|
||||||
|
import sharedStyles from '../Styles';
|
||||||
|
import database from '../../lib/realm';
|
||||||
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
import log from '../../utils/log';
|
||||||
|
|
||||||
|
const SectionTitle = React.memo(({ title }) => <Text style={styles.sectionTitle}>{title}</Text>);
|
||||||
|
|
||||||
|
const SectionSeparator = React.memo(() => <View style={styles.sectionSeparatorBorder} />);
|
||||||
|
|
||||||
|
const Info = React.memo(({ info }) => <Text style={styles.infoText}>{info}</Text>);
|
||||||
|
|
||||||
|
SectionTitle.propTypes = {
|
||||||
|
title: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
Info.propTypes = {
|
||||||
|
info: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const OPTIONS = {
|
||||||
|
desktopNotifications: [{
|
||||||
|
label: I18n.t('Default'), value: 'default'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('All_Messages'), value: 'all'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Mentions'), value: 'mentions'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Nothing'), value: 'nothing'
|
||||||
|
}],
|
||||||
|
audioNotifications: [{
|
||||||
|
label: I18n.t('Default'), value: 'default'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('All_Messages'), value: 'all'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Mentions'), value: 'mentions'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Nothing'), value: 'nothing'
|
||||||
|
}],
|
||||||
|
mobilePushNotifications: [{
|
||||||
|
label: I18n.t('Default'), value: 'default'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('All_Messages'), value: 'all'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Mentions'), value: 'mentions'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Nothing'), value: 'nothing'
|
||||||
|
}],
|
||||||
|
emailNotifications: [{
|
||||||
|
label: I18n.t('Default'), value: 'default'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('All_Messages'), value: 'all'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Mentions'), value: 'mentions'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Nothing'), value: 'nothing'
|
||||||
|
}],
|
||||||
|
desktopNotificationDuration: [{
|
||||||
|
label: I18n.t('Default'), value: 0
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Seconds', { second: 1 }), value: 1
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Seconds', { second: 2 }), value: 2
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Seconds', { second: 3 }), value: 3
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Seconds', { second: 4 }), value: 4
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Seconds', { second: 5 }), value: 5
|
||||||
|
}],
|
||||||
|
audioNotificationValue: [{
|
||||||
|
label: 'None', value: 'none None'
|
||||||
|
}, {
|
||||||
|
label: I18n.t('Default'), value: '0 Default'
|
||||||
|
}, {
|
||||||
|
label: 'Beep', value: 'beep Beep'
|
||||||
|
}, {
|
||||||
|
label: 'Ding', value: 'ding Ding'
|
||||||
|
}, {
|
||||||
|
label: 'Chelle', value: 'chelle Chelle'
|
||||||
|
}, {
|
||||||
|
label: 'Droplet', value: 'droplet Droplet'
|
||||||
|
}, {
|
||||||
|
label: 'Highbell', value: 'highbell Highbell'
|
||||||
|
}, {
|
||||||
|
label: 'Seasons', value: 'seasons Seasons'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class NotificationPreferencesView extends React.Component {
|
||||||
|
static navigationOptions = () => ({
|
||||||
|
title: I18n.t('Notification_Preferences')
|
||||||
|
})
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
|
this.state = {
|
||||||
|
room: JSON.parse(JSON.stringify(this.rooms[0] || {}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChangeSwitch = async(key, value) => {
|
||||||
|
const { room: newRoom } = this.state;
|
||||||
|
newRoom[key] = value;
|
||||||
|
this.setState({ room: newRoom });
|
||||||
|
const params = {
|
||||||
|
[key]: value ? '1' : '0'
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await RocketChat.saveNotificationSettings(this.rid, params);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChangePicker = async(key, value) => {
|
||||||
|
const { room: newRoom } = this.state;
|
||||||
|
newRoom[key] = value;
|
||||||
|
this.setState({ room: newRoom });
|
||||||
|
const params = {
|
||||||
|
[key]: value.toString()
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await RocketChat.saveNotificationSettings(this.rid, params);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPicker = (key) => {
|
||||||
|
const { room } = this.state;
|
||||||
|
return (
|
||||||
|
<RNPickerSelect
|
||||||
|
testID={key}
|
||||||
|
style={{ viewContainer: styles.viewContainer }}
|
||||||
|
value={room[key]}
|
||||||
|
textInputProps={{ style: styles.pickerText }}
|
||||||
|
useNativeAndroidPickerStyle={false}
|
||||||
|
placeholder={{}}
|
||||||
|
onValueChange={value => this.onValueChangePicker(key, value)}
|
||||||
|
items={OPTIONS[key]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSwitch = (key) => {
|
||||||
|
const { room } = this.state;
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
value={!room[key]}
|
||||||
|
testID={key}
|
||||||
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
|
onValueChange={value => this.onValueChangeSwitch(key, !value)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { room } = this.state;
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={sharedStyles.listSafeArea} testID='notification-preference-view' forceInset={{ vertical: 'never' }}>
|
||||||
|
<StatusBar />
|
||||||
|
<ScrollView
|
||||||
|
{...scrollPersistTaps}
|
||||||
|
contentContainerStyle={styles.contentContainer}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
testID='notification-preference-view-list'
|
||||||
|
>
|
||||||
|
<Separator />
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Receive_Notification')}
|
||||||
|
testID='notification-preference-view-receive-notification'
|
||||||
|
right={() => this.renderSwitch('disableNotifications')}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<Info info={I18n.t('Receive_notifications_from', { name: room.name })} />
|
||||||
|
<SectionSeparator />
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Receive_Group_Mentions')}
|
||||||
|
testID='notification-preference-view-group-mentions'
|
||||||
|
right={() => this.renderSwitch('muteGroupMentions')}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<Info info={I18n.t('Receive_Group_Mentions_Info')} />
|
||||||
|
|
||||||
|
<SectionSeparator />
|
||||||
|
<Separator />
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Show_Unread_Counter')}
|
||||||
|
testID='notification-preference-view-unread-count'
|
||||||
|
right={() => this.renderSwitch('hideUnreadStatus')}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<Info info={I18n.t('Show_Unread_Counter_Info')} />
|
||||||
|
|
||||||
|
<SectionSeparator />
|
||||||
|
<SectionTitle title={I18n.t('IN_APP_AND_DESKTOP')} />
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Alert')}
|
||||||
|
testID='notification-preference-view-alert'
|
||||||
|
right={() => this.renderPicker('desktopNotifications')}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<Info info={I18n.t('In_App_and_Desktop_Alert_info')} />
|
||||||
|
|
||||||
|
<SectionSeparator />
|
||||||
|
<SectionTitle title={I18n.t('PUSH_NOTIFICATIONS')} />
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Alert')}
|
||||||
|
testID='notification-preference-view-push-notification'
|
||||||
|
right={() => this.renderPicker('mobilePushNotifications')}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<Info info={I18n.t('Push_Notifications_Alert_Info')} />
|
||||||
|
|
||||||
|
<SectionSeparator />
|
||||||
|
<SectionTitle title={I18n.t('DESKTOP_OPTIONS')} />
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Audio')}
|
||||||
|
testID='notification-preference-view-audio'
|
||||||
|
right={() => this.renderPicker('audioNotifications')}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Sound')}
|
||||||
|
testID='notification-preference-view-sound'
|
||||||
|
right={() => this.renderPicker('audioNotificationValue')}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Notification_Duration')}
|
||||||
|
testID='notification-preference-view-notification-duration'
|
||||||
|
right={() => this.renderPicker('desktopNotificationDuration')}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<SectionSeparator />
|
||||||
|
<SectionTitle title={I18n.t('EMAIL')} />
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Alert')}
|
||||||
|
testID='notification-preference-view-email-alert'
|
||||||
|
right={() => this.renderPicker('emailNotifications')}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<View style={styles.marginBottom} />
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
import { COLOR_BACKGROUND_CONTAINER, COLOR_PRIMARY, COLOR_WHITE } from '../../constants/colors';
|
||||||
|
import sharedStyles from '../Styles';
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
sectionSeparatorBorder: {
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
||||||
|
height: 10
|
||||||
|
},
|
||||||
|
marginBottom: {
|
||||||
|
height: 30,
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER
|
||||||
|
},
|
||||||
|
contentContainer: {
|
||||||
|
backgroundColor: COLOR_WHITE,
|
||||||
|
marginVertical: 10
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
...sharedStyles.textColorNormal,
|
||||||
|
fontSize: 13,
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
paddingVertical: 10,
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
...sharedStyles.separatorBottom,
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
||||||
|
paddingVertical: 10,
|
||||||
|
fontSize: 14,
|
||||||
|
...sharedStyles.textColorNormal
|
||||||
|
},
|
||||||
|
viewContainer: {
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
pickerText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontSize: 16,
|
||||||
|
color: COLOR_PRIMARY
|
||||||
|
}
|
||||||
|
});
|
|
@ -63,7 +63,7 @@ class ProfileView extends React.Component {
|
||||||
const result = await RocketChat.getAvatarSuggestion();
|
const result = await RocketChat.getAvatarSuggestion();
|
||||||
this.setState({ avatarSuggestions: result });
|
this.setState({ avatarSuggestions: result });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_avatar_suggestion', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ class RegisterView extends React.Component {
|
||||||
try {
|
try {
|
||||||
this.parsedCustomFields = JSON.parse(props.Accounts_CustomFields);
|
this.parsedCustomFields = JSON.parse(props.Accounts_CustomFields);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_parsing_account_custom_fields', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object.keys(this.parsedCustomFields).forEach((key) => {
|
Object.keys(this.parsedCustomFields).forEach((key) => {
|
||||||
|
|
|
@ -64,8 +64,8 @@ class RoomActionsView extends React.Component {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState({ room: { ...result.channel, rid: result.channel._id } });
|
this.setState({ room: { ...result.channel, rid: result.channel._id } });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_get_channel_info', error);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,8 +75,8 @@ class RoomActionsView extends React.Component {
|
||||||
if (counters.success) {
|
if (counters.success) {
|
||||||
this.setState({ membersCount: counters.members, joined: counters.joined });
|
this.setState({ membersCount: counters.members, joined: counters.joined });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_get_room_counters', error);
|
log(e);
|
||||||
}
|
}
|
||||||
} else if (room.t === 'd') {
|
} else if (room.t === 'd') {
|
||||||
this.updateRoomMember();
|
this.updateRoomMember();
|
||||||
|
@ -168,13 +168,14 @@ class RoomActionsView extends React.Component {
|
||||||
room, membersCount, canViewMembers, joined, canAutoTranslate
|
room, membersCount, canViewMembers, joined, canAutoTranslate
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
rid, t, blocker, notifications
|
rid, t, blocker
|
||||||
} = room;
|
} = room;
|
||||||
|
|
||||||
const notificationsAction = {
|
const notificationsAction = {
|
||||||
icon: notifications ? 'bell' : 'Bell-off',
|
icon: 'bell',
|
||||||
name: I18n.t(`${ notifications ? 'Enable' : 'Disable' }_notifications`),
|
name: I18n.t('Notifications'),
|
||||||
event: this.toggleNotifications,
|
route: 'NotificationPrefView',
|
||||||
|
params: { rid },
|
||||||
testID: 'room-actions-notifications'
|
testID: 'room-actions-notifications'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -184,7 +185,7 @@ class RoomActionsView extends React.Component {
|
||||||
name: I18n.t('Room_Info'),
|
name: I18n.t('Room_Info'),
|
||||||
route: 'RoomInfoView',
|
route: 'RoomInfoView',
|
||||||
// forward room only if room isn't joined
|
// forward room only if room isn't joined
|
||||||
params: { rid, t, room: joined ? null : room },
|
params: { rid, t },
|
||||||
testID: 'room-actions-info'
|
testID: 'room-actions-info'
|
||||||
}],
|
}],
|
||||||
renderItem: this.renderRoomInfo
|
renderItem: this.renderRoomInfo
|
||||||
|
@ -341,7 +342,7 @@ class RoomActionsView extends React.Component {
|
||||||
this.setState({ member: result.user });
|
this.setState({ member: result.user });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_update_room_member', e);
|
log(e);
|
||||||
this.setState({ member: {} });
|
this.setState({ member: {} });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,7 +354,7 @@ class RoomActionsView extends React.Component {
|
||||||
try {
|
try {
|
||||||
RocketChat.toggleBlockUser(rid, member._id, !blocker);
|
RocketChat.toggleBlockUser(rid, member._id, !blocker);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_toggle_block_user', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,18 +387,6 @@ class RoomActionsView extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleNotifications = () => {
|
|
||||||
const { room } = this.state;
|
|
||||||
try {
|
|
||||||
const notifications = {
|
|
||||||
mobilePushNotifications: room.notifications ? 'default' : 'nothing'
|
|
||||||
};
|
|
||||||
RocketChat.saveNotificationSettings(room.rid, notifications);
|
|
||||||
} catch (e) {
|
|
||||||
log('err_toggle_notifications', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRoomInfo = ({ item }) => {
|
renderRoomInfo = ({ item }) => {
|
||||||
const { room, member } = this.state;
|
const { room, member } = this.state;
|
||||||
const { name, t, topic } = room;
|
const { name, t, topic } = room;
|
||||||
|
|
|
@ -206,7 +206,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
this.setState({ nameError: e });
|
this.setState({ nameError: e });
|
||||||
}
|
}
|
||||||
error = true;
|
error = true;
|
||||||
log('err_save_room_settings', e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.setState({ saving: false });
|
await this.setState({ saving: false });
|
||||||
|
@ -261,7 +261,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
try {
|
try {
|
||||||
await RocketChat.toggleArchiveRoom(rid, t, !archived);
|
await RocketChat.toggleArchiveRoom(rid, t, !archived);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_toggle_archive', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ import log from '../../utils/log';
|
||||||
const PERMISSION_EDIT_ROOM = 'edit-room';
|
const PERMISSION_EDIT_ROOM = 'edit-room';
|
||||||
|
|
||||||
const camelize = str => str.replace(/^(.)/, (match, chr) => chr.toUpperCase());
|
const camelize = str => str.replace(/^(.)/, (match, chr) => chr.toUpperCase());
|
||||||
const getRoomTitle = room => (room.t === 'd'
|
const getRoomTitle = (room, type, name) => (type === 'd'
|
||||||
? <Text testID='room-info-view-name' style={styles.roomTitle}>{room.fname}</Text>
|
? <Text testID='room-info-view-name' style={styles.roomTitle}>{name}</Text>
|
||||||
: (
|
: (
|
||||||
<View style={styles.roomTitleRow}>
|
<View style={styles.roomTitleRow}>
|
||||||
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} key='room-info-type' />
|
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} key='room-info-type' />
|
||||||
|
@ -59,28 +59,18 @@ class RoomInfoView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
const room = props.navigation.getParam('room');
|
|
||||||
this.t = props.navigation.getParam('t');
|
this.t = props.navigation.getParam('t');
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
|
||||||
this.roles = database.objects('roles');
|
this.roles = database.objects('roles');
|
||||||
this.sub = {
|
this.sub = {
|
||||||
unsubscribe: () => {}
|
unsubscribe: () => {}
|
||||||
};
|
};
|
||||||
this.state = {
|
this.state = {
|
||||||
room: this.rooms[0] || room || {},
|
room: {},
|
||||||
roomUser: {}
|
roomUser: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
|
||||||
const { room } = this.state;
|
|
||||||
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
|
||||||
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) {
|
|
||||||
const { navigation } = this.props;
|
|
||||||
navigation.setParams({ showEdit: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.t === 'd') {
|
if (this.t === 'd') {
|
||||||
const { user } = this.props;
|
const { user } = this.props;
|
||||||
const roomUserId = RocketChat.getRoomMemberId(this.rid, user.id);
|
const roomUserId = RocketChat.getRoomMemberId(this.rid, user.id);
|
||||||
|
@ -89,14 +79,34 @@ class RoomInfoView extends React.Component {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState({ roomUser: result.user });
|
this.setState({ roomUser: result.user });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_get_user_info', error);
|
log(e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
|
safeAddListener(this.rooms, this.updateRoom);
|
||||||
|
let room = {};
|
||||||
|
if (this.rooms.length > 0) {
|
||||||
|
this.setState({ room: this.rooms[0] });
|
||||||
|
[room] = this.rooms;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const result = await RocketChat.getRoomInfo(this.rid);
|
||||||
|
if (result.success) {
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
room = result.room;
|
||||||
|
this.setState({ room });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
||||||
|
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) {
|
||||||
componentWillUnmount() {
|
const { navigation } = this.props;
|
||||||
this.rooms.removeAllListeners();
|
navigation.setParams({ showEdit: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getRoleDescription = (id) => {
|
getRoleDescription = (id) => {
|
||||||
|
@ -107,10 +117,7 @@ class RoomInfoView extends React.Component {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
isDirect = () => {
|
isDirect = () => this.t === 'd'
|
||||||
const { room: { t } } = this.state;
|
|
||||||
return t === 'd';
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRoom = () => {
|
updateRoom = () => {
|
||||||
if (this.rooms.length > 0) {
|
if (this.rooms.length > 0) {
|
||||||
|
@ -181,15 +188,15 @@ class RoomInfoView extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
text={room.name}
|
text={room.name || roomUser.username}
|
||||||
size={100}
|
size={100}
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
type={room.t}
|
type={this.t}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
userId={user.id}
|
userId={user.id}
|
||||||
token={user.token}
|
token={user.token}
|
||||||
>
|
>
|
||||||
{room.t === 'd' && roomUser._id ? <Status style={[sharedStyles.status, styles.status]} size={24} id={roomUser._id} /> : null}
|
{this.t === 'd' && roomUser._id ? <Status style={[sharedStyles.status, styles.status]} size={24} id={roomUser._id} /> : null}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -231,6 +238,29 @@ class RoomInfoView extends React.Component {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderChannel = () => {
|
||||||
|
const { room } = this.state;
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.renderItem('description', room)}
|
||||||
|
{this.renderItem('topic', room)}
|
||||||
|
{this.renderItem('announcement', room)}
|
||||||
|
{room.broadcast ? this.renderBroadcast() : null}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDirect = () => {
|
||||||
|
const { roomUser } = this.state;
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.renderRoles()}
|
||||||
|
{this.renderTimezone()}
|
||||||
|
{this.renderCustomFields(roomUser._id)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { room, roomUser } = this.state;
|
const { room, roomUser } = this.state;
|
||||||
if (!room) {
|
if (!room) {
|
||||||
|
@ -242,15 +272,9 @@ class RoomInfoView extends React.Component {
|
||||||
<SafeAreaView style={styles.container} testID='room-info-view' forceInset={{ vertical: 'never' }}>
|
<SafeAreaView style={styles.container} testID='room-info-view' forceInset={{ vertical: 'never' }}>
|
||||||
<View style={styles.avatarContainer}>
|
<View style={styles.avatarContainer}>
|
||||||
{this.renderAvatar(room, roomUser)}
|
{this.renderAvatar(room, roomUser)}
|
||||||
<View style={styles.roomTitleContainer}>{ getRoomTitle(room) }</View>
|
<View style={styles.roomTitleContainer}>{ getRoomTitle(room, this.t, roomUser && roomUser.name) }</View>
|
||||||
</View>
|
</View>
|
||||||
{!this.isDirect() ? this.renderItem('description', room) : null}
|
{this.isDirect() ? this.renderDirect() : this.renderChannel()}
|
||||||
{!this.isDirect() ? this.renderItem('topic', room) : null}
|
|
||||||
{!this.isDirect() ? this.renderItem('announcement', room) : null}
|
|
||||||
{this.isDirect() ? this.renderRoles() : null}
|
|
||||||
{this.isDirect() ? this.renderTimezone() : null}
|
|
||||||
{this.isDirect() ? this.renderCustomFields(roomUser._id) : null}
|
|
||||||
{room.broadcast ? this.renderBroadcast() : null}
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|
|
@ -138,7 +138,7 @@ class RoomMembersView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_on_press_user', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ class RoomMembersView extends React.Component {
|
||||||
this.fetchMembers();
|
this.fetchMembers();
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_toggle_status', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,8 +203,8 @@ class RoomMembersView extends React.Component {
|
||||||
end: newMembers.length < PAGE_SIZE
|
end: newMembers.length < PAGE_SIZE
|
||||||
});
|
});
|
||||||
navigation.setParams({ allUsers });
|
navigation.setParams({ allUsers });
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_fetch_members, error');
|
log(e);
|
||||||
this.setState({ isLoading: false });
|
this.setState({ isLoading: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,7 +228,7 @@ class RoomMembersView extends React.Component {
|
||||||
await RocketChat.toggleMuteUserInRoom(rid, userLongPressed.username, !userLongPressed.muted);
|
await RocketChat.toggleMuteUserInRoom(rid, userLongPressed.username, !userLongPressed.muted);
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('User_has_been_key', { key: userLongPressed.muted ? I18n.t('unmuted') : I18n.t('muted') }) });
|
EventEmitter.emit(LISTENER, { message: I18n.t('User_has_been_key', { key: userLongPressed.muted ? I18n.t('unmuted') : I18n.t('muted') }) });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_handle_mute', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,10 @@ import { COLOR_TEXT_DESCRIPTION, HEADER_TITLE, COLOR_WHITE } from '../../../cons
|
||||||
const TITLE_SIZE = 16;
|
const TITLE_SIZE = 16;
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
height: '100%'
|
flex: 1,
|
||||||
|
height: '100%',
|
||||||
|
marginRight: isAndroid ? 15 : 5,
|
||||||
|
marginLeft: isAndroid ? 10 : 0
|
||||||
},
|
},
|
||||||
titleContainer: {
|
titleContainer: {
|
||||||
flex: 6,
|
flex: 6,
|
||||||
|
|
|
@ -105,7 +105,7 @@ export class List extends React.PureComponent {
|
||||||
this.setState({ end: result.length < 50, loading: false });
|
this.setState({ end: result.length < 50, loading: false });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
log('err_list_view_on_end_reached', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}, 300)
|
}, 300)
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ class UploadProgress extends Component {
|
||||||
try {
|
try {
|
||||||
database.write(() => database.delete(uploadItem[0]));
|
database.write(() => database.delete(uploadItem[0]));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_upload_progress_delete', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ class UploadProgress extends Component {
|
||||||
try {
|
try {
|
||||||
await RocketChat.cancelUpload(item.path);
|
await RocketChat.cancelUpload(item.path);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_upload_progress_cancel', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ class UploadProgress extends Component {
|
||||||
});
|
});
|
||||||
await RocketChat.sendFileMessage(rid, item, undefined, server, user);
|
await RocketChat.sendFileMessage(rid, item, undefined, server, user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_upload_progress_try_again', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView, HeaderBackButton } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import EJSON from 'ejson';
|
import EJSON from 'ejson';
|
||||||
|
@ -36,7 +36,7 @@ import I18n from '../../i18n';
|
||||||
import RoomHeaderView, { RightButtons } from './Header';
|
import RoomHeaderView, { RightButtons } from './Header';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import Separator from './Separator';
|
import Separator from './Separator';
|
||||||
import { COLOR_WHITE } from '../../constants/colors';
|
import { COLOR_WHITE, HEADER_BACK } from '../../constants/colors';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import buildMessage from '../../lib/methods/helpers/buildMessage';
|
import buildMessage from '../../lib/methods/helpers/buildMessage';
|
||||||
import FileModal from '../../containers/FileModal';
|
import FileModal from '../../containers/FileModal';
|
||||||
|
@ -52,8 +52,8 @@ class RoomView extends React.Component {
|
||||||
const t = navigation.getParam('t');
|
const t = navigation.getParam('t');
|
||||||
const tmid = navigation.getParam('tmid');
|
const tmid = navigation.getParam('tmid');
|
||||||
const toggleFollowThread = navigation.getParam('toggleFollowThread', () => {});
|
const toggleFollowThread = navigation.getParam('toggleFollowThread', () => {});
|
||||||
|
const unreadsCount = navigation.getParam('unreadsCount', null);
|
||||||
return {
|
return {
|
||||||
headerTitleContainerStyle: styles.headerTitleContainerStyle,
|
|
||||||
headerTitle: (
|
headerTitle: (
|
||||||
<RoomHeaderView
|
<RoomHeaderView
|
||||||
rid={rid}
|
rid={rid}
|
||||||
|
@ -72,6 +72,14 @@ class RoomView extends React.Component {
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
toggleFollowThread={toggleFollowThread}
|
toggleFollowThread={toggleFollowThread}
|
||||||
/>
|
/>
|
||||||
|
),
|
||||||
|
headerLeft: (
|
||||||
|
<HeaderBackButton
|
||||||
|
title={unreadsCount > 999 ? '+999' : unreadsCount || ' '}
|
||||||
|
backTitleVisible
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
tintColor={HEADER_BACK}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -112,6 +120,7 @@ class RoomView extends React.Component {
|
||||||
this.t = props.navigation.getParam('t');
|
this.t = props.navigation.getParam('t');
|
||||||
this.tmid = props.navigation.getParam('tmid');
|
this.tmid = props.navigation.getParam('tmid');
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
|
this.chats = database.objects('subscriptions').filtered('rid != $0', this.rid);
|
||||||
const canAutoTranslate = RocketChat.canAutoTranslate();
|
const canAutoTranslate = RocketChat.canAutoTranslate();
|
||||||
this.state = {
|
this.state = {
|
||||||
joined: this.rooms.length > 0,
|
joined: this.rooms.length > 0,
|
||||||
|
@ -149,6 +158,7 @@ class RoomView extends React.Component {
|
||||||
EventEmitter.addEventListener('connected', this.handleConnected);
|
EventEmitter.addEventListener('connected', this.handleConnected);
|
||||||
}
|
}
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
safeAddListener(this.rooms, this.updateRoom);
|
||||||
|
safeAddListener(this.chats, this.updateUnreadCount);
|
||||||
this.mounted = true;
|
this.mounted = true;
|
||||||
});
|
});
|
||||||
console.timeEnd(`${ this.constructor.name } mount`);
|
console.timeEnd(`${ this.constructor.name } mount`);
|
||||||
|
@ -222,6 +232,7 @@ class RoomView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.rooms.removeAllListeners();
|
this.rooms.removeAllListeners();
|
||||||
|
this.chats.removeAllListeners();
|
||||||
if (this.sub && this.sub.stop) {
|
if (this.sub && this.sub.stop) {
|
||||||
this.sub.stop();
|
this.sub.stop();
|
||||||
}
|
}
|
||||||
|
@ -283,7 +294,7 @@ class RoomView extends React.Component {
|
||||||
this.setState({ canAutoTranslate });
|
this.setState({ canAutoTranslate });
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_room_init', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +320,7 @@ class RoomView extends React.Component {
|
||||||
}
|
}
|
||||||
RocketChat.setReaction(shortname, messageId);
|
RocketChat.setReaction(shortname, messageId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_room_on_reaction_press', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -329,6 +340,17 @@ class RoomView extends React.Component {
|
||||||
});
|
});
|
||||||
}, 1000, true)
|
}, 1000, true)
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
updateUnreadCount = debounce(() => {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
const unreadsCount = this.chats.filtered('archived != true && open == true && unread > 0').reduce((a, b) => a + (b.unread || 0), 0);
|
||||||
|
if (unreadsCount !== navigation.getParam('unreadsCount')) {
|
||||||
|
navigation.setParams({
|
||||||
|
unreadsCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 300, false)
|
||||||
|
|
||||||
onThreadPress = debounce((item) => {
|
onThreadPress = debounce((item) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
if (item.tmid) {
|
if (item.tmid) {
|
||||||
|
@ -405,7 +427,7 @@ class RoomView extends React.Component {
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_messages', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,7 +435,7 @@ class RoomView extends React.Component {
|
||||||
try {
|
try {
|
||||||
return RocketChat.loadThreadMessages({ tmid: this.tmid });
|
return RocketChat.loadThreadMessages({ tmid: this.tmid });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_get_thread_messages', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,7 +448,7 @@ class RoomView extends React.Component {
|
||||||
joined: true
|
joined: true
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_join_room', e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -438,8 +460,8 @@ class RoomView extends React.Component {
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
database.create('threads', buildMessage(EJSON.fromJSONValue(thread)), true);
|
database.create('threads', buildMessage(EJSON.fromJSONValue(thread)), true);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_fetch_thread_name', error);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,10 +470,18 @@ class RoomView extends React.Component {
|
||||||
await RocketChat.toggleFollowMessage(this.tmid, !isFollowingThread);
|
await RocketChat.toggleFollowMessage(this.tmid, !isFollowingThread);
|
||||||
EventEmitter.emit(LISTENER, { message: isFollowingThread ? 'Unfollowed thread' : 'Following thread' });
|
EventEmitter.emit(LISTENER, { message: isFollowingThread ? 'Unfollowed thread' : 'Following thread' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_toggle_follow_thread', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navToRoomInfo = (navParam) => {
|
||||||
|
const { navigation, user } = this.props;
|
||||||
|
if (navParam.rid === user.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigation.navigate('RoomInfoView', navParam);
|
||||||
|
}
|
||||||
|
|
||||||
renderItem = (item, previousItem) => {
|
renderItem = (item, previousItem) => {
|
||||||
const { room, lastOpen, canAutoTranslate } = this.state;
|
const { room, lastOpen, canAutoTranslate } = this.state;
|
||||||
const {
|
const {
|
||||||
|
@ -500,6 +530,7 @@ class RoomView extends React.Component {
|
||||||
isReadReceiptEnabled={Message_Read_Receipt_Enabled}
|
isReadReceiptEnabled={Message_Read_Receipt_Enabled}
|
||||||
autoTranslateRoom={canAutoTranslate && room.autoTranslate}
|
autoTranslateRoom={canAutoTranslate && room.autoTranslate}
|
||||||
autoTranslateLanguage={room.autoTranslateLanguage}
|
autoTranslateLanguage={room.autoTranslateLanguage}
|
||||||
|
navToRoomInfo={this.navToRoomInfo}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { StyleSheet } from 'react-native';
|
||||||
import {
|
import {
|
||||||
COLOR_SEPARATOR, COLOR_PRIMARY, COLOR_WHITE, COLOR_TEXT_DESCRIPTION
|
COLOR_SEPARATOR, COLOR_PRIMARY, COLOR_WHITE, COLOR_TEXT_DESCRIPTION
|
||||||
} from '../../constants/colors';
|
} from '../../constants/colors';
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
|
@ -65,9 +64,5 @@ export default StyleSheet.create({
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
...sharedStyles.textMedium,
|
...sharedStyles.textMedium,
|
||||||
...sharedStyles.textColorNormal
|
...sharedStyles.textColorNormal
|
||||||
},
|
|
||||||
headerTitleContainerStyle: {
|
|
||||||
justifyContent: 'flex-start',
|
|
||||||
left: isIOS ? 40 : 50
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -58,7 +58,7 @@ class Sort extends PureComponent {
|
||||||
setSortPreference(param);
|
setSortPreference(param);
|
||||||
RocketChat.saveSortPreference(param);
|
RocketChat.saveSortPreference(param);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_set_sort_preference', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -350,7 +350,7 @@ class RoomsListView extends React.Component {
|
||||||
return this.goRoom({ rid: result.room._id, name: username, t: 'd' });
|
return this.goRoom({ rid: result.room._id, name: username, t: 'd' });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_on_press_item', e);
|
log(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.goRoom(item);
|
return this.goRoom(item);
|
||||||
|
@ -383,7 +383,7 @@ class RoomsListView extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('error_toggle_favorite', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +399,7 @@ class RoomsListView extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('error_toggle_read', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,7 +413,7 @@ class RoomsListView extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('error_hide_channel', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import equal from 'deep-equal';
|
||||||
import RCTextInput from '../../containers/TextInput';
|
import RCTextInput from '../../containers/TextInput';
|
||||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Markdown from '../../containers/message/Markdown';
|
import Markdown from '../../containers/markdown';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import Message from '../../containers/message/Message';
|
import Message from '../../containers/message/Message';
|
||||||
|
@ -68,9 +68,9 @@ class SearchMessagesView extends React.Component {
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
log('err_search_messages', error);
|
log(e);
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ class SelectedUsersView extends React.Component {
|
||||||
await RocketChat.addUsersToRoom(rid);
|
await RocketChat.addUsersToRoom(rid);
|
||||||
navigation.pop();
|
navigation.pop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_add_user', e);
|
log(e);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingInvite(false);
|
setLoadingInvite(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ class SetUsernameView extends React.Component {
|
||||||
await RocketChat.setUsername(username);
|
await RocketChat.setUsername(username);
|
||||||
await loginRequest({ resume: token });
|
await loginRequest({ resume: token });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_submit_username', e);
|
log(e);
|
||||||
}
|
}
|
||||||
this.setState({ saving: false });
|
this.setState({ saving: false });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
View, Linking, ScrollView, AsyncStorage, SafeAreaView, Switch, Share
|
View, Linking, ScrollView, AsyncStorage, SafeAreaView, Switch, Text, Share
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { toggleMarkdown as toggleMarkdownAction } from '../../actions/markdown';
|
import { toggleMarkdown as toggleMarkdownAction } from '../../actions/markdown';
|
||||||
|
import { toggleCrashReport as toggleCrashReportAction } from '../../actions/crashReport';
|
||||||
import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
||||||
import { DrawerButton } from '../../containers/HeaderButton';
|
import { DrawerButton } from '../../containers/HeaderButton';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
|
@ -13,16 +14,25 @@ import ListItem from '../../containers/ListItem';
|
||||||
import { DisclosureImage } from '../../containers/DisclosureIndicator';
|
import { DisclosureImage } from '../../containers/DisclosureIndicator';
|
||||||
import Separator from '../../containers/Separator';
|
import Separator from '../../containers/Separator';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { MARKDOWN_KEY } from '../../lib/rocketchat';
|
import { MARKDOWN_KEY, CRASH_REPORT_KEY } from '../../lib/rocketchat';
|
||||||
import { getReadableVersion, getDeviceModel, isAndroid } from '../../utils/deviceInfo';
|
import { getReadableVersion, getDeviceModel, isAndroid } from '../../utils/deviceInfo';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import { showErrorAlert } from '../../utils/info';
|
import { showErrorAlert } from '../../utils/info';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
|
import { loggerConfig, analytics } from '../../utils/log';
|
||||||
import { PLAY_MARKET_LINK, APP_STORE_LINK, LICENSE_LINK } from '../../constants/links';
|
import { PLAY_MARKET_LINK, APP_STORE_LINK, LICENSE_LINK } from '../../constants/links';
|
||||||
|
|
||||||
const SectionSeparator = React.memo(() => <View style={styles.sectionSeparatorBorder} />);
|
const SectionSeparator = React.memo(() => <View style={styles.sectionSeparatorBorder} />);
|
||||||
|
const ItemInfo = React.memo(({ info }) => (
|
||||||
|
<View style={styles.infoContainer}>
|
||||||
|
<Text style={styles.infoText}>{info}</Text>
|
||||||
|
</View>
|
||||||
|
));
|
||||||
|
ItemInfo.propTypes = {
|
||||||
|
info: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
class SettingsView extends React.Component {
|
class SettingsView extends React.Component {
|
||||||
static navigationOptions = ({ navigation }) => ({
|
static navigationOptions = ({ navigation }) => ({
|
||||||
|
@ -34,7 +44,9 @@ class SettingsView extends React.Component {
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
server: PropTypes.object,
|
server: PropTypes.object,
|
||||||
useMarkdown: PropTypes.bool,
|
useMarkdown: PropTypes.bool,
|
||||||
toggleMarkdown: PropTypes.func
|
allowCrashReport: PropTypes.bool,
|
||||||
|
toggleMarkdown: PropTypes.func,
|
||||||
|
toggleCrashReport: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleMarkdown = (value) => {
|
toggleMarkdown = (value) => {
|
||||||
|
@ -43,6 +55,20 @@ class SettingsView extends React.Component {
|
||||||
toggleMarkdown(value);
|
toggleMarkdown(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleCrashReport = (value) => {
|
||||||
|
AsyncStorage.setItem(CRASH_REPORT_KEY, JSON.stringify(value));
|
||||||
|
const { toggleCrashReport } = this.props;
|
||||||
|
toggleCrashReport(value);
|
||||||
|
loggerConfig.autoNotify = value;
|
||||||
|
analytics().setAnalyticsCollectionEnabled(value);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
loggerConfig.clearBeforeSendCallbacks();
|
||||||
|
} else {
|
||||||
|
loggerConfig.registerBeforeSendCallback(() => false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
navigateToRoom = (room) => {
|
navigateToRoom = (room) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.navigate(room);
|
navigation.navigate(room);
|
||||||
|
@ -81,6 +107,17 @@ class SettingsView extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderCrashReportSwitch = () => {
|
||||||
|
const { allowCrashReport } = this.props;
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
value={allowCrashReport}
|
||||||
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
|
onValueChange={this.toggleCrashReport}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { server } = this.props;
|
const { server } = this.props;
|
||||||
return (
|
return (
|
||||||
|
@ -88,7 +125,7 @@ class SettingsView extends React.Component {
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<ScrollView
|
<ScrollView
|
||||||
{...scrollPersistTaps}
|
{...scrollPersistTaps}
|
||||||
contentContainerStyle={sharedStyles.listContentContainer}
|
contentContainerStyle={[sharedStyles.listContentContainer, styles.listWithoutBorderBottom]}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
testID='settings-view-list'
|
testID='settings-view-list'
|
||||||
>
|
>
|
||||||
|
@ -148,6 +185,18 @@ class SettingsView extends React.Component {
|
||||||
testID='settings-view-markdown'
|
testID='settings-view-markdown'
|
||||||
right={() => this.renderMarkdownSwitch()}
|
right={() => this.renderMarkdownSwitch()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SectionSeparator />
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Send_crash_report')}
|
||||||
|
testID='settings-view-crash-report'
|
||||||
|
right={() => this.renderCrashReportSwitch()}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<ItemInfo
|
||||||
|
info={I18n.t('Crash_report_disclaimer')}
|
||||||
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
@ -156,11 +205,13 @@ class SettingsView extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
server: state.server,
|
server: state.server,
|
||||||
useMarkdown: state.markdown.useMarkdown
|
useMarkdown: state.markdown.useMarkdown,
|
||||||
|
allowCrashReport: state.crashReport.allowCrashReport
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
toggleMarkdown: params => dispatch(toggleMarkdownAction(params))
|
toggleMarkdown: params => dispatch(toggleMarkdownAction(params)),
|
||||||
|
toggleCrashReport: params => dispatch(toggleCrashReportAction(params))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SettingsView);
|
export default connect(mapStateToProps, mapDispatchToProps)(SettingsView);
|
||||||
|
|
|
@ -8,5 +8,18 @@ export default StyleSheet.create({
|
||||||
...sharedStyles.separatorVertical,
|
...sharedStyles.separatorVertical,
|
||||||
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
||||||
height: 10
|
height: 10
|
||||||
|
},
|
||||||
|
listWithoutBorderBottom: {
|
||||||
|
borderBottomWidth: 0
|
||||||
|
},
|
||||||
|
infoContainer: {
|
||||||
|
padding: 15,
|
||||||
|
paddingBottom: 40,
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
fontSize: 14,
|
||||||
|
...sharedStyles.textColorNormal,
|
||||||
|
...sharedStyles.textRegular
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -132,7 +132,7 @@ class ShareListView extends React.Component {
|
||||||
value, fileInfo, isMedia, mediaLoading: false
|
value, fileInfo, isMedia, mediaLoading: false
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_process_media_share_extension', e);
|
log(e);
|
||||||
this.setState({ mediaLoading: false });
|
this.setState({ mediaLoading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ class ShareView extends React.Component {
|
||||||
try {
|
try {
|
||||||
await RocketChat.sendFileMessage(rid, fileMessage, undefined, server, user);
|
await RocketChat.sendFileMessage(rid, fileMessage, undefined, server, user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_send_media_message', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,8 +127,8 @@ class ShareView extends React.Component {
|
||||||
if (value !== '' && rid !== '') {
|
if (value !== '' && rid !== '') {
|
||||||
try {
|
try {
|
||||||
await RocketChat.sendMessage(rid, value, undefined, user);
|
await RocketChat.sendMessage(rid, value, undefined, user);
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
log('err_share_extension_send_message', error);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -157,7 +157,7 @@ class Sidebar extends Component {
|
||||||
try {
|
try {
|
||||||
RocketChat.setUserPresenceDefaultStatus(item.id);
|
RocketChat.setUserPresenceDefaultStatus(item.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_set_user_presence_default_status', e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { ScrollView } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import I18n from '../i18n';
|
||||||
|
import { isIOS } from '../utils/deviceInfo';
|
||||||
|
|
||||||
|
export default class TableView extends React.Component {
|
||||||
|
static navigationOptions = () => ({
|
||||||
|
title: I18n.t('Table')
|
||||||
|
});
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
const renderRows = navigation.getParam('renderRows');
|
||||||
|
const tableWidth = navigation.getParam('tableWidth');
|
||||||
|
|
||||||
|
if (isIOS) {
|
||||||
|
return (
|
||||||
|
<ScrollView contentContainerStyle={{ width: tableWidth }}>
|
||||||
|
{renderRows()}
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<ScrollView horizontal>
|
||||||
|
{renderRows()}
|
||||||
|
</ScrollView>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue