Commit 766a4d0d authored by jbuechele's avatar jbuechele
Browse files

Merge branch 'develop' into v2.5.0

# Conflicts:
#	android/app/build.gradle
#	ios/Podfile.lock
#	ios/Pods/Local Podspecs/EXVideoThumbnails.podspec.json
#	ios/Pods/Manifest.lock
#	ios/Pods/Pods.xcodeproj/project.pbxproj
#	ios/Pods/Target Support Files/Pods-ShareRocketChatRN/Pods-ShareRocketChatRN-acknowledgements.markdown
#	ios/Pods/Target Support Files/Pods-ShareRocketChatRN/Pods-ShareRocketChatRN-acknowledgements.plist
#	ios/Pods/Target Support Files/Pods-ShareRocketChatRN/Pods-ShareRocketChatRN-resources.sh
#	ios/Pods/Target Support Files/Pods-ShareRocketChatRN/Pods-ShareRocketChatRN.debug.xcconfig
#	ios/Pods/Target Support Files/Pods-ShareRocketChatRN/Pods-ShareRocketChatRN.release.xcconfig
#	ios/RocketChatRN.xcodeproj/project.pbxproj
#	ios/RocketChatRN/Info.plist
#	ios/ShareRocketChatRN/Info.plist
parents a311b367 eedea407
......@@ -416,9 +416,13 @@ workflows:
- lint-testunit
# iOS Experimental
- ios-hold-build-experimental:
type: approval
requires:
- lint-testunit
- ios-build-experimental:
requires:
- lint-testunit
- ios-hold-build-experimental
- ios-hold-testflight-experimental:
type: approval
requires:
......@@ -444,9 +448,13 @@ workflows:
- ios-hold-testflight-official
# Android Experimental
- android-hold-build-experimental:
type: approval
requires:
- lint-testunit
- android-build-experimental:
requires:
- lint-testunit
- android-hold-build-experimental
- android-hold-google-play-beta-experimental:
type: approval
requires:
......
......@@ -6,7 +6,7 @@ module.exports = {
}
}
},
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"extends": "airbnb",
"parserOptions": {
"sourceType": "module",
......@@ -21,7 +21,8 @@ module.exports = {
"react",
"jsx-a11y",
"import",
"react-native"
"react-native",
"@babel"
],
"env": {
"browser": true,
......@@ -148,7 +149,8 @@ module.exports = {
"react/jsx-curly-newline": [0],
"react/state-in-constructor": [0],
"no-async-promise-executor": [0],
"max-classes-per-file": [0]
"max-classes-per-file": [0],
"no-multiple-empty-lines": [0]
},
"globals": {
"__DEV__": true
......
......@@ -70,3 +70,5 @@ export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']);
export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET']);
import * as types from './actionsTypes';
export function setPermissions(permissions) {
return {
type: types.PERMISSIONS.SET,
permissions
};
}
......@@ -63,6 +63,7 @@ export const themes = {
passcodeDotFull: '#6C727A',
previewBackground: '#1F2329',
previewTintColor: '#ffffff',
backdropOpacity: 0.3,
...mentions
},
dark: {
......@@ -109,6 +110,7 @@ export const themes = {
passcodeDotFull: '#6C727A',
previewBackground: '#030b1b',
previewTintColor: '#ffffff',
backdropOpacity: 0.9,
...mentions
},
black: {
......@@ -155,6 +157,7 @@ export const themes = {
passcodeDotFull: '#6C727A',
previewBackground: '#000000',
previewTintColor: '#ffffff',
backdropOpacity: 0.9,
...mentions
}
};
......@@ -101,6 +101,9 @@ export default {
Jitsi_Enabled_TokenAuth: {
type: 'valueAsBoolean'
},
Jitsi_URL_Room_Hash: {
type: 'valueAsBoolean'
},
Jitsi_URL_Room_Prefix: {
type: 'valueAsString'
},
......
......@@ -147,7 +147,7 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
const animatedPosition = React.useRef(new Value(0));
const opacity = interpolate(animatedPosition.current, {
inputRange: [0, 1],
outputRange: [0, 0.7],
outputRange: [0, themes[theme].backdropOpacity],
extrapolate: Extrapolate.CLAMP
});
......
......@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb';
import isEqual from 'react-fast-compare';
import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login';
......@@ -34,7 +33,8 @@ class AvatarContainer extends React.Component {
}
componentDidUpdate(prevProps) {
if (!isEqual(prevProps, this.props)) {
const { text, type } = this.props;
if (prevProps.text !== text || prevProps.type !== type) {
this.init();
}
}
......@@ -52,8 +52,8 @@ class AvatarContainer extends React.Component {
init = async() => {
const db = database.active;
const usersCollection = db.collections.get('users');
const subsCollection = db.collections.get('subscriptions');
const usersCollection = db.get('users');
const subsCollection = db.get('subscriptions');
let record;
try {
......
......@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import ScrollableTabView from 'react-native-scrollable-tab-view';
import equal from 'deep-equal';
import { dequal } from 'dequal';
import { connect } from 'react-redux';
import orderBy from 'lodash/orderBy';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
......@@ -67,7 +67,7 @@ class EmojiPicker extends Component {
if (nextState.width !== width) {
return true;
}
if (!equal(nextState.frequentlyUsed, frequentlyUsed)) {
if (!dequal(nextState.frequentlyUsed, frequentlyUsed)) {
return true;
}
return false;
......@@ -95,7 +95,7 @@ class EmojiPicker extends Component {
// eslint-disable-next-line react/sort-comp
_addFrequentlyUsed = protectedFunction(async(emoji) => {
const db = database.active;
const freqEmojiCollection = db.collections.get('frequently_used_emojis');
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojiRecord;
try {
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
......@@ -120,7 +120,7 @@ class EmojiPicker extends Component {
updateFrequentlyUsed = async() => {
const db = database.active;
const frequentlyUsedRecords = await db.collections.get('frequently_used_emojis').query().fetch();
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
let frequentlyUsed = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
frequentlyUsed = frequentlyUsed.map((item) => {
if (item.isCustom) {
......
import React, { memo, useEffect } from 'react';
import PropTypes from 'prop-types';
import { NotifierRoot, Notifier, Easing } from 'react-native-notifier';
import { connect } from 'react-redux';
import { dequal } from 'dequal';
import NotifierComponent from './NotifierComponent';
import EventEmitter from '../../utils/events';
......@@ -8,13 +11,13 @@ import { getActiveRoute } from '../../utils/navigation';
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
const InAppNotification = memo(() => {
const InAppNotification = memo(({ rooms }) => {
const show = (notification) => {
const { payload } = notification;
const state = Navigation.navigationRef.current?.getRootState();
const route = getActiveRoute(state);
if (payload.rid) {
if ((route?.name === 'RoomView' && route.params?.rid === payload.rid) || route?.name === 'JitsiMeetView') {
if (rooms.includes(payload.rid) || route?.name === 'JitsiMeetView') {
return;
}
Notifier.showNotification({
......@@ -28,13 +31,21 @@ const InAppNotification = memo(() => {
};
useEffect(() => {
EventEmitter.addEventListener(INAPP_NOTIFICATION_EMITTER, show);
const listener = EventEmitter.addEventListener(INAPP_NOTIFICATION_EMITTER, show);
return () => {
EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER);
EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener);
};
}, []);
}, [rooms]);
return <NotifierRoot />;
}, (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms));
const mapStateToProps = state => ({
rooms: state.room.rooms
});
export default InAppNotification;
InAppNotification.propTypes = {
rooms: PropTypes.array
};
export default connect(mapStateToProps)(InAppNotification);
......@@ -96,7 +96,7 @@ const Header = React.memo(({
const setEmojis = async() => {
try {
const db = database.active;
const freqEmojiCollection = db.collections.get('frequently_used_emojis');
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojis = await freqEmojiCollection.query().fetch();
const isLandscape = width > height;
......
......@@ -34,20 +34,24 @@ const MessageActions = React.memo(forwardRef(({
Message_AllowPinning,
Message_AllowStarring,
Message_Read_Receipt_Store_Users,
isMasterDetail
isMasterDetail,
editMessagePermission,
deleteMessagePermission,
forceDeleteMessagePermission,
pinMessagePermission
}, ref) => {
let permissions = {};
const { showActionSheet, hideActionSheet } = useActionSheet();
const getPermissions = async() => {
try {
const permission = ['edit-message', 'delete-message', 'force-delete-message', 'pin-message'];
const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission];
const result = await RocketChat.hasPermission(permission, room.rid);
permissions = {
hasEditPermission: result[permission[0]],
hasDeletePermission: result[permission[1]],
hasForceDeletePermission: result[permission[2]],
hasPinPermission: result[permission[3]]
hasEditPermission: result[0],
hasDeletePermission: result[1],
hasForceDeletePermission: result[2],
hasPinPermission: result[3]
};
} catch {
// Do nothing
......@@ -141,7 +145,7 @@ const MessageActions = React.memo(forwardRef(({
const db = database.active;
const result = await RocketChat.markAsUnread({ messageId });
if (result.success) {
const subCollection = db.collections.get('subscriptions');
const subCollection = db.get('subscriptions');
const subRecord = await subCollection.find(rid);
await db.action(async() => {
try {
......@@ -171,7 +175,7 @@ const MessageActions = React.memo(forwardRef(({
const handleCopy = async(message) => {
logEvent(events.ROOM_MSG_ACTION_COPY);
await Clipboard.setString(message.msg);
await Clipboard.setString(message?.attachments?.[0]?.description || message.msg);
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
};
......@@ -440,7 +444,11 @@ MessageActions.propTypes = {
Message_AllowPinning: PropTypes.bool,
Message_AllowStarring: PropTypes.bool,
Message_Read_Receipt_Store_Users: PropTypes.bool,
server: PropTypes.string
server: PropTypes.string,
editMessagePermission: PropTypes.array,
deleteMessagePermission: PropTypes.array,
forceDeleteMessagePermission: PropTypes.array,
pinMessagePermission: PropTypes.array
};
const mapStateToProps = state => ({
......@@ -452,7 +460,11 @@ const mapStateToProps = state => ({
Message_AllowPinning: state.settings.Message_AllowPinning,
Message_AllowStarring: state.settings.Message_AllowStarring,
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users,
isMasterDetail: state.app.isMasterDetail
isMasterDetail: state.app.isMasterDetail,
editMessagePermission: state.permissions['edit-message'],
deleteMessagePermission: state.permissions['delete-message'],
forceDeleteMessagePermission: state.permissions['force-delete-message'],
pinMessagePermission: state.permissions['pin-message']
});
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);
import React from 'react';
import { FlatList } from 'react-native';
import PropTypes from 'prop-types';
import equal from 'deep-equal';
import { dequal } from 'dequal';
import Item from './Item';
import styles from '../styles';
......@@ -31,7 +31,7 @@ const CommandsPreview = React.memo(({ theme, commandPreview, showCommandPreview
if (prevProps.showCommandPreview !== nextProps.showCommandPreview) {
return false;
}
if (!equal(prevProps.commandPreview, nextProps.commandPreview)) {
if (!dequal(prevProps.commandPreview, nextProps.commandPreview)) {
return false;
}
return true;
......
import React from 'react';
import { FlatList, View } from 'react-native';
import PropTypes from 'prop-types';
import equal from 'deep-equal';
import { dequal } from 'dequal';
import styles from '../styles';
import MentionItem from './MentionItem';
......@@ -30,7 +30,7 @@ const Mentions = React.memo(({ mentions, trackingType, theme }) => {
if (prevProps.trackingType !== nextProps.trackingType) {
return false;
}
if (!equal(prevProps.mentions, nextProps.mentions)) {
if (!dequal(prevProps.mentions, nextProps.mentions)) {
return false;
}
return true;
......
......@@ -3,7 +3,6 @@ import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import moment from 'moment';
import { connect } from 'react-redux';
import isEqual from 'lodash/isEqual';
import Markdown from '../markdown';
import { CustomIcon } from '../../lib/Icons';
......@@ -43,7 +42,7 @@ const styles = StyleSheet.create({
});
const ReplyPreview = React.memo(({
message, Message_TimeFormat, baseUrl, username, replying, getCustomEmoji, close, theme
message, Message_TimeFormat, baseUrl, username, replying, getCustomEmoji, close, theme, useRealName
}) => {
if (!replying) {
return null;
......@@ -59,7 +58,7 @@ const ReplyPreview = React.memo(({
>
<View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}>
<View style={styles.header}>
<Text style={[styles.username, { color: themes[theme].tintColor }]}>{message.u?.username}</Text>
<Text style={[styles.username, { color: themes[theme].tintColor }]}>{useRealName ? message.u?.name : message.u?.username}</Text>
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View>
<Markdown
......@@ -75,7 +74,7 @@ const ReplyPreview = React.memo(({
<CustomIcon name='close' color={themes[theme].auxiliaryText} size={20} style={styles.close} onPress={close} />
</View>
);
}, (prevProps, nextProps) => prevProps.replying === nextProps.replying && prevProps.theme === nextProps.theme && isEqual(prevProps.message, nextProps.message));
}, (prevProps, nextProps) => prevProps.replying === nextProps.replying && prevProps.theme === nextProps.theme && prevProps.message.id === nextProps.message.id);
ReplyPreview.propTypes = {
replying: PropTypes.bool,
......@@ -85,12 +84,14 @@ ReplyPreview.propTypes = {
baseUrl: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
getCustomEmoji: PropTypes.func,
theme: PropTypes.string
theme: PropTypes.string,
useRealName: PropTypes.bool
};
const mapStateToProps = state => ({
Message_TimeFormat: state.settings.Message_TimeFormat,
baseUrl: state.server.server
baseUrl: state.server.server,
useRealName: state.settings.UI_Use_Real_Name
});
export default connect(mapStateToProps)(ReplyPreview);
......@@ -6,7 +6,7 @@ import {
import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker from 'react-native-image-crop-picker';
import equal from 'deep-equal';
import { dequal } from 'dequal';
import DocumentPicker from 'react-native-document-picker';
import { Q } from '@nozbe/watermelondb';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
......@@ -63,6 +63,7 @@ const imagePickerConfig = {
const libraryPickerConfig = {
multiple: true,
compressVideoPreset: 'Passthrough',
mediaType: 'any'
};
......@@ -189,8 +190,8 @@ class MessageBox extends Component {
} = this.props;
let msg;
try {
const threadsCollection = db.collections.get('threads');
const subsCollection = db.collections.get('subscriptions');
const threadsCollection = db.get('threads');
const subsCollection = db.get('subscriptions');
try {
this.room = await subsCollection.find(rid);
} catch (error) {
......@@ -270,7 +271,7 @@ class MessageBox extends Component {
} = this.state;
const {
roomType, replying, editing, isFocused, message, theme, children
roomType, replying, editing, isFocused, message, theme
} = this.props;
if (nextProps.theme !== theme) {
return true;
......@@ -299,16 +300,13 @@ class MessageBox extends Component {
if (nextState.tshow !== tshow) {
return true;
}
if (!equal(nextState.mentions, mentions)) {
if (!dequal(nextState.mentions, mentions)) {
return true;
}
if (!equal(nextState.commandPreview, commandPreview)) {
if (!dequal(nextState.commandPreview, commandPreview)) {
return true;
}
if (!equal(nextProps.message, message)) {
return true;
}
if (!equal(nextProps.children, children)) {
if (!dequal(nextProps.message?.id, message?.id)) {
return true;
}
return false;
......@@ -366,7 +364,7 @@ class MessageBox extends Component {
const slashCommand = text.match(/^\/([a-z0-9._-]+) (.+)/im);
if (slashCommand) {
const [, name, params] = slashCommand;
const commandsCollection = db.collections.get('slash_commands');
const commandsCollection = db.get('slash_commands');
try {
const command = await commandsCollection.find(name);
if (command.providesPreview) {
......@@ -507,7 +505,7 @@ class MessageBox extends Component {
getEmojis = debounce(async(keyword) => {
const db = database.active;
if (keyword) {
const customEmojisCollection = db.collections.get('custom_emojis');
const customEmojisCollection = db.get('custom_emojis');
const likeString = sanitizeLikeString(keyword);
let customEmojis = await customEmojisCollection.query(
Q.where('name', Q.like(`${ likeString }%`))
......@@ -521,7 +519,7 @@ class MessageBox extends Component {
getSlashCommands = debounce(async(keyword) => {
const db = database.active;
const commandsCollection = db.collections.get('slash_commands');
const commandsCollection = db.get('slash_commands');
const likeString = sanitizeLikeString(keyword);
const commands = await commandsCollection.query(
Q.where('id', Q.like(`${ likeString }%`))
......@@ -753,7 +751,7 @@ class MessageBox extends Component {
// Slash command
if (message[0] === MENTIONS_TRACKING_TYPE_COMMANDS) {
const db = database.active;
const commandsCollection = db.collections.get('slash_commands');
const commandsCollection = db.get('slash_commands');
const command = message.replace(/ .*/, '').slice(1);
const likeString = sanitizeLikeString(command);
const slashCommand = await commandsCollection.query(
......
......@@ -19,8 +19,8 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => {
try {
const db = database.active;
const deleteBatch = [];
const msgCollection = db.collections.get('messages');
const threadCollection = db.collections.get('threads');
const msgCollection = db.get('messages');
const threadCollection = db.get('threads');
// Delete the object (it can be Message or ThreadMessage instance)
deleteBatch.push(message.prepareDestroyPermanently());
......
import React from 'react';
import { View } from 'react-native';
import _ from 'lodash';
import range from 'lodash/range';
import PropTypes from 'prop-types';
import styles from './styles';
......@@ -11,7 +11,7 @@ const SIZE_FULL = 16;
const Dots = React.memo(({ passcode, theme, length }) => (
<View style={styles.dotsContainer}>
{_.range(length).map((val) => {
{range(length).map((val) => {
const lengthSup = (passcode.length >= val + 1);
const height = lengthSup ? SIZE_FULL : SIZE_EMPTY;
const width = lengthSup ? SIZE_FULL : SIZE_EMPTY;
......
......@@ -2,7 +2,7 @@ import React, {
useState, forwardRef, useImperativeHandle, useRef
} from 'react';
import { Col, Row, Grid } from 'react-native-easy-grid';
import _ from 'lodash';
import range from 'lodash/range';
import PropTypes from 'prop-types';
import * as Animatable from 'react-native-animatable';
import * as Haptics from 'expo-haptics';
......@@ -84,21 +84,21 @@ const Base = forwardRef(({
</Animatable.View>
</Row>
<Row style={[styles.row, styles.buttonRow]}>
{_.range(1, 4).map(i => (
{range(1, 4).map(i => (
<Col key={i} style={styles.colButton}>
<Button text={i} theme={theme} onPress={onPressNumber} />
</Col>
))}
</Row>
<Row style={[styles.row, styles.buttonRow]}>
{_.range(4, 7).map(i => (
{range(4, 7).map(i => (
<Col key={i} style={styles.colButton}>
<Button text={i} theme={theme} onPress={onPressNumber} />
</Col>
))}
</Row>
<Row style={[styles.row, styles.buttonRow]}>
{_.range(7, 10).map(i => (
{range(7, 10).map(i => (
<Col key={i} style={styles.colButton}>
<Button text={i} theme={theme} onPress={onPressNumber} />
</Col>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment