Merge branch 'develop' into appium-v2
This commit is contained in:
commit
4af0d7a881
|
@ -150,7 +150,7 @@ commands:
|
|||
if [[ $CIRCLE_JOB == "android-build-official" ]]; then
|
||||
./gradlew bundleOfficialPlayRelease
|
||||
fi
|
||||
if [[ $CIRCLE_JOB == "android-build-experimental" ]]; then
|
||||
if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then
|
||||
./gradlew bundleExperimentalPlayRelease
|
||||
fi
|
||||
if [[ ! $KEYSTORE ]]; then
|
||||
|
@ -169,7 +169,7 @@ commands:
|
|||
--source-map=android/app/build/generated/sourcemaps/react/officialPlay/release/app.bundle.map \
|
||||
--bundle android/app/build/generated/assets/react/officialPlay/release/app.bundle
|
||||
fi
|
||||
if [[ $CIRCLE_JOB == "android-build-experimental" ]]; then
|
||||
if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then
|
||||
npx bugsnag-source-maps upload-react-native \
|
||||
--api-key=$BUGSNAG_KEY \
|
||||
--app-version-code=$CIRCLE_BUILD_NUM \
|
||||
|
@ -379,7 +379,19 @@ jobs:
|
|||
resource_class: large
|
||||
steps:
|
||||
- android-build
|
||||
|
||||
|
||||
# Android automatic builds
|
||||
android-automatic-build-experimental:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/android:api-29-node
|
||||
environment:
|
||||
<<: *android-env
|
||||
<<: *bash-env
|
||||
resource_class: large
|
||||
steps:
|
||||
- android-build
|
||||
|
||||
android-build-official:
|
||||
<<: *defaults
|
||||
docker:
|
||||
|
@ -485,6 +497,10 @@ workflows:
|
|||
type: approval
|
||||
requires:
|
||||
- lint-testunit
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- develop
|
||||
- android-build-experimental:
|
||||
requires:
|
||||
- android-hold-build-experimental
|
||||
|
@ -505,7 +521,7 @@ workflows:
|
|||
- android-google-play-production-experimental:
|
||||
requires:
|
||||
- android-hold-google-play-production-experimental
|
||||
|
||||
|
||||
# Android Official
|
||||
- android-hold-build-official:
|
||||
type: approval
|
||||
|
@ -521,3 +537,15 @@ workflows:
|
|||
- android-google-play-beta-official:
|
||||
requires:
|
||||
- android-hold-google-play-beta-official
|
||||
|
||||
# Android Automatic Experimental
|
||||
- android-automatic-build-experimental:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
requires:
|
||||
- lint-testunit
|
||||
- android-google-play-production-experimental:
|
||||
requires:
|
||||
- android-automatic-build-experimental
|
||||
|
|
|
@ -1,33 +1,21 @@
|
|||
import { useBackHandler } from '@react-native-community/hooks';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { Keyboard, Text } from 'react-native';
|
||||
import { HandlerStateChangeEventPayload, State, TapGestureHandler } from 'react-native-gesture-handler';
|
||||
import Animated, { Easing, Extrapolate, interpolateNode, Value } from 'react-native-reanimated';
|
||||
import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState, useCallback } from 'react';
|
||||
import { Keyboard } from 'react-native';
|
||||
import { Easing } from 'react-native-reanimated';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import ScrollBottomSheet from 'react-native-scroll-bottom-sheet';
|
||||
import BottomSheet, { BottomSheetBackdrop } from '@gorhom/bottom-sheet';
|
||||
|
||||
import { themes } from '../../lib/constants';
|
||||
import { useDimensions, useOrientation } from '../../dimensions';
|
||||
import I18n from '../../i18n';
|
||||
import { useTheme } from '../../theme';
|
||||
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
||||
import * as List from '../List';
|
||||
import { Button } from './Button';
|
||||
import { Handle } from './Handle';
|
||||
import { IActionSheetItem, Item } from './Item';
|
||||
import { TActionSheetOptions, TActionSheetOptionsItem } from './Provider';
|
||||
import { TActionSheetOptions } from './Provider';
|
||||
import BottomSheetContent from './BottomSheetContent';
|
||||
import styles, { ITEM_HEIGHT } from './styles';
|
||||
import { testProps } from '../../lib/methods/testProps';
|
||||
|
||||
const getItemLayout = (data: TActionSheetOptionsItem[] | null | undefined, index: number) => ({
|
||||
length: ITEM_HEIGHT,
|
||||
offset: ITEM_HEIGHT * index,
|
||||
index
|
||||
});
|
||||
|
||||
const HANDLE_HEIGHT = isIOS ? 40 : 56;
|
||||
const MAX_SNAP_HEIGHT = 16;
|
||||
const MIN_SNAP_HEIGHT = 16;
|
||||
const CANCEL_HEIGHT = 64;
|
||||
|
||||
const ANIMATION_DURATION = 250;
|
||||
|
@ -40,27 +28,26 @@ const ANIMATION_CONFIG = {
|
|||
|
||||
const ActionSheet = React.memo(
|
||||
forwardRef(({ children }: { children: React.ReactElement }, ref) => {
|
||||
const { theme } = useTheme();
|
||||
const bottomSheetRef = useRef<ScrollBottomSheet<TActionSheetOptionsItem>>(null);
|
||||
const { colors } = useTheme();
|
||||
const bottomSheetRef = useRef<BottomSheet>(null);
|
||||
const [data, setData] = useState<TActionSheetOptions>({} as TActionSheetOptions);
|
||||
const [isVisible, setVisible] = useState(false);
|
||||
const { height } = useDimensions();
|
||||
const { isLandscape } = useOrientation();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const maxSnap = Math.max(
|
||||
height -
|
||||
// Items height
|
||||
ITEM_HEIGHT * (data?.options?.length || 0) -
|
||||
const maxSnap = Math.min(
|
||||
// Items height
|
||||
ITEM_HEIGHT * (data?.options?.length || 0) +
|
||||
// Handle height
|
||||
HANDLE_HEIGHT -
|
||||
HANDLE_HEIGHT +
|
||||
// Custom header height
|
||||
(data?.headerHeight || 0) -
|
||||
(data?.headerHeight || 0) +
|
||||
// Insets bottom height (Notch devices)
|
||||
insets.bottom -
|
||||
insets.bottom +
|
||||
// Cancel button height
|
||||
(data?.hasCancel ? CANCEL_HEIGHT : 0),
|
||||
MAX_SNAP_HEIGHT
|
||||
height - MIN_SNAP_HEIGHT
|
||||
);
|
||||
|
||||
/*
|
||||
|
@ -70,14 +57,13 @@ const ActionSheet = React.memo(
|
|||
* we'll provide more one snap
|
||||
* that point 50% of the whole screen
|
||||
*/
|
||||
const snaps = height - maxSnap > height * 0.6 && !isLandscape ? [maxSnap, height * 0.5, height] : [maxSnap, height];
|
||||
const openedSnapIndex = snaps.length > 2 ? 1 : 0;
|
||||
const closedSnapIndex = snaps.length - 1;
|
||||
const snaps = maxSnap > height * 0.6 && !isLandscape && !data.snaps ? [height * 0.5, maxSnap] : [maxSnap];
|
||||
|
||||
const toggleVisible = () => setVisible(!isVisible);
|
||||
|
||||
const hide = () => {
|
||||
bottomSheetRef.current?.snapTo(closedSnapIndex);
|
||||
bottomSheetRef.current?.close();
|
||||
toggleVisible();
|
||||
};
|
||||
|
||||
const show = (options: TActionSheetOptions) => {
|
||||
|
@ -85,12 +71,6 @@ const ActionSheet = React.memo(
|
|||
toggleVisible();
|
||||
};
|
||||
|
||||
const onBackdropPressed = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
|
||||
if (nativeEvent.oldState === State.ACTIVE) {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
|
||||
useBackHandler(() => {
|
||||
if (isVisible) {
|
||||
hide();
|
||||
|
@ -102,7 +82,6 @@ const ActionSheet = React.memo(
|
|||
if (isVisible) {
|
||||
Keyboard.dismiss();
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
bottomSheetRef.current?.snapTo(openedSnapIndex);
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
|
@ -123,26 +102,18 @@ const ActionSheet = React.memo(
|
|||
</>
|
||||
);
|
||||
|
||||
const renderFooter = () =>
|
||||
data?.hasCancel ? (
|
||||
<Button
|
||||
onPress={hide}
|
||||
style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
// TODO: Remove when migrate Touch
|
||||
theme={theme}
|
||||
accessibilityLabel={I18n.t('Cancel')}>
|
||||
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{I18n.t('Cancel')}</Text>
|
||||
</Button>
|
||||
) : null;
|
||||
|
||||
const renderItem = ({ item }: { item: IActionSheetItem['item'] }) => <Item item={item} hide={hide} />;
|
||||
|
||||
const animatedPosition = React.useRef(new Value(0));
|
||||
const opacity = interpolateNode(animatedPosition.current, {
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, themes[theme].backdropOpacity],
|
||||
extrapolate: Extrapolate.CLAMP
|
||||
}) as any; // The function's return differs from the expected type of opacity, however this problem is something related to lib, maybe when updating the types will be fixed.
|
||||
const renderBackdrop = useCallback(
|
||||
props => (
|
||||
<BottomSheetBackdrop
|
||||
{...props}
|
||||
appearsOnIndex={0}
|
||||
// Backdrop should be visible all the time bottom sheet is open
|
||||
disappearsOnIndex={-1}
|
||||
opacity={colors.backdropOpacity}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {};
|
||||
|
||||
|
@ -150,42 +121,19 @@ const ActionSheet = React.memo(
|
|||
<>
|
||||
{children}
|
||||
{isVisible && (
|
||||
<>
|
||||
<TapGestureHandler onHandlerStateChange={onBackdropPressed}>
|
||||
<Animated.View
|
||||
{...testProps('action-sheet-backdrop')}
|
||||
style={[
|
||||
styles.backdrop,
|
||||
{
|
||||
backgroundColor: themes[theme].backdropColor,
|
||||
opacity
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</TapGestureHandler>
|
||||
<ScrollBottomSheet<TActionSheetOptionsItem>
|
||||
{...testProps('action-sheet')}
|
||||
ref={bottomSheetRef}
|
||||
componentType='FlatList'
|
||||
snapPoints={snaps}
|
||||
initialSnapIndex={closedSnapIndex}
|
||||
renderHandle={renderHandle}
|
||||
onSettle={index => index === closedSnapIndex && toggleVisible()}
|
||||
animatedPosition={animatedPosition.current}
|
||||
containerStyle={{ ...styles.container, ...bottomSheet, backgroundColor: themes[theme].focusedBackground }}
|
||||
animationConfig={ANIMATION_CONFIG}
|
||||
data={data.options}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item.title}
|
||||
style={{ backgroundColor: themes[theme].focusedBackground }}
|
||||
contentContainerStyle={styles.content}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
ListHeaderComponent={List.Separator}
|
||||
ListFooterComponent={renderFooter}
|
||||
getItemLayout={getItemLayout}
|
||||
removeClippedSubviews={isIOS}
|
||||
/>
|
||||
</>
|
||||
<BottomSheet
|
||||
ref={bottomSheetRef}
|
||||
snapPoints={data?.snaps ? data.snaps : snaps}
|
||||
animationConfigs={ANIMATION_CONFIG}
|
||||
animateOnMount={true}
|
||||
backdropComponent={renderBackdrop}
|
||||
handleComponent={renderHandle}
|
||||
enablePanDownToClose
|
||||
style={{ ...styles.container, ...bottomSheet }}
|
||||
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
||||
onChange={index => index === -1 && toggleVisible()}>
|
||||
<BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />
|
||||
</BottomSheet>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import { Text } from 'react-native';
|
||||
import React from 'react';
|
||||
import { BottomSheetView, BottomSheetFlatList } from '@gorhom/bottom-sheet';
|
||||
|
||||
import { Button } from './Button';
|
||||
import I18n from '../../i18n';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IActionSheetItem, Item } from './Item';
|
||||
import { TActionSheetOptionsItem } from './Provider';
|
||||
import styles from './styles';
|
||||
import * as List from '../List';
|
||||
|
||||
interface IBottomSheetContentProps {
|
||||
hasCancel?: boolean;
|
||||
options?: TActionSheetOptionsItem[];
|
||||
hide: () => void;
|
||||
children?: React.ReactElement | null;
|
||||
}
|
||||
|
||||
const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: IBottomSheetContentProps) => {
|
||||
const { theme, colors } = useTheme();
|
||||
|
||||
const renderFooter = () =>
|
||||
hasCancel ? (
|
||||
<Button
|
||||
onPress={hide}
|
||||
style={[styles.button, { backgroundColor: colors.auxiliaryBackground }]}
|
||||
// TODO: Remove when migrate Touch
|
||||
theme={theme}
|
||||
accessibilityLabel={I18n.t('Cancel')}>
|
||||
<Text style={[styles.text, { color: colors.bodyText }]}>{I18n.t('Cancel')}</Text>
|
||||
</Button>
|
||||
) : null;
|
||||
|
||||
const renderItem = ({ item }: { item: IActionSheetItem['item'] }) => <Item item={item} hide={hide} />;
|
||||
|
||||
if (options) {
|
||||
return (
|
||||
<BottomSheetFlatList
|
||||
data={options}
|
||||
refreshing={false}
|
||||
keyExtractor={item => item.title}
|
||||
bounces={true}
|
||||
renderItem={renderItem}
|
||||
style={{ backgroundColor: colors.focusedBackground }}
|
||||
keyboardDismissMode='interactive'
|
||||
indicatorStyle='black'
|
||||
contentContainerStyle={styles.content}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
ListHeaderComponent={List.Separator}
|
||||
ListFooterComponent={renderFooter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <BottomSheetView>{children}</BottomSheetView>;
|
||||
});
|
||||
|
||||
export default BottomSheetContent;
|
|
@ -13,10 +13,13 @@ export type TActionSheetOptionsItem = {
|
|||
};
|
||||
|
||||
export type TActionSheetOptions = {
|
||||
options: TActionSheetOptionsItem[];
|
||||
options?: TActionSheetOptionsItem[];
|
||||
headerHeight?: number;
|
||||
customHeader?: React.ReactElement | null;
|
||||
hasCancel?: boolean;
|
||||
type?: string;
|
||||
children?: React.ReactElement | null;
|
||||
snaps?: string[] | number[];
|
||||
};
|
||||
interface IActionSheetProvider {
|
||||
showActionSheet: (item: TActionSheetOptions) => void;
|
||||
|
|
|
@ -46,8 +46,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
bottomSheet: {
|
||||
width: '50%',
|
||||
alignSelf: 'center',
|
||||
left: '25%'
|
||||
marginHorizontal: '25%'
|
||||
},
|
||||
button: {
|
||||
marginHorizontal: 16,
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react-native';
|
||||
|
||||
import Button from '.';
|
||||
|
||||
const buttonProps = {
|
||||
title: 'Press me!',
|
||||
type: 'primary',
|
||||
onPress: () => {},
|
||||
testID: 'testButton',
|
||||
fontSize: 16,
|
||||
style: {
|
||||
padding: 10,
|
||||
justifyContent: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
const stories = storiesOf('Button', module);
|
||||
|
||||
stories.add('primary button', () => <Button {...buttonProps} />);
|
||||
|
||||
stories.add('secondary button', () => <Button {...buttonProps} type='secondary' />);
|
||||
|
||||
stories.add('loading button', () => <Button loading {...buttonProps} />);
|
||||
|
||||
stories.add('disabled button', () => <Button disabled {...buttonProps} />);
|
||||
|
||||
stories.add('disabled loading button', () => <Button disabled loading {...buttonProps} />);
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { fireEvent, render } from '@testing-library/react-native';
|
||||
|
||||
import Button from '.';
|
||||
|
||||
const onPressMock = jest.fn();
|
||||
|
||||
const testProps = {
|
||||
title: 'Press me!',
|
||||
type: 'primary',
|
||||
onPress: onPressMock,
|
||||
testID: 'testButton',
|
||||
initialText: 'Initial text',
|
||||
textAfterPress: 'Button pressed!'
|
||||
};
|
||||
|
||||
const TestButton = ({ loading = false, disabled = false }) => (
|
||||
<View>
|
||||
<Button
|
||||
title={testProps.title}
|
||||
type={testProps.title}
|
||||
onPress={testProps.onPress}
|
||||
testID={testProps.testID}
|
||||
accessibilityLabel={testProps.title}
|
||||
disabled={disabled}
|
||||
loading={loading}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
describe('ButtonTests', () => {
|
||||
test('rendered', async () => {
|
||||
const { findByTestId } = render(<TestButton />);
|
||||
const Button = await findByTestId(testProps.testID);
|
||||
expect(Button).toBeTruthy();
|
||||
});
|
||||
|
||||
test('rendered with correct title', async () => {
|
||||
const { findByText } = render(<TestButton />);
|
||||
const ButtonTitle = await findByText(testProps.title);
|
||||
expect(ButtonTitle).toBeTruthy();
|
||||
expect(ButtonTitle.props.children).toEqual(testProps.title);
|
||||
});
|
||||
|
||||
test('find button using accessibilityLabel', async () => {
|
||||
const { findByA11yLabel } = render(<TestButton />);
|
||||
const Button = await findByA11yLabel(testProps.title);
|
||||
expect(Button).toBeTruthy();
|
||||
});
|
||||
|
||||
test('title not visible while loading', async () => {
|
||||
const { queryByText } = render(<TestButton loading={true} />);
|
||||
const ButtonTitle = await queryByText(testProps.title);
|
||||
expect(ButtonTitle).toBeNull();
|
||||
});
|
||||
|
||||
test('should not trigger onPress on disabled button', async () => {
|
||||
const { findByTestId } = render(<TestButton disabled={true} />);
|
||||
const Button = await findByTestId(testProps.testID);
|
||||
fireEvent.press(Button);
|
||||
expect(onPressMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should trigger onPress function on button press', async () => {
|
||||
const { findByTestId } = render(<TestButton />);
|
||||
const Button = await findByTestId(testProps.testID);
|
||||
fireEvent.press(Button);
|
||||
expect(onPressMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Button disabled button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"testButton\\",\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3,\\"padding\\":10}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
||||
exports[`Storyshots Button disabled loading button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"testButton\\",\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3,\\"padding\\":10}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"animating\\":true,\\"color\\":\\"#ffffff\\",\\"hidesWhenStopped\\":true,\\"size\\":\\"small\\",\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null]},\\"children\\":null}]}"`;
|
||||
|
||||
exports[`Storyshots Button loading button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"testButton\\",\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"animating\\":true,\\"color\\":\\"#ffffff\\",\\"hidesWhenStopped\\":true,\\"size\\":\\"small\\",\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null]},\\"children\\":null}]}"`;
|
||||
|
||||
exports[`Storyshots Button primary button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"testButton\\",\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
||||
exports[`Storyshots Button secondary button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"testButton\\",\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
|
@ -1,26 +1,21 @@
|
|||
import React from 'react';
|
||||
import { ButtonProps, StyleSheet, Text } from 'react-native';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
import { StyleProp, StyleSheet, Text, TextStyle } from 'react-native';
|
||||
import Touchable, { PlatformTouchableProps } from 'react-native-platform-touchable';
|
||||
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { testProps } from '../../lib/methods/testProps';
|
||||
import { useTheme } from '../../theme';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import ActivityIndicator from '../ActivityIndicator';
|
||||
|
||||
interface IButtonProps extends ButtonProps {
|
||||
interface IButtonProps extends PlatformTouchableProps {
|
||||
title: string;
|
||||
type: string;
|
||||
onPress(): void;
|
||||
disabled: boolean;
|
||||
backgroundColor: string;
|
||||
loading: boolean;
|
||||
theme: TSupportedThemes;
|
||||
color: string;
|
||||
fontSize: any;
|
||||
style: any;
|
||||
styleText?: any;
|
||||
testID: string;
|
||||
onPress: () => void;
|
||||
type?: string;
|
||||
backgroundColor?: string;
|
||||
loading?: boolean;
|
||||
color?: string;
|
||||
fontSize?: number;
|
||||
styleText?: StyleProp<TextStyle>[];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -32,7 +27,6 @@ const styles = StyleSheet.create({
|
|||
marginBottom: 12
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textMedium,
|
||||
...sharedStyles.textAlignCenter
|
||||
},
|
||||
|
@ -41,60 +35,50 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
export default class Button extends React.PureComponent<Partial<IButtonProps>, any> {
|
||||
static defaultProps = {
|
||||
title: 'Press me!',
|
||||
type: 'primary',
|
||||
onPress: () => alert('It works!'),
|
||||
disabled: false,
|
||||
loading: false
|
||||
};
|
||||
const Button = ({
|
||||
type = 'primary',
|
||||
disabled = false,
|
||||
loading = false,
|
||||
fontSize = 16,
|
||||
title,
|
||||
onPress,
|
||||
backgroundColor,
|
||||
color,
|
||||
style,
|
||||
styleText,
|
||||
testID,
|
||||
...otherProps
|
||||
}: IButtonProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const isPrimary = type === 'primary';
|
||||
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
type,
|
||||
onPress,
|
||||
disabled,
|
||||
backgroundColor,
|
||||
color,
|
||||
loading,
|
||||
style,
|
||||
theme,
|
||||
fontSize,
|
||||
styleText,
|
||||
testID,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
const isPrimary = type === 'primary';
|
||||
|
||||
let textColor = isPrimary ? themes[theme!].buttonText : themes[theme!].bodyText;
|
||||
if (color) {
|
||||
textColor = color;
|
||||
}
|
||||
|
||||
return (
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
disabled={disabled || loading}
|
||||
style={[
|
||||
styles.container,
|
||||
backgroundColor
|
||||
? { backgroundColor }
|
||||
: { backgroundColor: isPrimary ? themes[theme!].actionTintColor : themes[theme!].backgroundColor },
|
||||
disabled && styles.disabled,
|
||||
style
|
||||
]}
|
||||
{...otherProps}
|
||||
{...testProps((testID || title) as string)}>
|
||||
{loading ? (
|
||||
<ActivityIndicator color={textColor} />
|
||||
) : (
|
||||
<Text style={[styles.text, { color: textColor }, fontSize && { fontSize }, styleText]} accessibilityLabel={title}>
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
</Touchable>
|
||||
);
|
||||
let textColor = isPrimary ? colors.buttonText : colors.bodyText;
|
||||
if (color) {
|
||||
textColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
disabled={disabled || loading}
|
||||
style={[
|
||||
styles.container,
|
||||
backgroundColor ? { backgroundColor } : { backgroundColor: isPrimary ? colors.actionTintColor : colors.backgroundColor },
|
||||
disabled && styles.disabled,
|
||||
style
|
||||
]}
|
||||
accessibilityLabel={title}
|
||||
{...otherProps}
|
||||
{...testProps((testID || title) as string)}>
|
||||
{loading ? (
|
||||
<ActivityIndicator color={textColor} />
|
||||
) : (
|
||||
<Text style={[styles.text, { color: textColor, fontSize }, styleText]} accessibilityLabel={title}>
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
</Touchable>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
|
|
|
@ -327,7 +327,6 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
|
|||
title={collapsed ? I18n.t('Onboarding_more_options') : I18n.t('Onboarding_less_options')}
|
||||
type='secondary'
|
||||
onPress={this.toggleServices}
|
||||
theme={theme}
|
||||
style={styles.options}
|
||||
color={themes[theme].actionTintColor}
|
||||
/>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Q } from '@nozbe/watermelondb';
|
|||
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
|
||||
|
||||
import { generateTriggerId } from '../../lib/methods/actions';
|
||||
import TextInput, { IThemedTextInput } from '../../presentation/TextInput';
|
||||
import TextInput, { IThemedTextInput } from '../TextInput';
|
||||
import { userTyping as userTypingAction } from '../../actions/room';
|
||||
import styles from './styles';
|
||||
import database from '../../lib/database';
|
||||
|
@ -678,7 +678,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
canUploadFile = (file: any) => {
|
||||
const { permissionToUpload } = this.state;
|
||||
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props;
|
||||
const result = canUploadFile(file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize, permissionToUpload);
|
||||
const result = canUploadFile({
|
||||
file,
|
||||
allowList: FileUpload_MediaTypeWhiteList,
|
||||
maxFileSize: FileUpload_MaxFileSize,
|
||||
permissionToUploadFile: permissionToUpload
|
||||
});
|
||||
if (result.success) {
|
||||
return true;
|
||||
}
|
||||
|
@ -726,7 +731,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
chooseFile = async () => {
|
||||
logEvent(events.ROOM_BOX_ACTION_FILE);
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
const res = await DocumentPicker.pickSingle({
|
||||
type: [DocumentPicker.types.allFiles]
|
||||
});
|
||||
const file = {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { StyleProp, ViewStyle } from 'react-native';
|
||||
import { SvgUri } from 'react-native-svg';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { OmnichannelSourceType, IApplicationState, IOmnichannelSource } from '../../definitions';
|
||||
import { OmnichannelSourceType, IOmnichannelSource } from '../../definitions';
|
||||
import { STATUS_COLORS } from '../../lib/constants';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||
|
||||
interface IIconMap {
|
||||
|
@ -29,8 +29,8 @@ interface IOmnichannelRoomIconProps {
|
|||
}
|
||||
|
||||
export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnichannelRoomIconProps) => {
|
||||
const baseUrl = useSelector((state: IApplicationState) => state.server?.server);
|
||||
const connected = useSelector((state: IApplicationState) => state.meteor?.connected);
|
||||
const baseUrl = useAppSelector(state => state.server?.server);
|
||||
const connected = useAppSelector(state => state.meteor?.connected);
|
||||
|
||||
if (sourceType?.type === OmnichannelSourceType.APP && sourceType.id && sourceType.sidebarIcon && connected) {
|
||||
return (
|
||||
|
|
|
@ -5,7 +5,7 @@ import Touchable from 'react-native-platform-touchable';
|
|||
import { themes } from '../lib/constants';
|
||||
import I18n from '../i18n';
|
||||
import { CustomIcon } from './CustomIcon';
|
||||
import TextInput from '../presentation/TextInput';
|
||||
import TextInput from './TextInput';
|
||||
import { useTheme } from '../theme';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
import sharedStyles from '../views/Styles';
|
||||
|
|
|
@ -5,7 +5,7 @@ import I18n from '../i18n';
|
|||
import { useTheme } from '../theme';
|
||||
import sharedStyles from '../views/Styles';
|
||||
import { themes } from '../lib/constants';
|
||||
import TextInput from '../presentation/TextInput';
|
||||
import TextInput from './TextInput';
|
||||
import { isIOS, isTablet } from '../utils/deviceInfo';
|
||||
import { useOrientation } from '../dimensions';
|
||||
import { testProps } from '../lib/methods/testProps';
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IApplicationState, TUserStatus } from '../../definitions';
|
||||
import { TUserStatus } from '../../definitions';
|
||||
import Status from './Status';
|
||||
import { IStatus } from './definition';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
const StatusContainer = ({ id, style, size = 32, ...props }: Omit<IStatus, 'status'>): React.ReactElement => {
|
||||
const status = useSelector((state: IApplicationState) =>
|
||||
const status = useAppSelector(state =>
|
||||
state.meteor.connected ? state.activeUsers[id] && state.activeUsers[id].status : 'loading'
|
||||
) as TUserStatus;
|
||||
return <Status size={size} style={style} status={status} {...props} />;
|
||||
|
|
|
@ -2,13 +2,13 @@ import React from 'react';
|
|||
import { StyleProp, StyleSheet, Text, TextInputProps, TextInput as RNTextInput, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
|
||||
import sharedStyles from '../views/Styles';
|
||||
import TextInput from '../presentation/TextInput';
|
||||
import { themes } from '../lib/constants';
|
||||
import { CustomIcon, TIconsName } from './CustomIcon';
|
||||
import ActivityIndicator from './ActivityIndicator';
|
||||
import { testProps } from '../lib/methods/testProps';
|
||||
import { TSupportedThemes } from '../theme';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import TextInput from './index';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||
import ActivityIndicator from '../ActivityIndicator';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { testProps } from '../../lib/methods/testProps';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
error: {
|
||||
|
@ -71,7 +71,7 @@ interface IRCTextInputState {
|
|||
showPassword: boolean;
|
||||
}
|
||||
|
||||
export default class RCTextInput extends React.PureComponent<IRCTextInputProps, IRCTextInputState> {
|
||||
export default class FormTextInput extends React.PureComponent<IRCTextInputProps, IRCTextInputState> {
|
||||
static defaultProps = {
|
||||
error: {},
|
||||
theme: 'light'
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import { storiesOf } from '@storybook/react-native';
|
||||
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import TextInput from './TextInput';
|
||||
import FormTextInput from './FormTextInput';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
paddingHorizontal: {
|
||||
|
@ -23,9 +23,9 @@ const theme = 'light';
|
|||
stories.add('Short and Long Text', () => (
|
||||
<>
|
||||
<View style={styles.paddingHorizontal}>
|
||||
<TextInput label='Short Text' placeholder='placeholder' value={item.name} theme={theme} />
|
||||
<FormTextInput label='Short Text' placeholder='placeholder' value={item.name} theme={theme} />
|
||||
|
||||
<TextInput label='Long Text' placeholder='placeholder' value={item.longText} theme={theme} />
|
||||
<FormTextInput label='Long Text' placeholder='placeholder' value={item.longText} theme={theme} />
|
||||
</View>
|
||||
</>
|
||||
));
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { I18nManager, StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native';
|
||||
|
||||
import { IRCTextInputProps } from '../containers/TextInput';
|
||||
import { themes } from '../lib/constants';
|
||||
import { testProps } from '../lib/methods/testProps';
|
||||
import { TSupportedThemes } from '../theme';
|
||||
import { IRCTextInputProps } from './FormTextInput';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { testProps } from '../../lib/methods/testProps';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
input: {
|
|
@ -6,7 +6,7 @@ import Modal from 'react-native-modal';
|
|||
import useDeepCompareEffect from 'use-deep-compare-effect';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import TextInput from '../TextInput';
|
||||
import FormTextInput from '../TextInput/FormTextInput';
|
||||
import I18n from '../../i18n';
|
||||
import EventEmitter from '../../utils/events';
|
||||
import { useTheme } from '../../theme';
|
||||
|
@ -114,7 +114,7 @@ const TwoFactor = React.memo(({ isMasterDetail }: { isMasterDetail: boolean }) =
|
|||
]}>
|
||||
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
|
||||
{method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
value={code}
|
||||
theme={theme}
|
||||
inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
|
||||
|
@ -139,16 +139,8 @@ const TwoFactor = React.memo(({ isMasterDetail }: { isMasterDetail: boolean }) =
|
|||
backgroundColor={themes[theme].chatComponentBackground}
|
||||
style={styles.button}
|
||||
onPress={onCancel}
|
||||
theme={theme}
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Send')}
|
||||
type='primary'
|
||||
style={styles.button}
|
||||
onPress={onSubmit}
|
||||
theme={theme}
|
||||
testID='two-factor-send'
|
||||
/>
|
||||
<Button title={I18n.t('Send')} type='primary' style={styles.button} onPress={onSubmit} testID='two-factor-send' />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -4,10 +4,8 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
|||
import Button from '../Button';
|
||||
import I18n from '../../i18n';
|
||||
import { IActions } from './interfaces';
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
export const Actions = ({ blockId, appId, elements, parser }: IActions) => {
|
||||
const { theme } = useTheme();
|
||||
const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5);
|
||||
const renderedElements = showMoreVisible ? elements?.slice(0, 5) : elements;
|
||||
|
||||
|
@ -18,7 +16,7 @@ export const Actions = ({ blockId, appId, elements, parser }: IActions) => {
|
|||
return (
|
||||
<>
|
||||
<Elements />
|
||||
{showMoreVisible && <Button theme={theme} title={I18n.t('Show_more')} onPress={() => setShowMoreVisible(false)} />}
|
||||
{showMoreVisible && <Button title={I18n.t('Show_more')} onPress={() => setShowMoreVisible(false)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -56,9 +56,7 @@ export const DatePicker = ({ element, language, action, context, loading, value,
|
|||
}
|
||||
};
|
||||
|
||||
let button = placeholder ? (
|
||||
<Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} theme={theme} />
|
||||
) : null;
|
||||
let button = placeholder ? <Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} /> : null;
|
||||
|
||||
if (context === BLOCK_CONTEXT.FORM) {
|
||||
button = (
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||
|
||||
import Button from '../../Button';
|
||||
import TextInput from '../../TextInput';
|
||||
import FormTextInput from '../../TextInput/FormTextInput';
|
||||
import { textParser } from '../utils';
|
||||
import { themes } from '../../../lib/constants';
|
||||
import I18n from '../../../i18n';
|
||||
|
@ -139,7 +139,7 @@ export const MultiSelect = React.memo(
|
|||
return (
|
||||
<View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
testID='multi-select-search'
|
||||
onChangeText={onSearch || onSearchChange}
|
||||
placeholder={I18n.t('Search')}
|
||||
|
@ -157,7 +157,7 @@ export const MultiSelect = React.memo(
|
|||
});
|
||||
|
||||
let button = multiselect ? (
|
||||
<Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} theme={theme} />
|
||||
<Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} />
|
||||
) : (
|
||||
<Input
|
||||
onPress={onShow}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKi
|
|||
|
||||
import Markdown, { MarkdownPreview } from '../markdown';
|
||||
import Button from '../Button';
|
||||
import TextInput from '../TextInput';
|
||||
import FormTextInput from '../TextInput/FormTextInput';
|
||||
import { textParser, useBlockContext } from './utils';
|
||||
import { themes } from '../../lib/constants';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
@ -63,7 +63,6 @@ class MessageParser extends UiKitParserMessage {
|
|||
button(element: IButton, context: BlockContext) {
|
||||
const { text, value, actionId, style } = element;
|
||||
const [{ loading }, action] = useBlockContext(element, context);
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<Button
|
||||
key={actionId}
|
||||
|
@ -72,7 +71,6 @@ class MessageParser extends UiKitParserMessage {
|
|||
loading={loading}
|
||||
onPress={() => action({ value })}
|
||||
style={styles.button}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -171,7 +169,7 @@ class ModalParser extends UiKitParserModal {
|
|||
const { theme } = useContext(ThemeContext);
|
||||
const { multiline, actionId, placeholder } = element;
|
||||
return (
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
key={actionId}
|
||||
placeholder={plainText(placeholder)}
|
||||
multiline={multiline}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import { Image, StyleProp, Text, TextStyle } from 'react-native';
|
||||
import { Node, Parser } from 'commonmark';
|
||||
import { Parser } from 'commonmark';
|
||||
import Renderer from 'commonmark-react-renderer';
|
||||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import MarkdownLink from './Link';
|
||||
import MarkdownList from './List';
|
||||
import MarkdownListItem from './ListItem';
|
||||
|
@ -37,7 +36,6 @@ interface IMarkdownProps {
|
|||
baseUrl?: string;
|
||||
username?: string;
|
||||
tmid?: string;
|
||||
isEdited?: boolean;
|
||||
numberOfLines?: number;
|
||||
customEmojis?: boolean;
|
||||
useRealName?: boolean;
|
||||
|
@ -133,9 +131,7 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
|
||||
table: this.renderTable,
|
||||
table_row: this.renderTableRow,
|
||||
table_cell: this.renderTableCell,
|
||||
|
||||
editedIndicator: this.renderEditedIndicator
|
||||
table_cell: this.renderTableCell
|
||||
},
|
||||
renderParagraphsInLists: true
|
||||
});
|
||||
|
@ -145,21 +141,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
return !!enableMessageParser && !!md;
|
||||
}
|
||||
|
||||
editedMessage = (ast: any) => {
|
||||
const { isEdited } = this.props;
|
||||
if (isEdited) {
|
||||
const editIndicatorNode = new Node('edited_indicator');
|
||||
if (ast.lastChild && ['heading', 'paragraph'].includes(ast.lastChild.type)) {
|
||||
ast.lastChild.appendChild(editIndicatorNode);
|
||||
} else {
|
||||
const node = new Node('paragraph');
|
||||
node.appendChild(editIndicatorNode);
|
||||
|
||||
ast.appendChild(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderText = ({ context, literal }: { context: []; literal: string }) => {
|
||||
const { numberOfLines, style = [] } = this.props;
|
||||
const defaultStyle = [this.isMessageContainsOnlyEmoji ? styles.textBig : {}, ...context.map(type => styles[type])];
|
||||
|
@ -274,11 +255,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
return <Image style={styles.inlineImage} source={{ uri: encodeURI(src) }} />;
|
||||
};
|
||||
|
||||
renderEditedIndicator = () => {
|
||||
const { theme } = this.props;
|
||||
return <Text style={[styles.edited, { color: themes[theme].auxiliaryText }]}> ({I18n.t('edited')})</Text>;
|
||||
};
|
||||
|
||||
renderHeading = ({ children, level }: any) => {
|
||||
const { numberOfLines, theme } = this.props;
|
||||
// @ts-ignore
|
||||
|
@ -373,7 +349,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
let ast = parser.parse(m);
|
||||
ast = mergeTextNodes(ast);
|
||||
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
|
||||
this.editedMessage(ast);
|
||||
return this.renderer.render(ast);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,10 +94,6 @@ export default StyleSheet.create({
|
|||
fontSize: 16,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
edited: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
heading1: {
|
||||
...sharedStyles.textBold,
|
||||
fontSize: 24
|
||||
|
|
|
@ -43,7 +43,7 @@ const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
|
|||
};
|
||||
|
||||
if (element.type === 'button') {
|
||||
return <Button theme={theme} onPress={onPress} title={element.text} />;
|
||||
return <Button onPress={onPress} title={element.text} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -59,7 +59,6 @@ const Content = React.memo(
|
|||
getCustomEmoji={props.getCustomEmoji}
|
||||
enableMessageParser={user.enableMessageParserEarlyAdoption}
|
||||
username={user.username}
|
||||
isEdited={props.isEdited}
|
||||
channels={props.channels}
|
||||
mentions={props.mentions}
|
||||
navToRoomInfo={props.navToRoomInfo}
|
||||
|
@ -72,7 +71,7 @@ const Content = React.memo(
|
|||
}
|
||||
|
||||
// If this is a encrypted message and is not a preview
|
||||
if (props.type === E2E_MESSAGE_TYPE && !isPreview) {
|
||||
if (props.type === E2E_MESSAGE_TYPE && !isPreview && !props.isHeader) {
|
||||
content = (
|
||||
<View style={styles.flex}>
|
||||
<View style={styles.contentContainer}>{content}</View>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import React, { memo } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import { useTheme } from '../../theme';
|
||||
import { themes } from '../../lib/constants';
|
||||
import styles from './styles';
|
||||
|
||||
const Edited = memo(({ isEdited }: { isEdited: boolean }) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
if (!isEdited) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.leftIcons}>
|
||||
<CustomIcon name='edit' size={16} color={themes[theme].auxiliaryText} />
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
export default Edited;
|
|
@ -17,7 +17,7 @@ const Encrypted = React.memo(({ type }: { type: string }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Touchable onPress={onEncryptedPress} style={styles.encrypted} hitSlop={BUTTON_HIT_SLOP}>
|
||||
<Touchable onPress={onEncryptedPress} style={styles.leftIcons} hitSlop={BUTTON_HIT_SLOP}>
|
||||
<CustomIcon name='encrypted' size={16} color={themes[theme].auxiliaryText} />
|
||||
</Touchable>
|
||||
);
|
||||
|
|
|
@ -20,6 +20,8 @@ import CallButton from './CallButton';
|
|||
import { themes } from '../../lib/constants';
|
||||
import { IMessage, IMessageInner, IMessageTouchable } from './interfaces';
|
||||
import { useTheme } from '../../theme';
|
||||
import Edited from './Edited';
|
||||
import MessageError from './MessageError';
|
||||
|
||||
const MessageInner = React.memo((props: IMessageInner) => {
|
||||
const { attachments } = props;
|
||||
|
@ -102,6 +104,12 @@ const Message = React.memo((props: IMessage) => {
|
|||
<View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}>
|
||||
<MessageInner {...props} />
|
||||
</View>
|
||||
{!props.isHeader ? (
|
||||
<>
|
||||
<Edited isEdited={props.isEdited} />
|
||||
<MessageError hasError={props.hasError} />
|
||||
</>
|
||||
) : null}
|
||||
<ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread || false} />
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -18,8 +18,8 @@ const MessageError = React.memo(
|
|||
}
|
||||
|
||||
return (
|
||||
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}>
|
||||
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />
|
||||
<Touchable onPress={onErrorPress} style={styles.leftIcons} hitSlop={BUTTON_HIT_SLOP}>
|
||||
<CustomIcon name='warning' color={themes[theme].dangerColor} size={16} />
|
||||
</Touchable>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useTheme } from '../../theme';
|
|||
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread: boolean }) => {
|
||||
const { theme } = useTheme();
|
||||
if (isReadReceiptEnabled && !unread && unread !== null) {
|
||||
return <CustomIcon name='check' color={themes[theme].tintColor} size={15} style={styles.readReceipt} />;
|
||||
return <CustomIcon name='check' color={themes[theme].tintColor} size={16} style={[styles.leftIcons, styles.readReceipt]} />;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
|
|
@ -11,12 +11,12 @@ import sharedStyles from '../../views/Styles';
|
|||
import { themes } from '../../lib/constants';
|
||||
import MessageContext from './Context';
|
||||
import { fileDownloadAndPreview } from '../../utils/fileDownload';
|
||||
import { IAttachment } from '../../definitions/IAttachment';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { IAttachment, TGetCustomEmoji } from '../../definitions';
|
||||
import RCActivityIndicator from '../ActivityIndicator';
|
||||
import Attachments from './Attachments';
|
||||
import { TSupportedThemes, useTheme } from '../../theme';
|
||||
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
|
||||
import messageStyles from './styles';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
|
@ -44,15 +44,9 @@ const styles = StyleSheet.create({
|
|||
marginBottom: 8
|
||||
},
|
||||
author: {
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
time: {
|
||||
fontSize: 12,
|
||||
marginLeft: 8,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
fieldsContainer: {
|
||||
flex: 1,
|
||||
flexWrap: 'wrap',
|
||||
|
@ -106,8 +100,8 @@ const Title = React.memo(
|
|||
{attachment.author_name ? (
|
||||
<Text style={[styles.author, { color: themes[theme].auxiliaryTintColor }]}>{attachment.author_name}</Text>
|
||||
) : null}
|
||||
{time ? <Text style={[messageStyles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text> : null}
|
||||
{attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null}
|
||||
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text> : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import MessageContext from './Context';
|
|||
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils';
|
||||
import { SubscriptionType } from '../../definitions';
|
||||
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||
import Edited from './Edited';
|
||||
import Encrypted from './Encrypted';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -19,7 +21,12 @@ const styles = StyleSheet.create({
|
|||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
},
|
||||
actionIcons: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
username: {
|
||||
flexShrink: 1,
|
||||
fontSize: 16,
|
||||
lineHeight: 22,
|
||||
...sharedStyles.textMedium
|
||||
|
@ -29,7 +36,6 @@ const styles = StyleSheet.create({
|
|||
...sharedStyles.textMedium
|
||||
},
|
||||
titleContainer: {
|
||||
flexShrink: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
|
@ -41,7 +47,7 @@ const styles = StyleSheet.create({
|
|||
|
||||
interface IMessageUser {
|
||||
isHeader?: boolean;
|
||||
hasError?: boolean;
|
||||
hasError: boolean;
|
||||
useRealName?: boolean;
|
||||
author?: {
|
||||
_id: string;
|
||||
|
@ -53,10 +59,11 @@ interface IMessageUser {
|
|||
timeFormat?: string;
|
||||
navToRoomInfo?: (navParam: IRoomInfoParam) => void;
|
||||
type: string;
|
||||
isEdited: boolean;
|
||||
}
|
||||
|
||||
const User = React.memo(
|
||||
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, ...props }: IMessageUser) => {
|
||||
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, isEdited, ...props }: IMessageUser) => {
|
||||
const { user } = useContext(MessageContext);
|
||||
const { theme } = useTheme();
|
||||
|
||||
|
@ -99,9 +106,13 @@ const User = React.memo(
|
|||
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
|
||||
{textContent}
|
||||
</Text>
|
||||
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
||||
</TouchableOpacity>
|
||||
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text>
|
||||
{hasError ? <MessageError hasError={hasError} {...props} /> : null}
|
||||
<View style={styles.actionIcons}>
|
||||
<Encrypted type={type} />
|
||||
<Edited isEdited={isEdited} />
|
||||
<MessageError hasError={hasError} {...props} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -256,7 +256,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
|
||||
get isInfo(): string | boolean {
|
||||
const { item } = this.props;
|
||||
if (['e2e', 'discussion-created'].includes(item.t)) {
|
||||
if (['e2e', 'discussion-created', 'jitsi_call_started'].includes(item.t)) {
|
||||
return false;
|
||||
}
|
||||
return item.t;
|
||||
|
|
|
@ -60,6 +60,8 @@ export interface IMessageContent {
|
|||
isIgnored: boolean;
|
||||
type: string;
|
||||
comment?: string;
|
||||
hasError: boolean;
|
||||
isHeader: boolean;
|
||||
}
|
||||
|
||||
export interface IMessageEmoji {
|
||||
|
|
|
@ -74,10 +74,6 @@ export default StyleSheet.create({
|
|||
avatarSmall: {
|
||||
marginLeft: 16
|
||||
},
|
||||
errorButton: {
|
||||
paddingLeft: 10,
|
||||
paddingVertical: 5
|
||||
},
|
||||
buttonContainer: {
|
||||
marginTop: 8,
|
||||
flexDirection: 'row',
|
||||
|
@ -133,7 +129,7 @@ export default StyleSheet.create({
|
|||
...sharedStyles.textRegular
|
||||
},
|
||||
time: {
|
||||
fontSize: 12,
|
||||
fontSize: 13,
|
||||
marginLeft: 8,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
|
@ -167,12 +163,13 @@ export default StyleSheet.create({
|
|||
threadBell: {
|
||||
marginLeft: 8
|
||||
},
|
||||
leftIcons: {
|
||||
paddingLeft: 5,
|
||||
paddingVertical: 5
|
||||
},
|
||||
readReceipt: {
|
||||
lineHeight: 20
|
||||
},
|
||||
encrypted: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
threadDetails: {
|
||||
flex: 1,
|
||||
marginLeft: 12
|
||||
|
|
|
@ -56,3 +56,14 @@ export interface IServerAttachment {
|
|||
url: string;
|
||||
user: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
}
|
||||
|
||||
export interface IShareAttachment {
|
||||
filename: string;
|
||||
description?: string;
|
||||
size: number;
|
||||
mime?: string;
|
||||
path: string;
|
||||
canUpload: boolean;
|
||||
error?: any;
|
||||
uri: string;
|
||||
}
|
||||
|
|
|
@ -18,4 +18,7 @@ export interface ICredentials {
|
|||
fullName?: AppleAuthenticationFullName | null;
|
||||
email?: string | null;
|
||||
identityToken?: string | null;
|
||||
credentialToken?: string;
|
||||
saml?: boolean;
|
||||
cas?: { credentialToken?: string };
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export interface IVisitorEmail {
|
|||
|
||||
export interface ILivechatVisitor extends IRocketChatRecord {
|
||||
username: string;
|
||||
ts: Date;
|
||||
ts: Date | string;
|
||||
token: string;
|
||||
department?: string;
|
||||
name?: string;
|
||||
|
|
|
@ -89,11 +89,7 @@ export interface IMessageFromServer {
|
|||
drid?: string;
|
||||
dcount?: number;
|
||||
dml: string | Date;
|
||||
starred?:
|
||||
| {
|
||||
_id: string;
|
||||
}
|
||||
| boolean;
|
||||
starred?: boolean;
|
||||
pinned?: boolean;
|
||||
pinnedAt?: string | Date;
|
||||
pinnedBy?: {
|
||||
|
@ -120,11 +116,6 @@ export interface IMessage extends IMessageFromServer {
|
|||
emoji?: string;
|
||||
status?: number;
|
||||
pinned?: boolean;
|
||||
starred?:
|
||||
| {
|
||||
_id: string;
|
||||
}
|
||||
| boolean;
|
||||
editedBy?: IEditedBy;
|
||||
reactions?: IReaction[];
|
||||
role?: string;
|
||||
|
@ -144,6 +135,8 @@ export interface IMessage extends IMessageFromServer {
|
|||
tshow?: boolean;
|
||||
comment?: string;
|
||||
subscription?: { id: string };
|
||||
user?: string;
|
||||
editedAt?: string | Date;
|
||||
}
|
||||
|
||||
export type TMessageModel = IMessage & Model;
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import rocketchat from '../lib/rocketchat';
|
||||
|
||||
export type TRocketChat = typeof rocketchat;
|
||||
|
||||
export interface IRocketChat extends TRocketChat {
|
||||
closeListener: any;
|
||||
usersListener: any;
|
||||
notifyAllListener: any;
|
||||
rolesListener: any;
|
||||
notifyLoggedListener: any;
|
||||
activeUsers: any;
|
||||
_setUserTimer: any;
|
||||
connectedListener: any;
|
||||
connectingListener: any;
|
||||
connectTimeout: any;
|
||||
sdk: any;
|
||||
activeUsersSubTimeout: any;
|
||||
roomsSub: any;
|
||||
}
|
|
@ -59,6 +59,20 @@ export interface IRoom {
|
|||
waitingResponse?: boolean;
|
||||
}
|
||||
|
||||
export interface IRoomSettings {
|
||||
roomName?: string;
|
||||
roomAvatar?: string;
|
||||
roomDescription?: string;
|
||||
roomTopic?: string;
|
||||
roomAnnouncement?: string;
|
||||
roomType?: SubscriptionType;
|
||||
readOnly?: boolean;
|
||||
reactWhenReadOnly?: boolean;
|
||||
systemMessages?: string[];
|
||||
joinCode?: string;
|
||||
encrypted?: boolean;
|
||||
}
|
||||
|
||||
export enum OmnichannelSourceType {
|
||||
WIDGET = 'widget',
|
||||
EMAIL = 'email',
|
||||
|
|
|
@ -22,7 +22,6 @@ export * from './IUser';
|
|||
export * from './IServer';
|
||||
export * from './ILoggedUser';
|
||||
export * from './IServerHistory';
|
||||
export * from './IRocketChat';
|
||||
export * from './ICertificate';
|
||||
export * from './IUrl';
|
||||
export * from './ICredentials';
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './useAppSelector';
|
|
@ -0,0 +1,5 @@
|
|||
import { TypedUseSelectorHook, useSelector } from 'react-redux';
|
||||
|
||||
import { IApplicationState } from '../../definitions';
|
||||
|
||||
export const useAppSelector: TypedUseSelectorHook<IApplicationState> = useSelector;
|
|
@ -6,20 +6,24 @@ import { store as reduxStore } from '../store/auxStore';
|
|||
import { setActiveUsers } from '../../actions/activeUsers';
|
||||
import { setUser } from '../../actions/login';
|
||||
import database from '../database';
|
||||
import { IRocketChat, IUser } from '../../definitions';
|
||||
import { IUser } from '../../definitions';
|
||||
import sdk from '../services/sdk';
|
||||
import { compareServerVersion } from './helpers/compareServerVersion';
|
||||
|
||||
export function subscribeUsersPresence(this: IRocketChat) {
|
||||
export const _activeUsersSubTimeout: { activeUsersSubTimeout: boolean | ReturnType<typeof setTimeout> } = {
|
||||
activeUsersSubTimeout: false
|
||||
};
|
||||
|
||||
export function subscribeUsersPresence() {
|
||||
const serverVersion = reduxStore.getState().server.version as string;
|
||||
|
||||
// if server is lower than 1.1.0
|
||||
if (compareServerVersion(serverVersion, 'lowerThan', '1.1.0')) {
|
||||
if (this.activeUsersSubTimeout) {
|
||||
clearTimeout(this.activeUsersSubTimeout);
|
||||
this.activeUsersSubTimeout = false;
|
||||
if (_activeUsersSubTimeout.activeUsersSubTimeout) {
|
||||
clearTimeout(_activeUsersSubTimeout.activeUsersSubTimeout as number);
|
||||
_activeUsersSubTimeout.activeUsersSubTimeout = false;
|
||||
}
|
||||
this.activeUsersSubTimeout = setTimeout(() => {
|
||||
_activeUsersSubTimeout.activeUsersSubTimeout = setTimeout(() => {
|
||||
sdk.subscribe('activeUsers');
|
||||
}, 5000);
|
||||
} else if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import { LISTENER } from '../../../containers/Toast';
|
||||
import EventEmitter from '../../../utils/events';
|
||||
|
||||
export const showToast = (message: string): void => EventEmitter.emit(LISTENER, { message });
|
|
@ -34,3 +34,4 @@ export * from './userPreferences';
|
|||
export * from './userPreferencesMethods';
|
||||
export * from './crashReport';
|
||||
export * from './parseSettings';
|
||||
export * from './subscribeRooms';
|
||||
|
|
|
@ -8,11 +8,13 @@ import { BASIC_AUTH_KEY } from '../../utils/fetch';
|
|||
import database, { getDatabase } from '../database';
|
||||
import { isSsl } from '../../utils/url';
|
||||
import log from '../../utils/log';
|
||||
import { ICertificate, IRocketChat } from '../../definitions';
|
||||
import { ICertificate } from '../../definitions';
|
||||
import sdk from '../services/sdk';
|
||||
import { CURRENT_SERVER, E2E_PRIVATE_KEY, E2E_PUBLIC_KEY, E2E_RANDOM_PASSWORD_KEY, TOKEN_KEY } from '../constants';
|
||||
import UserPreferences from './userPreferences';
|
||||
import { Services } from '../services';
|
||||
import { roomsSubscription } from './subscriptions/rooms';
|
||||
import { _activeUsersSubTimeout } from '.';
|
||||
|
||||
function removeServerKeys({ server, userId }: { server: string; userId?: string | null }) {
|
||||
UserPreferences.removeItem(`${TOKEN_KEY}-${server}`);
|
||||
|
@ -98,15 +100,14 @@ export async function removeServer({ server }: { server: string }): Promise<void
|
|||
}
|
||||
}
|
||||
|
||||
export async function logout(this: IRocketChat, { server }: { server: string }): Promise<void> {
|
||||
if (this.roomsSub) {
|
||||
this.roomsSub.stop();
|
||||
this.roomsSub = null;
|
||||
export async function logout({ server }: { server: string }): Promise<void> {
|
||||
if (roomsSubscription?.stop) {
|
||||
roomsSubscription.stop();
|
||||
}
|
||||
|
||||
if (this.activeUsersSubTimeout) {
|
||||
clearTimeout(this.activeUsersSubTimeout);
|
||||
this.activeUsersSubTimeout = false;
|
||||
if (_activeUsersSubTimeout.activeUsersSubTimeout) {
|
||||
clearTimeout(_activeUsersSubTimeout.activeUsersSubTimeout as number);
|
||||
_activeUsersSubTimeout.activeUsersSubTimeout = false;
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -2,16 +2,35 @@ import { InteractionManager } from 'react-native';
|
|||
|
||||
import { setActiveUsers } from '../../actions/activeUsers';
|
||||
import { setUser } from '../../actions/login';
|
||||
import { IUser } from '../../definitions';
|
||||
import { store as reduxStore } from '../store/auxStore';
|
||||
import { compareServerVersion } from './helpers/compareServerVersion';
|
||||
|
||||
// TODO
|
||||
export function _setUser(this: any, ddpMessage: { fields: any; id: any; cleared: any }) {
|
||||
this.activeUsers = this.activeUsers || {};
|
||||
export interface IActiveUsers {
|
||||
[key: string]: { status: string; statusText?: string } | string | boolean;
|
||||
msg: string;
|
||||
collection: string;
|
||||
id: string;
|
||||
cleared: boolean;
|
||||
fields: {
|
||||
emails: {
|
||||
address: string;
|
||||
verified: boolean;
|
||||
}[];
|
||||
username: string;
|
||||
status: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const _activeUsers = { activeUsers: {} as IActiveUsers };
|
||||
export const _setUserTimer: { setUserTimer: null | ReturnType<typeof setTimeout> } = { setUserTimer: null };
|
||||
|
||||
export function _setUser(ddpMessage: IActiveUsers): void {
|
||||
_activeUsers.activeUsers = _activeUsers.activeUsers || {};
|
||||
const { user } = reduxStore.getState().login;
|
||||
|
||||
if (ddpMessage.fields && user && user.id === ddpMessage.id) {
|
||||
reduxStore.dispatch(setUser(ddpMessage.fields));
|
||||
reduxStore.dispatch(setUser(ddpMessage.fields as Partial<IUser>));
|
||||
}
|
||||
|
||||
if (ddpMessage.cleared && user && user.id === ddpMessage.id) {
|
||||
|
@ -20,21 +39,22 @@ export function _setUser(this: any, ddpMessage: { fields: any; id: any; cleared:
|
|||
|
||||
const serverVersion = reduxStore.getState().server.version;
|
||||
if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) {
|
||||
if (!this._setUserTimer) {
|
||||
this._setUserTimer = setTimeout(() => {
|
||||
const activeUsersBatch = this.activeUsers;
|
||||
if (!_setUserTimer.setUserTimer) {
|
||||
_setUserTimer.setUserTimer = setTimeout(() => {
|
||||
const activeUsersBatch = _activeUsers.activeUsers;
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
// @ts-ignore
|
||||
reduxStore.dispatch(setActiveUsers(activeUsersBatch));
|
||||
});
|
||||
this._setUserTimer = null;
|
||||
return (this.activeUsers = {});
|
||||
_setUserTimer.setUserTimer = null;
|
||||
_activeUsers.activeUsers = {} as IActiveUsers;
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ddpMessage.fields) {
|
||||
this.activeUsers[ddpMessage.id] = { status: 'offline' };
|
||||
_activeUsers.activeUsers[ddpMessage.id] = { status: 'offline' };
|
||||
} else if (ddpMessage.fields.status) {
|
||||
this.activeUsers[ddpMessage.id] = { status: ddpMessage.fields.status };
|
||||
_activeUsers.activeUsers[ddpMessage.id] = { status: ddpMessage.fields.status };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
import log from '../../utils/log';
|
||||
import subscribeRoomsTmp from './subscriptions/rooms';
|
||||
import subscribeRoomsTmp, { roomsSubscription } from './subscriptions/rooms';
|
||||
|
||||
// TODO: remove this
|
||||
export async function subscribeRooms(this: any) {
|
||||
if (!this.roomsSub) {
|
||||
export async function subscribeRooms(): Promise<void> {
|
||||
if (!roomsSubscription?.stop) {
|
||||
try {
|
||||
// TODO: We need to change this naming. Maybe move this logic to the SDK?
|
||||
this.roomsSub = await subscribeRoomsTmp.call(this);
|
||||
await subscribeRoomsTmp();
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
export function unsubscribeRooms(this: any) {
|
||||
if (this.roomsSub) {
|
||||
this.roomsSub.stop();
|
||||
this.roomsSub = null;
|
||||
export function unsubscribeRooms(): void {
|
||||
if (roomsSubscription?.stop) {
|
||||
roomsSubscription.stop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ let queue: { [key: string]: ISubscription | IRoom } = {};
|
|||
let subTimer: number | null | false = null;
|
||||
const WINDOW_TIME = 500;
|
||||
|
||||
export let roomsSubscription: { stop: () => void } | null = null;
|
||||
|
||||
const createOrUpdateSubscription = async (subscription: ISubscription, room: IServerRoom | IRoom) => {
|
||||
try {
|
||||
const db = database.active;
|
||||
|
@ -404,6 +406,7 @@ export default function subscribeRooms() {
|
|||
clearTimeout(subTimer);
|
||||
subTimer = false;
|
||||
}
|
||||
roomsSubscription = null;
|
||||
};
|
||||
|
||||
streamListener = sdk.onStreamData('stream-notify-user', handleStreamMessageReceived);
|
||||
|
@ -412,10 +415,8 @@ export default function subscribeRooms() {
|
|||
// set the server that started this task
|
||||
subServer = sdk.current.client.host;
|
||||
sdk.current.subscribeNotifyUser().catch((e: unknown) => console.log(e));
|
||||
|
||||
return {
|
||||
stop: () => stop()
|
||||
};
|
||||
roomsSubscription = { stop: () => stop() };
|
||||
return null;
|
||||
} catch (e) {
|
||||
log(e);
|
||||
return Promise.reject();
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IApplicationState, TServerModel } from '../../definitions';
|
||||
import database from '../database';
|
||||
import { useAppSelector } from '../hooks';
|
||||
|
||||
export default function useServer() {
|
||||
const [server, setServer] = useState<TServerModel | null>(null);
|
||||
const shareServer = useSelector((state: IApplicationState) => state.share.server.server);
|
||||
const appServer = useSelector((state: IApplicationState) => state.server.server);
|
||||
const shareServer = useAppSelector((state: IApplicationState) => state.share.server.server);
|
||||
const appServer = useAppSelector((state: IApplicationState) => state.server.server);
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
|
|
|
@ -3,7 +3,7 @@ import EJSON from 'ejson';
|
|||
import { store } from '../store/auxStore';
|
||||
import { deepLinkingOpen } from '../../actions/deepLinking';
|
||||
import { isFDroidBuild } from '../constants';
|
||||
import PushNotification from './push';
|
||||
import { deviceToken, pushNotificationConfigure, setNotificationsBadgeCount } from './push';
|
||||
import { INotification, SubscriptionType } from '../../definitions';
|
||||
|
||||
interface IEjson {
|
||||
|
@ -47,11 +47,11 @@ export const onNotification = (push: INotification): void => {
|
|||
}
|
||||
};
|
||||
|
||||
export const getDeviceToken = (): string => PushNotification.getDeviceToken();
|
||||
export const setBadgeCount = (count?: number): void => PushNotification.setBadgeCount(count);
|
||||
export const getDeviceToken = (): string => deviceToken;
|
||||
export const setBadgeCount = (count?: number): void => setNotificationsBadgeCount(count);
|
||||
export const initializePushNotifications = (): Promise<INotification> | undefined => {
|
||||
if (!isFDroidBuild) {
|
||||
setBadgeCount();
|
||||
return PushNotification.configure(onNotification);
|
||||
return pushNotificationConfigure(onNotification);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -13,76 +13,62 @@ import { isIOS } from '../../utils/deviceInfo';
|
|||
import { store as reduxStore } from '../store/auxStore';
|
||||
import I18n from '../../i18n';
|
||||
|
||||
class PushNotification {
|
||||
onNotification: (notification: any) => void;
|
||||
deviceToken: string;
|
||||
constructor() {
|
||||
this.onNotification = () => {};
|
||||
this.deviceToken = '';
|
||||
if (isIOS) {
|
||||
// init
|
||||
Notifications.ios.registerRemoteNotifications();
|
||||
export let deviceToken = '';
|
||||
|
||||
// setCategories
|
||||
const notificationAction = new NotificationAction('REPLY_ACTION', 'background', I18n.t('Reply'), true, {
|
||||
buttonTitle: I18n.t('Reply'),
|
||||
placeholder: I18n.t('Type_message')
|
||||
});
|
||||
const notificationCategory = new NotificationCategory('MESSAGE', [notificationAction]);
|
||||
Notifications.setCategories([notificationCategory]);
|
||||
export const setNotificationsBadgeCount = (count = 0): void => {
|
||||
if (isIOS) {
|
||||
Notifications.ios.setBadgeCount(count);
|
||||
}
|
||||
};
|
||||
|
||||
export const pushNotificationConfigure = (onNotification: (notification: INotification) => void): Promise<any> => {
|
||||
if (isIOS) {
|
||||
// init
|
||||
Notifications.ios.registerRemoteNotifications();
|
||||
// setCategories
|
||||
const notificationAction = new NotificationAction('REPLY_ACTION', 'background', I18n.t('Reply'), true, {
|
||||
buttonTitle: I18n.t('Reply'),
|
||||
placeholder: I18n.t('Type_message')
|
||||
});
|
||||
const notificationCategory = new NotificationCategory('MESSAGE', [notificationAction]);
|
||||
Notifications.setCategories([notificationCategory]);
|
||||
} else {
|
||||
// init
|
||||
Notifications.android.registerRemoteNotifications();
|
||||
}
|
||||
|
||||
Notifications.events().registerRemoteNotificationsRegistered((event: Registered) => {
|
||||
deviceToken = event.deviceToken;
|
||||
});
|
||||
|
||||
Notifications.events().registerRemoteNotificationsRegistrationFailed((event: RegistrationError) => {
|
||||
// TODO: Handle error
|
||||
console.log(event);
|
||||
});
|
||||
|
||||
Notifications.events().registerNotificationReceivedForeground(
|
||||
(notification: Notification, completion: (response: NotificationCompletion) => void) => {
|
||||
completion({ alert: false, sound: false, badge: false });
|
||||
}
|
||||
);
|
||||
|
||||
Notifications.events().registerNotificationOpened((notification: Notification, completion: () => void) => {
|
||||
if (isIOS) {
|
||||
const { background } = reduxStore.getState().app;
|
||||
if (background) {
|
||||
onNotification(notification);
|
||||
}
|
||||
} else {
|
||||
// init
|
||||
Notifications.android.registerRemoteNotifications();
|
||||
onNotification(notification);
|
||||
}
|
||||
completion();
|
||||
});
|
||||
|
||||
Notifications.events().registerRemoteNotificationsRegistered((event: Registered) => {
|
||||
this.deviceToken = event.deviceToken;
|
||||
});
|
||||
|
||||
Notifications.events().registerRemoteNotificationsRegistrationFailed((event: RegistrationError) => {
|
||||
// TODO: Handle error
|
||||
console.log(event);
|
||||
});
|
||||
|
||||
Notifications.events().registerNotificationReceivedForeground(
|
||||
(notification: Notification, completion: (response: NotificationCompletion) => void) => {
|
||||
completion({ alert: false, sound: false, badge: false });
|
||||
}
|
||||
);
|
||||
|
||||
Notifications.events().registerNotificationOpened((notification: Notification, completion: () => void) => {
|
||||
if (isIOS) {
|
||||
const { background } = reduxStore.getState().app;
|
||||
if (background) {
|
||||
this.onNotification(notification);
|
||||
}
|
||||
} else {
|
||||
this.onNotification(notification);
|
||||
}
|
||||
completion();
|
||||
});
|
||||
|
||||
Notifications.events().registerNotificationReceivedBackground(
|
||||
(notification: Notification, completion: (response: any) => void) => {
|
||||
completion({ alert: true, sound: true, badge: false });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getDeviceToken() {
|
||||
return this.deviceToken;
|
||||
}
|
||||
|
||||
setBadgeCount = (count = 0) => {
|
||||
if (isIOS) {
|
||||
Notifications.ios.setBadgeCount(count);
|
||||
Notifications.events().registerNotificationReceivedBackground(
|
||||
(notification: Notification, completion: (response: any) => void) => {
|
||||
completion({ alert: true, sound: true, badge: false });
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
configure(onNotification: (notification: INotification) => void): Promise<any> {
|
||||
this.onNotification = onNotification;
|
||||
return Notifications.getInitialNotification();
|
||||
}
|
||||
}
|
||||
|
||||
export default new PushNotification();
|
||||
return Notifications.getInitialNotification();
|
||||
};
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { _setUser } from './methods/setUser';
|
||||
import { logout } from './methods/logout';
|
||||
import { subscribeRooms, unsubscribeRooms } from './methods/subscribeRooms';
|
||||
import { subscribeUsersPresence } from './methods/getUsersPresence';
|
||||
import { connect } from './services/connect';
|
||||
|
||||
const RocketChat = {
|
||||
logout,
|
||||
subscribeRooms,
|
||||
unsubscribeRooms,
|
||||
_setUser,
|
||||
subscribeUsersPresence,
|
||||
connect
|
||||
};
|
||||
|
||||
export default RocketChat;
|
|
@ -1,5 +1,5 @@
|
|||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||
import { settings as RocketChatSettings, Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { InteractionManager } from 'react-native';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
@ -14,8 +14,7 @@ import { store } from '../store/auxStore';
|
|||
import { loginRequest, setLoginServices, setUser } from '../../actions/login';
|
||||
import sdk from './sdk';
|
||||
import I18n from '../../i18n';
|
||||
import RocketChat from '../rocketchat';
|
||||
import { ICredentials, ILoggedUser, IRocketChat, STATUSES } from '../../definitions';
|
||||
import { ICredentials, ILoggedUser, STATUSES } from '../../definitions';
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import { connectRequest, connectSuccess, disconnect as disconnectAction } from '../../actions/connect';
|
||||
import { updatePermission } from '../../actions/permissions';
|
||||
|
@ -24,7 +23,8 @@ import { updateSettings } from '../../actions/settings';
|
|||
import { defaultSettings, MIN_ROCKETCHAT_VERSION } from '../constants';
|
||||
import { compareServerVersion } from '../methods/helpers/compareServerVersion';
|
||||
import { onRolesChanged } from '../methods/getRoles';
|
||||
import { getSettings } from '../methods';
|
||||
import { isSsl } from '../../utils/url';
|
||||
import { getSettings, IActiveUsers, unsubscribeRooms, _activeUsers, _setUser, _setUserTimer } from '../methods';
|
||||
|
||||
interface IServices {
|
||||
[index: string]: string | boolean;
|
||||
|
@ -35,11 +35,15 @@ interface IServices {
|
|||
service: string;
|
||||
}
|
||||
|
||||
// FIXME: Remove `this` context
|
||||
function connect(
|
||||
this: IRocketChat,
|
||||
{ server, logoutOnError = false }: { server: string; logoutOnError: boolean }
|
||||
): Promise<void> {
|
||||
let connectingListener: any;
|
||||
let connectedListener: any;
|
||||
let closeListener: any;
|
||||
let usersListener: any;
|
||||
let notifyAllListener: any;
|
||||
let rolesListener: any;
|
||||
let notifyLoggedListener: any;
|
||||
|
||||
function connect({ server, logoutOnError = false }: { server: string; logoutOnError: boolean }): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
if (sdk.current?.client?.host === server) {
|
||||
return resolve();
|
||||
|
@ -49,39 +53,40 @@ function connect(
|
|||
|
||||
store.dispatch(connectRequest());
|
||||
|
||||
if (this.connectTimeout) {
|
||||
clearTimeout(this.connectTimeout);
|
||||
// It's not called anywhere else
|
||||
// if (this.connectTimeout) {
|
||||
// clearTimeout(this.connectTimeout);
|
||||
// }
|
||||
|
||||
if (connectingListener) {
|
||||
connectingListener.then(stopListener);
|
||||
}
|
||||
|
||||
if (this.connectingListener) {
|
||||
this.connectingListener.then(stopListener);
|
||||
if (connectedListener) {
|
||||
connectedListener.then(stopListener);
|
||||
}
|
||||
|
||||
if (this.connectedListener) {
|
||||
this.connectedListener.then(stopListener);
|
||||
if (closeListener) {
|
||||
closeListener.then(stopListener);
|
||||
}
|
||||
|
||||
if (this.closeListener) {
|
||||
this.closeListener.then(stopListener);
|
||||
if (usersListener) {
|
||||
usersListener.then(stopListener);
|
||||
}
|
||||
|
||||
if (this.usersListener) {
|
||||
this.usersListener.then(stopListener);
|
||||
if (notifyAllListener) {
|
||||
notifyAllListener.then(stopListener);
|
||||
}
|
||||
|
||||
if (this.notifyAllListener) {
|
||||
this.notifyAllListener.then(stopListener);
|
||||
if (rolesListener) {
|
||||
rolesListener.then(stopListener);
|
||||
}
|
||||
|
||||
if (this.rolesListener) {
|
||||
this.rolesListener.then(stopListener);
|
||||
if (notifyLoggedListener) {
|
||||
notifyLoggedListener.then(stopListener);
|
||||
}
|
||||
|
||||
if (this.notifyLoggedListener) {
|
||||
this.notifyLoggedListener.then(stopListener);
|
||||
}
|
||||
|
||||
this.unsubscribeRooms();
|
||||
unsubscribeRooms();
|
||||
|
||||
EventEmitter.emit('INQUIRY_UNSUBSCRIBE');
|
||||
|
||||
|
@ -97,11 +102,11 @@ function connect(
|
|||
console.log('connect error', err);
|
||||
});
|
||||
|
||||
this.connectingListener = sdk.current.onStreamData('connecting', () => {
|
||||
connectingListener = sdk.current.onStreamData('connecting', () => {
|
||||
store.dispatch(connectRequest());
|
||||
});
|
||||
|
||||
this.connectedListener = sdk.current.onStreamData('connected', () => {
|
||||
connectedListener = sdk.current.onStreamData('connected', () => {
|
||||
const { connected } = store.getState().meteor;
|
||||
if (connected) {
|
||||
return;
|
||||
|
@ -113,16 +118,16 @@ function connect(
|
|||
}
|
||||
});
|
||||
|
||||
this.closeListener = sdk.current.onStreamData('close', () => {
|
||||
closeListener = sdk.current.onStreamData('close', () => {
|
||||
store.dispatch(disconnectAction());
|
||||
});
|
||||
|
||||
this.usersListener = sdk.current.onStreamData(
|
||||
usersListener = sdk.current.onStreamData(
|
||||
'users',
|
||||
protectedFunction((ddpMessage: any) => RocketChat._setUser(ddpMessage))
|
||||
protectedFunction((ddpMessage: any) => _setUser(ddpMessage))
|
||||
);
|
||||
|
||||
this.notifyAllListener = sdk.current.onStreamData(
|
||||
notifyAllListener = sdk.current.onStreamData(
|
||||
'stream-notify-all',
|
||||
protectedFunction(async (ddpMessage: { fields: { args?: any; eventName: string } }) => {
|
||||
const { eventName } = ddpMessage.fields;
|
||||
|
@ -150,7 +155,7 @@ function connect(
|
|||
})
|
||||
);
|
||||
|
||||
this.rolesListener = sdk.current.onStreamData(
|
||||
rolesListener = sdk.current.onStreamData(
|
||||
'stream-roles',
|
||||
protectedFunction((ddpMessage: any) => onRolesChanged(ddpMessage))
|
||||
);
|
||||
|
@ -171,27 +176,29 @@ function connect(
|
|||
}
|
||||
});
|
||||
|
||||
this.notifyLoggedListener = sdk.current.onStreamData(
|
||||
notifyLoggedListener = sdk.current.onStreamData(
|
||||
'stream-notify-logged',
|
||||
protectedFunction(async (ddpMessage: { fields: { args?: any; eventName?: any } }) => {
|
||||
const { eventName } = ddpMessage.fields;
|
||||
|
||||
// `user-status` event is deprecated after RC 4.1 in favor of `stream-user-presence/${uid}`
|
||||
if (/user-status/.test(eventName)) {
|
||||
this.activeUsers = this.activeUsers || {};
|
||||
if (!this._setUserTimer) {
|
||||
this._setUserTimer = setTimeout(() => {
|
||||
const activeUsersBatch = this.activeUsers;
|
||||
_activeUsers.activeUsers = _activeUsers.activeUsers || {};
|
||||
if (!_setUserTimer.setUserTimer) {
|
||||
_setUserTimer.setUserTimer = setTimeout(() => {
|
||||
const activeUsersBatch = _activeUsers.activeUsers;
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
// @ts-ignore
|
||||
store.dispatch(setActiveUsers(activeUsersBatch));
|
||||
});
|
||||
this._setUserTimer = null;
|
||||
return (this.activeUsers = {});
|
||||
_setUserTimer.setUserTimer = null;
|
||||
_activeUsers.activeUsers = {} as IActiveUsers;
|
||||
return null;
|
||||
}, 10000);
|
||||
}
|
||||
const userStatus = ddpMessage.fields.args[0];
|
||||
const [id, , status, statusText] = userStatus;
|
||||
this.activeUsers[id] = { status: STATUSES[status], statusText };
|
||||
_activeUsers.activeUsers[id] = { status: STATUSES[status], statusText };
|
||||
|
||||
const { user: loggedUser } = store.getState().login;
|
||||
if (loggedUser && loggedUser.id === id) {
|
||||
|
@ -419,10 +426,10 @@ async function getServerInfo(server: string) {
|
|||
}
|
||||
|
||||
async function getWebsocketInfo({ server }: { server: string }) {
|
||||
sdk.initialize(server);
|
||||
const websocketSdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: isSsl(server) });
|
||||
|
||||
try {
|
||||
await sdk.current.connect();
|
||||
await websocketSdk.connect();
|
||||
} catch (err: any) {
|
||||
if (err.message && err.message.includes('400')) {
|
||||
return {
|
||||
|
@ -432,7 +439,7 @@ async function getWebsocketInfo({ server }: { server: string }) {
|
|||
}
|
||||
}
|
||||
|
||||
sdk.disconnect();
|
||||
websocketSdk.disconnect();
|
||||
|
||||
return {
|
||||
success: true
|
||||
|
|
|
@ -16,10 +16,9 @@ import { TParams } from '../../definitions/ILivechatEditView';
|
|||
import { store as reduxStore } from '../store/auxStore';
|
||||
import { getDeviceToken } from '../notifications';
|
||||
import { getBundleId, isIOS } from '../../utils/deviceInfo';
|
||||
import { RoomTypes, roomTypeToApiType } from '../methods';
|
||||
import { RoomTypes, roomTypeToApiType, unsubscribeRooms } from '../methods';
|
||||
import sdk from './sdk';
|
||||
import { compareServerVersion } from '../methods/helpers/compareServerVersion';
|
||||
import RocketChat from '../rocketchat';
|
||||
|
||||
export const createChannel = ({
|
||||
name,
|
||||
|
@ -254,7 +253,7 @@ export const markAsUnread = ({ messageId }: { messageId: string }) =>
|
|||
// RC 0.65.0
|
||||
sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
|
||||
|
||||
export const toggleStarMessage = (messageId: string, starred: boolean) => {
|
||||
export const toggleStarMessage = (messageId: string, starred?: boolean) => {
|
||||
if (starred) {
|
||||
// RC 0.59.0
|
||||
return sdk.post('chat.unStarMessage', { messageId });
|
||||
|
@ -263,7 +262,7 @@ export const toggleStarMessage = (messageId: string, starred: boolean) => {
|
|||
return sdk.post('chat.starMessage', { messageId });
|
||||
};
|
||||
|
||||
export const togglePinMessage = (messageId: string, pinned: boolean) => {
|
||||
export const togglePinMessage = (messageId: string, pinned?: boolean) => {
|
||||
if (pinned) {
|
||||
// RC 0.59.0
|
||||
return sdk.post('chat.unPinMessage', { messageId });
|
||||
|
@ -807,7 +806,7 @@ export const emitTyping = (room: IRoom, typing = true) => {
|
|||
|
||||
export function e2eResetOwnKey(): Promise<boolean | {}> {
|
||||
// {} when TOTP is enabled
|
||||
RocketChat.unsubscribeRooms();
|
||||
unsubscribeRooms();
|
||||
|
||||
// RC 0.72.0
|
||||
return sdk.methodCallWrapper('e2e.resetOwnE2EKey');
|
||||
|
|
|
@ -7,7 +7,6 @@ import { appStart } from '../actions/app';
|
|||
import { selectServerRequest, serverFinishAdd } from '../actions/server';
|
||||
import { loginFailure, loginSuccess, logout as logoutAction, setUser } from '../actions/login';
|
||||
import { roomsRequest } from '../actions/rooms';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import log, { events, logEvent } from '../utils/log';
|
||||
import I18n, { setLanguage } from '../i18n';
|
||||
import database from '../lib/database';
|
||||
|
@ -30,14 +29,16 @@ import {
|
|||
getSlashCommands,
|
||||
getUserPresence,
|
||||
isOmnichannelModuleAvailable,
|
||||
subscribeSettings
|
||||
logout,
|
||||
subscribeSettings,
|
||||
subscribeUsersPresence
|
||||
} from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
|
||||
const getServer = state => state.server.server;
|
||||
const loginWithPasswordCall = args => Services.loginWithPassword(args);
|
||||
const loginCall = (credentials, isFromWebView) => Services.login(credentials, isFromWebView);
|
||||
const logoutCall = args => RocketChat.logout(args);
|
||||
const logoutCall = args => logout(args);
|
||||
|
||||
const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnError = false, isFromWebView = false }) {
|
||||
logEvent(events.LOGIN_DEFAULT_LOGIN);
|
||||
|
@ -114,7 +115,7 @@ const registerPushTokenFork = function* registerPushTokenFork() {
|
|||
};
|
||||
|
||||
const fetchUsersPresenceFork = function* fetchUsersPresenceFork() {
|
||||
RocketChat.subscribeUsersPresence();
|
||||
subscribeUsersPresence();
|
||||
};
|
||||
|
||||
const fetchEnterpriseModulesFork = function* fetchEnterpriseModulesFork({ user }) {
|
||||
|
|
|
@ -7,9 +7,8 @@ import { roomsFailure, roomsRefresh, roomsSuccess } from '../actions/rooms';
|
|||
import database from '../lib/database';
|
||||
import log from '../utils/log';
|
||||
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import buildMessage from '../lib/methods/helpers/buildMessage';
|
||||
import { getRooms } from '../lib/methods';
|
||||
import { getRooms, subscribeRooms } from '../lib/methods';
|
||||
|
||||
const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
|
||||
const serversDB = database.servers;
|
||||
|
@ -30,7 +29,7 @@ const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
|
|||
const handleRoomsRequest = function* handleRoomsRequest({ params }) {
|
||||
try {
|
||||
const serversDB = database.servers;
|
||||
RocketChat.subscribeRooms();
|
||||
subscribeRooms();
|
||||
const newRoomsUpdatedAt = new Date();
|
||||
let roomsUpdatedAt;
|
||||
const server = yield select(state => state.server.server);
|
||||
|
|
|
@ -11,7 +11,6 @@ import { selectServerFailure, selectServerRequest, selectServerSuccess, serverFa
|
|||
import { clearSettings } from '../actions/settings';
|
||||
import { clearUser, setUser } from '../actions/login';
|
||||
import { clearActiveUsers } from '../actions/activeUsers';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import database from '../lib/database';
|
||||
import log, { logServerVersion } from '../utils/log';
|
||||
import I18n from '../i18n';
|
||||
|
@ -25,6 +24,7 @@ import { RootEnum } from '../definitions';
|
|||
import { CERTIFICATE_KEY, CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
|
||||
import { getLoginSettings, setCustomEmojis, setEnterpriseModules, setPermissions, setRoles, setSettings } from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
import { connect } from '../lib/services/connect';
|
||||
|
||||
const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||
try {
|
||||
|
@ -115,11 +115,11 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
|||
if (user) {
|
||||
yield put(clearSettings());
|
||||
yield put(setUser(user));
|
||||
yield RocketChat.connect({ server, logoutOnError: true });
|
||||
yield connect({ server, logoutOnError: true });
|
||||
yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
|
||||
} else {
|
||||
yield put(clearUser());
|
||||
yield RocketChat.connect({ server });
|
||||
yield connect({ server });
|
||||
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { select, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
import Push from '../lib/notifications/push';
|
||||
import log from '../utils/log';
|
||||
import { localAuthenticate, saveLastLocalAuthenticationSession } from '../utils/localAuthentication';
|
||||
import { APP_STATE } from '../actions/actionsTypes';
|
||||
import { RootEnum } from '../definitions';
|
||||
import { Services } from '../lib/services';
|
||||
import { setBadgeCount } from '../lib/notifications';
|
||||
|
||||
const appHasComeBackToForeground = function* appHasComeBackToForeground() {
|
||||
const appRoot = yield select(state => state.app.root);
|
||||
|
@ -20,7 +20,7 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() {
|
|||
try {
|
||||
yield localAuthenticate(server.server);
|
||||
Services.checkAndReopen();
|
||||
Push.setBadgeCount();
|
||||
setBadgeCount();
|
||||
return yield Services.setUserPresenceOnline();
|
||||
} catch (e) {
|
||||
log(e);
|
||||
|
|
|
@ -101,7 +101,7 @@ const ChatsStackNavigator = () => {
|
|||
options={SearchMessagesView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen name='SelectedUsersView' component={SelectedUsersView} />
|
||||
<ChatsStack.Screen name='InviteUsersView' component={InviteUsersView} options={InviteUsersView.navigationOptions} />
|
||||
<ChatsStack.Screen name='InviteUsersView' component={InviteUsersView} />
|
||||
<ChatsStack.Screen
|
||||
name='InviteUsersEditView'
|
||||
component={InviteUsersEditView}
|
||||
|
@ -131,7 +131,7 @@ const ChatsStackNavigator = () => {
|
|||
component={AddExistingChannelView}
|
||||
options={AddExistingChannelView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen name='MarkdownTableView' component={MarkdownTableView} options={MarkdownTableView.navigationOptions} />
|
||||
<ChatsStack.Screen name='MarkdownTableView' component={MarkdownTableView} />
|
||||
<ChatsStack.Screen name='ReadReceiptsView' component={ReadReceiptsView} options={ReadReceiptsView.navigationOptions} />
|
||||
<ChatsStack.Screen name='QueueListView' component={QueueListView} options={QueueListView.navigationOptions} />
|
||||
<ChatsStack.Screen name='CannedResponsesListView' component={CannedResponsesListView} />
|
||||
|
@ -198,7 +198,7 @@ const AdminPanelStackNavigator = () => {
|
|||
return (
|
||||
<AdminPanelStack.Navigator
|
||||
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions}>
|
||||
<AdminPanelStack.Screen name='AdminPanelView' component={AdminPanelView} options={AdminPanelView.navigationOptions} />
|
||||
<AdminPanelStack.Screen name='AdminPanelView' component={AdminPanelView} />
|
||||
</AdminPanelStack.Navigator>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -129,7 +129,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
|||
options={SearchMessagesView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen name='SelectedUsersView' component={SelectedUsersView} />
|
||||
<ModalStack.Screen name='InviteUsersView' component={InviteUsersView} options={InviteUsersView.navigationOptions} />
|
||||
<ModalStack.Screen name='InviteUsersView' component={InviteUsersView} />
|
||||
<ModalStack.Screen name='AddChannelTeamView' component={AddChannelTeamView} />
|
||||
<ModalStack.Screen
|
||||
name='AddExistingChannelView'
|
||||
|
@ -166,7 +166,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
|||
<ModalStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} />
|
||||
<ModalStack.Screen name='DiscussionsView' component={DiscussionsView} />
|
||||
<ModalStack.Screen name='TeamChannelsView' component={TeamChannelsView} options={TeamChannelsView.navigationOptions} />
|
||||
<ModalStack.Screen name='MarkdownTableView' component={MarkdownTableView} options={MarkdownTableView.navigationOptions} />
|
||||
<ModalStack.Screen name='MarkdownTableView' component={MarkdownTableView} />
|
||||
<ModalStack.Screen
|
||||
name='ReadReceiptsView'
|
||||
component={ReadReceiptsView}
|
||||
|
@ -196,11 +196,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
|||
options={props => ProfileView.navigationOptions!({ ...props, isMasterDetail: true })}
|
||||
/>
|
||||
<ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} />
|
||||
<ModalStack.Screen
|
||||
name='AdminPanelView'
|
||||
component={AdminPanelView}
|
||||
options={props => AdminPanelView.navigationOptions!({ ...props, isMasterDetail: true })}
|
||||
/>
|
||||
<ModalStack.Screen name='AdminPanelView' component={AdminPanelView} />
|
||||
<ModalStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} />
|
||||
<ModalStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} />
|
||||
<ModalStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} />
|
||||
|
|
|
@ -28,7 +28,7 @@ const _OutsideStack = () => {
|
|||
<Outside.Screen name='ForgotPasswordView' component={ForgotPasswordView} options={ForgotPasswordView.navigationOptions} />
|
||||
<Outside.Screen name='SendEmailConfirmationView' component={SendEmailConfirmationView} />
|
||||
<Outside.Screen name='RegisterView' component={RegisterView} options={RegisterView.navigationOptions} />
|
||||
<Outside.Screen name='LegalView' component={LegalView} options={LegalView.navigationOptions} />
|
||||
<Outside.Screen name='LegalView' component={LegalView} />
|
||||
</Outside.Navigator>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ import { ISubscription, SubscriptionType, TSubscriptionModel } from '../definiti
|
|||
import { ICannedResponse } from '../definitions/ICannedResponse';
|
||||
import { TDataSelect } from '../definitions/IDataSelect';
|
||||
import { ModalStackParamList } from './MasterDetailStack/types';
|
||||
import { TThreadModel } from '../definitions';
|
||||
|
||||
export type ChatsStackParamList = {
|
||||
ModalStackNavigator: NavigatorScreenParams<ModalStackParamList>;
|
||||
|
@ -246,7 +247,7 @@ export type InsideStackParamList = {
|
|||
serverInfo: IServer;
|
||||
text: string;
|
||||
room: TSubscriptionModel;
|
||||
thread: any; // TODO: Change
|
||||
thread: TThreadModel;
|
||||
};
|
||||
ModalBlockView: {
|
||||
data: any; // TODO: Change;
|
||||
|
|
|
@ -2,8 +2,6 @@ import { Platform } from 'react-native';
|
|||
import DeviceInfo from 'react-native-device-info';
|
||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
||||
export type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put';
|
||||
|
||||
interface CustomHeaders {
|
||||
|
@ -46,13 +44,11 @@ export default (url: string, options: IOptions = {}): Promise<Response> => {
|
|||
if (options && options.headers) {
|
||||
customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } };
|
||||
}
|
||||
// TODO: Refactor when migrate rocketchat.js
|
||||
// @ts-ignore
|
||||
// WHAT?
|
||||
if (RocketChat.controller) {
|
||||
// @ts-ignore
|
||||
const { signal } = RocketChat.controller;
|
||||
customOptions = { ...customOptions, signal };
|
||||
}
|
||||
// TODO: Check if this really works and if anyone else has complained about this problem.
|
||||
// if (RocketChat.controller) {
|
||||
// // @ts-ignore
|
||||
// const { signal } = RocketChat.controller;
|
||||
// customOptions = { ...customOptions, signal };
|
||||
// }
|
||||
return fetch(url, customOptions);
|
||||
};
|
||||
|
|
|
@ -142,13 +142,13 @@ export default {
|
|||
SE_CONTACT_US: 'se_contact_us',
|
||||
SE_CONTACT_US_F: 'se_contact_us_f',
|
||||
SE_GO_LANGUAGE: 'se_go_language',
|
||||
SE_REVIEW_THIS_APP: 'se_review_this_app',
|
||||
SE_REVIEW_THIS_APP_F: 'se_review_this_app_f',
|
||||
SE_SHARE_THIS_APP: 'se_share_this_app',
|
||||
SE_GO_DEFAULTBROWSER: 'se_go_default_browser',
|
||||
SE_GO_THEME: 'se_go_theme',
|
||||
SE_GO_PROFILE: 'se_go_profile',
|
||||
SE_GO_SECURITYPRIVACY: 'se_go_securityprivacy',
|
||||
SE_REVIEW_THIS_APP: 'se_review_this_app',
|
||||
SE_REVIEW_THIS_APP_F: 'se_review_this_app_f',
|
||||
SE_SHARE_THIS_APP: 'se_share_this_app',
|
||||
SE_READ_LICENSE: 'se_read_license',
|
||||
SE_COPY_APP_VERSION: 'se_copy_app_version',
|
||||
SE_COPY_SERVER_VERSION: 'se_copy_server_version',
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import { IAttachment } from '../views/ShareView/interfaces';
|
||||
import { IShareAttachment } from '../definitions';
|
||||
|
||||
export const canUploadFile = (
|
||||
file: IAttachment,
|
||||
allowList: string,
|
||||
maxFileSize: number,
|
||||
permissionToUploadFile: boolean
|
||||
): { success: boolean; error?: string } => {
|
||||
export const canUploadFile = ({
|
||||
file,
|
||||
allowList,
|
||||
maxFileSize,
|
||||
permissionToUploadFile
|
||||
}: {
|
||||
file: IShareAttachment;
|
||||
allowList?: string;
|
||||
maxFileSize?: number;
|
||||
permissionToUploadFile: boolean;
|
||||
}): { success: boolean; error?: string } => {
|
||||
if (!(file && file.path)) {
|
||||
return { success: true };
|
||||
}
|
||||
if (maxFileSize > -1 && file.size > maxFileSize) {
|
||||
if (maxFileSize && maxFileSize > -1 && file.size > maxFileSize) {
|
||||
return { success: false, error: 'error-file-too-large' };
|
||||
}
|
||||
if (!permissionToUploadFile) {
|
||||
|
|
|
@ -29,8 +29,7 @@ const RCSSLPinning = Platform.select({
|
|||
pickCertificate: () =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
// @ts-ignore
|
||||
const res = await DocumentPicker.pickSingle({
|
||||
type: ['com.rsa.pkcs-12']
|
||||
});
|
||||
const { uri, name } = res;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { connect } from 'react-redux';
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CompositeNavigationProp } from '@react-navigation/core';
|
||||
|
||||
import * as List from '../containers/List';
|
||||
|
@ -10,15 +10,14 @@ import * as HeaderButton from '../containers/HeaderButton';
|
|||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import I18n from '../i18n';
|
||||
import { ChatsStackParamList, DrawerParamList, NewMessageStackParamList } from '../stacks/types';
|
||||
import { IApplicationState } from '../definitions';
|
||||
|
||||
interface IAddChannelTeamView {
|
||||
navigation: CompositeNavigationProp<
|
||||
StackNavigationProp<ChatsStackParamList, 'AddChannelTeamView'>,
|
||||
CompositeNavigationProp<StackNavigationProp<NewMessageStackParamList>, StackNavigationProp<DrawerParamList>>
|
||||
>;
|
||||
route: RouteProp<ChatsStackParamList, 'AddChannelTeamView'>;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
type TRoute = RouteProp<ChatsStackParamList, 'AddChannelTeamView'>;
|
||||
|
||||
type TNavigation = CompositeNavigationProp<
|
||||
StackNavigationProp<ChatsStackParamList, 'AddChannelTeamView'>,
|
||||
CompositeNavigationProp<StackNavigationProp<NewMessageStackParamList>, StackNavigationProp<DrawerParamList>>
|
||||
>;
|
||||
|
||||
const setHeader = ({
|
||||
navigation,
|
||||
|
@ -38,12 +37,14 @@ const setHeader = ({
|
|||
navigation.setOptions(options);
|
||||
};
|
||||
|
||||
const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTeamView) => {
|
||||
const { teamId, teamChannels } = route.params;
|
||||
const AddChannelTeamView = () => {
|
||||
const navigation = useNavigation<TNavigation>();
|
||||
const isMasterDetail = useSelector((state: IApplicationState) => state.app.isMasterDetail);
|
||||
const { teamChannels, teamId } = useRoute<TRoute>().params;
|
||||
|
||||
useEffect(() => {
|
||||
setHeader({ navigation, isMasterDetail });
|
||||
}, []);
|
||||
}, [isMasterDetail, navigation]);
|
||||
|
||||
return (
|
||||
<SafeAreaView testID='add-channel-team-view'>
|
||||
|
@ -80,8 +81,4 @@ const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTe
|
|||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: any) => ({
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(AddChannelTeamView);
|
||||
export default AddChannelTeamView;
|
||||
|
|
|
@ -21,7 +21,7 @@ import { goRoom } from '../utils/goRoom';
|
|||
import { showErrorAlert } from '../utils/info';
|
||||
import debounce from '../utils/debounce';
|
||||
import { ChatsStackParamList } from '../stacks/types';
|
||||
import { TSubscriptionModel, SubscriptionType } from '../definitions';
|
||||
import { TSubscriptionModel, SubscriptionType, IApplicationState } from '../definitions';
|
||||
import { getRoomTitle, hasPermission } from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
|
||||
|
@ -37,7 +37,7 @@ interface IAddExistingChannelViewProps {
|
|||
route: RouteProp<ChatsStackParamList, 'AddExistingChannelView'>;
|
||||
theme: TSupportedThemes;
|
||||
isMasterDetail: boolean;
|
||||
addTeamChannelPermission: string[];
|
||||
addTeamChannelPermission?: string[];
|
||||
}
|
||||
|
||||
const QUERY_SIZE = 50;
|
||||
|
@ -222,7 +222,7 @@ class AddExistingChannelView extends React.Component<IAddExistingChannelViewProp
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => ({
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
addTeamChannelPermission: state.permissions['add-team-channel']
|
||||
});
|
||||
|
|
|
@ -1,55 +1,45 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import { connect } from 'react-redux';
|
||||
import { DrawerScreenProps } from '@react-navigation/drawer';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import { withTheme } from '../../theme';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import { AdminPanelStackParamList } from '../../stacks/types';
|
||||
import { IApplicationState } from '../../definitions';
|
||||
|
||||
interface IAdminPanelViewProps {
|
||||
baseUrl: string;
|
||||
token: string;
|
||||
}
|
||||
const AdminPanelView = () => {
|
||||
const navigation = useNavigation<StackNavigationProp<AdminPanelStackParamList, 'AdminPanelView'>>();
|
||||
const baseUrl = useSelector((state: IApplicationState) => state.server.server);
|
||||
const token = useSelector((state: IApplicationState) => getUserSelector(state).token);
|
||||
const isMasterDetail = useSelector((state: IApplicationState) => state.app.isMasterDetail);
|
||||
|
||||
interface INavigationOptions {
|
||||
navigation: DrawerScreenProps<AdminPanelStackParamList, 'AdminPanelView'>;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: isMasterDetail ? undefined : () => <HeaderButton.Drawer navigation={navigation} />,
|
||||
title: I18n.t('Admin_Panel')
|
||||
});
|
||||
}, [isMasterDetail, navigation]);
|
||||
|
||||
class AdminPanelView extends React.Component<IAdminPanelViewProps, any> {
|
||||
static navigationOptions = ({ navigation, isMasterDetail }: INavigationOptions): StackNavigationOptions => ({
|
||||
headerLeft: isMasterDetail ? undefined : () => <HeaderButton.Drawer navigation={navigation} />,
|
||||
title: I18n.t('Admin_Panel')
|
||||
});
|
||||
|
||||
render() {
|
||||
const { baseUrl, token } = this.props;
|
||||
if (!baseUrl) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<StatusBar />
|
||||
<WebView
|
||||
// https://github.com/react-native-community/react-native-webview/issues/1311
|
||||
onMessage={() => {}}
|
||||
source={{ uri: `${baseUrl}/admin/info?layout=embedded` }}
|
||||
injectedJavaScript={`Meteor.loginWithToken('${token}', function() { })`}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
if (!baseUrl) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => ({
|
||||
baseUrl: state.server.server,
|
||||
token: getUserSelector(state).token
|
||||
});
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<StatusBar />
|
||||
<WebView
|
||||
// https://github.com/react-native-community/react-native-webview/issues/1311
|
||||
onMessage={() => {}}
|
||||
source={{ uri: `${baseUrl}/admin/info?layout=embedded` }}
|
||||
injectedJavaScript={`Meteor.loginWithToken('${token}', function() { })`}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(AdminPanelView));
|
||||
export default AdminPanelView;
|
||||
|
|
|
@ -26,6 +26,7 @@ import StatusBar from '../containers/StatusBar';
|
|||
import { InsideStackParamList } from '../stacks/types';
|
||||
import { IAttachment } from '../definitions/IAttachment';
|
||||
import { formatAttachmentUrl } from '../lib/methods/helpers/formatAttachmentUrl';
|
||||
import { IApplicationState, IUser } from '../definitions';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -46,10 +47,7 @@ interface IAttachmentViewProps {
|
|||
width: number;
|
||||
height: number;
|
||||
insets: { left: number; bottom: number; right: number; top: number };
|
||||
user: {
|
||||
id: string;
|
||||
token: string;
|
||||
};
|
||||
user: IUser;
|
||||
Allow_Save_Media_to_Gallery: boolean;
|
||||
}
|
||||
|
||||
|
@ -200,10 +198,10 @@ class AttachmentView extends React.Component<IAttachmentViewProps, IAttachmentVi
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => ({
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
baseUrl: state.server.server,
|
||||
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 as boolean) ?? true
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(withDimensions(withSafeAreaInsets(AttachmentView))));
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import React from 'react';
|
||||
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IApplicationState } from '../definitions';
|
||||
import I18n from '../i18n';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { useTheme } from '../theme';
|
||||
import sharedStyles from './Styles';
|
||||
import { useAppSelector } from '../lib/hooks';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -23,7 +22,7 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
const AuthLoadingView = React.memo((): React.ReactElement => {
|
||||
const text = useSelector((state: IApplicationState) => state.app.text);
|
||||
const text = useAppSelector(state => state.app.text);
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: colors.backgroundColor }]}>
|
||||
|
|
|
@ -14,6 +14,7 @@ import { TSupportedThemes, withTheme } from '../theme';
|
|||
import debounce from '../utils/debounce';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import { Services } from '../lib/services';
|
||||
import { IApplicationState, ICredentials } from '../definitions';
|
||||
|
||||
const userAgent = isIOS
|
||||
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'
|
||||
|
@ -93,7 +94,7 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
|||
navigation.pop();
|
||||
};
|
||||
|
||||
login = (params: any) => {
|
||||
login = (params: ICredentials) => {
|
||||
const { logging } = this.state;
|
||||
if (logging) {
|
||||
return;
|
||||
|
@ -111,7 +112,7 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
|||
};
|
||||
|
||||
// Force 3s delay so the server has time to evaluate the token
|
||||
debouncedLogin = debounce((params: any) => this.login(params), 3000);
|
||||
debouncedLogin = debounce((params: ICredentials) => this.login(params), 3000);
|
||||
|
||||
tryLogin = debounce(
|
||||
async () => {
|
||||
|
@ -135,7 +136,7 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
|||
const parsedUrl = parse(url, true);
|
||||
// ticket -> cas / validate & saml_idp_credentialToken -> saml
|
||||
if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) {
|
||||
let payload;
|
||||
let payload: ICredentials;
|
||||
if (authType === 'saml') {
|
||||
const token = parsedUrl.query?.saml_idp_credentialToken || ssoToken;
|
||||
const credentialToken = { credentialToken: token };
|
||||
|
@ -202,10 +203,10 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => ({
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
server: state.server.server,
|
||||
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url,
|
||||
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method
|
||||
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url as string,
|
||||
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method as string
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(AuthenticationWebView));
|
||||
|
|
|
@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
|
|||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { StyleSheet, Text, View, ScrollView } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
|
@ -17,6 +16,7 @@ import { ICannedResponse } from '../definitions/ICannedResponse';
|
|||
import { ChatsStackParamList } from '../stacks/types';
|
||||
import sharedStyles from './Styles';
|
||||
import { getRoomTitle, getUidDirectMessage } from '../lib/methods';
|
||||
import { useAppSelector } from '../lib/hooks';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scroll: {
|
||||
|
@ -96,8 +96,8 @@ interface ICannedResponseDetailProps {
|
|||
const CannedResponseDetail = ({ navigation, route }: ICannedResponseDetailProps): JSX.Element => {
|
||||
const { cannedResponse } = route?.params;
|
||||
const { theme } = useTheme();
|
||||
const { isMasterDetail } = useSelector((state: any) => state.app);
|
||||
const { rooms } = useSelector((state: any) => state.room);
|
||||
const { isMasterDetail } = useAppSelector(state => state.app);
|
||||
const { rooms } = useAppSelector(state => state.room);
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
|
@ -159,13 +159,7 @@ const CannedResponseDetail = ({ navigation, route }: ICannedResponseDetailProps)
|
|||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<Button
|
||||
title={I18n.t('Use')}
|
||||
theme={theme}
|
||||
style={styles.button}
|
||||
type='primary'
|
||||
onPress={() => navigateToRoom(cannedResponse)}
|
||||
/>
|
||||
<Button title={I18n.t('Use')} style={styles.button} type='primary' onPress={() => navigateToRoom(cannedResponse)} />
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
|
|
@ -40,7 +40,6 @@ const CannedResponseItem = ({
|
|||
fontSize={12}
|
||||
color={themes[theme].titleText}
|
||||
style={[styles.cannedUseButton, { backgroundColor: themes[theme].chatComponentBackground }]}
|
||||
theme={theme}
|
||||
onPress={onPressUse}
|
||||
/>
|
||||
</View>
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,5 @@
|
|||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
|
@ -30,8 +29,8 @@ import { ChatsStackParamList } from '../../stacks/types';
|
|||
import { ISubscription } from '../../definitions/ISubscription';
|
||||
import { getRoomTitle, getUidDirectMessage } from '../../lib/methods';
|
||||
import { Services } from '../../lib/services';
|
||||
import { IApplicationState } from '../../definitions';
|
||||
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
const COUNT = 25;
|
||||
|
||||
|
@ -76,8 +75,8 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
|
|||
|
||||
const insets = useSafeAreaInsets();
|
||||
const { theme } = useTheme();
|
||||
const isMasterDetail = useSelector((state: IApplicationState) => state.app.isMasterDetail);
|
||||
const rooms = useSelector((state: IApplicationState) => state.room.rooms);
|
||||
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
|
||||
const rooms = useAppSelector(state => state.room.rooms);
|
||||
|
||||
const getRoomFromDb = async () => {
|
||||
const { rid } = route.params;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { FlatList, ScrollView, StyleSheet, Switch, Text, View, SwitchProps } fro
|
|||
import { dequal } from 'dequal';
|
||||
|
||||
import * as List from '../containers/List';
|
||||
import TextInput from '../presentation/TextInput';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import Loading from '../containers/Loading';
|
||||
import { createChannelRequest } from '../actions/createChannel';
|
||||
import { removeUser } from '../actions/selectedUsers';
|
||||
|
|
|
@ -11,7 +11,7 @@ import * as HeaderButton from '../../containers/HeaderButton';
|
|||
import StatusBar from '../../containers/StatusBar';
|
||||
import { withTheme } from '../../theme';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import TextInput from '../../containers/TextInput';
|
||||
import FormTextInput from '../../containers/TextInput/FormTextInput';
|
||||
import Navigation from '../../lib/navigation/appNavigation';
|
||||
import { createDiscussionRequest, ICreateDiscussionRequestData } from '../../actions/createDiscussion';
|
||||
import { showErrorAlert } from '../../utils/info';
|
||||
|
@ -167,7 +167,7 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
|
|||
serverVersion={serverVersion}
|
||||
theme={theme}
|
||||
/>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={I18n.t('Discussion_name')}
|
||||
testID='multi-select-discussion-name'
|
||||
placeholder={I18n.t('A_meaningful_name_for_the_discussion_room')}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { FlatList, StyleSheet } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
|
||||
import { IApplicationState, IMessageFromServer } from '../../definitions';
|
||||
import { IMessageFromServer } from '../../definitions';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -23,6 +22,7 @@ import SearchHeader from '../../containers/SearchHeader';
|
|||
import { TThreadModel } from '../../definitions/IThread';
|
||||
import Item from './Item';
|
||||
import { Services } from '../../lib/services';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
const API_FETCH_COUNT = 50;
|
||||
|
||||
|
@ -42,8 +42,8 @@ const DiscussionsView = ({ navigation, route }: IDiscussionsViewProps): React.Re
|
|||
const rid = route.params?.rid;
|
||||
const t = route.params?.t;
|
||||
|
||||
const baseUrl = useSelector((state: IApplicationState) => state.server?.server);
|
||||
const isMasterDetail = useSelector((state: IApplicationState) => state.app?.isMasterDetail);
|
||||
const baseUrl = useAppSelector(state => state.server?.server);
|
||||
const isMasterDetail = useAppSelector(state => state.app?.isMasterDetail);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [discussions, setDiscussions] = useState<IMessageFromServer[]>([]);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { StackNavigationProp } from '@react-navigation/stack';
|
|||
import { useNavigation } from '@react-navigation/native';
|
||||
import { Switch } from 'react-native';
|
||||
import { RadioButton } from 'react-native-ui-lib';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { setPreference } from '../actions/sortPreferences';
|
||||
import { DisplayMode, SortBy } from '../lib/constants';
|
||||
|
@ -12,21 +12,22 @@ import * as List from '../containers/List';
|
|||
import { ICON_SIZE } from '../containers/List/constants';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { IApplicationState, IPreferences } from '../definitions';
|
||||
import { IPreferences } from '../definitions';
|
||||
import I18n from '../i18n';
|
||||
import { SettingsStackParamList } from '../stacks/types';
|
||||
import { useTheme } from '../theme';
|
||||
import { events, logEvent } from '../utils/log';
|
||||
import { saveSortPreference } from '../lib/methods';
|
||||
import { useAppSelector } from '../lib/hooks';
|
||||
|
||||
const DisplayPrefsView = (): React.ReactElement => {
|
||||
const navigation = useNavigation<StackNavigationProp<SettingsStackParamList, 'DisplayPrefsView'>>();
|
||||
const { colors } = useTheme();
|
||||
|
||||
const { sortBy, groupByType, showFavorites, showUnread, showAvatar, displayMode } = useSelector(
|
||||
(state: IApplicationState) => state.sortPreferences
|
||||
const { sortBy, groupByType, showFavorites, showUnread, showAvatar, displayMode } = useAppSelector(
|
||||
state => state.sortPreferences
|
||||
);
|
||||
const { isMasterDetail } = useSelector((state: IApplicationState) => state.app);
|
||||
const { isMasterDetail } = useAppSelector(state => state.app);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text, View, TextInput as TextInputComp } from 'react-native';
|
||||
import { StyleSheet, Text, View, TextInput as RNTextInput } from 'react-native';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import * as List from '../containers/List';
|
||||
import I18n from '../i18n';
|
||||
import log, { events, logEvent } from '../utils/log';
|
||||
import { TSupportedThemes, withTheme } from '../theme';
|
||||
import { withTheme } from '../theme';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import FormTextInput from '../containers/TextInput/FormTextInput';
|
||||
import Button from '../containers/Button';
|
||||
import { getUserSelector } from '../selectors/login';
|
||||
import { PADDING_HORIZONTAL } from '../containers/List/constants';
|
||||
import { themes } from '../lib/constants';
|
||||
import { Encryption } from '../lib/encryption';
|
||||
import { logout as logoutAction } from '../actions/login';
|
||||
import { logout } from '../actions/login';
|
||||
import { showConfirmationAlert, showErrorAlert } from '../utils/info';
|
||||
import EventEmitter from '../utils/events';
|
||||
import { LISTENER } from '../containers/Toast';
|
||||
import debounce from '../utils/debounce';
|
||||
import sharedStyles from './Styles';
|
||||
import { IUser } from '../definitions';
|
||||
import { IApplicationState, IBaseScreen, IUser } from '../definitions';
|
||||
import { Services } from '../lib/services';
|
||||
import { SettingsStackParamList } from '../stacks/types';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -47,12 +47,10 @@ interface IE2EEncryptionSecurityViewState {
|
|||
newPassword: string;
|
||||
}
|
||||
|
||||
interface IE2EEncryptionSecurityViewProps {
|
||||
theme?: TSupportedThemes;
|
||||
interface IE2EEncryptionSecurityViewProps extends IBaseScreen<SettingsStackParamList, 'E2EEncryptionSecurityView'> {
|
||||
user: IUser;
|
||||
server: string;
|
||||
encryptionEnabled: boolean;
|
||||
logout(): void;
|
||||
}
|
||||
|
||||
class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityViewProps, IE2EEncryptionSecurityViewState> {
|
||||
|
@ -66,7 +64,7 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
|
|||
|
||||
onChangePasswordText = debounce((text: string) => this.setState({ newPassword: text }), 300);
|
||||
|
||||
setNewPasswordRef = (ref: TextInputComp) => (this.newPasswordInputRef = ref);
|
||||
setNewPasswordRef = (ref: RNTextInput) => (this.newPasswordInputRef = ref);
|
||||
|
||||
changePassword = () => {
|
||||
const { newPassword } = this.state;
|
||||
|
@ -107,8 +105,8 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
|
|||
* that's why we're using strict equality to boolean
|
||||
*/
|
||||
if (res === true) {
|
||||
const { logout } = this.props;
|
||||
logout();
|
||||
const { dispatch } = this.props;
|
||||
dispatch(logout());
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
|
@ -127,13 +125,13 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
|
|||
return (
|
||||
<>
|
||||
<List.Section>
|
||||
<Text style={[styles.title, { color: themes[theme!].headerTitleColor }]}>
|
||||
<Text style={[styles.title, { color: themes[theme].headerTitleColor }]}>
|
||||
{I18n.t('E2E_encryption_change_password_title')}
|
||||
</Text>
|
||||
<Text style={[styles.description, { color: themes[theme!].bodyText }]}>
|
||||
<Text style={[styles.description, { color: themes[theme].bodyText }]}>
|
||||
{I18n.t('E2E_encryption_change_password_description')}
|
||||
</Text>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
inputRef={this.setNewPasswordRef}
|
||||
placeholder={I18n.t('New_Password')}
|
||||
returnKeyType='send'
|
||||
|
@ -146,7 +144,6 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
|
|||
<Button
|
||||
onPress={this.changePassword}
|
||||
title={I18n.t('Save_Changes')}
|
||||
theme={theme}
|
||||
disabled={!newPassword.trim()}
|
||||
style={styles.changePasswordButton}
|
||||
testID='e2e-encryption-security-view-change-password'
|
||||
|
@ -161,25 +158,24 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
|
|||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView testID='e2e-encryption-security-view' style={{ backgroundColor: themes[theme!].backgroundColor }}>
|
||||
<SafeAreaView testID='e2e-encryption-security-view' style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
<StatusBar />
|
||||
<List.Container>
|
||||
<View style={styles.container}>
|
||||
{this.renderChangePassword()}
|
||||
|
||||
<List.Section>
|
||||
<Text style={[styles.title, { color: themes[theme!].headerTitleColor }]}>
|
||||
<Text style={[styles.title, { color: themes[theme].headerTitleColor }]}>
|
||||
{I18n.t('E2E_encryption_reset_title')}
|
||||
</Text>
|
||||
<Text style={[styles.description, { color: themes[theme!].bodyText }]}>
|
||||
<Text style={[styles.description, { color: themes[theme].bodyText }]}>
|
||||
{I18n.t('E2E_encryption_reset_description')}
|
||||
</Text>
|
||||
<Button
|
||||
onPress={this.resetOwnKey}
|
||||
title={I18n.t('E2E_encryption_reset_button')}
|
||||
theme={theme}
|
||||
type='secondary'
|
||||
backgroundColor={themes[theme!].chatComponentBackground}
|
||||
backgroundColor={themes[theme].chatComponentBackground}
|
||||
testID='e2e-encryption-security-view-reset-key'
|
||||
/>
|
||||
</List.Section>
|
||||
|
@ -190,14 +186,10 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => ({
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
server: state.server.server,
|
||||
user: getUserSelector(state),
|
||||
encryptionEnabled: state.encryption.enabled
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
logout: () => dispatch(logoutAction(true))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(E2EEncryptionSecurityView));
|
||||
export default connect(mapStateToProps)(withTheme(E2EEncryptionSecurityView));
|
||||
|
|
|
@ -9,7 +9,7 @@ import Button from '../containers/Button';
|
|||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import FormTextInput from '../containers/TextInput/FormTextInput';
|
||||
import { IBaseScreen } from '../definitions';
|
||||
import I18n from '../i18n';
|
||||
import KeyboardView from '../containers/KeyboardView';
|
||||
|
@ -75,7 +75,7 @@ class E2EEnterYourPasswordView extends React.Component<TE2EEnterYourPasswordView
|
|||
<SafeAreaView
|
||||
style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}
|
||||
testID='e2e-enter-your-password-view'>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
inputRef={(e: RNTextInput) => {
|
||||
this.passwordInput = e;
|
||||
}}
|
||||
|
@ -93,7 +93,6 @@ class E2EEnterYourPasswordView extends React.Component<TE2EEnterYourPasswordView
|
|||
onPress={this.submit}
|
||||
title={I18n.t('Confirm')}
|
||||
disabled={!password}
|
||||
theme={theme}
|
||||
testID='e2e-enter-your-password-view-confirm'
|
||||
/>
|
||||
<Text style={[styles.info, { color: themes[theme].bodyText }]}>{I18n.t('Enter_Your_Encryption_Password_desc1')}</Text>
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import React from 'react';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import { themes } from '../lib/constants';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import Markdown from '../containers/markdown';
|
||||
import { TSupportedThemes, withTheme } from '../theme';
|
||||
import { withTheme } from '../theme';
|
||||
import I18n from '../i18n';
|
||||
import { E2ESaveYourPasswordStackParamList } from '../stacks/types';
|
||||
import { IBaseScreen } from '../definitions';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -23,17 +22,10 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
interface INavigation {
|
||||
navigation: StackNavigationProp<E2ESaveYourPasswordStackParamList, 'E2EHowItWorksView'>;
|
||||
route: RouteProp<E2ESaveYourPasswordStackParamList, 'E2EHowItWorksView'>;
|
||||
}
|
||||
type TE2EHowItWorksViewProps = IBaseScreen<E2ESaveYourPasswordStackParamList, 'E2EHowItWorksView'>;
|
||||
|
||||
interface IE2EHowItWorksViewProps extends INavigation {
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
|
||||
class E2EHowItWorksView extends React.Component<IE2EHowItWorksViewProps, any> {
|
||||
static navigationOptions = ({ route, navigation }: INavigation) => {
|
||||
class E2EHowItWorksView extends React.Component<TE2EHowItWorksViewProps, any> {
|
||||
static navigationOptions = ({ route, navigation }: Pick<TE2EHowItWorksViewProps, 'navigation' | 'route'>) => {
|
||||
const showCloseModal = route.params?.showCloseModal;
|
||||
return {
|
||||
title: I18n.t('How_It_Works'),
|
||||
|
|
|
@ -55,7 +55,7 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
interface IE2ESaveYourPasswordViewState {
|
||||
password: string;
|
||||
password: string | null;
|
||||
}
|
||||
|
||||
interface IE2ESaveYourPasswordViewProps extends IBaseScreen<E2ESaveYourPasswordStackParamList, 'E2ESaveYourPasswordView'> {
|
||||
|
@ -87,7 +87,7 @@ class E2ESaveYourPasswordView extends React.Component<IE2ESaveYourPasswordViewPr
|
|||
// Set stored password on local state
|
||||
const password = UserPreferences.getString(`${server}-${E2E_RANDOM_PASSWORD_KEY}`);
|
||||
if (this.mounted) {
|
||||
this.setState({ password: password! });
|
||||
this.setState({ password });
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.state.password = password;
|
||||
|
@ -110,8 +110,10 @@ class E2ESaveYourPasswordView extends React.Component<IE2ESaveYourPasswordViewPr
|
|||
onCopy = () => {
|
||||
logEvent(events.E2E_SAVE_PW_COPY);
|
||||
const { password } = this.state;
|
||||
Clipboard.setString(password);
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||
if (password) {
|
||||
Clipboard.setString(password);
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||
}
|
||||
};
|
||||
|
||||
onHowItWorks = () => {
|
||||
|
@ -144,7 +146,6 @@ class E2ESaveYourPasswordView extends React.Component<IE2ESaveYourPasswordViewPr
|
|||
title={I18n.t('Copy')}
|
||||
type='secondary'
|
||||
fontSize={12}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.info, { color: themes[theme].bodyText }]}>{I18n.t('Save_Your_Encryption_Password_info')}</Text>
|
||||
|
@ -153,13 +154,11 @@ class E2ESaveYourPasswordView extends React.Component<IE2ESaveYourPasswordViewPr
|
|||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
title={I18n.t('How_It_Works')}
|
||||
type='secondary'
|
||||
theme={theme}
|
||||
testID='e2e-save-password-view-how-it-works'
|
||||
/>
|
||||
<Button
|
||||
onPress={this.onSaved}
|
||||
title={I18n.t('I_Saved_My_E2E_Password')}
|
||||
theme={theme}
|
||||
testID='e2e-save-password-view-saved-password'
|
||||
/>
|
||||
</View>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Text } from 'react-native';
|
|||
|
||||
import Button from '../containers/Button';
|
||||
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import FormTextInput from '../containers/TextInput/FormTextInput';
|
||||
import I18n from '../i18n';
|
||||
import { themes } from '../lib/constants';
|
||||
import { Services } from '../lib/services';
|
||||
|
@ -92,7 +92,7 @@ class ForgotPasswordView extends React.Component<IForgotPasswordViewProps, IForg
|
|||
<Text style={[sharedStyles.loginTitle, sharedStyles.textBold, { color: themes[theme].titleText }]}>
|
||||
{I18n.t('Forgot_password')}
|
||||
</Text>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
autoFocus
|
||||
placeholder={I18n.t('Email')}
|
||||
keyboardType='email-address'
|
||||
|
@ -110,7 +110,6 @@ class ForgotPasswordView extends React.Component<IForgotPasswordViewProps, IForg
|
|||
testID='forgot-password-view-submit'
|
||||
loading={isFetching}
|
||||
disabled={invalidEmail}
|
||||
theme={theme}
|
||||
/>
|
||||
</FormContainerInner>
|
||||
</FormContainer>
|
||||
|
|
|
@ -121,7 +121,6 @@ class InviteUsersEditView extends React.Component<IInviteUsersEditViewProps, any
|
|||
};
|
||||
|
||||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<List.Container>
|
||||
|
@ -134,7 +133,7 @@ class InviteUsersEditView extends React.Component<IInviteUsersEditViewProps, any
|
|||
<List.Separator />
|
||||
</List.Section>
|
||||
<View style={styles.innerContainer}>
|
||||
<Button title={I18n.t('Generate_New_Link')} type='primary' onPress={this.createInviteLink} theme={theme} />
|
||||
<Button title={I18n.t('Generate_New_Link')} type='primary' onPress={this.createInviteLink} />
|
||||
</View>
|
||||
</List.Container>
|
||||
</SafeAreaView>
|
||||
|
|
|
@ -1,69 +1,58 @@
|
|||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import React, { useEffect } from 'react';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { ScrollView, Share, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { inviteLinksClear, inviteLinksCreate } from '../../actions/inviteLinks';
|
||||
import { themes } from '../../lib/constants';
|
||||
import Button from '../../containers/Button';
|
||||
import Markdown from '../../containers/markdown';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import RCTextInput from '../../containers/TextInput';
|
||||
import FormTextInput from '../../containers/TextInput/FormTextInput';
|
||||
import { IApplicationState, IBaseScreen } from '../../definitions';
|
||||
import I18n from '../../i18n';
|
||||
import { TInvite } from '../../reducers/inviteLinks';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { withTheme } from '../../theme';
|
||||
import { useTheme } from '../../theme';
|
||||
import { events, logEvent } from '../../utils/log';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import styles from './styles';
|
||||
|
||||
interface IInviteUsersViewProps extends IBaseScreen<ChatsStackParamList, 'InviteUsersView'> {
|
||||
timeDateFormat: string;
|
||||
invite: TInvite;
|
||||
}
|
||||
class InviteUsersView extends React.Component<IInviteUsersViewProps, any> {
|
||||
static navigationOptions = (): StackNavigationOptions => ({
|
||||
title: I18n.t('Invite_users')
|
||||
});
|
||||
type IInviteUsersViewProps = IBaseScreen<ChatsStackParamList, 'InviteUsersView'>;
|
||||
|
||||
private rid: string;
|
||||
const InviteUsersView = ({ route, navigation }: IInviteUsersViewProps): React.ReactElement => {
|
||||
const rid = route.params?.rid;
|
||||
const timeDateFormat = useSelector((state: IApplicationState) => state.settings.Message_TimeAndDateFormat as string);
|
||||
const invite = useSelector((state: IApplicationState) => state.inviteLinks.invite);
|
||||
const { colors, theme } = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
constructor(props: IInviteUsersViewProps) {
|
||||
super(props);
|
||||
this.rid = props.route.params?.rid;
|
||||
}
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: I18n.t('Invite_users')
|
||||
});
|
||||
}, []);
|
||||
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(inviteLinksCreate(this.rid));
|
||||
}
|
||||
useEffect(() => {
|
||||
dispatch(inviteLinksCreate(rid));
|
||||
return () => {
|
||||
dispatch(inviteLinksClear());
|
||||
};
|
||||
}, []);
|
||||
|
||||
componentWillUnmount() {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(inviteLinksClear());
|
||||
}
|
||||
|
||||
share = () => {
|
||||
const share = () => {
|
||||
logEvent(events.IU_SHARE);
|
||||
const { invite } = this.props;
|
||||
if (!invite || !invite.url) {
|
||||
return;
|
||||
}
|
||||
Share.share({ message: invite.url });
|
||||
};
|
||||
|
||||
edit = () => {
|
||||
const edit = () => {
|
||||
logEvent(events.IU_GO_IU_EDIT);
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('InviteUsersEditView', { rid: this.rid });
|
||||
navigation.navigate('InviteUsersEditView', { rid });
|
||||
};
|
||||
|
||||
linkExpirationText = () => {
|
||||
const { timeDateFormat, invite } = this.props;
|
||||
|
||||
const linkExpirationText = () => {
|
||||
if (!invite || !invite.url) {
|
||||
return null;
|
||||
}
|
||||
|
@ -90,38 +79,28 @@ class InviteUsersView extends React.Component<IInviteUsersViewProps, any> {
|
|||
return I18n.t('Your_invite_link_will_never_expire');
|
||||
};
|
||||
|
||||
renderExpiration = () => {
|
||||
const { theme } = this.props;
|
||||
const expirationMessage = this.linkExpirationText();
|
||||
const renderExpiration = () => {
|
||||
const expirationMessage = linkExpirationText();
|
||||
return <Markdown msg={expirationMessage} theme={theme} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { theme, invite } = this.props;
|
||||
return (
|
||||
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
{/* @ts-ignore*/}
|
||||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
showsVerticalScrollIndicator={false}>
|
||||
<StatusBar />
|
||||
<View style={styles.innerContainer}>
|
||||
<RCTextInput label={I18n.t('Invite_Link')} theme={theme} value={invite && invite.url} editable={false} />
|
||||
{this.renderExpiration()}
|
||||
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
|
||||
<Button title={I18n.t('Share_Link')} type='primary' onPress={this.share} theme={theme} />
|
||||
<Button title={I18n.t('Edit_Invite')} type='secondary' onPress={this.edit} theme={theme} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<SafeAreaView style={{ backgroundColor: colors.backgroundColor }}>
|
||||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
style={{ backgroundColor: colors.auxiliaryBackground }}
|
||||
showsVerticalScrollIndicator={false}>
|
||||
<StatusBar />
|
||||
<View style={styles.innerContainer}>
|
||||
<FormTextInput label={I18n.t('Invite_Link')} theme={theme} value={invite && invite.url} editable={false} />
|
||||
{renderExpiration()}
|
||||
<View style={[styles.divider, { backgroundColor: colors.separatorColor }]} />
|
||||
<Button title={I18n.t('Share_Link')} type='primary' onPress={share} />
|
||||
<Button title={I18n.t('Edit_Invite')} type='secondary' onPress={edit} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
timeDateFormat: state.settings.Message_TimeAndDateFormat as string,
|
||||
invite: state.inviteLinks.invite
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(InviteUsersView));
|
||||
export default InviteUsersView;
|
||||
|
|
|
@ -1,62 +1,60 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import openLink from '../utils/openLink';
|
||||
import { TSupportedThemes, withTheme } from '../theme';
|
||||
import { useTheme } from '../theme';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import * as List from '../containers/List';
|
||||
import { OutsideParamList } from '../stacks/types';
|
||||
import { IBaseScreen, IApplicationState } from '../definitions';
|
||||
|
||||
interface ILegalView {
|
||||
interface ILegalViewProps extends IBaseScreen<OutsideParamList, 'LegalView'> {
|
||||
server: string;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
|
||||
class LegalView extends React.Component<ILegalView, any> {
|
||||
static navigationOptions = (): StackNavigationOptions => ({
|
||||
title: I18n.t('Legal')
|
||||
});
|
||||
const LegalView = ({ navigation }: ILegalViewProps): React.ReactElement => {
|
||||
const server = useSelector((state: IApplicationState) => state.server.server);
|
||||
const { theme } = useTheme();
|
||||
|
||||
onPressItem = ({ route }: { route: string }) => {
|
||||
const { server, theme } = this.props;
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: I18n.t('Legal')
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onPressItem = ({ route }: { route: string }) => {
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
openLink(`${server}/${route}`, theme);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SafeAreaView testID='legal-view'>
|
||||
<StatusBar />
|
||||
<List.Container>
|
||||
<List.Section>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Terms_of_Service'
|
||||
onPress={() => this.onPressItem({ route: 'terms-of-service' })}
|
||||
testID='legal-terms-button'
|
||||
showActionIndicator
|
||||
/>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Privacy_Policy'
|
||||
onPress={() => this.onPressItem({ route: 'privacy-policy' })}
|
||||
testID='legal-privacy-button'
|
||||
showActionIndicator
|
||||
/>
|
||||
<List.Separator />
|
||||
</List.Section>
|
||||
</List.Container>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<SafeAreaView testID='legal-view'>
|
||||
<StatusBar />
|
||||
<List.Container>
|
||||
<List.Section>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Terms_of_Service'
|
||||
onPress={() => onPressItem({ route: 'terms-of-service' })}
|
||||
testID='legal-terms-button'
|
||||
showActionIndicator
|
||||
/>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Privacy_Policy'
|
||||
onPress={() => onPressItem({ route: 'privacy-policy' })}
|
||||
testID='legal-privacy-button'
|
||||
showActionIndicator
|
||||
/>
|
||||
<List.Separator />
|
||||
</List.Section>
|
||||
</List.Container>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: any) => ({
|
||||
server: state.server.server
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(LegalView));
|
||||
export default LegalView;
|
||||
|
|
|
@ -7,7 +7,7 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
|||
|
||||
import { TSupportedThemes, withTheme } from '../theme';
|
||||
import { themes } from '../lib/constants';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import FormTextInput from '../containers/TextInput/FormTextInput';
|
||||
import KeyboardView from '../containers/KeyboardView';
|
||||
import I18n from '../i18n';
|
||||
import { LISTENER } from '../containers/Toast';
|
||||
|
@ -193,7 +193,7 @@ const LivechatEditView = ({
|
|||
<ScrollView {...scrollPersistTaps} style={styles.container}>
|
||||
<SafeAreaView>
|
||||
<Title title={visitor?.username} theme={theme} />
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={I18n.t('Name')}
|
||||
defaultValue={visitor?.name}
|
||||
onChangeText={text => onChangeText('name', text)}
|
||||
|
@ -203,7 +203,7 @@ const LivechatEditView = ({
|
|||
theme={theme}
|
||||
editable={!!permissions[0]}
|
||||
/>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={I18n.t('Email')}
|
||||
inputRef={e => {
|
||||
inputs.name = e;
|
||||
|
@ -216,7 +216,7 @@ const LivechatEditView = ({
|
|||
theme={theme}
|
||||
editable={!!permissions[0]}
|
||||
/>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={I18n.t('Phone')}
|
||||
inputRef={e => {
|
||||
inputs.phone = e;
|
||||
|
@ -236,7 +236,7 @@ const LivechatEditView = ({
|
|||
editable={!!permissions[0]}
|
||||
/>
|
||||
{Object.entries(customFields?.visitor || {}).map(([key, value], index, array) => (
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={key}
|
||||
defaultValue={value}
|
||||
inputRef={e => {
|
||||
|
@ -254,7 +254,7 @@ const LivechatEditView = ({
|
|||
/>
|
||||
))}
|
||||
<Title title={I18n.t('Conversation')} theme={theme} />
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={I18n.t('Topic')}
|
||||
inputRef={e => {
|
||||
inputs.topic = e;
|
||||
|
@ -280,7 +280,7 @@ const LivechatEditView = ({
|
|||
/>
|
||||
|
||||
{Object.entries(customFields?.livechat || {}).map(([key, value], index, array: any) => (
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={key}
|
||||
defaultValue={value}
|
||||
inputRef={e => {
|
||||
|
@ -298,7 +298,7 @@ const LivechatEditView = ({
|
|||
/>
|
||||
))}
|
||||
|
||||
<Button title={I18n.t('Save')} onPress={submit} theme={theme} />
|
||||
<Button title={I18n.t('Save')} onPress={submit} />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</KeyboardView>
|
||||
|
|
|
@ -9,7 +9,7 @@ import Button from '../containers/Button';
|
|||
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import LoginServices from '../containers/LoginServices';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import FormTextInput from '../containers/TextInput/FormTextInput';
|
||||
import { IApplicationState, IBaseScreen } from '../definitions';
|
||||
import I18n from '../i18n';
|
||||
import { OutsideParamList } from '../stacks/types';
|
||||
|
@ -157,7 +157,7 @@ class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
|
|||
return (
|
||||
<>
|
||||
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Login')}</Text>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={I18n.t('Username_or_email')}
|
||||
containerStyle={styles.inputContainer}
|
||||
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Username_or_email')}
|
||||
|
@ -173,7 +173,7 @@ class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
|
|||
theme={theme}
|
||||
value={user}
|
||||
/>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={I18n.t('Password')}
|
||||
containerStyle={styles.inputContainer}
|
||||
inputRef={e => {
|
||||
|
@ -196,7 +196,6 @@ class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
|
|||
testID='login-view-submit'
|
||||
loading={isFetching}
|
||||
disabled={!this.valid()}
|
||||
theme={theme}
|
||||
style={styles.loginButton}
|
||||
/>
|
||||
{Accounts_PasswordReset && (
|
||||
|
@ -205,7 +204,6 @@ class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
|
|||
type='secondary'
|
||||
onPress={this.forgotPassword}
|
||||
testID='login-view-forgot-password'
|
||||
theme={theme}
|
||||
color={themes[theme].auxiliaryText}
|
||||
fontSize={14}
|
||||
/>
|
||||
|
|
|
@ -1,43 +1,38 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ScrollView } from 'react-native';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
import { themes } from '../lib/constants';
|
||||
import { TSupportedThemes, withTheme } from '../theme';
|
||||
import { useTheme } from '../theme';
|
||||
import { ChatsStackParamList } from '../stacks/types';
|
||||
import { IBaseScreen } from '../definitions';
|
||||
|
||||
interface IMarkdownTableViewProps {
|
||||
route: RouteProp<ChatsStackParamList, 'MarkdownTableView'>;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
type IMarkdownTableViewProps = IBaseScreen<ChatsStackParamList, 'MarkdownTableView'>;
|
||||
|
||||
class MarkdownTableView extends React.Component<IMarkdownTableViewProps> {
|
||||
static navigationOptions = (): StackNavigationOptions => ({
|
||||
title: I18n.t('Table')
|
||||
});
|
||||
const MarkdownTableView = ({ navigation, route }: IMarkdownTableViewProps): React.ReactElement => {
|
||||
const renderRows = route.params?.renderRows;
|
||||
const tableWidth = route.params?.tableWidth;
|
||||
const { colors } = useTheme();
|
||||
|
||||
render() {
|
||||
const { route, theme } = this.props;
|
||||
const renderRows = route.params?.renderRows;
|
||||
const tableWidth = route.params?.tableWidth;
|
||||
|
||||
if (isIOS) {
|
||||
return (
|
||||
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }} contentContainerStyle={{ width: tableWidth }}>
|
||||
{renderRows()}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: I18n.t('Table')
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isIOS) {
|
||||
return (
|
||||
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
<ScrollView horizontal>{renderRows()}</ScrollView>
|
||||
<ScrollView style={{ backgroundColor: colors.backgroundColor }} contentContainerStyle={{ width: tableWidth }}>
|
||||
{renderRows()}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(MarkdownTableView);
|
||||
return (
|
||||
<ScrollView style={{ backgroundColor: colors.backgroundColor }}>
|
||||
<ScrollView horizontal>{renderRows()}</ScrollView>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownTableView;
|
||||
|
|
|
@ -20,7 +20,17 @@ import getThreadName from '../../lib/methods/getThreadName';
|
|||
import styles from './styles';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { IRoomInfoParam } from '../SearchMessagesView';
|
||||
import { TMessageModel, IEmoji, ISubscription, SubscriptionType, IUrl } from '../../definitions';
|
||||
import {
|
||||
IApplicationState,
|
||||
TMessageModel,
|
||||
IEmoji,
|
||||
ISubscription,
|
||||
SubscriptionType,
|
||||
IAttachment,
|
||||
IMessage,
|
||||
TAnyMessageModel,
|
||||
IUrl
|
||||
} from '../../definitions';
|
||||
import { Services } from '../../lib/services';
|
||||
|
||||
interface IMessagesViewProps {
|
||||
|
@ -37,36 +47,19 @@ interface IMessagesViewProps {
|
|||
route: RouteProp<ChatsStackParamList, 'MessagesView'>;
|
||||
customEmojis: { [key: string]: IEmoji };
|
||||
theme: TSupportedThemes;
|
||||
showActionSheet: Function;
|
||||
showActionSheet: (params: { options: string[]; hasCancel: boolean }) => void;
|
||||
useRealName: boolean;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
interface IMessagesViewState {
|
||||
loading: boolean;
|
||||
messages: [];
|
||||
messages: IMessage[];
|
||||
message?: IMessage;
|
||||
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: boolean;
|
||||
pinned: boolean;
|
||||
type: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface IParams {
|
||||
rid: string;
|
||||
t: SubscriptionType;
|
||||
|
@ -75,24 +68,25 @@ interface IParams {
|
|||
name?: string;
|
||||
fname?: string;
|
||||
prid?: string;
|
||||
room: ISubscription;
|
||||
room?: ISubscription;
|
||||
jumpToMessageId?: string;
|
||||
jumpToThreadId?: string;
|
||||
roomUserId?: string;
|
||||
}
|
||||
|
||||
class MessagesView extends React.Component<IMessagesViewProps, any> {
|
||||
class MessagesView extends React.Component<IMessagesViewProps, IMessagesViewState> {
|
||||
private rid: string;
|
||||
private t: SubscriptionType;
|
||||
private content: any;
|
||||
private room: any;
|
||||
private room?: ISubscription;
|
||||
|
||||
constructor(props: IMessagesViewProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false,
|
||||
messages: [],
|
||||
fileLoading: true
|
||||
fileLoading: true,
|
||||
total: 0
|
||||
};
|
||||
this.setHeader();
|
||||
this.rid = props.route.params?.rid;
|
||||
|
@ -104,7 +98,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
this.load();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: IMessagesViewProps, nextState: any) {
|
||||
shouldComponentUpdate(nextProps: IMessagesViewProps, nextState: IMessagesViewState) {
|
||||
const { loading, messages, fileLoading } = this.state;
|
||||
const { theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
|
@ -137,7 +131,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
navigation.navigate('RoomInfoView', navParam);
|
||||
};
|
||||
|
||||
jumpToMessage = async ({ item }: { item: IMessageItem }) => {
|
||||
jumpToMessage = async ({ item }: { item: IMessage }) => {
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
let params: IParams = {
|
||||
rid: this.rid,
|
||||
|
@ -165,7 +159,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
|
||||
defineMessagesViewContent = (name: string) => {
|
||||
const { user, baseUrl, theme, useRealName } = this.props;
|
||||
const renderItemCommonProps = (item: IMessageItem) => ({
|
||||
const renderItemCommonProps = (item: TAnyMessageModel) => ({
|
||||
item,
|
||||
baseUrl,
|
||||
user,
|
||||
|
@ -224,8 +218,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
},
|
||||
noDataMsg: I18n.t('No_mentioned_messages'),
|
||||
testID: 'mentioned-messages-view',
|
||||
// @ts-ignore TODO: unify IMessage
|
||||
renderItem: (item: IMessageItem) => <Message {...renderItemCommonProps(item)} msg={item.msg} theme={theme} />
|
||||
renderItem: (item: TAnyMessageModel) => <Message {...renderItemCommonProps(item)} msg={item.msg} theme={theme} />
|
||||
},
|
||||
// Starred Messages Screen
|
||||
Starred: {
|
||||
|
@ -236,16 +229,15 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
},
|
||||
noDataMsg: I18n.t('No_starred_messages'),
|
||||
testID: 'starred-messages-view',
|
||||
renderItem: (item: IMessageItem) => (
|
||||
// @ts-ignore TODO: unify IMessage
|
||||
renderItem: (item: TAnyMessageModel) => (
|
||||
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
||||
),
|
||||
action: (message: IMessageItem) => ({
|
||||
action: (message: IMessage) => ({
|
||||
title: I18n.t('Unstar'),
|
||||
icon: message.starred ? 'star-filled' : 'star',
|
||||
onPress: this.handleActionPress
|
||||
}),
|
||||
handleActionPress: (message: IMessageItem) => Services.toggleStarMessage(message._id, message.starred)
|
||||
handleActionPress: (message: IMessage) => Services.toggleStarMessage(message._id, message.starred)
|
||||
},
|
||||
// Pinned Messages Screen
|
||||
Pinned: {
|
||||
|
@ -256,14 +248,12 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
},
|
||||
noDataMsg: I18n.t('No_pinned_messages'),
|
||||
testID: 'pinned-messages-view',
|
||||
renderItem: (item: IMessageItem) => (
|
||||
// @ts-ignore TODO: unify IMessage
|
||||
renderItem: (item: TAnyMessageModel) => (
|
||||
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
||||
),
|
||||
action: () => ({ title: I18n.t('Unpin'), icon: 'pin', onPress: this.handleActionPress }),
|
||||
handleActionPress: (message: IMessageItem) => Services.togglePinMessage(message._id, message.pinned)
|
||||
handleActionPress: (message: IMessage) => Services.togglePinMessage(message._id, message.pinned)
|
||||
}
|
||||
// @ts-ignore
|
||||
}[name];
|
||||
};
|
||||
|
||||
|
@ -316,12 +306,12 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
return null;
|
||||
};
|
||||
|
||||
showAttachment = (attachment: any) => {
|
||||
showAttachment = (attachment: IAttachment) => {
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('AttachmentView', { attachment });
|
||||
};
|
||||
|
||||
onLongPress = (message: IMessageItem) => {
|
||||
onLongPress = (message: IMessage) => {
|
||||
this.setState({ message }, this.showActionSheet);
|
||||
};
|
||||
|
||||
|
@ -338,7 +328,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
const result = await this.content.handleActionPress(message);
|
||||
if (result.success) {
|
||||
this.setState((prevState: IMessagesViewState) => ({
|
||||
messages: prevState.messages.filter((item: IMessageItem) => item._id !== message._id),
|
||||
messages: prevState.messages.filter((item: IMessage) => item._id !== message?._id),
|
||||
total: prevState.total - 1
|
||||
}));
|
||||
}
|
||||
|
@ -360,7 +350,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
);
|
||||
};
|
||||
|
||||
renderItem = ({ item }: { item: IMessageItem }) => this.content.renderItem(item);
|
||||
renderItem = ({ item }: { item: IMessage }) => this.content.renderItem(item);
|
||||
|
||||
render() {
|
||||
const { messages, loading } = this.state;
|
||||
|
@ -386,7 +376,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => ({
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state),
|
||||
customEmojis: state.customEmojis,
|
||||
|
|
|
@ -171,16 +171,11 @@ class ModalBlockView extends React.Component<IModalBlockViewProps, IModalBlockVi
|
|||
}
|
||||
};
|
||||
|
||||
cancel = async ({ closeModal }: { closeModal?: () => void }) => {
|
||||
cancel = async () => {
|
||||
const { data } = this.state;
|
||||
const { appId, viewId, view } = data;
|
||||
|
||||
// handle tablet case
|
||||
if (closeModal) {
|
||||
closeModal();
|
||||
} else {
|
||||
Navigation.back();
|
||||
}
|
||||
Navigation.back();
|
||||
|
||||
try {
|
||||
await triggerCancel({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FlatList, StyleSheet, TextInputProps, View } from 'react-native';
|
||||
|
||||
import TextInput from '../../../containers/TextInput';
|
||||
import FormTextInput from '../../../containers/TextInput/FormTextInput';
|
||||
import * as List from '../../../containers/List';
|
||||
import { themes } from '../../../lib/constants';
|
||||
import I18n from '../../../i18n';
|
||||
|
@ -51,7 +51,7 @@ const ServerInput = ({
|
|||
const [focused, setFocused] = useState(false);
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TextInput
|
||||
<FormTextInput
|
||||
label={I18n.t('Enter_workspace_URL')}
|
||||
placeholder={I18n.t('Workspace_URL_Example')}
|
||||
containerStyle={styles.inputContainer}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue