Merge branch 'develop' into new.add-discusions-roomactionsview
This commit is contained in:
commit
2b7f589847
|
@ -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']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
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
|
|
@ -1,5 +1,21 @@
|
|||
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());
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
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*/}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
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';
|
||||
|
|
|
@ -783,5 +783,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"
|
||||
"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"
|
||||
"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 };
|
||||
return { path: url, isCall: true, fullURL };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,14 @@ async function jitsiURL({ room }) {
|
|||
return `${protocol}${domain}${prefix}${rname}${queryString}`;
|
||||
}
|
||||
|
||||
export function callJitsiWithoutServer(path) {
|
||||
logEvent(events.RA_JITSI_VIDEO);
|
||||
const { Jitsi_SSL } = reduxStore.getState().settings;
|
||||
const protocol = Jitsi_SSL ? 'https://' : 'http://';
|
||||
const url = `${protocol}${path}`;
|
||||
Navigation.navigate('JitsiMeetView', { url, onlyAudio: false });
|
||||
}
|
||||
|
||||
async function callJitsi(room, onlyAudio = false) {
|
||||
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
||||
const url = await jitsiURL.call(this, { room });
|
||||
|
|
|
@ -54,7 +54,7 @@ import loadMissedMessages from './methods/loadMissedMessages';
|
|||
import loadThreadMessages from './methods/loadThreadMessages';
|
||||
import sendMessage, { resendMessage } from './methods/sendMessage';
|
||||
import { cancelUpload, isUploadActive, sendFileMessage } from './methods/sendFileMessage';
|
||||
import callJitsi from './methods/callJitsi';
|
||||
import callJitsi, { callJitsiWithoutServer } from './methods/callJitsi';
|
||||
import logout, { removeServer } from './methods/logout';
|
||||
import UserPreferences from './userPreferences';
|
||||
import { Encryption } from './encryption';
|
||||
|
@ -76,6 +76,7 @@ const RocketChat = {
|
|||
CURRENT_SERVER,
|
||||
CERTIFICATE_KEY,
|
||||
callJitsi,
|
||||
callJitsiWithoutServer,
|
||||
async subscribeRooms() {
|
||||
if (!this.roomsSub) {
|
||||
try {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import React from 'react';
|
||||
import { Image } from 'react-native';
|
||||
import { FastImageProps } from '@rocket.chat/react-native-fast-image';
|
||||
|
||||
import { types } from './types';
|
||||
|
||||
export const ImageComponent = (type: string) => {
|
||||
export const ImageComponent = (type?: string): React.ComponentType<Partial<Image> | FastImageProps> => {
|
||||
let Component;
|
||||
if (type === types.REACT_NATIVE_IMAGE) {
|
||||
const { Image } = require('react-native');
|
||||
|
|
|
@ -16,13 +16,14 @@ const styles = StyleSheet.create({
|
|||
|
||||
interface IImageViewer {
|
||||
uri: string;
|
||||
imageComponentType: string;
|
||||
imageComponentType?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
theme: string;
|
||||
onLoadEnd?: () => void;
|
||||
}
|
||||
|
||||
export const ImageViewer = ({ uri, imageComponentType, theme, width, height, ...props }: IImageViewer) => {
|
||||
export const ImageViewer = ({ uri, imageComponentType, theme, width, height, ...props }: IImageViewer): JSX.Element => {
|
||||
const backgroundColor = themes[theme].previewBackground;
|
||||
const Component = ImageComponent(imageComponentType);
|
||||
return (
|
|
@ -1,5 +1,3 @@
|
|||
// @ts-ignore
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
export * from './ImageViewer';
|
||||
export * from './types';
|
||||
export * from './ImageComponent';
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import React from 'react';
|
||||
import { KeyboardAwareScrollView } from '@codler/react-native-keyboard-aware-scroll-view';
|
||||
import { KeyboardAwareScrollView, KeyboardAwareScrollViewProps } from '@codler/react-native-keyboard-aware-scroll-view';
|
||||
|
||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||
|
||||
interface IKeyboardViewProps {
|
||||
style: any;
|
||||
contentContainerStyle: any;
|
||||
interface IKeyboardViewProps extends KeyboardAwareScrollViewProps {
|
||||
keyboardVerticalOffset: number;
|
||||
scrollEnabled: boolean;
|
||||
children: JSX.Element;
|
||||
scrollEnabled?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default class KeyboardView extends React.PureComponent<IKeyboardViewProps, any> {
|
||||
|
@ -22,9 +20,7 @@ export default class KeyboardView extends React.PureComponent<IKeyboardViewProps
|
|||
contentContainerStyle={contentContainerStyle}
|
||||
scrollEnabled={scrollEnabled}
|
||||
alwaysBounceVertical={false}
|
||||
extraHeight={keyboardVerticalOffset}
|
||||
// @ts-ignore
|
||||
behavior='position'>
|
||||
extraHeight={keyboardVerticalOffset}>
|
||||
{children}
|
||||
</KeyboardAwareScrollView>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import { Pressable, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
|
||||
|
||||
import Avatar from '../containers/Avatar';
|
||||
import { CustomIcon } from '../lib/Icons';
|
||||
|
@ -44,8 +44,8 @@ interface IUserItem {
|
|||
username: string;
|
||||
onPress(): void;
|
||||
testID: string;
|
||||
onLongPress(): void;
|
||||
style: any;
|
||||
onLongPress?: () => void;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
icon: string;
|
||||
theme: string;
|
||||
}
|
||||
|
|
|
@ -122,6 +122,11 @@ const handleOpen = function* handleOpen({ params }) {
|
|||
host = id;
|
||||
}
|
||||
});
|
||||
|
||||
if (!host && params.fullURL) {
|
||||
RocketChat.callJitsiWithoutServer(params.fullURL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.type === 'oauth') {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import RNFetchBlob, { FetchBlobResponse } from 'rn-fetch-blob';
|
||||
import FileViewer from 'react-native-file-viewer';
|
||||
|
||||
import EventEmitter from '../events';
|
||||
import { LISTENER } from '../../containers/Toast';
|
||||
import I18n from '../../i18n';
|
||||
import { DOCUMENTS_PATH, DOWNLOAD_PATH } from '../../constants/localPath';
|
||||
|
||||
interface IAttachment {
|
||||
title: string;
|
||||
title_link: string;
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const getLocalFilePathFromFile = (localPath: string, attachment: IAttachment): string => `${localPath}${attachment.title}`;
|
||||
|
||||
export const fileDownload = (url: string, attachment: IAttachment): Promise<FetchBlobResponse> => {
|
||||
const path = getLocalFilePathFromFile(DOWNLOAD_PATH, attachment);
|
||||
|
||||
const options = {
|
||||
path,
|
||||
timeout: 10000,
|
||||
indicator: true,
|
||||
overwrite: true,
|
||||
addAndroidDownloads: {
|
||||
path,
|
||||
notification: true,
|
||||
useDownloadManager: true
|
||||
}
|
||||
};
|
||||
|
||||
return RNFetchBlob.config(options).fetch('GET', url);
|
||||
};
|
||||
|
||||
export const fileDownloadAndPreview = async (url: string, attachment: IAttachment): Promise<void> => {
|
||||
try {
|
||||
const path = getLocalFilePathFromFile(DOCUMENTS_PATH, attachment);
|
||||
const file = await RNFetchBlob.config({
|
||||
timeout: 10000,
|
||||
indicator: true,
|
||||
path
|
||||
}).fetch('GET', url);
|
||||
|
||||
FileViewer.open(file.data, {
|
||||
showOpenWithDialog: true,
|
||||
showAppsSuggestions: true
|
||||
})
|
||||
.then(res => res)
|
||||
.catch(async () => {
|
||||
const file = await fileDownload(url, attachment);
|
||||
file
|
||||
? EventEmitter.emit(LISTENER, { message: I18n.t('Downloaded_file') })
|
||||
: EventEmitter.emit(LISTENER, { message: I18n.t('Error_Download_file') });
|
||||
});
|
||||
} catch (e) {
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Error_Download_file') });
|
||||
}
|
||||
};
|
|
@ -155,6 +155,9 @@ export default {
|
|||
SE_CLEAR_LOCAL_SERVER_CACHE: 'se_clear_local_server_cache',
|
||||
SE_LOG_OUT: 'se_log_out',
|
||||
|
||||
// USER PREFERENCE VIEW
|
||||
UP_GO_USER_NOTIFICATION_PREF: 'up_go_user_notification_pref',
|
||||
|
||||
// SECURITY PRIVACY VIEW
|
||||
SP_GO_E2EENCRYPTIONSECURITY: 'sp_go_e2e_encryption_security',
|
||||
SP_GO_SCREENLOCKCONFIG: 'sp_go_screen_lock_cfg',
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
export default {
|
||||
keyboardShouldPersistTaps: 'always',
|
||||
keyboardDismissMode: 'interactive'
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
import { KeyboardAwareScrollViewProps } from '@codler/react-native-keyboard-aware-scroll-view';
|
||||
|
||||
const scrollPersistTaps: Partial<KeyboardAwareScrollViewProps> = {
|
||||
keyboardShouldPersistTaps: 'always',
|
||||
keyboardDismissMode: 'interactive'
|
||||
};
|
||||
|
||||
export default scrollPersistTaps;
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import * as List from '../containers/List';
|
||||
|
@ -9,8 +10,16 @@ import * as HeaderButton from '../containers/HeaderButton';
|
|||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import I18n from '../i18n';
|
||||
|
||||
const setHeader = (navigation, isMasterDetail) => {
|
||||
const options = {
|
||||
type TNavigation = StackNavigationProp<any, 'AddChannelTeamView'>;
|
||||
|
||||
interface IAddChannelTeamView {
|
||||
route: RouteProp<{ AddChannelTeamView: { teamId: string; teamChannels: object[] } }, 'AddChannelTeamView'>;
|
||||
navigation: TNavigation;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
const setHeader = (navigation: TNavigation, isMasterDetail: boolean) => {
|
||||
const options: StackNavigationOptions = {
|
||||
headerTitle: I18n.t('Add_Channel_to_Team')
|
||||
};
|
||||
|
||||
|
@ -21,7 +30,7 @@ const setHeader = (navigation, isMasterDetail) => {
|
|||
navigation.setOptions(options);
|
||||
};
|
||||
|
||||
const AddChannelTeamView = ({ navigation, route, isMasterDetail }) => {
|
||||
const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTeamView) => {
|
||||
const { teamId, teamChannels } = route.params;
|
||||
const { theme } = useTheme();
|
||||
|
||||
|
@ -66,13 +75,7 @@ const AddChannelTeamView = ({ navigation, route, isMasterDetail }) => {
|
|||
);
|
||||
};
|
||||
|
||||
AddChannelTeamView.propTypes = {
|
||||
route: PropTypes.object,
|
||||
navigation: PropTypes.object,
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { FlatList, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
@ -21,18 +22,27 @@ import { goRoom } from '../utils/goRoom';
|
|||
import { showErrorAlert } from '../utils/info';
|
||||
import debounce from '../utils/debounce';
|
||||
|
||||
interface IAddExistingChannelViewState {
|
||||
// TODO: refactor with Room Model
|
||||
search: any[];
|
||||
channels: any[];
|
||||
selected: string[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface IAddExistingChannelViewProps {
|
||||
navigation: StackNavigationProp<any, 'AddExistingChannelView'>;
|
||||
route: RouteProp<{ AddExistingChannelView: { teamId: string } }, 'AddExistingChannelView'>;
|
||||
theme: string;
|
||||
isMasterDetail: boolean;
|
||||
addTeamChannelPermission: string[];
|
||||
}
|
||||
|
||||
const QUERY_SIZE = 50;
|
||||
|
||||
class AddExistingChannelView extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
addTeamChannelPermission: PropTypes.array
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
class AddExistingChannelView extends React.Component<IAddExistingChannelViewProps, IAddExistingChannelViewState> {
|
||||
private teamId: string;
|
||||
constructor(props: IAddExistingChannelViewProps) {
|
||||
super(props);
|
||||
this.query();
|
||||
this.teamId = props.route?.params?.teamId;
|
||||
|
@ -49,7 +59,7 @@ class AddExistingChannelView extends React.Component {
|
|||
const { navigation, isMasterDetail } = this.props;
|
||||
const { selected } = this.state;
|
||||
|
||||
const options = {
|
||||
const options: StackNavigationOptions = {
|
||||
headerTitle: I18n.t('Add_Existing_Channel')
|
||||
};
|
||||
|
||||
|
@ -82,9 +92,10 @@ class AddExistingChannelView extends React.Component {
|
|||
)
|
||||
.fetch();
|
||||
|
||||
const asyncFilter = async channelsArray => {
|
||||
// TODO: Refactor with Room Model
|
||||
const asyncFilter = async (channelsArray: any[]) => {
|
||||
const results = await Promise.all(
|
||||
channelsArray.map(async channel => {
|
||||
channelsArray.map(async (channel: any) => {
|
||||
if (channel.prid) {
|
||||
return false;
|
||||
}
|
||||
|
@ -96,7 +107,7 @@ class AddExistingChannelView extends React.Component {
|
|||
})
|
||||
);
|
||||
|
||||
return channelsArray.filter((_v, index) => results[index]);
|
||||
return channelsArray.filter((_v: any, index: number) => results[index]);
|
||||
};
|
||||
const channelFiltered = await asyncFilter(channels);
|
||||
this.setState({ channels: channelFiltered });
|
||||
|
@ -105,7 +116,7 @@ class AddExistingChannelView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
onSearchChangeText = debounce(text => {
|
||||
onSearchChangeText = debounce((text: string) => {
|
||||
this.query(text);
|
||||
}, 300);
|
||||
|
||||
|
@ -126,7 +137,7 @@ class AddExistingChannelView extends React.Component {
|
|||
this.setState({ loading: false });
|
||||
goRoom({ item: result, isMasterDetail });
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
logEvent(events.CT_ADD_ROOM_TO_TEAM_F);
|
||||
showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {});
|
||||
this.setState({ loading: false });
|
||||
|
@ -137,17 +148,17 @@ class AddExistingChannelView extends React.Component {
|
|||
const { theme } = this.props;
|
||||
return (
|
||||
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
|
||||
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='add-existing-channel-view-search' />
|
||||
<SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='add-existing-channel-view-search' />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
isChecked = rid => {
|
||||
isChecked = (rid: string) => {
|
||||
const { selected } = this.state;
|
||||
return selected.includes(rid);
|
||||
};
|
||||
|
||||
toggleChannel = rid => {
|
||||
toggleChannel = (rid: string) => {
|
||||
const { selected } = this.state;
|
||||
|
||||
animateNextTransition();
|
||||
|
@ -161,7 +172,8 @@ class AddExistingChannelView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
// TODO: refactor with Room Model
|
||||
renderItem = ({ item }: { item: any }) => {
|
||||
const isChecked = this.isChecked(item.rid);
|
||||
// TODO: reuse logic inside RoomTypeIcon
|
||||
const icon = item.t === 'p' && !item.teamId ? 'channel-private' : 'channel-public';
|
||||
|
@ -207,7 +219,7 @@ class AddExistingChannelView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
addTeamChannelPermission: state.permissions['add-team-channel']
|
||||
});
|
|
@ -1,12 +1,13 @@
|
|||
import React from 'react';
|
||||
import { PermissionsAndroid, StyleSheet, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import CameraRoll from '@react-native-community/cameraroll';
|
||||
import * as mime from 'react-native-mime-types';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { Video } from 'expo-av';
|
||||
import SHA256 from 'js-sha256';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import { LISTENER } from '../containers/Toast';
|
||||
|
@ -30,23 +31,41 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class AttachmentView extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
baseUrl: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
insets: PropTypes.object,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
token: PropTypes.string
|
||||
}),
|
||||
Allow_Save_Media_to_Gallery: PropTypes.bool
|
||||
};
|
||||
// TODO: refactor when react-navigation is done
|
||||
export interface IAttachment {
|
||||
title: string;
|
||||
title_link?: string;
|
||||
image_url?: string;
|
||||
image_type?: string;
|
||||
video_url?: string;
|
||||
video_type?: string;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
interface IAttachmentViewState {
|
||||
attachment: IAttachment;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface IAttachmentViewProps {
|
||||
navigation: StackNavigationProp<any, 'AttachmentView'>;
|
||||
route: RouteProp<{ AttachmentView: { attachment: IAttachment } }, 'AttachmentView'>;
|
||||
theme: string;
|
||||
baseUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
insets: { left: number; bottom: number; right: number; top: number };
|
||||
user: {
|
||||
id: string;
|
||||
token: string;
|
||||
};
|
||||
Allow_Save_Media_to_Gallery: boolean;
|
||||
}
|
||||
|
||||
class AttachmentView extends React.Component<IAttachmentViewProps, IAttachmentViewState> {
|
||||
private unsubscribeBlur: (() => void) | undefined;
|
||||
private videoRef: any;
|
||||
|
||||
constructor(props: IAttachmentViewProps) {
|
||||
super(props);
|
||||
const attachment = props.route.params?.attachment;
|
||||
this.state = { attachment, loading: true };
|
||||
|
@ -79,21 +98,9 @@ class AttachmentView extends React.Component {
|
|||
}
|
||||
const options = {
|
||||
title,
|
||||
headerLeft: () => (
|
||||
<HeaderButton.CloseModal
|
||||
testID='close-attachment-view'
|
||||
navigation={navigation}
|
||||
buttonStyle={{ color: themes[theme].previewTintColor }}
|
||||
/>
|
||||
),
|
||||
headerLeft: () => <HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} />,
|
||||
headerRight: () =>
|
||||
Allow_Save_Media_to_Gallery ? (
|
||||
<HeaderButton.Download
|
||||
testID='save-image'
|
||||
onPress={this.handleSave}
|
||||
buttonStyle={{ color: themes[theme].previewTintColor }}
|
||||
/>
|
||||
) : null,
|
||||
Allow_Save_Media_to_Gallery ? <HeaderButton.Download testID='save-image' onPress={this.handleSave} /> : null,
|
||||
headerBackground: () => <View style={{ flex: 1, backgroundColor: themes[theme].previewBackground }} />,
|
||||
headerTintColor: themes[theme].previewTintColor,
|
||||
headerTitleStyle: { color: themes[theme].previewTintColor, marginHorizontal: 10 }
|
||||
|
@ -101,7 +108,7 @@ class AttachmentView extends React.Component {
|
|||
navigation.setOptions(options);
|
||||
};
|
||||
|
||||
getVideoRef = ref => (this.videoRef = ref);
|
||||
getVideoRef = (ref: Video) => (this.videoRef = ref);
|
||||
|
||||
handleSave = async () => {
|
||||
const { attachment } = this.state;
|
||||
|
@ -113,7 +120,8 @@ class AttachmentView extends React.Component {
|
|||
if (isAndroid) {
|
||||
const rationale = {
|
||||
title: I18n.t('Write_External_Permission'),
|
||||
message: I18n.t('Write_External_Permission_Message')
|
||||
message: I18n.t('Write_External_Permission_Message'),
|
||||
buttonPositive: 'Ok'
|
||||
};
|
||||
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, rationale);
|
||||
if (!(result || result === PermissionsAndroid.RESULTS.GRANTED)) {
|
||||
|
@ -125,7 +133,7 @@ class AttachmentView extends React.Component {
|
|||
try {
|
||||
const extension = image_url ? `.${mime.extension(image_type) || 'jpg'}` : `.${mime.extension(video_type) || 'mp4'}`;
|
||||
const documentDir = `${RNFetchBlob.fs.dirs.DocumentDir}/`;
|
||||
const path = `${documentDir + SHA256(url) + extension}`;
|
||||
const path = `${documentDir + sha256(url!) + extension}`;
|
||||
const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment);
|
||||
await CameraRoll.save(path, { album: 'Rocket.Chat' });
|
||||
await file.flush();
|
||||
|
@ -136,7 +144,7 @@ class AttachmentView extends React.Component {
|
|||
this.setState({ loading: false });
|
||||
};
|
||||
|
||||
renderImage = uri => {
|
||||
renderImage = (uri: string) => {
|
||||
const { theme, width, height, insets } = this.props;
|
||||
const headerHeight = getHeaderHeight(width > height);
|
||||
return (
|
||||
|
@ -150,7 +158,7 @@ class AttachmentView extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
renderVideo = uri => (
|
||||
renderVideo = (uri: string) => (
|
||||
<Video
|
||||
source={{ uri }}
|
||||
rate={1.0}
|
||||
|
@ -190,7 +198,7 @@ class AttachmentView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state),
|
||||
Allow_Save_Media_to_Gallery: state.settings.Allow_Save_Media_to_Gallery ?? true
|
|
@ -1,15 +1,13 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Orientation from 'react-native-orientation-locker';
|
||||
import useDeepCompareEffect from 'use-deep-compare-effect';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import Modal from 'react-native-modal';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
|
||||
import { withTheme } from '../theme';
|
||||
import { useTheme } from '../theme';
|
||||
import { hasNotch, isTablet } from '../utils/deviceInfo';
|
||||
import { TYPE } from '../containers/Passcode/constants';
|
||||
import { PasscodeChoose } from '../containers/Passcode';
|
||||
import EventEmitter from '../utils/events';
|
||||
import { CustomIcon } from '../lib/Icons';
|
||||
|
@ -27,9 +25,17 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const ChangePasscodeView = React.memo(({ theme }) => {
|
||||
interface IArgs {
|
||||
submit(passcode: string): void;
|
||||
cancel(): void;
|
||||
force: boolean;
|
||||
}
|
||||
|
||||
const ChangePasscodeView = React.memo(() => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [data, setData] = useState({});
|
||||
const [data, setData] = useState<Partial<IArgs>>({});
|
||||
|
||||
const { theme } = useTheme();
|
||||
|
||||
useDeepCompareEffect(() => {
|
||||
if (!isEmpty(data)) {
|
||||
|
@ -39,11 +45,11 @@ const ChangePasscodeView = React.memo(({ theme }) => {
|
|||
}
|
||||
}, [data]);
|
||||
|
||||
const showChangePasscode = args => {
|
||||
const showChangePasscode = (args: IArgs) => {
|
||||
setData(args);
|
||||
};
|
||||
|
||||
const onSubmit = passcode => {
|
||||
const onSubmit = (passcode: string) => {
|
||||
const { submit } = data;
|
||||
if (submit) {
|
||||
submit(passcode);
|
||||
|
@ -74,7 +80,7 @@ const ChangePasscodeView = React.memo(({ theme }) => {
|
|||
|
||||
return (
|
||||
<Modal useNativeDriver isVisible={visible} hideModalContentWhileAnimating style={styles.modal}>
|
||||
<PasscodeChoose theme={theme} type={TYPE.choose} finishProcess={onSubmit} force={data?.force} />
|
||||
<PasscodeChoose theme={theme} finishProcess={onSubmit} force={data?.force} />
|
||||
{!data?.force ? (
|
||||
<Touchable onPress={onCancel} style={styles.close}>
|
||||
<CustomIcon name='close' color={themes[theme].passcodePrimary} size={30} />
|
||||
|
@ -84,8 +90,4 @@ const ChangePasscodeView = React.memo(({ theme }) => {
|
|||
);
|
||||
});
|
||||
|
||||
ChangePasscodeView.propTypes = {
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default withTheme(ChangePasscodeView);
|
||||
export default ChangePasscodeView;
|
|
@ -1,7 +1,9 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, ScrollView, StyleSheet, Switch, Text, View } from 'react-native';
|
||||
import { Dispatch } from 'redux';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { FlatList, ScrollView, StyleSheet, Switch, Text, View, SwitchProps } from 'react-native';
|
||||
import { dequal } from 'dequal';
|
||||
|
||||
import * as List from '../containers/List';
|
||||
|
@ -66,30 +68,59 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class CreateChannelView extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
create: PropTypes.func.isRequired,
|
||||
removeUser: PropTypes.func.isRequired,
|
||||
error: PropTypes.object,
|
||||
failure: PropTypes.bool,
|
||||
isFetching: PropTypes.bool,
|
||||
encryptionEnabled: PropTypes.bool,
|
||||
users: PropTypes.array.isRequired,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
roles: PropTypes.array
|
||||
}),
|
||||
theme: PropTypes.string,
|
||||
teamId: PropTypes.string,
|
||||
createPublicChannelPermission: PropTypes.array,
|
||||
createPrivateChannelPermission: PropTypes.array
|
||||
};
|
||||
interface IOtherUser {
|
||||
_id: string;
|
||||
name: string;
|
||||
fname: string;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
interface ICreateFunction extends Omit<ICreateChannelViewState, 'channelName' | 'permissions'> {
|
||||
name: string;
|
||||
users: string[];
|
||||
teamId: string;
|
||||
}
|
||||
|
||||
interface ICreateChannelViewState {
|
||||
channelName: string;
|
||||
type: boolean;
|
||||
readOnly: boolean;
|
||||
encrypted: boolean;
|
||||
broadcast: boolean;
|
||||
isTeam: boolean;
|
||||
permissions: boolean[];
|
||||
}
|
||||
|
||||
interface ICreateChannelViewProps {
|
||||
navigation: StackNavigationProp<any, 'CreateChannelView'>;
|
||||
route: RouteProp<{ CreateChannelView: { isTeam: boolean; teamId: string } }, 'CreateChannelView'>;
|
||||
baseUrl: string;
|
||||
create: (data: ICreateFunction) => void;
|
||||
removeUser: (user: IOtherUser) => void;
|
||||
error: object;
|
||||
failure: boolean;
|
||||
isFetching: boolean;
|
||||
encryptionEnabled: boolean;
|
||||
users: IOtherUser[];
|
||||
user: {
|
||||
id: string;
|
||||
token: string;
|
||||
roles: string[];
|
||||
};
|
||||
theme: string;
|
||||
teamId: string;
|
||||
createPublicChannelPermission: string[];
|
||||
createPrivateChannelPermission: string[];
|
||||
}
|
||||
|
||||
interface ISwitch extends SwitchProps {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreateChannelViewState> {
|
||||
private teamId: string;
|
||||
|
||||
constructor(props: ICreateChannelViewProps) {
|
||||
super(props);
|
||||
const { route } = this.props;
|
||||
const isTeam = route?.params?.isTeam || false;
|
||||
|
@ -110,7 +141,7 @@ class CreateChannelView extends React.Component {
|
|||
this.handleHasPermission();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps: ICreateChannelViewProps, nextState: ICreateChannelViewState) {
|
||||
const { channelName, type, readOnly, broadcast, encrypted, permissions } = this.state;
|
||||
const { users, isFetching, encryptionEnabled, theme, createPublicChannelPermission, createPrivateChannelPermission } =
|
||||
this.props;
|
||||
|
@ -153,7 +184,7 @@ class CreateChannelView extends React.Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: ICreateChannelViewProps) {
|
||||
const { createPublicChannelPermission, createPrivateChannelPermission } = this.props;
|
||||
if (
|
||||
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
|
||||
|
@ -172,7 +203,7 @@ class CreateChannelView extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
toggleRightButton = channelName => {
|
||||
toggleRightButton = (channelName: string) => {
|
||||
const { navigation } = this.props;
|
||||
navigation.setOptions({
|
||||
headerRight: () =>
|
||||
|
@ -184,7 +215,7 @@ class CreateChannelView extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
onChangeText = channelName => {
|
||||
onChangeText = (channelName: string) => {
|
||||
this.toggleRightButton(channelName);
|
||||
this.setState({ channelName });
|
||||
};
|
||||
|
@ -215,13 +246,13 @@ class CreateChannelView extends React.Component {
|
|||
Review.pushPositiveEvent();
|
||||
};
|
||||
|
||||
removeUser = user => {
|
||||
removeUser = (user: IOtherUser) => {
|
||||
logEvent(events.CR_REMOVE_USER);
|
||||
const { removeUser } = this.props;
|
||||
removeUser(user);
|
||||
};
|
||||
|
||||
renderSwitch = ({ id, value, label, onValueChange, disabled = false }) => {
|
||||
renderSwitch = ({ id, value, label, onValueChange, disabled = false }: ISwitch) => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<View style={[styles.switchContainer, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
|
@ -253,7 +284,7 @@ class CreateChannelView extends React.Component {
|
|||
value: permissions[1] ? type : false,
|
||||
disabled: isDisabled,
|
||||
label: isTeam ? 'Private_Team' : 'Private_Channel',
|
||||
onValueChange: value => {
|
||||
onValueChange: (value: boolean) => {
|
||||
logEvent(events.CR_TOGGLE_TYPE);
|
||||
// If we set the channel as public, encrypted status should be false
|
||||
this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted }));
|
||||
|
@ -313,8 +344,8 @@ class CreateChannelView extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
const { baseUrl, user, theme } = this.props;
|
||||
renderItem = ({ item }: { item: IOtherUser }) => {
|
||||
const { theme } = this.props;
|
||||
|
||||
return (
|
||||
<UserItem
|
||||
|
@ -323,8 +354,6 @@ class CreateChannelView extends React.Component {
|
|||
onPress={() => this.removeUser(item)}
|
||||
testID={`create-channel-view-item-${item.name}`}
|
||||
icon='check'
|
||||
baseUrl={baseUrl}
|
||||
user={user}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
|
@ -348,7 +377,6 @@ class CreateChannelView extends React.Component {
|
|||
]}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
enableEmptySections
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
);
|
||||
|
@ -371,7 +399,6 @@ class CreateChannelView extends React.Component {
|
|||
<TextInput
|
||||
autoFocus
|
||||
style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]}
|
||||
label={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
|
||||
value={channelName}
|
||||
onChangeText={this.onChangeText}
|
||||
placeholder={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
|
||||
|
@ -406,7 +433,7 @@ class CreateChannelView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
baseUrl: state.server.server,
|
||||
isFetching: state.createChannel.isFetching,
|
||||
encryptionEnabled: state.encryption.enabled,
|
||||
|
@ -416,9 +443,9 @@ const mapStateToProps = state => ({
|
|||
createPrivateChannelPermission: state.permissions['create-p']
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
create: data => dispatch(createChannelRequestAction(data)),
|
||||
removeUser: user => dispatch(removeUserAction(user))
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
create: (data: ICreateFunction) => dispatch(createChannelRequestAction(data)),
|
||||
removeUser: (user: IOtherUser) => dispatch(removeUserAction(user))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(CreateChannelView));
|
|
@ -150,14 +150,12 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, any> {
|
|||
const { name, users, encrypted } = this.state;
|
||||
const { server, user, loading, blockUnauthenticatedAccess, theme, serverVersion } = this.props;
|
||||
return (
|
||||
// @ts-ignore
|
||||
<KeyboardView
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
contentContainerStyle={styles.container}
|
||||
keyboardVerticalOffset={128}>
|
||||
<StatusBar />
|
||||
<SafeAreaView testID='create-discussion-view' style={styles.container}>
|
||||
{/* @ts-ignore*/}
|
||||
<ScrollView {...scrollPersistTaps}>
|
||||
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Discussion_Desc')}</Text>
|
||||
<SelectChannel
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { FlatList, Linking } from 'react-native';
|
||||
|
||||
import I18n from '../i18n';
|
||||
|
@ -13,7 +13,14 @@ import SafeAreaView from '../containers/SafeAreaView';
|
|||
import UserPreferences from '../lib/userPreferences';
|
||||
import { events, logEvent } from '../utils/log';
|
||||
|
||||
const DEFAULT_BROWSERS = [
|
||||
type TValue = 'inApp' | 'systemDefault:' | 'googlechrome:' | 'firefox:' | 'brave:';
|
||||
|
||||
interface IBrowsersValues {
|
||||
title: string;
|
||||
value: TValue;
|
||||
}
|
||||
|
||||
const DEFAULT_BROWSERS: IBrowsersValues[] = [
|
||||
{
|
||||
title: 'In_app',
|
||||
value: 'inApp'
|
||||
|
@ -24,7 +31,7 @@ const DEFAULT_BROWSERS = [
|
|||
}
|
||||
];
|
||||
|
||||
const BROWSERS = [
|
||||
const BROWSERS: IBrowsersValues[] = [
|
||||
{
|
||||
title: 'Chrome',
|
||||
value: 'googlechrome:'
|
||||
|
@ -39,16 +46,23 @@ const BROWSERS = [
|
|||
}
|
||||
];
|
||||
|
||||
class DefaultBrowserView extends React.Component {
|
||||
static navigationOptions = () => ({
|
||||
interface IDefaultBrowserViewState {
|
||||
browser: any;
|
||||
supported: any[];
|
||||
}
|
||||
|
||||
interface IDefaultBrowserViewProps {
|
||||
theme: string;
|
||||
}
|
||||
|
||||
class DefaultBrowserView extends React.Component<IDefaultBrowserViewProps, IDefaultBrowserViewState> {
|
||||
private mounted?: boolean;
|
||||
|
||||
static navigationOptions = (): StackNavigationOptions => ({
|
||||
title: I18n.t('Default_browser')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IDefaultBrowserViewProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
browser: null,
|
||||
|
@ -74,6 +88,7 @@ class DefaultBrowserView extends React.Component {
|
|||
this.setState(({ supported }) => ({ supported: [...supported, browser] }));
|
||||
} else {
|
||||
const { supported } = this.state;
|
||||
// @ts-ignore
|
||||
this.state.supported = [...supported, browser];
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +96,7 @@ class DefaultBrowserView extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
isSelected = value => {
|
||||
isSelected = (value: TValue) => {
|
||||
const { browser } = this.state;
|
||||
if (!browser && value === 'systemDefault:') {
|
||||
return true;
|
||||
|
@ -89,7 +104,7 @@ class DefaultBrowserView extends React.Component {
|
|||
return browser === value;
|
||||
};
|
||||
|
||||
changeDefaultBrowser = async newBrowser => {
|
||||
changeDefaultBrowser = async (newBrowser: TValue) => {
|
||||
logEvent(events.DB_CHANGE_DEFAULT_BROWSER, { browser: newBrowser });
|
||||
try {
|
||||
const browser = newBrowser !== 'systemDefault:' ? newBrowser : null;
|
||||
|
@ -105,7 +120,7 @@ class DefaultBrowserView extends React.Component {
|
|||
return <List.Icon name='check' color={themes[theme].tintColor} />;
|
||||
};
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
renderItem = ({ item }: { item: IBrowsersValues }) => {
|
||||
const { title, value } = item;
|
||||
return (
|
||||
<List.Item
|
|
@ -21,19 +21,19 @@ const DisplayPrefsView = props => {
|
|||
const { theme } = useTheme();
|
||||
|
||||
const { sortBy, groupByType, showFavorites, showUnread, showAvatar, displayMode } = useSelector(state => state.sortPreferences);
|
||||
const { isMasterDetail } = useSelector(state => state.app);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
const { navigation, isMasterDetail } = props;
|
||||
const { navigation } = props;
|
||||
navigation.setOptions({
|
||||
title: I18n.t('Display'),
|
||||
headerLeft: () =>
|
||||
isMasterDetail ? (
|
||||
<HeaderButton.CloseModal navigation={navigation} testID='display-view-close' />
|
||||
) : (
|
||||
<HeaderButton.Drawer navigation={navigation} testID='display-view-drawer' />
|
||||
)
|
||||
title: I18n.t('Display')
|
||||
});
|
||||
if (!isMasterDetail) {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => <HeaderButton.Drawer navigation={navigation} testID='display-view-drawer' />
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setSortPreference = async param => {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet, Text, View, TextInput as TextInputComp } from 'react-native';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import * as List from '../containers/List';
|
||||
|
@ -41,20 +42,41 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class E2EEncryptionSecurityView extends React.Component {
|
||||
interface IE2EEncryptionSecurityViewState {
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
interface IE2EEncryptionSecurityViewProps {
|
||||
theme: string;
|
||||
user: {
|
||||
roles: string[];
|
||||
id: string;
|
||||
};
|
||||
server: string;
|
||||
encryptionEnabled: boolean;
|
||||
logout(): void;
|
||||
}
|
||||
|
||||
class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityViewProps, IE2EEncryptionSecurityViewState> {
|
||||
private newPasswordInputRef: any = React.createRef();
|
||||
|
||||
static navigationOptions = (): StackNavigationOptions => ({
|
||||
title: I18n.t('E2E_Encryption')
|
||||
});
|
||||
|
||||
state = { newPassword: '' };
|
||||
|
||||
newPasswordInputRef = React.createRef();
|
||||
onChangePasswordText = debounce((text: string) => this.setState({ newPassword: text }), 300);
|
||||
|
||||
onChangePasswordText = debounce(text => this.setState({ newPassword: text }), 300);
|
||||
|
||||
setNewPasswordRef = ref => (this.newPasswordInputRef = ref);
|
||||
setNewPasswordRef = (ref: TextInputComp) => (this.newPasswordInputRef = ref);
|
||||
|
||||
changePassword = () => {
|
||||
const { newPassword } = this.state;
|
||||
if (!newPassword.trim()) {
|
||||
return;
|
||||
}
|
||||
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
|
||||
// @ts-ignore
|
||||
showConfirmationAlert({
|
||||
title: I18n.t('Are_you_sure_question_mark'),
|
||||
message: I18n.t('E2E_encryption_change_password_message'),
|
||||
|
@ -76,6 +98,8 @@ class E2EEncryptionSecurityView extends React.Component {
|
|||
};
|
||||
|
||||
resetOwnKey = () => {
|
||||
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
|
||||
// @ts-ignore
|
||||
showConfirmationAlert({
|
||||
title: I18n.t('Are_you_sure_question_mark'),
|
||||
message: I18n.t('E2E_encryption_reset_message'),
|
||||
|
@ -170,29 +194,14 @@ class E2EEncryptionSecurityView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
server: state.server.server,
|
||||
user: getUserSelector(state),
|
||||
encryptionEnabled: state.encryption.enabled
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
logout: () => dispatch(logoutAction(true))
|
||||
});
|
||||
|
||||
E2EEncryptionSecurityView.navigationOptions = () => ({
|
||||
title: I18n.t('E2E_Encryption')
|
||||
});
|
||||
|
||||
E2EEncryptionSecurityView.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
roles: PropTypes.array,
|
||||
id: PropTypes.string
|
||||
}),
|
||||
server: PropTypes.string,
|
||||
encryptionEnabled: PropTypes.bool,
|
||||
logout: PropTypes.func
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(E2EEncryptionSecurityView));
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScrollView, StyleSheet, Text } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import { withTheme } from '../theme';
|
||||
|
@ -27,18 +28,26 @@ const styles = StyleSheet.create({
|
|||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
||||
class E2EEnterYourPasswordView extends React.Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
|
||||
interface IE2EEnterYourPasswordViewState {
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface IE2EEnterYourPasswordViewProps {
|
||||
encryptionDecodeKey: (password: string) => void;
|
||||
theme: string;
|
||||
navigation: StackNavigationProp<any, 'E2EEnterYourPasswordView'>;
|
||||
}
|
||||
|
||||
class E2EEnterYourPasswordView extends React.Component<IE2EEnterYourPasswordViewProps, IE2EEnterYourPasswordViewState> {
|
||||
private passwordInput?: TextInput;
|
||||
|
||||
static navigationOptions = ({ navigation }: Pick<IE2EEnterYourPasswordViewProps, 'navigation'>): StackNavigationOptions => ({
|
||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='e2e-enter-your-password-view-close' />,
|
||||
title: I18n.t('Enter_Your_E2E_Password')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
encryptionDecodeKey: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IE2EEnterYourPasswordViewProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
password: ''
|
||||
|
@ -65,12 +74,12 @@ class E2EEnterYourPasswordView extends React.Component {
|
|||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
style={sharedStyles.container}
|
||||
contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
|
||||
contentContainerStyle={sharedStyles.containerScrollView}>
|
||||
<SafeAreaView
|
||||
style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}
|
||||
testID='e2e-enter-your-password-view'>
|
||||
<TextInput
|
||||
inputRef={e => {
|
||||
inputRef={(e: TextInput) => {
|
||||
this.passwordInput = e;
|
||||
}}
|
||||
placeholder={I18n.t('Password')}
|
||||
|
@ -99,7 +108,7 @@ class E2EEnterYourPasswordView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
encryptionDecodeKey: password => dispatch(encryptionDecodeKeyAction(password))
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
encryptionDecodeKey: (password: string) => dispatch(encryptionDecodeKeyAction(password))
|
||||
});
|
||||
export default connect(null, mapDispatchToProps)(withTheme(E2EEnterYourPasswordView));
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
|
@ -21,8 +22,17 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class E2EHowItWorksView extends React.Component {
|
||||
static navigationOptions = ({ route, navigation }) => {
|
||||
interface INavigation {
|
||||
navigation: StackNavigationProp<any, 'E2EHowItWorksView'>;
|
||||
route: RouteProp<{ E2EHowItWorksView: { showCloseModal: boolean } }, 'E2EHowItWorksView'>;
|
||||
}
|
||||
|
||||
interface IE2EHowItWorksViewProps extends INavigation {
|
||||
theme: string;
|
||||
}
|
||||
|
||||
class E2EHowItWorksView extends React.Component<IE2EHowItWorksViewProps, any> {
|
||||
static navigationOptions = ({ route, navigation }: INavigation) => {
|
||||
const showCloseModal = route.params?.showCloseModal;
|
||||
return {
|
||||
title: I18n.t('How_It_Works'),
|
||||
|
@ -30,20 +40,21 @@ class E2EHowItWorksView extends React.Component {
|
|||
};
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
render() {
|
||||
const { theme } = this.props;
|
||||
|
||||
const infoStyle = [styles.info, { color: themes[theme].bodyText }];
|
||||
|
||||
// TODO: Refactor when migrate Markdown
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} testID='e2e-how-it-works-view'>
|
||||
{/* @ts-ignore */}
|
||||
<Markdown msg={I18n.t('E2E_How_It_Works_info1')} style={infoStyle} theme={theme} />
|
||||
{/* @ts-ignore */}
|
||||
<Markdown msg={I18n.t('E2E_How_It_Works_info2')} style={infoStyle} theme={theme} />
|
||||
{/* @ts-ignore */}
|
||||
<Markdown msg={I18n.t('E2E_How_It_Works_info3')} style={infoStyle} theme={theme} />
|
||||
{/* @ts-ignore */}
|
||||
<Markdown msg={I18n.t('E2E_How_It_Works_info4')} style={infoStyle} theme={theme} />
|
||||
</SafeAreaView>
|
||||
);
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { Dispatch } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Clipboard, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
|
@ -53,20 +54,26 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class E2ESaveYourPasswordView extends React.Component {
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
interface IE2ESaveYourPasswordViewState {
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface IE2ESaveYourPasswordViewProps {
|
||||
server: string;
|
||||
navigation: StackNavigationProp<any, 'E2ESaveYourPasswordView'>;
|
||||
encryptionSetBanner(): void;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
class E2ESaveYourPasswordView extends React.Component<IE2ESaveYourPasswordViewProps, IE2ESaveYourPasswordViewState> {
|
||||
private mounted: boolean;
|
||||
|
||||
static navigationOptions = ({ navigation }: Pick<IE2ESaveYourPasswordViewProps, 'navigation'>) => ({
|
||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='e2e-save-your-password-view-close' />,
|
||||
title: I18n.t('Save_Your_E2E_Password')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
server: PropTypes.string,
|
||||
navigation: PropTypes.object,
|
||||
encryptionSetBanner: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IE2ESaveYourPasswordViewProps) {
|
||||
super(props);
|
||||
this.mounted = false;
|
||||
this.state = { password: '' };
|
||||
|
@ -83,8 +90,9 @@ class E2ESaveYourPasswordView extends React.Component {
|
|||
// Set stored password on local state
|
||||
const password = await UserPreferences.getStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`);
|
||||
if (this.mounted) {
|
||||
this.setState({ password });
|
||||
this.setState({ password: password! });
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.state.password = password;
|
||||
}
|
||||
} catch {
|
||||
|
@ -164,10 +172,10 @@ class E2ESaveYourPasswordView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
server: state.server.server
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
encryptionSetBanner: () => dispatch(encryptionSetBannerAction())
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(E2ESaveYourPasswordView));
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
|
||||
import TextInput from '../containers/TextInput';
|
||||
import Button from '../containers/Button';
|
||||
|
@ -14,23 +15,30 @@ import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
|||
import { events, logEvent } from '../utils/log';
|
||||
import sharedStyles from './Styles';
|
||||
|
||||
class ForgotPasswordView extends React.Component {
|
||||
static navigationOptions = ({ route }) => ({
|
||||
interface IForgotPasswordViewState {
|
||||
email: string;
|
||||
invalidEmail: boolean;
|
||||
isFetching: boolean;
|
||||
}
|
||||
|
||||
interface IForgotPasswordViewProps {
|
||||
navigation: StackNavigationProp<any, 'ForgotPasswordView'>;
|
||||
route: RouteProp<{ ForgotPasswordView: { title: string } }, 'ForgotPasswordView'>;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
class ForgotPasswordView extends React.Component<IForgotPasswordViewProps, IForgotPasswordViewState> {
|
||||
static navigationOptions = ({ route }: Pick<IForgotPasswordViewProps, 'route'>) => ({
|
||||
title: route.params?.title ?? 'Rocket.Chat'
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
state = {
|
||||
email: '',
|
||||
invalidEmail: true,
|
||||
isFetching: false
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps: IForgotPasswordViewProps, nextState: IForgotPasswordViewState) {
|
||||
const { email, invalidEmail, isFetching } = this.state;
|
||||
const { theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
|
@ -48,7 +56,7 @@ class ForgotPasswordView extends React.Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
validate = email => {
|
||||
validate = (email: string) => {
|
||||
if (!isValidEmail(email)) {
|
||||
this.setState({ invalidEmail: true });
|
||||
return;
|
||||
|
@ -70,7 +78,7 @@ class ForgotPasswordView extends React.Component {
|
|||
navigation.pop();
|
||||
showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert'));
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
logEvent(events.FP_FORGOT_PASSWORD_F);
|
||||
const msg = (e.data && e.data.error) || I18n.t('There_was_an_error_while_action', { action: I18n.t('resetting_password') });
|
||||
showErrorAlert(msg, I18n.t('Alert'));
|
|
@ -1,7 +1,10 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { Dispatch } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import { withTheme } from '../theme';
|
||||
|
@ -10,6 +13,7 @@ import RocketChat from '../lib/rocketchat';
|
|||
import OrSeparator from '../containers/OrSeparator';
|
||||
import Input from '../containers/UIKit/MultiSelect/Input';
|
||||
import { forwardRoom as forwardRoomAction } from '../actions/room';
|
||||
import { ILivechatDepartment } from './definition/ILivechatDepartment';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -18,12 +22,43 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
||||
const [departments, setDepartments] = useState([]);
|
||||
const [departmentId, setDepartment] = useState();
|
||||
const [users, setUsers] = useState([]);
|
||||
// TODO: Refactor when migrate room
|
||||
interface IRoom {
|
||||
departmentId?: any;
|
||||
servedBy?: {
|
||||
_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITransferData {
|
||||
roomId: string;
|
||||
userId?: string;
|
||||
departmentId?: string;
|
||||
}
|
||||
|
||||
interface IUser {
|
||||
username: string;
|
||||
_id: string;
|
||||
}
|
||||
|
||||
interface IParsedData {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface IForwardLivechatViewProps {
|
||||
navigation: StackNavigationProp<any, 'ForwardLivechatView'>;
|
||||
route: RouteProp<{ ForwardLivechatView: { rid: string } }, 'ForwardLivechatView'>;
|
||||
theme: string;
|
||||
forwardRoom: (rid: string, transferData: ITransferData) => void;
|
||||
}
|
||||
|
||||
const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForwardLivechatViewProps) => {
|
||||
const [departments, setDepartments] = useState<IParsedData[]>([]);
|
||||
const [departmentId, setDepartment] = useState('');
|
||||
const [users, setUsers] = useState<IParsedData[]>([]);
|
||||
const [userId, setUser] = useState();
|
||||
const [room, setRoom] = useState();
|
||||
const [room, setRoom] = useState<IRoom>({});
|
||||
|
||||
const rid = route.params?.rid;
|
||||
|
||||
|
@ -31,7 +66,9 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
|||
try {
|
||||
const result = await RocketChat.getDepartments();
|
||||
if (result.success) {
|
||||
setDepartments(result.departments.map(department => ({ label: department.name, value: department._id })));
|
||||
setDepartments(
|
||||
result.departments.map((department: ILivechatDepartment) => ({ label: department.name, value: department._id }))
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
|
@ -47,7 +84,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
|||
term
|
||||
});
|
||||
if (result.success) {
|
||||
const parsedUsers = result.items.map(user => ({ label: user.username, value: user._id }));
|
||||
const parsedUsers = result.items.map((user: IUser) => ({ label: user.username, value: user._id }));
|
||||
setUsers(parsedUsers);
|
||||
return parsedUsers;
|
||||
}
|
||||
|
@ -69,7 +106,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
|||
};
|
||||
|
||||
const submit = () => {
|
||||
const transferData = { roomId: rid };
|
||||
const transferData: ITransferData = { roomId: rid };
|
||||
|
||||
if (!departmentId && !userId) {
|
||||
return;
|
||||
|
@ -85,11 +122,14 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: I18n.t('Forward_Chat')
|
||||
});
|
||||
getRoom();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (room) {
|
||||
if (!isEmpty(room)) {
|
||||
getUsers();
|
||||
getDepartments();
|
||||
}
|
||||
|
@ -129,18 +169,9 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
|||
</View>
|
||||
);
|
||||
};
|
||||
ForwardLivechatView.propTypes = {
|
||||
forwardRoom: PropTypes.func,
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
ForwardLivechatView.navigationOptions = {
|
||||
title: I18n.t('Forward_Chat')
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
forwardRoom: (rid, transferData) => dispatch(forwardRoomAction(rid, transferData))
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
forwardRoom: (rid: string, transferData: ITransferData) => dispatch(forwardRoomAction(rid, transferData))
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(withTheme(ForwardLivechatView));
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View } from 'react-native';
|
||||
import { TextInputProps, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
inviteLinksCreate as inviteLinksCreateAction,
|
||||
|
@ -65,25 +67,29 @@ const OPTIONS = {
|
|||
]
|
||||
};
|
||||
|
||||
class InviteUsersView extends React.Component {
|
||||
static navigationOptions = () => ({
|
||||
interface IInviteUsersEditView {
|
||||
navigation: StackNavigationProp<any, 'InviteUsersEditView'>;
|
||||
route: RouteProp<{ InviteUsersEditView: { rid: string } }, 'InviteUsersEditView'>;
|
||||
theme: string;
|
||||
createInviteLink(rid: string): void;
|
||||
inviteLinksSetParams(params: { [key: string]: number }): void;
|
||||
days: number;
|
||||
maxUses: number;
|
||||
}
|
||||
|
||||
class InviteUsersView extends React.Component<IInviteUsersEditView, any> {
|
||||
static navigationOptions = (): StackNavigationOptions => ({
|
||||
title: I18n.t('Invite_users')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
createInviteLink: PropTypes.func,
|
||||
inviteLinksSetParams: PropTypes.func
|
||||
};
|
||||
private rid: string;
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IInviteUsersEditView) {
|
||||
super(props);
|
||||
this.rid = props.route.params?.rid;
|
||||
}
|
||||
|
||||
onValueChangePicker = (key, value) => {
|
||||
onValueChangePicker = (key: string, value: number) => {
|
||||
logEvent(events.IU_EDIT_SET_LINK_PARAM);
|
||||
const { inviteLinksSetParams } = this.props;
|
||||
const params = {
|
||||
|
@ -99,9 +105,10 @@ class InviteUsersView extends React.Component {
|
|||
navigation.pop();
|
||||
};
|
||||
|
||||
renderPicker = (key, first) => {
|
||||
renderPicker = (key: 'days' | 'maxUses', first: string) => {
|
||||
const { props } = this;
|
||||
const { theme } = props;
|
||||
const textInputStyle: TextInputProps = { style: { ...styles.pickerText, color: themes[theme].actionTintColor } };
|
||||
const firstEl = [
|
||||
{
|
||||
label: I18n.t(first),
|
||||
|
@ -112,7 +119,7 @@ class InviteUsersView extends React.Component {
|
|||
<RNPickerSelect
|
||||
style={{ viewContainer: styles.viewContainer }}
|
||||
value={props[key]}
|
||||
textInputProps={{ style: { ...styles.pickerText, color: themes[theme].actionTintColor } }}
|
||||
textInputProps={textInputStyle}
|
||||
useNativeAndroidPickerStyle={false}
|
||||
placeholder={{}}
|
||||
onValueChange={value => this.onValueChangePicker(key, value)}
|
||||
|
@ -143,14 +150,14 @@ class InviteUsersView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
days: state.inviteLinks.days,
|
||||
maxUses: state.inviteLinks.maxUses
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
inviteLinksSetParams: params => dispatch(inviteLinksSetParamsAction(params)),
|
||||
createInviteLink: rid => dispatch(inviteLinksCreateAction(rid))
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
inviteLinksSetParams: (params: object) => dispatch(inviteLinksSetParamsAction(params)),
|
||||
createInviteLink: (rid: string) => dispatch(inviteLinksCreateAction(rid))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(InviteUsersView));
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import JitsiMeet, { JitsiMeetView as RNJitsiMeetView } from 'react-native-jitsi-meet';
|
||||
import BackgroundTimer from 'react-native-background-timer';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -12,23 +13,36 @@ import { events, logEvent } from '../utils/log';
|
|||
import { isAndroid, isIOS } from '../utils/deviceInfo';
|
||||
import { withTheme } from '../theme';
|
||||
|
||||
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) =>
|
||||
const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLFragment: string) =>
|
||||
`${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`;
|
||||
class JitsiMeetView extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
token: PropTypes.string
|
||||
})
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
interface IJitsiMeetViewState {
|
||||
userInfo: {
|
||||
displayName: string;
|
||||
avatar: string;
|
||||
};
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface IJitsiMeetViewProps {
|
||||
navigation: StackNavigationProp<any, 'JitsiMeetView'>;
|
||||
route: RouteProp<{ JitsiMeetView: { rid: string; url: string; onlyAudio?: boolean } }, 'JitsiMeetView'>;
|
||||
baseUrl: string;
|
||||
theme: string;
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
token: string;
|
||||
};
|
||||
}
|
||||
|
||||
class JitsiMeetView extends React.Component<IJitsiMeetViewProps, IJitsiMeetViewState> {
|
||||
private rid: string;
|
||||
private url: string;
|
||||
private jitsiTimeout: number | null;
|
||||
|
||||
constructor(props: IJitsiMeetViewProps) {
|
||||
super(props);
|
||||
this.rid = props.route.params?.rid;
|
||||
this.url = props.route.params?.url;
|
||||
|
@ -81,15 +95,17 @@ class JitsiMeetView extends React.Component {
|
|||
// call is not ended and is available to web users.
|
||||
onConferenceJoined = () => {
|
||||
logEvent(events.JM_CONFERENCE_JOIN);
|
||||
RocketChat.updateJitsiTimeout(this.rid).catch(e => console.log(e));
|
||||
if (this.jitsiTimeout) {
|
||||
BackgroundTimer.clearInterval(this.jitsiTimeout);
|
||||
BackgroundTimer.stopBackgroundTimer();
|
||||
this.jitsiTimeout = null;
|
||||
if (this.rid) {
|
||||
RocketChat.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e));
|
||||
if (this.jitsiTimeout) {
|
||||
BackgroundTimer.clearInterval(this.jitsiTimeout);
|
||||
BackgroundTimer.stopBackgroundTimer();
|
||||
this.jitsiTimeout = null;
|
||||
}
|
||||
this.jitsiTimeout = BackgroundTimer.setInterval(() => {
|
||||
RocketChat.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e));
|
||||
}, 10000);
|
||||
}
|
||||
this.jitsiTimeout = BackgroundTimer.setInterval(() => {
|
||||
RocketChat.updateJitsiTimeout(this.rid).catch(e => console.log(e));
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
onConferenceTerminated = () => {
|
||||
|
@ -118,7 +134,7 @@ class JitsiMeetView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
user: getUserSelector(state),
|
||||
baseUrl: state.server.server
|
||||
});
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import RNRestart from 'react-native-restart';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import I18n, { LANGUAGES, isRTL } from '../../i18n';
|
||||
|
@ -18,26 +18,33 @@ import { getUserSelector } from '../../selectors/login';
|
|||
import database from '../../lib/database';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
class LanguageView extends React.Component {
|
||||
interface ILanguageViewProps {
|
||||
user: {
|
||||
id: string;
|
||||
language: string;
|
||||
};
|
||||
setUser(user: object): void;
|
||||
appStart(params: any): void;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
interface ILanguageViewState {
|
||||
language: string;
|
||||
}
|
||||
|
||||
class LanguageView extends React.Component<ILanguageViewProps, ILanguageViewState> {
|
||||
static navigationOptions = () => ({
|
||||
title: I18n.t('Change_Language')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
user: PropTypes.object,
|
||||
setUser: PropTypes.func,
|
||||
appStart: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: ILanguageViewProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
language: props.user ? props.user.language : 'en'
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps: ILanguageViewProps, nextState: ILanguageViewState) {
|
||||
const { language } = this.state;
|
||||
const { user, theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
|
@ -52,12 +59,12 @@ class LanguageView extends React.Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
formIsChanged = language => {
|
||||
formIsChanged = (language: string) => {
|
||||
const { user } = this.props;
|
||||
return user.language !== language;
|
||||
};
|
||||
|
||||
submit = async language => {
|
||||
submit = async (language: string) => {
|
||||
if (!this.formIsChanged(language)) {
|
||||
return;
|
||||
}
|
||||
|
@ -78,11 +85,11 @@ class LanguageView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
changeLanguage = async language => {
|
||||
changeLanguage = async (language: string) => {
|
||||
logEvent(events.LANG_SET_LANGUAGE);
|
||||
const { user, setUser } = this.props;
|
||||
|
||||
const params = {};
|
||||
const params: { language?: string } = {};
|
||||
|
||||
// language
|
||||
if (user.language !== language) {
|
||||
|
@ -95,10 +102,10 @@ class LanguageView extends React.Component {
|
|||
|
||||
const serversDB = database.servers;
|
||||
const usersCollection = serversDB.get('users');
|
||||
await serversDB.action(async () => {
|
||||
await serversDB.write(async () => {
|
||||
try {
|
||||
const userRecord = await usersCollection.find(user.id);
|
||||
await userRecord.update(record => {
|
||||
await userRecord.update((record: any) => {
|
||||
record.language = params.language;
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -117,7 +124,7 @@ class LanguageView extends React.Component {
|
|||
return <List.Icon name='check' color={themes[theme].tintColor} />;
|
||||
};
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
renderItem = ({ item }: { item: { value: string; label: string } }) => {
|
||||
const { value, label } = item;
|
||||
const { language } = this.state;
|
||||
const isSelected = language === value;
|
||||
|
@ -151,13 +158,13 @@ class LanguageView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
user: getUserSelector(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setUser: params => dispatch(setUserAction(params)),
|
||||
appStart: params => dispatch(appStartAction(params))
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
setUser: (params: any) => dispatch(setUserAction(params)),
|
||||
appStart: (params: any) => dispatch(appStartAction(params))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LanguageView));
|
|
@ -1,22 +1,26 @@
|
|||
import React from 'react';
|
||||
import { ScrollView } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
import { themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
|
||||
class MarkdownTableView extends React.Component {
|
||||
static navigationOptions = () => ({
|
||||
interface IMarkdownTableViewProps {
|
||||
route: RouteProp<
|
||||
{ MarkdownTableView: { renderRows: (drawExtraBorders?: boolean) => JSX.Element; tableWidth: number } },
|
||||
'MarkdownTableView'
|
||||
>;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
class MarkdownTableView extends React.Component<IMarkdownTableViewProps> {
|
||||
static navigationOptions = (): StackNavigationOptions => ({
|
||||
title: I18n.t('Table')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
render() {
|
||||
const { route, theme } = this.props;
|
||||
const renderRows = route.params?.renderRows;
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { dequal } from 'dequal';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
|
||||
import Message from '../../containers/message';
|
||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||
|
@ -18,20 +19,67 @@ import SafeAreaView from '../../containers/SafeAreaView';
|
|||
import getThreadName from '../../lib/methods/getThreadName';
|
||||
import styles from './styles';
|
||||
|
||||
class MessagesView extends React.Component {
|
||||
static propTypes = {
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
customEmojis: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
showActionSheet: PropTypes.func,
|
||||
useRealName: PropTypes.bool,
|
||||
isMasterDetail: PropTypes.bool
|
||||
type TMessagesViewRouteParams = {
|
||||
MessagesView: {
|
||||
rid: string;
|
||||
t: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
interface IMessagesViewProps {
|
||||
user: {
|
||||
id: string;
|
||||
};
|
||||
baseUrl: string;
|
||||
navigation: StackNavigationProp<any, 'MessagesView'>;
|
||||
route: RouteProp<TMessagesViewRouteParams, 'MessagesView'>;
|
||||
customEmojis: { [key: string]: string };
|
||||
theme: string;
|
||||
showActionSheet: Function;
|
||||
useRealName: boolean;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
interface IMessagesViewState {
|
||||
loading: boolean;
|
||||
messages: [];
|
||||
fileLoading: boolean;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface IMessageItem {
|
||||
u?: string;
|
||||
user?: string;
|
||||
editedAt?: Date;
|
||||
attachments?: any;
|
||||
_id: string;
|
||||
tmid?: string;
|
||||
ts?: Date;
|
||||
uploadedAt?: Date;
|
||||
name?: string;
|
||||
description?: string;
|
||||
msg?: string;
|
||||
starred: string;
|
||||
pinned: boolean;
|
||||
}
|
||||
|
||||
interface IParams {
|
||||
rid?: string;
|
||||
jumpToMessageId: string;
|
||||
t?: string;
|
||||
room: any;
|
||||
tmid?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
class MessagesView extends React.Component<IMessagesViewProps, any> {
|
||||
private rid?: string;
|
||||
private t?: string;
|
||||
private content: any;
|
||||
private room: any;
|
||||
|
||||
constructor(props: IMessagesViewProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false,
|
||||
|
@ -48,7 +96,7 @@ class MessagesView extends React.Component {
|
|||
this.load();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps: IMessagesViewProps, nextState: any) {
|
||||
const { loading, messages, fileLoading } = this.state;
|
||||
const { theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
|
@ -73,7 +121,7 @@ class MessagesView extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
navToRoomInfo = navParam => {
|
||||
navToRoomInfo = (navParam: { rid: string }) => {
|
||||
const { navigation, user } = this.props;
|
||||
if (navParam.rid === user.id) {
|
||||
return;
|
||||
|
@ -81,9 +129,9 @@ class MessagesView extends React.Component {
|
|||
navigation.navigate('RoomInfoView', navParam);
|
||||
};
|
||||
|
||||
jumpToMessage = async ({ item }) => {
|
||||
jumpToMessage = async ({ item }: { item: IMessageItem }) => {
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
let params = {
|
||||
let params: IParams = {
|
||||
rid: this.rid,
|
||||
jumpToMessageId: item._id,
|
||||
t: this.t,
|
||||
|
@ -107,9 +155,9 @@ class MessagesView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
defineMessagesViewContent = name => {
|
||||
defineMessagesViewContent = (name: string) => {
|
||||
const { user, baseUrl, theme, useRealName } = this.props;
|
||||
const renderItemCommonProps = item => ({
|
||||
const renderItemCommonProps = (item: IMessageItem) => ({
|
||||
item,
|
||||
baseUrl,
|
||||
user,
|
||||
|
@ -137,7 +185,7 @@ class MessagesView extends React.Component {
|
|||
},
|
||||
noDataMsg: I18n.t('No_files'),
|
||||
testID: 'room-files-view',
|
||||
renderItem: item => (
|
||||
renderItem: (item: IMessageItem) => (
|
||||
<Message
|
||||
{...renderItemCommonProps(item)}
|
||||
item={{
|
||||
|
@ -165,7 +213,7 @@ class MessagesView extends React.Component {
|
|||
},
|
||||
noDataMsg: I18n.t('No_mentioned_messages'),
|
||||
testID: 'mentioned-messages-view',
|
||||
renderItem: item => <Message {...renderItemCommonProps(item)} msg={item.msg} theme={theme} />
|
||||
renderItem: (item: IMessageItem) => <Message {...renderItemCommonProps(item)} msg={item.msg} theme={theme} />
|
||||
},
|
||||
// Starred Messages Screen
|
||||
Starred: {
|
||||
|
@ -176,15 +224,15 @@ class MessagesView extends React.Component {
|
|||
},
|
||||
noDataMsg: I18n.t('No_starred_messages'),
|
||||
testID: 'starred-messages-view',
|
||||
renderItem: item => (
|
||||
renderItem: (item: IMessageItem) => (
|
||||
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
||||
),
|
||||
action: message => ({
|
||||
action: (message: IMessageItem) => ({
|
||||
title: I18n.t('Unstar'),
|
||||
icon: message.starred ? 'star-filled' : 'star',
|
||||
onPress: this.handleActionPress
|
||||
}),
|
||||
handleActionPress: message => RocketChat.toggleStarMessage(message._id, message.starred)
|
||||
handleActionPress: (message: IMessageItem) => RocketChat.toggleStarMessage(message._id, message.starred)
|
||||
},
|
||||
// Pinned Messages Screen
|
||||
Pinned: {
|
||||
|
@ -195,12 +243,13 @@ class MessagesView extends React.Component {
|
|||
},
|
||||
noDataMsg: I18n.t('No_pinned_messages'),
|
||||
testID: 'pinned-messages-view',
|
||||
renderItem: item => (
|
||||
renderItem: (item: IMessageItem) => (
|
||||
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
||||
),
|
||||
action: () => ({ title: I18n.t('Unpin'), icon: 'pin', onPress: this.handleActionPress }),
|
||||
handleActionPress: message => RocketChat.togglePinMessage(message._id, message.pinned)
|
||||
handleActionPress: (message: IMessageItem) => RocketChat.togglePinMessage(message._id, message.pinned)
|
||||
}
|
||||
// @ts-ignore
|
||||
}[name];
|
||||
};
|
||||
|
||||
|
@ -227,7 +276,7 @@ class MessagesView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
getCustomEmoji = name => {
|
||||
getCustomEmoji = (name: string) => {
|
||||
const { customEmojis } = this.props;
|
||||
const emoji = customEmojis[name];
|
||||
if (emoji) {
|
||||
|
@ -236,12 +285,12 @@ class MessagesView extends React.Component {
|
|||
return null;
|
||||
};
|
||||
|
||||
showAttachment = attachment => {
|
||||
showAttachment = (attachment: any) => {
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('AttachmentView', { attachment });
|
||||
};
|
||||
|
||||
onLongPress = message => {
|
||||
onLongPress = (message: IMessageItem) => {
|
||||
this.setState({ message }, this.showActionSheet);
|
||||
};
|
||||
|
||||
|
@ -257,8 +306,8 @@ class MessagesView extends React.Component {
|
|||
try {
|
||||
const result = await this.content.handleActionPress(message);
|
||||
if (result.success) {
|
||||
this.setState(prevState => ({
|
||||
messages: prevState.messages.filter(item => item._id !== message._id),
|
||||
this.setState((prevState: IMessagesViewState) => ({
|
||||
messages: prevState.messages.filter((item: IMessageItem) => item._id !== message._id),
|
||||
total: prevState.total - 1
|
||||
}));
|
||||
}
|
||||
|
@ -267,7 +316,7 @@ class MessagesView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
setFileLoading = fileLoading => {
|
||||
setFileLoading = (fileLoading: boolean) => {
|
||||
this.setState({ fileLoading });
|
||||
};
|
||||
|
||||
|
@ -280,7 +329,7 @@ class MessagesView extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
renderItem = ({ item }) => this.content.renderItem(item);
|
||||
renderItem = ({ item }: { item: IMessageItem }) => this.content.renderItem(item);
|
||||
|
||||
render() {
|
||||
const { messages, loading } = this.state;
|
||||
|
@ -306,7 +355,7 @@ class MessagesView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state),
|
||||
customEmojis: state.customEmojis,
|
|
@ -1,6 +1,9 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Switch, Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import Model from '@nozbe/watermelondb/Model';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import database from '../../lib/database';
|
||||
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
||||
|
@ -22,18 +25,31 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class NotificationPreferencesView extends React.Component {
|
||||
interface INotificationPreferencesView {
|
||||
navigation: StackNavigationProp<any, 'NotificationPreferencesView'>;
|
||||
route: RouteProp<
|
||||
{
|
||||
NotificationPreferencesView: {
|
||||
rid: string;
|
||||
room: Model;
|
||||
};
|
||||
},
|
||||
'NotificationPreferencesView'
|
||||
>;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
class NotificationPreferencesView extends React.Component<INotificationPreferencesView, any> {
|
||||
static navigationOptions = () => ({
|
||||
title: I18n.t('Notification_Preferences')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
private mounted: boolean;
|
||||
private rid: string | undefined;
|
||||
private roomObservable?: Observable<Model>;
|
||||
private subscription?: Subscription;
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: INotificationPreferencesView) {
|
||||
super(props);
|
||||
this.mounted = false;
|
||||
this.rid = props.route.params?.rid;
|
||||
|
@ -43,10 +59,11 @@ class NotificationPreferencesView extends React.Component {
|
|||
};
|
||||
if (room && room.observe) {
|
||||
this.roomObservable = room.observe();
|
||||
this.subscription = this.roomObservable.subscribe(changes => {
|
||||
this.subscription = this.roomObservable.subscribe((changes: any) => {
|
||||
if (this.mounted) {
|
||||
this.setState({ room: changes });
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.state.room = changes;
|
||||
}
|
||||
});
|
||||
|
@ -63,7 +80,8 @@ class NotificationPreferencesView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
saveNotificationSettings = async (key, value, params) => {
|
||||
saveNotificationSettings = async (key: string, value: string | boolean, params: any) => {
|
||||
// @ts-ignore
|
||||
logEvent(events[`NP_${key.toUpperCase()}`]);
|
||||
const { room } = this.state;
|
||||
const db = database.active;
|
||||
|
@ -71,7 +89,7 @@ class NotificationPreferencesView extends React.Component {
|
|||
try {
|
||||
await db.action(async () => {
|
||||
await room.update(
|
||||
protectedFunction(r => {
|
||||
protectedFunction((r: any) => {
|
||||
r[key] = value;
|
||||
})
|
||||
);
|
||||
|
@ -88,36 +106,38 @@ class NotificationPreferencesView extends React.Component {
|
|||
|
||||
await db.action(async () => {
|
||||
await room.update(
|
||||
protectedFunction(r => {
|
||||
protectedFunction((r: any) => {
|
||||
r[key] = room[key];
|
||||
})
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
// @ts-ignore
|
||||
logEvent(events[`NP_${key.toUpperCase()}_F`]);
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
onValueChangeSwitch = (key, value) => this.saveNotificationSettings(key, value, { [key]: value ? '1' : '0' });
|
||||
onValueChangeSwitch = (key: string, value: string | boolean) =>
|
||||
this.saveNotificationSettings(key, value, { [key]: value ? '1' : '0' });
|
||||
|
||||
onValueChangePicker = (key, value) => this.saveNotificationSettings(key, value, { [key]: value.toString() });
|
||||
onValueChangePicker = (key: string, value: string) => this.saveNotificationSettings(key, value, { [key]: value.toString() });
|
||||
|
||||
pickerSelection = (title, key) => {
|
||||
pickerSelection = (title: string, key: string) => {
|
||||
const { room } = this.state;
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('PickerView', {
|
||||
title,
|
||||
data: OPTIONS[key],
|
||||
value: room[key],
|
||||
onChangeValue: value => this.onValueChangePicker(key, value)
|
||||
onChangeValue: (value: string) => this.onValueChangePicker(key, value)
|
||||
});
|
||||
};
|
||||
|
||||
renderPickerOption = key => {
|
||||
renderPickerOption = (key: string) => {
|
||||
const { room } = this.state;
|
||||
const { theme } = this.props;
|
||||
const text = room[key] ? OPTIONS[key].find(option => option.value === room[key]) : OPTIONS[key][0];
|
||||
const text = room[key] ? OPTIONS[key].find((option: any) => option.value === room[key]) : OPTIONS[key][0];
|
||||
return (
|
||||
<Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>
|
||||
{I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })}
|
||||
|
@ -125,7 +145,7 @@ class NotificationPreferencesView extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
renderSwitch = key => {
|
||||
renderSwitch = (key: string) => {
|
||||
const { room } = this.state;
|
||||
return (
|
||||
<Switch
|
||||
|
@ -181,7 +201,7 @@ class NotificationPreferencesView extends React.Component {
|
|||
<List.Item
|
||||
title='Alert'
|
||||
testID='notification-preference-view-alert'
|
||||
onPress={title => this.pickerSelection(title, 'desktopNotifications')}
|
||||
onPress={(title: string) => this.pickerSelection(title, 'desktopNotifications')}
|
||||
right={() => this.renderPickerOption('desktopNotifications')}
|
||||
/>
|
||||
<List.Separator />
|
||||
|
@ -193,7 +213,7 @@ class NotificationPreferencesView extends React.Component {
|
|||
<List.Item
|
||||
title='Alert'
|
||||
testID='notification-preference-view-push-notification'
|
||||
onPress={title => this.pickerSelection(title, 'mobilePushNotifications')}
|
||||
onPress={(title: string) => this.pickerSelection(title, 'mobilePushNotifications')}
|
||||
right={() => this.renderPickerOption('mobilePushNotifications')}
|
||||
/>
|
||||
<List.Separator />
|
||||
|
@ -205,21 +225,21 @@ class NotificationPreferencesView extends React.Component {
|
|||
<List.Item
|
||||
title='Audio'
|
||||
testID='notification-preference-view-audio'
|
||||
onPress={title => this.pickerSelection(title, 'audioNotifications')}
|
||||
onPress={(title: string) => this.pickerSelection(title, 'audioNotifications')}
|
||||
right={() => this.renderPickerOption('audioNotifications')}
|
||||
/>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Sound'
|
||||
testID='notification-preference-view-sound'
|
||||
onPress={title => this.pickerSelection(title, 'audioNotificationValue')}
|
||||
onPress={(title: string) => this.pickerSelection(title, 'audioNotificationValue')}
|
||||
right={() => this.renderPickerOption('audioNotificationValue')}
|
||||
/>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Notification_Duration'
|
||||
testID='notification-preference-view-notification-duration'
|
||||
onPress={title => this.pickerSelection(title, 'desktopNotificationDuration')}
|
||||
onPress={(title: string) => this.pickerSelection(title, 'desktopNotificationDuration')}
|
||||
right={() => this.renderPickerOption('desktopNotificationDuration')}
|
||||
/>
|
||||
<List.Separator />
|
||||
|
@ -230,7 +250,7 @@ class NotificationPreferencesView extends React.Component {
|
|||
<List.Item
|
||||
title='Alert'
|
||||
testID='notification-preference-view-email-alert'
|
||||
onPress={title => this.pickerSelection(title, 'emailNotifications')}
|
||||
onPress={(title: string) => this.pickerSelection(title, 'emailNotifications')}
|
||||
right={() => this.renderPickerOption('emailNotifications')}
|
||||
/>
|
||||
<List.Separator />
|
|
@ -1,4 +1,18 @@
|
|||
export const OPTIONS = {
|
||||
interface IOptionsField {
|
||||
label: string;
|
||||
value: string | number;
|
||||
second?: number;
|
||||
}
|
||||
export interface INotificationOptions {
|
||||
[desktopNotifications: string]: IOptionsField[];
|
||||
audioNotifications: IOptionsField[];
|
||||
mobilePushNotifications: IOptionsField[];
|
||||
emailNotifications: IOptionsField[];
|
||||
desktopNotificationDuration: IOptionsField[];
|
||||
audioNotificationValue: IOptionsField[];
|
||||
}
|
||||
|
||||
export const OPTIONS: INotificationOptions = {
|
||||
desktopNotifications: [
|
||||
{
|
||||
label: 'Default',
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import I18n from '../i18n';
|
||||
|
@ -24,7 +25,41 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const Item = React.memo(({ item, selected, onItemPress, theme }) => (
|
||||
interface IData {
|
||||
label: string;
|
||||
value: string;
|
||||
second?: string;
|
||||
}
|
||||
|
||||
interface IItem {
|
||||
item: IData;
|
||||
selected: boolean;
|
||||
onItemPress: () => void;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
interface IPickerViewState {
|
||||
data: IData[];
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface IParams {
|
||||
title: string;
|
||||
value: string;
|
||||
data: IData[];
|
||||
onChangeText: (value: string) => IData[];
|
||||
goBack: boolean;
|
||||
onChange: Function;
|
||||
onChangeValue: (value: string) => void;
|
||||
}
|
||||
|
||||
interface IPickerViewProps {
|
||||
navigation: StackNavigationProp<any, 'PickerView'>;
|
||||
route: RouteProp<{ PickerView: IParams }, 'PickerView'>;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
const Item = React.memo(({ item, selected, onItemPress, theme }: IItem) => (
|
||||
<List.Item
|
||||
title={I18n.t(item.label, { defaultValue: item.label, second: item?.second })}
|
||||
right={selected && (() => <List.Icon name='check' color={themes[theme].tintColor} />)}
|
||||
|
@ -32,25 +67,15 @@ const Item = React.memo(({ item, selected, onItemPress, theme }) => (
|
|||
translateTitle={false}
|
||||
/>
|
||||
));
|
||||
Item.propTypes = {
|
||||
item: PropTypes.object,
|
||||
selected: PropTypes.bool,
|
||||
onItemPress: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
class PickerView extends React.PureComponent {
|
||||
static navigationOptions = ({ route }) => ({
|
||||
class PickerView extends React.PureComponent<IPickerViewProps, IPickerViewState> {
|
||||
private onSearch: (text: string) => IData[];
|
||||
|
||||
static navigationOptions = ({ route }: IPickerViewProps) => ({
|
||||
title: route.params?.title ?? I18n.t('Select_an_option')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IPickerViewProps) {
|
||||
super(props);
|
||||
const data = props.route.params?.data ?? [];
|
||||
const value = props.route.params?.value;
|
||||
|
@ -59,7 +84,7 @@ class PickerView extends React.PureComponent {
|
|||
this.onSearch = props.route.params?.onChangeText;
|
||||
}
|
||||
|
||||
onChangeValue = value => {
|
||||
onChangeValue = (value: string) => {
|
||||
const { navigation, route } = this.props;
|
||||
const goBack = route.params?.goBack ?? true;
|
||||
const onChange = route.params?.onChangeValue ?? (() => {});
|
||||
|
@ -70,7 +95,7 @@ class PickerView extends React.PureComponent {
|
|||
};
|
||||
|
||||
onChangeText = debounce(
|
||||
async text => {
|
||||
async (text: string) => {
|
||||
if (this.onSearch) {
|
||||
const data = await this.onSearch(text);
|
||||
this.setState({ data });
|
|
@ -1,13 +1,13 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Keyboard, ScrollView, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import prompt from 'react-native-prompt-android';
|
||||
import SHA256 from 'js-sha256';
|
||||
import ImagePicker from 'react-native-image-crop-picker';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import ImagePicker, { Image } from 'react-native-image-crop-picker';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
import { dequal } from 'dequal';
|
||||
import omit from 'lodash/omit';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
import KeyboardView from '../../presentation/KeyboardView';
|
||||
|
@ -31,43 +31,40 @@ import { withTheme } from '../../theme';
|
|||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import styles from './styles';
|
||||
import { IAvatar, IAvatarButton, INavigationOptions, IParams, IProfileViewProps, IProfileViewState, IUser } from './interfaces';
|
||||
|
||||
class ProfileView extends React.Component {
|
||||
static navigationOptions = ({ navigation, isMasterDetail }) => {
|
||||
const options = {
|
||||
class ProfileView extends React.Component<IProfileViewProps, IProfileViewState> {
|
||||
private name: any;
|
||||
private username: any;
|
||||
private email: any;
|
||||
private avatarUrl: any;
|
||||
private newPassword: any;
|
||||
|
||||
static navigationOptions = ({ navigation, isMasterDetail }: INavigationOptions) => {
|
||||
const options: StackNavigationOptions = {
|
||||
title: I18n.t('Profile')
|
||||
};
|
||||
if (!isMasterDetail) {
|
||||
options.headerLeft = () => <HeaderButton.Drawer navigation={navigation} />;
|
||||
}
|
||||
options.headerRight = () => (
|
||||
<HeaderButton.Preferences onPress={() => navigation.navigate('UserPreferencesView')} testID='preferences-view-open' />
|
||||
<HeaderButton.Preferences onPress={() => navigation?.navigate('UserPreferencesView')} testID='preferences-view-open' />
|
||||
);
|
||||
return options;
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
Accounts_AllowEmailChange: PropTypes.bool,
|
||||
Accounts_AllowPasswordChange: PropTypes.bool,
|
||||
Accounts_AllowRealNameChange: PropTypes.bool,
|
||||
Accounts_AllowUserAvatarChange: PropTypes.bool,
|
||||
Accounts_AllowUsernameChange: PropTypes.bool,
|
||||
Accounts_CustomFields: PropTypes.string,
|
||||
setUser: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
state = {
|
||||
state: IProfileViewState = {
|
||||
saving: false,
|
||||
name: null,
|
||||
username: null,
|
||||
email: null,
|
||||
newPassword: null,
|
||||
currentPassword: null,
|
||||
avatarUrl: null,
|
||||
avatar: {},
|
||||
name: '',
|
||||
username: '',
|
||||
email: '',
|
||||
newPassword: '',
|
||||
currentPassword: '',
|
||||
avatarUrl: '',
|
||||
avatar: {
|
||||
data: {},
|
||||
url: ''
|
||||
},
|
||||
avatarSuggestions: {},
|
||||
customFields: {}
|
||||
};
|
||||
|
@ -83,7 +80,7 @@ class ProfileView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: IProfileViewProps) {
|
||||
const { user } = this.props;
|
||||
/*
|
||||
* We need to ignore status because on Android ImagePicker
|
||||
|
@ -96,7 +93,7 @@ class ProfileView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
setAvatar = avatar => {
|
||||
setAvatar = (avatar: IAvatar) => {
|
||||
const { Accounts_AllowUserAvatarChange } = this.props;
|
||||
|
||||
if (!Accounts_AllowUserAvatarChange) {
|
||||
|
@ -106,7 +103,7 @@ class ProfileView extends React.Component {
|
|||
this.setState({ avatar });
|
||||
};
|
||||
|
||||
init = user => {
|
||||
init = (user?: IUser) => {
|
||||
const { user: userProps } = this.props;
|
||||
const { name, username, emails, customFields } = user || userProps;
|
||||
|
||||
|
@ -117,7 +114,10 @@ class ProfileView extends React.Component {
|
|||
newPassword: null,
|
||||
currentPassword: null,
|
||||
avatarUrl: null,
|
||||
avatar: {},
|
||||
avatar: {
|
||||
data: {},
|
||||
url: ''
|
||||
},
|
||||
customFields: customFields || {}
|
||||
});
|
||||
};
|
||||
|
@ -142,12 +142,12 @@ class ProfileView extends React.Component {
|
|||
!newPassword &&
|
||||
user.emails &&
|
||||
user.emails[0].address === email &&
|
||||
!avatar.data &&
|
||||
!avatar!.data &&
|
||||
!customFieldsChanged
|
||||
);
|
||||
};
|
||||
|
||||
handleError = (e, func, action) => {
|
||||
handleError = (e: any, func: string, action: string) => {
|
||||
if (e.data && e.data.error.includes('[error-too-many-requests]')) {
|
||||
return showErrorAlert(e.data.error);
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ class ProfileView extends React.Component {
|
|||
|
||||
const { name, username, email, newPassword, currentPassword, avatar, customFields } = this.state;
|
||||
const { user, setUser } = this.props;
|
||||
const params = {};
|
||||
const params = {} as IParams;
|
||||
|
||||
// Name
|
||||
if (user.name !== name) {
|
||||
|
@ -189,7 +189,7 @@ class ProfileView extends React.Component {
|
|||
|
||||
// currentPassword
|
||||
if (currentPassword) {
|
||||
params.currentPassword = SHA256(currentPassword);
|
||||
params.currentPassword = sha256(currentPassword);
|
||||
}
|
||||
|
||||
const requirePassword = !!params.email || newPassword;
|
||||
|
@ -202,7 +202,7 @@ class ProfileView extends React.Component {
|
|||
{ text: I18n.t('Cancel'), onPress: () => {}, style: 'cancel' },
|
||||
{
|
||||
text: I18n.t('Save'),
|
||||
onPress: p => {
|
||||
onPress: (p: string) => {
|
||||
this.setState({ currentPassword: p });
|
||||
this.submit();
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ class ProfileView extends React.Component {
|
|||
}
|
||||
|
||||
try {
|
||||
if (avatar.url) {
|
||||
if (avatar!.url) {
|
||||
try {
|
||||
logEvent(events.PROFILE_SAVE_AVATAR);
|
||||
await RocketChat.setAvatarFromService(avatar);
|
||||
|
@ -283,7 +283,7 @@ class ProfileView extends React.Component {
|
|||
};
|
||||
try {
|
||||
logEvent(events.PROFILE_PICK_AVATAR);
|
||||
const response = await ImagePicker.openPicker(options);
|
||||
const response: Image = await ImagePicker.openPicker(options);
|
||||
this.setAvatar({ url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' });
|
||||
} catch (error) {
|
||||
logEvent(events.PROFILE_PICK_AVATAR_F);
|
||||
|
@ -291,12 +291,12 @@ class ProfileView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
pickImageWithURL = avatarUrl => {
|
||||
pickImageWithURL = (avatarUrl: string) => {
|
||||
logEvent(events.PROFILE_PICK_AVATAR_WITH_URL);
|
||||
this.setAvatar({ url: avatarUrl, data: avatarUrl, service: 'url' });
|
||||
};
|
||||
|
||||
renderAvatarButton = ({ key, child, onPress, disabled = false }) => {
|
||||
renderAvatarButton = ({ key, child, onPress, disabled = false }: IAvatarButton) => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<Touch
|
||||
|
@ -331,7 +331,7 @@ class ProfileView extends React.Component {
|
|||
})}
|
||||
{this.renderAvatarButton({
|
||||
child: <CustomIcon name='link' size={30} color={themes[theme].bodyText} />,
|
||||
onPress: () => this.pickImageWithURL(avatarUrl),
|
||||
onPress: () => this.pickImageWithURL(avatarUrl!),
|
||||
disabled: !avatarUrl,
|
||||
key: 'profile-view-avatar-url-button'
|
||||
})}
|
||||
|
@ -365,19 +365,20 @@ class ProfileView extends React.Component {
|
|||
const parsedCustomFields = JSON.parse(Accounts_CustomFields);
|
||||
return Object.keys(parsedCustomFields).map((key, index, array) => {
|
||||
if (parsedCustomFields[key].type === 'select') {
|
||||
const options = parsedCustomFields[key].options.map(option => ({ label: option, value: option }));
|
||||
const options = parsedCustomFields[key].options.map((option: string) => ({ label: option, value: option }));
|
||||
return (
|
||||
<RNPickerSelect
|
||||
key={key}
|
||||
items={options}
|
||||
onValueChange={value => {
|
||||
const newValue = {};
|
||||
const newValue: { [key: string]: string } = {};
|
||||
newValue[key] = value;
|
||||
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||
}}
|
||||
value={customFields[key]}>
|
||||
<RCTextInput
|
||||
inputRef={e => {
|
||||
// @ts-ignore
|
||||
this[key] = e;
|
||||
}}
|
||||
label={key}
|
||||
|
@ -393,6 +394,7 @@ class ProfileView extends React.Component {
|
|||
return (
|
||||
<RCTextInput
|
||||
inputRef={e => {
|
||||
// @ts-ignore
|
||||
this[key] = e;
|
||||
}}
|
||||
key={key}
|
||||
|
@ -400,12 +402,13 @@ class ProfileView extends React.Component {
|
|||
placeholder={key}
|
||||
value={customFields[key]}
|
||||
onChangeText={value => {
|
||||
const newValue = {};
|
||||
const newValue: { [key: string]: string } = {};
|
||||
newValue[key] = value;
|
||||
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
if (array.length - 1 > index) {
|
||||
// @ts-ignore
|
||||
return this[array[index + 1]].focus();
|
||||
}
|
||||
this.avatarUrl.focus();
|
||||
|
@ -421,6 +424,7 @@ class ProfileView extends React.Component {
|
|||
|
||||
logoutOtherLocations = () => {
|
||||
logEvent(events.PL_OTHER_LOCATIONS);
|
||||
// @ts-ignore
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('You_will_be_logged_out_from_other_locations'),
|
||||
confirmationText: I18n.t('Logout'),
|
||||
|
@ -469,7 +473,7 @@ class ProfileView extends React.Component {
|
|||
label={I18n.t('Name')}
|
||||
placeholder={I18n.t('Name')}
|
||||
value={name}
|
||||
onChangeText={value => this.setState({ name: value })}
|
||||
onChangeText={(value: string) => this.setState({ name: value })}
|
||||
onSubmitEditing={() => {
|
||||
this.username.focus();
|
||||
}}
|
||||
|
@ -500,7 +504,7 @@ class ProfileView extends React.Component {
|
|||
}}
|
||||
label={I18n.t('Email')}
|
||||
placeholder={I18n.t('Email')}
|
||||
value={email}
|
||||
value={email!}
|
||||
onChangeText={value => this.setState({ email: value })}
|
||||
onSubmitEditing={() => {
|
||||
this.newPassword.focus();
|
||||
|
@ -516,10 +520,11 @@ class ProfileView extends React.Component {
|
|||
}}
|
||||
label={I18n.t('New_Password')}
|
||||
placeholder={I18n.t('New_Password')}
|
||||
value={newPassword}
|
||||
value={newPassword!}
|
||||
onChangeText={value => this.setState({ newPassword: value })}
|
||||
onSubmitEditing={() => {
|
||||
if (Accounts_CustomFields && Object.keys(customFields).length) {
|
||||
// @ts-ignore
|
||||
return this[Object.keys(customFields)[0]].focus();
|
||||
}
|
||||
this.avatarUrl.focus();
|
||||
|
@ -537,7 +542,7 @@ class ProfileView extends React.Component {
|
|||
}}
|
||||
label={I18n.t('Avatar_Url')}
|
||||
placeholder={I18n.t('Avatar_Url')}
|
||||
value={avatarUrl}
|
||||
value={avatarUrl!}
|
||||
onChangeText={value => this.setState({ avatarUrl: value })}
|
||||
onSubmitEditing={this.submit}
|
||||
testID='profile-view-avatar-url'
|
||||
|
@ -568,7 +573,7 @@ class ProfileView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
user: getUserSelector(state),
|
||||
Accounts_AllowEmailChange: state.settings.Accounts_AllowEmailChange,
|
||||
Accounts_AllowPasswordChange: state.settings.Accounts_AllowPasswordChange,
|
||||
|
@ -579,8 +584,8 @@ const mapStateToProps = state => ({
|
|||
baseUrl: state.server.server
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setUser: params => dispatch(setUserAction(params))
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
setUser: (params: any) => dispatch(setUserAction(params))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(ProfileView));
|
|
@ -0,0 +1,79 @@
|
|||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import React from 'react';
|
||||
|
||||
export interface IUser {
|
||||
id: string;
|
||||
name: string;
|
||||
username: string;
|
||||
emails: {
|
||||
[index: number]: {
|
||||
address: string;
|
||||
};
|
||||
};
|
||||
customFields: {
|
||||
[index: string | number]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IParams {
|
||||
name: string;
|
||||
username: string;
|
||||
email: string | null;
|
||||
newPassword: string;
|
||||
currentPassword: string;
|
||||
}
|
||||
|
||||
export interface IAvatarButton {
|
||||
key: React.Key;
|
||||
child: React.ReactNode;
|
||||
onPress: Function;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export interface INavigationOptions {
|
||||
navigation: StackNavigationProp<any, 'ProfileView'>;
|
||||
isMasterDetail?: boolean;
|
||||
}
|
||||
|
||||
export interface IProfileViewProps {
|
||||
user: IUser;
|
||||
navigation: StackNavigationProp<any, 'ProfileView'>;
|
||||
isMasterDetail?: boolean;
|
||||
baseUrl: string;
|
||||
Accounts_AllowEmailChange: boolean;
|
||||
Accounts_AllowPasswordChange: boolean;
|
||||
Accounts_AllowRealNameChange: boolean;
|
||||
Accounts_AllowUserAvatarChange: boolean;
|
||||
Accounts_AllowUsernameChange: boolean;
|
||||
Accounts_CustomFields: string;
|
||||
setUser: Function;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export interface IAvatar {
|
||||
data: {} | string | null;
|
||||
url?: string;
|
||||
contentType?: string;
|
||||
service?: any;
|
||||
}
|
||||
|
||||
export interface IProfileViewState {
|
||||
saving: boolean;
|
||||
name: string;
|
||||
username: string;
|
||||
email: string | null;
|
||||
newPassword: string | null;
|
||||
currentPassword: string | null;
|
||||
avatarUrl: string | null;
|
||||
avatar: IAvatar;
|
||||
avatarSuggestions: {
|
||||
[service: string]: {
|
||||
url: string;
|
||||
blob: string;
|
||||
contentType: string;
|
||||
};
|
||||
};
|
||||
customFields: {
|
||||
[key: string | number]: string;
|
||||
};
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, Text, View, RefreshControl } from 'react-native';
|
||||
import { dequal } from 'dequal';
|
||||
import moment from 'moment';
|
||||
import { connect } from 'react-redux';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
|
||||
import * as List from '../../containers/List';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
|
@ -16,9 +17,40 @@ import { themes } from '../../constants/colors';
|
|||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import styles from './styles';
|
||||
|
||||
class ReadReceiptView extends React.Component {
|
||||
static navigationOptions = ({ navigation, isMasterDetail }) => {
|
||||
const options = {
|
||||
interface IReceipts {
|
||||
_id: string;
|
||||
roomId: string;
|
||||
userId: string;
|
||||
messageId: string;
|
||||
ts: string;
|
||||
user?: {
|
||||
_id: string;
|
||||
name: string;
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface IReadReceiptViewState {
|
||||
loading: boolean;
|
||||
receipts: IReceipts[];
|
||||
}
|
||||
|
||||
interface INavigationOption {
|
||||
navigation: StackNavigationProp<any, 'ReadReceiptView'>;
|
||||
route: RouteProp<{ ReadReceiptView: { messageId: string } }, 'ReadReceiptView'>;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
interface IReadReceiptViewProps extends INavigationOption {
|
||||
Message_TimeAndDateFormat: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
class ReadReceiptView extends React.Component<IReadReceiptViewProps, IReadReceiptViewState> {
|
||||
private messageId: string;
|
||||
|
||||
static navigationOptions = ({ navigation, isMasterDetail }: INavigationOption) => {
|
||||
const options: StackNavigationOptions = {
|
||||
title: I18n.t('Read_Receipt')
|
||||
};
|
||||
if (isMasterDetail) {
|
||||
|
@ -27,13 +59,7 @@ class ReadReceiptView extends React.Component {
|
|||
return options;
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
route: PropTypes.object,
|
||||
Message_TimeAndDateFormat: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IReadReceiptViewProps) {
|
||||
super(props);
|
||||
this.messageId = props.route.params?.messageId;
|
||||
this.state = {
|
||||
|
@ -46,7 +72,7 @@ class ReadReceiptView extends React.Component {
|
|||
this.load();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps: IReadReceiptViewProps, nextState: IReadReceiptViewState) {
|
||||
const { loading, receipts } = this.state;
|
||||
const { theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
|
@ -98,7 +124,7 @@ class ReadReceiptView extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
renderItem = ({ item }: { item: IReceipts }) => {
|
||||
const { theme, Message_TimeAndDateFormat } = this.props;
|
||||
const time = moment(item.ts).format(Message_TimeAndDateFormat);
|
||||
if (!item?.user?.username) {
|
||||
|
@ -152,7 +178,7 @@ class ReadReceiptView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
Message_TimeAndDateFormat: state.settings.Message_TimeAndDateFormat
|
||||
});
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -12,6 +13,7 @@ import debounce from '../../utils/debounce';
|
|||
import RocketChat from '../../lib/rocketchat';
|
||||
import Message from '../../containers/message';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import { IMessage, IMessageAttachments } from '../../containers/message/interfaces';
|
||||
import I18n from '../../i18n';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import log from '../../utils/log';
|
||||
|
@ -29,9 +31,52 @@ import { compareServerVersion, methods } from '../../lib/utils';
|
|||
import styles from './styles';
|
||||
|
||||
const QUERY_SIZE = 50;
|
||||
class SearchMessagesView extends React.Component {
|
||||
static navigationOptions = ({ navigation, route }) => {
|
||||
const options = {
|
||||
|
||||
type TRouteParams = {
|
||||
SearchMessagesView: {
|
||||
showCloseModal?: boolean;
|
||||
rid: string;
|
||||
t?: string;
|
||||
encrypted?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
interface ISearchMessagesViewState {
|
||||
loading: boolean;
|
||||
messages: IMessage[];
|
||||
searchText: string;
|
||||
}
|
||||
interface INavigationOption {
|
||||
navigation: StackNavigationProp<any, 'SearchMessagesView'>;
|
||||
route: RouteProp<TRouteParams, 'SearchMessagesView'>;
|
||||
}
|
||||
|
||||
interface ISearchMessagesViewProps extends INavigationOption {
|
||||
user: { id: string };
|
||||
baseUrl: string;
|
||||
serverVersion: string;
|
||||
customEmojis: {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
extension: string;
|
||||
};
|
||||
};
|
||||
theme: string;
|
||||
useRealName: boolean;
|
||||
}
|
||||
class SearchMessagesView extends React.Component<ISearchMessagesViewProps, ISearchMessagesViewState> {
|
||||
private offset: number;
|
||||
|
||||
private rid: string;
|
||||
|
||||
private t: string | undefined;
|
||||
|
||||
private encrypted: boolean | undefined;
|
||||
|
||||
private room: { rid: any; name: any; fname: any; t: any } | null | undefined;
|
||||
|
||||
static navigationOptions = ({ navigation, route }: INavigationOption) => {
|
||||
const options: StackNavigationOptions = {
|
||||
title: I18n.t('Search')
|
||||
};
|
||||
const showCloseModal = route.params?.showCloseModal;
|
||||
|
@ -41,18 +86,7 @@ class SearchMessagesView extends React.Component {
|
|||
return options;
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
serverVersion: PropTypes.string,
|
||||
customEmojis: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
useRealName: PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: ISearchMessagesViewProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false,
|
||||
|
@ -60,7 +94,7 @@ class SearchMessagesView extends React.Component {
|
|||
searchText: ''
|
||||
};
|
||||
this.offset = 0;
|
||||
this.rid = props.route.params?.rid;
|
||||
this.rid = props.route.params.rid;
|
||||
this.t = props.route.params?.t;
|
||||
this.encrypted = props.route.params?.encrypted;
|
||||
}
|
||||
|
@ -69,7 +103,7 @@ class SearchMessagesView extends React.Component {
|
|||
this.room = await getRoomInfo(this.rid);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps: ISearchMessagesViewProps, nextState: ISearchMessagesViewState) {
|
||||
const { loading, searchText, messages } = this.state;
|
||||
const { theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
|
@ -88,11 +122,11 @@ class SearchMessagesView extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.search?.stop?.();
|
||||
this.searchDebounced?.stop?.();
|
||||
}
|
||||
|
||||
// Handle encrypted rooms search messages
|
||||
searchMessages = async searchText => {
|
||||
searchMessages = async (searchText: string) => {
|
||||
if (!searchText) {
|
||||
return [];
|
||||
}
|
||||
|
@ -117,7 +151,7 @@ class SearchMessagesView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
getMessages = async (searchText, debounced) => {
|
||||
getMessages = async (searchText: string, debounced?: boolean) => {
|
||||
try {
|
||||
const messages = await this.searchMessages(searchText);
|
||||
this.setState(prevState => ({
|
||||
|
@ -130,17 +164,17 @@ class SearchMessagesView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
search = searchText => {
|
||||
search = (searchText: string) => {
|
||||
this.offset = 0;
|
||||
this.setState({ searchText, loading: true, messages: [] });
|
||||
this.searchDebounced(searchText);
|
||||
};
|
||||
|
||||
searchDebounced = debounce(async searchText => {
|
||||
searchDebounced = debounce(async (searchText: string) => {
|
||||
await this.getMessages(searchText, true);
|
||||
}, 1000);
|
||||
|
||||
getCustomEmoji = name => {
|
||||
getCustomEmoji = (name: string) => {
|
||||
const { customEmojis } = this.props;
|
||||
const emoji = customEmojis[name];
|
||||
if (emoji) {
|
||||
|
@ -149,12 +183,12 @@ class SearchMessagesView extends React.Component {
|
|||
return null;
|
||||
};
|
||||
|
||||
showAttachment = attachment => {
|
||||
showAttachment = (attachment: IMessageAttachments) => {
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('AttachmentView', { attachment });
|
||||
};
|
||||
|
||||
navToRoomInfo = navParam => {
|
||||
navToRoomInfo = (navParam: IMessage) => {
|
||||
const { navigation, user } = this.props;
|
||||
if (navParam.rid === user.id) {
|
||||
return;
|
||||
|
@ -162,9 +196,9 @@ class SearchMessagesView extends React.Component {
|
|||
navigation.navigate('RoomInfoView', navParam);
|
||||
};
|
||||
|
||||
jumpToMessage = async ({ item }) => {
|
||||
jumpToMessage = async ({ item }: { item: IMessage }) => {
|
||||
const { navigation } = this.props;
|
||||
let params = {
|
||||
let params: any = {
|
||||
rid: this.rid,
|
||||
jumpToMessageId: item._id,
|
||||
t: this.t,
|
||||
|
@ -210,7 +244,7 @@ class SearchMessagesView extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
renderItem = ({ item }: { item: IMessage }) => {
|
||||
const { user, baseUrl, theme, useRealName } = this.props;
|
||||
return (
|
||||
<Message
|
||||
|
@ -268,6 +302,7 @@ class SearchMessagesView extends React.Component {
|
|||
testID='search-message-view-input'
|
||||
theme={theme}
|
||||
/>
|
||||
{/* @ts-ignore */}
|
||||
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} username='' baseUrl='' theme={theme} />
|
||||
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
|
||||
</View>
|
||||
|
@ -277,7 +312,7 @@ class SearchMessagesView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
serverVersion: state.server.version,
|
||||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state),
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
|
@ -35,7 +34,13 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const Header = React.memo(({ room, thread, theme }) => {
|
||||
interface IHeader {
|
||||
room: { prid?: string; t?: string };
|
||||
thread: { id?: string };
|
||||
theme: string;
|
||||
}
|
||||
|
||||
const Header = React.memo(({ room, thread, theme }: IHeader) => {
|
||||
let type;
|
||||
if (thread?.id) {
|
||||
type = 'thread';
|
||||
|
@ -88,10 +93,5 @@ const Header = React.memo(({ room, thread, theme }) => {
|
|||
</View>
|
||||
);
|
||||
});
|
||||
Header.propTypes = {
|
||||
room: PropTypes.object,
|
||||
thread: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default withTheme(Header);
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Video } from 'expo-av';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { ScrollView, StyleSheet, Text } from 'react-native';
|
||||
|
@ -15,6 +14,7 @@ import I18n from '../../i18n';
|
|||
import { isAndroid } from '../../utils/deviceInfo';
|
||||
import { allowPreview } from './utils';
|
||||
import { THUMBS_HEIGHT } from './constants';
|
||||
import { IAttachment, IUseDimensions } from './interfaces';
|
||||
|
||||
const MESSAGEBOX_HEIGHT = 56;
|
||||
|
||||
|
@ -35,7 +35,17 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const IconPreview = React.memo(({ iconName, title, description, theme, width, height, danger }) => (
|
||||
interface IIconPreview {
|
||||
iconName: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
theme: string;
|
||||
width: number;
|
||||
height: number;
|
||||
danger?: boolean;
|
||||
}
|
||||
|
||||
const IconPreview = React.memo(({ iconName, title, description, theme, width, height, danger }: IIconPreview) => (
|
||||
<ScrollView
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
contentContainerStyle={[styles.fileContainer, { width, height }]}>
|
||||
|
@ -45,9 +55,16 @@ const IconPreview = React.memo(({ iconName, title, description, theme, width, he
|
|||
</ScrollView>
|
||||
));
|
||||
|
||||
const Preview = React.memo(({ item, theme, isShareExtension, length }) => {
|
||||
interface IPreview {
|
||||
item: IAttachment;
|
||||
theme: string;
|
||||
isShareExtension: boolean;
|
||||
length: number;
|
||||
}
|
||||
|
||||
const Preview = React.memo(({ item, theme, isShareExtension, length }: IPreview) => {
|
||||
const type = item?.mime;
|
||||
const { width, height } = useDimensions();
|
||||
const { width, height } = useDimensions() as IUseDimensions;
|
||||
const { isLandscape } = useOrientation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const headerHeight = getHeaderHeight(isLandscape);
|
||||
|
@ -111,21 +128,5 @@ const Preview = React.memo(({ item, theme, isShareExtension, length }) => {
|
|||
/>
|
||||
);
|
||||
});
|
||||
Preview.propTypes = {
|
||||
item: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
isShareExtension: PropTypes.bool,
|
||||
length: PropTypes.number
|
||||
};
|
||||
|
||||
IconPreview.propTypes = {
|
||||
iconName: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
danger: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Preview;
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, Image, StyleSheet, View } from 'react-native';
|
||||
import { RectButton, TouchableNativeFeedback, TouchableOpacity } from 'react-native-gesture-handler';
|
||||
|
||||
|
@ -9,6 +8,7 @@ import { CustomIcon } from '../../lib/Icons';
|
|||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import { THUMBS_HEIGHT } from './constants';
|
||||
import { allowPreview } from './utils';
|
||||
import { IAttachment } from './interfaces';
|
||||
|
||||
const THUMB_SIZE = 64;
|
||||
|
||||
|
@ -60,22 +60,34 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const ThumbButton = isIOS ? TouchableOpacity : TouchableNativeFeedback;
|
||||
interface IThumbContent {
|
||||
item: IAttachment;
|
||||
theme: string;
|
||||
isShareExtension: boolean;
|
||||
}
|
||||
|
||||
const ThumbContent = React.memo(({ item, theme, isShareExtension }) => {
|
||||
interface IThumb extends IThumbContent {
|
||||
onPress(item: IAttachment): void;
|
||||
onRemove(item: IAttachment): void;
|
||||
}
|
||||
|
||||
interface IThumbs extends Omit<IThumb, 'item'> {
|
||||
attachments: IAttachment[];
|
||||
}
|
||||
|
||||
const ThumbContent = React.memo(({ item, theme, isShareExtension }: IThumbContent) => {
|
||||
const type = item?.mime;
|
||||
|
||||
if (type?.match(/image/)) {
|
||||
// Disallow preview of images too big in order to prevent memory issues on iOS share extension
|
||||
if (allowPreview(isShareExtension, item?.size)) {
|
||||
return <Image source={{ uri: item.path }} style={[styles.thumb, { borderColor: themes[theme].borderColor }]} />;
|
||||
} else {
|
||||
return (
|
||||
<View style={[styles.thumb, { borderColor: themes[theme].borderColor }]}>
|
||||
<CustomIcon name='image' size={30} color={themes[theme].tintColor} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={[styles.thumb, { borderColor: themes[theme].borderColor }]}>
|
||||
<CustomIcon name='image' size={30} color={themes[theme].tintColor} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (type?.match(/video/)) {
|
||||
|
@ -85,22 +97,23 @@ const ThumbContent = React.memo(({ item, theme, isShareExtension }) => {
|
|||
<CustomIcon name='camera' size={30} color={themes[theme].tintColor} />
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
const { uri } = item;
|
||||
return (
|
||||
<>
|
||||
<Image source={{ uri }} style={styles.thumb} />
|
||||
<CustomIcon name='camera-filled' size={20} color={themes[theme].buttonText} style={styles.videoThumbIcon} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
const { uri } = item;
|
||||
return (
|
||||
<>
|
||||
<Image source={{ uri }} style={styles.thumb} />
|
||||
<CustomIcon name='camera-filled' size={20} color={themes[theme].buttonText} style={styles.videoThumbIcon} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Multiple files upload of files different than image/video is not implemented, so there's no thumb
|
||||
return null;
|
||||
});
|
||||
|
||||
const Thumb = ({ item, theme, isShareExtension, onPress, onRemove }) => (
|
||||
const ThumbButton: typeof React.Component = isIOS ? TouchableOpacity : TouchableNativeFeedback;
|
||||
|
||||
const Thumb = ({ item, theme, isShareExtension, onPress, onRemove }: IThumb) => (
|
||||
<ThumbButton style={styles.item} onPress={() => onPress(item)} activeOpacity={0.7}>
|
||||
<>
|
||||
<ThumbContent item={item} theme={theme} isShareExtension={isShareExtension} />
|
||||
|
@ -121,7 +134,7 @@ const Thumb = ({ item, theme, isShareExtension, onPress, onRemove }) => (
|
|||
</ThumbButton>
|
||||
);
|
||||
|
||||
const Thumbs = React.memo(({ attachments, theme, isShareExtension, onPress, onRemove }) => {
|
||||
const Thumbs = React.memo(({ attachments, theme, isShareExtension, onPress, onRemove }: IThumbs) => {
|
||||
if (attachments?.length > 1) {
|
||||
return (
|
||||
<FlatList
|
||||
|
@ -143,24 +156,5 @@ const Thumbs = React.memo(({ attachments, theme, isShareExtension, onPress, onRe
|
|||
}
|
||||
return null;
|
||||
});
|
||||
Thumbs.propTypes = {
|
||||
attachments: PropTypes.array,
|
||||
theme: PropTypes.string,
|
||||
isShareExtension: PropTypes.bool,
|
||||
onPress: PropTypes.func,
|
||||
onRemove: PropTypes.func
|
||||
};
|
||||
Thumb.propTypes = {
|
||||
item: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
isShareExtension: PropTypes.bool,
|
||||
onPress: PropTypes.func,
|
||||
onRemove: PropTypes.func
|
||||
};
|
||||
ThumbContent.propTypes = {
|
||||
item: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
isShareExtension: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Thumbs;
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { NativeModules, Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ShareExtension from 'rn-extensions-share';
|
||||
|
@ -24,9 +25,61 @@ import Thumbs from './Thumbs';
|
|||
import Preview from './Preview';
|
||||
import Header from './Header';
|
||||
import styles from './styles';
|
||||
import { IAttachment, IServer } from './interfaces';
|
||||
|
||||
class ShareView extends Component {
|
||||
constructor(props) {
|
||||
interface IShareViewState {
|
||||
selected: IAttachment;
|
||||
loading: boolean;
|
||||
readOnly: boolean;
|
||||
attachments: IAttachment[];
|
||||
text: string;
|
||||
// TODO: Refactor when migrate room
|
||||
room: any;
|
||||
thread: any;
|
||||
maxFileSize: number;
|
||||
mediaAllowList: number;
|
||||
}
|
||||
|
||||
interface IShareViewProps {
|
||||
// TODO: Refactor after react-navigation
|
||||
navigation: StackNavigationProp<any, 'ShareView'>;
|
||||
route: RouteProp<
|
||||
{
|
||||
ShareView: {
|
||||
attachments: IAttachment[];
|
||||
isShareView?: boolean;
|
||||
isShareExtension: boolean;
|
||||
serverInfo: IServer;
|
||||
text: string;
|
||||
room: any;
|
||||
thread: any; // change
|
||||
};
|
||||
},
|
||||
'ShareView'
|
||||
>;
|
||||
theme: string;
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
token: string;
|
||||
};
|
||||
server: string;
|
||||
FileUpload_MediaTypeWhiteList?: number;
|
||||
FileUpload_MaxFileSize?: number;
|
||||
}
|
||||
|
||||
interface IMessageBoxShareView {
|
||||
text: string;
|
||||
forceUpdate(): void;
|
||||
}
|
||||
|
||||
class ShareView extends Component<IShareViewProps, IShareViewState> {
|
||||
private messagebox: React.RefObject<IMessageBoxShareView>;
|
||||
private files: any[];
|
||||
private isShareExtension: boolean;
|
||||
private serverInfo: any;
|
||||
|
||||
constructor(props: IShareViewProps) {
|
||||
super(props);
|
||||
this.messagebox = React.createRef();
|
||||
this.files = props.route.params?.attachments ?? [];
|
||||
|
@ -34,7 +87,7 @@ class ShareView extends Component {
|
|||
this.serverInfo = props.route.params?.serverInfo ?? {};
|
||||
|
||||
this.state = {
|
||||
selected: {},
|
||||
selected: {} as IAttachment,
|
||||
loading: false,
|
||||
readOnly: false,
|
||||
attachments: [],
|
||||
|
@ -61,7 +114,7 @@ class ShareView extends Component {
|
|||
const { room, thread, readOnly, attachments } = this.state;
|
||||
const { navigation, theme } = this.props;
|
||||
|
||||
const options = {
|
||||
const options: StackNavigationOptions = {
|
||||
headerTitle: () => <Header room={room} thread={thread} />,
|
||||
headerTitleAlign: 'left',
|
||||
headerTintColor: themes[theme].previewTintColor
|
||||
|
@ -69,9 +122,7 @@ class ShareView extends Component {
|
|||
|
||||
// if is share extension show default back button
|
||||
if (!this.isShareExtension) {
|
||||
options.headerLeft = () => (
|
||||
<HeaderButton.CloseModal navigation={navigation} buttonStyle={{ color: themes[theme].previewTintColor }} />
|
||||
);
|
||||
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
|
||||
}
|
||||
|
||||
if (!attachments.length && !readOnly) {
|
||||
|
@ -203,10 +254,10 @@ class ShareView extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
selectFile = item => {
|
||||
selectFile = (item: IAttachment) => {
|
||||
const { attachments, selected } = this.state;
|
||||
if (attachments.length > 0) {
|
||||
const { text } = this.messagebox.current;
|
||||
const text = this.messagebox.current?.text;
|
||||
const newAttachments = attachments.map(att => {
|
||||
if (att.path === selected.path) {
|
||||
att.description = text;
|
||||
|
@ -217,7 +268,7 @@ class ShareView extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
removeFile = item => {
|
||||
removeFile = (item: IAttachment) => {
|
||||
const { selected, attachments } = this.state;
|
||||
let newSelected;
|
||||
if (item.path === selected.path) {
|
||||
|
@ -235,7 +286,7 @@ class ShareView extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
onChangeText = text => {
|
||||
onChangeText = (text: string) => {
|
||||
this.setState({ text });
|
||||
};
|
||||
|
||||
|
@ -318,21 +369,7 @@ class ShareView extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
ShareView.propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
token: PropTypes.string.isRequired
|
||||
}),
|
||||
server: PropTypes.string,
|
||||
FileUpload_MediaTypeWhiteList: PropTypes.string,
|
||||
FileUpload_MaxFileSize: PropTypes.string
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
user: getUserSelector(state),
|
||||
server: state.share.server.server || state.server.server,
|
||||
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
|
|
@ -0,0 +1,33 @@
|
|||
export interface IAttachment {
|
||||
filename: string;
|
||||
description?: string;
|
||||
size: number;
|
||||
mime?: string;
|
||||
path: string;
|
||||
canUpload: boolean;
|
||||
error?: any;
|
||||
uri: string;
|
||||
}
|
||||
|
||||
export interface IUseDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
// TODO: move this to specific folder
|
||||
export interface IServer {
|
||||
name: string;
|
||||
iconURL: string;
|
||||
useRealName: boolean;
|
||||
FileUpload_MediaTypeWhiteList: string;
|
||||
FileUpload_MaxFileSize: number;
|
||||
roomsUpdatedAt: Date;
|
||||
version: string;
|
||||
lastLocalAuthenticatedSession: Date;
|
||||
autoLock: boolean;
|
||||
autoLockTime: number | null;
|
||||
biometry: boolean | null;
|
||||
uniqueID: string;
|
||||
enterpriseModules: string;
|
||||
E2E_Enable: boolean;
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
import { isAndroid } from '../../utils/deviceInfo';
|
||||
|
||||
// Limit preview to 3MB on iOS share extension
|
||||
export const allowPreview = (isShareExtension, size) => isAndroid || !isShareExtension || size < 3000000;
|
|
@ -0,0 +1,5 @@
|
|||
import { isAndroid } from '../../utils/deviceInfo';
|
||||
|
||||
// Limit preview to 3MB on iOS share extension
|
||||
export const allowPreview = (isShareExtension: boolean, size: number): boolean =>
|
||||
isAndroid || !isShareExtension || size < 3000000;
|
|
@ -1,13 +1,22 @@
|
|||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import styles from './styles';
|
||||
|
||||
const Item = React.memo(({ left, right, text, onPress, testID, current, theme }) => (
|
||||
interface SidebarItemProps {
|
||||
left: JSX.Element;
|
||||
right: JSX.Element;
|
||||
text: string;
|
||||
current: boolean;
|
||||
onPress(): void;
|
||||
testID: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
const Item = React.memo(({ left, right, text, onPress, testID, current, theme }: SidebarItemProps) => (
|
||||
<Touch
|
||||
key={testID}
|
||||
testID={testID}
|
||||
|
@ -24,14 +33,4 @@ const Item = React.memo(({ left, right, text, onPress, testID, current, theme })
|
|||
</Touch>
|
||||
));
|
||||
|
||||
Item.propTypes = {
|
||||
left: PropTypes.element,
|
||||
right: PropTypes.element,
|
||||
text: PropTypes.string,
|
||||
current: PropTypes.bool,
|
||||
onPress: PropTypes.func,
|
||||
testID: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default withTheme(Item);
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DrawerNavigationProp } from '@react-navigation/drawer';
|
||||
import { DrawerNavigationState } from '@react-navigation/native';
|
||||
import { ScrollView, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { dequal } from 'dequal';
|
||||
|
@ -18,38 +19,52 @@ import Navigation from '../../lib/Navigation';
|
|||
import SidebarItem from './SidebarItem';
|
||||
import styles from './styles';
|
||||
|
||||
const Separator = React.memo(({ theme }) => <View style={[styles.separator, { borderColor: themes[theme].separatorColor }]} />);
|
||||
Separator.propTypes = {
|
||||
theme: PropTypes.string
|
||||
};
|
||||
interface ISeparatorProps {
|
||||
theme: string;
|
||||
}
|
||||
|
||||
class Sidebar extends Component {
|
||||
static propTypes = {
|
||||
baseUrl: PropTypes.string,
|
||||
navigation: PropTypes.object,
|
||||
Site_Name: PropTypes.string.isRequired,
|
||||
user: PropTypes.object,
|
||||
state: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
loadingServer: PropTypes.bool,
|
||||
useRealName: PropTypes.bool,
|
||||
allowStatusMessage: PropTypes.bool,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
viewStatisticsPermission: PropTypes.object,
|
||||
viewRoomAdministrationPermission: PropTypes.object,
|
||||
viewUserAdministrationPermission: PropTypes.object,
|
||||
viewPrivilegedSettingPermission: PropTypes.object
|
||||
// TODO: remove this
|
||||
const Separator = React.memo(({ theme }: ISeparatorProps) => (
|
||||
<View style={[styles.separator, { borderColor: themes[theme].separatorColor }]} />
|
||||
));
|
||||
|
||||
interface ISidebarState {
|
||||
showStatus: boolean;
|
||||
}
|
||||
|
||||
interface ISidebarProps {
|
||||
baseUrl: string;
|
||||
navigation: DrawerNavigationProp<any, 'Sidebar'>;
|
||||
state: DrawerNavigationState<any>;
|
||||
Site_Name: string;
|
||||
user: {
|
||||
statusText: string;
|
||||
status: string;
|
||||
username: string;
|
||||
name: string;
|
||||
roles: string[];
|
||||
};
|
||||
theme: string;
|
||||
loadingServer: boolean;
|
||||
useRealName: boolean;
|
||||
allowStatusMessage: boolean;
|
||||
isMasterDetail: boolean;
|
||||
viewStatisticsPermission: string[];
|
||||
viewRoomAdministrationPermission: string[];
|
||||
viewUserAdministrationPermission: string[];
|
||||
viewPrivilegedSettingPermission: string[];
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||
constructor(props: ISidebarProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showStatus: false
|
||||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { showStatus, isAdmin } = this.state;
|
||||
shouldComponentUpdate(nextProps: ISidebarProps, nextState: ISidebarState) {
|
||||
const { showStatus } = this.state;
|
||||
const {
|
||||
Site_Name,
|
||||
user,
|
||||
|
@ -91,9 +106,6 @@ class Sidebar extends Component {
|
|||
if (nextProps.useRealName !== useRealName) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.isAdmin !== isAdmin) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(nextProps.viewStatisticsPermission, viewStatisticsPermission)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -127,7 +139,7 @@ class Sidebar extends Component {
|
|||
let isAdmin = false;
|
||||
|
||||
if (roles) {
|
||||
isAdmin = allPermissions.reduce((result, permission) => {
|
||||
isAdmin = allPermissions.reduce((result: boolean, permission) => {
|
||||
if (permission) {
|
||||
return result || permission.some(r => roles.indexOf(r) !== -1);
|
||||
}
|
||||
|
@ -137,7 +149,8 @@ class Sidebar extends Component {
|
|||
return isAdmin;
|
||||
}
|
||||
|
||||
sidebarNavigate = route => {
|
||||
sidebarNavigate = (route: string) => {
|
||||
// @ts-ignore
|
||||
logEvent(events[`SIDEBAR_GO_${route.replace('StackNavigator', '').replace('View', '').toUpperCase()}`]);
|
||||
Navigation.navigate(route);
|
||||
};
|
||||
|
@ -242,7 +255,7 @@ class Sidebar extends Component {
|
|||
]}
|
||||
{...scrollPersistTaps}>
|
||||
<TouchableWithoutFeedback onPress={this.onPressUser} testID='sidebar-close-drawer'>
|
||||
<View style={styles.header} theme={theme}>
|
||||
<View style={styles.header}>
|
||||
<Avatar text={user.username} style={styles.avatar} size={30} />
|
||||
<View style={styles.headerTextContainer}>
|
||||
<View style={styles.headerUsername}>
|
||||
|
@ -278,7 +291,7 @@ class Sidebar extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
Site_Name: state.settings.Site_Name,
|
||||
user: getUserSelector(state),
|
||||
baseUrl: state.server.server,
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { themes } from '../../constants/colors';
|
||||
|
@ -22,20 +22,34 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class UserNotificationPreferencesView extends React.Component {
|
||||
static navigationOptions = () => ({
|
||||
type TKey = 'desktopNotifications' | 'pushNotifications' | 'emailNotificationMode';
|
||||
|
||||
interface IUserNotificationPreferencesViewState {
|
||||
preferences: {
|
||||
desktopNotifications?: string;
|
||||
pushNotifications?: string;
|
||||
emailNotificationMode?: string;
|
||||
};
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface IUserNotificationPreferencesViewProps {
|
||||
navigation: StackNavigationProp<any, 'UserNotificationPreferencesView'>;
|
||||
theme: string;
|
||||
user: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
class UserNotificationPreferencesView extends React.Component<
|
||||
IUserNotificationPreferencesViewProps,
|
||||
IUserNotificationPreferencesViewState
|
||||
> {
|
||||
static navigationOptions = (): StackNavigationOptions => ({
|
||||
title: I18n.t('Notification_Preferences')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string
|
||||
})
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IUserNotificationPreferencesViewProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
preferences: {},
|
||||
|
@ -51,43 +65,43 @@ class UserNotificationPreferencesView extends React.Component {
|
|||
this.setState({ preferences, loading: true });
|
||||
}
|
||||
|
||||
findDefaultOption = key => {
|
||||
findDefaultOption = (key: TKey) => {
|
||||
const { preferences } = this.state;
|
||||
const option = preferences[key] ? OPTIONS[key].find(item => item.value === preferences[key]) : OPTIONS[key][0];
|
||||
return option;
|
||||
};
|
||||
|
||||
renderPickerOption = key => {
|
||||
renderPickerOption = (key: TKey) => {
|
||||
const { theme } = this.props;
|
||||
const text = this.findDefaultOption(key);
|
||||
return (
|
||||
<Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>
|
||||
{I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })}
|
||||
</Text>
|
||||
);
|
||||
return <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>{I18n.t(text?.label)}</Text>;
|
||||
};
|
||||
|
||||
pickerSelection = (title, key) => {
|
||||
pickerSelection = (title: string, key: TKey) => {
|
||||
const { preferences } = this.state;
|
||||
const { navigation } = this.props;
|
||||
let values = OPTIONS[key];
|
||||
|
||||
const defaultOption = this.findDefaultOption(key);
|
||||
if (OPTIONS[key][0]?.value !== 'default') {
|
||||
values = [{ label: `${I18n.t('Default')} (${I18n.t(defaultOption.label)})` }, ...OPTIONS[key]];
|
||||
const defaultValue = { label: `${I18n.t('Default')} (${I18n.t(defaultOption?.label)})` } as {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
values = [defaultValue, ...OPTIONS[key]];
|
||||
}
|
||||
|
||||
navigation.navigate('PickerView', {
|
||||
title,
|
||||
data: values,
|
||||
value: preferences[key],
|
||||
onChangeValue: value => this.onValueChangePicker(key, value ?? defaultOption.value)
|
||||
onChangeValue: (value: string) => this.onValueChangePicker(key, value ?? defaultOption?.value)
|
||||
});
|
||||
};
|
||||
|
||||
onValueChangePicker = (key, value) => this.saveNotificationPreferences({ [key]: value.toString() });
|
||||
onValueChangePicker = (key: TKey, value: string) => this.saveNotificationPreferences({ [key]: value.toString() });
|
||||
|
||||
saveNotificationPreferences = async params => {
|
||||
saveNotificationPreferences = async (params: { [key: string]: string }) => {
|
||||
const { user } = this.props;
|
||||
const { id } = user;
|
||||
const result = await RocketChat.setUserPreferences(id, params);
|
||||
|
@ -111,7 +125,7 @@ class UserNotificationPreferencesView extends React.Component {
|
|||
<List.Item
|
||||
title='Alert'
|
||||
testID='user-notification-preference-view-alert'
|
||||
onPress={title => this.pickerSelection(title, 'desktopNotifications')}
|
||||
onPress={(title: string) => this.pickerSelection(title, 'desktopNotifications')}
|
||||
right={() => this.renderPickerOption('desktopNotifications')}
|
||||
/>
|
||||
<List.Separator />
|
||||
|
@ -123,8 +137,8 @@ class UserNotificationPreferencesView extends React.Component {
|
|||
<List.Item
|
||||
title='Alert'
|
||||
testID='user-notification-preference-view-push-notification'
|
||||
onPress={title => this.pickerSelection(title, 'mobileNotifications')}
|
||||
right={() => this.renderPickerOption('mobileNotifications')}
|
||||
onPress={(title: string) => this.pickerSelection(title, 'pushNotifications')}
|
||||
right={() => this.renderPickerOption('pushNotifications')}
|
||||
/>
|
||||
<List.Separator />
|
||||
<List.Info info='Push_Notifications_Alert_Info' />
|
||||
|
@ -135,7 +149,7 @@ class UserNotificationPreferencesView extends React.Component {
|
|||
<List.Item
|
||||
title='Alert'
|
||||
testID='user-notification-preference-view-email-alert'
|
||||
onPress={title => this.pickerSelection(title, 'emailNotificationMode')}
|
||||
onPress={(title: string) => this.pickerSelection(title, 'emailNotificationMode')}
|
||||
right={() => this.renderPickerOption('emailNotificationMode')}
|
||||
/>
|
||||
<List.Separator />
|
||||
|
@ -151,7 +165,7 @@ class UserNotificationPreferencesView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
user: getUserSelector(state)
|
||||
});
|
||||
|
|
@ -19,7 +19,7 @@ const commonOptions = [
|
|||
|
||||
export const OPTIONS = {
|
||||
desktopNotifications: commonOptions,
|
||||
mobileNotifications: commonOptions,
|
||||
pushNotifications: commonOptions,
|
||||
emailNotificationMode: [
|
||||
{
|
||||
label: 'Email_Notification_Mode_All',
|
|
@ -1,6 +1,6 @@
|
|||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Switch } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
|
@ -12,7 +12,11 @@ import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
|||
import { getUserSelector } from '../../selectors/login';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
||||
const UserPreferencesView = ({ navigation }) => {
|
||||
interface IUserPreferencesViewProps {
|
||||
navigation: StackNavigationProp<any, 'UserPreferencesView'>;
|
||||
}
|
||||
|
||||
const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Element => {
|
||||
const user = useSelector(state => getUserSelector(state));
|
||||
const [enableParser, setEnableParser] = useState(user.enableMessageParserEarlyAdoption);
|
||||
|
||||
|
@ -22,12 +26,12 @@ const UserPreferencesView = ({ navigation }) => {
|
|||
});
|
||||
}, []);
|
||||
|
||||
const navigateToScreen = (screen, params) => {
|
||||
logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]);
|
||||
navigation.navigate(screen, params);
|
||||
const navigateToScreen = (screen: string) => {
|
||||
logEvent(events.UP_GO_USER_NOTIFICATION_PREF);
|
||||
navigation.navigate(screen);
|
||||
};
|
||||
|
||||
const toggleMessageParser = async value => {
|
||||
const toggleMessageParser = async (value: boolean) => {
|
||||
try {
|
||||
await RocketChat.saveUserPreferences({ id: user.id, enableMessageParserEarlyAdoption: value });
|
||||
setEnableParser(value);
|
||||
|
@ -68,8 +72,4 @@ const UserPreferencesView = ({ navigation }) => {
|
|||
);
|
||||
};
|
||||
|
||||
UserPreferencesView.propTypes = {
|
||||
navigation: PropTypes.object
|
||||
};
|
||||
|
||||
export default UserPreferencesView;
|
|
@ -0,0 +1,15 @@
|
|||
export interface ILivechatDepartment {
|
||||
_id: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
description: string;
|
||||
showOnRegistration: boolean;
|
||||
showOnOfflineForm: boolean;
|
||||
requestTagBeforeClosingChat: boolean;
|
||||
email: string;
|
||||
chatClosingTags: string[];
|
||||
offlineMessageChannelName: string;
|
||||
numAgents: number;
|
||||
_updatedAt?: Date;
|
||||
businessHourId?: string;
|
||||
}
|
|
@ -80,7 +80,7 @@ db.getCollection("rocketchat_permissions").insert({"_id":"change-setting-Account
|
|||
db.getCollection("rocketchat_permissions").insert({"_id":"change-setting-Accounts_Default_User_Preferences_hideUsernames","_updatedAt":new Date(1591734395388),"group":"Accounts","groupPermissionId":"change-setting-Accounts","level":"settings","roles":[],"section":"Accounts_Default_User_Preferences","sectionPermissionId":"change-setting-Accounts_Default_User_Preferences","settingId":"Accounts_Default_User_Preferences_hideUsernames","sorter":NumberInt(60)});
|
||||
db.getCollection("rocketchat_permissions").insert({"_id":"change-setting-Accounts_Default_User_Preferences_idleTimeLimit","_updatedAt":new Date(1591734395359),"group":"Accounts","groupPermissionId":"change-setting-Accounts","level":"settings","roles":[],"section":"Accounts_Default_User_Preferences","sectionPermissionId":"change-setting-Accounts_Default_User_Preferences","settingId":"Accounts_Default_User_Preferences_idleTimeLimit","sorter":NumberInt(48)});
|
||||
db.getCollection("rocketchat_permissions").insert({"_id":"change-setting-Accounts_Default_User_Preferences_messageViewMode","_updatedAt":new Date(1591734395410),"group":"Accounts","groupPermissionId":"change-setting-Accounts","level":"settings","roles":[],"section":"Accounts_Default_User_Preferences","sectionPermissionId":"change-setting-Accounts_Default_User_Preferences","settingId":"Accounts_Default_User_Preferences_messageViewMode","sorter":NumberInt(71)});
|
||||
db.getCollection("rocketchat_permissions").insert({"_id":"change-setting-Accounts_Default_User_Preferences_mobileNotifications","_updatedAt":new Date(1591734395373),"group":"Accounts","groupPermissionId":"change-setting-Accounts","level":"settings","roles":[],"section":"Accounts_Default_User_Preferences","sectionPermissionId":"change-setting-Accounts_Default_User_Preferences","settingId":"Accounts_Default_User_Preferences_mobileNotifications","sorter":NumberInt(53)});
|
||||
db.getCollection("rocketchat_permissions").insert({"_id":"change-setting-Accounts_Default_User_Preferences_pushNotifications","_updatedAt":new Date(1591734395373),"group":"Accounts","groupPermissionId":"change-setting-Accounts","level":"settings","roles":[],"section":"Accounts_Default_User_Preferences","sectionPermissionId":"change-setting-Accounts_Default_User_Preferences","settingId":"Accounts_Default_User_Preferences_pushNotifications","sorter":NumberInt(53)});
|
||||
db.getCollection("rocketchat_permissions").insert({"_id":"change-setting-Accounts_Default_User_Preferences_muteFocusedConversations","_updatedAt":new Date(1591734395420),"group":"Accounts","groupPermissionId":"change-setting-Accounts","level":"settings","roles":[],"section":"Accounts_Default_User_Preferences","sectionPermissionId":"change-setting-Accounts_Default_User_Preferences","settingId":"Accounts_Default_User_Preferences_muteFocusedConversations","sorter":NumberInt(75)});
|
||||
db.getCollection("rocketchat_permissions").insert({"_id":"change-setting-Accounts_Default_User_Preferences_newMessageNotification","_updatedAt":new Date(1591734395417),"group":"Accounts","groupPermissionId":"change-setting-Accounts","level":"settings","roles":[],"section":"Accounts_Default_User_Preferences","sectionPermissionId":"change-setting-Accounts_Default_User_Preferences","settingId":"Accounts_Default_User_Preferences_newMessageNotification","sorter":NumberInt(74)});
|
||||
db.getCollection("rocketchat_permissions").insert({"_id":"change-setting-Accounts_Default_User_Preferences_newRoomNotification","_updatedAt":new Date(1591734395415),"group":"Accounts","groupPermissionId":"change-setting-Accounts","level":"settings","roles":[],"section":"Accounts_Default_User_Preferences","sectionPermissionId":"change-setting-Accounts_Default_User_Preferences","settingId":"Accounts_Default_User_Preferences_newRoomNotification","sorter":NumberInt(73)});
|
||||
|
|
|
@ -64,7 +64,7 @@ db.getCollection("rocketchat_settings").insert({"_id":"Accounts_Default_User_Pre
|
|||
db.getCollection("rocketchat_settings").insert({"_id":"Accounts_Default_User_Preferences_hideUsernames","_updatedAt":new Date(1591734377818),"autocomplete":true,"blocked":false,"createdAt":new Date(1584022362944),"group":"Accounts","hidden":false,"i18nDescription":"Accounts_Default_User_Preferences_hideUsernames_Description","i18nLabel":"Hide_usernames","packageValue":false,"public":true,"secret":false,"section":"Accounts_Default_User_Preferences","sorter":NumberInt(60),"ts":new Date(1589465206154),"type":"boolean","value":false,"valueSource":"packageValue"});
|
||||
db.getCollection("rocketchat_settings").insert({"_id":"Accounts_Default_User_Preferences_idleTimeLimit","_updatedAt":new Date(1591734377768),"autocomplete":true,"blocked":false,"createdAt":new Date(1584022362881),"group":"Accounts","hidden":false,"i18nDescription":"Accounts_Default_User_Preferences_idleTimeLimit_Description","i18nLabel":"Idle_Time_Limit","packageValue":NumberInt(300),"public":true,"secret":false,"section":"Accounts_Default_User_Preferences","sorter":NumberInt(48),"ts":new Date(1589465206085),"type":"int","value":NumberInt(300),"valueSource":"packageValue"});
|
||||
db.getCollection("rocketchat_settings").insert({"_id":"Accounts_Default_User_Preferences_messageViewMode","_updatedAt":new Date(1591734377861),"autocomplete":true,"blocked":false,"createdAt":new Date(1584022362992),"group":"Accounts","hidden":false,"i18nDescription":"Accounts_Default_User_Preferences_messageViewMode_Description","i18nLabel":"MessageBox_view_mode","packageValue":NumberInt(0),"public":true,"secret":false,"section":"Accounts_Default_User_Preferences","sorter":NumberInt(71),"ts":new Date(1589465206214),"type":"select","value":NumberInt(0),"valueSource":"packageValue","values":[{"key":NumberInt(0),"i18nLabel":"Normal"},{"key":NumberInt(1),"i18nLabel":"Cozy"},{"key":NumberInt(2),"i18nLabel":"Compact"}]});
|
||||
db.getCollection("rocketchat_settings").insert({"_id":"Accounts_Default_User_Preferences_mobileNotifications","_updatedAt":new Date(1591734377791),"autocomplete":true,"blocked":false,"createdAt":new Date(1584022362905),"group":"Accounts","hidden":false,"i18nDescription":"Accounts_Default_User_Preferences_mobileNotifications_Description","i18nLabel":"Accounts_Default_User_Preferences_mobileNotifications","packageValue":"all","public":true,"secret":false,"section":"Accounts_Default_User_Preferences","sorter":NumberInt(53),"ts":new Date(1589465206111),"type":"select","value":"all","valueSource":"packageValue","values":[{"key":"all","i18nLabel":"All_messages"},{"key":"mentions","i18nLabel":"Mentions"},{"key":"nothing","i18nLabel":"Nothing"}]});
|
||||
db.getCollection("rocketchat_settings").insert({"_id":"Accounts_Default_User_Preferences_pushNotifications","_updatedAt":new Date(1591734377791),"autocomplete":true,"blocked":false,"createdAt":new Date(1584022362905),"group":"Accounts","hidden":false,"i18nDescription":"Accounts_Default_User_Preferences_pushNotifications_Description","i18nLabel":"Accounts_Default_User_Preferences_pushNotifications","packageValue":"all","public":true,"secret":false,"section":"Accounts_Default_User_Preferences","sorter":NumberInt(53),"ts":new Date(1589465206111),"type":"select","value":"all","valueSource":"packageValue","values":[{"key":"all","i18nLabel":"All_messages"},{"key":"mentions","i18nLabel":"Mentions"},{"key":"nothing","i18nLabel":"Nothing"}]});
|
||||
db.getCollection("rocketchat_settings").insert({"_id":"Accounts_Default_User_Preferences_muteFocusedConversations","_updatedAt":new Date(1591734377881),"autocomplete":true,"blocked":false,"createdAt":new Date(1584022363009),"group":"Accounts","hidden":false,"i18nDescription":"Accounts_Default_User_Preferences_muteFocusedConversations_Description","i18nLabel":"Mute_Focused_Conversations","packageValue":true,"public":true,"secret":false,"section":"Accounts_Default_User_Preferences","sorter":NumberInt(75),"ts":new Date(1589465206242),"type":"boolean","value":true,"valueSource":"packageValue"});
|
||||
db.getCollection("rocketchat_settings").insert({"_id":"Accounts_Default_User_Preferences_newMessageNotification","_updatedAt":new Date(1591734377877),"autocomplete":true,"blocked":false,"createdAt":new Date(1584022363005),"group":"Accounts","hidden":false,"i18nDescription":"Accounts_Default_User_Preferences_newMessageNotification_Description","i18nLabel":"New_Message_Notification","packageValue":"chime","public":true,"secret":false,"section":"Accounts_Default_User_Preferences","sorter":NumberInt(74),"ts":new Date(1589465206236),"type":"select","value":"chime","valueSource":"packageValue","values":[{"key":"none","i18nLabel":"None"},{"key":"chime","i18nLabel":"Default"}]});
|
||||
db.getCollection("rocketchat_settings").insert({"_id":"Accounts_Default_User_Preferences_newRoomNotification","_updatedAt":new Date(1591734377868),"autocomplete":true,"blocked":false,"createdAt":new Date(1584022363000),"group":"Accounts","hidden":false,"i18nDescription":"Accounts_Default_User_Preferences_newRoomNotification_Description","i18nLabel":"New_Room_Notification","packageValue":"door","public":true,"secret":false,"section":"Accounts_Default_User_Preferences","sorter":NumberInt(73),"ts":new Date(1589465206232),"type":"select","value":"door","valueSource":"packageValue","values":[{"key":"none","i18nLabel":"None"},{"key":"door","i18nLabel":"Default"}]});
|
||||
|
|
|
@ -532,6 +532,8 @@ PODS:
|
|||
- Firebase/Crashlytics (~> 6.27.0)
|
||||
- React
|
||||
- RNFBApp
|
||||
- RNFileViewer (2.1.4):
|
||||
- React-Core
|
||||
- RNGestureHandler (1.10.3):
|
||||
- React-Core
|
||||
- RNImageCropPicker (0.36.3):
|
||||
|
@ -700,6 +702,7 @@ DEPENDENCIES:
|
|||
- "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)"
|
||||
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
|
||||
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
|
||||
- RNFileViewer (from `../node_modules/react-native-file-viewer`)
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
|
||||
- RNLocalize (from `../node_modules/react-native-localize`)
|
||||
|
@ -894,6 +897,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/@react-native-firebase/app"
|
||||
RNFBCrashlytics:
|
||||
:path: "../node_modules/@react-native-firebase/crashlytics"
|
||||
RNFileViewer:
|
||||
:path: "../node_modules/react-native-file-viewer"
|
||||
RNGestureHandler:
|
||||
:path: "../node_modules/react-native-gesture-handler"
|
||||
RNImageCropPicker:
|
||||
|
@ -1028,6 +1033,7 @@ SPEC CHECKSUMS:
|
|||
RNFBAnalytics: dae6d7b280ba61c96e1bbdd34aca3154388f025e
|
||||
RNFBApp: 6fd8a7e757135d4168bf033a8812c241af7363a0
|
||||
RNFBCrashlytics: 88de72c2476b5868a892d9523b89b86c527c540e
|
||||
RNFileViewer: 83cc066ad795b1f986791d03b56fe0ee14b6a69f
|
||||
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
|
||||
RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d
|
||||
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
"react-native-document-picker": "5.2.0",
|
||||
"react-native-easy-grid": "^0.2.2",
|
||||
"react-native-easy-toast": "^1.2.0",
|
||||
"react-native-file-viewer": "^2.1.4",
|
||||
"react-native-gesture-handler": "^1.10.3",
|
||||
"react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker",
|
||||
"react-native-image-progress": "^1.1.1",
|
||||
|
@ -147,6 +148,7 @@
|
|||
"@types/lodash": "^4.14.171",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-native": "^0.62.7",
|
||||
"@types/react-native-background-timer": "^2.0.0",
|
||||
"@types/react-native-config-reader": "^4.1.0",
|
||||
"@types/react-native-platform-touchable": "^1.1.2",
|
||||
"@types/react-native-scrollable-tab-view": "^0.10.2",
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -4372,6 +4372,11 @@
|
|||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-native-background-timer@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-native-background-timer/-/react-native-background-timer-2.0.0.tgz#c44c57f8fbca9d9d5521fdd72a8f55232b79381e"
|
||||
integrity sha512-y5VW82dL/ESOLg+5QQHyBdsFVA4ZklENxmOyxv8o06T+3HBG2JOSuz/CIPz1vKdB7dmWDGPZNuPosdtnp+xv2A==
|
||||
|
||||
"@types/react-native-config-reader@^4.1.0":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-native-config-reader/-/react-native-config-reader-4.1.0.tgz#33066cd0452b86b605b41bed47b38470dd85d428"
|
||||
|
@ -14254,6 +14259,11 @@ react-native-easy-toast@^1.2.0:
|
|||
dependencies:
|
||||
prop-types "^15.5.10"
|
||||
|
||||
react-native-file-viewer@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/react-native-file-viewer/-/react-native-file-viewer-2.1.4.tgz#987b2902f0f0ac87b42f3ac3d3037c8ae98f17a6"
|
||||
integrity sha512-G3ko9lmqxT+lWhsDNy2K3Jes6xSMsUvlYwuwnRCNk2wC6hgYMeoeaiwDt8R3CdON781hB6Ej1eu3ir1QATtHXg==
|
||||
|
||||
react-native-flipper@^0.34.0:
|
||||
version "0.34.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-flipper/-/react-native-flipper-0.34.0.tgz#7df1f38ba5d97a9321125fe0fccbe47d99e6fa1d"
|
||||
|
|
Loading…
Reference in New Issue