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

Merge 4.22.0 into master (#3523)

parent 5cd4d824
......@@ -2,7 +2,7 @@ module.exports = {
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.ios.js', '.android.js', '.native.js', '.ts', '.tsx']
extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js', '.native.js']
}
}
},
......
name: iOS Detox
on: [pull_request]
jobs:
detox-build:
runs-on: macos-latest
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode_11.5.app
steps:
- name: Checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Generate Detox app cache key
run: echo $(git rev-parse HEAD:app) > "./app-git-revision.txt"
- name: Cache Detox app
uses: actions/cache@v1
id: detoxappcache
with:
path: ios/build/Build/Products/Release-iphonesimulator
key: iOSDetoxRelease-v4-${{ hashFiles('yarn.lock') }}-${{ hashFiles('ios/Podfile.lock') }}-${{ hashFiles('./app-git-revision.txt') }}
- name: Node
if: steps.detoxappcache.outputs.cache-hit != 'true'
uses: actions/setup-node@v1
- name: Cache node modules
if: steps.detoxappcache.outputs.cache-hit != 'true'
uses: actions/cache@v1
id: npmcache
with:
path: node_modules
key: node-modules-${{ hashFiles('**/yarn.lock') }}
- name: Rebuild detox
if: steps.detoxappcache.outputs.cache-hit != 'true' && steps.npmcache.outputs.cache-hit == 'true'
run: yarn detox clean-framework-cache && yarn detox build-framework-cache
- name: Install Dependencies
if: steps.detoxappcache.outputs.cache-hit != 'true' && steps.npmcache.outputs.cache-hit != 'true'
run: yarn install
- run: yarn detox build e2e --configuration ios.sim.release
if: steps.detoxappcache.outputs.cache-hit != 'true'
detox-test-rooms:
needs: detox-build
runs-on: macos-latest
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode_11.5.app
steps:
- name: Checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Generate Detox app cache key
run: echo $(git rev-parse HEAD:app) > "./app-git-revision.txt"
- name: Cache Detox app
uses: actions/cache@v1
id: detoxappcache
with:
path: ios/build/Build/Products/Release-iphonesimulator
key: iOSDetoxRelease-v4-${{ hashFiles('yarn.lock') }}-${{ hashFiles('ios/Podfile.lock') }}-${{ hashFiles('./app-git-revision.txt') }}
- name: Check for Detox app
if: steps.detoxappcache.outputs.cache-hit != 'true'
run: exit 1
- name: Node
uses: actions/setup-node@v1
- name: Cache node modules
uses: actions/cache@v1
id: npmcache
with:
path: node_modules
key: node-modules-${{ hashFiles('**/yarn.lock') }}
- name: Rebuild detox
if: steps.npmcache.outputs.cache-hit == 'true'
run: yarn detox clean-framework-cache && yarn detox build-framework-cache
- name: Install Dependencies
if: steps.npmcache.outputs.cache-hit != 'true'
run: yarn install
- run: brew tap wix/brew
- run: brew install applesimutils
- run: yarn detox test e2e/tests/room --configuration ios.sim.release --cleanup
- name: Upload test artifacts
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: artifacts
path: artifacts
detox-test-assorted:
needs: detox-build
runs-on: macos-latest
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode_11.5.app
steps:
- name: Checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Generate Detox app cache key
run: echo $(git rev-parse HEAD:app) > "./app-git-revision.txt"
- name: Cache Detox app
uses: actions/cache@v1
id: detoxappcache
with:
path: ios/build/Build/Products/Release-iphonesimulator
key: iOSDetoxRelease-v4-${{ hashFiles('yarn.lock') }}-${{ hashFiles('ios/Podfile.lock') }}-${{ hashFiles('./app-git-revision.txt') }}
- name: Check for Detox app
if: steps.detoxappcache.outputs.cache-hit != 'true'
run: exit 1
- name: Node
uses: actions/setup-node@v1
- name: Cache node modules
uses: actions/cache@v1
id: npmcache
with:
path: node_modules
key: node-modules-${{ hashFiles('**/yarn.lock') }}
- name: Rebuild detox
if: steps.npmcache.outputs.cache-hit == 'true'
run: yarn detox clean-framework-cache && yarn detox build-framework-cache
- name: Install Dependencies
if: steps.npmcache.outputs.cache-hit != 'true'
run: yarn install
- run: brew tap wix/brew
- run: brew install applesimutils
- run: yarn detox test e2e/tests/assorted --configuration ios.sim.release --cleanup
- name: Upload test artifacts
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: artifacts
path: artifacts
detox-test-onboarding:
needs: detox-build
runs-on: macos-latest
timeout-minutes: 60
env:
DEVELOPER_DIR: /Applications/Xcode_11.5.app
steps:
- name: Checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Generate Detox app cache key
run: echo $(git rev-parse HEAD:app) > "./app-git-revision.txt"
- name: Cache Detox app
uses: actions/cache@v1
id: detoxappcache
with:
path: ios/build/Build/Products/Release-iphonesimulator
key: iOSDetoxRelease-v4-${{ hashFiles('yarn.lock') }}-${{ hashFiles('ios/Podfile.lock') }}-${{ hashFiles('./app-git-revision.txt') }}
- name: Check for Detox app
if: steps.detoxappcache.outputs.cache-hit != 'true'
run: exit 1
- name: Node
uses: actions/setup-node@v1
- name: Cache node modules
uses: actions/cache@v1
id: npmcache
with:
path: node_modules
key: node-modules-${{ hashFiles('**/yarn.lock') }}
- name: Rebuild detox
if: steps.npmcache.outputs.cache-hit == 'true'
run: yarn detox clean-framework-cache && yarn detox build-framework-cache
- name: Install Dependencies
if: steps.npmcache.outputs.cache-hit != 'true'
run: yarn install
- run: brew tap wix/brew
- run: brew install applesimutils
- run: yarn detox test e2e/tests/onboarding --configuration ios.sim.release --cleanup
- name: Upload test artifacts
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: artifacts
path: artifacts
import initStoryshots from '@storybook/addon-storyshots';
jest.mock('rn-fetch-blob', () => ({
fs: {
dirs: {
DocumentDir: '/data/com.rocket.chat/documents',
DownloadDir: '/data/com.rocket.chat/downloads'
},
exists: jest.fn(() => null)
},
fetch: jest.fn(() => null),
config: jest.fn(() => null)
}));
jest.mock('react-native-file-viewer', () => ({
open: jest.fn(() => null)
}));
jest.mock('../app/lib/database', () => jest.fn(() => null));
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
......
......@@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.21.0"
versionName "4.22.0"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
......
......@@ -65,6 +65,7 @@ export const themes: any = {
previewBackground: '#1F2329',
previewTintColor: '#ffffff',
backdropOpacity: 0.3,
attachmentLoadingOpacity: 0.7,
...mentions
},
dark: {
......@@ -112,6 +113,7 @@ export const themes: any = {
previewBackground: '#030b1b',
previewTintColor: '#ffffff',
backdropOpacity: 0.9,
attachmentLoadingOpacity: 0.3,
...mentions
},
black: {
......@@ -159,6 +161,7 @@ export const themes: any = {
previewBackground: '#000000',
previewTintColor: '#ffffff',
backdropOpacity: 0.9,
attachmentLoadingOpacity: 0.3,
...mentions
}
};
import RNFetchBlob from 'rn-fetch-blob';
export const DOCUMENTS_PATH = `${RNFetchBlob.fs.dirs.DocumentDir}/`;
export const DOWNLOAD_PATH = `${RNFetchBlob.fs.dirs.DownloadDir}/`;
......@@ -17,7 +17,7 @@ export const useActionSheet = () => useContext(context);
const { Provider, Consumer } = context;
export const withActionSheet = (Component: React.FC) =>
export const withActionSheet = <P extends object>(Component: React.ComponentType<P>) =>
forwardRef((props: any, ref: ForwardedRef<any>) => (
<Consumer>{(contexts: any) => <Component {...props} {...contexts} ref={ref} />}</Consumer>
));
......
......@@ -16,7 +16,7 @@ export interface IAvatar {
onPress(): void;
getCustomEmoji(): any;
avatarETag: string;
isStatic: boolean;
isStatic: boolean | string;
rid: string;
blockUnauthenticatedAccess: boolean;
serverVersion: string;
......
......@@ -27,13 +27,11 @@ export const FormContainerInner = ({ children }: { children: React.ReactNode }):
);
const FormContainer = ({ children, theme, testID, ...props }: IFormContainer): JSX.Element => (
// @ts-ignore
<KeyboardView
style={{ backgroundColor: themes[theme].backgroundColor }}
contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128}>
<StatusBar />
{/* @ts-ignore*/}
<ScrollView
style={sharedStyles.container}
contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}
......
......@@ -8,7 +8,7 @@ import I18n from '../../i18n';
interface IPasscodeChoose {
theme: string;
force: boolean;
force?: boolean;
finishProcess: Function;
}
......
import React from 'react';
import { StyleProp, TextStyle } from 'react-native';
import { CustomIcon } from '../../lib/Icons';
import { STATUS_COLORS } from '../../constants/colors';
......@@ -6,14 +7,14 @@ import { STATUS_COLORS } from '../../constants/colors';
interface IStatus {
status: string;
size: number;
style: any;
style?: StyleProp<TextStyle>;
}
const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: IStatus) => {
const name = `status-${status}`;
const isNameValid = CustomIcon.hasIcon(name);
const iconName = isNameValid ? name : 'status-offline';
const calculatedStyle = [
const calculatedStyle: StyleProp<TextStyle> = [
{
width: size,
height: size,
......
......@@ -58,7 +58,7 @@ interface IRCTextInputProps extends TextInputProps {
};
loading?: boolean;
containerStyle?: StyleProp<ViewStyle>;
inputStyle?: TextStyle;
inputStyle?: StyleProp<TextStyle>;
inputRef?: React.Ref<unknown>;
testID?: string;
iconLeft?: string;
......
......@@ -8,10 +8,10 @@ import ActivityIndicator from '../../ActivityIndicator';
import styles from './styles';
interface IInput {
children: JSX.Element;
children?: JSX.Element;
onPress: Function;
theme: string;
inputStyle: object;
inputStyle?: object;
disabled?: boolean | object;
placeholder?: string;
loading?: boolean;
......
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import moment from 'moment';
import { transparentize } from 'color2k';
......@@ -11,6 +11,9 @@ import openLink from '../../utils/openLink';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
import { fileDownloadAndPreview } from '../../utils/fileDownload';
import { formatAttachmentUrl } from '../../lib/utils';
import RCActivityIndicator from '../ActivityIndicator';
const styles = StyleSheet.create({
button: {
......@@ -28,6 +31,9 @@ const styles = StyleSheet.create({
flexDirection: 'column',
padding: 15
},
backdrop: {
...StyleSheet.absoluteFillObject
},
authorContainer: {
flex: 1,
flexDirection: 'row',
......@@ -120,7 +126,7 @@ interface IMessageFields {
}
interface IMessageReply {
attachment: Partial<IMessageReplyAttachment>;
attachment: IMessageReplyAttachment;
timeFormat: string;
index: number;
theme: string;
......@@ -209,12 +215,14 @@ const Fields = React.memo(
const Reply = React.memo(
({ attachment, timeFormat, index, getCustomEmoji, theme }: IMessageReply) => {
const [loading, setLoading] = useState(false);
if (!attachment) {
return null;
}
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
const onPress = () => {
const onPress = async () => {
let url = attachment.title_link || attachment.author_link;
if (attachment.message_link) {
return jumpToMessage(attachment.message_link);
......@@ -223,10 +231,11 @@ const Reply = React.memo(
return;
}
if (attachment.type === 'file') {
if (!url.startsWith('http')) {
url = `${baseUrl}${url}`;
}
url = `${url}?rc_uid=${user.id}&rc_token=${user.token}`;
setLoading(true);
url = formatAttachmentUrl(attachment.title_link, user.id, user.token, baseUrl);
await fileDownloadAndPreview(url, attachment);
setLoading(false);
return;
}
openLink(url, theme);
};
......@@ -254,12 +263,23 @@ const Reply = React.memo(
borderColor
}
]}
background={Touchable.Ripple(themes[theme].bannerBackground)}>
background={Touchable.Ripple(themes[theme].bannerBackground)}
disabled={loading}>
<View style={styles.attachmentContainer}>
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
<UrlImage image={attachment.thumb_url} />
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
<Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
{loading ? (
<View style={[styles.backdrop]}>
<View
style={[
styles.backdrop,
{ backgroundColor: themes[theme].bannerBackground, opacity: themes[theme].attachmentLoadingOpacity }
]}></View>
<RCActivityIndicator theme={theme} />
</View>
) : null}
</View>
</Touchable>
{/* @ts-ignore*/}
......
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import { StyleSheet } from 'react-native';
import { dequal } from 'dequal';
import Touchable from './Touchable';
import Markdown from '../markdown';
import openLink from '../../utils/openLink';
import { isIOS } from '../../utils/deviceInfo';
import { CustomIcon } from '../../lib/Icons';
import { formatAttachmentUrl } from '../../lib/utils';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
import { fileDownload } from '../../utils/fileDownload';
import EventEmitter from '../../utils/events';
import { LISTENER } from '../Toast';
import I18n from '../../i18n';
import RCActivityIndicator from '../ActivityIndicator';
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
const isTypeSupported = (type: any) => SUPPORTED_TYPES.indexOf(type) !== -1;
......@@ -27,6 +31,9 @@ const styles = StyleSheet.create({
interface IMessageVideo {
file: {
title: string;
title_link: string;
type: string;
video_type: string;
video_url: string;
description: string;
......@@ -39,15 +46,34 @@ interface IMessageVideo {
const Video = React.memo(
({ file, showAttachment, getCustomEmoji, theme }: IMessageVideo) => {
const { baseUrl, user } = useContext(MessageContext);
const [loading, setLoading] = useState(false);
if (!baseUrl) {
return null;
}
const onPress = () => {
const onPress = async () => {
if (isTypeSupported(file.video_type)) {
return showAttachment(file);
}
const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
openLink(uri, theme);
if (!isIOS) {
const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
await downloadVideo(uri);
return;
}
EventEmitter.emit(LISTENER, { message: I18n.t('Unsupported_format') });
};
const downloadVideo = async (uri: string) => {
setLoading(true);
const fileDownloaded = await fileDownload(uri, file);
setLoading(false);
if (fileDownloaded) {
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
return;
}
EventEmitter.emit(LISTENER, { message: I18n.t('error-save-video') });
};
return (
......@@ -56,7 +82,11 @@ const Video = React.memo(
onPress={onPress}
style={[styles.button, { backgroundColor: themes[theme].videoBackground }]}
background={Touchable.Ripple(themes[theme].bannerBackground)}>
<CustomIcon name='play-filled' size={54} color={themes[theme].buttonText} />
{loading ? (
<RCActivityIndicator theme={theme} />
) : (
<CustomIcon name='play-filled' size={54} color={themes[theme].buttonText} />
)}
</Touchable>
{/* @ts-ignore*/}
<Markdown
......
......@@ -59,6 +59,7 @@ export interface IUser {
export type UserMention = Pick<IUser, '_id' | 'username' | 'name'>;
export interface IMessageContent {
_id: string;
isTemp: boolean;
isInfo: boolean;
tmid: string;
......
......@@ -9,3 +9,7 @@ declare module '@rocket.chat/ui-kit';
declare module '@rocket.chat/sdk';
declare module 'react-native-config-reader';
declare module 'react-native-keycommands';
declare module 'react-native-mime-types';
declare module 'react-native-restart';
declare module 'react-native-prompt-android';
declare module 'react-native-jitsi-meet';
......@@ -782,5 +782,8 @@
"No_canned_responses": "No canned responses",
"Send_email_confirmation": "Send email confirmation",
"sending_email_confirmation": "sending email confirmation",
"Enable_Message_Parser": "Enable Message Parser"
}
\ No newline at end of file
"Enable_Message_Parser": "Enable Message Parser",
"Unsupported_format": "Unsupported format",
"Downloaded_file": "Downloaded file",
"Error_Download_file": "Error while downloading file"
}
......@@ -733,5 +733,8 @@
"Sharing": "Compartilhando",
"No_canned_responses": "Não há respostas predefinidas",
"Send_email_confirmation": "Enviar email de confirmação",
"sending_email_confirmation": "enviando email de confirmação"
}
\ No newline at end of file
"sending_email_confirmation": "enviando email de confirmação",
"Unsupported_format": "Formato não suportado",
"Downloaded_file": "Arquivo baixado",
"Error_Download_file": "Erro ao baixar o arquivo"
}
......@@ -63,10 +63,12 @@ const parseDeepLinking = (url: string) => {
}
}
const call = /^(https:\/\/)?jitsi.rocket.chat\//;
const fullURL = url;
if (url.match(call)) {
url = url.replace(call, '').trim();
if (url) {
return { path: url, isCall: true };