Merge 4.28.0 into master (#4257)
This commit is contained in:
parent
60cc4cdd89
commit
31ead3c09c
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
open-pull-requests-limit: 25
|
|
@ -144,7 +144,7 @@ android {
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "4.27.1"
|
versionName "4.28.0"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
if (!isFoss) {
|
if (!isFoss) {
|
||||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
|
@ -2,8 +2,16 @@ import { Action } from 'redux';
|
||||||
|
|
||||||
import { CREATE_DISCUSSION } from './actionsTypes';
|
import { CREATE_DISCUSSION } from './actionsTypes';
|
||||||
|
|
||||||
|
export interface ICreateDiscussionRequestData {
|
||||||
|
prid: string;
|
||||||
|
pmid?: string;
|
||||||
|
t_name?: string;
|
||||||
|
reply?: string;
|
||||||
|
users: string[];
|
||||||
|
encrypted?: boolean;
|
||||||
|
}
|
||||||
interface ICreateDiscussionRequest extends Action {
|
interface ICreateDiscussionRequest extends Action {
|
||||||
data: any;
|
data: ICreateDiscussionRequestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICreateDiscussionSuccess extends Action {
|
interface ICreateDiscussionSuccess extends Action {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
|
|
||||||
import { ICustomEmojis } from '../reducers/customEmojis';
|
|
||||||
import { SET_CUSTOM_EMOJIS } from './actionsTypes';
|
import { SET_CUSTOM_EMOJIS } from './actionsTypes';
|
||||||
|
import { ICustomEmojis } from '../definitions';
|
||||||
|
|
||||||
export interface ISetCustomEmojis extends Action {
|
export interface ISetCustomEmojis extends Action {
|
||||||
emojis: ICustomEmojis;
|
emojis: ICustomEmojis;
|
||||||
|
|
|
@ -1,32 +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';
|
||||||
|
|
||||||
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;
|
||||||
|
@ -39,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
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -69,14 +57,12 @@ 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();
|
||||||
};
|
};
|
||||||
|
|
||||||
const show = (options: TActionSheetOptions) => {
|
const show = (options: TActionSheetOptions) => {
|
||||||
|
@ -84,12 +70,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();
|
||||||
|
@ -101,7 +81,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]);
|
||||||
|
|
||||||
|
@ -122,26 +101,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 : {};
|
||||||
|
|
||||||
|
@ -149,42 +120,19 @@ const ActionSheet = React.memo(
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
{isVisible && (
|
{isVisible && (
|
||||||
<>
|
<BottomSheet
|
||||||
<TapGestureHandler onHandlerStateChange={onBackdropPressed}>
|
|
||||||
<Animated.View
|
|
||||||
testID='action-sheet-backdrop'
|
|
||||||
style={[
|
|
||||||
styles.backdrop,
|
|
||||||
{
|
|
||||||
backgroundColor: themes[theme].backdropColor,
|
|
||||||
opacity
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</TapGestureHandler>
|
|
||||||
<ScrollBottomSheet<TActionSheetOptionsItem>
|
|
||||||
testID='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}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
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
|
||||||
|
testID='action-sheet'
|
||||||
|
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;
|
|
@ -2,20 +2,14 @@ import React from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
|
import { TActionSheetOptionsItem } from './Provider';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
export interface IActionSheetItem {
|
export interface IActionSheetItem {
|
||||||
item: {
|
item: TActionSheetOptionsItem;
|
||||||
title: string;
|
|
||||||
icon: string;
|
|
||||||
danger?: boolean;
|
|
||||||
testID?: string;
|
|
||||||
onPress: () => void;
|
|
||||||
right?: Function;
|
|
||||||
};
|
|
||||||
hide(): void;
|
hide(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,25 @@
|
||||||
import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
|
import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
|
||||||
|
|
||||||
import ActionSheet from './ActionSheet';
|
import ActionSheet from './ActionSheet';
|
||||||
|
import { TIconsName } from '../CustomIcon';
|
||||||
|
|
||||||
export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void; danger?: boolean };
|
export type TActionSheetOptionsItem = {
|
||||||
|
title: string;
|
||||||
|
icon: TIconsName;
|
||||||
|
danger?: boolean;
|
||||||
|
testID?: string;
|
||||||
|
onPress: () => void;
|
||||||
|
right?: () => React.ReactElement;
|
||||||
|
};
|
||||||
|
|
||||||
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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react-native';
|
||||||
|
|
||||||
|
import Button from '.';
|
||||||
|
|
||||||
|
const buttonProps = {
|
||||||
|
title: 'Press me!',
|
||||||
|
type: 'primary',
|
||||||
|
onPress: () => {},
|
||||||
|
testID: 'testButton',
|
||||||
|
fontSize: 16,
|
||||||
|
style: {
|
||||||
|
padding: 10,
|
||||||
|
justifyContent: 'center'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stories = storiesOf('Button', module);
|
||||||
|
|
||||||
|
stories.add('primary button', () => <Button {...buttonProps} />);
|
||||||
|
|
||||||
|
stories.add('secondary button', () => <Button {...buttonProps} type='secondary' />);
|
||||||
|
|
||||||
|
stories.add('loading button', () => <Button loading {...buttonProps} />);
|
||||||
|
|
||||||
|
stories.add('disabled button', () => <Button disabled {...buttonProps} />);
|
||||||
|
|
||||||
|
stories.add('disabled loading button', () => <Button disabled loading {...buttonProps} />);
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { fireEvent, render } from '@testing-library/react-native';
|
||||||
|
|
||||||
|
import Button from '.';
|
||||||
|
|
||||||
|
const onPressMock = jest.fn();
|
||||||
|
|
||||||
|
const testProps = {
|
||||||
|
title: 'Press me!',
|
||||||
|
type: 'primary',
|
||||||
|
onPress: onPressMock,
|
||||||
|
testID: 'testButton',
|
||||||
|
initialText: 'Initial text',
|
||||||
|
textAfterPress: 'Button pressed!'
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestButton = ({ loading = false, disabled = false }) => (
|
||||||
|
<View>
|
||||||
|
<Button
|
||||||
|
title={testProps.title}
|
||||||
|
type={testProps.title}
|
||||||
|
onPress={testProps.onPress}
|
||||||
|
testID={testProps.testID}
|
||||||
|
accessibilityLabel={testProps.title}
|
||||||
|
disabled={disabled}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('ButtonTests', () => {
|
||||||
|
test('rendered', async () => {
|
||||||
|
const { findByTestId } = render(<TestButton />);
|
||||||
|
const Button = await findByTestId(testProps.testID);
|
||||||
|
expect(Button).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rendered with correct title', async () => {
|
||||||
|
const { findByText } = render(<TestButton />);
|
||||||
|
const ButtonTitle = await findByText(testProps.title);
|
||||||
|
expect(ButtonTitle).toBeTruthy();
|
||||||
|
expect(ButtonTitle.props.children).toEqual(testProps.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('find button using accessibilityLabel', async () => {
|
||||||
|
const { findByA11yLabel } = render(<TestButton />);
|
||||||
|
const Button = await findByA11yLabel(testProps.title);
|
||||||
|
expect(Button).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('title not visible while loading', async () => {
|
||||||
|
const { queryByText } = render(<TestButton loading={true} />);
|
||||||
|
const ButtonTitle = await queryByText(testProps.title);
|
||||||
|
expect(ButtonTitle).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not trigger onPress on disabled button', async () => {
|
||||||
|
const { findByTestId } = render(<TestButton disabled={true} />);
|
||||||
|
const Button = await findByTestId(testProps.testID);
|
||||||
|
fireEvent.press(Button);
|
||||||
|
expect(onPressMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should trigger onPress function on button press', async () => {
|
||||||
|
const { findByTestId } = render(<TestButton />);
|
||||||
|
const Button = await findByTestId(testProps.testID);
|
||||||
|
fireEvent.press(Button);
|
||||||
|
expect(onPressMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Storyshots Button disabled button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"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\\":\\"Press me!\\",\\"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\\":\\"Press me!\\",\\"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\\":\\"Press me!\\",\\"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\\":\\"Press me!\\",\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
|
@ -1,25 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { 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 { useTheme } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
|
|
||||||
interface IButtonProps {
|
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({
|
||||||
|
@ -31,7 +26,6 @@ const styles = StyleSheet.create({
|
||||||
marginBottom: 12
|
marginBottom: 12
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
fontSize: 16,
|
|
||||||
...sharedStyles.textMedium,
|
...sharedStyles.textMedium,
|
||||||
...sharedStyles.textAlignCenter
|
...sharedStyles.textAlignCenter
|
||||||
},
|
},
|
||||||
|
@ -40,21 +34,23 @@ 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,
|
title,
|
||||||
loading: false
|
onPress,
|
||||||
};
|
backgroundColor,
|
||||||
|
color,
|
||||||
render() {
|
style,
|
||||||
const { title, type, onPress, disabled, backgroundColor, color, loading, style, theme, fontSize, styleText, ...otherProps } =
|
styleText,
|
||||||
this.props;
|
...otherProps
|
||||||
|
}: 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;
|
||||||
}
|
}
|
||||||
|
@ -65,9 +61,7 @@ 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
|
||||||
]}
|
]}
|
||||||
|
@ -76,11 +70,12 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
|
||||||
{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;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from './CustomIcon';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import { useTheme } from '../theme';
|
import { useTheme } from '../theme';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { createIconSetFromIcoMoon } from 'react-native-vector-icons';
|
||||||
|
import { TextProps } from 'react-native';
|
||||||
|
|
||||||
|
import { mappedIcons } from './mappedIcons';
|
||||||
|
|
||||||
|
const icoMoonConfig = require('./selection.json');
|
||||||
|
|
||||||
|
export const IconSet = createIconSetFromIcoMoon(icoMoonConfig, 'custom', 'custom.ttf');
|
||||||
|
|
||||||
|
export type TIconsName = keyof typeof mappedIcons;
|
||||||
|
|
||||||
|
interface ICustomIcon extends TextProps {
|
||||||
|
name: TIconsName;
|
||||||
|
size: number;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomIcon = ({ name, size, color, ...props }: ICustomIcon) => (
|
||||||
|
// @ts-ignore TODO remove this after update @types/react-native to 0.65.0
|
||||||
|
<IconSet name={name} size={size} color={color} {...props} />
|
||||||
|
);
|
||||||
|
export { CustomIcon };
|
|
@ -0,0 +1,202 @@
|
||||||
|
export const mappedIcons = {
|
||||||
|
attach: 59676,
|
||||||
|
link: 59752,
|
||||||
|
'status-away': 59741,
|
||||||
|
'status-busy': 59742,
|
||||||
|
'status-loading': 59743,
|
||||||
|
'status-offline': 59744,
|
||||||
|
'status-online': 59745,
|
||||||
|
'teams-private': 59750,
|
||||||
|
'channel-auto-join': 59746,
|
||||||
|
'channel-move-to-team': 59747,
|
||||||
|
'lock-filled': 59748,
|
||||||
|
locker: 59749,
|
||||||
|
teams: 59751,
|
||||||
|
shield: 59661,
|
||||||
|
ignore: 59740,
|
||||||
|
'checkbox-unchecked': 59648,
|
||||||
|
'checkbox-checked': 59649,
|
||||||
|
'github-monochromatic': 59650,
|
||||||
|
'gitlab-monochromatic': 59651,
|
||||||
|
'google-monochromatic': 59652,
|
||||||
|
'linkedin-monochromatic': 59653,
|
||||||
|
'meteor-monochromatic': 59654,
|
||||||
|
'twitter-monochromatic': 59655,
|
||||||
|
administration: 59657,
|
||||||
|
'adobe-reader-monochromatic': 59658,
|
||||||
|
'all-contacts-in-channels': 59659,
|
||||||
|
'all-contacts-in-queue': 59660,
|
||||||
|
'apple-monochromatic': 59662,
|
||||||
|
apps: 59663,
|
||||||
|
'arrow-back': 59664,
|
||||||
|
'arrow-collapse': 59665,
|
||||||
|
'arrow-decrease': 59666,
|
||||||
|
'arrow-down-box': 59667,
|
||||||
|
'arrow-down-circle': 59668,
|
||||||
|
'arrow-down': 59669,
|
||||||
|
'arrow-expand': 59670,
|
||||||
|
'arrow-increase': 59671,
|
||||||
|
'arrow-looping': 59672,
|
||||||
|
'arrow-return': 59673,
|
||||||
|
'arrow-up-box': 59674,
|
||||||
|
'arrow-up': 59675,
|
||||||
|
'audio-disabled': 59677,
|
||||||
|
'audio-unavailable': 59678,
|
||||||
|
audio: 59679,
|
||||||
|
auditing: 59680,
|
||||||
|
auth: 59681,
|
||||||
|
avatar: 59682,
|
||||||
|
backspace: 59683,
|
||||||
|
bold: 59684,
|
||||||
|
book: 59685,
|
||||||
|
business: 59686,
|
||||||
|
calendar: 59687,
|
||||||
|
'camera-disabled': 59688,
|
||||||
|
'camera-filled': 59689,
|
||||||
|
'camera-photo': 59690,
|
||||||
|
'camera-unavailable': 59691,
|
||||||
|
camera: 59692,
|
||||||
|
'canned-response': 59693,
|
||||||
|
card: 59694,
|
||||||
|
'channel-private': 59695,
|
||||||
|
'channel-public': 59696,
|
||||||
|
'chat-close': 59697,
|
||||||
|
'chat-forward': 59698,
|
||||||
|
check: 59699,
|
||||||
|
'chevron-down': 59700,
|
||||||
|
'chevron-left-big': 59701,
|
||||||
|
'chevron-left': 59702,
|
||||||
|
'chevron-right': 59703,
|
||||||
|
'chevron-up': 59704,
|
||||||
|
'circle-check': 59705,
|
||||||
|
clipboard: 59706,
|
||||||
|
clock: 59707,
|
||||||
|
close: 59708,
|
||||||
|
'cloud-connectivity': 59709,
|
||||||
|
code: 59710,
|
||||||
|
contacts: 59711,
|
||||||
|
copy: 59712,
|
||||||
|
create: 59713,
|
||||||
|
dashboard: 59714,
|
||||||
|
delete: 59715,
|
||||||
|
desktop: 59716,
|
||||||
|
dialpad: 59717,
|
||||||
|
'directory-disabled': 59718,
|
||||||
|
directory: 59719,
|
||||||
|
discussions: 59720,
|
||||||
|
document: 59721,
|
||||||
|
donner: 59722,
|
||||||
|
download: 59723,
|
||||||
|
edit: 59724,
|
||||||
|
'emoji-bad-mood': 59725,
|
||||||
|
'emoji-neutral-mood': 59726,
|
||||||
|
emoji: 59727,
|
||||||
|
encrypted: 59728,
|
||||||
|
'engagement-dashboard': 59729,
|
||||||
|
'enterprise-feature': 59730,
|
||||||
|
'facebook-monochromatic': 59731,
|
||||||
|
'file-document': 59732,
|
||||||
|
'file-sheet': 59733,
|
||||||
|
filter: 59734,
|
||||||
|
fingerprint: 59735,
|
||||||
|
flag: 59736,
|
||||||
|
folder: 59737,
|
||||||
|
game: 59738,
|
||||||
|
'giphy-monochromatic': 59739,
|
||||||
|
'google-drive-monochromatic': 59756,
|
||||||
|
'group-by-type': 59757,
|
||||||
|
hamburguer: 59758,
|
||||||
|
history: 59759,
|
||||||
|
home: 59760,
|
||||||
|
image: 59761,
|
||||||
|
info: 59762,
|
||||||
|
'input-clear': 59763,
|
||||||
|
instance: 59764,
|
||||||
|
italic: 59765,
|
||||||
|
'jump-backward': 59766,
|
||||||
|
'jump-forward': 59767,
|
||||||
|
'jump-to-message': 59768,
|
||||||
|
kebab: 59769,
|
||||||
|
keyboard: 59770,
|
||||||
|
language: 59771,
|
||||||
|
'live-streaming': 59773,
|
||||||
|
live: 59774,
|
||||||
|
'livechat-monochromatic': 59775,
|
||||||
|
'log-view': 59778,
|
||||||
|
login: 59779,
|
||||||
|
logout: 59780,
|
||||||
|
mail: 59781,
|
||||||
|
marketplace: 59782,
|
||||||
|
meatballs: 59783,
|
||||||
|
mention: 59784,
|
||||||
|
'message-disabled': 59785,
|
||||||
|
message: 59786,
|
||||||
|
'microphone-disabled': 59787,
|
||||||
|
microphone: 59788,
|
||||||
|
mobile: 59789,
|
||||||
|
moon: 59790,
|
||||||
|
'move-to-the-queue': 59791,
|
||||||
|
'musical-note': 59792,
|
||||||
|
'new-window': 59793,
|
||||||
|
'notification-disabled': 59794,
|
||||||
|
notification: 59795,
|
||||||
|
omnichannel: 59796,
|
||||||
|
order: 59797,
|
||||||
|
'ordering-ascending': 59798,
|
||||||
|
'ordering-descending': 59800,
|
||||||
|
'pause-filled': 59802,
|
||||||
|
pause: 59803,
|
||||||
|
'phone-disabled': 59804,
|
||||||
|
'phone-end': 59805,
|
||||||
|
phone: 59806,
|
||||||
|
'pin-map': 59807,
|
||||||
|
pin: 59808,
|
||||||
|
Pipe: 59809,
|
||||||
|
'play-filled': 59810,
|
||||||
|
play: 59811,
|
||||||
|
prune: 59817,
|
||||||
|
queue: 59818,
|
||||||
|
quote: 59819,
|
||||||
|
'reaction-add': 59820,
|
||||||
|
record: 59821,
|
||||||
|
refresh: 59822,
|
||||||
|
search: 59823,
|
||||||
|
'send-filled': 59824,
|
||||||
|
send: 59825,
|
||||||
|
settings: 59826,
|
||||||
|
share: 59827,
|
||||||
|
'shield-check': 59828,
|
||||||
|
'shield-alt': 59829,
|
||||||
|
signal: 59830,
|
||||||
|
'sort-az': 59831,
|
||||||
|
sort: 59832,
|
||||||
|
'star-filled': 59833,
|
||||||
|
star: 59834,
|
||||||
|
strike: 59846,
|
||||||
|
sun: 59847,
|
||||||
|
support: 59848,
|
||||||
|
team: 59849,
|
||||||
|
threads: 59850,
|
||||||
|
total: 59851,
|
||||||
|
transcript: 59852,
|
||||||
|
underline: 59853,
|
||||||
|
undo: 59854,
|
||||||
|
Unlimited: 59855,
|
||||||
|
'unread-on-top-disabled': 59856,
|
||||||
|
'unread-on-top': 59857,
|
||||||
|
upload: 59858,
|
||||||
|
'user-add': 59859,
|
||||||
|
'user-forward': 59860,
|
||||||
|
user: 59861,
|
||||||
|
'view-condensed': 59862,
|
||||||
|
'view-extended': 59863,
|
||||||
|
'view-medium': 59864,
|
||||||
|
'waiting-on-me': 59865,
|
||||||
|
warning: 59866,
|
||||||
|
'whatsapp-monochromatic': 59868,
|
||||||
|
'wordpress-monochromatic': 59656,
|
||||||
|
workspaces: 59870,
|
||||||
|
zip: 59871,
|
||||||
|
add: 59872,
|
||||||
|
sms: 59753
|
||||||
|
};
|
|
@ -17,8 +17,8 @@ interface IDirectoryItemLabel {
|
||||||
|
|
||||||
interface IDirectoryItem {
|
interface IDirectoryItem {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description?: string;
|
||||||
avatar: string;
|
avatar?: string;
|
||||||
type: string;
|
type: string;
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
testID: string;
|
testID: string;
|
||||||
|
|
|
@ -8,7 +8,6 @@ const CustomEmoji = React.memo(
|
||||||
<FastImage
|
<FastImage
|
||||||
style={style}
|
style={style}
|
||||||
source={{
|
source={{
|
||||||
// @ts-ignore
|
|
||||||
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`,
|
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`,
|
||||||
priority: FastImage.priority.high
|
priority: FastImage.priority.high
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -26,16 +26,17 @@ const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
class EmojiCategory extends React.Component<Partial<IEmojiCategory>> {
|
class EmojiCategory extends React.Component<IEmojiCategory> {
|
||||||
renderItem(emoji: any) {
|
renderItem(emoji: IEmoji) {
|
||||||
const { baseUrl, onEmojiSelected } = this.props;
|
const { baseUrl, onEmojiSelected } = this.props;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
|
// @ts-ignore
|
||||||
key={emoji && emoji.isCustom ? emoji.content : emoji}
|
key={emoji && emoji.isCustom ? emoji.content : emoji}
|
||||||
onPress={() => onEmojiSelected!(emoji)}
|
onPress={() => onEmojiSelected(emoji)}
|
||||||
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}>
|
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}>
|
||||||
{renderEmoji(emoji, EMOJI_SIZE, baseUrl!)}
|
{renderEmoji(emoji, EMOJI_SIZE, baseUrl)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +52,6 @@ class EmojiCategory extends React.Component<Partial<IEmojiCategory>> {
|
||||||
const marginHorizontal = (width - numColumns * EMOJI_SIZE) / 2;
|
const marginHorizontal = (width - numColumns * EMOJI_SIZE) / 2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
|
||||||
<FlatList
|
<FlatList
|
||||||
contentContainerStyle={{ marginHorizontal }}
|
contentContainerStyle={{ marginHorizontal }}
|
||||||
// rerender FlatList in case of width changes
|
// rerender FlatList in case of width changes
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, TouchableOpacity, View } from 'react-native';
|
import { StyleProp, Text, TextStyle, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
interface ITabBarProps {
|
interface ITabBarProps {
|
||||||
goToPage: Function;
|
goToPage?: (page: number) => void;
|
||||||
activeTab: number;
|
activeTab?: number;
|
||||||
tabs: [];
|
tabs?: string[];
|
||||||
tabEmojiStyle: object;
|
tabEmojiStyle: StyleProp<TextStyle>;
|
||||||
theme: TSupportedThemes;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TabBar extends React.Component<Partial<ITabBarProps>> {
|
export default class TabBar extends React.Component<ITabBarProps> {
|
||||||
shouldComponentUpdate(nextProps: any) {
|
shouldComponentUpdate(nextProps: ITabBarProps) {
|
||||||
const { activeTab, theme } = this.props;
|
const { activeTab, theme } = this.props;
|
||||||
if (nextProps.activeTab !== activeTab) {
|
if (nextProps.activeTab !== activeTab) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -30,16 +30,20 @@ export default class TabBar extends React.Component<Partial<ITabBarProps>> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.tabsContainer}>
|
<View style={styles.tabsContainer}>
|
||||||
{tabs!.map((tab, i) => (
|
{tabs?.map((tab, i) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
key={tab}
|
key={tab}
|
||||||
onPress={() => goToPage!(i)}
|
onPress={() => {
|
||||||
|
if (goToPage) {
|
||||||
|
goToPage(i);
|
||||||
|
}
|
||||||
|
}}
|
||||||
style={styles.tab}
|
style={styles.tab}
|
||||||
testID={`reaction-picker-${tab}`}>
|
testID={`reaction-picker-${tab}`}>
|
||||||
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
|
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
|
||||||
{activeTab === i ? (
|
{activeTab === i ? (
|
||||||
<View style={[styles.activeTabLine, { backgroundColor: themes[theme!].tintColor }]} />
|
<View style={[styles.activeTabLine, { backgroundColor: themes[theme].tintColor }]} />
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.tabLine} />
|
<View style={styles.tabLine} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const emojisByCategory: any = {
|
export const emojisByCategory = {
|
||||||
people: [
|
people: [
|
||||||
'grinning',
|
'grinning',
|
||||||
'grimacing',
|
'grimacing',
|
|
@ -1,38 +1,34 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { View } from 'react-native';
|
import { StyleProp, TextStyle, View } from 'react-native';
|
||||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
import { ImageStyle } from '@rocket.chat/react-native-fast-image';
|
||||||
|
|
||||||
import TabBar from './TabBar';
|
import TabBar from './TabBar';
|
||||||
import EmojiCategory from './EmojiCategory';
|
import EmojiCategory from './EmojiCategory';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import categories from './categories';
|
import categories from './categories';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { emojisByCategory } from '../../emojis';
|
import { emojisByCategory } from './emojis';
|
||||||
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { TSupportedThemes, withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import { IEmoji } from '../../definitions/IEmoji';
|
import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||||
|
|
||||||
const scrollProps = {
|
|
||||||
keyboardShouldPersistTaps: 'always',
|
|
||||||
keyboardDismissMode: 'none'
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IEmojiPickerProps {
|
interface IEmojiPickerProps {
|
||||||
isMessageContainsOnlyEmoji: boolean;
|
isMessageContainsOnlyEmoji?: boolean;
|
||||||
getCustomEmoji?: Function;
|
getCustomEmoji?: TGetCustomEmoji;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
customEmojis?: any;
|
customEmojis: ICustomEmojis;
|
||||||
style: object;
|
style?: StyleProp<ImageStyle>;
|
||||||
theme: TSupportedThemes;
|
theme: TSupportedThemes;
|
||||||
onEmojiSelected?: ((emoji: any) => void) | ((keyboardId: any, params?: any) => void);
|
onEmojiSelected: (emoji: string, shortname?: string) => void;
|
||||||
tabEmojiStyle?: object;
|
tabEmojiStyle?: StyleProp<TextStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IEmojiPickerState {
|
interface IEmojiPickerState {
|
||||||
|
@ -65,7 +61,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
this.setState({ show: true });
|
this.setState({ show: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: any, nextState: any) {
|
shouldComponentUpdate(nextProps: IEmojiPickerProps, nextState: IEmojiPickerState) {
|
||||||
const { frequentlyUsed, show, width } = this.state;
|
const { frequentlyUsed, show, width } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
|
@ -92,12 +88,12 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
extension: emoji.extension,
|
extension: emoji.extension,
|
||||||
isCustom: true
|
isCustom: true
|
||||||
});
|
});
|
||||||
onEmojiSelected!(`:${emoji.content}:`);
|
onEmojiSelected(`:${emoji.content}:`);
|
||||||
} else {
|
} else {
|
||||||
const content = emoji;
|
const content = emoji;
|
||||||
this._addFrequentlyUsed({ content, isCustom: false });
|
this._addFrequentlyUsed({ content, isCustom: false });
|
||||||
const shortname = `:${emoji}:`;
|
const shortname = `:${emoji}:`;
|
||||||
onEmojiSelected!(shortnameToUnicode(shortname), shortname);
|
onEmojiSelected(shortnameToUnicode(shortname), shortname);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -107,9 +103,8 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
_addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => {
|
_addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
const freqEmojiCollection = db.get('frequently_used_emojis');
|
||||||
let freqEmojiRecord: any;
|
let freqEmojiRecord: TFrequentlyUsedEmojiModel;
|
||||||
try {
|
try {
|
||||||
// @ts-ignore
|
|
||||||
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
|
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
@ -117,11 +112,13 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
|
|
||||||
await db.write(async () => {
|
await db.write(async () => {
|
||||||
if (freqEmojiRecord) {
|
if (freqEmojiRecord) {
|
||||||
await freqEmojiRecord.update((f: any) => {
|
await freqEmojiRecord.update(f => {
|
||||||
|
if (f.count) {
|
||||||
f.count += 1;
|
f.count += 1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await freqEmojiCollection.create((f: any) => {
|
await freqEmojiCollection.create(f => {
|
||||||
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
|
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
|
||||||
Object.assign(f, emoji);
|
Object.assign(f, emoji);
|
||||||
f.count = 1;
|
f.count = 1;
|
||||||
|
@ -149,7 +146,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
}
|
}
|
||||||
}: any) => this.setState({ width });
|
}: any) => this.setState({ width });
|
||||||
|
|
||||||
renderCategory(category: any, i: number, label: string) {
|
renderCategory(category: keyof typeof emojisByCategory, i: number, label: string) {
|
||||||
const { frequentlyUsed, customEmojis, width } = this.state;
|
const { frequentlyUsed, customEmojis, width } = this.state;
|
||||||
const { baseUrl } = this.props;
|
const { baseUrl } = this.props;
|
||||||
|
|
||||||
|
@ -166,7 +163,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
emojis={emojis}
|
emojis={emojis}
|
||||||
onEmojiSelected={(emoji: IEmoji) => this.onEmojiSelected(emoji)}
|
onEmojiSelected={(emoji: IEmoji) => this.onEmojiSelected(emoji)}
|
||||||
style={styles.categoryContainer}
|
style={styles.categoryContainer}
|
||||||
width={width!}
|
width={width}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
tabLabel={label}
|
tabLabel={label}
|
||||||
/>
|
/>
|
||||||
|
@ -184,10 +181,12 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
<View onLayout={this.onLayout} style={{ flex: 1 }}>
|
<View onLayout={this.onLayout} style={{ flex: 1 }}>
|
||||||
<ScrollableTabView
|
<ScrollableTabView
|
||||||
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
|
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
|
||||||
/* @ts-ignore*/
|
contentProps={{
|
||||||
contentProps={scrollProps}
|
keyboardShouldPersistTaps: 'always',
|
||||||
|
keyboardDismissMode: 'none'
|
||||||
|
}}
|
||||||
style={{ backgroundColor: themes[theme].focusedBackground }}>
|
style={{ backgroundColor: themes[theme].focusedBackground }}>
|
||||||
{categories.tabs.map((tab, i) =>
|
{categories.tabs.map((tab: any, i) =>
|
||||||
i === 0 && frequentlyUsed.length === 0
|
i === 0 && frequentlyUsed.length === 0
|
||||||
? null // when no frequentlyUsed don't show the tab
|
? null // when no frequentlyUsed don't show the tab
|
||||||
: this.renderCategory(tab.category, i, tab.tabLabel)
|
: this.renderCategory(tab.category, i, tab.tabLabel)
|
||||||
|
@ -198,9 +197,8 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
customEmojis: state.customEmojis
|
customEmojis: state.customEmojis
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO - remove this as any, at the new PR to fix the HOC erros
|
export default connect(mapStateToProps)(withTheme(EmojiPicker));
|
||||||
export default connect(mapStateToProps)(withTheme(EmojiPicker)) as any;
|
|
||||||
|
|
|
@ -2,14 +2,14 @@ import React from 'react';
|
||||||
import { Platform, StyleSheet, Text } from 'react-native';
|
import { Platform, StyleSheet, Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
|
||||||
interface IHeaderButtonItem {
|
interface IHeaderButtonItem {
|
||||||
title?: string;
|
title?: string;
|
||||||
iconName?: string;
|
iconName?: TIconsName;
|
||||||
onPress?: <T>(arg: T) => void;
|
onPress?: <T>(arg: T) => void;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
badge?(): void;
|
badge?(): void;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Notifier } from 'react-native-notifier';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
|
@ -136,7 +136,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie
|
||||||
</>
|
</>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
<Touchable onPress={hideNotification} hitSlop={BUTTON_HIT_SLOP} background={Touchable.SelectableBackgroundBorderless()}>
|
<Touchable onPress={hideNotification} hitSlop={BUTTON_HIT_SLOP} background={Touchable.SelectableBackgroundBorderless()}>
|
||||||
<CustomIcon name='close' style={[styles.close, { color: themes[theme].titleText }]} size={20} />
|
<CustomIcon name='close' size={20} color={themes[theme].titleText} style={styles.close} />
|
||||||
</Touchable>
|
</Touchable>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,12 +2,12 @@ import React from 'react';
|
||||||
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
|
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { ICON_SIZE } from './constants';
|
import { ICON_SIZE } from './constants';
|
||||||
|
|
||||||
interface IListIcon {
|
interface IListIcon {
|
||||||
name: string;
|
name: TIconsName;
|
||||||
color?: string | null;
|
color?: string | null;
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import I18n from '../../i18n';
|
||||||
import { Icon } from '.';
|
import { Icon } from '.';
|
||||||
import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants';
|
import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants';
|
||||||
import { useDimensions } from '../../dimensions';
|
import { useDimensions } from '../../dimensions';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -100,7 +100,7 @@ const Content = React.memo(
|
||||||
{translateTitle && title ? I18n.t(title) : title}
|
{translateTitle && title ? I18n.t(title) : title}
|
||||||
</Text>
|
</Text>
|
||||||
{alert ? (
|
{alert ? (
|
||||||
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
|
<CustomIcon name='info' size={ICON_SIZE} color={themes[theme].dangerColor} style={styles.alertIcon} />
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
{subtitle ? (
|
{subtitle ? (
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Animated, Modal, StyleSheet, View } from 'react-native';
|
import { Modal, StyleSheet, View, PixelRatio } from 'react-native';
|
||||||
|
import Animated, {
|
||||||
|
cancelAnimation,
|
||||||
|
Extrapolate,
|
||||||
|
interpolate,
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
withRepeat,
|
||||||
|
withSequence,
|
||||||
|
withTiming
|
||||||
|
} from 'react-native-reanimated';
|
||||||
|
|
||||||
import { TSupportedThemes, withTheme } from '../theme';
|
import { useTheme } from '../theme';
|
||||||
import { themes } from '../lib/constants';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -11,100 +20,37 @@ const styles = StyleSheet.create({
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
width: 100,
|
width: PixelRatio.get() * 40,
|
||||||
height: 100,
|
height: PixelRatio.get() * 40,
|
||||||
resizeMode: 'contain'
|
resizeMode: 'contain'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ILoadingProps {
|
interface ILoadingProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
theme?: TSupportedThemes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ILoadingState {
|
const Loading = ({ visible }: ILoadingProps): React.ReactElement => {
|
||||||
scale: Animated.Value;
|
const opacity = useSharedValue(0);
|
||||||
opacity: Animated.Value;
|
const scale = useSharedValue(1);
|
||||||
}
|
const { colors } = useTheme();
|
||||||
|
|
||||||
class Loading extends React.PureComponent<ILoadingProps, ILoadingState> {
|
|
||||||
state = {
|
|
||||||
scale: new Animated.Value(1),
|
|
||||||
opacity: new Animated.Value(0)
|
|
||||||
};
|
|
||||||
|
|
||||||
private opacityAnimation?: Animated.CompositeAnimation;
|
|
||||||
|
|
||||||
private scaleAnimation?: Animated.CompositeAnimation;
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { opacity, scale } = this.state;
|
|
||||||
const { visible } = this.props;
|
|
||||||
|
|
||||||
this.opacityAnimation = Animated.timing(opacity, {
|
|
||||||
toValue: 1,
|
|
||||||
duration: 200,
|
|
||||||
useNativeDriver: true
|
|
||||||
});
|
|
||||||
this.scaleAnimation = Animated.loop(
|
|
||||||
Animated.sequence([
|
|
||||||
Animated.timing(scale, {
|
|
||||||
toValue: 0,
|
|
||||||
duration: 1000,
|
|
||||||
useNativeDriver: true
|
|
||||||
}),
|
|
||||||
Animated.timing(scale, {
|
|
||||||
toValue: 1,
|
|
||||||
duration: 1000,
|
|
||||||
useNativeDriver: true
|
|
||||||
})
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this.startAnimations();
|
opacity.value = withTiming(1, {
|
||||||
}
|
duration: 200
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: ILoadingProps) {
|
|
||||||
const { visible } = this.props;
|
|
||||||
if (visible && visible !== prevProps.visible) {
|
|
||||||
this.startAnimations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.opacityAnimation && this.opacityAnimation.stop) {
|
|
||||||
this.opacityAnimation.stop();
|
|
||||||
}
|
|
||||||
if (this.scaleAnimation && this.scaleAnimation.stop) {
|
|
||||||
this.scaleAnimation.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startAnimations() {
|
|
||||||
if (this.opacityAnimation && this.opacityAnimation.start) {
|
|
||||||
this.opacityAnimation.start();
|
|
||||||
}
|
|
||||||
if (this.scaleAnimation && this.scaleAnimation.start) {
|
|
||||||
this.scaleAnimation.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { opacity, scale } = this.state;
|
|
||||||
const { visible, theme } = this.props;
|
|
||||||
|
|
||||||
const scaleAnimation = scale.interpolate({
|
|
||||||
inputRange: [0, 0.5, 1],
|
|
||||||
outputRange: [1, 1.1, 1]
|
|
||||||
});
|
});
|
||||||
|
scale.value = withRepeat(withSequence(withTiming(0, { duration: 1000 }), withTiming(1, { duration: 1000 })), -1);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
cancelAnimation(scale);
|
||||||
|
};
|
||||||
|
}, [opacity, scale, visible]);
|
||||||
|
|
||||||
const opacityAnimation = opacity.interpolate({
|
const animatedOpacity = useAnimatedStyle(() => ({
|
||||||
inputRange: [0, 1],
|
opacity: interpolate(opacity.value, [0, 1], [0, colors.backdropOpacity], Extrapolate.CLAMP)
|
||||||
outputRange: [0, themes[theme!].backdropOpacity],
|
}));
|
||||||
extrapolate: 'clamp'
|
const animatedScale = useAnimatedStyle(() => ({ transform: [{ scale: interpolate(scale.value, [0, 0.5, 1], [1, 1.1, 1]) }] }));
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal visible={visible} transparent onRequestClose={() => {}}>
|
<Modal visible={visible} transparent onRequestClose={() => {}}>
|
||||||
|
@ -113,28 +59,15 @@ class Loading extends React.PureComponent<ILoadingProps, ILoadingState> {
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
...StyleSheet.absoluteFillObject,
|
...StyleSheet.absoluteFillObject,
|
||||||
backgroundColor: themes[theme!].backdropColor,
|
backgroundColor: colors.backdropColor
|
||||||
opacity: opacityAnimation
|
},
|
||||||
}
|
animatedOpacity
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Animated.Image
|
|
||||||
source={require('../static/images/logo.png')}
|
|
||||||
style={[
|
|
||||||
styles.image,
|
|
||||||
{
|
|
||||||
transform: [
|
|
||||||
{
|
|
||||||
scale: scaleAnimation
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
<Animated.Image source={require('../static/images/logo.png')} style={[styles.image, animatedScale]} />
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default withTheme(Loading);
|
export default Loading;
|
||||||
|
|
|
@ -14,11 +14,11 @@ import Touch from '../utils/touch';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import random from '../utils/random';
|
import random from '../utils/random';
|
||||||
import { events, logEvent } from '../utils/log';
|
import { events, logEvent } from '../utils/log';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import { CustomIcon, TIconsName } from './CustomIcon';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
|
||||||
import { IServices } from '../selectors/login';
|
import { IServices } from '../selectors/login';
|
||||||
import { OutsideParamList } from '../stacks/types';
|
import { OutsideParamList } from '../stacks/types';
|
||||||
import { IApplicationState } from '../definitions';
|
import { IApplicationState } from '../definitions';
|
||||||
|
import { Services } from '../lib/services';
|
||||||
|
|
||||||
const BUTTON_HEIGHT = 48;
|
const BUTTON_HEIGHT = 48;
|
||||||
const SERVICE_HEIGHT = 58;
|
const SERVICE_HEIGHT = 58;
|
||||||
|
@ -248,7 +248,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
|
||||||
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
AppleAuthentication.AppleAuthenticationScope.EMAIL
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
await RocketChat.loginOAuthOrSso({ fullName, email, identityToken });
|
await Services.loginOAuthOrSso({ fullName, email, identityToken });
|
||||||
} catch {
|
} catch {
|
||||||
logEvent(events.ENTER_WITH_APPLE_F);
|
logEvent(events.ENTER_WITH_APPLE_F);
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -345,7 +344,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
|
||||||
const { CAS_enabled, theme } = this.props;
|
const { CAS_enabled, theme } = this.props;
|
||||||
let { name } = service;
|
let { name } = service;
|
||||||
name = name === 'meteor-developer' ? 'meteor' : name;
|
name = name === 'meteor-developer' ? 'meteor' : name;
|
||||||
const icon = `${name}-monochromatic`;
|
const icon = `${name}-monochromatic` as TIconsName;
|
||||||
const isSaml = service.service === 'saml';
|
const isSaml = service.service === 'saml';
|
||||||
let onPress = () => {};
|
let onPress = () => {};
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,14 @@ import { FlatList, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
import { TSupportedThemes, useTheme } from '../../theme';
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { Button } from '../ActionSheet';
|
import { Button } from '../ActionSheet';
|
||||||
import { useDimensions } from '../../dimensions';
|
import { useDimensions } from '../../dimensions';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji';
|
import { TAnyMessageModel, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||||
import { TAnyMessageModel } from '../../definitions';
|
|
||||||
import { IEmoji } from '../../definitions/IEmoji';
|
|
||||||
|
|
||||||
type TItem = TFrequentlyUsedEmojiModel | string;
|
type TItem = TFrequentlyUsedEmojiModel | string;
|
||||||
|
|
||||||
|
@ -83,7 +81,7 @@ const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => {
|
||||||
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||||
theme={theme}>
|
theme={theme}>
|
||||||
{emojiModel?.isCustom ? (
|
{emojiModel?.isCustom ? (
|
||||||
<CustomEmoji style={styles.customEmoji} emoji={emojiModel as IEmoji} baseUrl={server} />
|
<CustomEmoji style={styles.customEmoji} emoji={emojiModel} baseUrl={server} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${emoji}:`)}</Text>
|
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${emoji}:`)}</Text>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import Clipboard from '@react-native-clipboard/clipboard';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import RocketChat from '../../lib/rocketchat';
|
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import log, { logEvent } from '../../utils/log';
|
import log, { logEvent } from '../../utils/log';
|
||||||
|
@ -17,8 +16,10 @@ import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet';
|
||||||
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
||||||
import events from '../../utils/log/events';
|
import events from '../../utils/log/events';
|
||||||
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||||
|
import { getPermalinkMessage, hasPermission } from '../../lib/methods';
|
||||||
|
import { Services } from '../../lib/services';
|
||||||
|
|
||||||
export interface IMessageActions {
|
export interface IMessageActionsProps {
|
||||||
room: TSubscriptionModel;
|
room: TSubscriptionModel;
|
||||||
tmid?: string;
|
tmid?: string;
|
||||||
user: Pick<ILoggedUser, 'id'>;
|
user: Pick<ILoggedUser, 'id'>;
|
||||||
|
@ -42,8 +43,12 @@ export interface IMessageActions {
|
||||||
pinMessagePermission?: string[];
|
pinMessagePermission?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IMessageActions {
|
||||||
|
showMessageActions: (message: TAnyMessageModel) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
const MessageActions = React.memo(
|
const MessageActions = React.memo(
|
||||||
forwardRef(
|
forwardRef<IMessageActions, IMessageActionsProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
room,
|
room,
|
||||||
|
@ -67,7 +72,7 @@ const MessageActions = React.memo(
|
||||||
deleteMessagePermission,
|
deleteMessagePermission,
|
||||||
forceDeleteMessagePermission,
|
forceDeleteMessagePermission,
|
||||||
pinMessagePermission
|
pinMessagePermission
|
||||||
}: IMessageActions,
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
let permissions = {
|
let permissions = {
|
||||||
|
@ -81,7 +86,7 @@ const MessageActions = React.memo(
|
||||||
const getPermissions = async () => {
|
const getPermissions = async () => {
|
||||||
try {
|
try {
|
||||||
const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission];
|
const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission];
|
||||||
const result = await RocketChat.hasPermission(permission, room.rid);
|
const result = await hasPermission(permission, room.rid);
|
||||||
permissions = {
|
permissions = {
|
||||||
hasEditPermission: result[0],
|
hasEditPermission: result[0],
|
||||||
hasDeletePermission: result[1],
|
hasDeletePermission: result[1],
|
||||||
|
@ -150,7 +155,7 @@ const MessageActions = React.memo(
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPermalink = (message: TAnyMessageModel) => RocketChat.getPermalinkMessage(message);
|
const getPermalink = (message: TAnyMessageModel) => getPermalinkMessage(message);
|
||||||
|
|
||||||
const handleReply = (message: TAnyMessageModel) => {
|
const handleReply = (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_REPLY);
|
logEvent(events.ROOM_MSG_ACTION_REPLY);
|
||||||
|
@ -178,7 +183,7 @@ const MessageActions = React.memo(
|
||||||
const { rid } = room;
|
const { rid } = room;
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const result = await RocketChat.markAsUnread({ messageId });
|
const result = await Services.markAsUnread({ messageId });
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const subCollection = db.get('subscriptions');
|
const subCollection = db.get('subscriptions');
|
||||||
const subRecord = await subCollection.find(rid);
|
const subRecord = await subCollection.find(rid);
|
||||||
|
@ -234,7 +239,7 @@ const MessageActions = React.memo(
|
||||||
const handleStar = async (message: TAnyMessageModel) => {
|
const handleStar = async (message: TAnyMessageModel) => {
|
||||||
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
|
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
|
||||||
try {
|
try {
|
||||||
await RocketChat.toggleStarMessage(message.id, message.starred as boolean); // TODO: reevaluate `message.starred` type on IMessage
|
await Services.toggleStarMessage(message.id, message.starred as boolean); // TODO: reevaluate `message.starred` type on IMessage
|
||||||
EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') });
|
EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logEvent(events.ROOM_MSG_ACTION_STAR_F);
|
logEvent(events.ROOM_MSG_ACTION_STAR_F);
|
||||||
|
@ -245,7 +250,7 @@ const MessageActions = React.memo(
|
||||||
const handlePin = async (message: TAnyMessageModel) => {
|
const handlePin = async (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_PIN);
|
logEvent(events.ROOM_MSG_ACTION_PIN);
|
||||||
try {
|
try {
|
||||||
await RocketChat.togglePinMessage(message.id, message.pinned as boolean); // TODO: reevaluate `message.pinned` type on IMessage
|
await Services.togglePinMessage(message.id, message.pinned as boolean); // TODO: reevaluate `message.pinned` type on IMessage
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logEvent(events.ROOM_MSG_ACTION_PIN_F);
|
logEvent(events.ROOM_MSG_ACTION_PIN_F);
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -286,13 +291,7 @@ const MessageActions = React.memo(
|
||||||
});
|
});
|
||||||
const translatedMessage = getMessageTranslation(message, room.autoTranslateLanguage);
|
const translatedMessage = getMessageTranslation(message, room.autoTranslateLanguage);
|
||||||
if (!translatedMessage) {
|
if (!translatedMessage) {
|
||||||
const m = {
|
await Services.translateMessage(message.id, room.autoTranslateLanguage);
|
||||||
_id: message.id,
|
|
||||||
rid: message.subscription ? message.subscription.id : '',
|
|
||||||
u: message.u,
|
|
||||||
msg: message.msg
|
|
||||||
};
|
|
||||||
await RocketChat.translateMessage(m, room.autoTranslateLanguage);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -302,7 +301,7 @@ const MessageActions = React.memo(
|
||||||
const handleReport = async (message: TAnyMessageModel) => {
|
const handleReport = async (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_REPORT);
|
logEvent(events.ROOM_MSG_ACTION_REPORT);
|
||||||
try {
|
try {
|
||||||
await RocketChat.reportMessage(message.id);
|
await Services.reportMessage(message.id);
|
||||||
Alert.alert(I18n.t('Message_Reported'));
|
Alert.alert(I18n.t('Message_Reported'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logEvent(events.ROOM_MSG_ACTION_REPORT_F);
|
logEvent(events.ROOM_MSG_ACTION_REPORT_F);
|
||||||
|
@ -317,7 +316,7 @@ const MessageActions = React.memo(
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
try {
|
try {
|
||||||
logEvent(events.ROOM_MSG_ACTION_DELETE);
|
logEvent(events.ROOM_MSG_ACTION_DELETE);
|
||||||
await RocketChat.deleteMessage(message.id, message.subscription ? message.subscription.id : '');
|
await Services.deleteMessage(message.id, message.subscription ? message.subscription.id : '');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logEvent(events.ROOM_MSG_ACTION_DELETE_F);
|
logEvent(events.ROOM_MSG_ACTION_DELETE_F);
|
||||||
log(e);
|
log(e);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React, { useContext, useState } from 'react';
|
||||||
import { TouchableOpacity } from 'react-native';
|
import { TouchableOpacity } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../CustomIcon';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
import ActivityIndicator from '../../ActivityIndicator';
|
import ActivityIndicator from '../../ActivityIndicator';
|
||||||
import MessageboxContext from '../Context';
|
import MessageboxContext from '../Context';
|
||||||
|
|
|
@ -7,7 +7,6 @@ import EmojiPicker from '../EmojiPicker';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { TSupportedThemes, withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import { IEmoji } from '../../definitions/IEmoji';
|
|
||||||
|
|
||||||
interface IMessageBoxEmojiKeyboard {
|
interface IMessageBoxEmojiKeyboard {
|
||||||
theme: TSupportedThemes;
|
theme: TSupportedThemes;
|
||||||
|
@ -22,7 +21,7 @@ export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiK
|
||||||
this.baseUrl = state.share.server.server || state.server.server;
|
this.baseUrl = state.share.server.server || state.server.server;
|
||||||
}
|
}
|
||||||
|
|
||||||
onEmojiSelected = (emoji: IEmoji) => {
|
onEmojiSelected = (emoji: string) => {
|
||||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,7 +31,7 @@ export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiK
|
||||||
<View
|
<View
|
||||||
style={[styles.emojiKeyboardContainer, { borderTopColor: themes[theme].borderColor }]}
|
style={[styles.emojiKeyboardContainer, { borderTopColor: themes[theme].borderColor }]}
|
||||||
testID='messagebox-keyboard-emoji'>
|
testID='messagebox-keyboard-emoji'>
|
||||||
<EmojiPicker onEmojiSelected={this.onEmojiSelected} baseUrl={this.baseUrl} />
|
<EmojiPicker onEmojiSelected={this.onEmojiSelected} baseUrl={this.baseUrl} theme={theme} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../CustomIcon';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
import sharedStyles from '../../../views/Styles';
|
import sharedStyles from '../../../views/Styles';
|
||||||
import MessageboxContext from '../Context';
|
import MessageboxContext from '../Context';
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { events, logEvent } from '../../utils/log';
|
import { events, logEvent } from '../../utils/log';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import moment from 'moment';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { MarkdownPreview } from '../markdown';
|
import { MarkdownPreview } from '../markdown';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { IMessage } from '../../definitions/IMessage';
|
import { IMessage } from '../../definitions/IMessage';
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { View } from 'react-native';
|
||||||
|
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon, TIconsName } from '../../CustomIcon';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
|
|
||||||
|
@ -12,11 +12,11 @@ interface IBaseButton {
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
testID: string;
|
testID: string;
|
||||||
accessibilityLabel: string;
|
accessibilityLabel: string;
|
||||||
icon: string;
|
icon: TIconsName;
|
||||||
color: string;
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseButton = ({ accessibilityLabel, icon, color, ...props }: Partial<IBaseButton>) => {
|
const BaseButton = ({ accessibilityLabel, icon, color, ...props }: IBaseButton) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<BorderlessButton {...props} style={styles.actionButton}>
|
<BorderlessButton {...props} style={styles.actionButton}>
|
||||||
|
|
|
@ -9,12 +9,11 @@ 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 RocketChat from '../../lib/rocketchat';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { emojis } from '../../emojis';
|
import { emojis } from '../EmojiPicker/emojis';
|
||||||
import log, { events, logEvent } from '../../utils/log';
|
import log, { events, logEvent } from '../../utils/log';
|
||||||
import RecordAudio from './RecordAudio';
|
import RecordAudio from './RecordAudio';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
@ -47,11 +46,13 @@ import { getUserSelector } from '../../selectors/login';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import { withActionSheet } from '../ActionSheet';
|
import { withActionSheet } from '../ActionSheet';
|
||||||
import { sanitizeLikeString } from '../../lib/database/utils';
|
import { sanitizeLikeString } from '../../lib/database/utils';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { IMessage } from '../../definitions/IMessage';
|
import { IMessage } from '../../definitions/IMessage';
|
||||||
import { forceJpgExtension } from './forceJpgExtension';
|
import { forceJpgExtension } from './forceJpgExtension';
|
||||||
import { IBaseScreen, IPreviewItem, IUser, TSubscriptionModel, TThreadModel } from '../../definitions';
|
import { IBaseScreen, IPreviewItem, IUser, TGetCustomEmoji, TSubscriptionModel, TThreadModel } from '../../definitions';
|
||||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
|
import { getPermalinkMessage, hasPermission, search, sendFileMessage } from '../../lib/methods';
|
||||||
|
import { Services } from '../../lib/services';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
|
@ -91,7 +92,7 @@ export interface IMessageBoxProps extends IBaseScreen<MasterDetailInsideStackPar
|
||||||
FileUpload_MediaTypeWhiteList: string;
|
FileUpload_MediaTypeWhiteList: string;
|
||||||
FileUpload_MaxFileSize: number;
|
FileUpload_MaxFileSize: number;
|
||||||
Message_AudioRecorderEnabled: boolean;
|
Message_AudioRecorderEnabled: boolean;
|
||||||
getCustomEmoji: Function;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
editCancel: Function;
|
editCancel: Function;
|
||||||
editRequest: Function;
|
editRequest: Function;
|
||||||
onSubmit: Function;
|
onSubmit: Function;
|
||||||
|
@ -127,7 +128,7 @@ interface IMessageBoxState {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
private text: string;
|
public text: string;
|
||||||
|
|
||||||
private selection: { start: number; end: number };
|
private selection: { start: number; end: number };
|
||||||
|
|
||||||
|
@ -408,7 +409,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionToUpload = await RocketChat.hasPermission([uploadFilePermission], rid);
|
const permissionToUpload = await hasPermission([uploadFilePermission], rid);
|
||||||
this.setState({ permissionToUpload: permissionToUpload[0] });
|
this.setState({ permissionToUpload: permissionToUpload[0] });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -529,14 +530,14 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
try {
|
try {
|
||||||
const { appId } = command;
|
const { appId } = command;
|
||||||
const triggerId = generateTriggerId(appId);
|
const triggerId = generateTriggerId(appId);
|
||||||
RocketChat.executeCommandPreview(name, params, rid, item, triggerId, tmid || messageTmid);
|
Services.executeCommandPreview(name, params, rid, item, triggerId, tmid || messageTmid);
|
||||||
replyCancel();
|
replyCancel();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onEmojiSelected = (keyboardId: any, params: any) => {
|
onEmojiSelected = (keyboardId: string, params: { emoji: string }) => {
|
||||||
const { text } = this;
|
const { text } = this;
|
||||||
const { emoji } = params;
|
const { emoji } = params;
|
||||||
let newText = '';
|
let newText = '';
|
||||||
|
@ -552,7 +553,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
|
|
||||||
getPermalink = async (message: any) => {
|
getPermalink = async (message: any) => {
|
||||||
try {
|
try {
|
||||||
return await RocketChat.getPermalinkMessage(message);
|
return await getPermalinkMessage(message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -570,13 +571,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
getUsers = debounce(async (keyword: any) => {
|
getUsers = debounce(async (keyword: any) => {
|
||||||
let res = await RocketChat.search({ text: keyword, filterRooms: false, filterUsers: true });
|
let res = await search({ text: keyword, filterRooms: false, filterUsers: true });
|
||||||
res = [...this.getFixedMentions(keyword), ...res];
|
res = [...this.getFixedMentions(keyword), ...res];
|
||||||
this.setState({ mentions: res, mentionLoading: false });
|
this.setState({ mentions: res, mentionLoading: false });
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
getRooms = debounce(async (keyword = '') => {
|
getRooms = debounce(async (keyword = '') => {
|
||||||
const res = await RocketChat.search({ text: keyword, filterRooms: true, filterUsers: false });
|
const res = await search({ text: keyword, filterRooms: true, filterUsers: false });
|
||||||
this.setState({ mentions: res, mentionLoading: false });
|
this.setState({ mentions: res, mentionLoading: false });
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
|
@ -604,7 +605,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
getCannedResponses = debounce(async (text?: string) => {
|
getCannedResponses = debounce(async (text?: string) => {
|
||||||
const res = await RocketChat.getListCannedResponse({ text });
|
const res = await Services.getListCannedResponse({ text });
|
||||||
this.setState({ mentions: res.success ? res.cannedResponses : [], mentionLoading: false });
|
this.setState({ mentions: res.success ? res.cannedResponses : [], mentionLoading: false });
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
@ -641,7 +642,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
setCommandPreview = async (command: any, name: string, params: string) => {
|
setCommandPreview = async (command: any, name: string, params: string) => {
|
||||||
const { rid } = this.props;
|
const { rid } = this.props;
|
||||||
try {
|
try {
|
||||||
const response = await RocketChat.getCommandPreview(name, rid, params);
|
const response = await Services.getCommandPreview(name, rid, params);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
return this.setState({ commandPreview: response.preview?.items || [], showCommandPreview: true, command });
|
return this.setState({ commandPreview: response.preview?.items || [], showCommandPreview: true, command });
|
||||||
}
|
}
|
||||||
|
@ -676,7 +677,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;
|
||||||
}
|
}
|
||||||
|
@ -724,7 +730,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 = {
|
||||||
|
@ -836,7 +842,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
if (fileInfo) {
|
if (fileInfo) {
|
||||||
try {
|
try {
|
||||||
if (this.canUploadFile(fileInfo)) {
|
if (this.canUploadFile(fileInfo)) {
|
||||||
await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user);
|
await sendFileMessage(rid, fileInfo, tmid, server, user);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -888,7 +894,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
const messageWithoutCommand = message.replace(/([^\s]+)/, '').trim();
|
const messageWithoutCommand = message.replace(/([^\s]+)/, '').trim();
|
||||||
const [{ appId }] = slashCommand;
|
const [{ appId }] = slashCommand;
|
||||||
const triggerId = generateTriggerId(appId);
|
const triggerId = generateTriggerId(appId);
|
||||||
await RocketChat.runSlashCommand(command, roomId, messageWithoutCommand, triggerId, tmid || messageTmid);
|
await Services.runSlashCommand(command, roomId, messageWithoutCommand, triggerId, tmid || messageTmid);
|
||||||
replyCancel();
|
replyCancel();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logEvent(events.COMMAND_RUN_F);
|
logEvent(events.COMMAND_RUN_F);
|
||||||
|
@ -1181,4 +1187,6 @@ const dispatchToProps = {
|
||||||
typing: (rid: any, status: any) => userTypingAction(rid, status)
|
typing: (rid: any, status: any) => userTypingAction(rid, status)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox)) as any;
|
export type MessageBoxType = MessageBox;
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox));
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
import { forwardRef, useImperativeHandle } from 'react';
|
import { forwardRef, useImperativeHandle } from 'react';
|
||||||
import Model from '@nozbe/watermelondb/Model';
|
import Model from '@nozbe/watermelondb/Model';
|
||||||
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
|
||||||
import database from '../lib/database';
|
import database from '../lib/database';
|
||||||
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||||
import { useActionSheet } from './ActionSheet';
|
import { useActionSheet } from './ActionSheet';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import { TMessageModel } from '../definitions';
|
import { TMessageModel } from '../definitions';
|
||||||
|
import { resendMessage } from '../lib/methods';
|
||||||
|
|
||||||
const MessageErrorActions = forwardRef(({ tmid }: { tmid: string }, ref) => {
|
export interface IMessageErrorActions {
|
||||||
// TODO - remove this any after merge ActionSheet evaluate
|
showMessageErrorActions: (message: TMessageModel) => void;
|
||||||
const { showActionSheet }: any = useActionSheet();
|
}
|
||||||
|
|
||||||
|
const MessageErrorActions = forwardRef<IMessageErrorActions, { tmid?: string }>(({ tmid }, ref) => {
|
||||||
|
const { showActionSheet } = useActionSheet();
|
||||||
|
|
||||||
const handleResend = protectedFunction(async (message: TMessageModel) => {
|
const handleResend = protectedFunction(async (message: TMessageModel) => {
|
||||||
await RocketChat.resendMessage(message, tmid);
|
await resendMessage(message, tmid);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDelete = async (message: TMessageModel) => {
|
const handleDelete = async (message: TMessageModel) => {
|
||||||
|
|
|
@ -4,12 +4,12 @@ import { Text } from 'react-native';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import Touch from '../../../utils/touch';
|
import Touch from '../../../utils/touch';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon, TIconsName } from '../../CustomIcon';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface IPasscodeButton {
|
interface IPasscodeButton {
|
||||||
text?: string;
|
text?: string;
|
||||||
icon?: string;
|
icon?: TIconsName;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onPress?: Function;
|
onPress?: Function;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Row } from 'react-native-easy-grid';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../CustomIcon';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
const LockIcon = React.memo(() => {
|
const LockIcon = React.memo(() => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import Emoji from './message/Emoji';
|
import Emoji from './message/Emoji';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from './CustomIcon';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import { TSupportedThemes, useTheme, withTheme } from '../theme';
|
import { TSupportedThemes, useTheme, withTheme } from '../theme';
|
||||||
|
@ -125,7 +125,7 @@ const ModalContent = React.memo(({ message, onClose, ...props }: IModalContent)
|
||||||
<SafeAreaView style={styles.safeArea}>
|
<SafeAreaView style={styles.safeArea}>
|
||||||
<Touchable onPress={onClose}>
|
<Touchable onPress={onClose}>
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleContainer}>
|
||||||
<CustomIcon style={[styles.closeButton, { color: themes[props.theme].buttonText }]} name='close' size={20} />
|
<CustomIcon name='close' size={20} color={themes[props.theme].buttonText} style={styles.closeButton} />
|
||||||
<Text style={[styles.title, { color: themes[props.theme].buttonText }]}>{I18n.t('Reactions')}</Text>
|
<Text style={[styles.title, { color: themes[props.theme].buttonText }]}>{I18n.t('Reactions')}</Text>
|
||||||
</View>
|
</View>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,7 +3,7 @@ import { Animated, View } from 'react-native';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import { isRTL } from '../../i18n';
|
import { isRTL } from '../../i18n';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { DisplayMode, themes } from '../../lib/constants';
|
import { DisplayMode, themes } from '../../lib/constants';
|
||||||
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
|
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
|
||||||
import { ILeftActionsProps, IRightActionsProps } from './interfaces';
|
import { ILeftActionsProps, IRightActionsProps } from './interfaces';
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
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 { CustomIcon } from '../../lib/Icons';
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
|
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||||
|
|
||||||
const iconMap = {
|
interface IIconMap {
|
||||||
|
[key: string]: TIconsName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconMap: IIconMap = {
|
||||||
widget: 'livechat-monochromatic',
|
widget: 'livechat-monochromatic',
|
||||||
email: 'mail',
|
email: 'mail',
|
||||||
sms: 'sms',
|
sms: 'sms',
|
||||||
|
@ -25,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 (
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { StyleSheet, ViewStyle } from 'react-native';
|
import { StyleSheet, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
import { OmnichannelRoomIcon } from './OmnichannelRoomIcon';
|
import { OmnichannelRoomIcon } from './OmnichannelRoomIcon';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||||
import { STATUS_COLORS, themes } from '../../lib/constants';
|
import { STATUS_COLORS, themes } from '../../lib/constants';
|
||||||
import Status from '../Status/Status';
|
import Status from '../Status/Status';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
|
@ -46,7 +46,7 @@ const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, teamMain, s
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move this to a separate function
|
// TODO: move this to a separate function
|
||||||
let icon = 'channel-private';
|
let icon: TIconsName = 'channel-private';
|
||||||
if (teamMain) {
|
if (teamMain) {
|
||||||
icon = `teams${type === 'p' ? '-private' : ''}`;
|
icon = `teams${type === 'p' ? '-private' : ''}`;
|
||||||
} else if (type === 'discussion') {
|
} else if (type === 'discussion') {
|
||||||
|
@ -61,7 +61,7 @@ const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, teamMain, s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <CustomIcon name={icon} size={size} style={iconStyle} />;
|
return <CustomIcon name={icon} size={size} color={color} style={iconStyle} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default RoomTypeIcon;
|
export default RoomTypeIcon;
|
||||||
|
|
|
@ -4,8 +4,8 @@ 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 '../lib/Icons';
|
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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleProp, TextStyle } from 'react-native';
|
import { StyleProp, TextStyle } from 'react-native';
|
||||||
|
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon, IconSet, TIconsName } from '../CustomIcon';
|
||||||
import { STATUS_COLORS } from '../../lib/constants';
|
import { STATUS_COLORS } from '../../lib/constants';
|
||||||
import { IStatus } from './definition';
|
import { IStatus } from './definition';
|
||||||
|
|
||||||
const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: Omit<IStatus, 'id'>) => {
|
const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: Omit<IStatus, 'id'>) => {
|
||||||
const name = `status-${status}`;
|
const name: TIconsName = `status-${status}`;
|
||||||
const isNameValid = CustomIcon.hasIcon(name);
|
const isNameValid = IconSet.hasIcon(name);
|
||||||
const iconName = isNameValid ? name : 'status-offline';
|
const iconName = isNameValid ? name : 'status-offline';
|
||||||
const calculatedStyle: StyleProp<TextStyle> = [
|
const calculatedStyle: StyleProp<TextStyle> = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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} />;
|
||||||
|
|
|
@ -2,12 +2,12 @@ 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 } from '../lib/Icons';
|
import { CustomIcon, TIconsName } from '../CustomIcon';
|
||||||
import ActivityIndicator from './ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
import { TSupportedThemes } from '../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
error: {
|
error: {
|
||||||
|
@ -59,8 +59,8 @@ export interface IRCTextInputProps extends TextInputProps {
|
||||||
inputStyle?: StyleProp<TextStyle>;
|
inputStyle?: StyleProp<TextStyle>;
|
||||||
inputRef?: React.Ref<RNTextInput>;
|
inputRef?: React.Ref<RNTextInput>;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
iconLeft?: string;
|
iconLeft?: TIconsName;
|
||||||
iconRight?: string;
|
iconRight?: TIconsName;
|
||||||
left?: JSX.Element;
|
left?: JSX.Element;
|
||||||
onIconRightPress?(): void;
|
onIconRightPress?(): void;
|
||||||
theme: TSupportedThemes;
|
theme: TSupportedThemes;
|
||||||
|
@ -70,7 +70,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'
|
||||||
|
@ -82,23 +82,24 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
||||||
|
|
||||||
get iconLeft() {
|
get iconLeft() {
|
||||||
const { testID, iconLeft, theme } = this.props;
|
const { testID, iconLeft, theme } = this.props;
|
||||||
return (
|
return iconLeft ? (
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name={iconLeft}
|
name={iconLeft}
|
||||||
testID={testID ? `${testID}-icon-left` : null}
|
testID={testID ? `${testID}-icon-left` : undefined}
|
||||||
style={[styles.iconContainer, styles.iconLeft, { color: themes[theme].bodyText }]}
|
|
||||||
size={20}
|
size={20}
|
||||||
|
color={themes[theme].bodyText}
|
||||||
|
style={[styles.iconContainer, styles.iconLeft]}
|
||||||
/>
|
/>
|
||||||
);
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get iconRight() {
|
get iconRight() {
|
||||||
const { iconRight, onIconRightPress, theme } = this.props;
|
const { iconRight, onIconRightPress, theme } = this.props;
|
||||||
return (
|
return iconRight ? (
|
||||||
<Touchable onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}>
|
<Touchable onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}>
|
||||||
<CustomIcon name={iconRight} style={{ color: themes[theme].bodyText }} size={20} />
|
<CustomIcon name={iconRight} size={20} color={themes[theme].bodyText} />
|
||||||
</Touchable>
|
</Touchable>
|
||||||
);
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get iconPassword() {
|
get iconPassword() {
|
||||||
|
@ -108,9 +109,9 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
||||||
<Touchable onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}>
|
<Touchable onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}>
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name={showPassword ? 'unread-on-top' : 'unread-on-top-disabled'}
|
name={showPassword ? 'unread-on-top' : 'unread-on-top-disabled'}
|
||||||
testID={testID ? `${testID}-icon-right` : null}
|
testID={testID ? `${testID}-icon-right` : undefined}
|
||||||
style={{ color: themes[theme].auxiliaryText }}
|
|
||||||
size={20}
|
size={20}
|
||||||
|
color={themes[theme].auxiliaryText}
|
||||||
/>
|
/>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
);
|
);
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
));
|
));
|
|
@ -1,9 +1,9 @@
|
||||||
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 { TSupportedThemes } from '../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
input: {
|
input: {
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { StyleSheet, Text, View, ViewStyle } from 'react-native';
|
import { StyleSheet, Text, View, ViewStyle } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from './CustomIcon';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { useTheme } from '../theme';
|
import { useTheme } from '../theme';
|
||||||
|
|
|
@ -6,16 +6,16 @@ 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';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { IApplicationState } from '../../definitions';
|
import { IApplicationState } from '../../definitions';
|
||||||
|
import { Services } from '../../lib/services';
|
||||||
|
|
||||||
export const TWO_FACTOR = 'TWO_FACTOR';
|
export const TWO_FACTOR = 'TWO_FACTOR';
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ const TwoFactor = React.memo(({ isMasterDetail }: { isMasterDetail: boolean }) =
|
||||||
|
|
||||||
const method = data.method ? methods[data.method] : null;
|
const method = data.method ? methods[data.method] : null;
|
||||||
const isEmail = data.method === 'email';
|
const isEmail = data.method === 'email';
|
||||||
const sendEmail = () => RocketChat.sendEmailCode();
|
const sendEmail = () => Services.sendEmailCode();
|
||||||
|
|
||||||
useDeepCompareEffect(() => {
|
useDeepCompareEffect(() => {
|
||||||
if (!isEmpty(data)) {
|
if (!isEmpty(data)) {
|
||||||
|
@ -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>
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BlockContext } 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;
|
||||||
|
|
||||||
const Elements = () => (
|
const Elements = () => (
|
||||||
<>{renderedElements?.map(element => parser?.renderActions({ blockId, appId, ...element }, BLOCK_CONTEXT.ACTION, parser))}</>
|
<>{renderedElements?.map(element => parser?.renderActions({ blockId, appId, ...element }, BlockContext.ACTION, parser))}</>
|
||||||
);
|
);
|
||||||
|
|
||||||
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)} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import { IContext } from './interfaces';
|
import { IContext } from './interfaces';
|
||||||
|
|
||||||
|
@ -13,5 +13,5 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Context = ({ elements, parser }: IContext) => (
|
export const Context = ({ elements, parser }: IContext) => (
|
||||||
<View style={styles.container}>{elements?.map(element => parser?.renderContext(element, BLOCK_CONTEXT.CONTEXT, parser))}</View>
|
<View style={styles.container}>{elements?.map(element => parser?.renderContext(element, BlockContext.CONTEXT, parser))}</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,14 +2,14 @@ import React, { useState } from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import DateTimePicker, { Event } from '@react-native-community/datetimepicker';
|
import DateTimePicker, { Event } from '@react-native-community/datetimepicker';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import { textParser } from './utils';
|
import { textParser } from './utils';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { isAndroid } from '../../utils/deviceInfo';
|
import { isAndroid } from '../../utils/deviceInfo';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
|
@ -56,11 +56,9 @@ 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 === BlockContext.FORM) {
|
||||||
button = (
|
button = (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={() => onShow(!show)}
|
onPress={() => onShow(!show)}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import ImageContainer from '../message/Image';
|
import ImageContainer from '../message/Image';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
|
@ -36,9 +36,9 @@ export const Media = ({ element }: IImage) => {
|
||||||
|
|
||||||
const genericImage = (element: IElement, context?: number) => {
|
const genericImage = (element: IElement, context?: number) => {
|
||||||
switch (context) {
|
switch (context) {
|
||||||
case BLOCK_CONTEXT.SECTION:
|
case BlockContext.SECTION:
|
||||||
return <Thumb element={element} />;
|
return <Thumb element={element} />;
|
||||||
case BLOCK_CONTEXT.CONTEXT:
|
case BlockContext.CONTEXT:
|
||||||
return <ThumbContext element={element} />;
|
return <ThumbContext element={element} />;
|
||||||
default:
|
default:
|
||||||
return <Media element={element} />;
|
return <Media element={element} />;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
|
@ -38,7 +38,7 @@ export const Input = ({ element, parser, label, description, error, hint, theme
|
||||||
<Text style={[styles.label, { color: error ? themes[theme].dangerColor : themes[theme].titleText }]}>{label}</Text>
|
<Text style={[styles.label, { color: error ? themes[theme].dangerColor : themes[theme].titleText }]}>{label}</Text>
|
||||||
) : null}
|
) : null}
|
||||||
{description ? <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{description}</Text> : null}
|
{description ? <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{description}</Text> : null}
|
||||||
{parser.renderInputs({ ...element }, BLOCK_CONTEXT.FORM, parser)}
|
{parser.renderInputs({ ...element }, BlockContext.FORM, parser)}
|
||||||
{error ? <Text style={[styles.error, { color: themes[theme].dangerColor }]}>{error}</Text> : null}
|
{error ? <Text style={[styles.error, { color: themes[theme].dangerColor }]}>{error}</Text> : null}
|
||||||
{hint ? <Text style={[styles.hint, { color: themes[theme].auxiliaryText }]}>{hint}</Text> : null}
|
{hint ? <Text style={[styles.hint, { color: themes[theme].auxiliaryText }]}>{hint}</Text> : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import FastImage from '@rocket.chat/react-native-fast-image';
|
||||||
|
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import { textParser } from '../utils';
|
import { textParser } from '../utils';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../CustomIcon';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { IItemData } from '.';
|
import { IItemData } from '.';
|
||||||
import { TSupportedThemes } from '../../../theme';
|
import { TSupportedThemes } from '../../../theme';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../CustomIcon';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import ActivityIndicator from '../../ActivityIndicator';
|
import ActivityIndicator from '../../ActivityIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
|
@ -10,16 +10,16 @@ import {
|
||||||
View,
|
View,
|
||||||
TextStyle
|
TextStyle
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BlockContext } 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';
|
||||||
import { isIOS } from '../../../utils/deviceInfo';
|
import { isIOS } from '../../../utils/deviceInfo';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
import { BlockContext, IText } from '../interfaces';
|
import { IText } from '../interfaces';
|
||||||
import Chips from './Chips';
|
import Chips from './Chips';
|
||||||
import Items from './Items';
|
import Items from './Items';
|
||||||
import Input from './Input';
|
import Input from './Input';
|
||||||
|
@ -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}
|
||||||
|
@ -172,7 +172,7 @@ export const MultiSelect = React.memo(
|
||||||
</Input>
|
</Input>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context === BLOCK_CONTEXT.FORM) {
|
if (context === BlockContext.FORM) {
|
||||||
const items: any = options.filter((option: any) => selected.includes(option.value));
|
const items: any = options.filter((option: any) => selected.includes(option.value));
|
||||||
button = (
|
button = (
|
||||||
<Input
|
<Input
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { FlatList, StyleSheet, Text } from 'react-native';
|
||||||
import Popover from 'react-native-popover-view';
|
import Popover from 'react-native-popover-view';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { IAccessoryComponent, IFields, ISection } from './interfaces';
|
import { IAccessoryComponent, IFields, ISection } from './interfaces';
|
||||||
|
@ -26,7 +26,7 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
const Accessory = ({ element, parser }: IAccessoryComponent) =>
|
const Accessory = ({ element, parser }: IAccessoryComponent) =>
|
||||||
parser.renderAccessories({ ...element }, BLOCK_CONTEXT.SECTION, parser);
|
parser.renderAccessories({ ...element }, BlockContext.SECTION, parser);
|
||||||
|
|
||||||
const Fields = ({ fields, parser, theme }: IFields) => (
|
const Fields = ({ fields, parser, theme }: IFields) => (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import RNPickerSelect from 'react-native-picker-select';
|
||||||
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { textParser } from './utils';
|
import { textParser } from './utils';
|
||||||
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { StyleSheet, Text } from 'react-native';
|
import { StyleSheet, Text } from 'react-native';
|
||||||
import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKitModal } from '@rocket.chat/ui-kit';
|
import {
|
||||||
|
UiKitParserMessage,
|
||||||
|
UiKitParserModal,
|
||||||
|
uiKitMessage,
|
||||||
|
uiKitModal,
|
||||||
|
BlockContext,
|
||||||
|
Markdown as IMarkdown,
|
||||||
|
PlainText
|
||||||
|
} from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
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';
|
||||||
|
@ -20,7 +28,7 @@ import { Input } from './Input';
|
||||||
import { DatePicker } from './DatePicker';
|
import { DatePicker } from './DatePicker';
|
||||||
import { Overflow } from './Overflow';
|
import { Overflow } from './Overflow';
|
||||||
import { ThemeContext } from '../../theme';
|
import { ThemeContext } from '../../theme';
|
||||||
import { BlockContext, IActions, IButton, IElement, IInputIndex, IParser, ISection, IText } from './interfaces';
|
import { IActions, IButton, IElement, IInputIndex, IParser, ISection } from './interfaces';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
input: {
|
input: {
|
||||||
|
@ -42,28 +50,38 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
const plainText = ({ text } = { text: '' }) => text;
|
const plainText = ({ text } = { text: '' }) => text;
|
||||||
|
|
||||||
class MessageParser extends UiKitParserMessage {
|
class MessageParser extends UiKitParserMessage<React.ReactElement> {
|
||||||
get current() {
|
get current() {
|
||||||
return this as unknown as IParser;
|
return this as unknown as IParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
text({ text, type }: Partial<IText> = { text: '' }, context: BlockContext) {
|
plain_text(element: PlainText, context: BlockContext): React.ReactElement {
|
||||||
const { theme } = useContext(ThemeContext);
|
const { theme } = useContext(ThemeContext);
|
||||||
if (type !== 'mrkdwn') {
|
|
||||||
return <Text style={[styles.text, { color: themes[theme].bodyText }]}>{text}</Text>;
|
const isContext = context === BlockContext.CONTEXT;
|
||||||
|
if (isContext) {
|
||||||
|
return (
|
||||||
|
<MarkdownPreview msg={element.text} style={[isContext && { color: themes[theme].auxiliaryText }]} numberOfLines={0} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <Text style={[styles.text, { color: themes[theme].bodyText }]}>{element.text}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isContext = context === BLOCK_CONTEXT.CONTEXT;
|
mrkdwn(element: IMarkdown, context: BlockContext) {
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
|
||||||
|
const isContext = context === BlockContext.CONTEXT;
|
||||||
if (isContext) {
|
if (isContext) {
|
||||||
return <MarkdownPreview msg={text} style={[isContext && { color: themes[theme].auxiliaryText }]} numberOfLines={0} />;
|
return (
|
||||||
|
<MarkdownPreview msg={element.text} style={[isContext && { color: themes[theme].auxiliaryText }]} numberOfLines={0} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return <Markdown msg={text} theme={theme} style={[isContext && { color: themes[theme].auxiliaryText }]} />;
|
return <Markdown msg={element.text} theme={theme} style={[isContext && { color: themes[theme].auxiliaryText }]} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +90,6 @@ class MessageParser extends UiKitParserMessage {
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onPress={() => action({ value })}
|
onPress={() => action({ value })}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
theme={theme}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -134,10 +151,13 @@ class MessageParser extends UiKitParserMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModalParser extends UiKitParserModal {
|
// plain_text and mrkdwn functions are created in MessageParser and the ModalParser's constructor use the same functions
|
||||||
|
// @ts-ignore
|
||||||
|
class ModalParser extends UiKitParserModal<React.ReactElement> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
Object.getOwnPropertyNames(MessageParser.prototype).forEach(method => {
|
Object.getOwnPropertyNames(MessageParser.prototype).forEach(method => {
|
||||||
|
// @ts-ignore
|
||||||
ModalParser.prototype[method] = ModalParser.prototype[method] || MessageParser.prototype[method];
|
ModalParser.prototype[method] = ModalParser.prototype[method] || MessageParser.prototype[method];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -171,7 +191,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}
|
||||||
|
@ -190,7 +210,7 @@ class ModalParser extends UiKitParserModal {
|
||||||
export const messageParser = new MessageParser();
|
export const messageParser = new MessageParser();
|
||||||
export const modalParser = new ModalParser();
|
export const modalParser = new ModalParser();
|
||||||
|
|
||||||
export const UiKitMessage = uiKitMessage(messageParser);
|
export const UiKitMessage = uiKitMessage(messageParser, { engine: 'rocket.chat' }) as any;
|
||||||
export const UiKitModal = uiKitModal(modalParser);
|
export const UiKitModal = uiKitModal(modalParser) as any;
|
||||||
|
|
||||||
export const UiKitComponent = ({ render, blocks }: any) => render(blocks);
|
export const UiKitComponent = ({ render, blocks }: any) => render(blocks);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
export enum ElementTypes {
|
export enum ElementTypes {
|
||||||
|
@ -22,14 +24,6 @@ export enum ElementTypes {
|
||||||
MARKDOWN = 'mrkdwn'
|
MARKDOWN = 'mrkdwn'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BlockContext {
|
|
||||||
BLOCK,
|
|
||||||
SECTION,
|
|
||||||
ACTION,
|
|
||||||
FORM,
|
|
||||||
CONTEXT
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
ACTION = 'blockAction',
|
ACTION = 'blockAction',
|
||||||
SUBMIT = 'viewSubmit',
|
SUBMIT = 'viewSubmit',
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
/* eslint-disable no-shadow */
|
/* eslint-disable no-shadow */
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import { BlockContext, IText } from './interfaces';
|
import { IText } from './interfaces';
|
||||||
|
|
||||||
export const textParser = ([{ text }]: IText[]) => text;
|
export const textParser = ([{ text }]: IText[]) => text;
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUse
|
||||||
|
|
||||||
const error = errors && actionId && errors[actionId];
|
const error = errors && actionId && errors[actionId];
|
||||||
|
|
||||||
if ([BLOCK_CONTEXT.SECTION, BLOCK_CONTEXT.ACTION].includes(context)) {
|
if ([BlockContext.SECTION, BlockContext.ACTION].includes(context)) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
loading,
|
loading,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { Pressable, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
|
import { Pressable, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon, TIconsName } from './CustomIcon';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import { isIOS } from '../utils/deviceInfo';
|
import { isIOS } from '../utils/deviceInfo';
|
||||||
|
@ -46,7 +46,7 @@ interface IUserItem {
|
||||||
testID: string;
|
testID: string;
|
||||||
onLongPress?: () => void;
|
onLongPress?: () => void;
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
icon?: string | null;
|
icon?: TIconsName | null;
|
||||||
theme: TSupportedThemes;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, t
|
||||||
@{username}
|
@{username}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{icon ? <CustomIcon name={icon} size={22} style={[styles.icon, { color: themes[theme].actionTintColor }]} /> : null}
|
{icon ? <CustomIcon name={icon} size={22} color={themes[theme].actionTintColor} style={styles.icon} /> : null}
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,11 +2,12 @@ import React from 'react';
|
||||||
import { ScrollView, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
|
import { ScrollView, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
import { CELL_WIDTH } from './TableCell';
|
import { CELL_WIDTH } from './TableCell';
|
||||||
import styles from './styles';
|
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
|
import styles from './styles';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
|
|
||||||
interface ITable {
|
interface ITable {
|
||||||
children: React.ReactElement | null;
|
children: React.ReactElement | null;
|
||||||
|
@ -18,6 +19,7 @@ const MAX_HEIGHT = 300;
|
||||||
|
|
||||||
const Table = React.memo(({ children, numColumns, theme }: ITable) => {
|
const Table = React.memo(({ children, numColumns, theme }: ITable) => {
|
||||||
const getTableWidth = () => numColumns * CELL_WIDTH;
|
const getTableWidth = () => numColumns * CELL_WIDTH;
|
||||||
|
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
|
||||||
|
|
||||||
const renderRows = (drawExtraBorders = true) => {
|
const renderRows = (drawExtraBorders = true) => {
|
||||||
const tableStyle: ViewStyle[] = [styles.table, { borderColor: themes[theme].borderColor }];
|
const tableStyle: ViewStyle[] = [styles.table, { borderColor: themes[theme].borderColor }];
|
||||||
|
@ -33,7 +35,16 @@ const Table = React.memo(({ children, numColumns, theme }: ITable) => {
|
||||||
return <View style={tableStyle}>{rows}</View>;
|
return <View style={tableStyle}>{rows}</View>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPress = () => Navigation.navigate('MarkdownTableView', { renderRows, tableWidth: getTableWidth() });
|
const onPress = () => {
|
||||||
|
if (isMasterDetail) {
|
||||||
|
Navigation.navigate('ModalStackNavigator', {
|
||||||
|
screen: 'MarkdownTableView',
|
||||||
|
params: { renderRows, tableWidth: getTableWidth() }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Navigation.navigate('MarkdownTableView', { renderRows, tableWidth: getTableWidth() });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={onPress}>
|
<TouchableOpacity onPress={onPress}>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Sound } from 'expo-av/build/Audio/Sound';
|
||||||
|
|
||||||
import Touchable from './Touchable';
|
import Touchable from './Touchable';
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { useContext } from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
|
|
||||||
import Touchable from './Touchable';
|
import Touchable from './Touchable';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Touchable from './Touchable';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { IMessageCallButton } from './interfaces';
|
import { IMessageCallButton } from './interfaces';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { StyleSheet, Text, View } from 'react-native';
|
||||||
import { themes } from '../../../../lib/constants';
|
import { themes } from '../../../../lib/constants';
|
||||||
import { IAttachment } from '../../../../definitions/IAttachment';
|
import { IAttachment } from '../../../../definitions/IAttachment';
|
||||||
import { TGetCustomEmoji } from '../../../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../../../definitions/IEmoji';
|
||||||
import { CustomIcon } from '../../../../lib/Icons';
|
import { CustomIcon } from '../../../CustomIcon';
|
||||||
import { useTheme } from '../../../../theme';
|
import { useTheme } from '../../../../theme';
|
||||||
import sharedStyles from '../../../../views/Styles';
|
import sharedStyles from '../../../../views/Styles';
|
||||||
import Markdown from '../../../markdown';
|
import Markdown from '../../../markdown';
|
||||||
|
|
|
@ -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, testID }: { isEdited: boolean; testID?: string }) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
if (!isEdited) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View testID={testID} style={styles.rightIcons}>
|
||||||
|
<CustomIcon name='edit' size={16} color={themes[theme].auxiliaryText} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Edited;
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
|
import Touchable from '../../Touchable';
|
||||||
|
import { CustomIcon } from '../../../CustomIcon';
|
||||||
|
import { BUTTON_HIT_SLOP } from '../../utils';
|
||||||
|
import MessageContext from '../../Context';
|
||||||
|
import styles from '../../styles';
|
||||||
|
import { useTheme } from '../../../../theme';
|
||||||
|
import { E2E_MESSAGE_TYPE, themes } from '../../../../lib/constants';
|
||||||
|
|
||||||
|
const Encrypted = React.memo(({ type }: { type: string }) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const { onEncryptedPress } = useContext(MessageContext);
|
||||||
|
|
||||||
|
if (type !== E2E_MESSAGE_TYPE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Touchable onPress={onEncryptedPress} style={styles.rightIcons} hitSlop={BUTTON_HIT_SLOP}>
|
||||||
|
<CustomIcon name='encrypted' size={16} color={themes[theme].auxiliaryText} />
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Encrypted;
|
|
@ -1,12 +1,12 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
import Touchable from './Touchable';
|
import Touchable from '../../Touchable';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../../CustomIcon';
|
||||||
import styles from './styles';
|
import styles from '../../styles';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from '../../utils';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../../../lib/constants';
|
||||||
import MessageContext from './Context';
|
import MessageContext from '../../Context';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../../../theme';
|
||||||
|
|
||||||
const MessageError = React.memo(
|
const MessageError = React.memo(
|
||||||
({ hasError }: { hasError: boolean }) => {
|
({ hasError }: { hasError: boolean }) => {
|
||||||
|
@ -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.rightIcons} hitSlop={BUTTON_HIT_SLOP}>
|
||||||
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />
|
<CustomIcon name='warning' color={themes[theme].dangerColor} size={16} />
|
||||||
</Touchable>
|
</Touchable>
|
||||||
);
|
);
|
||||||
},
|
},
|
|
@ -1,14 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../../../lib/constants';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../../CustomIcon';
|
||||||
import styles from './styles';
|
import styles from '../../styles';
|
||||||
import { useTheme } from '../../theme';
|
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.rightIcons} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
|
import Encrypted from './Encrypted';
|
||||||
|
import Edited from './Edited';
|
||||||
|
import MessageError from './MessageError';
|
||||||
|
import ReadReceipt from './ReadReceipt';
|
||||||
|
import { MessageType } from '../../../../definitions';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
actionIcons: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IRightIcons {
|
||||||
|
type: MessageType;
|
||||||
|
msg?: string;
|
||||||
|
isEdited: boolean;
|
||||||
|
isReadReceiptEnabled: boolean;
|
||||||
|
unread: boolean;
|
||||||
|
hasError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RightIcons = ({ type, msg, isEdited, hasError, isReadReceiptEnabled, unread }: IRightIcons) => (
|
||||||
|
<View style={styles.actionIcons}>
|
||||||
|
<Encrypted type={type} />
|
||||||
|
<Edited testID={`${msg}-edited`} isEdited={isEdited} />
|
||||||
|
<MessageError hasError={hasError} />
|
||||||
|
<ReadReceipt isReadReceiptEnabled={isReadReceiptEnabled} unread={unread || false} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default RightIcons;
|
|
@ -8,10 +8,9 @@ import Markdown, { MarkdownPreview } from '../markdown';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME, getInfoMessage } from './utils';
|
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME, getInfoMessage } from './utils';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import Encrypted from './Encrypted';
|
|
||||||
import { IMessageContent } from './interfaces';
|
import { IMessageContent } from './interfaces';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { E2E_MESSAGE_TYPE, themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
|
|
||||||
const Content = React.memo(
|
const Content = React.memo(
|
||||||
(props: IMessageContent) => {
|
(props: IMessageContent) => {
|
||||||
|
@ -59,7 +58,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}
|
||||||
|
@ -71,16 +69,6 @@ const Content = React.memo(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is a encrypted message and is not a preview
|
|
||||||
if (props.type === E2E_MESSAGE_TYPE && !isPreview) {
|
|
||||||
content = (
|
|
||||||
<View style={styles.flex}>
|
|
||||||
<View style={styles.contentContainer}>{content}</View>
|
|
||||||
<Encrypted type={props.type} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.isIgnored) {
|
if (props.isIgnored) {
|
||||||
content = <Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]}>{I18n.t('Message_Ignored')}</Text>;
|
content = <Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]}>{I18n.t('Message_Ignored')}</Text>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Touchable from './Touchable';
|
||||||
import { BUTTON_HIT_SLOP, formatMessageCount } from './utils';
|
import { BUTTON_HIT_SLOP, formatMessageCount } from './utils';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { DISCUSSION } from './constants';
|
import { DISCUSSION } from './constants';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import React, { useContext } from 'react';
|
|
||||||
|
|
||||||
import Touchable from './Touchable';
|
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
|
||||||
import MessageContext from './Context';
|
|
||||||
import styles from './styles';
|
|
||||||
import { useTheme } from '../../theme';
|
|
||||||
import { E2E_MESSAGE_TYPE, themes } from '../../lib/constants';
|
|
||||||
|
|
||||||
const Encrypted = React.memo(({ type }: { type: string }) => {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const { onEncryptedPress } = useContext(MessageContext);
|
|
||||||
|
|
||||||
if (type !== E2E_MESSAGE_TYPE) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Touchable onPress={onEncryptedPress} style={styles.encrypted} hitSlop={BUTTON_HIT_SLOP}>
|
|
||||||
<CustomIcon name='encrypted' size={16} color={themes[theme].auxiliaryText} />
|
|
||||||
</Touchable>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Encrypted;
|
|
|
@ -15,11 +15,11 @@ import Reactions from './Reactions';
|
||||||
import Broadcast from './Broadcast';
|
import Broadcast from './Broadcast';
|
||||||
import Discussion from './Discussion';
|
import Discussion from './Discussion';
|
||||||
import Content from './Content';
|
import Content from './Content';
|
||||||
import ReadReceipt from './ReadReceipt';
|
|
||||||
import CallButton from './CallButton';
|
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 RightIcons from './Components/RightIcons';
|
||||||
|
|
||||||
const MessageInner = React.memo((props: IMessageInner) => {
|
const MessageInner = React.memo((props: IMessageInner) => {
|
||||||
const { attachments } = props;
|
const { attachments } = props;
|
||||||
|
@ -102,7 +102,16 @@ 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>
|
||||||
<ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread || false} />
|
{!props.isHeader ? (
|
||||||
|
<RightIcons
|
||||||
|
type={props.type}
|
||||||
|
msg={props.msg}
|
||||||
|
isEdited={props.isEdited}
|
||||||
|
hasError={props.hasError}
|
||||||
|
isReadReceiptEnabled={props.isReadReceiptEnabled || false}
|
||||||
|
unread={props.unread || false}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { useContext } from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
|
|
||||||
import Touchable from './Touchable';
|
import Touchable from './Touchable';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Emoji from './Emoji';
|
import Emoji from './Emoji';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { memo, useEffect, useState } from 'react';
|
import React, { memo, useEffect, useState } from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
|
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
|
|
@ -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].auxiliaryText }]}>{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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,13 @@ import moment from 'moment';
|
||||||
|
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import MessageError from './MessageError';
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import messageStyles from './styles';
|
import messageStyles from './styles';
|
||||||
import MessageContext from './Context';
|
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 { MessageType, SubscriptionType } from '../../definitions';
|
||||||
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||||
|
import RightIcons from './Components/RightIcons';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -19,7 +19,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
|
||||||
|
@ -41,7 +46,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;
|
||||||
|
@ -52,15 +57,18 @@ interface IMessageUser {
|
||||||
ts?: Date;
|
ts?: Date;
|
||||||
timeFormat?: string;
|
timeFormat?: string;
|
||||||
navToRoomInfo?: (navParam: IRoomInfoParam) => void;
|
navToRoomInfo?: (navParam: IRoomInfoParam) => void;
|
||||||
type: string;
|
type: MessageType;
|
||||||
|
isEdited: boolean;
|
||||||
|
isReadReceiptEnabled?: boolean;
|
||||||
|
unread?: 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();
|
||||||
|
|
||||||
if (isHeader || hasError) {
|
if (isHeader) {
|
||||||
const username = (useRealName && author?.name) || author?.username;
|
const username = (useRealName && author?.name) || author?.username;
|
||||||
const aliasUsername = alias ? (
|
const aliasUsername = alias ? (
|
||||||
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
|
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
|
||||||
|
@ -99,9 +107,15 @@ 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>
|
<RightIcons
|
||||||
{hasError ? <MessageError hasError={hasError} {...props} /> : null}
|
type={type}
|
||||||
|
isEdited={isEdited}
|
||||||
|
hasError={hasError}
|
||||||
|
isReadReceiptEnabled={props.isReadReceiptEnabled || false}
|
||||||
|
unread={props.unread || false}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { dequal } from 'dequal';
|
||||||
import Touchable from './Touchable';
|
import Touchable from './Touchable';
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import { fileDownload } from '../../utils/fileDownload';
|
import { fileDownload } from '../../utils/fileDownload';
|
||||||
|
|
|
@ -5,11 +5,10 @@ import { Subscription } from 'rxjs';
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
import { getMessageTranslation } from './utils';
|
||||||
import { TSupportedThemes, withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { IAttachment, TAnyMessageModel, TGetCustomEmoji } from '../../definitions';
|
||||||
import { IAttachment, TAnyMessageModel } from '../../definitions';
|
|
||||||
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||||
import { E2E_MESSAGE_TYPE, E2E_STATUS, messagesStatus } from '../../lib/constants';
|
import { E2E_MESSAGE_TYPE, E2E_STATUS, messagesStatus } from '../../lib/constants';
|
||||||
|
|
||||||
|
@ -31,6 +30,7 @@ interface IMessageContainerProps {
|
||||||
Message_GroupingPeriod?: number;
|
Message_GroupingPeriod?: number;
|
||||||
isReadReceiptEnabled?: boolean;
|
isReadReceiptEnabled?: boolean;
|
||||||
isThreadRoom: boolean;
|
isThreadRoom: boolean;
|
||||||
|
isSystemMessage?: boolean;
|
||||||
useRealName?: boolean;
|
useRealName?: boolean;
|
||||||
autoTranslateRoom?: boolean;
|
autoTranslateRoom?: boolean;
|
||||||
autoTranslateLanguage?: string;
|
autoTranslateLanguage?: string;
|
||||||
|
@ -254,9 +254,12 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
return t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE;
|
return t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isInfo(): boolean {
|
get isInfo(): string | boolean {
|
||||||
const { item } = this.props;
|
const { item } = this.props;
|
||||||
return (item.t && SYSTEM_MESSAGES.includes(item.t)) ?? false;
|
if (['e2e', 'discussion-created', 'jitsi_call_started'].includes(item.t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return item.t;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isTemp(): boolean {
|
get isTemp(): boolean {
|
||||||
|
|
|
@ -45,7 +45,7 @@ export interface IMessageCallButton {
|
||||||
export interface IMessageContent {
|
export interface IMessageContent {
|
||||||
_id: string;
|
_id: string;
|
||||||
isTemp: boolean;
|
isTemp: boolean;
|
||||||
isInfo: boolean;
|
isInfo: string | boolean;
|
||||||
tmid?: string;
|
tmid?: string;
|
||||||
isThreadRoom: boolean;
|
isThreadRoom: boolean;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
|
@ -58,8 +58,10 @@ export interface IMessageContent {
|
||||||
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
||||||
useRealName?: boolean;
|
useRealName?: boolean;
|
||||||
isIgnored: boolean;
|
isIgnored: boolean;
|
||||||
type: string;
|
type: MessageType;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
|
hasError: boolean;
|
||||||
|
isHeader: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageEmoji {
|
export interface IMessageEmoji {
|
||||||
|
@ -76,7 +78,7 @@ export interface IMessageThread extends Pick<IThread, 'msg' | 'tcount' | 'tlm' |
|
||||||
|
|
||||||
export interface IMessageTouchable {
|
export interface IMessageTouchable {
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
isInfo: boolean;
|
isInfo: string | boolean;
|
||||||
isThreadReply: boolean;
|
isThreadReply: boolean;
|
||||||
isTemp: boolean;
|
isTemp: boolean;
|
||||||
archived?: boolean;
|
archived?: boolean;
|
||||||
|
@ -110,7 +112,7 @@ export interface IMessageInner
|
||||||
export interface IMessage extends IMessageRepliedThread, IMessageInner, IMessageAvatar {
|
export interface IMessage extends IMessageRepliedThread, IMessageInner, IMessageAvatar {
|
||||||
isThreadReply: boolean;
|
isThreadReply: boolean;
|
||||||
isThreadSequential: boolean;
|
isThreadSequential: boolean;
|
||||||
isInfo: boolean;
|
isInfo: string | boolean;
|
||||||
isTemp: boolean;
|
isTemp: boolean;
|
||||||
isHeader: boolean;
|
isHeader: boolean;
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
|
|
|
@ -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,11 +163,8 @@ export default StyleSheet.create({
|
||||||
threadBell: {
|
threadBell: {
|
||||||
marginLeft: 8
|
marginLeft: 8
|
||||||
},
|
},
|
||||||
readReceipt: {
|
rightIcons: {
|
||||||
lineHeight: 20
|
paddingLeft: 5
|
||||||
},
|
|
||||||
encrypted: {
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
},
|
||||||
threadDetails: {
|
threadDetails: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable complexity */
|
||||||
import { TMessageModel } from '../../definitions/IMessage';
|
import { TMessageModel } from '../../definitions/IMessage';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { DISCUSSION } from './constants';
|
import { DISCUSSION } from './constants';
|
||||||
|
@ -56,10 +57,25 @@ export const SYSTEM_MESSAGES = [
|
||||||
'user-converted-to-channel',
|
'user-converted-to-channel',
|
||||||
'user-deleted-room-from-team',
|
'user-deleted-room-from-team',
|
||||||
'user-removed-room-from-team',
|
'user-removed-room-from-team',
|
||||||
|
'room-disallowed-reacting',
|
||||||
|
'room-allowed-reacting',
|
||||||
|
'room-set-read-only',
|
||||||
|
'room-removed-read-only',
|
||||||
'omnichannel_placed_chat_on_hold',
|
'omnichannel_placed_chat_on_hold',
|
||||||
'omnichannel_on_hold_chat_resumed'
|
'omnichannel_on_hold_chat_resumed'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const IGNORED_LIVECHAT_SYSTEM_MESSAGES = [
|
||||||
|
'livechat_navigation_history',
|
||||||
|
'livechat_transcript_history',
|
||||||
|
'livechat_transfer_history',
|
||||||
|
'command',
|
||||||
|
'livechat-close',
|
||||||
|
'livechat-started',
|
||||||
|
'livechat_video_call',
|
||||||
|
'livechat_webrtc_video_call'
|
||||||
|
];
|
||||||
|
|
||||||
export const SYSTEM_MESSAGE_TYPES = {
|
export const SYSTEM_MESSAGE_TYPES = {
|
||||||
MESSAGE_REMOVED: 'rm',
|
MESSAGE_REMOVED: 'rm',
|
||||||
MESSAGE_PINNED: 'message_pinned',
|
MESSAGE_PINNED: 'message_pinned',
|
||||||
|
@ -77,7 +93,15 @@ export const SYSTEM_MESSAGE_TYPES = {
|
||||||
DELETED_ROOM_FROM_TEAM: 'user-deleted-room-from-team',
|
DELETED_ROOM_FROM_TEAM: 'user-deleted-room-from-team',
|
||||||
REMOVED_ROOM_FROM_TEAM: 'user-removed-room-from-team',
|
REMOVED_ROOM_FROM_TEAM: 'user-removed-room-from-team',
|
||||||
OMNICHANNEL_PLACED_CHAT_ON_HOLD: 'omnichannel_placed_chat_on_hold',
|
OMNICHANNEL_PLACED_CHAT_ON_HOLD: 'omnichannel_placed_chat_on_hold',
|
||||||
OMNICHANNEL_ON_HOLD_CHAT_RESUMED: 'omnichannel_on_hold_chat_resumed'
|
OMNICHANNEL_ON_HOLD_CHAT_RESUMED: 'omnichannel_on_hold_chat_resumed',
|
||||||
|
LIVECHAT_NAVIGATION_HISTORY: 'livechat_navigation_history',
|
||||||
|
LIVECHAT_TRANSCRIPT_HISTORY: 'livechat_transcript_history',
|
||||||
|
COMMAND: 'command',
|
||||||
|
LIVECHAT_STARTED: 'livechat-started',
|
||||||
|
LIVECHAT_CLOSE: 'livechat-close',
|
||||||
|
LIVECHAT_VIDEO_CALL: 'livechat_video_call',
|
||||||
|
LIVECHAT_WEBRTC_VIDEO_CALL: 'livechat_webrtc_video_call',
|
||||||
|
LIVECHAT_TRANSFER_HISTORY: 'livechat_transfer_history'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
|
export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
|
||||||
|
@ -95,7 +119,15 @@ export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
|
||||||
SYSTEM_MESSAGE_TYPES.CONVERTED_TO_TEAM,
|
SYSTEM_MESSAGE_TYPES.CONVERTED_TO_TEAM,
|
||||||
SYSTEM_MESSAGE_TYPES.CONVERTED_TO_CHANNEL,
|
SYSTEM_MESSAGE_TYPES.CONVERTED_TO_CHANNEL,
|
||||||
SYSTEM_MESSAGE_TYPES.DELETED_ROOM_FROM_TEAM,
|
SYSTEM_MESSAGE_TYPES.DELETED_ROOM_FROM_TEAM,
|
||||||
SYSTEM_MESSAGE_TYPES.REMOVED_ROOM_FROM_TEAM
|
SYSTEM_MESSAGE_TYPES.REMOVED_ROOM_FROM_TEAM,
|
||||||
|
SYSTEM_MESSAGE_TYPES.LIVECHAT_NAVIGATION_HISTORY,
|
||||||
|
SYSTEM_MESSAGE_TYPES.LIVECHAT_TRANSCRIPT_HISTORY,
|
||||||
|
SYSTEM_MESSAGE_TYPES.COMMAND,
|
||||||
|
SYSTEM_MESSAGE_TYPES.LIVECHAT_STARTED,
|
||||||
|
SYSTEM_MESSAGE_TYPES.LIVECHAT_CLOSE,
|
||||||
|
SYSTEM_MESSAGE_TYPES.LIVECHAT_VIDEO_CALL,
|
||||||
|
SYSTEM_MESSAGE_TYPES.LIVECHAT_WEBRTC_VIDEO_CALL,
|
||||||
|
SYSTEM_MESSAGE_TYPES.LIVECHAT_TRANSFER_HISTORY
|
||||||
];
|
];
|
||||||
|
|
||||||
type TInfoMessage = {
|
type TInfoMessage = {
|
||||||
|
@ -108,6 +140,7 @@ type TInfoMessage = {
|
||||||
|
|
||||||
export const getInfoMessage = ({ type, role, msg, author, comment }: TInfoMessage): string => {
|
export const getInfoMessage = ({ type, role, msg, author, comment }: TInfoMessage): string => {
|
||||||
const { username } = author;
|
const { username } = author;
|
||||||
|
|
||||||
if (type === 'rm') {
|
if (type === 'rm') {
|
||||||
return I18n.t('Message_removed');
|
return I18n.t('Message_removed');
|
||||||
}
|
}
|
||||||
|
@ -198,13 +231,37 @@ export const getInfoMessage = ({ type, role, msg, author, comment }: TInfoMessag
|
||||||
if (type === 'user-removed-room-from-team') {
|
if (type === 'user-removed-room-from-team') {
|
||||||
return I18n.t('Removed__roomName__from_this_team', { roomName: msg });
|
return I18n.t('Removed__roomName__from_this_team', { roomName: msg });
|
||||||
}
|
}
|
||||||
|
if (type === 'room-disallowed-reacting') {
|
||||||
|
return I18n.t('Room_disallowed_reacting', { userBy: username });
|
||||||
|
}
|
||||||
|
if (type === 'room-allowed-reacting') {
|
||||||
|
return I18n.t('Room_allowed_reacting', { userBy: username });
|
||||||
|
}
|
||||||
|
if (type === 'room-set-read-only') {
|
||||||
|
return I18n.t('Room_set_read_only', { userBy: username });
|
||||||
|
}
|
||||||
|
if (type === 'room-removed-read-only') {
|
||||||
|
return I18n.t('Room_removed_read_only', { userBy: username });
|
||||||
|
}
|
||||||
if (type === 'omnichannel_placed_chat_on_hold') {
|
if (type === 'omnichannel_placed_chat_on_hold') {
|
||||||
return I18n.t('Omnichannel_placed_chat_on_hold', { comment });
|
return I18n.t('Omnichannel_placed_chat_on_hold', { comment });
|
||||||
}
|
}
|
||||||
if (type === 'omnichannel_on_hold_chat_resumed') {
|
if (type === 'omnichannel_on_hold_chat_resumed') {
|
||||||
return I18n.t('Omnichannel_on_hold_chat_resumed', { comment });
|
return I18n.t('Omnichannel_on_hold_chat_resumed', { comment });
|
||||||
}
|
}
|
||||||
return '';
|
if (type === 'command') {
|
||||||
|
return I18n.t('Livechat_transfer_return_to_the_queue');
|
||||||
|
}
|
||||||
|
if (type === 'livechat-started') {
|
||||||
|
return I18n.t('Chat_started');
|
||||||
|
}
|
||||||
|
if (type === 'livechat-close') {
|
||||||
|
return I18n.t('Conversation_closed');
|
||||||
|
}
|
||||||
|
if (type === 'livechat_transfer_history') {
|
||||||
|
return I18n.t('New_chat_transfer', { agent: username });
|
||||||
|
}
|
||||||
|
return I18n.t('Unsupported_system_message');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMessageTranslation = (message: TMessageModel, autoTranslateLanguage: string) => {
|
export const getMessageTranslation = (message: TMessageModel, autoTranslateLanguage: string) => {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IAssetsFavicon512 {
|
||||||
|
url?: string;
|
||||||
|
defaultUrl: string;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue