Merge branch 'develop' into appium-v2

This commit is contained in:
GleidsonDaniel 2022-05-23 11:29:32 -03:00
commit 4af0d7a881
138 changed files with 1447 additions and 1511 deletions

View File

@ -150,7 +150,7 @@ commands:
if [[ $CIRCLE_JOB == "android-build-official" ]]; then if [[ $CIRCLE_JOB == "android-build-official" ]]; then
./gradlew bundleOfficialPlayRelease ./gradlew bundleOfficialPlayRelease
fi fi
if [[ $CIRCLE_JOB == "android-build-experimental" ]]; then if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then
./gradlew bundleExperimentalPlayRelease ./gradlew bundleExperimentalPlayRelease
fi fi
if [[ ! $KEYSTORE ]]; then if [[ ! $KEYSTORE ]]; then
@ -169,7 +169,7 @@ commands:
--source-map=android/app/build/generated/sourcemaps/react/officialPlay/release/app.bundle.map \ --source-map=android/app/build/generated/sourcemaps/react/officialPlay/release/app.bundle.map \
--bundle android/app/build/generated/assets/react/officialPlay/release/app.bundle --bundle android/app/build/generated/assets/react/officialPlay/release/app.bundle
fi 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 \ npx bugsnag-source-maps upload-react-native \
--api-key=$BUGSNAG_KEY \ --api-key=$BUGSNAG_KEY \
--app-version-code=$CIRCLE_BUILD_NUM \ --app-version-code=$CIRCLE_BUILD_NUM \
@ -380,6 +380,18 @@ jobs:
steps: steps:
- android-build - 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: android-build-official:
<<: *defaults <<: *defaults
docker: docker:
@ -485,6 +497,10 @@ workflows:
type: approval type: approval
requires: requires:
- lint-testunit - lint-testunit
filters:
branches:
ignore:
- develop
- android-build-experimental: - android-build-experimental:
requires: requires:
- android-hold-build-experimental - android-hold-build-experimental
@ -521,3 +537,15 @@ workflows:
- android-google-play-beta-official: - android-google-play-beta-official:
requires: requires:
- android-hold-google-play-beta-official - 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

View File

@ -1,33 +1,21 @@
import { useBackHandler } from '@react-native-community/hooks'; import { useBackHandler } from '@react-native-community/hooks';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState } from 'react'; import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState, useCallback } from 'react';
import { Keyboard, Text } from 'react-native'; import { Keyboard } from 'react-native';
import { HandlerStateChangeEventPayload, State, TapGestureHandler } from 'react-native-gesture-handler'; import { Easing } from 'react-native-reanimated';
import Animated, { Easing, Extrapolate, interpolateNode, Value } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; 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 { useDimensions, useOrientation } from '../../dimensions';
import I18n from '../../i18n';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { isIOS, isTablet } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
import * as List from '../List';
import { Button } from './Button';
import { Handle } from './Handle'; import { Handle } from './Handle';
import { IActionSheetItem, Item } from './Item'; import { TActionSheetOptions } from './Provider';
import { TActionSheetOptions, TActionSheetOptionsItem } from './Provider'; import BottomSheetContent from './BottomSheetContent';
import styles, { ITEM_HEIGHT } from './styles'; 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 HANDLE_HEIGHT = isIOS ? 40 : 56;
const MAX_SNAP_HEIGHT = 16; const MIN_SNAP_HEIGHT = 16;
const CANCEL_HEIGHT = 64; const CANCEL_HEIGHT = 64;
const ANIMATION_DURATION = 250; const ANIMATION_DURATION = 250;
@ -40,27 +28,26 @@ const ANIMATION_CONFIG = {
const ActionSheet = React.memo( const ActionSheet = React.memo(
forwardRef(({ children }: { children: React.ReactElement }, ref) => { forwardRef(({ children }: { children: React.ReactElement }, ref) => {
const { theme } = useTheme(); const { colors } = useTheme();
const bottomSheetRef = useRef<ScrollBottomSheet<TActionSheetOptionsItem>>(null); const bottomSheetRef = useRef<BottomSheet>(null);
const [data, setData] = useState<TActionSheetOptions>({} as TActionSheetOptions); const [data, setData] = useState<TActionSheetOptions>({} as TActionSheetOptions);
const [isVisible, setVisible] = useState(false); const [isVisible, setVisible] = useState(false);
const { height } = useDimensions(); const { height } = useDimensions();
const { isLandscape } = useOrientation(); const { isLandscape } = useOrientation();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const maxSnap = Math.max( const maxSnap = Math.min(
height -
// Items height // Items height
ITEM_HEIGHT * (data?.options?.length || 0) - ITEM_HEIGHT * (data?.options?.length || 0) +
// Handle height // Handle height
HANDLE_HEIGHT - HANDLE_HEIGHT +
// Custom header height // Custom header height
(data?.headerHeight || 0) - (data?.headerHeight || 0) +
// Insets bottom height (Notch devices) // Insets bottom height (Notch devices)
insets.bottom - insets.bottom +
// Cancel button height // Cancel button height
(data?.hasCancel ? CANCEL_HEIGHT : 0), (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 * we'll provide more one snap
* that point 50% of the whole screen * that point 50% of the whole screen
*/ */
const snaps = height - maxSnap > height * 0.6 && !isLandscape ? [maxSnap, height * 0.5, height] : [maxSnap, height]; const snaps = maxSnap > height * 0.6 && !isLandscape && !data.snaps ? [height * 0.5, maxSnap] : [maxSnap];
const openedSnapIndex = snaps.length > 2 ? 1 : 0;
const closedSnapIndex = snaps.length - 1;
const toggleVisible = () => setVisible(!isVisible); const toggleVisible = () => setVisible(!isVisible);
const hide = () => { const hide = () => {
bottomSheetRef.current?.snapTo(closedSnapIndex); bottomSheetRef.current?.close();
toggleVisible();
}; };
const show = (options: TActionSheetOptions) => { const show = (options: TActionSheetOptions) => {
@ -85,12 +71,6 @@ const ActionSheet = React.memo(
toggleVisible(); toggleVisible();
}; };
const onBackdropPressed = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
if (nativeEvent.oldState === State.ACTIVE) {
hide();
}
};
useBackHandler(() => { useBackHandler(() => {
if (isVisible) { if (isVisible) {
hide(); hide();
@ -102,7 +82,6 @@ const ActionSheet = React.memo(
if (isVisible) { if (isVisible) {
Keyboard.dismiss(); Keyboard.dismiss();
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
bottomSheetRef.current?.snapTo(openedSnapIndex);
} }
}, [isVisible]); }, [isVisible]);
@ -123,26 +102,18 @@ const ActionSheet = React.memo(
</> </>
); );
const renderFooter = () => const renderBackdrop = useCallback(
data?.hasCancel ? ( props => (
<Button <BottomSheetBackdrop
onPress={hide} {...props}
style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]} appearsOnIndex={0}
// TODO: Remove when migrate Touch // Backdrop should be visible all the time bottom sheet is open
theme={theme} disappearsOnIndex={-1}
accessibilityLabel={I18n.t('Cancel')}> opacity={colors.backdropOpacity}
<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 bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {}; const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {};
@ -150,42 +121,19 @@ const ActionSheet = React.memo(
<> <>
{children} {children}
{isVisible && ( {isVisible && (
<> <BottomSheet
<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} ref={bottomSheetRef}
componentType='FlatList' snapPoints={data?.snaps ? data.snaps : snaps}
snapPoints={snaps} animationConfigs={ANIMATION_CONFIG}
initialSnapIndex={closedSnapIndex} animateOnMount={true}
renderHandle={renderHandle} backdropComponent={renderBackdrop}
onSettle={index => index === closedSnapIndex && toggleVisible()} handleComponent={renderHandle}
animatedPosition={animatedPosition.current} enablePanDownToClose
containerStyle={{ ...styles.container, ...bottomSheet, backgroundColor: themes[theme].focusedBackground }} style={{ ...styles.container, ...bottomSheet }}
animationConfig={ANIMATION_CONFIG} backgroundStyle={{ backgroundColor: colors.focusedBackground }}
data={data.options} onChange={index => index === -1 && toggleVisible()}>
renderItem={renderItem} <BottomSheetContent options={data?.options} hide={hide} children={data?.children} hasCancel={data?.hasCancel} />
keyExtractor={item => item.title} </BottomSheet>
style={{ backgroundColor: themes[theme].focusedBackground }}
contentContainerStyle={styles.content}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={List.Separator}
ListFooterComponent={renderFooter}
getItemLayout={getItemLayout}
removeClippedSubviews={isIOS}
/>
</>
)} )}
</> </>
); );

View File

@ -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;

View File

@ -13,10 +13,13 @@ export type TActionSheetOptionsItem = {
}; };
export type TActionSheetOptions = { export type TActionSheetOptions = {
options: TActionSheetOptionsItem[]; options?: TActionSheetOptionsItem[];
headerHeight?: number; headerHeight?: number;
customHeader?: React.ReactElement | null; customHeader?: React.ReactElement | null;
hasCancel?: boolean; hasCancel?: boolean;
type?: string;
children?: React.ReactElement | null;
snaps?: string[] | number[];
}; };
interface IActionSheetProvider { interface IActionSheetProvider {
showActionSheet: (item: TActionSheetOptions) => void; showActionSheet: (item: TActionSheetOptions) => void;

View File

@ -46,8 +46,7 @@ export default StyleSheet.create({
}, },
bottomSheet: { bottomSheet: {
width: '50%', width: '50%',
alignSelf: 'center', marginHorizontal: '25%'
left: '25%'
}, },
button: { button: {
marginHorizontal: 16, marginHorizontal: 16,

View File

@ -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} />);

View File

@ -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();
});
});

View File

@ -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!\\"]}]}"`;

View File

@ -1,26 +1,21 @@
import React from 'react'; import React from 'react';
import { ButtonProps, StyleSheet, Text } from 'react-native'; import { StyleProp, StyleSheet, Text, TextStyle } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable, { PlatformTouchableProps } from 'react-native-platform-touchable';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import { testProps } from '../../lib/methods/testProps'; import { testProps } from '../../lib/methods/testProps';
import { useTheme } from '../../theme';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import ActivityIndicator from '../ActivityIndicator'; import ActivityIndicator from '../ActivityIndicator';
interface IButtonProps extends ButtonProps { interface IButtonProps extends PlatformTouchableProps {
title: string; title: string;
type: string; onPress: () => void;
onPress(): void; type?: string;
disabled: boolean; backgroundColor?: string;
backgroundColor: string; loading?: boolean;
loading: boolean; color?: string;
theme: TSupportedThemes; fontSize?: number;
color: string; styleText?: StyleProp<TextStyle>[];
fontSize: any;
style: any;
styleText?: any;
testID: string;
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -32,7 +27,6 @@ const styles = StyleSheet.create({
marginBottom: 12 marginBottom: 12
}, },
text: { text: {
fontSize: 16,
...sharedStyles.textMedium, ...sharedStyles.textMedium,
...sharedStyles.textAlignCenter ...sharedStyles.textAlignCenter
}, },
@ -41,34 +35,24 @@ const styles = StyleSheet.create({
} }
}); });
export default class Button extends React.PureComponent<Partial<IButtonProps>, any> { const Button = ({
static defaultProps = { type = 'primary',
title: 'Press me!', disabled = false,
type: 'primary', loading = false,
onPress: () => alert('It works!'), fontSize = 16,
disabled: false,
loading: false
};
render() {
const {
title, title,
type,
onPress, onPress,
disabled,
backgroundColor, backgroundColor,
color, color,
loading,
style, style,
theme,
fontSize,
styleText, styleText,
testID, testID,
...otherProps ...otherProps
} = this.props; }: IButtonProps): React.ReactElement => {
const { colors } = useTheme();
const isPrimary = type === 'primary'; const isPrimary = type === 'primary';
let textColor = isPrimary ? themes[theme!].buttonText : themes[theme!].bodyText; let textColor = isPrimary ? colors.buttonText : colors.bodyText;
if (color) { if (color) {
textColor = color; textColor = color;
} }
@ -79,22 +63,22 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
disabled={disabled || loading} disabled={disabled || loading}
style={[ style={[
styles.container, styles.container,
backgroundColor backgroundColor ? { backgroundColor } : { backgroundColor: isPrimary ? colors.actionTintColor : colors.backgroundColor },
? { backgroundColor }
: { backgroundColor: isPrimary ? themes[theme!].actionTintColor : themes[theme!].backgroundColor },
disabled && styles.disabled, disabled && styles.disabled,
style style
]} ]}
accessibilityLabel={title}
{...otherProps} {...otherProps}
{...testProps((testID || title) as string)}> {...testProps((testID || title) as string)}>
{loading ? ( {loading ? (
<ActivityIndicator color={textColor} /> <ActivityIndicator color={textColor} />
) : ( ) : (
<Text style={[styles.text, { color: textColor }, fontSize && { fontSize }, styleText]} accessibilityLabel={title}> <Text style={[styles.text, { color: textColor, fontSize }, styleText]} accessibilityLabel={title}>
{title} {title}
</Text> </Text>
)} )}
</Touchable> </Touchable>
); );
} };
}
export default Button;

View File

@ -327,7 +327,6 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
title={collapsed ? I18n.t('Onboarding_more_options') : I18n.t('Onboarding_less_options')} title={collapsed ? I18n.t('Onboarding_more_options') : I18n.t('Onboarding_less_options')}
type='secondary' type='secondary'
onPress={this.toggleServices} onPress={this.toggleServices}
theme={theme}
style={styles.options} style={styles.options}
color={themes[theme].actionTintColor} color={themes[theme].actionTintColor}
/> />

View File

@ -9,7 +9,7 @@ import { Q } from '@nozbe/watermelondb';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler'; import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
import { generateTriggerId } from '../../lib/methods/actions'; 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 { userTyping as userTypingAction } from '../../actions/room';
import styles from './styles'; import styles from './styles';
import database from '../../lib/database'; import database from '../../lib/database';
@ -678,7 +678,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
canUploadFile = (file: any) => { canUploadFile = (file: any) => {
const { permissionToUpload } = this.state; const { permissionToUpload } = this.state;
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props; 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) { if (result.success) {
return true; return true;
} }
@ -726,7 +731,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
chooseFile = async () => { chooseFile = async () => {
logEvent(events.ROOM_BOX_ACTION_FILE); logEvent(events.ROOM_BOX_ACTION_FILE);
try { try {
const res = await DocumentPicker.pick({ const res = await DocumentPicker.pickSingle({
type: [DocumentPicker.types.allFiles] type: [DocumentPicker.types.allFiles]
}); });
const file = { const file = {

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { StyleProp, ViewStyle } from 'react-native'; import { StyleProp, ViewStyle } from 'react-native';
import { SvgUri } from 'react-native-svg'; 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 { STATUS_COLORS } from '../../lib/constants';
import { useAppSelector } from '../../lib/hooks';
import { CustomIcon, TIconsName } from '../CustomIcon'; import { CustomIcon, TIconsName } from '../CustomIcon';
interface IIconMap { interface IIconMap {
@ -29,8 +29,8 @@ interface IOmnichannelRoomIconProps {
} }
export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnichannelRoomIconProps) => { export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnichannelRoomIconProps) => {
const baseUrl = useSelector((state: IApplicationState) => state.server?.server); const baseUrl = useAppSelector(state => state.server?.server);
const connected = useSelector((state: IApplicationState) => state.meteor?.connected); const connected = useAppSelector(state => state.meteor?.connected);
if (sourceType?.type === OmnichannelSourceType.APP && sourceType.id && sourceType.sidebarIcon && connected) { if (sourceType?.type === OmnichannelSourceType.APP && sourceType.id && sourceType.sidebarIcon && connected) {
return ( return (

View File

@ -5,7 +5,7 @@ import Touchable from 'react-native-platform-touchable';
import { themes } from '../lib/constants'; import { themes } from '../lib/constants';
import I18n from '../i18n'; import I18n from '../i18n';
import { CustomIcon } from './CustomIcon'; import { CustomIcon } from './CustomIcon';
import TextInput from '../presentation/TextInput'; import TextInput from './TextInput';
import { useTheme } from '../theme'; import { useTheme } from '../theme';
import { isIOS } from '../utils/deviceInfo'; import { isIOS } from '../utils/deviceInfo';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';

View File

@ -5,7 +5,7 @@ import I18n from '../i18n';
import { useTheme } from '../theme'; import { useTheme } from '../theme';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { themes } from '../lib/constants'; import { themes } from '../lib/constants';
import TextInput from '../presentation/TextInput'; import TextInput from './TextInput';
import { isIOS, isTablet } from '../utils/deviceInfo'; import { isIOS, isTablet } from '../utils/deviceInfo';
import { useOrientation } from '../dimensions'; import { useOrientation } from '../dimensions';
import { testProps } from '../lib/methods/testProps'; import { testProps } from '../lib/methods/testProps';

View File

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux';
import { IApplicationState, TUserStatus } from '../../definitions'; import { TUserStatus } from '../../definitions';
import Status from './Status'; import Status from './Status';
import { IStatus } from './definition'; import { IStatus } from './definition';
import { useAppSelector } from '../../lib/hooks';
const StatusContainer = ({ id, style, size = 32, ...props }: Omit<IStatus, 'status'>): React.ReactElement => { 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' state.meteor.connected ? state.activeUsers[id] && state.activeUsers[id].status : 'loading'
) as TUserStatus; ) as TUserStatus;
return <Status size={size} style={style} status={status} {...props} />; return <Status size={size} style={style} status={status} {...props} />;

View File

@ -2,13 +2,13 @@ import React from 'react';
import { StyleProp, StyleSheet, Text, TextInputProps, TextInput as RNTextInput, TextStyle, View, ViewStyle } from 'react-native'; import { StyleProp, StyleSheet, Text, TextInputProps, TextInput as RNTextInput, TextStyle, View, ViewStyle } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import sharedStyles from '../views/Styles'; import sharedStyles from '../../views/Styles';
import TextInput from '../presentation/TextInput'; import TextInput from './index';
import { themes } from '../lib/constants'; import { themes } from '../../lib/constants';
import { CustomIcon, TIconsName } from './CustomIcon'; import { CustomIcon, TIconsName } from '../CustomIcon';
import ActivityIndicator from './ActivityIndicator'; import ActivityIndicator from '../ActivityIndicator';
import { testProps } from '../lib/methods/testProps'; import { TSupportedThemes } from '../../theme';
import { TSupportedThemes } from '../theme'; import { testProps } from '../../lib/methods/testProps';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
error: { error: {
@ -71,7 +71,7 @@ interface IRCTextInputState {
showPassword: boolean; showPassword: boolean;
} }
export default class RCTextInput extends React.PureComponent<IRCTextInputProps, IRCTextInputState> { export default class FormTextInput extends React.PureComponent<IRCTextInputProps, IRCTextInputState> {
static defaultProps = { static defaultProps = {
error: {}, error: {},
theme: 'light' theme: 'light'

View File

@ -3,7 +3,7 @@ import React from 'react';
import { storiesOf } from '@storybook/react-native'; import { storiesOf } from '@storybook/react-native';
import { View, StyleSheet } from 'react-native'; import { View, StyleSheet } from 'react-native';
import TextInput from './TextInput'; import FormTextInput from './FormTextInput';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
paddingHorizontal: { paddingHorizontal: {
@ -23,9 +23,9 @@ const theme = 'light';
stories.add('Short and Long Text', () => ( stories.add('Short and Long Text', () => (
<> <>
<View style={styles.paddingHorizontal}> <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> </View>
</> </>
)); ));

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { I18nManager, StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native'; import { I18nManager, StyleProp, StyleSheet, TextInput, TextStyle } from 'react-native';
import { IRCTextInputProps } from '../containers/TextInput'; import { IRCTextInputProps } from './FormTextInput';
import { themes } from '../lib/constants'; import { themes } from '../../lib/constants';
import { testProps } from '../lib/methods/testProps'; import { TSupportedThemes } from '../../theme';
import { TSupportedThemes } from '../theme'; import { testProps } from '../../lib/methods/testProps';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
input: { input: {

View File

@ -6,7 +6,7 @@ import Modal from 'react-native-modal';
import useDeepCompareEffect from 'use-deep-compare-effect'; import useDeepCompareEffect from 'use-deep-compare-effect';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import TextInput from '../TextInput'; import FormTextInput from '../TextInput/FormTextInput';
import I18n from '../../i18n'; import I18n from '../../i18n';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import { useTheme } from '../../theme'; 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> <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} {method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
<TextInput <FormTextInput
value={code} value={code}
theme={theme} theme={theme}
inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())} inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
@ -139,16 +139,8 @@ const TwoFactor = React.memo(({ isMasterDetail }: { isMasterDetail: boolean }) =
backgroundColor={themes[theme].chatComponentBackground} backgroundColor={themes[theme].chatComponentBackground}
style={styles.button} style={styles.button}
onPress={onCancel} 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> </View>
</View> </View>

View File

@ -4,10 +4,8 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import Button from '../Button'; import Button from '../Button';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { IActions } from './interfaces'; import { IActions } from './interfaces';
import { useTheme } from '../../theme';
export const Actions = ({ blockId, appId, elements, parser }: IActions) => { export const Actions = ({ blockId, appId, elements, parser }: IActions) => {
const { theme } = useTheme();
const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5); const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5);
const renderedElements = showMoreVisible ? elements?.slice(0, 5) : elements; const renderedElements = showMoreVisible ? elements?.slice(0, 5) : elements;
@ -18,7 +16,7 @@ export const Actions = ({ blockId, appId, elements, parser }: IActions) => {
return ( return (
<> <>
<Elements /> <Elements />
{showMoreVisible && <Button theme={theme} title={I18n.t('Show_more')} onPress={() => setShowMoreVisible(false)} />} {showMoreVisible && <Button title={I18n.t('Show_more')} onPress={() => setShowMoreVisible(false)} />}
</> </>
); );
}; };

View File

@ -56,9 +56,7 @@ export const DatePicker = ({ element, language, action, context, loading, value,
} }
}; };
let button = placeholder ? ( let button = placeholder ? <Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} /> : null;
<Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} theme={theme} />
) : null;
if (context === BLOCK_CONTEXT.FORM) { if (context === BLOCK_CONTEXT.FORM) {
button = ( button = (

View File

@ -13,7 +13,7 @@ import {
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import Button from '../../Button'; import Button from '../../Button';
import TextInput from '../../TextInput'; import FormTextInput from '../../TextInput/FormTextInput';
import { textParser } from '../utils'; import { textParser } from '../utils';
import { themes } from '../../../lib/constants'; import { themes } from '../../../lib/constants';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
@ -139,7 +139,7 @@ export const MultiSelect = React.memo(
return ( return (
<View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}> <View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}>
<View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}> <View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}>
<TextInput <FormTextInput
testID='multi-select-search' testID='multi-select-search'
onChangeText={onSearch || onSearchChange} onChangeText={onSearch || onSearchChange}
placeholder={I18n.t('Search')} placeholder={I18n.t('Search')}
@ -157,7 +157,7 @@ export const MultiSelect = React.memo(
}); });
let button = multiselect ? ( let button = multiselect ? (
<Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} theme={theme} /> <Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} />
) : ( ) : (
<Input <Input
onPress={onShow} onPress={onShow}

View File

@ -5,7 +5,7 @@ import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKi
import Markdown, { MarkdownPreview } from '../markdown'; import Markdown, { MarkdownPreview } from '../markdown';
import Button from '../Button'; import Button from '../Button';
import TextInput from '../TextInput'; import FormTextInput from '../TextInput/FormTextInput';
import { textParser, useBlockContext } from './utils'; import { textParser, useBlockContext } from './utils';
import { themes } from '../../lib/constants'; import { themes } from '../../lib/constants';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
@ -63,7 +63,6 @@ class MessageParser extends UiKitParserMessage {
button(element: IButton, context: BlockContext) { button(element: IButton, context: BlockContext) {
const { text, value, actionId, style } = element; const { text, value, actionId, style } = element;
const [{ loading }, action] = useBlockContext(element, context); const [{ loading }, action] = useBlockContext(element, context);
const { theme } = useContext(ThemeContext);
return ( return (
<Button <Button
key={actionId} key={actionId}
@ -72,7 +71,6 @@ class MessageParser extends UiKitParserMessage {
loading={loading} loading={loading}
onPress={() => action({ value })} onPress={() => action({ value })}
style={styles.button} style={styles.button}
theme={theme}
/> />
); );
} }
@ -171,7 +169,7 @@ class ModalParser extends UiKitParserModal {
const { theme } = useContext(ThemeContext); const { theme } = useContext(ThemeContext);
const { multiline, actionId, placeholder } = element; const { multiline, actionId, placeholder } = element;
return ( return (
<TextInput <FormTextInput
key={actionId} key={actionId}
placeholder={plainText(placeholder)} placeholder={plainText(placeholder)}
multiline={multiline} multiline={multiline}

View File

@ -1,10 +1,9 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Image, StyleProp, Text, TextStyle } from 'react-native'; import { Image, StyleProp, Text, TextStyle } from 'react-native';
import { Node, Parser } from 'commonmark'; import { Parser } from 'commonmark';
import Renderer from 'commonmark-react-renderer'; import Renderer from 'commonmark-react-renderer';
import { MarkdownAST } from '@rocket.chat/message-parser'; import { MarkdownAST } from '@rocket.chat/message-parser';
import I18n from '../../i18n';
import MarkdownLink from './Link'; import MarkdownLink from './Link';
import MarkdownList from './List'; import MarkdownList from './List';
import MarkdownListItem from './ListItem'; import MarkdownListItem from './ListItem';
@ -37,7 +36,6 @@ interface IMarkdownProps {
baseUrl?: string; baseUrl?: string;
username?: string; username?: string;
tmid?: string; tmid?: string;
isEdited?: boolean;
numberOfLines?: number; numberOfLines?: number;
customEmojis?: boolean; customEmojis?: boolean;
useRealName?: boolean; useRealName?: boolean;
@ -133,9 +131,7 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
table: this.renderTable, table: this.renderTable,
table_row: this.renderTableRow, table_row: this.renderTableRow,
table_cell: this.renderTableCell, table_cell: this.renderTableCell
editedIndicator: this.renderEditedIndicator
}, },
renderParagraphsInLists: true renderParagraphsInLists: true
}); });
@ -145,21 +141,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
return !!enableMessageParser && !!md; 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 }) => { renderText = ({ context, literal }: { context: []; literal: string }) => {
const { numberOfLines, style = [] } = this.props; const { numberOfLines, style = [] } = this.props;
const defaultStyle = [this.isMessageContainsOnlyEmoji ? styles.textBig : {}, ...context.map(type => styles[type])]; 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) }} />; 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) => { renderHeading = ({ children, level }: any) => {
const { numberOfLines, theme } = this.props; const { numberOfLines, theme } = this.props;
// @ts-ignore // @ts-ignore
@ -373,7 +349,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
let ast = parser.parse(m); let ast = parser.parse(m);
ast = mergeTextNodes(ast); ast = mergeTextNodes(ast);
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3; this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
this.editedMessage(ast);
return this.renderer.render(ast); return this.renderer.render(ast);
} }
} }

View File

@ -94,10 +94,6 @@ export default StyleSheet.create({
fontSize: 16, fontSize: 16,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
edited: {
fontSize: 14,
...sharedStyles.textRegular
},
heading1: { heading1: {
...sharedStyles.textBold, ...sharedStyles.textBold,
fontSize: 24 fontSize: 24

View File

@ -43,7 +43,7 @@ const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
}; };
if (element.type === 'button') { if (element.type === 'button') {
return <Button theme={theme} onPress={onPress} title={element.text} />; return <Button onPress={onPress} title={element.text} />;
} }
return null; return null;

View File

@ -59,7 +59,6 @@ const Content = React.memo(
getCustomEmoji={props.getCustomEmoji} getCustomEmoji={props.getCustomEmoji}
enableMessageParser={user.enableMessageParserEarlyAdoption} enableMessageParser={user.enableMessageParserEarlyAdoption}
username={user.username} username={user.username}
isEdited={props.isEdited}
channels={props.channels} channels={props.channels}
mentions={props.mentions} mentions={props.mentions}
navToRoomInfo={props.navToRoomInfo} navToRoomInfo={props.navToRoomInfo}
@ -72,7 +71,7 @@ const Content = React.memo(
} }
// If this is a encrypted message and is not a preview // 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 = ( content = (
<View style={styles.flex}> <View style={styles.flex}>
<View style={styles.contentContainer}>{content}</View> <View style={styles.contentContainer}>{content}</View>

View File

@ -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;

View File

@ -17,7 +17,7 @@ const Encrypted = React.memo(({ type }: { type: string }) => {
} }
return ( 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} /> <CustomIcon name='encrypted' size={16} color={themes[theme].auxiliaryText} />
</Touchable> </Touchable>
); );

View File

@ -20,6 +20,8 @@ import CallButton from './CallButton';
import { themes } from '../../lib/constants'; import { themes } from '../../lib/constants';
import { IMessage, IMessageInner, IMessageTouchable } from './interfaces'; import { IMessage, IMessageInner, IMessageTouchable } from './interfaces';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import Edited from './Edited';
import MessageError from './MessageError';
const MessageInner = React.memo((props: IMessageInner) => { const MessageInner = React.memo((props: IMessageInner) => {
const { attachments } = props; const { attachments } = props;
@ -102,6 +104,12 @@ const Message = React.memo((props: IMessage) => {
<View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}> <View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}>
<MessageInner {...props} /> <MessageInner {...props} />
</View> </View>
{!props.isHeader ? (
<>
<Edited isEdited={props.isEdited} />
<MessageError hasError={props.hasError} />
</>
) : null}
<ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread || false} /> <ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread || false} />
</View> </View>
</View> </View>

View File

@ -18,8 +18,8 @@ const MessageError = React.memo(
} }
return ( return (
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}> <Touchable onPress={onErrorPress} style={styles.leftIcons} hitSlop={BUTTON_HIT_SLOP}>
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} /> <CustomIcon name='warning' color={themes[theme].dangerColor} size={16} />
</Touchable> </Touchable>
); );
}, },

View File

@ -8,7 +8,7 @@ import { useTheme } from '../../theme';
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread: boolean }) => { const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread: boolean }) => {
const { theme } = useTheme(); const { theme } = useTheme();
if (isReadReceiptEnabled && !unread && unread !== null) { 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; return null;
}); });

View File

@ -11,12 +11,12 @@ import sharedStyles from '../../views/Styles';
import { themes } from '../../lib/constants'; import { themes } from '../../lib/constants';
import MessageContext from './Context'; import MessageContext from './Context';
import { fileDownloadAndPreview } from '../../utils/fileDownload'; import { fileDownloadAndPreview } from '../../utils/fileDownload';
import { IAttachment } from '../../definitions/IAttachment'; import { IAttachment, TGetCustomEmoji } from '../../definitions';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import RCActivityIndicator from '../ActivityIndicator'; import RCActivityIndicator from '../ActivityIndicator';
import Attachments from './Attachments'; import Attachments from './Attachments';
import { TSupportedThemes, useTheme } from '../../theme'; import { TSupportedThemes, useTheme } from '../../theme';
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl'; import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
import messageStyles from './styles';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
button: { button: {
@ -44,15 +44,9 @@ const styles = StyleSheet.create({
marginBottom: 8 marginBottom: 8
}, },
author: { author: {
flex: 1,
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
time: {
fontSize: 12,
marginLeft: 8,
...sharedStyles.textRegular
},
fieldsContainer: { fieldsContainer: {
flex: 1, flex: 1,
flexWrap: 'wrap', flexWrap: 'wrap',
@ -106,8 +100,8 @@ const Title = React.memo(
{attachment.author_name ? ( {attachment.author_name ? (
<Text style={[styles.author, { color: themes[theme].auxiliaryTintColor }]}>{attachment.author_name}</Text> <Text style={[styles.author, { color: themes[theme].auxiliaryTintColor }]}>{attachment.author_name}</Text>
) : null} ) : 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} {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> </View>
); );
} }

View File

@ -11,6 +11,8 @@ import MessageContext from './Context';
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils'; import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils';
import { SubscriptionType } from '../../definitions'; import { SubscriptionType } from '../../definitions';
import { IRoomInfoParam } from '../../views/SearchMessagesView'; import { IRoomInfoParam } from '../../views/SearchMessagesView';
import Edited from './Edited';
import Encrypted from './Encrypted';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -19,7 +21,12 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center' alignItems: 'center'
}, },
actionIcons: {
flexDirection: 'row',
alignItems: 'center'
},
username: { username: {
flexShrink: 1,
fontSize: 16, fontSize: 16,
lineHeight: 22, lineHeight: 22,
...sharedStyles.textMedium ...sharedStyles.textMedium
@ -29,7 +36,6 @@ const styles = StyleSheet.create({
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
titleContainer: { titleContainer: {
flexShrink: 1,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'center'
}, },
@ -41,7 +47,7 @@ const styles = StyleSheet.create({
interface IMessageUser { interface IMessageUser {
isHeader?: boolean; isHeader?: boolean;
hasError?: boolean; hasError: boolean;
useRealName?: boolean; useRealName?: boolean;
author?: { author?: {
_id: string; _id: string;
@ -53,10 +59,11 @@ interface IMessageUser {
timeFormat?: string; timeFormat?: string;
navToRoomInfo?: (navParam: IRoomInfoParam) => void; navToRoomInfo?: (navParam: IRoomInfoParam) => void;
type: string; type: string;
isEdited: boolean;
} }
const User = React.memo( 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 { user } = useContext(MessageContext);
const { theme } = useTheme(); const { theme } = useTheme();
@ -99,9 +106,13 @@ const User = React.memo(
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}> <Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
{textContent} {textContent}
</Text> </Text>
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</TouchableOpacity> </TouchableOpacity>
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text> <View style={styles.actionIcons}>
{hasError ? <MessageError hasError={hasError} {...props} /> : null} <Encrypted type={type} />
<Edited isEdited={isEdited} />
<MessageError hasError={hasError} {...props} />
</View>
</View> </View>
); );
} }

View File

@ -256,7 +256,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
get isInfo(): string | boolean { get isInfo(): string | boolean {
const { item } = this.props; const { item } = this.props;
if (['e2e', 'discussion-created'].includes(item.t)) { if (['e2e', 'discussion-created', 'jitsi_call_started'].includes(item.t)) {
return false; return false;
} }
return item.t; return item.t;

View File

@ -60,6 +60,8 @@ export interface IMessageContent {
isIgnored: boolean; isIgnored: boolean;
type: string; type: string;
comment?: string; comment?: string;
hasError: boolean;
isHeader: boolean;
} }
export interface IMessageEmoji { export interface IMessageEmoji {

View File

@ -74,10 +74,6 @@ export default StyleSheet.create({
avatarSmall: { avatarSmall: {
marginLeft: 16 marginLeft: 16
}, },
errorButton: {
paddingLeft: 10,
paddingVertical: 5
},
buttonContainer: { buttonContainer: {
marginTop: 8, marginTop: 8,
flexDirection: 'row', flexDirection: 'row',
@ -133,7 +129,7 @@ export default StyleSheet.create({
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
time: { time: {
fontSize: 12, fontSize: 13,
marginLeft: 8, marginLeft: 8,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
@ -167,12 +163,13 @@ export default StyleSheet.create({
threadBell: { threadBell: {
marginLeft: 8 marginLeft: 8
}, },
leftIcons: {
paddingLeft: 5,
paddingVertical: 5
},
readReceipt: { readReceipt: {
lineHeight: 20 lineHeight: 20
}, },
encrypted: {
justifyContent: 'center'
},
threadDetails: { threadDetails: {
flex: 1, flex: 1,
marginLeft: 12 marginLeft: 12

View File

@ -56,3 +56,14 @@ export interface IServerAttachment {
url: string; url: string;
user: Pick<IUser, '_id' | 'username' | 'name'>; 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;
}

View File

@ -18,4 +18,7 @@ export interface ICredentials {
fullName?: AppleAuthenticationFullName | null; fullName?: AppleAuthenticationFullName | null;
email?: string | null; email?: string | null;
identityToken?: string | null; identityToken?: string | null;
credentialToken?: string;
saml?: boolean;
cas?: { credentialToken?: string };
} }

View File

@ -22,7 +22,7 @@ export interface IVisitorEmail {
export interface ILivechatVisitor extends IRocketChatRecord { export interface ILivechatVisitor extends IRocketChatRecord {
username: string; username: string;
ts: Date; ts: Date | string;
token: string; token: string;
department?: string; department?: string;
name?: string; name?: string;

View File

@ -89,11 +89,7 @@ export interface IMessageFromServer {
drid?: string; drid?: string;
dcount?: number; dcount?: number;
dml: string | Date; dml: string | Date;
starred?: starred?: boolean;
| {
_id: string;
}
| boolean;
pinned?: boolean; pinned?: boolean;
pinnedAt?: string | Date; pinnedAt?: string | Date;
pinnedBy?: { pinnedBy?: {
@ -120,11 +116,6 @@ export interface IMessage extends IMessageFromServer {
emoji?: string; emoji?: string;
status?: number; status?: number;
pinned?: boolean; pinned?: boolean;
starred?:
| {
_id: string;
}
| boolean;
editedBy?: IEditedBy; editedBy?: IEditedBy;
reactions?: IReaction[]; reactions?: IReaction[];
role?: string; role?: string;
@ -144,6 +135,8 @@ export interface IMessage extends IMessageFromServer {
tshow?: boolean; tshow?: boolean;
comment?: string; comment?: string;
subscription?: { id: string }; subscription?: { id: string };
user?: string;
editedAt?: string | Date;
} }
export type TMessageModel = IMessage & Model; export type TMessageModel = IMessage & Model;

View File

@ -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;
}

View File

@ -59,6 +59,20 @@ export interface IRoom {
waitingResponse?: boolean; 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 { export enum OmnichannelSourceType {
WIDGET = 'widget', WIDGET = 'widget',
EMAIL = 'email', EMAIL = 'email',

View File

@ -22,7 +22,6 @@ export * from './IUser';
export * from './IServer'; export * from './IServer';
export * from './ILoggedUser'; export * from './ILoggedUser';
export * from './IServerHistory'; export * from './IServerHistory';
export * from './IRocketChat';
export * from './ICertificate'; export * from './ICertificate';
export * from './IUrl'; export * from './IUrl';
export * from './ICredentials'; export * from './ICredentials';

1
app/lib/hooks/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './useAppSelector';

View File

@ -0,0 +1,5 @@
import { TypedUseSelectorHook, useSelector } from 'react-redux';
import { IApplicationState } from '../../definitions';
export const useAppSelector: TypedUseSelectorHook<IApplicationState> = useSelector;

View File

@ -6,20 +6,24 @@ import { store as reduxStore } from '../store/auxStore';
import { setActiveUsers } from '../../actions/activeUsers'; import { setActiveUsers } from '../../actions/activeUsers';
import { setUser } from '../../actions/login'; import { setUser } from '../../actions/login';
import database from '../database'; import database from '../database';
import { IRocketChat, IUser } from '../../definitions'; import { IUser } from '../../definitions';
import sdk from '../services/sdk'; import sdk from '../services/sdk';
import { compareServerVersion } from './helpers/compareServerVersion'; 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; const serverVersion = reduxStore.getState().server.version as string;
// if server is lower than 1.1.0 // if server is lower than 1.1.0
if (compareServerVersion(serverVersion, 'lowerThan', '1.1.0')) { if (compareServerVersion(serverVersion, 'lowerThan', '1.1.0')) {
if (this.activeUsersSubTimeout) { if (_activeUsersSubTimeout.activeUsersSubTimeout) {
clearTimeout(this.activeUsersSubTimeout); clearTimeout(_activeUsersSubTimeout.activeUsersSubTimeout as number);
this.activeUsersSubTimeout = false; _activeUsersSubTimeout.activeUsersSubTimeout = false;
} }
this.activeUsersSubTimeout = setTimeout(() => { _activeUsersSubTimeout.activeUsersSubTimeout = setTimeout(() => {
sdk.subscribe('activeUsers'); sdk.subscribe('activeUsers');
}, 5000); }, 5000);
} else if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) { } else if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) {

View File

@ -0,0 +1,4 @@
import { LISTENER } from '../../../containers/Toast';
import EventEmitter from '../../../utils/events';
export const showToast = (message: string): void => EventEmitter.emit(LISTENER, { message });

View File

@ -34,3 +34,4 @@ export * from './userPreferences';
export * from './userPreferencesMethods'; export * from './userPreferencesMethods';
export * from './crashReport'; export * from './crashReport';
export * from './parseSettings'; export * from './parseSettings';
export * from './subscribeRooms';

View File

@ -8,11 +8,13 @@ import { BASIC_AUTH_KEY } from '../../utils/fetch';
import database, { getDatabase } from '../database'; import database, { getDatabase } from '../database';
import { isSsl } from '../../utils/url'; import { isSsl } from '../../utils/url';
import log from '../../utils/log'; import log from '../../utils/log';
import { ICertificate, IRocketChat } from '../../definitions'; import { ICertificate } from '../../definitions';
import sdk from '../services/sdk'; import sdk from '../services/sdk';
import { CURRENT_SERVER, E2E_PRIVATE_KEY, E2E_PUBLIC_KEY, E2E_RANDOM_PASSWORD_KEY, TOKEN_KEY } from '../constants'; import { CURRENT_SERVER, E2E_PRIVATE_KEY, E2E_PUBLIC_KEY, E2E_RANDOM_PASSWORD_KEY, TOKEN_KEY } from '../constants';
import UserPreferences from './userPreferences'; import UserPreferences from './userPreferences';
import { Services } from '../services'; import { Services } from '../services';
import { roomsSubscription } from './subscriptions/rooms';
import { _activeUsersSubTimeout } from '.';
function removeServerKeys({ server, userId }: { server: string; userId?: string | null }) { function removeServerKeys({ server, userId }: { server: string; userId?: string | null }) {
UserPreferences.removeItem(`${TOKEN_KEY}-${server}`); 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> { export async function logout({ server }: { server: string }): Promise<void> {
if (this.roomsSub) { if (roomsSubscription?.stop) {
this.roomsSub.stop(); roomsSubscription.stop();
this.roomsSub = null;
} }
if (this.activeUsersSubTimeout) { if (_activeUsersSubTimeout.activeUsersSubTimeout) {
clearTimeout(this.activeUsersSubTimeout); clearTimeout(_activeUsersSubTimeout.activeUsersSubTimeout as number);
this.activeUsersSubTimeout = false; _activeUsersSubTimeout.activeUsersSubTimeout = false;
} }
try { try {

View File

@ -2,16 +2,35 @@ import { InteractionManager } from 'react-native';
import { setActiveUsers } from '../../actions/activeUsers'; import { setActiveUsers } from '../../actions/activeUsers';
import { setUser } from '../../actions/login'; import { setUser } from '../../actions/login';
import { IUser } from '../../definitions';
import { store as reduxStore } from '../store/auxStore'; import { store as reduxStore } from '../store/auxStore';
import { compareServerVersion } from './helpers/compareServerVersion'; import { compareServerVersion } from './helpers/compareServerVersion';
// TODO export interface IActiveUsers {
export function _setUser(this: any, ddpMessage: { fields: any; id: any; cleared: any }) { [key: string]: { status: string; statusText?: string } | string | boolean;
this.activeUsers = this.activeUsers || {}; 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; const { user } = reduxStore.getState().login;
if (ddpMessage.fields && user && user.id === ddpMessage.id) { 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) { 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; const serverVersion = reduxStore.getState().server.version;
if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) { if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) {
if (!this._setUserTimer) { if (!_setUserTimer.setUserTimer) {
this._setUserTimer = setTimeout(() => { _setUserTimer.setUserTimer = setTimeout(() => {
const activeUsersBatch = this.activeUsers; const activeUsersBatch = _activeUsers.activeUsers;
InteractionManager.runAfterInteractions(() => { InteractionManager.runAfterInteractions(() => {
// @ts-ignore
reduxStore.dispatch(setActiveUsers(activeUsersBatch)); reduxStore.dispatch(setActiveUsers(activeUsersBatch));
}); });
this._setUserTimer = null; _setUserTimer.setUserTimer = null;
return (this.activeUsers = {}); _activeUsers.activeUsers = {} as IActiveUsers;
}, 10000); }, 10000);
} }
} }
if (!ddpMessage.fields) { if (!ddpMessage.fields) {
this.activeUsers[ddpMessage.id] = { status: 'offline' }; _activeUsers.activeUsers[ddpMessage.id] = { status: 'offline' };
} else if (ddpMessage.fields.status) { } else if (ddpMessage.fields.status) {
this.activeUsers[ddpMessage.id] = { status: ddpMessage.fields.status }; _activeUsers.activeUsers[ddpMessage.id] = { status: ddpMessage.fields.status };
} }
} }

View File

@ -1,22 +1,18 @@
import log from '../../utils/log'; import log from '../../utils/log';
import subscribeRoomsTmp from './subscriptions/rooms'; import subscribeRoomsTmp, { roomsSubscription } from './subscriptions/rooms';
// TODO: remove this export async function subscribeRooms(): Promise<void> {
export async function subscribeRooms(this: any) { if (!roomsSubscription?.stop) {
if (!this.roomsSub) {
try { try {
// TODO: We need to change this naming. Maybe move this logic to the SDK? await subscribeRoomsTmp();
this.roomsSub = await subscribeRoomsTmp.call(this);
} catch (e) { } catch (e) {
log(e); log(e);
} }
} }
} }
// TODO: remove this export function unsubscribeRooms(): void {
export function unsubscribeRooms(this: any) { if (roomsSubscription?.stop) {
if (this.roomsSub) { roomsSubscription.stop();
this.roomsSub.stop();
this.roomsSub = null;
} }
} }

View File

@ -44,6 +44,8 @@ let queue: { [key: string]: ISubscription | IRoom } = {};
let subTimer: number | null | false = null; let subTimer: number | null | false = null;
const WINDOW_TIME = 500; const WINDOW_TIME = 500;
export let roomsSubscription: { stop: () => void } | null = null;
const createOrUpdateSubscription = async (subscription: ISubscription, room: IServerRoom | IRoom) => { const createOrUpdateSubscription = async (subscription: ISubscription, room: IServerRoom | IRoom) => {
try { try {
const db = database.active; const db = database.active;
@ -404,6 +406,7 @@ export default function subscribeRooms() {
clearTimeout(subTimer); clearTimeout(subTimer);
subTimer = false; subTimer = false;
} }
roomsSubscription = null;
}; };
streamListener = sdk.onStreamData('stream-notify-user', handleStreamMessageReceived); streamListener = sdk.onStreamData('stream-notify-user', handleStreamMessageReceived);
@ -412,10 +415,8 @@ export default function subscribeRooms() {
// set the server that started this task // set the server that started this task
subServer = sdk.current.client.host; subServer = sdk.current.client.host;
sdk.current.subscribeNotifyUser().catch((e: unknown) => console.log(e)); sdk.current.subscribeNotifyUser().catch((e: unknown) => console.log(e));
roomsSubscription = { stop: () => stop() };
return { return null;
stop: () => stop()
};
} catch (e) { } catch (e) {
log(e); log(e);
return Promise.reject(); return Promise.reject();

View File

@ -1,13 +1,13 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { IApplicationState, TServerModel } from '../../definitions'; import { IApplicationState, TServerModel } from '../../definitions';
import database from '../database'; import database from '../database';
import { useAppSelector } from '../hooks';
export default function useServer() { export default function useServer() {
const [server, setServer] = useState<TServerModel | null>(null); const [server, setServer] = useState<TServerModel | null>(null);
const shareServer = useSelector((state: IApplicationState) => state.share.server.server); const shareServer = useAppSelector((state: IApplicationState) => state.share.server.server);
const appServer = useSelector((state: IApplicationState) => state.server.server); const appServer = useAppSelector((state: IApplicationState) => state.server.server);
useEffect(() => { useEffect(() => {
async function init() { async function init() {

View File

@ -3,7 +3,7 @@ import EJSON from 'ejson';
import { store } from '../store/auxStore'; import { store } from '../store/auxStore';
import { deepLinkingOpen } from '../../actions/deepLinking'; import { deepLinkingOpen } from '../../actions/deepLinking';
import { isFDroidBuild } from '../constants'; import { isFDroidBuild } from '../constants';
import PushNotification from './push'; import { deviceToken, pushNotificationConfigure, setNotificationsBadgeCount } from './push';
import { INotification, SubscriptionType } from '../../definitions'; import { INotification, SubscriptionType } from '../../definitions';
interface IEjson { interface IEjson {
@ -47,11 +47,11 @@ export const onNotification = (push: INotification): void => {
} }
}; };
export const getDeviceToken = (): string => PushNotification.getDeviceToken(); export const getDeviceToken = (): string => deviceToken;
export const setBadgeCount = (count?: number): void => PushNotification.setBadgeCount(count); export const setBadgeCount = (count?: number): void => setNotificationsBadgeCount(count);
export const initializePushNotifications = (): Promise<INotification> | undefined => { export const initializePushNotifications = (): Promise<INotification> | undefined => {
if (!isFDroidBuild) { if (!isFDroidBuild) {
setBadgeCount(); setBadgeCount();
return PushNotification.configure(onNotification); return pushNotificationConfigure(onNotification);
} }
}; };

View File

@ -13,16 +13,18 @@ import { isIOS } from '../../utils/deviceInfo';
import { store as reduxStore } from '../store/auxStore'; import { store as reduxStore } from '../store/auxStore';
import I18n from '../../i18n'; import I18n from '../../i18n';
class PushNotification { export let deviceToken = '';
onNotification: (notification: any) => void;
deviceToken: string; export const setNotificationsBadgeCount = (count = 0): void => {
constructor() { if (isIOS) {
this.onNotification = () => {}; Notifications.ios.setBadgeCount(count);
this.deviceToken = ''; }
};
export const pushNotificationConfigure = (onNotification: (notification: INotification) => void): Promise<any> => {
if (isIOS) { if (isIOS) {
// init // init
Notifications.ios.registerRemoteNotifications(); Notifications.ios.registerRemoteNotifications();
// setCategories // setCategories
const notificationAction = new NotificationAction('REPLY_ACTION', 'background', I18n.t('Reply'), true, { const notificationAction = new NotificationAction('REPLY_ACTION', 'background', I18n.t('Reply'), true, {
buttonTitle: I18n.t('Reply'), buttonTitle: I18n.t('Reply'),
@ -36,7 +38,7 @@ class PushNotification {
} }
Notifications.events().registerRemoteNotificationsRegistered((event: Registered) => { Notifications.events().registerRemoteNotificationsRegistered((event: Registered) => {
this.deviceToken = event.deviceToken; deviceToken = event.deviceToken;
}); });
Notifications.events().registerRemoteNotificationsRegistrationFailed((event: RegistrationError) => { Notifications.events().registerRemoteNotificationsRegistrationFailed((event: RegistrationError) => {
@ -54,10 +56,10 @@ class PushNotification {
if (isIOS) { if (isIOS) {
const { background } = reduxStore.getState().app; const { background } = reduxStore.getState().app;
if (background) { if (background) {
this.onNotification(notification); onNotification(notification);
} }
} else { } else {
this.onNotification(notification); onNotification(notification);
} }
completion(); completion();
}); });
@ -67,22 +69,6 @@ class PushNotification {
completion({ alert: true, sound: true, badge: false }); completion({ alert: true, sound: true, badge: false });
} }
); );
}
getDeviceToken() {
return this.deviceToken;
}
setBadgeCount = (count = 0) => {
if (isIOS) {
Notifications.ios.setBadgeCount(count);
}
};
configure(onNotification: (notification: INotification) => void): Promise<any> {
this.onNotification = onNotification;
return Notifications.getInitialNotification(); return Notifications.getInitialNotification();
} };
}
export default new PushNotification();

View File

@ -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;

View File

@ -1,5 +1,5 @@
import RNFetchBlob from 'rn-fetch-blob'; 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 { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { InteractionManager } from 'react-native'; import { InteractionManager } from 'react-native';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
@ -14,8 +14,7 @@ import { store } from '../store/auxStore';
import { loginRequest, setLoginServices, setUser } from '../../actions/login'; import { loginRequest, setLoginServices, setUser } from '../../actions/login';
import sdk from './sdk'; import sdk from './sdk';
import I18n from '../../i18n'; import I18n from '../../i18n';
import RocketChat from '../rocketchat'; import { ICredentials, ILoggedUser, STATUSES } from '../../definitions';
import { ICredentials, ILoggedUser, IRocketChat, STATUSES } from '../../definitions';
import { isIOS } from '../../utils/deviceInfo'; import { isIOS } from '../../utils/deviceInfo';
import { connectRequest, connectSuccess, disconnect as disconnectAction } from '../../actions/connect'; import { connectRequest, connectSuccess, disconnect as disconnectAction } from '../../actions/connect';
import { updatePermission } from '../../actions/permissions'; import { updatePermission } from '../../actions/permissions';
@ -24,7 +23,8 @@ import { updateSettings } from '../../actions/settings';
import { defaultSettings, MIN_ROCKETCHAT_VERSION } from '../constants'; import { defaultSettings, MIN_ROCKETCHAT_VERSION } from '../constants';
import { compareServerVersion } from '../methods/helpers/compareServerVersion'; import { compareServerVersion } from '../methods/helpers/compareServerVersion';
import { onRolesChanged } from '../methods/getRoles'; 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 { interface IServices {
[index: string]: string | boolean; [index: string]: string | boolean;
@ -35,11 +35,15 @@ interface IServices {
service: string; service: string;
} }
// FIXME: Remove `this` context let connectingListener: any;
function connect( let connectedListener: any;
this: IRocketChat, let closeListener: any;
{ server, logoutOnError = false }: { server: string; logoutOnError: boolean } let usersListener: any;
): Promise<void> { 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 => { return new Promise<void>(resolve => {
if (sdk.current?.client?.host === server) { if (sdk.current?.client?.host === server) {
return resolve(); return resolve();
@ -49,39 +53,40 @@ function connect(
store.dispatch(connectRequest()); store.dispatch(connectRequest());
if (this.connectTimeout) { // It's not called anywhere else
clearTimeout(this.connectTimeout); // if (this.connectTimeout) {
// clearTimeout(this.connectTimeout);
// }
if (connectingListener) {
connectingListener.then(stopListener);
} }
if (this.connectingListener) { if (connectedListener) {
this.connectingListener.then(stopListener); connectedListener.then(stopListener);
} }
if (this.connectedListener) { if (closeListener) {
this.connectedListener.then(stopListener); closeListener.then(stopListener);
} }
if (this.closeListener) { if (usersListener) {
this.closeListener.then(stopListener); usersListener.then(stopListener);
} }
if (this.usersListener) { if (notifyAllListener) {
this.usersListener.then(stopListener); notifyAllListener.then(stopListener);
} }
if (this.notifyAllListener) { if (rolesListener) {
this.notifyAllListener.then(stopListener); rolesListener.then(stopListener);
} }
if (this.rolesListener) { if (notifyLoggedListener) {
this.rolesListener.then(stopListener); notifyLoggedListener.then(stopListener);
} }
if (this.notifyLoggedListener) { unsubscribeRooms();
this.notifyLoggedListener.then(stopListener);
}
this.unsubscribeRooms();
EventEmitter.emit('INQUIRY_UNSUBSCRIBE'); EventEmitter.emit('INQUIRY_UNSUBSCRIBE');
@ -97,11 +102,11 @@ function connect(
console.log('connect error', err); console.log('connect error', err);
}); });
this.connectingListener = sdk.current.onStreamData('connecting', () => { connectingListener = sdk.current.onStreamData('connecting', () => {
store.dispatch(connectRequest()); store.dispatch(connectRequest());
}); });
this.connectedListener = sdk.current.onStreamData('connected', () => { connectedListener = sdk.current.onStreamData('connected', () => {
const { connected } = store.getState().meteor; const { connected } = store.getState().meteor;
if (connected) { if (connected) {
return; return;
@ -113,16 +118,16 @@ function connect(
} }
}); });
this.closeListener = sdk.current.onStreamData('close', () => { closeListener = sdk.current.onStreamData('close', () => {
store.dispatch(disconnectAction()); store.dispatch(disconnectAction());
}); });
this.usersListener = sdk.current.onStreamData( usersListener = sdk.current.onStreamData(
'users', 'users',
protectedFunction((ddpMessage: any) => RocketChat._setUser(ddpMessage)) protectedFunction((ddpMessage: any) => _setUser(ddpMessage))
); );
this.notifyAllListener = sdk.current.onStreamData( notifyAllListener = sdk.current.onStreamData(
'stream-notify-all', 'stream-notify-all',
protectedFunction(async (ddpMessage: { fields: { args?: any; eventName: string } }) => { protectedFunction(async (ddpMessage: { fields: { args?: any; eventName: string } }) => {
const { eventName } = ddpMessage.fields; const { eventName } = ddpMessage.fields;
@ -150,7 +155,7 @@ function connect(
}) })
); );
this.rolesListener = sdk.current.onStreamData( rolesListener = sdk.current.onStreamData(
'stream-roles', 'stream-roles',
protectedFunction((ddpMessage: any) => onRolesChanged(ddpMessage)) protectedFunction((ddpMessage: any) => onRolesChanged(ddpMessage))
); );
@ -171,27 +176,29 @@ function connect(
} }
}); });
this.notifyLoggedListener = sdk.current.onStreamData( notifyLoggedListener = sdk.current.onStreamData(
'stream-notify-logged', 'stream-notify-logged',
protectedFunction(async (ddpMessage: { fields: { args?: any; eventName?: any } }) => { protectedFunction(async (ddpMessage: { fields: { args?: any; eventName?: any } }) => {
const { eventName } = ddpMessage.fields; const { eventName } = ddpMessage.fields;
// `user-status` event is deprecated after RC 4.1 in favor of `stream-user-presence/${uid}` // `user-status` event is deprecated after RC 4.1 in favor of `stream-user-presence/${uid}`
if (/user-status/.test(eventName)) { if (/user-status/.test(eventName)) {
this.activeUsers = this.activeUsers || {}; _activeUsers.activeUsers = _activeUsers.activeUsers || {};
if (!this._setUserTimer) { if (!_setUserTimer.setUserTimer) {
this._setUserTimer = setTimeout(() => { _setUserTimer.setUserTimer = setTimeout(() => {
const activeUsersBatch = this.activeUsers; const activeUsersBatch = _activeUsers.activeUsers;
InteractionManager.runAfterInteractions(() => { InteractionManager.runAfterInteractions(() => {
// @ts-ignore
store.dispatch(setActiveUsers(activeUsersBatch)); store.dispatch(setActiveUsers(activeUsersBatch));
}); });
this._setUserTimer = null; _setUserTimer.setUserTimer = null;
return (this.activeUsers = {}); _activeUsers.activeUsers = {} as IActiveUsers;
return null;
}, 10000); }, 10000);
} }
const userStatus = ddpMessage.fields.args[0]; const userStatus = ddpMessage.fields.args[0];
const [id, , status, statusText] = userStatus; 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; const { user: loggedUser } = store.getState().login;
if (loggedUser && loggedUser.id === id) { if (loggedUser && loggedUser.id === id) {
@ -419,10 +426,10 @@ async function getServerInfo(server: string) {
} }
async function getWebsocketInfo({ server }: { server: string }) { async function getWebsocketInfo({ server }: { server: string }) {
sdk.initialize(server); const websocketSdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: isSsl(server) });
try { try {
await sdk.current.connect(); await websocketSdk.connect();
} catch (err: any) { } catch (err: any) {
if (err.message && err.message.includes('400')) { if (err.message && err.message.includes('400')) {
return { return {
@ -432,7 +439,7 @@ async function getWebsocketInfo({ server }: { server: string }) {
} }
} }
sdk.disconnect(); websocketSdk.disconnect();
return { return {
success: true success: true

View File

@ -16,10 +16,9 @@ import { TParams } from '../../definitions/ILivechatEditView';
import { store as reduxStore } from '../store/auxStore'; import { store as reduxStore } from '../store/auxStore';
import { getDeviceToken } from '../notifications'; import { getDeviceToken } from '../notifications';
import { getBundleId, isIOS } from '../../utils/deviceInfo'; import { getBundleId, isIOS } from '../../utils/deviceInfo';
import { RoomTypes, roomTypeToApiType } from '../methods'; import { RoomTypes, roomTypeToApiType, unsubscribeRooms } from '../methods';
import sdk from './sdk'; import sdk from './sdk';
import { compareServerVersion } from '../methods/helpers/compareServerVersion'; import { compareServerVersion } from '../methods/helpers/compareServerVersion';
import RocketChat from '../rocketchat';
export const createChannel = ({ export const createChannel = ({
name, name,
@ -254,7 +253,7 @@ export const markAsUnread = ({ messageId }: { messageId: string }) =>
// RC 0.65.0 // RC 0.65.0
sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } }); sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
export const toggleStarMessage = (messageId: string, starred: boolean) => { export const toggleStarMessage = (messageId: string, starred?: boolean) => {
if (starred) { if (starred) {
// RC 0.59.0 // RC 0.59.0
return sdk.post('chat.unStarMessage', { messageId }); return sdk.post('chat.unStarMessage', { messageId });
@ -263,7 +262,7 @@ export const toggleStarMessage = (messageId: string, starred: boolean) => {
return sdk.post('chat.starMessage', { messageId }); return sdk.post('chat.starMessage', { messageId });
}; };
export const togglePinMessage = (messageId: string, pinned: boolean) => { export const togglePinMessage = (messageId: string, pinned?: boolean) => {
if (pinned) { if (pinned) {
// RC 0.59.0 // RC 0.59.0
return sdk.post('chat.unPinMessage', { messageId }); return sdk.post('chat.unPinMessage', { messageId });
@ -807,7 +806,7 @@ export const emitTyping = (room: IRoom, typing = true) => {
export function e2eResetOwnKey(): Promise<boolean | {}> { export function e2eResetOwnKey(): Promise<boolean | {}> {
// {} when TOTP is enabled // {} when TOTP is enabled
RocketChat.unsubscribeRooms(); unsubscribeRooms();
// RC 0.72.0 // RC 0.72.0
return sdk.methodCallWrapper('e2e.resetOwnE2EKey'); return sdk.methodCallWrapper('e2e.resetOwnE2EKey');

View File

@ -7,7 +7,6 @@ import { appStart } from '../actions/app';
import { selectServerRequest, serverFinishAdd } from '../actions/server'; import { selectServerRequest, serverFinishAdd } from '../actions/server';
import { loginFailure, loginSuccess, logout as logoutAction, setUser } from '../actions/login'; import { loginFailure, loginSuccess, logout as logoutAction, setUser } from '../actions/login';
import { roomsRequest } from '../actions/rooms'; import { roomsRequest } from '../actions/rooms';
import RocketChat from '../lib/rocketchat';
import log, { events, logEvent } from '../utils/log'; import log, { events, logEvent } from '../utils/log';
import I18n, { setLanguage } from '../i18n'; import I18n, { setLanguage } from '../i18n';
import database from '../lib/database'; import database from '../lib/database';
@ -30,14 +29,16 @@ import {
getSlashCommands, getSlashCommands,
getUserPresence, getUserPresence,
isOmnichannelModuleAvailable, isOmnichannelModuleAvailable,
subscribeSettings logout,
subscribeSettings,
subscribeUsersPresence
} from '../lib/methods'; } from '../lib/methods';
import { Services } from '../lib/services'; import { Services } from '../lib/services';
const getServer = state => state.server.server; const getServer = state => state.server.server;
const loginWithPasswordCall = args => Services.loginWithPassword(args); const loginWithPasswordCall = args => Services.loginWithPassword(args);
const loginCall = (credentials, isFromWebView) => Services.login(credentials, isFromWebView); 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 }) { const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnError = false, isFromWebView = false }) {
logEvent(events.LOGIN_DEFAULT_LOGIN); logEvent(events.LOGIN_DEFAULT_LOGIN);
@ -114,7 +115,7 @@ const registerPushTokenFork = function* registerPushTokenFork() {
}; };
const fetchUsersPresenceFork = function* fetchUsersPresenceFork() { const fetchUsersPresenceFork = function* fetchUsersPresenceFork() {
RocketChat.subscribeUsersPresence(); subscribeUsersPresence();
}; };
const fetchEnterpriseModulesFork = function* fetchEnterpriseModulesFork({ user }) { const fetchEnterpriseModulesFork = function* fetchEnterpriseModulesFork({ user }) {

View File

@ -7,9 +7,8 @@ import { roomsFailure, roomsRefresh, roomsSuccess } from '../actions/rooms';
import database from '../lib/database'; import database from '../lib/database';
import log from '../utils/log'; import log from '../utils/log';
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms'; import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
import RocketChat from '../lib/rocketchat';
import buildMessage from '../lib/methods/helpers/buildMessage'; 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 updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
const serversDB = database.servers; const serversDB = database.servers;
@ -30,7 +29,7 @@ const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
const handleRoomsRequest = function* handleRoomsRequest({ params }) { const handleRoomsRequest = function* handleRoomsRequest({ params }) {
try { try {
const serversDB = database.servers; const serversDB = database.servers;
RocketChat.subscribeRooms(); subscribeRooms();
const newRoomsUpdatedAt = new Date(); const newRoomsUpdatedAt = new Date();
let roomsUpdatedAt; let roomsUpdatedAt;
const server = yield select(state => state.server.server); const server = yield select(state => state.server.server);

View File

@ -11,7 +11,6 @@ import { selectServerFailure, selectServerRequest, selectServerSuccess, serverFa
import { clearSettings } from '../actions/settings'; import { clearSettings } from '../actions/settings';
import { clearUser, setUser } from '../actions/login'; import { clearUser, setUser } from '../actions/login';
import { clearActiveUsers } from '../actions/activeUsers'; import { clearActiveUsers } from '../actions/activeUsers';
import RocketChat from '../lib/rocketchat';
import database from '../lib/database'; import database from '../lib/database';
import log, { logServerVersion } from '../utils/log'; import log, { logServerVersion } from '../utils/log';
import I18n from '../i18n'; import I18n from '../i18n';
@ -25,6 +24,7 @@ import { RootEnum } from '../definitions';
import { CERTIFICATE_KEY, CURRENT_SERVER, TOKEN_KEY } from '../lib/constants'; import { CERTIFICATE_KEY, CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
import { getLoginSettings, setCustomEmojis, setEnterpriseModules, setPermissions, setRoles, setSettings } from '../lib/methods'; import { getLoginSettings, setCustomEmojis, setEnterpriseModules, setPermissions, setRoles, setSettings } from '../lib/methods';
import { Services } from '../lib/services'; import { Services } from '../lib/services';
import { connect } from '../lib/services/connect';
const getServerInfo = function* getServerInfo({ server, raiseError = true }) { const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
try { try {
@ -115,11 +115,11 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
if (user) { if (user) {
yield put(clearSettings()); yield put(clearSettings());
yield put(setUser(user)); yield put(setUser(user));
yield RocketChat.connect({ server, logoutOnError: true }); yield connect({ server, logoutOnError: true });
yield put(appStart({ root: RootEnum.ROOT_INSIDE })); yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
} else { } else {
yield put(clearUser()); yield put(clearUser());
yield RocketChat.connect({ server }); yield connect({ server });
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
} }

View File

@ -1,11 +1,11 @@
import { select, takeLatest } from 'redux-saga/effects'; import { select, takeLatest } from 'redux-saga/effects';
import Push from '../lib/notifications/push';
import log from '../utils/log'; import log from '../utils/log';
import { localAuthenticate, saveLastLocalAuthenticationSession } from '../utils/localAuthentication'; import { localAuthenticate, saveLastLocalAuthenticationSession } from '../utils/localAuthentication';
import { APP_STATE } from '../actions/actionsTypes'; import { APP_STATE } from '../actions/actionsTypes';
import { RootEnum } from '../definitions'; import { RootEnum } from '../definitions';
import { Services } from '../lib/services'; import { Services } from '../lib/services';
import { setBadgeCount } from '../lib/notifications';
const appHasComeBackToForeground = function* appHasComeBackToForeground() { const appHasComeBackToForeground = function* appHasComeBackToForeground() {
const appRoot = yield select(state => state.app.root); const appRoot = yield select(state => state.app.root);
@ -20,7 +20,7 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() {
try { try {
yield localAuthenticate(server.server); yield localAuthenticate(server.server);
Services.checkAndReopen(); Services.checkAndReopen();
Push.setBadgeCount(); setBadgeCount();
return yield Services.setUserPresenceOnline(); return yield Services.setUserPresenceOnline();
} catch (e) { } catch (e) {
log(e); log(e);

View File

@ -101,7 +101,7 @@ const ChatsStackNavigator = () => {
options={SearchMessagesView.navigationOptions} options={SearchMessagesView.navigationOptions}
/> />
<ChatsStack.Screen name='SelectedUsersView' component={SelectedUsersView} /> <ChatsStack.Screen name='SelectedUsersView' component={SelectedUsersView} />
<ChatsStack.Screen name='InviteUsersView' component={InviteUsersView} options={InviteUsersView.navigationOptions} /> <ChatsStack.Screen name='InviteUsersView' component={InviteUsersView} />
<ChatsStack.Screen <ChatsStack.Screen
name='InviteUsersEditView' name='InviteUsersEditView'
component={InviteUsersEditView} component={InviteUsersEditView}
@ -131,7 +131,7 @@ const ChatsStackNavigator = () => {
component={AddExistingChannelView} component={AddExistingChannelView}
options={AddExistingChannelView.navigationOptions} 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='ReadReceiptsView' component={ReadReceiptsView} options={ReadReceiptsView.navigationOptions} />
<ChatsStack.Screen name='QueueListView' component={QueueListView} options={QueueListView.navigationOptions} /> <ChatsStack.Screen name='QueueListView' component={QueueListView} options={QueueListView.navigationOptions} />
<ChatsStack.Screen name='CannedResponsesListView' component={CannedResponsesListView} /> <ChatsStack.Screen name='CannedResponsesListView' component={CannedResponsesListView} />
@ -198,7 +198,7 @@ const AdminPanelStackNavigator = () => {
return ( return (
<AdminPanelStack.Navigator <AdminPanelStack.Navigator
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions}> screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions}>
<AdminPanelStack.Screen name='AdminPanelView' component={AdminPanelView} options={AdminPanelView.navigationOptions} /> <AdminPanelStack.Screen name='AdminPanelView' component={AdminPanelView} />
</AdminPanelStack.Navigator> </AdminPanelStack.Navigator>
); );
}; };

View File

@ -129,7 +129,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
options={SearchMessagesView.navigationOptions} options={SearchMessagesView.navigationOptions}
/> />
<ModalStack.Screen name='SelectedUsersView' component={SelectedUsersView} /> <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='AddChannelTeamView' component={AddChannelTeamView} />
<ModalStack.Screen <ModalStack.Screen
name='AddExistingChannelView' name='AddExistingChannelView'
@ -166,7 +166,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
<ModalStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} /> <ModalStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} />
<ModalStack.Screen name='DiscussionsView' component={DiscussionsView} /> <ModalStack.Screen name='DiscussionsView' component={DiscussionsView} />
<ModalStack.Screen name='TeamChannelsView' component={TeamChannelsView} options={TeamChannelsView.navigationOptions} /> <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 <ModalStack.Screen
name='ReadReceiptsView' name='ReadReceiptsView'
component={ReadReceiptsView} component={ReadReceiptsView}
@ -196,11 +196,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
options={props => ProfileView.navigationOptions!({ ...props, isMasterDetail: true })} options={props => ProfileView.navigationOptions!({ ...props, isMasterDetail: true })}
/> />
<ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} /> <ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} />
<ModalStack.Screen <ModalStack.Screen name='AdminPanelView' component={AdminPanelView} />
name='AdminPanelView'
component={AdminPanelView}
options={props => AdminPanelView.navigationOptions!({ ...props, isMasterDetail: true })}
/>
<ModalStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} /> <ModalStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} />
<ModalStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} /> <ModalStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} />
<ModalStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} /> <ModalStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} />

View File

@ -28,7 +28,7 @@ const _OutsideStack = () => {
<Outside.Screen name='ForgotPasswordView' component={ForgotPasswordView} options={ForgotPasswordView.navigationOptions} /> <Outside.Screen name='ForgotPasswordView' component={ForgotPasswordView} options={ForgotPasswordView.navigationOptions} />
<Outside.Screen name='SendEmailConfirmationView' component={SendEmailConfirmationView} /> <Outside.Screen name='SendEmailConfirmationView' component={SendEmailConfirmationView} />
<Outside.Screen name='RegisterView' component={RegisterView} options={RegisterView.navigationOptions} /> <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> </Outside.Navigator>
); );
}; };

View File

@ -10,6 +10,7 @@ import { ISubscription, SubscriptionType, TSubscriptionModel } from '../definiti
import { ICannedResponse } from '../definitions/ICannedResponse'; import { ICannedResponse } from '../definitions/ICannedResponse';
import { TDataSelect } from '../definitions/IDataSelect'; import { TDataSelect } from '../definitions/IDataSelect';
import { ModalStackParamList } from './MasterDetailStack/types'; import { ModalStackParamList } from './MasterDetailStack/types';
import { TThreadModel } from '../definitions';
export type ChatsStackParamList = { export type ChatsStackParamList = {
ModalStackNavigator: NavigatorScreenParams<ModalStackParamList>; ModalStackNavigator: NavigatorScreenParams<ModalStackParamList>;
@ -246,7 +247,7 @@ export type InsideStackParamList = {
serverInfo: IServer; serverInfo: IServer;
text: string; text: string;
room: TSubscriptionModel; room: TSubscriptionModel;
thread: any; // TODO: Change thread: TThreadModel;
}; };
ModalBlockView: { ModalBlockView: {
data: any; // TODO: Change; data: any; // TODO: Change;

View File

@ -2,8 +2,6 @@ import { Platform } from 'react-native';
import DeviceInfo from 'react-native-device-info'; import DeviceInfo from 'react-native-device-info';
import { settings as RocketChatSettings } from '@rocket.chat/sdk'; import { settings as RocketChatSettings } from '@rocket.chat/sdk';
import RocketChat from '../lib/rocketchat';
export type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put'; export type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put';
interface CustomHeaders { interface CustomHeaders {
@ -46,13 +44,11 @@ export default (url: string, options: IOptions = {}): Promise<Response> => {
if (options && options.headers) { if (options && options.headers) {
customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } }; customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } };
} }
// TODO: Refactor when migrate rocketchat.js // TODO: Check if this really works and if anyone else has complained about this problem.
// @ts-ignore // if (RocketChat.controller) {
// WHAT? // // @ts-ignore
if (RocketChat.controller) { // const { signal } = RocketChat.controller;
// @ts-ignore // customOptions = { ...customOptions, signal };
const { signal } = RocketChat.controller; // }
customOptions = { ...customOptions, signal };
}
return fetch(url, customOptions); return fetch(url, customOptions);
}; };

View File

@ -142,13 +142,13 @@ export default {
SE_CONTACT_US: 'se_contact_us', SE_CONTACT_US: 'se_contact_us',
SE_CONTACT_US_F: 'se_contact_us_f', SE_CONTACT_US_F: 'se_contact_us_f',
SE_GO_LANGUAGE: 'se_go_language', 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_DEFAULTBROWSER: 'se_go_default_browser',
SE_GO_THEME: 'se_go_theme', SE_GO_THEME: 'se_go_theme',
SE_GO_PROFILE: 'se_go_profile', SE_GO_PROFILE: 'se_go_profile',
SE_GO_SECURITYPRIVACY: 'se_go_securityprivacy', 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_READ_LICENSE: 'se_read_license',
SE_COPY_APP_VERSION: 'se_copy_app_version', SE_COPY_APP_VERSION: 'se_copy_app_version',
SE_COPY_SERVER_VERSION: 'se_copy_server_version', SE_COPY_SERVER_VERSION: 'se_copy_server_version',

View File

@ -1,15 +1,20 @@
import { IAttachment } from '../views/ShareView/interfaces'; import { IShareAttachment } from '../definitions';
export const canUploadFile = ( export const canUploadFile = ({
file: IAttachment, file,
allowList: string, allowList,
maxFileSize: number, maxFileSize,
permissionToUploadFile: boolean permissionToUploadFile
): { success: boolean; error?: string } => { }: {
file: IShareAttachment;
allowList?: string;
maxFileSize?: number;
permissionToUploadFile: boolean;
}): { success: boolean; error?: string } => {
if (!(file && file.path)) { if (!(file && file.path)) {
return { success: true }; return { success: true };
} }
if (maxFileSize > -1 && file.size > maxFileSize) { if (maxFileSize && maxFileSize > -1 && file.size > maxFileSize) {
return { success: false, error: 'error-file-too-large' }; return { success: false, error: 'error-file-too-large' };
} }
if (!permissionToUploadFile) { if (!permissionToUploadFile) {

View File

@ -29,8 +29,7 @@ const RCSSLPinning = Platform.select({
pickCertificate: () => pickCertificate: () =>
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
try { try {
const res = await DocumentPicker.pick({ const res = await DocumentPicker.pickSingle({
// @ts-ignore
type: ['com.rsa.pkcs-12'] type: ['com.rsa.pkcs-12']
}); });
const { uri, name } = res; const { uri, name } = res;

View File

@ -1,7 +1,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native'; import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { connect } from 'react-redux'; import { useSelector } from 'react-redux';
import { CompositeNavigationProp } from '@react-navigation/core'; import { CompositeNavigationProp } from '@react-navigation/core';
import * as List from '../containers/List'; import * as List from '../containers/List';
@ -10,15 +10,14 @@ import * as HeaderButton from '../containers/HeaderButton';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import I18n from '../i18n'; import I18n from '../i18n';
import { ChatsStackParamList, DrawerParamList, NewMessageStackParamList } from '../stacks/types'; import { ChatsStackParamList, DrawerParamList, NewMessageStackParamList } from '../stacks/types';
import { IApplicationState } from '../definitions';
interface IAddChannelTeamView { type TRoute = RouteProp<ChatsStackParamList, 'AddChannelTeamView'>;
navigation: CompositeNavigationProp<
type TNavigation = CompositeNavigationProp<
StackNavigationProp<ChatsStackParamList, 'AddChannelTeamView'>, StackNavigationProp<ChatsStackParamList, 'AddChannelTeamView'>,
CompositeNavigationProp<StackNavigationProp<NewMessageStackParamList>, StackNavigationProp<DrawerParamList>> CompositeNavigationProp<StackNavigationProp<NewMessageStackParamList>, StackNavigationProp<DrawerParamList>>
>; >;
route: RouteProp<ChatsStackParamList, 'AddChannelTeamView'>;
isMasterDetail: boolean;
}
const setHeader = ({ const setHeader = ({
navigation, navigation,
@ -38,12 +37,14 @@ const setHeader = ({
navigation.setOptions(options); navigation.setOptions(options);
}; };
const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTeamView) => { const AddChannelTeamView = () => {
const { teamId, teamChannels } = route.params; const navigation = useNavigation<TNavigation>();
const isMasterDetail = useSelector((state: IApplicationState) => state.app.isMasterDetail);
const { teamChannels, teamId } = useRoute<TRoute>().params;
useEffect(() => { useEffect(() => {
setHeader({ navigation, isMasterDetail }); setHeader({ navigation, isMasterDetail });
}, []); }, [isMasterDetail, navigation]);
return ( return (
<SafeAreaView testID='add-channel-team-view'> <SafeAreaView testID='add-channel-team-view'>
@ -80,8 +81,4 @@ const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTe
); );
}; };
const mapStateToProps = (state: any) => ({ export default AddChannelTeamView;
isMasterDetail: state.app.isMasterDetail
});
export default connect(mapStateToProps)(AddChannelTeamView);

View File

@ -21,7 +21,7 @@ import { goRoom } from '../utils/goRoom';
import { showErrorAlert } from '../utils/info'; import { showErrorAlert } from '../utils/info';
import debounce from '../utils/debounce'; import debounce from '../utils/debounce';
import { ChatsStackParamList } from '../stacks/types'; import { ChatsStackParamList } from '../stacks/types';
import { TSubscriptionModel, SubscriptionType } from '../definitions'; import { TSubscriptionModel, SubscriptionType, IApplicationState } from '../definitions';
import { getRoomTitle, hasPermission } from '../lib/methods'; import { getRoomTitle, hasPermission } from '../lib/methods';
import { Services } from '../lib/services'; import { Services } from '../lib/services';
@ -37,7 +37,7 @@ interface IAddExistingChannelViewProps {
route: RouteProp<ChatsStackParamList, 'AddExistingChannelView'>; route: RouteProp<ChatsStackParamList, 'AddExistingChannelView'>;
theme: TSupportedThemes; theme: TSupportedThemes;
isMasterDetail: boolean; isMasterDetail: boolean;
addTeamChannelPermission: string[]; addTeamChannelPermission?: string[];
} }
const QUERY_SIZE = 50; 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, isMasterDetail: state.app.isMasterDetail,
addTeamChannelPermission: state.permissions['add-team-channel'] addTeamChannelPermission: state.permissions['add-team-channel']
}); });

View File

@ -1,38 +1,34 @@
import React from 'react'; import React, { useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';
import { WebView } from 'react-native-webview'; import { WebView } from 'react-native-webview';
import { connect } from 'react-redux'; import { useSelector } from 'react-redux';
import { DrawerScreenProps } from '@react-navigation/drawer'; import { StackNavigationProp } from '@react-navigation/stack';
import { StackNavigationOptions } from '@react-navigation/stack';
import I18n from '../../i18n'; import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import { withTheme } from '../../theme';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import { AdminPanelStackParamList } from '../../stacks/types'; import { AdminPanelStackParamList } from '../../stacks/types';
import { IApplicationState } from '../../definitions';
interface IAdminPanelViewProps { const AdminPanelView = () => {
baseUrl: string; const navigation = useNavigation<StackNavigationProp<AdminPanelStackParamList, 'AdminPanelView'>>();
token: string; 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 { useEffect(() => {
navigation: DrawerScreenProps<AdminPanelStackParamList, 'AdminPanelView'>; navigation.setOptions({
isMasterDetail: boolean;
}
class AdminPanelView extends React.Component<IAdminPanelViewProps, any> {
static navigationOptions = ({ navigation, isMasterDetail }: INavigationOptions): StackNavigationOptions => ({
headerLeft: isMasterDetail ? undefined : () => <HeaderButton.Drawer navigation={navigation} />, headerLeft: isMasterDetail ? undefined : () => <HeaderButton.Drawer navigation={navigation} />,
title: I18n.t('Admin_Panel') title: I18n.t('Admin_Panel')
}); });
}, [isMasterDetail, navigation]);
render() {
const { baseUrl, token } = this.props;
if (!baseUrl) { if (!baseUrl) {
return null; return null;
} }
return ( return (
<SafeAreaView> <SafeAreaView>
<StatusBar /> <StatusBar />
@ -44,12 +40,6 @@ class AdminPanelView extends React.Component<IAdminPanelViewProps, any> {
/> />
</SafeAreaView> </SafeAreaView>
); );
} };
}
const mapStateToProps = (state: any) => ({ export default AdminPanelView;
baseUrl: state.server.server,
token: getUserSelector(state).token
});
export default connect(mapStateToProps)(withTheme(AdminPanelView));

View File

@ -26,6 +26,7 @@ import StatusBar from '../containers/StatusBar';
import { InsideStackParamList } from '../stacks/types'; import { InsideStackParamList } from '../stacks/types';
import { IAttachment } from '../definitions/IAttachment'; import { IAttachment } from '../definitions/IAttachment';
import { formatAttachmentUrl } from '../lib/methods/helpers/formatAttachmentUrl'; import { formatAttachmentUrl } from '../lib/methods/helpers/formatAttachmentUrl';
import { IApplicationState, IUser } from '../definitions';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -46,10 +47,7 @@ interface IAttachmentViewProps {
width: number; width: number;
height: number; height: number;
insets: { left: number; bottom: number; right: number; top: number }; insets: { left: number; bottom: number; right: number; top: number };
user: { user: IUser;
id: string;
token: string;
};
Allow_Save_Media_to_Gallery: boolean; 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, baseUrl: state.server.server,
user: getUserSelector(state), user: getUserSelector(state),
Allow_Save_Media_to_Gallery: state.settings.Allow_Save_Media_to_Gallery ?? true Allow_Save_Media_to_Gallery: (state.settings.Allow_Save_Media_to_Gallery as boolean) ?? true
}); });
export default connect(mapStateToProps)(withTheme(withDimensions(withSafeAreaInsets(AttachmentView)))); export default connect(mapStateToProps)(withTheme(withDimensions(withSafeAreaInsets(AttachmentView))));

View File

@ -1,12 +1,11 @@
import React from 'react'; import React from 'react';
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'; import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
import { useSelector } from 'react-redux';
import { IApplicationState } from '../definitions';
import I18n from '../i18n'; import I18n from '../i18n';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { useTheme } from '../theme'; import { useTheme } from '../theme';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
import { useAppSelector } from '../lib/hooks';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -23,7 +22,7 @@ const styles = StyleSheet.create({
}); });
const AuthLoadingView = React.memo((): React.ReactElement => { const AuthLoadingView = React.memo((): React.ReactElement => {
const text = useSelector((state: IApplicationState) => state.app.text); const text = useAppSelector(state => state.app.text);
const { colors } = useTheme(); const { colors } = useTheme();
return ( return (
<View style={[styles.container, { backgroundColor: colors.backgroundColor }]}> <View style={[styles.container, { backgroundColor: colors.backgroundColor }]}>

View File

@ -14,6 +14,7 @@ import { TSupportedThemes, withTheme } from '../theme';
import debounce from '../utils/debounce'; import debounce from '../utils/debounce';
import * as HeaderButton from '../containers/HeaderButton'; import * as HeaderButton from '../containers/HeaderButton';
import { Services } from '../lib/services'; import { Services } from '../lib/services';
import { IApplicationState, ICredentials } from '../definitions';
const userAgent = isIOS 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' ? '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(); navigation.pop();
}; };
login = (params: any) => { login = (params: ICredentials) => {
const { logging } = this.state; const { logging } = this.state;
if (logging) { if (logging) {
return; return;
@ -111,7 +112,7 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
}; };
// Force 3s delay so the server has time to evaluate the token // 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( tryLogin = debounce(
async () => { async () => {
@ -135,7 +136,7 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
const parsedUrl = parse(url, true); const parsedUrl = parse(url, true);
// ticket -> cas / validate & saml_idp_credentialToken -> saml // ticket -> cas / validate & saml_idp_credentialToken -> saml
if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) { if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) {
let payload; let payload: ICredentials;
if (authType === 'saml') { if (authType === 'saml') {
const token = parsedUrl.query?.saml_idp_credentialToken || ssoToken; const token = parsedUrl.query?.saml_idp_credentialToken || ssoToken;
const credentialToken = { credentialToken: token }; 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, server: state.server.server,
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url, Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url as string,
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method as string
}); });
export default connect(mapStateToProps)(withTheme(AuthenticationWebView)); export default connect(mapStateToProps)(withTheme(AuthenticationWebView));

View File

@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
import { StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native'; import { RouteProp } from '@react-navigation/native';
import { StyleSheet, Text, View, ScrollView } from 'react-native'; import { StyleSheet, Text, View, ScrollView } from 'react-native';
import { useSelector } from 'react-redux';
import I18n from '../i18n'; import I18n from '../i18n';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
@ -17,6 +16,7 @@ import { ICannedResponse } from '../definitions/ICannedResponse';
import { ChatsStackParamList } from '../stacks/types'; import { ChatsStackParamList } from '../stacks/types';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
import { getRoomTitle, getUidDirectMessage } from '../lib/methods'; import { getRoomTitle, getUidDirectMessage } from '../lib/methods';
import { useAppSelector } from '../lib/hooks';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
scroll: { scroll: {
@ -96,8 +96,8 @@ interface ICannedResponseDetailProps {
const CannedResponseDetail = ({ navigation, route }: ICannedResponseDetailProps): JSX.Element => { const CannedResponseDetail = ({ navigation, route }: ICannedResponseDetailProps): JSX.Element => {
const { cannedResponse } = route?.params; const { cannedResponse } = route?.params;
const { theme } = useTheme(); const { theme } = useTheme();
const { isMasterDetail } = useSelector((state: any) => state.app); const { isMasterDetail } = useAppSelector(state => state.app);
const { rooms } = useSelector((state: any) => state.room); const { rooms } = useAppSelector(state => state.room);
useEffect(() => { useEffect(() => {
navigation.setOptions({ navigation.setOptions({
@ -159,13 +159,7 @@ const CannedResponseDetail = ({ navigation, route }: ICannedResponseDetailProps)
</View> </View>
</View> </View>
</View> </View>
<Button <Button title={I18n.t('Use')} style={styles.button} type='primary' onPress={() => navigateToRoom(cannedResponse)} />
title={I18n.t('Use')}
theme={theme}
style={styles.button}
type='primary'
onPress={() => navigateToRoom(cannedResponse)}
/>
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>
); );

View File

@ -40,7 +40,6 @@ const CannedResponseItem = ({
fontSize={12} fontSize={12}
color={themes[theme].titleText} color={themes[theme].titleText}
style={[styles.cannedUseButton, { backgroundColor: themes[theme].chatComponentBackground }]} style={[styles.cannedUseButton, { backgroundColor: themes[theme].chatComponentBackground }]}
theme={theme}
onPress={onPressUse} onPress={onPressUse}
/> />
</View> </View>

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,5 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import { useSelector } from 'react-redux';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { RouteProp } from '@react-navigation/native'; import { RouteProp } from '@react-navigation/native';
import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
@ -30,8 +29,8 @@ import { ChatsStackParamList } from '../../stacks/types';
import { ISubscription } from '../../definitions/ISubscription'; import { ISubscription } from '../../definitions/ISubscription';
import { getRoomTitle, getUidDirectMessage } from '../../lib/methods'; import { getRoomTitle, getUidDirectMessage } from '../../lib/methods';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { IApplicationState } from '../../definitions';
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment'; import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
import { useAppSelector } from '../../lib/hooks';
const COUNT = 25; const COUNT = 25;
@ -76,8 +75,8 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const { theme } = useTheme(); const { theme } = useTheme();
const isMasterDetail = useSelector((state: IApplicationState) => state.app.isMasterDetail); const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
const rooms = useSelector((state: IApplicationState) => state.room.rooms); const rooms = useAppSelector(state => state.room.rooms);
const getRoomFromDb = async () => { const getRoomFromDb = async () => {
const { rid } = route.params; const { rid } = route.params;

View File

@ -4,7 +4,7 @@ import { FlatList, ScrollView, StyleSheet, Switch, Text, View, SwitchProps } fro
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import * as List from '../containers/List'; import * as List from '../containers/List';
import TextInput from '../presentation/TextInput'; import TextInput from '../containers/TextInput';
import Loading from '../containers/Loading'; import Loading from '../containers/Loading';
import { createChannelRequest } from '../actions/createChannel'; import { createChannelRequest } from '../actions/createChannel';
import { removeUser } from '../actions/selectedUsers'; import { removeUser } from '../actions/selectedUsers';

View File

@ -11,7 +11,7 @@ import * as HeaderButton from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import TextInput from '../../containers/TextInput'; import FormTextInput from '../../containers/TextInput/FormTextInput';
import Navigation from '../../lib/navigation/appNavigation'; import Navigation from '../../lib/navigation/appNavigation';
import { createDiscussionRequest, ICreateDiscussionRequestData } from '../../actions/createDiscussion'; import { createDiscussionRequest, ICreateDiscussionRequestData } from '../../actions/createDiscussion';
import { showErrorAlert } from '../../utils/info'; import { showErrorAlert } from '../../utils/info';
@ -167,7 +167,7 @@ class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreate
serverVersion={serverVersion} serverVersion={serverVersion}
theme={theme} theme={theme}
/> />
<TextInput <FormTextInput
label={I18n.t('Discussion_name')} label={I18n.t('Discussion_name')}
testID='multi-select-discussion-name' testID='multi-select-discussion-name'
placeholder={I18n.t('A_meaningful_name_for_the_discussion_room')} placeholder={I18n.t('A_meaningful_name_for_the_discussion_room')}

View File

@ -1,11 +1,10 @@
import React, { useEffect, useLayoutEffect, useState } from 'react'; import React, { useEffect, useLayoutEffect, useState } from 'react';
import { FlatList, StyleSheet } from 'react-native'; import { FlatList, StyleSheet } from 'react-native';
import { useSelector } from 'react-redux';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/core'; import { RouteProp } from '@react-navigation/core';
import { IApplicationState, IMessageFromServer } from '../../definitions'; import { IMessageFromServer } from '../../definitions';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import ActivityIndicator from '../../containers/ActivityIndicator'; import ActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n'; import I18n from '../../i18n';
@ -23,6 +22,7 @@ import SearchHeader from '../../containers/SearchHeader';
import { TThreadModel } from '../../definitions/IThread'; import { TThreadModel } from '../../definitions/IThread';
import Item from './Item'; import Item from './Item';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { useAppSelector } from '../../lib/hooks';
const API_FETCH_COUNT = 50; const API_FETCH_COUNT = 50;
@ -42,8 +42,8 @@ const DiscussionsView = ({ navigation, route }: IDiscussionsViewProps): React.Re
const rid = route.params?.rid; const rid = route.params?.rid;
const t = route.params?.t; const t = route.params?.t;
const baseUrl = useSelector((state: IApplicationState) => state.server?.server); const baseUrl = useAppSelector(state => state.server?.server);
const isMasterDetail = useSelector((state: IApplicationState) => state.app?.isMasterDetail); const isMasterDetail = useAppSelector(state => state.app?.isMasterDetail);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [discussions, setDiscussions] = useState<IMessageFromServer[]>([]); const [discussions, setDiscussions] = useState<IMessageFromServer[]>([]);

View File

@ -3,7 +3,7 @@ import { StackNavigationProp } from '@react-navigation/stack';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { Switch } from 'react-native'; import { Switch } from 'react-native';
import { RadioButton } from 'react-native-ui-lib'; import { RadioButton } from 'react-native-ui-lib';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch } from 'react-redux';
import { setPreference } from '../actions/sortPreferences'; import { setPreference } from '../actions/sortPreferences';
import { DisplayMode, SortBy } from '../lib/constants'; import { DisplayMode, SortBy } from '../lib/constants';
@ -12,21 +12,22 @@ import * as List from '../containers/List';
import { ICON_SIZE } from '../containers/List/constants'; import { ICON_SIZE } from '../containers/List/constants';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { IApplicationState, IPreferences } from '../definitions'; import { IPreferences } from '../definitions';
import I18n from '../i18n'; import I18n from '../i18n';
import { SettingsStackParamList } from '../stacks/types'; import { SettingsStackParamList } from '../stacks/types';
import { useTheme } from '../theme'; import { useTheme } from '../theme';
import { events, logEvent } from '../utils/log'; import { events, logEvent } from '../utils/log';
import { saveSortPreference } from '../lib/methods'; import { saveSortPreference } from '../lib/methods';
import { useAppSelector } from '../lib/hooks';
const DisplayPrefsView = (): React.ReactElement => { const DisplayPrefsView = (): React.ReactElement => {
const navigation = useNavigation<StackNavigationProp<SettingsStackParamList, 'DisplayPrefsView'>>(); const navigation = useNavigation<StackNavigationProp<SettingsStackParamList, 'DisplayPrefsView'>>();
const { colors } = useTheme(); const { colors } = useTheme();
const { sortBy, groupByType, showFavorites, showUnread, showAvatar, displayMode } = useSelector( const { sortBy, groupByType, showFavorites, showUnread, showAvatar, displayMode } = useAppSelector(
(state: IApplicationState) => state.sortPreferences state => state.sortPreferences
); );
const { isMasterDetail } = useSelector((state: IApplicationState) => state.app); const { isMasterDetail } = useAppSelector(state => state.app);
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {

View File

@ -1,29 +1,29 @@
import React from 'react'; 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 { StackNavigationOptions } from '@react-navigation/stack';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import * as List from '../containers/List'; import * as List from '../containers/List';
import I18n from '../i18n'; import I18n from '../i18n';
import log, { events, logEvent } from '../utils/log'; import log, { events, logEvent } from '../utils/log';
import { TSupportedThemes, withTheme } from '../theme'; import { withTheme } from '../theme';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import TextInput from '../containers/TextInput'; import FormTextInput from '../containers/TextInput/FormTextInput';
import Button from '../containers/Button'; import Button from '../containers/Button';
import { getUserSelector } from '../selectors/login'; import { getUserSelector } from '../selectors/login';
import { PADDING_HORIZONTAL } from '../containers/List/constants'; import { PADDING_HORIZONTAL } from '../containers/List/constants';
import { themes } from '../lib/constants'; import { themes } from '../lib/constants';
import { Encryption } from '../lib/encryption'; import { Encryption } from '../lib/encryption';
import { logout as logoutAction } from '../actions/login'; import { logout } from '../actions/login';
import { showConfirmationAlert, showErrorAlert } from '../utils/info'; import { showConfirmationAlert, showErrorAlert } from '../utils/info';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
import { LISTENER } from '../containers/Toast'; import { LISTENER } from '../containers/Toast';
import debounce from '../utils/debounce'; import debounce from '../utils/debounce';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
import { IUser } from '../definitions'; import { IApplicationState, IBaseScreen, IUser } from '../definitions';
import { Services } from '../lib/services'; import { Services } from '../lib/services';
import { SettingsStackParamList } from '../stacks/types';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -47,12 +47,10 @@ interface IE2EEncryptionSecurityViewState {
newPassword: string; newPassword: string;
} }
interface IE2EEncryptionSecurityViewProps { interface IE2EEncryptionSecurityViewProps extends IBaseScreen<SettingsStackParamList, 'E2EEncryptionSecurityView'> {
theme?: TSupportedThemes;
user: IUser; user: IUser;
server: string; server: string;
encryptionEnabled: boolean; encryptionEnabled: boolean;
logout(): void;
} }
class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityViewProps, IE2EEncryptionSecurityViewState> { 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); onChangePasswordText = debounce((text: string) => this.setState({ newPassword: text }), 300);
setNewPasswordRef = (ref: TextInputComp) => (this.newPasswordInputRef = ref); setNewPasswordRef = (ref: RNTextInput) => (this.newPasswordInputRef = ref);
changePassword = () => { changePassword = () => {
const { newPassword } = this.state; const { newPassword } = this.state;
@ -107,8 +105,8 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
* that's why we're using strict equality to boolean * that's why we're using strict equality to boolean
*/ */
if (res === true) { if (res === true) {
const { logout } = this.props; const { dispatch } = this.props;
logout(); dispatch(logout());
} }
} catch (e) { } catch (e) {
log(e); log(e);
@ -127,13 +125,13 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
return ( return (
<> <>
<List.Section> <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')} {I18n.t('E2E_encryption_change_password_title')}
</Text> </Text>
<Text style={[styles.description, { color: themes[theme!].bodyText }]}> <Text style={[styles.description, { color: themes[theme].bodyText }]}>
{I18n.t('E2E_encryption_change_password_description')} {I18n.t('E2E_encryption_change_password_description')}
</Text> </Text>
<TextInput <FormTextInput
inputRef={this.setNewPasswordRef} inputRef={this.setNewPasswordRef}
placeholder={I18n.t('New_Password')} placeholder={I18n.t('New_Password')}
returnKeyType='send' returnKeyType='send'
@ -146,7 +144,6 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
<Button <Button
onPress={this.changePassword} onPress={this.changePassword}
title={I18n.t('Save_Changes')} title={I18n.t('Save_Changes')}
theme={theme}
disabled={!newPassword.trim()} disabled={!newPassword.trim()}
style={styles.changePasswordButton} style={styles.changePasswordButton}
testID='e2e-encryption-security-view-change-password' testID='e2e-encryption-security-view-change-password'
@ -161,25 +158,24 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
render() { render() {
const { theme } = this.props; const { theme } = this.props;
return ( return (
<SafeAreaView testID='e2e-encryption-security-view' style={{ backgroundColor: themes[theme!].backgroundColor }}> <SafeAreaView testID='e2e-encryption-security-view' style={{ backgroundColor: themes[theme].backgroundColor }}>
<StatusBar /> <StatusBar />
<List.Container> <List.Container>
<View style={styles.container}> <View style={styles.container}>
{this.renderChangePassword()} {this.renderChangePassword()}
<List.Section> <List.Section>
<Text style={[styles.title, { color: themes[theme!].headerTitleColor }]}> <Text style={[styles.title, { color: themes[theme].headerTitleColor }]}>
{I18n.t('E2E_encryption_reset_title')} {I18n.t('E2E_encryption_reset_title')}
</Text> </Text>
<Text style={[styles.description, { color: themes[theme!].bodyText }]}> <Text style={[styles.description, { color: themes[theme].bodyText }]}>
{I18n.t('E2E_encryption_reset_description')} {I18n.t('E2E_encryption_reset_description')}
</Text> </Text>
<Button <Button
onPress={this.resetOwnKey} onPress={this.resetOwnKey}
title={I18n.t('E2E_encryption_reset_button')} title={I18n.t('E2E_encryption_reset_button')}
theme={theme}
type='secondary' type='secondary'
backgroundColor={themes[theme!].chatComponentBackground} backgroundColor={themes[theme].chatComponentBackground}
testID='e2e-encryption-security-view-reset-key' testID='e2e-encryption-security-view-reset-key'
/> />
</List.Section> </List.Section>
@ -190,14 +186,10 @@ class E2EEncryptionSecurityView extends React.Component<IE2EEncryptionSecurityVi
} }
} }
const mapStateToProps = (state: any) => ({ const mapStateToProps = (state: IApplicationState) => ({
server: state.server.server, server: state.server.server,
user: getUserSelector(state), user: getUserSelector(state),
encryptionEnabled: state.encryption.enabled encryptionEnabled: state.encryption.enabled
}); });
const mapDispatchToProps = (dispatch: Dispatch) => ({ export default connect(mapStateToProps)(withTheme(E2EEncryptionSecurityView));
logout: () => dispatch(logoutAction(true))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(E2EEncryptionSecurityView));

View File

@ -9,7 +9,7 @@ import Button from '../containers/Button';
import * as HeaderButton from '../containers/HeaderButton'; import * as HeaderButton from '../containers/HeaderButton';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import TextInput from '../containers/TextInput'; import FormTextInput from '../containers/TextInput/FormTextInput';
import { IBaseScreen } from '../definitions'; import { IBaseScreen } from '../definitions';
import I18n from '../i18n'; import I18n from '../i18n';
import KeyboardView from '../containers/KeyboardView'; import KeyboardView from '../containers/KeyboardView';
@ -75,7 +75,7 @@ class E2EEnterYourPasswordView extends React.Component<TE2EEnterYourPasswordView
<SafeAreaView <SafeAreaView
style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}
testID='e2e-enter-your-password-view'> testID='e2e-enter-your-password-view'>
<TextInput <FormTextInput
inputRef={(e: RNTextInput) => { inputRef={(e: RNTextInput) => {
this.passwordInput = e; this.passwordInput = e;
}} }}
@ -93,7 +93,6 @@ class E2EEnterYourPasswordView extends React.Component<TE2EEnterYourPasswordView
onPress={this.submit} onPress={this.submit}
title={I18n.t('Confirm')} title={I18n.t('Confirm')}
disabled={!password} disabled={!password}
theme={theme}
testID='e2e-enter-your-password-view-confirm' testID='e2e-enter-your-password-view-confirm'
/> />
<Text style={[styles.info, { color: themes[theme].bodyText }]}>{I18n.t('Enter_Your_Encryption_Password_desc1')}</Text> <Text style={[styles.info, { color: themes[theme].bodyText }]}>{I18n.t('Enter_Your_Encryption_Password_desc1')}</Text>

View File

@ -1,15 +1,14 @@
import React from 'react'; import React from 'react';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import { themes } from '../lib/constants'; import { themes } from '../lib/constants';
import * as HeaderButton from '../containers/HeaderButton'; import * as HeaderButton from '../containers/HeaderButton';
import Markdown from '../containers/markdown'; import Markdown from '../containers/markdown';
import { TSupportedThemes, withTheme } from '../theme'; import { withTheme } from '../theme';
import I18n from '../i18n'; import I18n from '../i18n';
import { E2ESaveYourPasswordStackParamList } from '../stacks/types'; import { E2ESaveYourPasswordStackParamList } from '../stacks/types';
import { IBaseScreen } from '../definitions';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -23,17 +22,10 @@ const styles = StyleSheet.create({
} }
}); });
interface INavigation { type TE2EHowItWorksViewProps = IBaseScreen<E2ESaveYourPasswordStackParamList, 'E2EHowItWorksView'>;
navigation: StackNavigationProp<E2ESaveYourPasswordStackParamList, 'E2EHowItWorksView'>;
route: RouteProp<E2ESaveYourPasswordStackParamList, 'E2EHowItWorksView'>;
}
interface IE2EHowItWorksViewProps extends INavigation { class E2EHowItWorksView extends React.Component<TE2EHowItWorksViewProps, any> {
theme: TSupportedThemes; static navigationOptions = ({ route, navigation }: Pick<TE2EHowItWorksViewProps, 'navigation' | 'route'>) => {
}
class E2EHowItWorksView extends React.Component<IE2EHowItWorksViewProps, any> {
static navigationOptions = ({ route, navigation }: INavigation) => {
const showCloseModal = route.params?.showCloseModal; const showCloseModal = route.params?.showCloseModal;
return { return {
title: I18n.t('How_It_Works'), title: I18n.t('How_It_Works'),

View File

@ -55,7 +55,7 @@ const styles = StyleSheet.create({
}); });
interface IE2ESaveYourPasswordViewState { interface IE2ESaveYourPasswordViewState {
password: string; password: string | null;
} }
interface IE2ESaveYourPasswordViewProps extends IBaseScreen<E2ESaveYourPasswordStackParamList, 'E2ESaveYourPasswordView'> { interface IE2ESaveYourPasswordViewProps extends IBaseScreen<E2ESaveYourPasswordStackParamList, 'E2ESaveYourPasswordView'> {
@ -87,7 +87,7 @@ class E2ESaveYourPasswordView extends React.Component<IE2ESaveYourPasswordViewPr
// Set stored password on local state // Set stored password on local state
const password = UserPreferences.getString(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); const password = UserPreferences.getString(`${server}-${E2E_RANDOM_PASSWORD_KEY}`);
if (this.mounted) { if (this.mounted) {
this.setState({ password: password! }); this.setState({ password });
} else { } else {
// @ts-ignore // @ts-ignore
this.state.password = password; this.state.password = password;
@ -110,8 +110,10 @@ class E2ESaveYourPasswordView extends React.Component<IE2ESaveYourPasswordViewPr
onCopy = () => { onCopy = () => {
logEvent(events.E2E_SAVE_PW_COPY); logEvent(events.E2E_SAVE_PW_COPY);
const { password } = this.state; const { password } = this.state;
if (password) {
Clipboard.setString(password); Clipboard.setString(password);
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
}
}; };
onHowItWorks = () => { onHowItWorks = () => {
@ -144,7 +146,6 @@ class E2ESaveYourPasswordView extends React.Component<IE2ESaveYourPasswordViewPr
title={I18n.t('Copy')} title={I18n.t('Copy')}
type='secondary' type='secondary'
fontSize={12} fontSize={12}
theme={theme}
/> />
</View> </View>
<Text style={[styles.info, { color: themes[theme].bodyText }]}>{I18n.t('Save_Your_Encryption_Password_info')}</Text> <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 }} style={{ backgroundColor: themes[theme].auxiliaryBackground }}
title={I18n.t('How_It_Works')} title={I18n.t('How_It_Works')}
type='secondary' type='secondary'
theme={theme}
testID='e2e-save-password-view-how-it-works' testID='e2e-save-password-view-how-it-works'
/> />
<Button <Button
onPress={this.onSaved} onPress={this.onSaved}
title={I18n.t('I_Saved_My_E2E_Password')} title={I18n.t('I_Saved_My_E2E_Password')}
theme={theme}
testID='e2e-save-password-view-saved-password' testID='e2e-save-password-view-saved-password'
/> />
</View> </View>

View File

@ -3,7 +3,7 @@ import { Text } from 'react-native';
import Button from '../containers/Button'; import Button from '../containers/Button';
import FormContainer, { FormContainerInner } from '../containers/FormContainer'; import FormContainer, { FormContainerInner } from '../containers/FormContainer';
import TextInput from '../containers/TextInput'; import FormTextInput from '../containers/TextInput/FormTextInput';
import I18n from '../i18n'; import I18n from '../i18n';
import { themes } from '../lib/constants'; import { themes } from '../lib/constants';
import { Services } from '../lib/services'; 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 }]}> <Text style={[sharedStyles.loginTitle, sharedStyles.textBold, { color: themes[theme].titleText }]}>
{I18n.t('Forgot_password')} {I18n.t('Forgot_password')}
</Text> </Text>
<TextInput <FormTextInput
autoFocus autoFocus
placeholder={I18n.t('Email')} placeholder={I18n.t('Email')}
keyboardType='email-address' keyboardType='email-address'
@ -110,7 +110,6 @@ class ForgotPasswordView extends React.Component<IForgotPasswordViewProps, IForg
testID='forgot-password-view-submit' testID='forgot-password-view-submit'
loading={isFetching} loading={isFetching}
disabled={invalidEmail} disabled={invalidEmail}
theme={theme}
/> />
</FormContainerInner> </FormContainerInner>
</FormContainer> </FormContainer>

View File

@ -121,7 +121,6 @@ class InviteUsersEditView extends React.Component<IInviteUsersEditViewProps, any
}; };
render() { render() {
const { theme } = this.props;
return ( return (
<SafeAreaView> <SafeAreaView>
<List.Container> <List.Container>
@ -134,7 +133,7 @@ class InviteUsersEditView extends React.Component<IInviteUsersEditViewProps, any
<List.Separator /> <List.Separator />
</List.Section> </List.Section>
<View style={styles.innerContainer}> <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> </View>
</List.Container> </List.Container>
</SafeAreaView> </SafeAreaView>

View File

@ -1,69 +1,58 @@
import { StackNavigationOptions } from '@react-navigation/stack'; import React, { useEffect } from 'react';
import moment from 'moment'; import moment from 'moment';
import React from 'react';
import { ScrollView, Share, View } from 'react-native'; 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 { inviteLinksClear, inviteLinksCreate } from '../../actions/inviteLinks';
import { themes } from '../../lib/constants';
import Button from '../../containers/Button'; import Button from '../../containers/Button';
import Markdown from '../../containers/markdown'; import Markdown from '../../containers/markdown';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import RCTextInput from '../../containers/TextInput'; import FormTextInput from '../../containers/TextInput/FormTextInput';
import { IApplicationState, IBaseScreen } from '../../definitions'; import { IApplicationState, IBaseScreen } from '../../definitions';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { TInvite } from '../../reducers/inviteLinks';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import { withTheme } from '../../theme'; import { useTheme } from '../../theme';
import { events, logEvent } from '../../utils/log'; import { events, logEvent } from '../../utils/log';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import scrollPersistTaps from '../../utils/scrollPersistTaps';
import styles from './styles'; import styles from './styles';
interface IInviteUsersViewProps extends IBaseScreen<ChatsStackParamList, 'InviteUsersView'> { type IInviteUsersViewProps = IBaseScreen<ChatsStackParamList, 'InviteUsersView'>;
timeDateFormat: string;
invite: TInvite; const InviteUsersView = ({ route, navigation }: IInviteUsersViewProps): React.ReactElement => {
} const rid = route.params?.rid;
class InviteUsersView extends React.Component<IInviteUsersViewProps, any> { const timeDateFormat = useSelector((state: IApplicationState) => state.settings.Message_TimeAndDateFormat as string);
static navigationOptions = (): StackNavigationOptions => ({ const invite = useSelector((state: IApplicationState) => state.inviteLinks.invite);
const { colors, theme } = useTheme();
const dispatch = useDispatch();
useEffect(() => {
navigation.setOptions({
title: I18n.t('Invite_users') title: I18n.t('Invite_users')
}); });
}, []);
private rid: string; useEffect(() => {
dispatch(inviteLinksCreate(rid));
constructor(props: IInviteUsersViewProps) { return () => {
super(props);
this.rid = props.route.params?.rid;
}
componentDidMount() {
const { dispatch } = this.props;
dispatch(inviteLinksCreate(this.rid));
}
componentWillUnmount() {
const { dispatch } = this.props;
dispatch(inviteLinksClear()); dispatch(inviteLinksClear());
} };
}, []);
share = () => { const share = () => {
logEvent(events.IU_SHARE); logEvent(events.IU_SHARE);
const { invite } = this.props;
if (!invite || !invite.url) { if (!invite || !invite.url) {
return; return;
} }
Share.share({ message: invite.url }); Share.share({ message: invite.url });
}; };
edit = () => { const edit = () => {
logEvent(events.IU_GO_IU_EDIT); logEvent(events.IU_GO_IU_EDIT);
const { navigation } = this.props; navigation.navigate('InviteUsersEditView', { rid });
navigation.navigate('InviteUsersEditView', { rid: this.rid });
}; };
linkExpirationText = () => { const linkExpirationText = () => {
const { timeDateFormat, invite } = this.props;
if (!invite || !invite.url) { if (!invite || !invite.url) {
return null; return null;
} }
@ -90,38 +79,28 @@ class InviteUsersView extends React.Component<IInviteUsersViewProps, any> {
return I18n.t('Your_invite_link_will_never_expire'); return I18n.t('Your_invite_link_will_never_expire');
}; };
renderExpiration = () => { const renderExpiration = () => {
const { theme } = this.props; const expirationMessage = linkExpirationText();
const expirationMessage = this.linkExpirationText();
return <Markdown msg={expirationMessage} theme={theme} />; return <Markdown msg={expirationMessage} theme={theme} />;
}; };
render() {
const { theme, invite } = this.props;
return ( return (
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}> <SafeAreaView style={{ backgroundColor: colors.backgroundColor }}>
{/* @ts-ignore*/}
<ScrollView <ScrollView
{...scrollPersistTaps} {...scrollPersistTaps}
style={{ backgroundColor: themes[theme].auxiliaryBackground }} style={{ backgroundColor: colors.auxiliaryBackground }}
showsVerticalScrollIndicator={false}> showsVerticalScrollIndicator={false}>
<StatusBar /> <StatusBar />
<View style={styles.innerContainer}> <View style={styles.innerContainer}>
<RCTextInput label={I18n.t('Invite_Link')} theme={theme} value={invite && invite.url} editable={false} /> <FormTextInput label={I18n.t('Invite_Link')} theme={theme} value={invite && invite.url} editable={false} />
{this.renderExpiration()} {renderExpiration()}
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} /> <View style={[styles.divider, { backgroundColor: colors.separatorColor }]} />
<Button title={I18n.t('Share_Link')} type='primary' onPress={this.share} theme={theme} /> <Button title={I18n.t('Share_Link')} type='primary' onPress={share} />
<Button title={I18n.t('Edit_Invite')} type='secondary' onPress={this.edit} theme={theme} /> <Button title={I18n.t('Edit_Invite')} type='secondary' onPress={edit} />
</View> </View>
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>
); );
} };
}
const mapStateToProps = (state: IApplicationState) => ({ export default InviteUsersView;
timeDateFormat: state.settings.Message_TimeAndDateFormat as string,
invite: state.inviteLinks.invite
});
export default connect(mapStateToProps)(withTheme(InviteUsersView));

View File

@ -1,33 +1,36 @@
import React from 'react'; import React, { useEffect } from 'react';
import { connect } from 'react-redux'; import { useSelector } from 'react-redux';
import { StackNavigationOptions } from '@react-navigation/stack';
import I18n from '../i18n'; import I18n from '../i18n';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import openLink from '../utils/openLink'; import openLink from '../utils/openLink';
import { TSupportedThemes, withTheme } from '../theme'; import { useTheme } from '../theme';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import * as List from '../containers/List'; 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; server: string;
theme: TSupportedThemes;
} }
class LegalView extends React.Component<ILegalView, any> { const LegalView = ({ navigation }: ILegalViewProps): React.ReactElement => {
static navigationOptions = (): StackNavigationOptions => ({ const server = useSelector((state: IApplicationState) => state.server.server);
const { theme } = useTheme();
useEffect(() => {
navigation.setOptions({
title: I18n.t('Legal') title: I18n.t('Legal')
}); });
}, []);
onPressItem = ({ route }: { route: string }) => { const onPressItem = ({ route }: { route: string }) => {
const { server, theme } = this.props;
if (!server) { if (!server) {
return; return;
} }
openLink(`${server}/${route}`, theme); openLink(`${server}/${route}`, theme);
}; };
render() {
return ( return (
<SafeAreaView testID='legal-view'> <SafeAreaView testID='legal-view'>
<StatusBar /> <StatusBar />
@ -36,14 +39,14 @@ class LegalView extends React.Component<ILegalView, any> {
<List.Separator /> <List.Separator />
<List.Item <List.Item
title='Terms_of_Service' title='Terms_of_Service'
onPress={() => this.onPressItem({ route: 'terms-of-service' })} onPress={() => onPressItem({ route: 'terms-of-service' })}
testID='legal-terms-button' testID='legal-terms-button'
showActionIndicator showActionIndicator
/> />
<List.Separator /> <List.Separator />
<List.Item <List.Item
title='Privacy_Policy' title='Privacy_Policy'
onPress={() => this.onPressItem({ route: 'privacy-policy' })} onPress={() => onPressItem({ route: 'privacy-policy' })}
testID='legal-privacy-button' testID='legal-privacy-button'
showActionIndicator showActionIndicator
/> />
@ -52,11 +55,6 @@ class LegalView extends React.Component<ILegalView, any> {
</List.Container> </List.Container>
</SafeAreaView> </SafeAreaView>
); );
} };
}
const mapStateToProps = (state: any) => ({ export default LegalView;
server: state.server.server
});
export default connect(mapStateToProps)(withTheme(LegalView));

View File

@ -7,7 +7,7 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import { TSupportedThemes, withTheme } from '../theme'; import { TSupportedThemes, withTheme } from '../theme';
import { themes } from '../lib/constants'; import { themes } from '../lib/constants';
import TextInput from '../containers/TextInput'; import FormTextInput from '../containers/TextInput/FormTextInput';
import KeyboardView from '../containers/KeyboardView'; import KeyboardView from '../containers/KeyboardView';
import I18n from '../i18n'; import I18n from '../i18n';
import { LISTENER } from '../containers/Toast'; import { LISTENER } from '../containers/Toast';
@ -193,7 +193,7 @@ const LivechatEditView = ({
<ScrollView {...scrollPersistTaps} style={styles.container}> <ScrollView {...scrollPersistTaps} style={styles.container}>
<SafeAreaView> <SafeAreaView>
<Title title={visitor?.username} theme={theme} /> <Title title={visitor?.username} theme={theme} />
<TextInput <FormTextInput
label={I18n.t('Name')} label={I18n.t('Name')}
defaultValue={visitor?.name} defaultValue={visitor?.name}
onChangeText={text => onChangeText('name', text)} onChangeText={text => onChangeText('name', text)}
@ -203,7 +203,7 @@ const LivechatEditView = ({
theme={theme} theme={theme}
editable={!!permissions[0]} editable={!!permissions[0]}
/> />
<TextInput <FormTextInput
label={I18n.t('Email')} label={I18n.t('Email')}
inputRef={e => { inputRef={e => {
inputs.name = e; inputs.name = e;
@ -216,7 +216,7 @@ const LivechatEditView = ({
theme={theme} theme={theme}
editable={!!permissions[0]} editable={!!permissions[0]}
/> />
<TextInput <FormTextInput
label={I18n.t('Phone')} label={I18n.t('Phone')}
inputRef={e => { inputRef={e => {
inputs.phone = e; inputs.phone = e;
@ -236,7 +236,7 @@ const LivechatEditView = ({
editable={!!permissions[0]} editable={!!permissions[0]}
/> />
{Object.entries(customFields?.visitor || {}).map(([key, value], index, array) => ( {Object.entries(customFields?.visitor || {}).map(([key, value], index, array) => (
<TextInput <FormTextInput
label={key} label={key}
defaultValue={value} defaultValue={value}
inputRef={e => { inputRef={e => {
@ -254,7 +254,7 @@ const LivechatEditView = ({
/> />
))} ))}
<Title title={I18n.t('Conversation')} theme={theme} /> <Title title={I18n.t('Conversation')} theme={theme} />
<TextInput <FormTextInput
label={I18n.t('Topic')} label={I18n.t('Topic')}
inputRef={e => { inputRef={e => {
inputs.topic = e; inputs.topic = e;
@ -280,7 +280,7 @@ const LivechatEditView = ({
/> />
{Object.entries(customFields?.livechat || {}).map(([key, value], index, array: any) => ( {Object.entries(customFields?.livechat || {}).map(([key, value], index, array: any) => (
<TextInput <FormTextInput
label={key} label={key}
defaultValue={value} defaultValue={value}
inputRef={e => { 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> </SafeAreaView>
</ScrollView> </ScrollView>
</KeyboardView> </KeyboardView>

View File

@ -9,7 +9,7 @@ import Button from '../containers/Button';
import FormContainer, { FormContainerInner } from '../containers/FormContainer'; import FormContainer, { FormContainerInner } from '../containers/FormContainer';
import * as HeaderButton from '../containers/HeaderButton'; import * as HeaderButton from '../containers/HeaderButton';
import LoginServices from '../containers/LoginServices'; import LoginServices from '../containers/LoginServices';
import TextInput from '../containers/TextInput'; import FormTextInput from '../containers/TextInput/FormTextInput';
import { IApplicationState, IBaseScreen } from '../definitions'; import { IApplicationState, IBaseScreen } from '../definitions';
import I18n from '../i18n'; import I18n from '../i18n';
import { OutsideParamList } from '../stacks/types'; import { OutsideParamList } from '../stacks/types';
@ -157,7 +157,7 @@ class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
return ( return (
<> <>
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Login')}</Text> <Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Login')}</Text>
<TextInput <FormTextInput
label={I18n.t('Username_or_email')} label={I18n.t('Username_or_email')}
containerStyle={styles.inputContainer} containerStyle={styles.inputContainer}
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Username_or_email')} placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Username_or_email')}
@ -173,7 +173,7 @@ class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
theme={theme} theme={theme}
value={user} value={user}
/> />
<TextInput <FormTextInput
label={I18n.t('Password')} label={I18n.t('Password')}
containerStyle={styles.inputContainer} containerStyle={styles.inputContainer}
inputRef={e => { inputRef={e => {
@ -196,7 +196,6 @@ class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
testID='login-view-submit' testID='login-view-submit'
loading={isFetching} loading={isFetching}
disabled={!this.valid()} disabled={!this.valid()}
theme={theme}
style={styles.loginButton} style={styles.loginButton}
/> />
{Accounts_PasswordReset && ( {Accounts_PasswordReset && (
@ -205,7 +204,6 @@ class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
type='secondary' type='secondary'
onPress={this.forgotPassword} onPress={this.forgotPassword}
testID='login-view-forgot-password' testID='login-view-forgot-password'
theme={theme}
color={themes[theme].auxiliaryText} color={themes[theme].auxiliaryText}
fontSize={14} fontSize={14}
/> />

View File

@ -1,43 +1,38 @@
import React from 'react'; import React, { useEffect } from 'react';
import { ScrollView } from 'react-native'; import { ScrollView } from 'react-native';
import { StackNavigationOptions } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import I18n from '../i18n'; import I18n from '../i18n';
import { isIOS } from '../utils/deviceInfo'; import { isIOS } from '../utils/deviceInfo';
import { themes } from '../lib/constants'; import { useTheme } from '../theme';
import { TSupportedThemes, withTheme } from '../theme';
import { ChatsStackParamList } from '../stacks/types'; import { ChatsStackParamList } from '../stacks/types';
import { IBaseScreen } from '../definitions';
interface IMarkdownTableViewProps { type IMarkdownTableViewProps = IBaseScreen<ChatsStackParamList, 'MarkdownTableView'>;
route: RouteProp<ChatsStackParamList, 'MarkdownTableView'>;
theme: TSupportedThemes;
}
class MarkdownTableView extends React.Component<IMarkdownTableViewProps> { const MarkdownTableView = ({ navigation, route }: IMarkdownTableViewProps): React.ReactElement => {
static navigationOptions = (): StackNavigationOptions => ({
title: I18n.t('Table')
});
render() {
const { route, theme } = this.props;
const renderRows = route.params?.renderRows; const renderRows = route.params?.renderRows;
const tableWidth = route.params?.tableWidth; const tableWidth = route.params?.tableWidth;
const { colors } = useTheme();
useEffect(() => {
navigation.setOptions({
title: I18n.t('Table')
});
}, []);
if (isIOS) { if (isIOS) {
return ( return (
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }} contentContainerStyle={{ width: tableWidth }}> <ScrollView style={{ backgroundColor: colors.backgroundColor }} contentContainerStyle={{ width: tableWidth }}>
{renderRows()} {renderRows()}
</ScrollView> </ScrollView>
); );
} }
return ( return (
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}> <ScrollView style={{ backgroundColor: colors.backgroundColor }}>
<ScrollView horizontal>{renderRows()}</ScrollView> <ScrollView horizontal>{renderRows()}</ScrollView>
</ScrollView> </ScrollView>
); );
} };
}
export default withTheme(MarkdownTableView); export default MarkdownTableView;

View File

@ -20,7 +20,17 @@ import getThreadName from '../../lib/methods/getThreadName';
import styles from './styles'; import styles from './styles';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import { IRoomInfoParam } from '../SearchMessagesView'; 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'; import { Services } from '../../lib/services';
interface IMessagesViewProps { interface IMessagesViewProps {
@ -37,36 +47,19 @@ interface IMessagesViewProps {
route: RouteProp<ChatsStackParamList, 'MessagesView'>; route: RouteProp<ChatsStackParamList, 'MessagesView'>;
customEmojis: { [key: string]: IEmoji }; customEmojis: { [key: string]: IEmoji };
theme: TSupportedThemes; theme: TSupportedThemes;
showActionSheet: Function; showActionSheet: (params: { options: string[]; hasCancel: boolean }) => void;
useRealName: boolean; useRealName: boolean;
isMasterDetail: boolean; isMasterDetail: boolean;
} }
interface IMessagesViewState { interface IMessagesViewState {
loading: boolean; loading: boolean;
messages: []; messages: IMessage[];
message?: IMessage;
fileLoading: boolean; fileLoading: boolean;
total: number; 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 { interface IParams {
rid: string; rid: string;
t: SubscriptionType; t: SubscriptionType;
@ -75,24 +68,25 @@ interface IParams {
name?: string; name?: string;
fname?: string; fname?: string;
prid?: string; prid?: string;
room: ISubscription; room?: ISubscription;
jumpToMessageId?: string; jumpToMessageId?: string;
jumpToThreadId?: string; jumpToThreadId?: string;
roomUserId?: string; roomUserId?: string;
} }
class MessagesView extends React.Component<IMessagesViewProps, any> { class MessagesView extends React.Component<IMessagesViewProps, IMessagesViewState> {
private rid: string; private rid: string;
private t: SubscriptionType; private t: SubscriptionType;
private content: any; private content: any;
private room: any; private room?: ISubscription;
constructor(props: IMessagesViewProps) { constructor(props: IMessagesViewProps) {
super(props); super(props);
this.state = { this.state = {
loading: false, loading: false,
messages: [], messages: [],
fileLoading: true fileLoading: true,
total: 0
}; };
this.setHeader(); this.setHeader();
this.rid = props.route.params?.rid; this.rid = props.route.params?.rid;
@ -104,7 +98,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
this.load(); this.load();
} }
shouldComponentUpdate(nextProps: IMessagesViewProps, nextState: any) { shouldComponentUpdate(nextProps: IMessagesViewProps, nextState: IMessagesViewState) {
const { loading, messages, fileLoading } = this.state; const { loading, messages, fileLoading } = this.state;
const { theme } = this.props; const { theme } = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
@ -137,7 +131,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
navigation.navigate('RoomInfoView', navParam); navigation.navigate('RoomInfoView', navParam);
}; };
jumpToMessage = async ({ item }: { item: IMessageItem }) => { jumpToMessage = async ({ item }: { item: IMessage }) => {
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
let params: IParams = { let params: IParams = {
rid: this.rid, rid: this.rid,
@ -165,7 +159,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
defineMessagesViewContent = (name: string) => { defineMessagesViewContent = (name: string) => {
const { user, baseUrl, theme, useRealName } = this.props; const { user, baseUrl, theme, useRealName } = this.props;
const renderItemCommonProps = (item: IMessageItem) => ({ const renderItemCommonProps = (item: TAnyMessageModel) => ({
item, item,
baseUrl, baseUrl,
user, user,
@ -224,8 +218,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
}, },
noDataMsg: I18n.t('No_mentioned_messages'), noDataMsg: I18n.t('No_mentioned_messages'),
testID: 'mentioned-messages-view', testID: 'mentioned-messages-view',
// @ts-ignore TODO: unify IMessage renderItem: (item: TAnyMessageModel) => <Message {...renderItemCommonProps(item)} msg={item.msg} theme={theme} />
renderItem: (item: IMessageItem) => <Message {...renderItemCommonProps(item)} msg={item.msg} theme={theme} />
}, },
// Starred Messages Screen // Starred Messages Screen
Starred: { Starred: {
@ -236,16 +229,15 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
}, },
noDataMsg: I18n.t('No_starred_messages'), noDataMsg: I18n.t('No_starred_messages'),
testID: 'starred-messages-view', testID: 'starred-messages-view',
renderItem: (item: IMessageItem) => ( renderItem: (item: TAnyMessageModel) => (
// @ts-ignore TODO: unify IMessage
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} /> <Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
), ),
action: (message: IMessageItem) => ({ action: (message: IMessage) => ({
title: I18n.t('Unstar'), title: I18n.t('Unstar'),
icon: message.starred ? 'star-filled' : 'star', icon: message.starred ? 'star-filled' : 'star',
onPress: this.handleActionPress onPress: this.handleActionPress
}), }),
handleActionPress: (message: IMessageItem) => Services.toggleStarMessage(message._id, message.starred) handleActionPress: (message: IMessage) => Services.toggleStarMessage(message._id, message.starred)
}, },
// Pinned Messages Screen // Pinned Messages Screen
Pinned: { Pinned: {
@ -256,14 +248,12 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
}, },
noDataMsg: I18n.t('No_pinned_messages'), noDataMsg: I18n.t('No_pinned_messages'),
testID: 'pinned-messages-view', testID: 'pinned-messages-view',
renderItem: (item: IMessageItem) => ( renderItem: (item: TAnyMessageModel) => (
// @ts-ignore TODO: unify IMessage
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} /> <Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
), ),
action: () => ({ title: I18n.t('Unpin'), icon: 'pin', onPress: this.handleActionPress }), action: () => ({ title: I18n.t('Unpin'), icon: 'pin', onPress: this.handleActionPress }),
handleActionPress: (message: IMessageItem) => Services.togglePinMessage(message._id, message.pinned) handleActionPress: (message: IMessage) => Services.togglePinMessage(message._id, message.pinned)
} }
// @ts-ignore
}[name]; }[name];
}; };
@ -316,12 +306,12 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
return null; return null;
}; };
showAttachment = (attachment: any) => { showAttachment = (attachment: IAttachment) => {
const { navigation } = this.props; const { navigation } = this.props;
navigation.navigate('AttachmentView', { attachment }); navigation.navigate('AttachmentView', { attachment });
}; };
onLongPress = (message: IMessageItem) => { onLongPress = (message: IMessage) => {
this.setState({ message }, this.showActionSheet); this.setState({ message }, this.showActionSheet);
}; };
@ -338,7 +328,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
const result = await this.content.handleActionPress(message); const result = await this.content.handleActionPress(message);
if (result.success) { if (result.success) {
this.setState((prevState: IMessagesViewState) => ({ 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 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() { render() {
const { messages, loading } = this.state; 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, baseUrl: state.server.server,
user: getUserSelector(state), user: getUserSelector(state),
customEmojis: state.customEmojis, customEmojis: state.customEmojis,

View File

@ -171,16 +171,11 @@ class ModalBlockView extends React.Component<IModalBlockViewProps, IModalBlockVi
} }
}; };
cancel = async ({ closeModal }: { closeModal?: () => void }) => { cancel = async () => {
const { data } = this.state; const { data } = this.state;
const { appId, viewId, view } = data; const { appId, viewId, view } = data;
// handle tablet case
if (closeModal) {
closeModal();
} else {
Navigation.back(); Navigation.back();
}
try { try {
await triggerCancel({ await triggerCancel({

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FlatList, StyleSheet, TextInputProps, View } from 'react-native'; 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 * as List from '../../../containers/List';
import { themes } from '../../../lib/constants'; import { themes } from '../../../lib/constants';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
@ -51,7 +51,7 @@ const ServerInput = ({
const [focused, setFocused] = useState(false); const [focused, setFocused] = useState(false);
return ( return (
<View style={styles.container}> <View style={styles.container}>
<TextInput <FormTextInput
label={I18n.t('Enter_workspace_URL')} label={I18n.t('Enter_workspace_URL')}
placeholder={I18n.t('Workspace_URL_Example')} placeholder={I18n.t('Workspace_URL_Example')}
containerStyle={styles.inputContainer} containerStyle={styles.inputContainer}

Some files were not shown because too many files have changed in this diff Show More