Merge branch 'develop' into new.add-discusions-roomactionsview
This commit is contained in:
commit
2b7f589847
|
@ -2,7 +2,7 @@ module.exports = {
|
||||||
settings: {
|
settings: {
|
||||||
'import/resolver': {
|
'import/resolver': {
|
||||||
node: {
|
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';
|
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));
|
jest.mock('../app/lib/database', () => jest.fn(() => null));
|
||||||
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
|
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ export const themes: any = {
|
||||||
previewBackground: '#1F2329',
|
previewBackground: '#1F2329',
|
||||||
previewTintColor: '#ffffff',
|
previewTintColor: '#ffffff',
|
||||||
backdropOpacity: 0.3,
|
backdropOpacity: 0.3,
|
||||||
|
attachmentLoadingOpacity: 0.7,
|
||||||
...mentions
|
...mentions
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
|
@ -112,6 +113,7 @@ export const themes: any = {
|
||||||
previewBackground: '#030b1b',
|
previewBackground: '#030b1b',
|
||||||
previewTintColor: '#ffffff',
|
previewTintColor: '#ffffff',
|
||||||
backdropOpacity: 0.9,
|
backdropOpacity: 0.9,
|
||||||
|
attachmentLoadingOpacity: 0.3,
|
||||||
...mentions
|
...mentions
|
||||||
},
|
},
|
||||||
black: {
|
black: {
|
||||||
|
@ -159,6 +161,7 @@ export const themes: any = {
|
||||||
previewBackground: '#000000',
|
previewBackground: '#000000',
|
||||||
previewTintColor: '#ffffff',
|
previewTintColor: '#ffffff',
|
||||||
backdropOpacity: 0.9,
|
backdropOpacity: 0.9,
|
||||||
|
attachmentLoadingOpacity: 0.3,
|
||||||
...mentions
|
...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;
|
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>) => (
|
forwardRef((props: any, ref: ForwardedRef<any>) => (
|
||||||
<Consumer>{(contexts: any) => <Component {...props} {...contexts} ref={ref} />}</Consumer>
|
<Consumer>{(contexts: any) => <Component {...props} {...contexts} ref={ref} />}</Consumer>
|
||||||
));
|
));
|
||||||
|
|
|
@ -16,7 +16,7 @@ export interface IAvatar {
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
getCustomEmoji(): any;
|
getCustomEmoji(): any;
|
||||||
avatarETag: string;
|
avatarETag: string;
|
||||||
isStatic: boolean;
|
isStatic: boolean | string;
|
||||||
rid: string;
|
rid: string;
|
||||||
blockUnauthenticatedAccess: boolean;
|
blockUnauthenticatedAccess: boolean;
|
||||||
serverVersion: string;
|
serverVersion: string;
|
||||||
|
|
|
@ -27,13 +27,11 @@ export const FormContainerInner = ({ children }: { children: React.ReactNode }):
|
||||||
);
|
);
|
||||||
|
|
||||||
const FormContainer = ({ children, theme, testID, ...props }: IFormContainer): JSX.Element => (
|
const FormContainer = ({ children, theme, testID, ...props }: IFormContainer): JSX.Element => (
|
||||||
// @ts-ignore
|
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||||
contentContainerStyle={sharedStyles.container}
|
contentContainerStyle={sharedStyles.container}
|
||||||
keyboardVerticalOffset={128}>
|
keyboardVerticalOffset={128}>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
{/* @ts-ignore*/}
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={sharedStyles.container}
|
style={sharedStyles.container}
|
||||||
contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}
|
contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import I18n from '../../i18n';
|
||||||
|
|
||||||
interface IPasscodeChoose {
|
interface IPasscodeChoose {
|
||||||
theme: string;
|
theme: string;
|
||||||
force: boolean;
|
force?: boolean;
|
||||||
finishProcess: Function;
|
finishProcess: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { StyleProp, TextStyle } from 'react-native';
|
||||||
|
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { STATUS_COLORS } from '../../constants/colors';
|
import { STATUS_COLORS } from '../../constants/colors';
|
||||||
|
@ -6,14 +7,14 @@ import { STATUS_COLORS } from '../../constants/colors';
|
||||||
interface IStatus {
|
interface IStatus {
|
||||||
status: string;
|
status: string;
|
||||||
size: number;
|
size: number;
|
||||||
style: any;
|
style?: StyleProp<TextStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: IStatus) => {
|
const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: IStatus) => {
|
||||||
const name = `status-${status}`;
|
const name = `status-${status}`;
|
||||||
const isNameValid = CustomIcon.hasIcon(name);
|
const isNameValid = CustomIcon.hasIcon(name);
|
||||||
const iconName = isNameValid ? name : 'status-offline';
|
const iconName = isNameValid ? name : 'status-offline';
|
||||||
const calculatedStyle = [
|
const calculatedStyle: StyleProp<TextStyle> = [
|
||||||
{
|
{
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
|
|
|
@ -58,7 +58,7 @@ interface IRCTextInputProps extends TextInputProps {
|
||||||
};
|
};
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
containerStyle?: StyleProp<ViewStyle>;
|
containerStyle?: StyleProp<ViewStyle>;
|
||||||
inputStyle?: TextStyle;
|
inputStyle?: StyleProp<TextStyle>;
|
||||||
inputRef?: React.Ref<unknown>;
|
inputRef?: React.Ref<unknown>;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
iconLeft?: string;
|
iconLeft?: string;
|
||||||
|
|
|
@ -8,10 +8,10 @@ import ActivityIndicator from '../../ActivityIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
interface IInput {
|
interface IInput {
|
||||||
children: JSX.Element;
|
children?: JSX.Element;
|
||||||
onPress: Function;
|
onPress: Function;
|
||||||
theme: string;
|
theme: string;
|
||||||
inputStyle: object;
|
inputStyle?: object;
|
||||||
disabled?: boolean | object;
|
disabled?: boolean | object;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
loading?: boolean;
|
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 { StyleSheet, Text, View } from 'react-native';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { transparentize } from 'color2k';
|
import { transparentize } from 'color2k';
|
||||||
|
@ -11,6 +11,9 @@ import openLink from '../../utils/openLink';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
|
import { fileDownloadAndPreview } from '../../utils/fileDownload';
|
||||||
|
import { formatAttachmentUrl } from '../../lib/utils';
|
||||||
|
import RCActivityIndicator from '../ActivityIndicator';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
|
@ -28,6 +31,9 @@ const styles = StyleSheet.create({
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
padding: 15
|
padding: 15
|
||||||
},
|
},
|
||||||
|
backdrop: {
|
||||||
|
...StyleSheet.absoluteFillObject
|
||||||
|
},
|
||||||
authorContainer: {
|
authorContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -120,7 +126,7 @@ interface IMessageFields {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageReply {
|
interface IMessageReply {
|
||||||
attachment: Partial<IMessageReplyAttachment>;
|
attachment: IMessageReplyAttachment;
|
||||||
timeFormat: string;
|
timeFormat: string;
|
||||||
index: number;
|
index: number;
|
||||||
theme: string;
|
theme: string;
|
||||||
|
@ -209,12 +215,14 @@ const Fields = React.memo(
|
||||||
|
|
||||||
const Reply = React.memo(
|
const Reply = React.memo(
|
||||||
({ attachment, timeFormat, index, getCustomEmoji, theme }: IMessageReply) => {
|
({ attachment, timeFormat, index, getCustomEmoji, theme }: IMessageReply) => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = async () => {
|
||||||
let url = attachment.title_link || attachment.author_link;
|
let url = attachment.title_link || attachment.author_link;
|
||||||
if (attachment.message_link) {
|
if (attachment.message_link) {
|
||||||
return jumpToMessage(attachment.message_link);
|
return jumpToMessage(attachment.message_link);
|
||||||
|
@ -223,10 +231,11 @@ const Reply = React.memo(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (attachment.type === 'file') {
|
if (attachment.type === 'file') {
|
||||||
if (!url.startsWith('http')) {
|
setLoading(true);
|
||||||
url = `${baseUrl}${url}`;
|
url = formatAttachmentUrl(attachment.title_link, user.id, user.token, baseUrl);
|
||||||
}
|
await fileDownloadAndPreview(url, attachment);
|
||||||
url = `${url}?rc_uid=${user.id}&rc_token=${user.token}`;
|
setLoading(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
openLink(url, theme);
|
openLink(url, theme);
|
||||||
};
|
};
|
||||||
|
@ -254,12 +263,23 @@ const Reply = React.memo(
|
||||||
borderColor
|
borderColor
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}>
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
|
disabled={loading}>
|
||||||
<View style={styles.attachmentContainer}>
|
<View style={styles.attachmentContainer}>
|
||||||
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
|
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
|
||||||
<UrlImage image={attachment.thumb_url} />
|
<UrlImage image={attachment.thumb_url} />
|
||||||
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||||
<Fields 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>
|
</View>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
{/* @ts-ignore*/}
|
{/* @ts-ignore*/}
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
|
|
||||||
import Touchable from './Touchable';
|
import Touchable from './Touchable';
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import openLink from '../../utils/openLink';
|
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { formatAttachmentUrl } from '../../lib/utils';
|
import { formatAttachmentUrl } from '../../lib/utils';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import MessageContext from './Context';
|
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 SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
|
||||||
const isTypeSupported = (type: any) => SUPPORTED_TYPES.indexOf(type) !== -1;
|
const isTypeSupported = (type: any) => SUPPORTED_TYPES.indexOf(type) !== -1;
|
||||||
|
@ -27,6 +31,9 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
interface IMessageVideo {
|
interface IMessageVideo {
|
||||||
file: {
|
file: {
|
||||||
|
title: string;
|
||||||
|
title_link: string;
|
||||||
|
type: string;
|
||||||
video_type: string;
|
video_type: string;
|
||||||
video_url: string;
|
video_url: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -39,15 +46,34 @@ interface IMessageVideo {
|
||||||
const Video = React.memo(
|
const Video = React.memo(
|
||||||
({ file, showAttachment, getCustomEmoji, theme }: IMessageVideo) => {
|
({ file, showAttachment, getCustomEmoji, theme }: IMessageVideo) => {
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const onPress = () => {
|
const onPress = async () => {
|
||||||
if (isTypeSupported(file.video_type)) {
|
if (isTypeSupported(file.video_type)) {
|
||||||
return showAttachment(file);
|
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 (
|
return (
|
||||||
|
@ -56,7 +82,11 @@ const Video = React.memo(
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
style={[styles.button, { backgroundColor: themes[theme].videoBackground }]}
|
style={[styles.button, { backgroundColor: themes[theme].videoBackground }]}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}>
|
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>
|
</Touchable>
|
||||||
{/* @ts-ignore*/}
|
{/* @ts-ignore*/}
|
||||||
<Markdown
|
<Markdown
|
||||||
|
|
|
@ -59,6 +59,7 @@ export interface IUser {
|
||||||
export type UserMention = Pick<IUser, '_id' | 'username' | 'name'>;
|
export type UserMention = Pick<IUser, '_id' | 'username' | 'name'>;
|
||||||
|
|
||||||
export interface IMessageContent {
|
export interface IMessageContent {
|
||||||
|
_id: string;
|
||||||
isTemp: boolean;
|
isTemp: boolean;
|
||||||
isInfo: boolean;
|
isInfo: boolean;
|
||||||
tmid: string;
|
tmid: string;
|
||||||
|
|
|
@ -9,3 +9,7 @@ declare module '@rocket.chat/ui-kit';
|
||||||
declare module '@rocket.chat/sdk';
|
declare module '@rocket.chat/sdk';
|
||||||
declare module 'react-native-config-reader';
|
declare module 'react-native-config-reader';
|
||||||
declare module 'react-native-keycommands';
|
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",
|
"No_canned_responses": "No canned responses",
|
||||||
"Send_email_confirmation": "Send email confirmation",
|
"Send_email_confirmation": "Send email confirmation",
|
||||||
"sending_email_confirmation": "sending 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",
|
"Sharing": "Compartilhando",
|
||||||
"No_canned_responses": "Não há respostas predefinidas",
|
"No_canned_responses": "Não há respostas predefinidas",
|
||||||
"Send_email_confirmation": "Enviar email de confirmação",
|
"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 call = /^(https:\/\/)?jitsi.rocket.chat\//;
|
||||||
|
const fullURL = url;
|
||||||
|
|
||||||
if (url.match(call)) {
|
if (url.match(call)) {
|
||||||
url = url.replace(call, '').trim();
|
url = url.replace(call, '').trim();
|
||||||
if (url) {
|
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}`;
|
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) {
|
async function callJitsi(room, onlyAudio = false) {
|
||||||
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
||||||
const url = await jitsiURL.call(this, { room });
|
const url = await jitsiURL.call(this, { room });
|
||||||
|
|
|
@ -54,7 +54,7 @@ import loadMissedMessages from './methods/loadMissedMessages';
|
||||||
import loadThreadMessages from './methods/loadThreadMessages';
|
import loadThreadMessages from './methods/loadThreadMessages';
|
||||||
import sendMessage, { resendMessage } from './methods/sendMessage';
|
import sendMessage, { resendMessage } from './methods/sendMessage';
|
||||||
import { cancelUpload, isUploadActive, sendFileMessage } from './methods/sendFileMessage';
|
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 logout, { removeServer } from './methods/logout';
|
||||||
import UserPreferences from './userPreferences';
|
import UserPreferences from './userPreferences';
|
||||||
import { Encryption } from './encryption';
|
import { Encryption } from './encryption';
|
||||||
|
@ -76,6 +76,7 @@ const RocketChat = {
|
||||||
CURRENT_SERVER,
|
CURRENT_SERVER,
|
||||||
CERTIFICATE_KEY,
|
CERTIFICATE_KEY,
|
||||||
callJitsi,
|
callJitsi,
|
||||||
|
callJitsiWithoutServer,
|
||||||
async subscribeRooms() {
|
async subscribeRooms() {
|
||||||
if (!this.roomsSub) {
|
if (!this.roomsSub) {
|
||||||
try {
|
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';
|
import { types } from './types';
|
||||||
|
|
||||||
export const ImageComponent = (type: string) => {
|
export const ImageComponent = (type?: string): React.ComponentType<Partial<Image> | FastImageProps> => {
|
||||||
let Component;
|
let Component;
|
||||||
if (type === types.REACT_NATIVE_IMAGE) {
|
if (type === types.REACT_NATIVE_IMAGE) {
|
||||||
const { Image } = require('react-native');
|
const { Image } = require('react-native');
|
||||||
|
|
|
@ -16,13 +16,14 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
interface IImageViewer {
|
interface IImageViewer {
|
||||||
uri: string;
|
uri: string;
|
||||||
imageComponentType: string;
|
imageComponentType?: string;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
theme: string;
|
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 backgroundColor = themes[theme].previewBackground;
|
||||||
const Component = ImageComponent(imageComponentType);
|
const Component = ImageComponent(imageComponentType);
|
||||||
return (
|
return (
|
|
@ -1,5 +1,3 @@
|
||||||
// @ts-ignore
|
|
||||||
// eslint-disable-next-line import/no-unresolved
|
|
||||||
export * from './ImageViewer';
|
export * from './ImageViewer';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './ImageComponent';
|
export * from './ImageComponent';
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import React from 'react';
|
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';
|
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
|
|
||||||
interface IKeyboardViewProps {
|
interface IKeyboardViewProps extends KeyboardAwareScrollViewProps {
|
||||||
style: any;
|
|
||||||
contentContainerStyle: any;
|
|
||||||
keyboardVerticalOffset: number;
|
keyboardVerticalOffset: number;
|
||||||
scrollEnabled: boolean;
|
scrollEnabled?: boolean;
|
||||||
children: JSX.Element;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class KeyboardView extends React.PureComponent<IKeyboardViewProps, any> {
|
export default class KeyboardView extends React.PureComponent<IKeyboardViewProps, any> {
|
||||||
|
@ -22,9 +20,7 @@ export default class KeyboardView extends React.PureComponent<IKeyboardViewProps
|
||||||
contentContainerStyle={contentContainerStyle}
|
contentContainerStyle={contentContainerStyle}
|
||||||
scrollEnabled={scrollEnabled}
|
scrollEnabled={scrollEnabled}
|
||||||
alwaysBounceVertical={false}
|
alwaysBounceVertical={false}
|
||||||
extraHeight={keyboardVerticalOffset}
|
extraHeight={keyboardVerticalOffset}>
|
||||||
// @ts-ignore
|
|
||||||
behavior='position'>
|
|
||||||
{children}
|
{children}
|
||||||
</KeyboardAwareScrollView>
|
</KeyboardAwareScrollView>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// @ts-ignore
|
// @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 Avatar from '../containers/Avatar';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from '../lib/Icons';
|
||||||
|
@ -44,8 +44,8 @@ interface IUserItem {
|
||||||
username: string;
|
username: string;
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
testID: string;
|
testID: string;
|
||||||
onLongPress(): void;
|
onLongPress?: () => void;
|
||||||
style: any;
|
style?: StyleProp<ViewStyle>;
|
||||||
icon: string;
|
icon: string;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,6 +122,11 @@ const handleOpen = function* handleOpen({ params }) {
|
||||||
host = id;
|
host = id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!host && params.fullURL) {
|
||||||
|
RocketChat.callJitsiWithoutServer(params.fullURL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.type === 'oauth') {
|
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_CLEAR_LOCAL_SERVER_CACHE: 'se_clear_local_server_cache',
|
||||||
SE_LOG_OUT: 'se_log_out',
|
SE_LOG_OUT: 'se_log_out',
|
||||||
|
|
||||||
|
// USER PREFERENCE VIEW
|
||||||
|
UP_GO_USER_NOTIFICATION_PREF: 'up_go_user_notification_pref',
|
||||||
|
|
||||||
// SECURITY PRIVACY VIEW
|
// SECURITY PRIVACY VIEW
|
||||||
SP_GO_E2EENCRYPTIONSECURITY: 'sp_go_e2e_encryption_security',
|
SP_GO_E2EENCRYPTIONSECURITY: 'sp_go_e2e_encryption_security',
|
||||||
SP_GO_SCREENLOCKCONFIG: 'sp_go_screen_lock_cfg',
|
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 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 { connect } from 'react-redux';
|
||||||
|
|
||||||
import * as List from '../containers/List';
|
import * as List from '../containers/List';
|
||||||
|
@ -9,8 +10,16 @@ import * as HeaderButton from '../containers/HeaderButton';
|
||||||
import SafeAreaView from '../containers/SafeAreaView';
|
import SafeAreaView from '../containers/SafeAreaView';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
|
||||||
const setHeader = (navigation, isMasterDetail) => {
|
type TNavigation = StackNavigationProp<any, 'AddChannelTeamView'>;
|
||||||
const options = {
|
|
||||||
|
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')
|
headerTitle: I18n.t('Add_Channel_to_Team')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +30,7 @@ const setHeader = (navigation, isMasterDetail) => {
|
||||||
navigation.setOptions(options);
|
navigation.setOptions(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddChannelTeamView = ({ navigation, route, isMasterDetail }) => {
|
const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTeamView) => {
|
||||||
const { teamId, teamChannels } = route.params;
|
const { teamId, teamChannels } = route.params;
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
@ -66,13 +75,7 @@ const AddChannelTeamView = ({ navigation, route, isMasterDetail }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AddChannelTeamView.propTypes = {
|
const mapStateToProps = (state: any) => ({
|
||||||
route: PropTypes.object,
|
|
||||||
navigation: PropTypes.object,
|
|
||||||
isMasterDetail: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
isMasterDetail: state.app.isMasterDetail
|
isMasterDetail: state.app.isMasterDetail
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
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 { FlatList, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
@ -21,18 +22,27 @@ import { goRoom } from '../utils/goRoom';
|
||||||
import { showErrorAlert } from '../utils/info';
|
import { showErrorAlert } from '../utils/info';
|
||||||
import debounce from '../utils/debounce';
|
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;
|
const QUERY_SIZE = 50;
|
||||||
|
|
||||||
class AddExistingChannelView extends React.Component {
|
class AddExistingChannelView extends React.Component<IAddExistingChannelViewProps, IAddExistingChannelViewState> {
|
||||||
static propTypes = {
|
private teamId: string;
|
||||||
navigation: PropTypes.object,
|
constructor(props: IAddExistingChannelViewProps) {
|
||||||
route: PropTypes.object,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
isMasterDetail: PropTypes.bool,
|
|
||||||
addTeamChannelPermission: PropTypes.array
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.query();
|
this.query();
|
||||||
this.teamId = props.route?.params?.teamId;
|
this.teamId = props.route?.params?.teamId;
|
||||||
|
@ -49,7 +59,7 @@ class AddExistingChannelView extends React.Component {
|
||||||
const { navigation, isMasterDetail } = this.props;
|
const { navigation, isMasterDetail } = this.props;
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
|
|
||||||
const options = {
|
const options: StackNavigationOptions = {
|
||||||
headerTitle: I18n.t('Add_Existing_Channel')
|
headerTitle: I18n.t('Add_Existing_Channel')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -82,9 +92,10 @@ class AddExistingChannelView extends React.Component {
|
||||||
)
|
)
|
||||||
.fetch();
|
.fetch();
|
||||||
|
|
||||||
const asyncFilter = async channelsArray => {
|
// TODO: Refactor with Room Model
|
||||||
|
const asyncFilter = async (channelsArray: any[]) => {
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
channelsArray.map(async channel => {
|
channelsArray.map(async (channel: any) => {
|
||||||
if (channel.prid) {
|
if (channel.prid) {
|
||||||
return false;
|
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);
|
const channelFiltered = await asyncFilter(channels);
|
||||||
this.setState({ channels: channelFiltered });
|
this.setState({ channels: channelFiltered });
|
||||||
|
@ -105,7 +116,7 @@ class AddExistingChannelView extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onSearchChangeText = debounce(text => {
|
onSearchChangeText = debounce((text: string) => {
|
||||||
this.query(text);
|
this.query(text);
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
|
@ -126,7 +137,7 @@ class AddExistingChannelView extends React.Component {
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
goRoom({ item: result, isMasterDetail });
|
goRoom({ item: result, isMasterDetail });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
logEvent(events.CT_ADD_ROOM_TO_TEAM_F);
|
logEvent(events.CT_ADD_ROOM_TO_TEAM_F);
|
||||||
showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {});
|
showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {});
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
|
@ -137,17 +148,17 @@ class AddExistingChannelView extends React.Component {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
|
<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>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
isChecked = rid => {
|
isChecked = (rid: string) => {
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
return selected.includes(rid);
|
return selected.includes(rid);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleChannel = rid => {
|
toggleChannel = (rid: string) => {
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
|
|
||||||
animateNextTransition();
|
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);
|
const isChecked = this.isChecked(item.rid);
|
||||||
// TODO: reuse logic inside RoomTypeIcon
|
// TODO: reuse logic inside RoomTypeIcon
|
||||||
const icon = item.t === 'p' && !item.teamId ? 'channel-private' : 'channel-public';
|
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,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
addTeamChannelPermission: state.permissions['add-team-channel']
|
addTeamChannelPermission: state.permissions['add-team-channel']
|
||||||
});
|
});
|
|
@ -1,12 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { PermissionsAndroid, StyleSheet, View } from 'react-native';
|
import { PermissionsAndroid, StyleSheet, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
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 CameraRoll from '@react-native-community/cameraroll';
|
||||||
import * as mime from 'react-native-mime-types';
|
import * as mime from 'react-native-mime-types';
|
||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import RNFetchBlob from 'rn-fetch-blob';
|
||||||
import { Video } from 'expo-av';
|
import { Video } from 'expo-av';
|
||||||
import SHA256 from 'js-sha256';
|
import { sha256 } from 'js-sha256';
|
||||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
import { LISTENER } from '../containers/Toast';
|
import { LISTENER } from '../containers/Toast';
|
||||||
|
@ -30,23 +31,41 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class AttachmentView extends React.Component {
|
// TODO: refactor when react-navigation is done
|
||||||
static propTypes = {
|
export interface IAttachment {
|
||||||
navigation: PropTypes.object,
|
title: string;
|
||||||
route: PropTypes.object,
|
title_link?: string;
|
||||||
theme: PropTypes.string,
|
image_url?: string;
|
||||||
baseUrl: PropTypes.string,
|
image_type?: string;
|
||||||
width: PropTypes.number,
|
video_url?: string;
|
||||||
height: PropTypes.number,
|
video_type?: string;
|
||||||
insets: PropTypes.object,
|
}
|
||||||
user: PropTypes.shape({
|
|
||||||
id: PropTypes.string,
|
|
||||||
token: PropTypes.string
|
|
||||||
}),
|
|
||||||
Allow_Save_Media_to_Gallery: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
super(props);
|
||||||
const attachment = props.route.params?.attachment;
|
const attachment = props.route.params?.attachment;
|
||||||
this.state = { attachment, loading: true };
|
this.state = { attachment, loading: true };
|
||||||
|
@ -79,21 +98,9 @@ class AttachmentView extends React.Component {
|
||||||
}
|
}
|
||||||
const options = {
|
const options = {
|
||||||
title,
|
title,
|
||||||
headerLeft: () => (
|
headerLeft: () => <HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} />,
|
||||||
<HeaderButton.CloseModal
|
|
||||||
testID='close-attachment-view'
|
|
||||||
navigation={navigation}
|
|
||||||
buttonStyle={{ color: themes[theme].previewTintColor }}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
headerRight: () =>
|
headerRight: () =>
|
||||||
Allow_Save_Media_to_Gallery ? (
|
Allow_Save_Media_to_Gallery ? <HeaderButton.Download testID='save-image' onPress={this.handleSave} /> : null,
|
||||||
<HeaderButton.Download
|
|
||||||
testID='save-image'
|
|
||||||
onPress={this.handleSave}
|
|
||||||
buttonStyle={{ color: themes[theme].previewTintColor }}
|
|
||||||
/>
|
|
||||||
) : null,
|
|
||||||
headerBackground: () => <View style={{ flex: 1, backgroundColor: themes[theme].previewBackground }} />,
|
headerBackground: () => <View style={{ flex: 1, backgroundColor: themes[theme].previewBackground }} />,
|
||||||
headerTintColor: themes[theme].previewTintColor,
|
headerTintColor: themes[theme].previewTintColor,
|
||||||
headerTitleStyle: { color: themes[theme].previewTintColor, marginHorizontal: 10 }
|
headerTitleStyle: { color: themes[theme].previewTintColor, marginHorizontal: 10 }
|
||||||
|
@ -101,7 +108,7 @@ class AttachmentView extends React.Component {
|
||||||
navigation.setOptions(options);
|
navigation.setOptions(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
getVideoRef = ref => (this.videoRef = ref);
|
getVideoRef = (ref: Video) => (this.videoRef = ref);
|
||||||
|
|
||||||
handleSave = async () => {
|
handleSave = async () => {
|
||||||
const { attachment } = this.state;
|
const { attachment } = this.state;
|
||||||
|
@ -113,7 +120,8 @@ class AttachmentView extends React.Component {
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
const rationale = {
|
const rationale = {
|
||||||
title: I18n.t('Write_External_Permission'),
|
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);
|
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, rationale);
|
||||||
if (!(result || result === PermissionsAndroid.RESULTS.GRANTED)) {
|
if (!(result || result === PermissionsAndroid.RESULTS.GRANTED)) {
|
||||||
|
@ -125,7 +133,7 @@ class AttachmentView extends React.Component {
|
||||||
try {
|
try {
|
||||||
const extension = image_url ? `.${mime.extension(image_type) || 'jpg'}` : `.${mime.extension(video_type) || 'mp4'}`;
|
const extension = image_url ? `.${mime.extension(image_type) || 'jpg'}` : `.${mime.extension(video_type) || 'mp4'}`;
|
||||||
const documentDir = `${RNFetchBlob.fs.dirs.DocumentDir}/`;
|
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);
|
const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment);
|
||||||
await CameraRoll.save(path, { album: 'Rocket.Chat' });
|
await CameraRoll.save(path, { album: 'Rocket.Chat' });
|
||||||
await file.flush();
|
await file.flush();
|
||||||
|
@ -136,7 +144,7 @@ class AttachmentView extends React.Component {
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
renderImage = uri => {
|
renderImage = (uri: string) => {
|
||||||
const { theme, width, height, insets } = this.props;
|
const { theme, width, height, insets } = this.props;
|
||||||
const headerHeight = getHeaderHeight(width > height);
|
const headerHeight = getHeaderHeight(width > height);
|
||||||
return (
|
return (
|
||||||
|
@ -150,7 +158,7 @@ class AttachmentView extends React.Component {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderVideo = uri => (
|
renderVideo = (uri: string) => (
|
||||||
<Video
|
<Video
|
||||||
source={{ uri }}
|
source={{ uri }}
|
||||||
rate={1.0}
|
rate={1.0}
|
||||||
|
@ -190,7 +198,7 @@ class AttachmentView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state: any) => ({
|
||||||
baseUrl: state.server.server,
|
baseUrl: state.server.server,
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
Allow_Save_Media_to_Gallery: state.settings.Allow_Save_Media_to_Gallery ?? true
|
Allow_Save_Media_to_Gallery: state.settings.Allow_Save_Media_to_Gallery ?? true
|
|
@ -1,15 +1,13 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Orientation from 'react-native-orientation-locker';
|
import Orientation from 'react-native-orientation-locker';
|
||||||
import useDeepCompareEffect from 'use-deep-compare-effect';
|
import useDeepCompareEffect from 'use-deep-compare-effect';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import { withTheme } from '../theme';
|
import { useTheme } from '../theme';
|
||||||
import { hasNotch, isTablet } from '../utils/deviceInfo';
|
import { hasNotch, isTablet } from '../utils/deviceInfo';
|
||||||
import { TYPE } from '../containers/Passcode/constants';
|
|
||||||
import { PasscodeChoose } from '../containers/Passcode';
|
import { PasscodeChoose } from '../containers/Passcode';
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
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 [visible, setVisible] = useState(false);
|
||||||
const [data, setData] = useState({});
|
const [data, setData] = useState<Partial<IArgs>>({});
|
||||||
|
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
useDeepCompareEffect(() => {
|
useDeepCompareEffect(() => {
|
||||||
if (!isEmpty(data)) {
|
if (!isEmpty(data)) {
|
||||||
|
@ -39,11 +45,11 @@ const ChangePasscodeView = React.memo(({ theme }) => {
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const showChangePasscode = args => {
|
const showChangePasscode = (args: IArgs) => {
|
||||||
setData(args);
|
setData(args);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = passcode => {
|
const onSubmit = (passcode: string) => {
|
||||||
const { submit } = data;
|
const { submit } = data;
|
||||||
if (submit) {
|
if (submit) {
|
||||||
submit(passcode);
|
submit(passcode);
|
||||||
|
@ -74,7 +80,7 @@ const ChangePasscodeView = React.memo(({ theme }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal useNativeDriver isVisible={visible} hideModalContentWhileAnimating style={styles.modal}>
|
<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 ? (
|
{!data?.force ? (
|
||||||
<Touchable onPress={onCancel} style={styles.close}>
|
<Touchable onPress={onCancel} style={styles.close}>
|
||||||
<CustomIcon name='close' color={themes[theme].passcodePrimary} size={30} />
|
<CustomIcon name='close' color={themes[theme].passcodePrimary} size={30} />
|
||||||
|
@ -84,8 +90,4 @@ const ChangePasscodeView = React.memo(({ theme }) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ChangePasscodeView.propTypes = {
|
export default ChangePasscodeView;
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withTheme(ChangePasscodeView);
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import { Dispatch } from 'redux';
|
||||||
import { FlatList, ScrollView, StyleSheet, Switch, Text, View } from 'react-native';
|
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 { dequal } from 'dequal';
|
||||||
|
|
||||||
import * as List from '../containers/List';
|
import * as List from '../containers/List';
|
||||||
|
@ -66,30 +68,59 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class CreateChannelView extends React.Component {
|
interface IOtherUser {
|
||||||
static propTypes = {
|
_id: string;
|
||||||
navigation: PropTypes.object,
|
name: string;
|
||||||
route: PropTypes.object,
|
fname: string;
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
super(props);
|
||||||
const { route } = this.props;
|
const { route } = this.props;
|
||||||
const isTeam = route?.params?.isTeam || false;
|
const isTeam = route?.params?.isTeam || false;
|
||||||
|
@ -110,7 +141,7 @@ class CreateChannelView extends React.Component {
|
||||||
this.handleHasPermission();
|
this.handleHasPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps: ICreateChannelViewProps, nextState: ICreateChannelViewState) {
|
||||||
const { channelName, type, readOnly, broadcast, encrypted, permissions } = this.state;
|
const { channelName, type, readOnly, broadcast, encrypted, permissions } = this.state;
|
||||||
const { users, isFetching, encryptionEnabled, theme, createPublicChannelPermission, createPrivateChannelPermission } =
|
const { users, isFetching, encryptionEnabled, theme, createPublicChannelPermission, createPrivateChannelPermission } =
|
||||||
this.props;
|
this.props;
|
||||||
|
@ -153,7 +184,7 @@ class CreateChannelView extends React.Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps: ICreateChannelViewProps) {
|
||||||
const { createPublicChannelPermission, createPrivateChannelPermission } = this.props;
|
const { createPublicChannelPermission, createPrivateChannelPermission } = this.props;
|
||||||
if (
|
if (
|
||||||
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
|
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
|
||||||
|
@ -172,7 +203,7 @@ class CreateChannelView extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleRightButton = channelName => {
|
toggleRightButton = (channelName: string) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerRight: () =>
|
headerRight: () =>
|
||||||
|
@ -184,7 +215,7 @@ class CreateChannelView extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeText = channelName => {
|
onChangeText = (channelName: string) => {
|
||||||
this.toggleRightButton(channelName);
|
this.toggleRightButton(channelName);
|
||||||
this.setState({ channelName });
|
this.setState({ channelName });
|
||||||
};
|
};
|
||||||
|
@ -215,13 +246,13 @@ class CreateChannelView extends React.Component {
|
||||||
Review.pushPositiveEvent();
|
Review.pushPositiveEvent();
|
||||||
};
|
};
|
||||||
|
|
||||||
removeUser = user => {
|
removeUser = (user: IOtherUser) => {
|
||||||
logEvent(events.CR_REMOVE_USER);
|
logEvent(events.CR_REMOVE_USER);
|
||||||
const { removeUser } = this.props;
|
const { removeUser } = this.props;
|
||||||
removeUser(user);
|
removeUser(user);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderSwitch = ({ id, value, label, onValueChange, disabled = false }) => {
|
renderSwitch = ({ id, value, label, onValueChange, disabled = false }: ISwitch) => {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
<View style={[styles.switchContainer, { backgroundColor: themes[theme].backgroundColor }]}>
|
<View style={[styles.switchContainer, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
|
@ -253,7 +284,7 @@ class CreateChannelView extends React.Component {
|
||||||
value: permissions[1] ? type : false,
|
value: permissions[1] ? type : false,
|
||||||
disabled: isDisabled,
|
disabled: isDisabled,
|
||||||
label: isTeam ? 'Private_Team' : 'Private_Channel',
|
label: isTeam ? 'Private_Team' : 'Private_Channel',
|
||||||
onValueChange: value => {
|
onValueChange: (value: boolean) => {
|
||||||
logEvent(events.CR_TOGGLE_TYPE);
|
logEvent(events.CR_TOGGLE_TYPE);
|
||||||
// If we set the channel as public, encrypted status should be false
|
// If we set the channel as public, encrypted status should be false
|
||||||
this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted }));
|
this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted }));
|
||||||
|
@ -313,8 +344,8 @@ class CreateChannelView extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }: { item: IOtherUser }) => {
|
||||||
const { baseUrl, user, theme } = this.props;
|
const { theme } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserItem
|
<UserItem
|
||||||
|
@ -323,8 +354,6 @@ class CreateChannelView extends React.Component {
|
||||||
onPress={() => this.removeUser(item)}
|
onPress={() => this.removeUser(item)}
|
||||||
testID={`create-channel-view-item-${item.name}`}
|
testID={`create-channel-view-item-${item.name}`}
|
||||||
icon='check'
|
icon='check'
|
||||||
baseUrl={baseUrl}
|
|
||||||
user={user}
|
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -348,7 +377,6 @@ class CreateChannelView extends React.Component {
|
||||||
]}
|
]}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
ItemSeparatorComponent={List.Separator}
|
ItemSeparatorComponent={List.Separator}
|
||||||
enableEmptySections
|
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -371,7 +399,6 @@ class CreateChannelView extends React.Component {
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus
|
autoFocus
|
||||||
style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]}
|
style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]}
|
||||||
label={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
|
|
||||||
value={channelName}
|
value={channelName}
|
||||||
onChangeText={this.onChangeText}
|
onChangeText={this.onChangeText}
|
||||||
placeholder={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
|
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,
|
baseUrl: state.server.server,
|
||||||
isFetching: state.createChannel.isFetching,
|
isFetching: state.createChannel.isFetching,
|
||||||
encryptionEnabled: state.encryption.enabled,
|
encryptionEnabled: state.encryption.enabled,
|
||||||
|
@ -416,9 +443,9 @@ const mapStateToProps = state => ({
|
||||||
createPrivateChannelPermission: state.permissions['create-p']
|
createPrivateChannelPermission: state.permissions['create-p']
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||||
create: data => dispatch(createChannelRequestAction(data)),
|
create: (data: ICreateFunction) => dispatch(createChannelRequestAction(data)),
|
||||||
removeUser: user => dispatch(removeUserAction(user))
|
removeUser: (user: IOtherUser) => dispatch(removeUserAction(user))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(CreateChannelView));
|
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 { name, users, encrypted } = this.state;
|
||||||
const { server, user, loading, blockUnauthenticatedAccess, theme, serverVersion } = this.props;
|
const { server, user, loading, blockUnauthenticatedAccess, theme, serverVersion } = this.props;
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||||
contentContainerStyle={styles.container}
|
contentContainerStyle={styles.container}
|
||||||
keyboardVerticalOffset={128}>
|
keyboardVerticalOffset={128}>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<SafeAreaView testID='create-discussion-view' style={styles.container}>
|
<SafeAreaView testID='create-discussion-view' style={styles.container}>
|
||||||
{/* @ts-ignore*/}
|
|
||||||
<ScrollView {...scrollPersistTaps}>
|
<ScrollView {...scrollPersistTaps}>
|
||||||
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Discussion_Desc')}</Text>
|
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Discussion_Desc')}</Text>
|
||||||
<SelectChannel
|
<SelectChannel
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||||
import { FlatList, Linking } from 'react-native';
|
import { FlatList, Linking } from 'react-native';
|
||||||
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
@ -13,7 +13,14 @@ import SafeAreaView from '../containers/SafeAreaView';
|
||||||
import UserPreferences from '../lib/userPreferences';
|
import UserPreferences from '../lib/userPreferences';
|
||||||
import { events, logEvent } from '../utils/log';
|
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',
|
title: 'In_app',
|
||||||
value: 'inApp'
|
value: 'inApp'
|
||||||
|
@ -24,7 +31,7 @@ const DEFAULT_BROWSERS = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const BROWSERS = [
|
const BROWSERS: IBrowsersValues[] = [
|
||||||
{
|
{
|
||||||
title: 'Chrome',
|
title: 'Chrome',
|
||||||
value: 'googlechrome:'
|
value: 'googlechrome:'
|
||||||
|
@ -39,16 +46,23 @@ const BROWSERS = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
class DefaultBrowserView extends React.Component {
|
interface IDefaultBrowserViewState {
|
||||||
static navigationOptions = () => ({
|
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')
|
title: I18n.t('Default_browser')
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
constructor(props: IDefaultBrowserViewProps) {
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
browser: null,
|
browser: null,
|
||||||
|
@ -74,6 +88,7 @@ class DefaultBrowserView extends React.Component {
|
||||||
this.setState(({ supported }) => ({ supported: [...supported, browser] }));
|
this.setState(({ supported }) => ({ supported: [...supported, browser] }));
|
||||||
} else {
|
} else {
|
||||||
const { supported } = this.state;
|
const { supported } = this.state;
|
||||||
|
// @ts-ignore
|
||||||
this.state.supported = [...supported, browser];
|
this.state.supported = [...supported, browser];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +96,7 @@ class DefaultBrowserView extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
isSelected = value => {
|
isSelected = (value: TValue) => {
|
||||||
const { browser } = this.state;
|
const { browser } = this.state;
|
||||||
if (!browser && value === 'systemDefault:') {
|
if (!browser && value === 'systemDefault:') {
|
||||||
return true;
|
return true;
|
||||||
|
@ -89,7 +104,7 @@ class DefaultBrowserView extends React.Component {
|
||||||
return browser === value;
|
return browser === value;
|
||||||
};
|
};
|
||||||
|
|
||||||
changeDefaultBrowser = async newBrowser => {
|
changeDefaultBrowser = async (newBrowser: TValue) => {
|
||||||
logEvent(events.DB_CHANGE_DEFAULT_BROWSER, { browser: newBrowser });
|
logEvent(events.DB_CHANGE_DEFAULT_BROWSER, { browser: newBrowser });
|
||||||
try {
|
try {
|
||||||
const browser = newBrowser !== 'systemDefault:' ? newBrowser : null;
|
const browser = newBrowser !== 'systemDefault:' ? newBrowser : null;
|
||||||
|
@ -105,7 +120,7 @@ class DefaultBrowserView extends React.Component {
|
||||||
return <List.Icon name='check' color={themes[theme].tintColor} />;
|
return <List.Icon name='check' color={themes[theme].tintColor} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }: { item: IBrowsersValues }) => {
|
||||||
const { title, value } = item;
|
const { title, value } = item;
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
|
@ -21,19 +21,19 @@ const DisplayPrefsView = props => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const { sortBy, groupByType, showFavorites, showUnread, showAvatar, displayMode } = useSelector(state => state.sortPreferences);
|
const { sortBy, groupByType, showFavorites, showUnread, showAvatar, displayMode } = useSelector(state => state.sortPreferences);
|
||||||
|
const { isMasterDetail } = useSelector(state => state.app);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { navigation, isMasterDetail } = props;
|
const { navigation } = props;
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
title: I18n.t('Display'),
|
title: I18n.t('Display')
|
||||||
headerLeft: () =>
|
|
||||||
isMasterDetail ? (
|
|
||||||
<HeaderButton.CloseModal navigation={navigation} testID='display-view-close' />
|
|
||||||
) : (
|
|
||||||
<HeaderButton.Drawer navigation={navigation} testID='display-view-drawer' />
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
if (!isMasterDetail) {
|
||||||
|
navigation.setOptions({
|
||||||
|
headerLeft: () => <HeaderButton.Drawer navigation={navigation} testID='display-view-drawer' />
|
||||||
|
});
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setSortPreference = async param => {
|
const setSortPreference = async param => {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View, TextInput as TextInputComp } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
|
||||||
import StatusBar from '../containers/StatusBar';
|
import StatusBar from '../containers/StatusBar';
|
||||||
import * as List from '../containers/List';
|
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: '' };
|
state = { newPassword: '' };
|
||||||
|
|
||||||
newPasswordInputRef = React.createRef();
|
onChangePasswordText = debounce((text: string) => this.setState({ newPassword: text }), 300);
|
||||||
|
|
||||||
onChangePasswordText = debounce(text => this.setState({ newPassword: text }), 300);
|
setNewPasswordRef = (ref: TextInputComp) => (this.newPasswordInputRef = ref);
|
||||||
|
|
||||||
setNewPasswordRef = ref => (this.newPasswordInputRef = ref);
|
|
||||||
|
|
||||||
changePassword = () => {
|
changePassword = () => {
|
||||||
const { newPassword } = this.state;
|
const { newPassword } = this.state;
|
||||||
if (!newPassword.trim()) {
|
if (!newPassword.trim()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
|
||||||
|
// @ts-ignore
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
title: I18n.t('Are_you_sure_question_mark'),
|
title: I18n.t('Are_you_sure_question_mark'),
|
||||||
message: I18n.t('E2E_encryption_change_password_message'),
|
message: I18n.t('E2E_encryption_change_password_message'),
|
||||||
|
@ -76,6 +98,8 @@ class E2EEncryptionSecurityView extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
resetOwnKey = () => {
|
resetOwnKey = () => {
|
||||||
|
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
|
||||||
|
// @ts-ignore
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
title: I18n.t('Are_you_sure_question_mark'),
|
title: I18n.t('Are_you_sure_question_mark'),
|
||||||
message: I18n.t('E2E_encryption_reset_message'),
|
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,
|
server: state.server.server,
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
encryptionEnabled: state.encryption.enabled
|
encryptionEnabled: state.encryption.enabled
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||||
logout: () => dispatch(logoutAction(true))
|
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));
|
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(E2EEncryptionSecurityView));
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { ScrollView, StyleSheet, Text } from 'react-native';
|
import { ScrollView, StyleSheet, Text } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
|
@ -27,18 +28,26 @@ const styles = StyleSheet.create({
|
||||||
...sharedStyles.textRegular
|
...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' />,
|
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='e2e-enter-your-password-view-close' />,
|
||||||
title: I18n.t('Enter_Your_E2E_Password')
|
title: I18n.t('Enter_Your_E2E_Password')
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
constructor(props: IE2EEnterYourPasswordViewProps) {
|
||||||
encryptionDecodeKey: PropTypes.func,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
password: ''
|
password: ''
|
||||||
|
@ -65,12 +74,12 @@ class E2EEnterYourPasswordView extends React.Component {
|
||||||
<ScrollView
|
<ScrollView
|
||||||
{...scrollPersistTaps}
|
{...scrollPersistTaps}
|
||||||
style={sharedStyles.container}
|
style={sharedStyles.container}
|
||||||
contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
|
contentContainerStyle={sharedStyles.containerScrollView}>
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}
|
style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}
|
||||||
testID='e2e-enter-your-password-view'>
|
testID='e2e-enter-your-password-view'>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={e => {
|
inputRef={(e: TextInput) => {
|
||||||
this.passwordInput = e;
|
this.passwordInput = e;
|
||||||
}}
|
}}
|
||||||
placeholder={I18n.t('Password')}
|
placeholder={I18n.t('Password')}
|
||||||
|
@ -99,7 +108,7 @@ class E2EEnterYourPasswordView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||||
encryptionDecodeKey: password => dispatch(encryptionDecodeKeyAction(password))
|
encryptionDecodeKey: (password: string) => dispatch(encryptionDecodeKeyAction(password))
|
||||||
});
|
});
|
||||||
export default connect(null, mapDispatchToProps)(withTheme(E2EEnterYourPasswordView));
|
export default connect(null, mapDispatchToProps)(withTheme(E2EEnterYourPasswordView));
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
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 { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import SafeAreaView from '../containers/SafeAreaView';
|
import SafeAreaView from '../containers/SafeAreaView';
|
||||||
|
@ -21,8 +22,17 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class E2EHowItWorksView extends React.Component {
|
interface INavigation {
|
||||||
static navigationOptions = ({ route, navigation }) => {
|
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;
|
const showCloseModal = route.params?.showCloseModal;
|
||||||
return {
|
return {
|
||||||
title: I18n.t('How_It_Works'),
|
title: I18n.t('How_It_Works'),
|
||||||
|
@ -30,20 +40,21 @@ class E2EHowItWorksView extends React.Component {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
|
|
||||||
const infoStyle = [styles.info, { color: themes[theme].bodyText }];
|
const infoStyle = [styles.info, { color: themes[theme].bodyText }];
|
||||||
|
|
||||||
|
// TODO: Refactor when migrate Markdown
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} testID='e2e-how-it-works-view'>
|
<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} />
|
<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} />
|
<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} />
|
<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} />
|
<Markdown msg={I18n.t('E2E_How_It_Works_info4')} style={infoStyle} theme={theme} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
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 { connect } from 'react-redux';
|
||||||
import { Clipboard, ScrollView, StyleSheet, Text, View } from 'react-native';
|
import { Clipboard, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
@ -53,20 +54,26 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class E2ESaveYourPasswordView extends React.Component {
|
interface IE2ESaveYourPasswordViewState {
|
||||||
static navigationOptions = ({ navigation }) => ({
|
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' />,
|
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='e2e-save-your-password-view-close' />,
|
||||||
title: I18n.t('Save_Your_E2E_Password')
|
title: I18n.t('Save_Your_E2E_Password')
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
constructor(props: IE2ESaveYourPasswordViewProps) {
|
||||||
server: PropTypes.string,
|
|
||||||
navigation: PropTypes.object,
|
|
||||||
encryptionSetBanner: PropTypes.func,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
this.state = { password: '' };
|
this.state = { password: '' };
|
||||||
|
@ -83,8 +90,9 @@ class E2ESaveYourPasswordView extends React.Component {
|
||||||
// Set stored password on local state
|
// Set stored password on local state
|
||||||
const password = await UserPreferences.getStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`);
|
const password = await UserPreferences.getStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`);
|
||||||
if (this.mounted) {
|
if (this.mounted) {
|
||||||
this.setState({ password });
|
this.setState({ password: password! });
|
||||||
} else {
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
this.state.password = password;
|
this.state.password = password;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -164,10 +172,10 @@ class E2ESaveYourPasswordView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state: any) => ({
|
||||||
server: state.server.server
|
server: state.server.server
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||||
encryptionSetBanner: () => dispatch(encryptionSetBannerAction())
|
encryptionSetBanner: () => dispatch(encryptionSetBannerAction())
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(E2ESaveYourPasswordView));
|
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(E2ESaveYourPasswordView));
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text } from 'react-native';
|
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 TextInput from '../containers/TextInput';
|
||||||
import Button from '../containers/Button';
|
import Button from '../containers/Button';
|
||||||
|
@ -14,23 +15,30 @@ import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||||
import { events, logEvent } from '../utils/log';
|
import { events, logEvent } from '../utils/log';
|
||||||
import sharedStyles from './Styles';
|
import sharedStyles from './Styles';
|
||||||
|
|
||||||
class ForgotPasswordView extends React.Component {
|
interface IForgotPasswordViewState {
|
||||||
static navigationOptions = ({ route }) => ({
|
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'
|
title: route.params?.title ?? 'Rocket.Chat'
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
navigation: PropTypes.object,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
email: '',
|
email: '',
|
||||||
invalidEmail: true,
|
invalidEmail: true,
|
||||||
isFetching: false
|
isFetching: false
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps: IForgotPasswordViewProps, nextState: IForgotPasswordViewState) {
|
||||||
const { email, invalidEmail, isFetching } = this.state;
|
const { email, invalidEmail, isFetching } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
|
@ -48,7 +56,7 @@ class ForgotPasswordView extends React.Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate = email => {
|
validate = (email: string) => {
|
||||||
if (!isValidEmail(email)) {
|
if (!isValidEmail(email)) {
|
||||||
this.setState({ invalidEmail: true });
|
this.setState({ invalidEmail: true });
|
||||||
return;
|
return;
|
||||||
|
@ -70,7 +78,7 @@ class ForgotPasswordView extends React.Component {
|
||||||
navigation.pop();
|
navigation.pop();
|
||||||
showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert'));
|
showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert'));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
logEvent(events.FP_FORGOT_PASSWORD_F);
|
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') });
|
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'));
|
showErrorAlert(msg, I18n.t('Alert'));
|
|
@ -1,7 +1,10 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
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 { StyleSheet, View } from 'react-native';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
|
@ -10,6 +13,7 @@ import RocketChat from '../lib/rocketchat';
|
||||||
import OrSeparator from '../containers/OrSeparator';
|
import OrSeparator from '../containers/OrSeparator';
|
||||||
import Input from '../containers/UIKit/MultiSelect/Input';
|
import Input from '../containers/UIKit/MultiSelect/Input';
|
||||||
import { forwardRoom as forwardRoomAction } from '../actions/room';
|
import { forwardRoom as forwardRoomAction } from '../actions/room';
|
||||||
|
import { ILivechatDepartment } from './definition/ILivechatDepartment';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -18,12 +22,43 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
// TODO: Refactor when migrate room
|
||||||
const [departments, setDepartments] = useState([]);
|
interface IRoom {
|
||||||
const [departmentId, setDepartment] = useState();
|
departmentId?: any;
|
||||||
const [users, setUsers] = useState([]);
|
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 [userId, setUser] = useState();
|
||||||
const [room, setRoom] = useState();
|
const [room, setRoom] = useState<IRoom>({});
|
||||||
|
|
||||||
const rid = route.params?.rid;
|
const rid = route.params?.rid;
|
||||||
|
|
||||||
|
@ -31,7 +66,9 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.getDepartments();
|
const result = await RocketChat.getDepartments();
|
||||||
if (result.success) {
|
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 {
|
} catch {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -47,7 +84,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
||||||
term
|
term
|
||||||
});
|
});
|
||||||
if (result.success) {
|
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);
|
setUsers(parsedUsers);
|
||||||
return parsedUsers;
|
return parsedUsers;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +106,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
const transferData = { roomId: rid };
|
const transferData: ITransferData = { roomId: rid };
|
||||||
|
|
||||||
if (!departmentId && !userId) {
|
if (!departmentId && !userId) {
|
||||||
return;
|
return;
|
||||||
|
@ -85,11 +122,14 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
navigation.setOptions({
|
||||||
|
title: I18n.t('Forward_Chat')
|
||||||
|
});
|
||||||
getRoom();
|
getRoom();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (room) {
|
if (!isEmpty(room)) {
|
||||||
getUsers();
|
getUsers();
|
||||||
getDepartments();
|
getDepartments();
|
||||||
}
|
}
|
||||||
|
@ -129,18 +169,9 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => {
|
||||||
</View>
|
</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 => ({
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||||
forwardRoom: (rid, transferData) => dispatch(forwardRoomAction(rid, transferData))
|
forwardRoom: (rid: string, transferData: ITransferData) => dispatch(forwardRoomAction(rid, transferData))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(withTheme(ForwardLivechatView));
|
export default connect(null, mapDispatchToProps)(withTheme(ForwardLivechatView));
|
|
@ -1,8 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { TextInputProps, View } from 'react-native';
|
||||||
import { View } from 'react-native';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import RNPickerSelect from 'react-native-picker-select';
|
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 {
|
import {
|
||||||
inviteLinksCreate as inviteLinksCreateAction,
|
inviteLinksCreate as inviteLinksCreateAction,
|
||||||
|
@ -65,25 +67,29 @@ const OPTIONS = {
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
class InviteUsersView extends React.Component {
|
interface IInviteUsersEditView {
|
||||||
static navigationOptions = () => ({
|
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')
|
title: I18n.t('Invite_users')
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
private rid: string;
|
||||||
navigation: PropTypes.object,
|
|
||||||
route: PropTypes.object,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
createInviteLink: PropTypes.func,
|
|
||||||
inviteLinksSetParams: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: IInviteUsersEditView) {
|
||||||
super(props);
|
super(props);
|
||||||
this.rid = props.route.params?.rid;
|
this.rid = props.route.params?.rid;
|
||||||
}
|
}
|
||||||
|
|
||||||
onValueChangePicker = (key, value) => {
|
onValueChangePicker = (key: string, value: number) => {
|
||||||
logEvent(events.IU_EDIT_SET_LINK_PARAM);
|
logEvent(events.IU_EDIT_SET_LINK_PARAM);
|
||||||
const { inviteLinksSetParams } = this.props;
|
const { inviteLinksSetParams } = this.props;
|
||||||
const params = {
|
const params = {
|
||||||
|
@ -99,9 +105,10 @@ class InviteUsersView extends React.Component {
|
||||||
navigation.pop();
|
navigation.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPicker = (key, first) => {
|
renderPicker = (key: 'days' | 'maxUses', first: string) => {
|
||||||
const { props } = this;
|
const { props } = this;
|
||||||
const { theme } = props;
|
const { theme } = props;
|
||||||
|
const textInputStyle: TextInputProps = { style: { ...styles.pickerText, color: themes[theme].actionTintColor } };
|
||||||
const firstEl = [
|
const firstEl = [
|
||||||
{
|
{
|
||||||
label: I18n.t(first),
|
label: I18n.t(first),
|
||||||
|
@ -112,7 +119,7 @@ class InviteUsersView extends React.Component {
|
||||||
<RNPickerSelect
|
<RNPickerSelect
|
||||||
style={{ viewContainer: styles.viewContainer }}
|
style={{ viewContainer: styles.viewContainer }}
|
||||||
value={props[key]}
|
value={props[key]}
|
||||||
textInputProps={{ style: { ...styles.pickerText, color: themes[theme].actionTintColor } }}
|
textInputProps={textInputStyle}
|
||||||
useNativeAndroidPickerStyle={false}
|
useNativeAndroidPickerStyle={false}
|
||||||
placeholder={{}}
|
placeholder={{}}
|
||||||
onValueChange={value => this.onValueChangePicker(key, value)}
|
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,
|
days: state.inviteLinks.days,
|
||||||
maxUses: state.inviteLinks.maxUses
|
maxUses: state.inviteLinks.maxUses
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||||
inviteLinksSetParams: params => dispatch(inviteLinksSetParamsAction(params)),
|
inviteLinksSetParams: (params: object) => dispatch(inviteLinksSetParamsAction(params)),
|
||||||
createInviteLink: rid => dispatch(inviteLinksCreateAction(rid))
|
createInviteLink: (rid: string) => dispatch(inviteLinksCreateAction(rid))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(InviteUsersView));
|
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(InviteUsersView));
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
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 JitsiMeet, { JitsiMeetView as RNJitsiMeetView } from 'react-native-jitsi-meet';
|
||||||
import BackgroundTimer from 'react-native-background-timer';
|
import BackgroundTimer from 'react-native-background-timer';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -12,23 +13,36 @@ import { events, logEvent } from '../utils/log';
|
||||||
import { isAndroid, isIOS } from '../utils/deviceInfo';
|
import { isAndroid, isIOS } from '../utils/deviceInfo';
|
||||||
import { withTheme } from '../theme';
|
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}`;
|
`${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);
|
super(props);
|
||||||
this.rid = props.route.params?.rid;
|
this.rid = props.route.params?.rid;
|
||||||
this.url = props.route.params?.url;
|
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.
|
// call is not ended and is available to web users.
|
||||||
onConferenceJoined = () => {
|
onConferenceJoined = () => {
|
||||||
logEvent(events.JM_CONFERENCE_JOIN);
|
logEvent(events.JM_CONFERENCE_JOIN);
|
||||||
RocketChat.updateJitsiTimeout(this.rid).catch(e => console.log(e));
|
if (this.rid) {
|
||||||
if (this.jitsiTimeout) {
|
RocketChat.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e));
|
||||||
BackgroundTimer.clearInterval(this.jitsiTimeout);
|
if (this.jitsiTimeout) {
|
||||||
BackgroundTimer.stopBackgroundTimer();
|
BackgroundTimer.clearInterval(this.jitsiTimeout);
|
||||||
this.jitsiTimeout = null;
|
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 = () => {
|
onConferenceTerminated = () => {
|
||||||
|
@ -118,7 +134,7 @@ class JitsiMeetView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state: any) => ({
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
baseUrl: state.server.server
|
baseUrl: state.server.server
|
||||||
});
|
});
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { FlatList } from 'react-native';
|
import { FlatList } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import RNRestart from 'react-native-restart';
|
import RNRestart from 'react-native-restart';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import I18n, { LANGUAGES, isRTL } from '../../i18n';
|
import I18n, { LANGUAGES, isRTL } from '../../i18n';
|
||||||
|
@ -18,26 +18,33 @@ import { getUserSelector } from '../../selectors/login';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
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 = () => ({
|
static navigationOptions = () => ({
|
||||||
title: I18n.t('Change_Language')
|
title: I18n.t('Change_Language')
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
constructor(props: ILanguageViewProps) {
|
||||||
user: PropTypes.object,
|
|
||||||
setUser: PropTypes.func,
|
|
||||||
appStart: PropTypes.func,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
language: props.user ? props.user.language : 'en'
|
language: props.user ? props.user.language : 'en'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps: ILanguageViewProps, nextState: ILanguageViewState) {
|
||||||
const { language } = this.state;
|
const { language } = this.state;
|
||||||
const { user, theme } = this.props;
|
const { user, theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
|
@ -52,12 +59,12 @@ class LanguageView extends React.Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
formIsChanged = language => {
|
formIsChanged = (language: string) => {
|
||||||
const { user } = this.props;
|
const { user } = this.props;
|
||||||
return user.language !== language;
|
return user.language !== language;
|
||||||
};
|
};
|
||||||
|
|
||||||
submit = async language => {
|
submit = async (language: string) => {
|
||||||
if (!this.formIsChanged(language)) {
|
if (!this.formIsChanged(language)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -78,11 +85,11 @@ class LanguageView extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
changeLanguage = async language => {
|
changeLanguage = async (language: string) => {
|
||||||
logEvent(events.LANG_SET_LANGUAGE);
|
logEvent(events.LANG_SET_LANGUAGE);
|
||||||
const { user, setUser } = this.props;
|
const { user, setUser } = this.props;
|
||||||
|
|
||||||
const params = {};
|
const params: { language?: string } = {};
|
||||||
|
|
||||||
// language
|
// language
|
||||||
if (user.language !== language) {
|
if (user.language !== language) {
|
||||||
|
@ -95,10 +102,10 @@ class LanguageView extends React.Component {
|
||||||
|
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
const usersCollection = serversDB.get('users');
|
const usersCollection = serversDB.get('users');
|
||||||
await serversDB.action(async () => {
|
await serversDB.write(async () => {
|
||||||
try {
|
try {
|
||||||
const userRecord = await usersCollection.find(user.id);
|
const userRecord = await usersCollection.find(user.id);
|
||||||
await userRecord.update(record => {
|
await userRecord.update((record: any) => {
|
||||||
record.language = params.language;
|
record.language = params.language;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -117,7 +124,7 @@ class LanguageView extends React.Component {
|
||||||
return <List.Icon name='check' color={themes[theme].tintColor} />;
|
return <List.Icon name='check' color={themes[theme].tintColor} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }: { item: { value: string; label: string } }) => {
|
||||||
const { value, label } = item;
|
const { value, label } = item;
|
||||||
const { language } = this.state;
|
const { language } = this.state;
|
||||||
const isSelected = language === value;
|
const isSelected = language === value;
|
||||||
|
@ -151,13 +158,13 @@ class LanguageView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state: any) => ({
|
||||||
user: getUserSelector(state)
|
user: getUserSelector(state)
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||||
setUser: params => dispatch(setUserAction(params)),
|
setUser: (params: any) => dispatch(setUserAction(params)),
|
||||||
appStart: params => dispatch(appStartAction(params))
|
appStart: (params: any) => dispatch(appStartAction(params))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LanguageView));
|
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LanguageView));
|
|
@ -1,22 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView } from 'react-native';
|
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 I18n from '../i18n';
|
||||||
import { isIOS } from '../utils/deviceInfo';
|
import { isIOS } from '../utils/deviceInfo';
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
|
|
||||||
class MarkdownTableView extends React.Component {
|
interface IMarkdownTableViewProps {
|
||||||
static navigationOptions = () => ({
|
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')
|
title: I18n.t('Table')
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
route: PropTypes.object,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { route, theme } = this.props;
|
const { route, theme } = this.props;
|
||||||
const renderRows = route.params?.renderRows;
|
const renderRows = route.params?.renderRows;
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { FlatList, Text, View } from 'react-native';
|
import { FlatList, Text, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import { RouteProp } from '@react-navigation/core';
|
||||||
|
|
||||||
import Message from '../../containers/message';
|
import Message from '../../containers/message';
|
||||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
|
@ -18,20 +19,67 @@ import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import getThreadName from '../../lib/methods/getThreadName';
|
import getThreadName from '../../lib/methods/getThreadName';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
class MessagesView extends React.Component {
|
type TMessagesViewRouteParams = {
|
||||||
static propTypes = {
|
MessagesView: {
|
||||||
user: PropTypes.object,
|
rid: string;
|
||||||
baseUrl: PropTypes.string,
|
t: string;
|
||||||
navigation: PropTypes.object,
|
name: string;
|
||||||
route: PropTypes.object,
|
|
||||||
customEmojis: PropTypes.object,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
showActionSheet: PropTypes.func,
|
|
||||||
useRealName: PropTypes.bool,
|
|
||||||
isMasterDetail: PropTypes.bool
|
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
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);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -48,7 +96,7 @@ class MessagesView extends React.Component {
|
||||||
this.load();
|
this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps: IMessagesViewProps, nextState: any) {
|
||||||
const { loading, messages, fileLoading } = this.state;
|
const { loading, messages, fileLoading } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
|
@ -73,7 +121,7 @@ class MessagesView extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
navToRoomInfo = navParam => {
|
navToRoomInfo = (navParam: { rid: string }) => {
|
||||||
const { navigation, user } = this.props;
|
const { navigation, user } = this.props;
|
||||||
if (navParam.rid === user.id) {
|
if (navParam.rid === user.id) {
|
||||||
return;
|
return;
|
||||||
|
@ -81,9 +129,9 @@ class MessagesView extends React.Component {
|
||||||
navigation.navigate('RoomInfoView', navParam);
|
navigation.navigate('RoomInfoView', navParam);
|
||||||
};
|
};
|
||||||
|
|
||||||
jumpToMessage = async ({ item }) => {
|
jumpToMessage = async ({ item }: { item: IMessageItem }) => {
|
||||||
const { navigation, isMasterDetail } = this.props;
|
const { navigation, isMasterDetail } = this.props;
|
||||||
let params = {
|
let params: IParams = {
|
||||||
rid: this.rid,
|
rid: this.rid,
|
||||||
jumpToMessageId: item._id,
|
jumpToMessageId: item._id,
|
||||||
t: this.t,
|
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 { user, baseUrl, theme, useRealName } = this.props;
|
||||||
const renderItemCommonProps = item => ({
|
const renderItemCommonProps = (item: IMessageItem) => ({
|
||||||
item,
|
item,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
user,
|
user,
|
||||||
|
@ -137,7 +185,7 @@ class MessagesView extends React.Component {
|
||||||
},
|
},
|
||||||
noDataMsg: I18n.t('No_files'),
|
noDataMsg: I18n.t('No_files'),
|
||||||
testID: 'room-files-view',
|
testID: 'room-files-view',
|
||||||
renderItem: item => (
|
renderItem: (item: IMessageItem) => (
|
||||||
<Message
|
<Message
|
||||||
{...renderItemCommonProps(item)}
|
{...renderItemCommonProps(item)}
|
||||||
item={{
|
item={{
|
||||||
|
@ -165,7 +213,7 @@ class MessagesView extends React.Component {
|
||||||
},
|
},
|
||||||
noDataMsg: I18n.t('No_mentioned_messages'),
|
noDataMsg: I18n.t('No_mentioned_messages'),
|
||||||
testID: 'mentioned-messages-view',
|
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 Messages Screen
|
||||||
Starred: {
|
Starred: {
|
||||||
|
@ -176,15 +224,15 @@ class MessagesView extends React.Component {
|
||||||
},
|
},
|
||||||
noDataMsg: I18n.t('No_starred_messages'),
|
noDataMsg: I18n.t('No_starred_messages'),
|
||||||
testID: 'starred-messages-view',
|
testID: 'starred-messages-view',
|
||||||
renderItem: item => (
|
renderItem: (item: IMessageItem) => (
|
||||||
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
||||||
),
|
),
|
||||||
action: message => ({
|
action: (message: IMessageItem) => ({
|
||||||
title: I18n.t('Unstar'),
|
title: I18n.t('Unstar'),
|
||||||
icon: message.starred ? 'star-filled' : 'star',
|
icon: message.starred ? 'star-filled' : 'star',
|
||||||
onPress: this.handleActionPress
|
onPress: this.handleActionPress
|
||||||
}),
|
}),
|
||||||
handleActionPress: message => RocketChat.toggleStarMessage(message._id, message.starred)
|
handleActionPress: (message: IMessageItem) => RocketChat.toggleStarMessage(message._id, message.starred)
|
||||||
},
|
},
|
||||||
// Pinned Messages Screen
|
// Pinned Messages Screen
|
||||||
Pinned: {
|
Pinned: {
|
||||||
|
@ -195,12 +243,13 @@ class MessagesView extends React.Component {
|
||||||
},
|
},
|
||||||
noDataMsg: I18n.t('No_pinned_messages'),
|
noDataMsg: I18n.t('No_pinned_messages'),
|
||||||
testID: 'pinned-messages-view',
|
testID: 'pinned-messages-view',
|
||||||
renderItem: item => (
|
renderItem: (item: IMessageItem) => (
|
||||||
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
||||||
),
|
),
|
||||||
action: () => ({ title: I18n.t('Unpin'), icon: 'pin', onPress: this.handleActionPress }),
|
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];
|
}[name];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -227,7 +276,7 @@ class MessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getCustomEmoji = name => {
|
getCustomEmoji = (name: string) => {
|
||||||
const { customEmojis } = this.props;
|
const { customEmojis } = this.props;
|
||||||
const emoji = customEmojis[name];
|
const emoji = customEmojis[name];
|
||||||
if (emoji) {
|
if (emoji) {
|
||||||
|
@ -236,12 +285,12 @@ class MessagesView extends React.Component {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
showAttachment = attachment => {
|
showAttachment = (attachment: any) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.navigate('AttachmentView', { attachment });
|
navigation.navigate('AttachmentView', { attachment });
|
||||||
};
|
};
|
||||||
|
|
||||||
onLongPress = message => {
|
onLongPress = (message: IMessageItem) => {
|
||||||
this.setState({ message }, this.showActionSheet);
|
this.setState({ message }, this.showActionSheet);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -257,8 +306,8 @@ class MessagesView extends React.Component {
|
||||||
try {
|
try {
|
||||||
const result = await this.content.handleActionPress(message);
|
const result = await this.content.handleActionPress(message);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState(prevState => ({
|
this.setState((prevState: IMessagesViewState) => ({
|
||||||
messages: prevState.messages.filter(item => item._id !== message._id),
|
messages: prevState.messages.filter((item: IMessageItem) => item._id !== message._id),
|
||||||
total: prevState.total - 1
|
total: prevState.total - 1
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -267,7 +316,7 @@ class MessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setFileLoading = fileLoading => {
|
setFileLoading = (fileLoading: boolean) => {
|
||||||
this.setState({ fileLoading });
|
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() {
|
render() {
|
||||||
const { messages, loading } = this.state;
|
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,
|
baseUrl: state.server.server,
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
customEmojis: state.customEmojis,
|
customEmojis: state.customEmojis,
|
|
@ -1,6 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Switch, Text } from 'react-native';
|
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 database from '../../lib/database';
|
||||||
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
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 = () => ({
|
static navigationOptions = () => ({
|
||||||
title: I18n.t('Notification_Preferences')
|
title: I18n.t('Notification_Preferences')
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
private mounted: boolean;
|
||||||
navigation: PropTypes.object,
|
private rid: string | undefined;
|
||||||
route: PropTypes.object,
|
private roomObservable?: Observable<Model>;
|
||||||
theme: PropTypes.string
|
private subscription?: Subscription;
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: INotificationPreferencesView) {
|
||||||
super(props);
|
super(props);
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
this.rid = props.route.params?.rid;
|
this.rid = props.route.params?.rid;
|
||||||
|
@ -43,10 +59,11 @@ class NotificationPreferencesView extends React.Component {
|
||||||
};
|
};
|
||||||
if (room && room.observe) {
|
if (room && room.observe) {
|
||||||
this.roomObservable = room.observe();
|
this.roomObservable = room.observe();
|
||||||
this.subscription = this.roomObservable.subscribe(changes => {
|
this.subscription = this.roomObservable.subscribe((changes: any) => {
|
||||||
if (this.mounted) {
|
if (this.mounted) {
|
||||||
this.setState({ room: changes });
|
this.setState({ room: changes });
|
||||||
} else {
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
this.state.room = changes;
|
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()}`]);
|
logEvent(events[`NP_${key.toUpperCase()}`]);
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
|
@ -71,7 +89,7 @@ class NotificationPreferencesView extends React.Component {
|
||||||
try {
|
try {
|
||||||
await db.action(async () => {
|
await db.action(async () => {
|
||||||
await room.update(
|
await room.update(
|
||||||
protectedFunction(r => {
|
protectedFunction((r: any) => {
|
||||||
r[key] = value;
|
r[key] = value;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -88,36 +106,38 @@ class NotificationPreferencesView extends React.Component {
|
||||||
|
|
||||||
await db.action(async () => {
|
await db.action(async () => {
|
||||||
await room.update(
|
await room.update(
|
||||||
protectedFunction(r => {
|
protectedFunction((r: any) => {
|
||||||
r[key] = room[key];
|
r[key] = room[key];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// @ts-ignore
|
||||||
logEvent(events[`NP_${key.toUpperCase()}_F`]);
|
logEvent(events[`NP_${key.toUpperCase()}_F`]);
|
||||||
log(e);
|
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 { room } = this.state;
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.navigate('PickerView', {
|
navigation.navigate('PickerView', {
|
||||||
title,
|
title,
|
||||||
data: OPTIONS[key],
|
data: OPTIONS[key],
|
||||||
value: room[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 { room } = this.state;
|
||||||
const { theme } = this.props;
|
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 (
|
return (
|
||||||
<Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>
|
<Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>
|
||||||
{I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })}
|
{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;
|
const { room } = this.state;
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
|
@ -181,7 +201,7 @@ class NotificationPreferencesView extends React.Component {
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Alert'
|
title='Alert'
|
||||||
testID='notification-preference-view-alert'
|
testID='notification-preference-view-alert'
|
||||||
onPress={title => this.pickerSelection(title, 'desktopNotifications')}
|
onPress={(title: string) => this.pickerSelection(title, 'desktopNotifications')}
|
||||||
right={() => this.renderPickerOption('desktopNotifications')}
|
right={() => this.renderPickerOption('desktopNotifications')}
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
|
@ -193,7 +213,7 @@ class NotificationPreferencesView extends React.Component {
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Alert'
|
title='Alert'
|
||||||
testID='notification-preference-view-push-notification'
|
testID='notification-preference-view-push-notification'
|
||||||
onPress={title => this.pickerSelection(title, 'mobilePushNotifications')}
|
onPress={(title: string) => this.pickerSelection(title, 'mobilePushNotifications')}
|
||||||
right={() => this.renderPickerOption('mobilePushNotifications')}
|
right={() => this.renderPickerOption('mobilePushNotifications')}
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
|
@ -205,21 +225,21 @@ class NotificationPreferencesView extends React.Component {
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Audio'
|
title='Audio'
|
||||||
testID='notification-preference-view-audio'
|
testID='notification-preference-view-audio'
|
||||||
onPress={title => this.pickerSelection(title, 'audioNotifications')}
|
onPress={(title: string) => this.pickerSelection(title, 'audioNotifications')}
|
||||||
right={() => this.renderPickerOption('audioNotifications')}
|
right={() => this.renderPickerOption('audioNotifications')}
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Sound'
|
title='Sound'
|
||||||
testID='notification-preference-view-sound'
|
testID='notification-preference-view-sound'
|
||||||
onPress={title => this.pickerSelection(title, 'audioNotificationValue')}
|
onPress={(title: string) => this.pickerSelection(title, 'audioNotificationValue')}
|
||||||
right={() => this.renderPickerOption('audioNotificationValue')}
|
right={() => this.renderPickerOption('audioNotificationValue')}
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Notification_Duration'
|
title='Notification_Duration'
|
||||||
testID='notification-preference-view-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')}
|
right={() => this.renderPickerOption('desktopNotificationDuration')}
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
|
@ -230,7 +250,7 @@ class NotificationPreferencesView extends React.Component {
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Alert'
|
title='Alert'
|
||||||
testID='notification-preference-view-email-alert'
|
testID='notification-preference-view-email-alert'
|
||||||
onPress={title => this.pickerSelection(title, 'emailNotifications')}
|
onPress={(title: string) => this.pickerSelection(title, 'emailNotifications')}
|
||||||
right={() => this.renderPickerOption('emailNotifications')}
|
right={() => this.renderPickerOption('emailNotifications')}
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<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: [
|
desktopNotifications: [
|
||||||
{
|
{
|
||||||
label: 'Default',
|
label: 'Default',
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
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 { FlatList, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
import I18n from '../i18n';
|
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
|
<List.Item
|
||||||
title={I18n.t(item.label, { defaultValue: item.label, second: item?.second })}
|
title={I18n.t(item.label, { defaultValue: item.label, second: item?.second })}
|
||||||
right={selected && (() => <List.Icon name='check' color={themes[theme].tintColor} />)}
|
right={selected && (() => <List.Icon name='check' color={themes[theme].tintColor} />)}
|
||||||
|
@ -32,25 +67,15 @@ const Item = React.memo(({ item, selected, onItemPress, theme }) => (
|
||||||
translateTitle={false}
|
translateTitle={false}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
Item.propTypes = {
|
|
||||||
item: PropTypes.object,
|
|
||||||
selected: PropTypes.bool,
|
|
||||||
onItemPress: PropTypes.func,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
class PickerView extends React.PureComponent {
|
class PickerView extends React.PureComponent<IPickerViewProps, IPickerViewState> {
|
||||||
static navigationOptions = ({ route }) => ({
|
private onSearch: (text: string) => IData[];
|
||||||
|
|
||||||
|
static navigationOptions = ({ route }: IPickerViewProps) => ({
|
||||||
title: route.params?.title ?? I18n.t('Select_an_option')
|
title: route.params?.title ?? I18n.t('Select_an_option')
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
constructor(props: IPickerViewProps) {
|
||||||
navigation: PropTypes.object,
|
|
||||||
route: PropTypes.object,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
const data = props.route.params?.data ?? [];
|
const data = props.route.params?.data ?? [];
|
||||||
const value = props.route.params?.value;
|
const value = props.route.params?.value;
|
||||||
|
@ -59,7 +84,7 @@ class PickerView extends React.PureComponent {
|
||||||
this.onSearch = props.route.params?.onChangeText;
|
this.onSearch = props.route.params?.onChangeText;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeValue = value => {
|
onChangeValue = (value: string) => {
|
||||||
const { navigation, route } = this.props;
|
const { navigation, route } = this.props;
|
||||||
const goBack = route.params?.goBack ?? true;
|
const goBack = route.params?.goBack ?? true;
|
||||||
const onChange = route.params?.onChangeValue ?? (() => {});
|
const onChange = route.params?.onChangeValue ?? (() => {});
|
||||||
|
@ -70,7 +95,7 @@ class PickerView extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeText = debounce(
|
onChangeText = debounce(
|
||||||
async text => {
|
async (text: string) => {
|
||||||
if (this.onSearch) {
|
if (this.onSearch) {
|
||||||
const data = await this.onSearch(text);
|
const data = await this.onSearch(text);
|
||||||
this.setState({ data });
|
this.setState({ data });
|
|
@ -1,13 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Keyboard, ScrollView, View } from 'react-native';
|
import { Keyboard, ScrollView, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import prompt from 'react-native-prompt-android';
|
import prompt from 'react-native-prompt-android';
|
||||||
import SHA256 from 'js-sha256';
|
import { sha256 } from 'js-sha256';
|
||||||
import ImagePicker from 'react-native-image-crop-picker';
|
import ImagePicker, { Image } from 'react-native-image-crop-picker';
|
||||||
import RNPickerSelect from 'react-native-picker-select';
|
import RNPickerSelect from 'react-native-picker-select';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||||
|
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import KeyboardView from '../../presentation/KeyboardView';
|
import KeyboardView from '../../presentation/KeyboardView';
|
||||||
|
@ -31,43 +31,40 @@ import { withTheme } from '../../theme';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { IAvatar, IAvatarButton, INavigationOptions, IParams, IProfileViewProps, IProfileViewState, IUser } from './interfaces';
|
||||||
|
|
||||||
class ProfileView extends React.Component {
|
class ProfileView extends React.Component<IProfileViewProps, IProfileViewState> {
|
||||||
static navigationOptions = ({ navigation, isMasterDetail }) => {
|
private name: any;
|
||||||
const options = {
|
private username: any;
|
||||||
|
private email: any;
|
||||||
|
private avatarUrl: any;
|
||||||
|
private newPassword: any;
|
||||||
|
|
||||||
|
static navigationOptions = ({ navigation, isMasterDetail }: INavigationOptions) => {
|
||||||
|
const options: StackNavigationOptions = {
|
||||||
title: I18n.t('Profile')
|
title: I18n.t('Profile')
|
||||||
};
|
};
|
||||||
if (!isMasterDetail) {
|
if (!isMasterDetail) {
|
||||||
options.headerLeft = () => <HeaderButton.Drawer navigation={navigation} />;
|
options.headerLeft = () => <HeaderButton.Drawer navigation={navigation} />;
|
||||||
}
|
}
|
||||||
options.headerRight = () => (
|
options.headerRight = () => (
|
||||||
<HeaderButton.Preferences onPress={() => navigation.navigate('UserPreferencesView')} testID='preferences-view-open' />
|
<HeaderButton.Preferences onPress={() => navigation?.navigate('UserPreferencesView')} testID='preferences-view-open' />
|
||||||
);
|
);
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
state: IProfileViewState = {
|
||||||
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 = {
|
|
||||||
saving: false,
|
saving: false,
|
||||||
name: null,
|
name: '',
|
||||||
username: null,
|
username: '',
|
||||||
email: null,
|
email: '',
|
||||||
newPassword: null,
|
newPassword: '',
|
||||||
currentPassword: null,
|
currentPassword: '',
|
||||||
avatarUrl: null,
|
avatarUrl: '',
|
||||||
avatar: {},
|
avatar: {
|
||||||
|
data: {},
|
||||||
|
url: ''
|
||||||
|
},
|
||||||
avatarSuggestions: {},
|
avatarSuggestions: {},
|
||||||
customFields: {}
|
customFields: {}
|
||||||
};
|
};
|
||||||
|
@ -83,7 +80,7 @@ class ProfileView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps: IProfileViewProps) {
|
||||||
const { user } = this.props;
|
const { user } = this.props;
|
||||||
/*
|
/*
|
||||||
* We need to ignore status because on Android ImagePicker
|
* 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;
|
const { Accounts_AllowUserAvatarChange } = this.props;
|
||||||
|
|
||||||
if (!Accounts_AllowUserAvatarChange) {
|
if (!Accounts_AllowUserAvatarChange) {
|
||||||
|
@ -106,7 +103,7 @@ class ProfileView extends React.Component {
|
||||||
this.setState({ avatar });
|
this.setState({ avatar });
|
||||||
};
|
};
|
||||||
|
|
||||||
init = user => {
|
init = (user?: IUser) => {
|
||||||
const { user: userProps } = this.props;
|
const { user: userProps } = this.props;
|
||||||
const { name, username, emails, customFields } = user || userProps;
|
const { name, username, emails, customFields } = user || userProps;
|
||||||
|
|
||||||
|
@ -117,7 +114,10 @@ class ProfileView extends React.Component {
|
||||||
newPassword: null,
|
newPassword: null,
|
||||||
currentPassword: null,
|
currentPassword: null,
|
||||||
avatarUrl: null,
|
avatarUrl: null,
|
||||||
avatar: {},
|
avatar: {
|
||||||
|
data: {},
|
||||||
|
url: ''
|
||||||
|
},
|
||||||
customFields: customFields || {}
|
customFields: customFields || {}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -142,12 +142,12 @@ class ProfileView extends React.Component {
|
||||||
!newPassword &&
|
!newPassword &&
|
||||||
user.emails &&
|
user.emails &&
|
||||||
user.emails[0].address === email &&
|
user.emails[0].address === email &&
|
||||||
!avatar.data &&
|
!avatar!.data &&
|
||||||
!customFieldsChanged
|
!customFieldsChanged
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleError = (e, func, action) => {
|
handleError = (e: any, func: string, action: string) => {
|
||||||
if (e.data && e.data.error.includes('[error-too-many-requests]')) {
|
if (e.data && e.data.error.includes('[error-too-many-requests]')) {
|
||||||
return showErrorAlert(e.data.error);
|
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 { name, username, email, newPassword, currentPassword, avatar, customFields } = this.state;
|
||||||
const { user, setUser } = this.props;
|
const { user, setUser } = this.props;
|
||||||
const params = {};
|
const params = {} as IParams;
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
if (user.name !== name) {
|
if (user.name !== name) {
|
||||||
|
@ -189,7 +189,7 @@ class ProfileView extends React.Component {
|
||||||
|
|
||||||
// currentPassword
|
// currentPassword
|
||||||
if (currentPassword) {
|
if (currentPassword) {
|
||||||
params.currentPassword = SHA256(currentPassword);
|
params.currentPassword = sha256(currentPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
const requirePassword = !!params.email || newPassword;
|
const requirePassword = !!params.email || newPassword;
|
||||||
|
@ -202,7 +202,7 @@ class ProfileView extends React.Component {
|
||||||
{ text: I18n.t('Cancel'), onPress: () => {}, style: 'cancel' },
|
{ text: I18n.t('Cancel'), onPress: () => {}, style: 'cancel' },
|
||||||
{
|
{
|
||||||
text: I18n.t('Save'),
|
text: I18n.t('Save'),
|
||||||
onPress: p => {
|
onPress: (p: string) => {
|
||||||
this.setState({ currentPassword: p });
|
this.setState({ currentPassword: p });
|
||||||
this.submit();
|
this.submit();
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ class ProfileView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (avatar.url) {
|
if (avatar!.url) {
|
||||||
try {
|
try {
|
||||||
logEvent(events.PROFILE_SAVE_AVATAR);
|
logEvent(events.PROFILE_SAVE_AVATAR);
|
||||||
await RocketChat.setAvatarFromService(avatar);
|
await RocketChat.setAvatarFromService(avatar);
|
||||||
|
@ -283,7 +283,7 @@ class ProfileView extends React.Component {
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
logEvent(events.PROFILE_PICK_AVATAR);
|
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' });
|
this.setAvatar({ url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logEvent(events.PROFILE_PICK_AVATAR_F);
|
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);
|
logEvent(events.PROFILE_PICK_AVATAR_WITH_URL);
|
||||||
this.setAvatar({ url: avatarUrl, data: avatarUrl, service: '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;
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
<Touch
|
<Touch
|
||||||
|
@ -331,7 +331,7 @@ class ProfileView extends React.Component {
|
||||||
})}
|
})}
|
||||||
{this.renderAvatarButton({
|
{this.renderAvatarButton({
|
||||||
child: <CustomIcon name='link' size={30} color={themes[theme].bodyText} />,
|
child: <CustomIcon name='link' size={30} color={themes[theme].bodyText} />,
|
||||||
onPress: () => this.pickImageWithURL(avatarUrl),
|
onPress: () => this.pickImageWithURL(avatarUrl!),
|
||||||
disabled: !avatarUrl,
|
disabled: !avatarUrl,
|
||||||
key: 'profile-view-avatar-url-button'
|
key: 'profile-view-avatar-url-button'
|
||||||
})}
|
})}
|
||||||
|
@ -365,19 +365,20 @@ class ProfileView extends React.Component {
|
||||||
const parsedCustomFields = JSON.parse(Accounts_CustomFields);
|
const parsedCustomFields = JSON.parse(Accounts_CustomFields);
|
||||||
return Object.keys(parsedCustomFields).map((key, index, array) => {
|
return Object.keys(parsedCustomFields).map((key, index, array) => {
|
||||||
if (parsedCustomFields[key].type === 'select') {
|
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 (
|
return (
|
||||||
<RNPickerSelect
|
<RNPickerSelect
|
||||||
key={key}
|
key={key}
|
||||||
items={options}
|
items={options}
|
||||||
onValueChange={value => {
|
onValueChange={value => {
|
||||||
const newValue = {};
|
const newValue: { [key: string]: string } = {};
|
||||||
newValue[key] = value;
|
newValue[key] = value;
|
||||||
this.setState({ customFields: { ...customFields, ...newValue } });
|
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||||
}}
|
}}
|
||||||
value={customFields[key]}>
|
value={customFields[key]}>
|
||||||
<RCTextInput
|
<RCTextInput
|
||||||
inputRef={e => {
|
inputRef={e => {
|
||||||
|
// @ts-ignore
|
||||||
this[key] = e;
|
this[key] = e;
|
||||||
}}
|
}}
|
||||||
label={key}
|
label={key}
|
||||||
|
@ -393,6 +394,7 @@ class ProfileView extends React.Component {
|
||||||
return (
|
return (
|
||||||
<RCTextInput
|
<RCTextInput
|
||||||
inputRef={e => {
|
inputRef={e => {
|
||||||
|
// @ts-ignore
|
||||||
this[key] = e;
|
this[key] = e;
|
||||||
}}
|
}}
|
||||||
key={key}
|
key={key}
|
||||||
|
@ -400,12 +402,13 @@ class ProfileView extends React.Component {
|
||||||
placeholder={key}
|
placeholder={key}
|
||||||
value={customFields[key]}
|
value={customFields[key]}
|
||||||
onChangeText={value => {
|
onChangeText={value => {
|
||||||
const newValue = {};
|
const newValue: { [key: string]: string } = {};
|
||||||
newValue[key] = value;
|
newValue[key] = value;
|
||||||
this.setState({ customFields: { ...customFields, ...newValue } });
|
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||||
}}
|
}}
|
||||||
onSubmitEditing={() => {
|
onSubmitEditing={() => {
|
||||||
if (array.length - 1 > index) {
|
if (array.length - 1 > index) {
|
||||||
|
// @ts-ignore
|
||||||
return this[array[index + 1]].focus();
|
return this[array[index + 1]].focus();
|
||||||
}
|
}
|
||||||
this.avatarUrl.focus();
|
this.avatarUrl.focus();
|
||||||
|
@ -421,6 +424,7 @@ class ProfileView extends React.Component {
|
||||||
|
|
||||||
logoutOtherLocations = () => {
|
logoutOtherLocations = () => {
|
||||||
logEvent(events.PL_OTHER_LOCATIONS);
|
logEvent(events.PL_OTHER_LOCATIONS);
|
||||||
|
// @ts-ignore
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
message: I18n.t('You_will_be_logged_out_from_other_locations'),
|
message: I18n.t('You_will_be_logged_out_from_other_locations'),
|
||||||
confirmationText: I18n.t('Logout'),
|
confirmationText: I18n.t('Logout'),
|
||||||
|
@ -469,7 +473,7 @@ class ProfileView extends React.Component {
|
||||||
label={I18n.t('Name')}
|
label={I18n.t('Name')}
|
||||||
placeholder={I18n.t('Name')}
|
placeholder={I18n.t('Name')}
|
||||||
value={name}
|
value={name}
|
||||||
onChangeText={value => this.setState({ name: value })}
|
onChangeText={(value: string) => this.setState({ name: value })}
|
||||||
onSubmitEditing={() => {
|
onSubmitEditing={() => {
|
||||||
this.username.focus();
|
this.username.focus();
|
||||||
}}
|
}}
|
||||||
|
@ -500,7 +504,7 @@ class ProfileView extends React.Component {
|
||||||
}}
|
}}
|
||||||
label={I18n.t('Email')}
|
label={I18n.t('Email')}
|
||||||
placeholder={I18n.t('Email')}
|
placeholder={I18n.t('Email')}
|
||||||
value={email}
|
value={email!}
|
||||||
onChangeText={value => this.setState({ email: value })}
|
onChangeText={value => this.setState({ email: value })}
|
||||||
onSubmitEditing={() => {
|
onSubmitEditing={() => {
|
||||||
this.newPassword.focus();
|
this.newPassword.focus();
|
||||||
|
@ -516,10 +520,11 @@ class ProfileView extends React.Component {
|
||||||
}}
|
}}
|
||||||
label={I18n.t('New_Password')}
|
label={I18n.t('New_Password')}
|
||||||
placeholder={I18n.t('New_Password')}
|
placeholder={I18n.t('New_Password')}
|
||||||
value={newPassword}
|
value={newPassword!}
|
||||||
onChangeText={value => this.setState({ newPassword: value })}
|
onChangeText={value => this.setState({ newPassword: value })}
|
||||||
onSubmitEditing={() => {
|
onSubmitEditing={() => {
|
||||||
if (Accounts_CustomFields && Object.keys(customFields).length) {
|
if (Accounts_CustomFields && Object.keys(customFields).length) {
|
||||||
|
// @ts-ignore
|
||||||
return this[Object.keys(customFields)[0]].focus();
|
return this[Object.keys(customFields)[0]].focus();
|
||||||
}
|
}
|
||||||
this.avatarUrl.focus();
|
this.avatarUrl.focus();
|
||||||
|
@ -537,7 +542,7 @@ class ProfileView extends React.Component {
|
||||||
}}
|
}}
|
||||||
label={I18n.t('Avatar_Url')}
|
label={I18n.t('Avatar_Url')}
|
||||||
placeholder={I18n.t('Avatar_Url')}
|
placeholder={I18n.t('Avatar_Url')}
|
||||||
value={avatarUrl}
|
value={avatarUrl!}
|
||||||
onChangeText={value => this.setState({ avatarUrl: value })}
|
onChangeText={value => this.setState({ avatarUrl: value })}
|
||||||
onSubmitEditing={this.submit}
|
onSubmitEditing={this.submit}
|
||||||
testID='profile-view-avatar-url'
|
testID='profile-view-avatar-url'
|
||||||
|
@ -568,7 +573,7 @@ class ProfileView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state: any) => ({
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
Accounts_AllowEmailChange: state.settings.Accounts_AllowEmailChange,
|
Accounts_AllowEmailChange: state.settings.Accounts_AllowEmailChange,
|
||||||
Accounts_AllowPasswordChange: state.settings.Accounts_AllowPasswordChange,
|
Accounts_AllowPasswordChange: state.settings.Accounts_AllowPasswordChange,
|
||||||
|
@ -579,8 +584,8 @@ const mapStateToProps = state => ({
|
||||||
baseUrl: state.server.server
|
baseUrl: state.server.server
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = (dispatch: any) => ({
|
||||||
setUser: params => dispatch(setUserAction(params))
|
setUser: (params: any) => dispatch(setUserAction(params))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(ProfileView));
|
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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { FlatList, Text, View, RefreshControl } from 'react-native';
|
import { FlatList, Text, View, RefreshControl } from 'react-native';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { connect } from 'react-redux';
|
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 * as List from '../../containers/List';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
|
@ -16,9 +17,40 @@ import { themes } from '../../constants/colors';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
class ReadReceiptView extends React.Component {
|
interface IReceipts {
|
||||||
static navigationOptions = ({ navigation, isMasterDetail }) => {
|
_id: string;
|
||||||
const options = {
|
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')
|
title: I18n.t('Read_Receipt')
|
||||||
};
|
};
|
||||||
if (isMasterDetail) {
|
if (isMasterDetail) {
|
||||||
|
@ -27,13 +59,7 @@ class ReadReceiptView extends React.Component {
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
constructor(props: IReadReceiptViewProps) {
|
||||||
route: PropTypes.object,
|
|
||||||
Message_TimeAndDateFormat: PropTypes.string,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.messageId = props.route.params?.messageId;
|
this.messageId = props.route.params?.messageId;
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -46,7 +72,7 @@ class ReadReceiptView extends React.Component {
|
||||||
this.load();
|
this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps: IReadReceiptViewProps, nextState: IReadReceiptViewState) {
|
||||||
const { loading, receipts } = this.state;
|
const { loading, receipts } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
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 { theme, Message_TimeAndDateFormat } = this.props;
|
||||||
const time = moment(item.ts).format(Message_TimeAndDateFormat);
|
const time = moment(item.ts).format(Message_TimeAndDateFormat);
|
||||||
if (!item?.user?.username) {
|
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
|
Message_TimeAndDateFormat: state.settings.Message_TimeAndDateFormat
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
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 { FlatList, Text, View } from 'react-native';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -12,6 +13,7 @@ import debounce from '../../utils/debounce';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import Message from '../../containers/message';
|
import Message from '../../containers/message';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
import { IMessage, IMessageAttachments } from '../../containers/message/interfaces';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
@ -29,9 +31,52 @@ import { compareServerVersion, methods } from '../../lib/utils';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const QUERY_SIZE = 50;
|
const QUERY_SIZE = 50;
|
||||||
class SearchMessagesView extends React.Component {
|
|
||||||
static navigationOptions = ({ navigation, route }) => {
|
type TRouteParams = {
|
||||||
const options = {
|
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')
|
title: I18n.t('Search')
|
||||||
};
|
};
|
||||||
const showCloseModal = route.params?.showCloseModal;
|
const showCloseModal = route.params?.showCloseModal;
|
||||||
|
@ -41,18 +86,7 @@ class SearchMessagesView extends React.Component {
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
constructor(props: ISearchMessagesViewProps) {
|
||||||
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) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -60,7 +94,7 @@ class SearchMessagesView extends React.Component {
|
||||||
searchText: ''
|
searchText: ''
|
||||||
};
|
};
|
||||||
this.offset = 0;
|
this.offset = 0;
|
||||||
this.rid = props.route.params?.rid;
|
this.rid = props.route.params.rid;
|
||||||
this.t = props.route.params?.t;
|
this.t = props.route.params?.t;
|
||||||
this.encrypted = props.route.params?.encrypted;
|
this.encrypted = props.route.params?.encrypted;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +103,7 @@ class SearchMessagesView extends React.Component {
|
||||||
this.room = await getRoomInfo(this.rid);
|
this.room = await getRoomInfo(this.rid);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps: ISearchMessagesViewProps, nextState: ISearchMessagesViewState) {
|
||||||
const { loading, searchText, messages } = this.state;
|
const { loading, searchText, messages } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
|
@ -88,11 +122,11 @@ class SearchMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.search?.stop?.();
|
this.searchDebounced?.stop?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle encrypted rooms search messages
|
// Handle encrypted rooms search messages
|
||||||
searchMessages = async searchText => {
|
searchMessages = async (searchText: string) => {
|
||||||
if (!searchText) {
|
if (!searchText) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -117,7 +151,7 @@ class SearchMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getMessages = async (searchText, debounced) => {
|
getMessages = async (searchText: string, debounced?: boolean) => {
|
||||||
try {
|
try {
|
||||||
const messages = await this.searchMessages(searchText);
|
const messages = await this.searchMessages(searchText);
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
|
@ -130,17 +164,17 @@ class SearchMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
search = searchText => {
|
search = (searchText: string) => {
|
||||||
this.offset = 0;
|
this.offset = 0;
|
||||||
this.setState({ searchText, loading: true, messages: [] });
|
this.setState({ searchText, loading: true, messages: [] });
|
||||||
this.searchDebounced(searchText);
|
this.searchDebounced(searchText);
|
||||||
};
|
};
|
||||||
|
|
||||||
searchDebounced = debounce(async searchText => {
|
searchDebounced = debounce(async (searchText: string) => {
|
||||||
await this.getMessages(searchText, true);
|
await this.getMessages(searchText, true);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
getCustomEmoji = name => {
|
getCustomEmoji = (name: string) => {
|
||||||
const { customEmojis } = this.props;
|
const { customEmojis } = this.props;
|
||||||
const emoji = customEmojis[name];
|
const emoji = customEmojis[name];
|
||||||
if (emoji) {
|
if (emoji) {
|
||||||
|
@ -149,12 +183,12 @@ class SearchMessagesView extends React.Component {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
showAttachment = attachment => {
|
showAttachment = (attachment: IMessageAttachments) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.navigate('AttachmentView', { attachment });
|
navigation.navigate('AttachmentView', { attachment });
|
||||||
};
|
};
|
||||||
|
|
||||||
navToRoomInfo = navParam => {
|
navToRoomInfo = (navParam: IMessage) => {
|
||||||
const { navigation, user } = this.props;
|
const { navigation, user } = this.props;
|
||||||
if (navParam.rid === user.id) {
|
if (navParam.rid === user.id) {
|
||||||
return;
|
return;
|
||||||
|
@ -162,9 +196,9 @@ class SearchMessagesView extends React.Component {
|
||||||
navigation.navigate('RoomInfoView', navParam);
|
navigation.navigate('RoomInfoView', navParam);
|
||||||
};
|
};
|
||||||
|
|
||||||
jumpToMessage = async ({ item }) => {
|
jumpToMessage = async ({ item }: { item: IMessage }) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
let params = {
|
let params: any = {
|
||||||
rid: this.rid,
|
rid: this.rid,
|
||||||
jumpToMessageId: item._id,
|
jumpToMessageId: item._id,
|
||||||
t: this.t,
|
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;
|
const { user, baseUrl, theme, useRealName } = this.props;
|
||||||
return (
|
return (
|
||||||
<Message
|
<Message
|
||||||
|
@ -268,6 +302,7 @@ class SearchMessagesView extends React.Component {
|
||||||
testID='search-message-view-input'
|
testID='search-message-view-input'
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} username='' baseUrl='' theme={theme} />
|
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} username='' baseUrl='' theme={theme} />
|
||||||
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
|
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
|
||||||
</View>
|
</View>
|
||||||
|
@ -277,7 +312,7 @@ class SearchMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state: any) => ({
|
||||||
serverVersion: state.server.version,
|
serverVersion: state.server.version,
|
||||||
baseUrl: state.server.server,
|
baseUrl: state.server.server,
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
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;
|
let type;
|
||||||
if (thread?.id) {
|
if (thread?.id) {
|
||||||
type = 'thread';
|
type = 'thread';
|
||||||
|
@ -88,10 +93,5 @@ const Header = React.memo(({ room, thread, theme }) => {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Header.propTypes = {
|
|
||||||
room: PropTypes.object,
|
|
||||||
thread: PropTypes.object,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withTheme(Header);
|
export default withTheme(Header);
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Video } from 'expo-av';
|
import { Video } from 'expo-av';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { ScrollView, StyleSheet, Text } from 'react-native';
|
import { ScrollView, StyleSheet, Text } from 'react-native';
|
||||||
|
@ -15,6 +14,7 @@ import I18n from '../../i18n';
|
||||||
import { isAndroid } from '../../utils/deviceInfo';
|
import { isAndroid } from '../../utils/deviceInfo';
|
||||||
import { allowPreview } from './utils';
|
import { allowPreview } from './utils';
|
||||||
import { THUMBS_HEIGHT } from './constants';
|
import { THUMBS_HEIGHT } from './constants';
|
||||||
|
import { IAttachment, IUseDimensions } from './interfaces';
|
||||||
|
|
||||||
const MESSAGEBOX_HEIGHT = 56;
|
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
|
<ScrollView
|
||||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||||
contentContainerStyle={[styles.fileContainer, { width, height }]}>
|
contentContainerStyle={[styles.fileContainer, { width, height }]}>
|
||||||
|
@ -45,9 +55,16 @@ const IconPreview = React.memo(({ iconName, title, description, theme, width, he
|
||||||
</ScrollView>
|
</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 type = item?.mime;
|
||||||
const { width, height } = useDimensions();
|
const { width, height } = useDimensions() as IUseDimensions;
|
||||||
const { isLandscape } = useOrientation();
|
const { isLandscape } = useOrientation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const headerHeight = getHeaderHeight(isLandscape);
|
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;
|
export default Preview;
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { FlatList, Image, StyleSheet, View } from 'react-native';
|
import { FlatList, Image, StyleSheet, View } from 'react-native';
|
||||||
import { RectButton, TouchableNativeFeedback, TouchableOpacity } from 'react-native-gesture-handler';
|
import { RectButton, TouchableNativeFeedback, TouchableOpacity } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
|
@ -9,6 +8,7 @@ import { CustomIcon } from '../../lib/Icons';
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
import { THUMBS_HEIGHT } from './constants';
|
import { THUMBS_HEIGHT } from './constants';
|
||||||
import { allowPreview } from './utils';
|
import { allowPreview } from './utils';
|
||||||
|
import { IAttachment } from './interfaces';
|
||||||
|
|
||||||
const THUMB_SIZE = 64;
|
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;
|
const type = item?.mime;
|
||||||
|
|
||||||
if (type?.match(/image/)) {
|
if (type?.match(/image/)) {
|
||||||
// Disallow preview of images too big in order to prevent memory issues on iOS share extension
|
// Disallow preview of images too big in order to prevent memory issues on iOS share extension
|
||||||
if (allowPreview(isShareExtension, item?.size)) {
|
if (allowPreview(isShareExtension, item?.size)) {
|
||||||
return <Image source={{ uri: item.path }} style={[styles.thumb, { borderColor: themes[theme].borderColor }]} />;
|
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/)) {
|
if (type?.match(/video/)) {
|
||||||
|
@ -85,22 +97,23 @@ const ThumbContent = React.memo(({ item, theme, isShareExtension }) => {
|
||||||
<CustomIcon name='camera' size={30} color={themes[theme].tintColor} />
|
<CustomIcon name='camera' size={30} color={themes[theme].tintColor} />
|
||||||
</View>
|
</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
|
// Multiple files upload of files different than image/video is not implemented, so there's no thumb
|
||||||
return null;
|
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}>
|
<ThumbButton style={styles.item} onPress={() => onPress(item)} activeOpacity={0.7}>
|
||||||
<>
|
<>
|
||||||
<ThumbContent item={item} theme={theme} isShareExtension={isShareExtension} />
|
<ThumbContent item={item} theme={theme} isShareExtension={isShareExtension} />
|
||||||
|
@ -121,7 +134,7 @@ const Thumb = ({ item, theme, isShareExtension, onPress, onRemove }) => (
|
||||||
</ThumbButton>
|
</ThumbButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Thumbs = React.memo(({ attachments, theme, isShareExtension, onPress, onRemove }) => {
|
const Thumbs = React.memo(({ attachments, theme, isShareExtension, onPress, onRemove }: IThumbs) => {
|
||||||
if (attachments?.length > 1) {
|
if (attachments?.length > 1) {
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
|
@ -143,24 +156,5 @@ const Thumbs = React.memo(({ attachments, theme, isShareExtension, onPress, onRe
|
||||||
}
|
}
|
||||||
return null;
|
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;
|
export default Thumbs;
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { Component } from 'react';
|
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 { NativeModules, Text, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ShareExtension from 'rn-extensions-share';
|
import ShareExtension from 'rn-extensions-share';
|
||||||
|
@ -24,9 +25,61 @@ import Thumbs from './Thumbs';
|
||||||
import Preview from './Preview';
|
import Preview from './Preview';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { IAttachment, IServer } from './interfaces';
|
||||||
|
|
||||||
class ShareView extends Component {
|
interface IShareViewState {
|
||||||
constructor(props) {
|
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);
|
super(props);
|
||||||
this.messagebox = React.createRef();
|
this.messagebox = React.createRef();
|
||||||
this.files = props.route.params?.attachments ?? [];
|
this.files = props.route.params?.attachments ?? [];
|
||||||
|
@ -34,7 +87,7 @@ class ShareView extends Component {
|
||||||
this.serverInfo = props.route.params?.serverInfo ?? {};
|
this.serverInfo = props.route.params?.serverInfo ?? {};
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
selected: {},
|
selected: {} as IAttachment,
|
||||||
loading: false,
|
loading: false,
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
|
@ -61,7 +114,7 @@ class ShareView extends Component {
|
||||||
const { room, thread, readOnly, attachments } = this.state;
|
const { room, thread, readOnly, attachments } = this.state;
|
||||||
const { navigation, theme } = this.props;
|
const { navigation, theme } = this.props;
|
||||||
|
|
||||||
const options = {
|
const options: StackNavigationOptions = {
|
||||||
headerTitle: () => <Header room={room} thread={thread} />,
|
headerTitle: () => <Header room={room} thread={thread} />,
|
||||||
headerTitleAlign: 'left',
|
headerTitleAlign: 'left',
|
||||||
headerTintColor: themes[theme].previewTintColor
|
headerTintColor: themes[theme].previewTintColor
|
||||||
|
@ -69,9 +122,7 @@ class ShareView extends Component {
|
||||||
|
|
||||||
// if is share extension show default back button
|
// if is share extension show default back button
|
||||||
if (!this.isShareExtension) {
|
if (!this.isShareExtension) {
|
||||||
options.headerLeft = () => (
|
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
|
||||||
<HeaderButton.CloseModal navigation={navigation} buttonStyle={{ color: themes[theme].previewTintColor }} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!attachments.length && !readOnly) {
|
if (!attachments.length && !readOnly) {
|
||||||
|
@ -203,10 +254,10 @@ class ShareView extends Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
selectFile = item => {
|
selectFile = (item: IAttachment) => {
|
||||||
const { attachments, selected } = this.state;
|
const { attachments, selected } = this.state;
|
||||||
if (attachments.length > 0) {
|
if (attachments.length > 0) {
|
||||||
const { text } = this.messagebox.current;
|
const text = this.messagebox.current?.text;
|
||||||
const newAttachments = attachments.map(att => {
|
const newAttachments = attachments.map(att => {
|
||||||
if (att.path === selected.path) {
|
if (att.path === selected.path) {
|
||||||
att.description = text;
|
att.description = text;
|
||||||
|
@ -217,7 +268,7 @@ class ShareView extends Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
removeFile = item => {
|
removeFile = (item: IAttachment) => {
|
||||||
const { selected, attachments } = this.state;
|
const { selected, attachments } = this.state;
|
||||||
let newSelected;
|
let newSelected;
|
||||||
if (item.path === selected.path) {
|
if (item.path === selected.path) {
|
||||||
|
@ -235,7 +286,7 @@ class ShareView extends Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeText = text => {
|
onChangeText = (text: string) => {
|
||||||
this.setState({ text });
|
this.setState({ text });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -318,21 +369,7 @@ class ShareView extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShareView.propTypes = {
|
const mapStateToProps = (state: any) => ({
|
||||||
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 => ({
|
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
server: state.share.server.server || state.server.server,
|
server: state.share.server.server || state.server.server,
|
||||||
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
|
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 React from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import styles from './styles';
|
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
|
<Touch
|
||||||
key={testID}
|
key={testID}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
|
@ -24,14 +33,4 @@ const Item = React.memo(({ left, right, text, onPress, testID, current, theme })
|
||||||
</Touch>
|
</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);
|
export default withTheme(Item);
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { Component } from 'react';
|
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 { ScrollView, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
|
@ -18,38 +19,52 @@ import Navigation from '../../lib/Navigation';
|
||||||
import SidebarItem from './SidebarItem';
|
import SidebarItem from './SidebarItem';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const Separator = React.memo(({ theme }) => <View style={[styles.separator, { borderColor: themes[theme].separatorColor }]} />);
|
interface ISeparatorProps {
|
||||||
Separator.propTypes = {
|
theme: string;
|
||||||
theme: PropTypes.string
|
}
|
||||||
};
|
|
||||||
|
|
||||||
class Sidebar extends Component {
|
// TODO: remove this
|
||||||
static propTypes = {
|
const Separator = React.memo(({ theme }: ISeparatorProps) => (
|
||||||
baseUrl: PropTypes.string,
|
<View style={[styles.separator, { borderColor: themes[theme].separatorColor }]} />
|
||||||
navigation: PropTypes.object,
|
));
|
||||||
Site_Name: PropTypes.string.isRequired,
|
|
||||||
user: PropTypes.object,
|
interface ISidebarState {
|
||||||
state: PropTypes.string,
|
showStatus: boolean;
|
||||||
theme: PropTypes.string,
|
}
|
||||||
loadingServer: PropTypes.bool,
|
|
||||||
useRealName: PropTypes.bool,
|
interface ISidebarProps {
|
||||||
allowStatusMessage: PropTypes.bool,
|
baseUrl: string;
|
||||||
isMasterDetail: PropTypes.bool,
|
navigation: DrawerNavigationProp<any, 'Sidebar'>;
|
||||||
viewStatisticsPermission: PropTypes.object,
|
state: DrawerNavigationState<any>;
|
||||||
viewRoomAdministrationPermission: PropTypes.object,
|
Site_Name: string;
|
||||||
viewUserAdministrationPermission: PropTypes.object,
|
user: {
|
||||||
viewPrivilegedSettingPermission: PropTypes.object
|
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);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
showStatus: false
|
showStatus: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps: ISidebarProps, nextState: ISidebarState) {
|
||||||
const { showStatus, isAdmin } = this.state;
|
const { showStatus } = this.state;
|
||||||
const {
|
const {
|
||||||
Site_Name,
|
Site_Name,
|
||||||
user,
|
user,
|
||||||
|
@ -91,9 +106,6 @@ class Sidebar extends Component {
|
||||||
if (nextProps.useRealName !== useRealName) {
|
if (nextProps.useRealName !== useRealName) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextState.isAdmin !== isAdmin) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!dequal(nextProps.viewStatisticsPermission, viewStatisticsPermission)) {
|
if (!dequal(nextProps.viewStatisticsPermission, viewStatisticsPermission)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -127,7 +139,7 @@ class Sidebar extends Component {
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
|
|
||||||
if (roles) {
|
if (roles) {
|
||||||
isAdmin = allPermissions.reduce((result, permission) => {
|
isAdmin = allPermissions.reduce((result: boolean, permission) => {
|
||||||
if (permission) {
|
if (permission) {
|
||||||
return result || permission.some(r => roles.indexOf(r) !== -1);
|
return result || permission.some(r => roles.indexOf(r) !== -1);
|
||||||
}
|
}
|
||||||
|
@ -137,7 +149,8 @@ class Sidebar extends Component {
|
||||||
return isAdmin;
|
return isAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebarNavigate = route => {
|
sidebarNavigate = (route: string) => {
|
||||||
|
// @ts-ignore
|
||||||
logEvent(events[`SIDEBAR_GO_${route.replace('StackNavigator', '').replace('View', '').toUpperCase()}`]);
|
logEvent(events[`SIDEBAR_GO_${route.replace('StackNavigator', '').replace('View', '').toUpperCase()}`]);
|
||||||
Navigation.navigate(route);
|
Navigation.navigate(route);
|
||||||
};
|
};
|
||||||
|
@ -242,7 +255,7 @@ class Sidebar extends Component {
|
||||||
]}
|
]}
|
||||||
{...scrollPersistTaps}>
|
{...scrollPersistTaps}>
|
||||||
<TouchableWithoutFeedback onPress={this.onPressUser} testID='sidebar-close-drawer'>
|
<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} />
|
<Avatar text={user.username} style={styles.avatar} size={30} />
|
||||||
<View style={styles.headerTextContainer}>
|
<View style={styles.headerTextContainer}>
|
||||||
<View style={styles.headerUsername}>
|
<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,
|
Site_Name: state.settings.Site_Name,
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
baseUrl: state.server.server,
|
baseUrl: state.server.server,
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text } from 'react-native';
|
import { StyleSheet, Text } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
@ -22,20 +22,34 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class UserNotificationPreferencesView extends React.Component {
|
type TKey = 'desktopNotifications' | 'pushNotifications' | 'emailNotificationMode';
|
||||||
static navigationOptions = () => ({
|
|
||||||
|
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')
|
title: I18n.t('Notification_Preferences')
|
||||||
});
|
});
|
||||||
|
|
||||||
static propTypes = {
|
constructor(props: IUserNotificationPreferencesViewProps) {
|
||||||
navigation: PropTypes.object,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
user: PropTypes.shape({
|
|
||||||
id: PropTypes.string
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
preferences: {},
|
preferences: {},
|
||||||
|
@ -51,43 +65,43 @@ class UserNotificationPreferencesView extends React.Component {
|
||||||
this.setState({ preferences, loading: true });
|
this.setState({ preferences, loading: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
findDefaultOption = key => {
|
findDefaultOption = (key: TKey) => {
|
||||||
const { preferences } = this.state;
|
const { preferences } = this.state;
|
||||||
const option = preferences[key] ? OPTIONS[key].find(item => item.value === preferences[key]) : OPTIONS[key][0];
|
const option = preferences[key] ? OPTIONS[key].find(item => item.value === preferences[key]) : OPTIONS[key][0];
|
||||||
return option;
|
return option;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPickerOption = key => {
|
renderPickerOption = (key: TKey) => {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
const text = this.findDefaultOption(key);
|
const text = this.findDefaultOption(key);
|
||||||
return (
|
return <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>{I18n.t(text?.label)}</Text>;
|
||||||
<Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>
|
|
||||||
{I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pickerSelection = (title, key) => {
|
pickerSelection = (title: string, key: TKey) => {
|
||||||
const { preferences } = this.state;
|
const { preferences } = this.state;
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
let values = OPTIONS[key];
|
let values = OPTIONS[key];
|
||||||
|
|
||||||
const defaultOption = this.findDefaultOption(key);
|
const defaultOption = this.findDefaultOption(key);
|
||||||
if (OPTIONS[key][0]?.value !== 'default') {
|
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', {
|
navigation.navigate('PickerView', {
|
||||||
title,
|
title,
|
||||||
data: values,
|
data: values,
|
||||||
value: preferences[key],
|
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 { user } = this.props;
|
||||||
const { id } = user;
|
const { id } = user;
|
||||||
const result = await RocketChat.setUserPreferences(id, params);
|
const result = await RocketChat.setUserPreferences(id, params);
|
||||||
|
@ -111,7 +125,7 @@ class UserNotificationPreferencesView extends React.Component {
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Alert'
|
title='Alert'
|
||||||
testID='user-notification-preference-view-alert'
|
testID='user-notification-preference-view-alert'
|
||||||
onPress={title => this.pickerSelection(title, 'desktopNotifications')}
|
onPress={(title: string) => this.pickerSelection(title, 'desktopNotifications')}
|
||||||
right={() => this.renderPickerOption('desktopNotifications')}
|
right={() => this.renderPickerOption('desktopNotifications')}
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
|
@ -123,8 +137,8 @@ class UserNotificationPreferencesView extends React.Component {
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Alert'
|
title='Alert'
|
||||||
testID='user-notification-preference-view-push-notification'
|
testID='user-notification-preference-view-push-notification'
|
||||||
onPress={title => this.pickerSelection(title, 'mobileNotifications')}
|
onPress={(title: string) => this.pickerSelection(title, 'pushNotifications')}
|
||||||
right={() => this.renderPickerOption('mobileNotifications')}
|
right={() => this.renderPickerOption('pushNotifications')}
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
<List.Info info='Push_Notifications_Alert_Info' />
|
<List.Info info='Push_Notifications_Alert_Info' />
|
||||||
|
@ -135,7 +149,7 @@ class UserNotificationPreferencesView extends React.Component {
|
||||||
<List.Item
|
<List.Item
|
||||||
title='Alert'
|
title='Alert'
|
||||||
testID='user-notification-preference-view-email-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')}
|
right={() => this.renderPickerOption('emailNotificationMode')}
|
||||||
/>
|
/>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
|
@ -151,7 +165,7 @@ class UserNotificationPreferencesView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state: any) => ({
|
||||||
user: getUserSelector(state)
|
user: getUserSelector(state)
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,7 +19,7 @@ const commonOptions = [
|
||||||
|
|
||||||
export const OPTIONS = {
|
export const OPTIONS = {
|
||||||
desktopNotifications: commonOptions,
|
desktopNotifications: commonOptions,
|
||||||
mobileNotifications: commonOptions,
|
pushNotifications: commonOptions,
|
||||||
emailNotificationMode: [
|
emailNotificationMode: [
|
||||||
{
|
{
|
||||||
label: 'Email_Notification_Mode_All',
|
label: 'Email_Notification_Mode_All',
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Switch } from 'react-native';
|
import { Switch } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
@ -12,7 +12,11 @@ import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
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 user = useSelector(state => getUserSelector(state));
|
||||||
const [enableParser, setEnableParser] = useState(user.enableMessageParserEarlyAdoption);
|
const [enableParser, setEnableParser] = useState(user.enableMessageParserEarlyAdoption);
|
||||||
|
|
||||||
|
@ -22,12 +26,12 @@ const UserPreferencesView = ({ navigation }) => {
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const navigateToScreen = (screen, params) => {
|
const navigateToScreen = (screen: string) => {
|
||||||
logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]);
|
logEvent(events.UP_GO_USER_NOTIFICATION_PREF);
|
||||||
navigation.navigate(screen, params);
|
navigation.navigate(screen);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleMessageParser = async value => {
|
const toggleMessageParser = async (value: boolean) => {
|
||||||
try {
|
try {
|
||||||
await RocketChat.saveUserPreferences({ id: user.id, enableMessageParserEarlyAdoption: value });
|
await RocketChat.saveUserPreferences({ id: user.id, enableMessageParserEarlyAdoption: value });
|
||||||
setEnableParser(value);
|
setEnableParser(value);
|
||||||
|
@ -68,8 +72,4 @@ const UserPreferencesView = ({ navigation }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
UserPreferencesView.propTypes = {
|
|
||||||
navigation: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UserPreferencesView;
|
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_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_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_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_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_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)});
|
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_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_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_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_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_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"}]});
|
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)
|
- Firebase/Crashlytics (~> 6.27.0)
|
||||||
- React
|
- React
|
||||||
- RNFBApp
|
- RNFBApp
|
||||||
|
- RNFileViewer (2.1.4):
|
||||||
|
- React-Core
|
||||||
- RNGestureHandler (1.10.3):
|
- RNGestureHandler (1.10.3):
|
||||||
- React-Core
|
- React-Core
|
||||||
- RNImageCropPicker (0.36.3):
|
- RNImageCropPicker (0.36.3):
|
||||||
|
@ -700,6 +702,7 @@ DEPENDENCIES:
|
||||||
- "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)"
|
- "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)"
|
||||||
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
|
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
|
||||||
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
|
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
|
||||||
|
- RNFileViewer (from `../node_modules/react-native-file-viewer`)
|
||||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||||
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
|
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
|
||||||
- RNLocalize (from `../node_modules/react-native-localize`)
|
- RNLocalize (from `../node_modules/react-native-localize`)
|
||||||
|
@ -894,6 +897,8 @@ EXTERNAL SOURCES:
|
||||||
:path: "../node_modules/@react-native-firebase/app"
|
:path: "../node_modules/@react-native-firebase/app"
|
||||||
RNFBCrashlytics:
|
RNFBCrashlytics:
|
||||||
:path: "../node_modules/@react-native-firebase/crashlytics"
|
:path: "../node_modules/@react-native-firebase/crashlytics"
|
||||||
|
RNFileViewer:
|
||||||
|
:path: "../node_modules/react-native-file-viewer"
|
||||||
RNGestureHandler:
|
RNGestureHandler:
|
||||||
:path: "../node_modules/react-native-gesture-handler"
|
:path: "../node_modules/react-native-gesture-handler"
|
||||||
RNImageCropPicker:
|
RNImageCropPicker:
|
||||||
|
@ -1028,6 +1033,7 @@ SPEC CHECKSUMS:
|
||||||
RNFBAnalytics: dae6d7b280ba61c96e1bbdd34aca3154388f025e
|
RNFBAnalytics: dae6d7b280ba61c96e1bbdd34aca3154388f025e
|
||||||
RNFBApp: 6fd8a7e757135d4168bf033a8812c241af7363a0
|
RNFBApp: 6fd8a7e757135d4168bf033a8812c241af7363a0
|
||||||
RNFBCrashlytics: 88de72c2476b5868a892d9523b89b86c527c540e
|
RNFBCrashlytics: 88de72c2476b5868a892d9523b89b86c527c540e
|
||||||
|
RNFileViewer: 83cc066ad795b1f986791d03b56fe0ee14b6a69f
|
||||||
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
|
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
|
||||||
RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d
|
RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d
|
||||||
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
|
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"react-native-document-picker": "5.2.0",
|
"react-native-document-picker": "5.2.0",
|
||||||
"react-native-easy-grid": "^0.2.2",
|
"react-native-easy-grid": "^0.2.2",
|
||||||
"react-native-easy-toast": "^1.2.0",
|
"react-native-easy-toast": "^1.2.0",
|
||||||
|
"react-native-file-viewer": "^2.1.4",
|
||||||
"react-native-gesture-handler": "^1.10.3",
|
"react-native-gesture-handler": "^1.10.3",
|
||||||
"react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker",
|
"react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker",
|
||||||
"react-native-image-progress": "^1.1.1",
|
"react-native-image-progress": "^1.1.1",
|
||||||
|
@ -147,6 +148,7 @@
|
||||||
"@types/lodash": "^4.14.171",
|
"@types/lodash": "^4.14.171",
|
||||||
"@types/react": "^17.0.14",
|
"@types/react": "^17.0.14",
|
||||||
"@types/react-native": "^0.62.7",
|
"@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-config-reader": "^4.1.0",
|
||||||
"@types/react-native-platform-touchable": "^1.1.2",
|
"@types/react-native-platform-touchable": "^1.1.2",
|
||||||
"@types/react-native-scrollable-tab-view": "^0.10.2",
|
"@types/react-native-scrollable-tab-view": "^0.10.2",
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -4372,6 +4372,11 @@
|
||||||
"@types/history" "*"
|
"@types/history" "*"
|
||||||
"@types/react" "*"
|
"@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":
|
"@types/react-native-config-reader@^4.1.0":
|
||||||
version "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"
|
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:
|
dependencies:
|
||||||
prop-types "^15.5.10"
|
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:
|
react-native-flipper@^0.34.0:
|
||||||
version "0.34.0"
|
version "0.34.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-flipper/-/react-native-flipper-0.34.0.tgz#7df1f38ba5d97a9321125fe0fccbe47d99e6fa1d"
|
resolved "https://registry.yarnpkg.com/react-native-flipper/-/react-native-flipper-0.34.0.tgz#7df1f38ba5d97a9321125fe0fccbe47d99e6fa1d"
|
||||||
|
|
Loading…
Reference in New Issue