Commit b4100bc6 authored by Diego Mello's avatar Diego Mello Committed by GitHub

[RELEASE] Merge beta into master (#986)

parent 1610ba57
......@@ -253,9 +253,14 @@ workflows:
build-and-test:
jobs:
- lint-testunit
# - e2e-test:
# requires:
# - lint-testunit
- e2e-hold:
type: approval
requires:
- lint-testunit
- e2e-test:
requires:
- e2e-hold
- ios-build:
requires:
......
export default {
createSagaMonitor: () => {}
};
......@@ -109,10 +109,15 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "1.14.0"
versionName "1.15.0"
vectorDrawables.useSupportLibrary = true
}
packagingOptions {
pickFirst '**/libjsc.so'
pickFirst '**/libc++_shared.so'
}
signingConfigs {
release {
if (project.hasProperty('KEYSTORE')) {
......@@ -166,6 +171,7 @@ android {
}
dependencies {
implementation "org.webkit:android-jsc:r241213"
implementation project(':react-native-firebase')
implementation project(':react-native-webview')
implementation project(':react-native-orientation-locker')
......
......@@ -36,6 +36,10 @@ allprojects {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
maven {
// Local Maven repo containing AARs with JSC library built for Android
url "$rootDir/../node_modules/jsc-android/dist"
}
}
}
......
......@@ -58,6 +58,7 @@ export const SERVER = createRequestTypes('SERVER', [
...defaultTypes,
'SELECT_SUCCESS',
'SELECT_REQUEST',
'SELECT_FAILURE',
'INIT_ADD',
'FINISH_ADD'
]);
......@@ -66,4 +67,5 @@ export const LOGOUT = 'LOGOUT'; // logout is always success
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
export const NOTIFICATION = createRequestTypes('NOTIFICATION', ['RECEIVED', 'REMOVE']);
export const TOGGLE_MARKDOWN = 'TOGGLE_MARKDOWN';
import { NOTIFICATION } from './actionsTypes';
export function notificationReceived(params) {
return {
type: NOTIFICATION.RECEIVED,
payload: {
message: params.text,
payload: params.payload
}
};
}
export function removeNotification() {
return {
type: NOTIFICATION.REMOVE
};
}
......@@ -17,6 +17,12 @@ export function selectServerSuccess(server, version) {
};
}
export function selectServerFailure() {
return {
type: SERVER.SELECT_FAILURE
};
}
export function serverRequest(server) {
return {
type: SERVER.REQUEST,
......
......@@ -10,6 +10,7 @@ export const COLOR_TEXT = '#2F343D';
export const COLOR_TEXT_DESCRIPTION = '#9ca2a8';
export const COLOR_SEPARATOR = '#A7A7AA';
export const COLOR_BACKGROUND_CONTAINER = '#f3f4f5';
export const COLOR_BACKGROUND_NOTIFICATION = '#f8f8f8';
export const COLOR_BORDER = '#e1e5e8';
export const COLOR_UNREAD = '#e1e5e8';
export const COLOR_TOAST = '#0C0D0F';
......
......@@ -14,6 +14,9 @@ export default {
CROWD_Enable: {
type: 'valueAsBoolean'
},
FEDERATION_Enabled: {
type: 'valueAsBoolean'
},
LDAP_Enable: {
type: 'valueAsBoolean'
},
......@@ -56,6 +59,12 @@ export default {
Assets_favicon_512: {
type: null
},
Message_Read_Receipt_Enabled: {
type: 'valueAsBoolean'
},
Message_Read_Receipt_Store_Users: {
type: 'valueAsBoolean'
},
Threads_enabled: {
type: null
},
......
import React from 'react';
import { StyleSheet } from 'react-native';
import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles';
const styles = StyleSheet.create({
icon: {
width: 22,
height: 22,
marginHorizontal: 15,
...sharedStyles.textColorDescription
}
});
const Check = React.memo(() => <CustomIcon style={styles.icon} size={22} name='check' />);
export default Check;
......@@ -14,9 +14,12 @@ const styles = StyleSheet.create({
}
});
export const DisclosureImage = React.memo(() => <Image source={{ uri: 'disclosure_indicator' }} style={styles.disclosureIndicator} />);
const DisclosureIndicator = React.memo(() => (
<View style={styles.disclosureContainer}>
<Image source={{ uri: 'disclosure_indicator' }} style={styles.disclosureIndicator} />
<DisclosureImage />
</View>
));
export default DisclosureIndicator;
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { RectButton } from 'react-native-gesture-handler';
import { COLOR_TEXT } from '../constants/colors';
import sharedStyles from '../views/Styles';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
height: 56,
paddingHorizontal: 15
},
disabled: {
opacity: 0.3
},
textContainer: {
flex: 1,
justifyContent: 'center'
},
title: {
fontSize: 16,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular
},
subtitle: {
fontSize: 14,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular
}
});
const Content = React.memo(({
title, subtitle, disabled, testID, right
}) => (
<View style={[styles.container, disabled && styles.disabled]} testID={testID}>
<View style={styles.textContainer}>
<Text style={styles.title}>{title}</Text>
{subtitle
? <Text style={styles.subtitle}>{subtitle}</Text>
: null
}
</View>
{right ? right() : null}
</View>
));
const Button = React.memo(({
onPress, ...props
}) => (
<RectButton
onPress={onPress}
activeOpacity={0.1}
underlayColor={COLOR_TEXT}
enabled={!props.disabled}
>
<Content {...props} />
</RectButton>
));
const Item = React.memo(({ ...props }) => {
if (props.onPress) {
return <Button {...props} />;
}
return <Content {...props} />;
});
Item.propTypes = {
onPress: PropTypes.func
};
Content.propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string,
right: PropTypes.func,
disabled: PropTypes.bool,
testID: PropTypes.string
};
Button.propTypes = {
onPress: PropTypes.func,
disabled: PropTypes.bool
};
Button.defaultProps = {
disabled: false
};
export default Item;
......@@ -17,6 +17,7 @@ import { vibrate } from '../utils/vibration';
import RocketChat from '../lib/rocketchat';
import I18n from '../i18n';
import log from '../utils/log';
import Navigation from '../lib/Navigation';
@connect(
state => ({
......@@ -26,7 +27,8 @@ import log from '../utils/log';
Message_AllowEditing: state.settings.Message_AllowEditing,
Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes,
Message_AllowPinning: state.settings.Message_AllowPinning,
Message_AllowStarring: state.settings.Message_AllowStarring
Message_AllowStarring: state.settings.Message_AllowStarring,
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users
}),
dispatch => ({
actionsHide: () => dispatch(actionsHideAction()),
......@@ -56,7 +58,8 @@ export default class MessageActions extends React.Component {
Message_AllowEditing: PropTypes.bool,
Message_AllowEditing_BlockEditInMinutes: PropTypes.number,
Message_AllowPinning: PropTypes.bool,
Message_AllowStarring: PropTypes.bool
Message_AllowStarring: PropTypes.bool,
Message_Read_Receipt_Store_Users: PropTypes.bool
};
constructor(props) {
......@@ -64,7 +67,7 @@ export default class MessageActions extends React.Component {
this.handleActionPress = this.handleActionPress.bind(this);
this.setPermissions();
const { Message_AllowStarring, Message_AllowPinning } = this.props;
const { Message_AllowStarring, Message_AllowPinning, Message_Read_Receipt_Store_Users } = this.props;
// Cancel
this.options = [I18n.t('Cancel')];
......@@ -118,6 +121,12 @@ export default class MessageActions extends React.Component {
this.REACTION_INDEX = this.options.length - 1;
}
// Read Receipts
if (Message_Read_Receipt_Store_Users) {
this.options.push(I18n.t('Read_Receipt'));
this.READ_RECEIPT_INDEX = this.options.length - 1;
}
// Report
this.options.push(I18n.t('Report'));
this.REPORT_INDEX = this.options.length - 1;
......@@ -302,6 +311,11 @@ export default class MessageActions extends React.Component {
toggleReactionPicker(actionMessage);
}
handleReadReceipt = () => {
const { actionMessage } = this.props;
Navigation.navigate('ReadReceiptsView', { messageId: actionMessage._id });
}
handleReport = async() => {
const { actionMessage } = this.props;
try {
......@@ -348,6 +362,9 @@ export default class MessageActions extends React.Component {
case this.DELETE_INDEX:
this.handleDelete();
break;
case this.READ_RECEIPT_INDEX:
this.handleReadReceipt();
break;
default:
break;
}
......
import React from 'react';
import PropTypes from 'prop-types';
import { TouchableOpacity, ActivityIndicator } from 'react-native';
import FastImage from 'react-native-fast-image';
import styles from './styles';
import { CustomIcon } from '../../lib/Icons';
import { COLOR_PRIMARY } from '../../constants/colors';
export default class CommandPreview extends React.PureComponent {
static propTypes = {
onPress: PropTypes.func,
item: PropTypes.object
};
constructor(props) {
super(props);
this.state = { loading: true };
}
render() {
const { onPress, item } = this.props;
const { loading } = this.state;
return (
<TouchableOpacity
style={styles.commandPreview}
onPress={() => onPress(item)}
testID={`command-preview-item${ item.id }`}
>
{item.type === 'image'
? (
<FastImage
style={styles.commandPreviewImage}
source={{ uri: item.value }}
resizeMode={FastImage.resizeMode.cover}
onLoadStart={() => this.setState({ loading: true })}
onLoad={() => this.setState({ loading: false })}
>
{ loading ? <ActivityIndicator /> : null }
</FastImage>
)
: <CustomIcon name='file-generic' size={36} color={COLOR_PRIMARY} />
}
</TouchableOpacity>
);
}
}
This diff is collapsed.
......@@ -3,10 +3,11 @@ import { StyleSheet } from 'react-native';
import { isIOS } from '../../utils/deviceInfo';
import sharedStyles from '../../views/Styles';
import {
COLOR_BORDER, COLOR_SEPARATOR, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE
COLOR_BORDER, COLOR_SEPARATOR, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_PRIMARY
} from '../../constants/colors';
const MENTION_HEIGHT = 50;
const SCROLLVIEW_MENTION_HEIGHT = 4 * MENTION_HEIGHT;
export default StyleSheet.create({
textBox: {
......@@ -100,5 +101,35 @@ export default StyleSheet.create({
bottom: 0,
left: 0,
right: 0
},
slash: {
color: COLOR_PRIMARY,
backgroundColor: COLOR_BORDER,
height: 30,
width: 30,
padding: 5,
paddingHorizontal: 12,
marginHorizontal: 10,
borderRadius: 2
},
commandPreviewImage: {
justifyContent: 'center',
margin: 3,
width: 120,
height: 80,
borderRadius: 4
},
commandPreview: {
backgroundColor: COLOR_BACKGROUND_CONTAINER,
height: 100,
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
avatar: {
margin: 8
},
scrollViewMention: {
maxHeight: SCROLLVIEW_MENTION_HEIGHT
}
});
......@@ -34,7 +34,7 @@ const styles = StyleSheet.create({
}
});
const SearchBox = ({ onChangeText, testID }) => (
const SearchBox = ({ onChangeText, onSubmitEditing, testID }) => (
<View style={styles.container}>
<View style={styles.searchBox}>
<CustomIcon name='magnifier' size={14} color='#8E8E93' />
......@@ -49,6 +49,7 @@ const SearchBox = ({ onChangeText, testID }) => (
testID={testID}
underlineColorAndroid='transparent'
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
/>
</View>
</View>
......@@ -56,6 +57,7 @@ const SearchBox = ({ onChangeText, testID }) => (
SearchBox.propTypes = {
onChangeText: PropTypes.func.isRequired,
onSubmitEditing: PropTypes.func,
testID: PropTypes.string
};
......
import React from 'react';
import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { COLOR_SEPARATOR } from '../constants/colors';
const styles = StyleSheet.create({
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: COLOR_SEPARATOR
}
});
const Separator = React.memo(({ style }) => <View style={[styles.separator, style]} />);
Separator.propTypes = {
style: PropTypes.object
};
export default Separator;
......@@ -16,6 +16,7 @@ import Reactions from './Reactions';
import Broadcast from './Broadcast';
import Discussion from './Discussion';
import Content from './Content';
import ReadReceipt from './ReadReceipt';
const MessageInner = React.memo((props) => {
if (props.type === 'discussion-created') {
......@@ -72,6 +73,10 @@ const Message = React.memo((props) => {
>
<MessageInner {...props} />
</View>
<ReadReceipt
isReadReceiptEnabled={props.isReadReceiptEnabled}
unread={props.unread}
/>
</View>
</View>
);
......@@ -119,7 +124,9 @@ Message.propTypes = {
hasError: PropTypes.bool,
style: PropTypes.any,
onLongPress: PropTypes.func,
onPress: PropTypes.func
onPress: PropTypes.func,
isReadReceiptEnabled: PropTypes.bool,
unread: PropTypes.bool
};
MessageInner.propTypes = {
......
import React from 'react';
import PropTypes from 'prop-types';
import { COLOR_PRIMARY } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons';
import styles from './styles';
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }) => {
if (isReadReceiptEnabled && !unread && unread !== null) {
return <CustomIcon name='check' color={COLOR_PRIMARY} size={15} style={styles.readReceipt} />;
}
return null;
});
ReadReceipt.displayName = 'MessageReadReceipt';
ReadReceipt.propTypes = {
isReadReceiptEnabled: PropTypes.bool,
unread: PropTypes.bool
};
export default ReadReceipt;
......@@ -24,6 +24,7 @@ export default class MessageContainer extends React.Component {
_updatedAt: PropTypes.instanceOf(Date),
baseUrl: PropTypes.string,
Message_GroupingPeriod: PropTypes.number,
isReadReceiptEnabled: PropTypes.bool,
useRealName: PropTypes.bool,
useMarkdown: PropTypes.bool,
status: PropTypes.number,
......@@ -57,6 +58,9 @@ export default class MessageContainer extends React.Component {
if (item.tmsg !== nextProps.item.tmsg) {
return true;
}
if (item.unread !== nextProps.item.unread) {
return true;
}
return _updatedAt.toISOString() !== nextProps._updatedAt.toISOString();
}
......@@ -187,10 +191,10 @@ export default class MessageContainer extends React.Component {
render() {
const {
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled
} = this.props;
const {
_id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels
_id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread
} = item;
return (
......@@ -213,6 +217,8 @@ export default class MessageContainer extends React.Component {
broadcast={broadcast}
baseUrl={baseUrl}
useRealName={useRealName}
isReadReceiptEnabled={isReadReceiptEnabled}
unread={unread}
role={role}
drid={drid}
dcount={dcount}
......
......@@ -234,5 +234,8 @@ export default StyleSheet.create({
flex: 1,
color: COLOR_PRIMARY,
...sharedStyles.textRegular
},
readReceipt: {
lineHeight: 20
}
});
......@@ -81,6 +81,7 @@ export default {
Add_Reaction: 'Reaktion hinzufügen',
Add_Server: 'Server hinzufügen',
Add_user: 'Nutzer hinzufügen',
Admin_Panel: 'Admin Panel',
Alert: 'Warnen',
alert: 'warnen',
alerts: 'Warnungen',
......@@ -124,7 +125,9 @@ export default {
Connect: 'Verbinden',
Connect_to_a_server: 'Verbinden Sie sich mit einem Server',
Connected: 'Verbunden',
connecting_server: 'verbinde zum Server',
Connecting: 'Verbinden ...',
Contact_us: 'Kontaktiere uns',
Continue_with: 'Weitermachen mit',
Copied_to_clipboard: 'In die Zwischenablage kopiert!',
Copy: 'Kopieren',
......@@ -140,24 +143,29 @@ export default {
DELETE: 'LÖSCHEN',
description: 'Beschreibung',
Description: 'Beschreibung',
Disable_notifications: 'Benachrichtigungen ausschalten',
Directory: 'Verzeichnis',
Direct_Messages: 'Direkte Nachrichten',
Disable_notifications: 'Benachrichtigungen deaktiveren',
Discussions: 'Diskussionen',
Dont_Have_An_Account: 'Sie haben noch kein Konto?',
Do_you_really_want_to_key_this_room_question_mark: 'Möchten Sie diesen Raum wirklich {{key}}?',
edit: 'bearbeiten',
erasing_room: 'Raum löschen',
edited: 'bearbeitet',
Edit: 'Bearbeiten',
Email_or_password_field_is_empty: 'Das E-Mail- oder Passwortfeld ist leer',
Email: 'Email',
email: 'Email',
Enable_markdown: 'Markdown aktivieren',
Enable_notifications: 'Benachrichtigungen aktivieren',
Everyone_can_access_this_channel: 'Jeder kann auf diesen Kanal zugreifen',
erasing_room: 'lösche Raum',
Error_uploading: 'Fehler beim Hochladen',
Favorites: 'Favoriten',
Files: 'Dateien',
File_description: 'Dateibeschreibung',
File_name: 'Dateiname',
Finish_recording: 'Beenden Sie die Aufnahme',
Following_thread: 'Thread folgen',
For_your_security_you_must_enter_your_current_password_to_continue: 'Zu Ihrer Sicherheit müssen Sie Ihr aktuelles Passwort eingeben, um fortzufahren',
Forgot_my_password: 'Ich habe mein Passwort vergessen',
Forgot_password_If_this_email_is_registered: 'Wenn diese E-Mail registriert ist, senden wir Anweisungen zum Zurücksetzen Ihres Passworts. Wenn Sie in Kürze keine E-Mail erhalten, kommen Sie bitte zurück und versuchen Sie es erneut.',
......@@ -166,6 +174,7 @@ export default {
Group_by_favorites: 'Nach Favoriten gruppieren',
Group_by_type: 'Gruppieren nach Typ',
Has_joined_the_channel: 'Ist dem Kanal beigetreten',
Has_joined_the_conversation: 'Hat sich dem Gespräch angeschlossen',
Has_left_the_channel: 'Hat den Kanal verlassen',
Invisible: 'Unsichtbar',
Invite: 'Einladen',
......@@ -182,6 +191,7 @@ export default {
leaving_room: 'Raum verlassen',
leave: 'verlassen',
Legal: 'Rechtliches',
License: 'Lizenz',
Livechat: 'Live-Chat',
Login: 'Anmeldung',
Login_error: 'Ihre Referenzen wurden abgelehnt! Bitte versuche es erneut.',
......@@ -196,7 +206,10 @@ export default {
Message_actions: 'Nachrichtenaktionen',
Message_pinned: 'Eine Nachricht wurde angeheftet',
Message_removed: 'Nachricht entfernt',
message: 'Nachricht',
messages: 'Nachrichten',
Messages: 'Mitteilungen',
Message_Reported: 'Nachricht gemeldet',
Microphone_Permission_Message: 'Rocket Chat benötigt Zugriff auf Ihr Mikrofon, damit Sie eine Audionachricht senden können.',
Microphone_Permission: 'Mikrofonberechtigung',
Mute: 'Stumm',
......@@ -215,11 +228,14 @@ export default {
No_pinned_messages: 'Keine angehefteten Nachrichten',
No_results_found: 'keine Ergebnisse gefunden',
No_starred_messages: 'Keine markierten Nachrichten',
No_thread_messages: 'Keine Threadnachrichten',
No_announcement_provided: 'Keine Ankündigung erfolgt.',
No_description_provided: 'Keine Beschreibung angegeben.',
No_topic_provided: 'Kein Thema bereitgestellt',
No_Message: 'Keine Nachricht',
No_messages_yet: 'Noch keine Nachrichten',
No_Reactions: 'Keine Reaktionen',
No_Read_Receipts: 'Keine Lesebestätigungen',
Not_logged: 'Nicht protokolliert',
Nothing_to_save: 'Nichts zu speichern!',
Notify_active_in_this_room: 'Aktive Benutzer in diesem Raum benachrichtigen',
......@@ -252,9 +268,14 @@ export default {
Reactions: 'Reaktionen',
Read_Only_Channel: 'Nur-Lese-Kanal',
Read_Only: 'Schreibgeschützt',
Read_Receipt: 'Lesebestätigung',
Register: 'Registrieren',
Repeat_Password: 'Wiederhole das Passwort',
Replied_on: 'Antwortete am:',
replies: 'Antworten',
reply: 'Antworten',
Reply: 'Antworten',
Report: 'Bericht',
Resend: 'Erneut senden',
Reset_password: 'Passwort zurücksetzen',
resetting_password: 'Passwort zurücksetzen',
......@@ -278,35 +299,47 @@ export default {
saving_settings: 'Einstellungen speichern',
Search_Messages: 'Nachrichten suchen',
Search: 'Suche',
Search_by: 'Suche nach',
Search_global_users: 'Suche nach globalen Benutzern',
Search_global_users_description: 'Beim Einschalten können Sie nach Benutzern von anderen Unternehmen oder Servern suchen.',
Select_Avatar: 'Wählen Sie einen Avatar aus',
Select_Users: 'Wählen Sie einen Benutzer aus',