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

Merge 4.23.0 into master (#3574)

parent c8d50d8f
......@@ -1419,6 +1419,7 @@ Array [
</Text>
</View>
<View
accessibilityLabel="Use"
accessible={true}
focusable={true}
onClick={[Function]}
......@@ -1581,6 +1582,7 @@ Array [
</Text>
</View>
<View
accessibilityLabel="Use"
accessible={true}
focusable={true}
onClick={[Function]}
......@@ -41244,6 +41246,7 @@ exports[`Storyshots Message Show a button as attachment 1`] = `
Test Button
</Text>
<View
accessibilityLabel="Text button"
accessible={true}
focusable={true}
onClick={[Function]}
......@@ -144,13 +144,15 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.22.0"
versionName "4.23.0"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60" // See note below!
}
resValue "string", "rn_config_reader_custom_package", "chat.rocket.reactnative"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
signingConfigs {
......@@ -203,6 +205,10 @@ android {
dimension = "app"
buildConfigField "boolean", "IS_OFFICIAL", "false"
}
e2e {
dimension = "app"
buildConfigField "boolean", "IS_OFFICIAL", "false"
}
foss {
dimension = "type"
buildConfigField "boolean", "FDROID_BUILD", "true"
......@@ -230,6 +236,16 @@ android {
java.srcDirs = ['src/main/java', 'src/play/java']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
e2ePlayDebug {
java.srcDirs = ['src/main/java', 'src/play/java']
res.srcDirs = ['src/experimental/res']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
e2ePlayRelease {
java.srcDirs = ['src/main/java', 'src/play/java']
res.srcDirs = ['src/experimental/res']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
}
applicationVariants.all { variant ->
......@@ -294,6 +310,8 @@ dependencies {
implementation "com.tencent:mmkv-static:1.2.1"
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.9.0"
androidTestImplementation('com.wix:detox:+') { transitive = true }
androidTestImplementation 'junit:junit:4.12'
}
// Run this once to be able to run the application with BUCK
......
// Replace "com.example" here and below with your app's package name from the top of MainActivity.java
package chat.rocket.reactnative;
import com.wix.detox.Detox;
import com.wix.detox.config.DetoxConfig;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class DetoxTest {
@Rule
// Replace 'MainActivity' with the value of android:name entry in
// <activity> in AndroidManifest.xml
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
@Test
public void runDetoxTests() {
DetoxConfig detoxConfig = new DetoxConfig();
detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60;
detoxConfig.rnContextLoadTimeoutSec = (chat.rocket.reactnative.BuildConfig.DEBUG ? 180 : 60);
Detox.runTests(mActivityRule, detoxConfig);
}
}
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user"
tools:ignore="AcceptsUserCertificates" />
</trust-anchors>
</base-config>
</network-security-config>
\ No newline at end of file
......@@ -53,6 +53,10 @@ allprojects {
url("$rootDir/../node_modules/jsc-android/dist")
}
maven {
url "$rootDir/../node_modules/detox/Detox-android"
}
maven {
url jitsi_url
}
......
......@@ -3,6 +3,7 @@ import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { connect } from 'react-redux';
import { SetUsernameStackParamList, StackParamList } from './navigationTypes';
import Navigation from './lib/Navigation';
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app';
......@@ -17,7 +18,7 @@ import { ThemeContext } from './theme';
import { setCurrentScreen } from './utils/log';
// SetUsernameStack
const SetUsername = createStackNavigator();
const SetUsername = createStackNavigator<SetUsernameStackParamList>();
const SetUsernameStack = () => (
<SetUsername.Navigator screenOptions={defaultHeader}>
<SetUsername.Screen name='SetUsernameView' component={SetUsernameView} />
......@@ -25,7 +26,7 @@ const SetUsernameStack = () => (
);
// App
const Stack = createStackNavigator();
const Stack = createStackNavigator<StackParamList>();
const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => {
if (!root) {
return null;
......
......@@ -124,7 +124,11 @@ const ActionSheet = React.memo(
const renderFooter = () =>
data?.hasCancel ? (
<Button onPress={hide} style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]} theme={theme}>
<Button
onPress={hide}
style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]}
theme={theme}
accessibilityLabel={I18n.t('Cancel')}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{I18n.t('Cancel')}</Text>
</Button>
) : null;
......
......@@ -17,7 +17,7 @@ export const useActionSheet = () => useContext(context);
const { Provider, Consumer } = context;
export const withActionSheet = <P extends object>(Component: React.ComponentType<P>) =>
export const withActionSheet = (Component: any): any =>
forwardRef((props: any, ref: ForwardedRef<any>) => (
<Consumer>{(contexts: any) => <Component {...props} {...contexts} ref={ref} />}</Consumer>
));
......
......@@ -70,6 +70,7 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
disabled && styles.disabled,
style
]}
accessibilityLabel={title}
{...otherProps}>
{loading ? (
<ActivityIndicator color={textColor} />
......
......@@ -31,7 +31,7 @@ interface IEmojiPickerProps {
customEmojis?: any;
style: object;
theme?: string;
onEmojiSelected?: Function;
onEmojiSelected?: ((emoji: any) => void) | ((keyboardId: any, params?: any) => void);
tabEmojiStyle?: object;
}
......@@ -201,4 +201,5 @@ const mapStateToProps = (state: any) => ({
customEmojis: state.customEmojis
});
export default connect(mapStateToProps)(withTheme(EmojiPicker));
// TODO - remove this as any, at the new PR to fix the HOC erros
export default connect(mapStateToProps)(withTheme(EmojiPicker)) as any;
......@@ -2,7 +2,7 @@ import React from 'react';
import { StyleSheet, View } from 'react-native';
interface IHeaderButtonContainer {
children: JSX.Element;
children: React.ReactNode;
left?: boolean;
}
......
......@@ -423,4 +423,4 @@ const mapStateToProps = (state: any) => ({
services: state.login.services
});
export default connect(mapStateToProps)(withTheme(LoginServices));
export default connect(mapStateToProps)(withTheme(LoginServices)) as any;
......@@ -13,7 +13,7 @@ interface IMessageBoxEmojiKeyboard {
}
export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiKeyboard, any> {
private readonly baseUrl: any;
private readonly baseUrl: string;
constructor(props: IMessageBoxEmojiKeyboard) {
super(props);
......
......@@ -13,6 +13,7 @@ import { events, logEvent } from '../../utils/log';
interface IMessageBoxRecordAudioProps {
theme: string;
permissionToUpload: boolean;
recordingCallback: Function;
onFinish: Function;
}
......@@ -192,9 +193,11 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
};
render() {
const { theme } = this.props;
const { theme, permissionToUpload } = this.props;
const { isRecording, isRecorderActive } = this.state;
if (!permissionToUpload) {
return null;
}
if (!isRecording && !isRecorderActive) {
return (
<BorderlessButton
......
......@@ -109,6 +109,8 @@ interface IMessageBoxProps {
sharing: boolean;
isActionsEnabled: boolean;
usedCannedResponse: string;
uploadFilePermission: string[];
serverVersion: string;
}
interface IMessageBoxState {
......@@ -124,6 +126,7 @@ interface IMessageBoxState {
};
tshow: boolean;
mentionLoading: boolean;
permissionToUpload: boolean;
}
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
......@@ -179,41 +182,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
showCommandPreview: false,
command: {},
tshow: false,
mentionLoading: false
mentionLoading: false,
permissionToUpload: true
};
this.text = '';
this.selection = { start: 0, end: 0 };
this.focused = false;
// MessageBox Actions
this.options = [
{
title: I18n.t('Take_a_photo'),
icon: 'camera-photo',
onPress: this.takePhoto
},
{
title: I18n.t('Take_a_video'),
icon: 'camera',
onPress: this.takeVideo
},
{
title: I18n.t('Choose_from_library'),
icon: 'image',
onPress: this.chooseFromLibrary
},
{
title: I18n.t('Choose_file'),
icon: 'attach',
onPress: this.chooseFile
},
{
title: I18n.t('Create_Discussion'),
icon: 'discussions',
onPress: this.createDiscussion
}
];
const libPickerLabels = {
cropperChooseText: I18n.t('Choose'),
cropperCancelText: I18n.t('Cancel'),
......@@ -277,6 +252,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.onChangeText(usedCannedResponse);
}
this.setOptions();
this.unsubscribeFocus = navigation.addListener('focus', () => {
// didFocus
// We should wait pushed views be dismissed
......@@ -321,10 +298,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}
}
shouldComponentUpdate(nextProps: any, nextState: any) {
const { showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow, mentionLoading, trackingType } = this.state;
const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse } = this.props;
shouldComponentUpdate(nextProps: IMessageBoxProps, nextState: IMessageBoxState) {
const {
showEmojiKeyboard,
showSend,
recording,
mentions,
commandPreview,
tshow,
mentionLoading,
trackingType,
permissionToUpload
} = this.state;
const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse, uploadFilePermission } = this.props;
if (nextProps.theme !== theme) {
return true;
}
......@@ -358,6 +345,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (nextState.tshow !== tshow) {
return true;
}
if (nextState.permissionToUpload !== permissionToUpload) {
return true;
}
if (!dequal(nextState.mentions, mentions)) {
return true;
}
......@@ -367,12 +357,22 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (!dequal(nextProps.message?.id, message?.id)) {
return true;
}
if (!dequal(nextProps.uploadFilePermission, uploadFilePermission)) {
return true;
}
if (nextProps.usedCannedResponse !== usedCannedResponse) {
return true;
}
return false;
}
componentDidUpdate(prevProps: IMessageBoxProps) {
const { uploadFilePermission } = this.props;
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission)) {
this.setOptions();
}
}
componentWillUnmount() {
console.countReset(`${this.constructor.name}.render calls`);
if (this.onChangeText && this.onChangeText.stop) {
......@@ -404,6 +404,19 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}
}
setOptions = async () => {
const { uploadFilePermission, rid } = this.props;
// Servers older than 4.2
if (!uploadFilePermission) {
this.setState({ permissionToUpload: true });
return;
}
const permissionToUpload = await RocketChat.hasPermission([uploadFilePermission], rid);
this.setState({ permissionToUpload: permissionToUpload[0] });
};
onChangeText: any = (text: string): void => {
const isTextEmpty = text.length === 0;
this.setShowSend(!isTextEmpty);
......@@ -666,8 +679,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
};
canUploadFile = (file: any) => {
const { permissionToUpload } = this.state;
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props;
const result = canUploadFile(file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize);
const result = canUploadFile(file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize, permissionToUpload);
if (result.success) {
return true;
}
......@@ -766,8 +780,41 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
showMessageBoxActions = () => {
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
const { permissionToUpload } = this.state;
const { showActionSheet } = this.props;
showActionSheet({ options: this.options });
const options = [];
if (permissionToUpload) {
options.push(
{
title: I18n.t('Take_a_photo'),
icon: 'camera-photo',
onPress: this.takePhoto
},
{
title: I18n.t('Take_a_video'),
icon: 'camera',
onPress: this.takeVideo
},
{
title: I18n.t('Choose_from_library'),
icon: 'image',
onPress: this.chooseFromLibrary
},
{
title: I18n.t('Choose_file'),
icon: 'attach',
onPress: this.chooseFile
}
);
}
options.push({
title: I18n.t('Create_Discussion'),
icon: 'discussions',
onPress: this.createDiscussion
});
showActionSheet({ options });
};
editCancel = () => {
......@@ -968,8 +1015,17 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
};
renderContent = () => {
const { recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview, mentionLoading } =
this.state;
const {
recording,
showEmojiKeyboard,
showSend,
mentions,
trackingType,
commandPreview,
showCommandPreview,
mentionLoading,
permissionToUpload
} = this.state;
const {
editing,
message,
......@@ -995,7 +1051,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const recordAudio =
showSend || !Message_AudioRecorderEnabled ? null : (
<RecordAudio theme={theme} recordingCallback={this.recordingCallback} onFinish={this.finishAudioMessage} />
<RecordAudio
theme={theme}
recordingCallback={this.recordingCallback}
onFinish={this.finishAudioMessage}
permissionToUpload={permissionToUpload}
/>
);
const commandsPreviewAndMentions = !recording ? (
......@@ -1117,11 +1178,12 @@ const mapStateToProps = (state: any) => ({
user: getUserSelector(state),
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
uploadFilePermission: state.permissions['mobile-upload-file']
});
const dispatchToProps = {
typing: (rid: any, status: any) => userTypingAction(rid, status)
};
// @ts-ignore
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox));
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox)) as any;
......@@ -20,7 +20,7 @@ interface IPasscodeBase {
previousPasscode?: string;
title: string;
subtitle?: string;
showBiometry?: string;
showBiometry?: boolean;
onEndProcess: Function;
onError?: Function;
onBiometryPress?(): void;
......
......@@ -15,7 +15,7 @@ import I18n from '../../i18n';
interface IPasscodePasscodeEnter {
theme: string;
hasBiometry: string;
hasBiometry: boolean;
finishProcess: Function;
}
......
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { StyleSheet, Text, TextInputProps, View } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import TextInput from '../presentation/TextInput';
......@@ -45,7 +45,7 @@ const styles = StyleSheet.create({
});
interface ISearchBox {
onChangeText: () => void;
onChangeText: TextInputProps['onChangeText'];
onSubmitEditing: () => void;
hasCancel: boolean;
onCancelPress: Function;
......
......@@ -8,6 +8,7 @@ interface IStatus {
status: string;
size: number;
style?: StyleProp<TextStyle>;
testID?: string;
}
const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: IStatus) => {
......
......@@ -43,7 +43,11 @@ const Content = React.memo(
content = <Text style={[styles.text, { color: themes[props.theme].bodyText }]}>{I18n.t('Sent_an_attachment')}</Text>;
} else if (props.isEncrypted) {
content = (
<Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Encrypted_message')}</Text>
<Text
style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}
accessibilityLabel={I18n.t('Encrypted_message')}>
{I18n.t('Encrypted_message')}
</Text>
);
} else {
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
......
Supports Markdown
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