Merge branch 'develop' into update.enable-multiline-android-tablets

This commit is contained in:
Gerzon Z 2022-04-04 17:11:05 -04:00
commit c369baf63f
166 changed files with 2218 additions and 1887 deletions

View File

@ -3,7 +3,7 @@ defaults: &defaults
macos: &macos
macos:
xcode: "12.5.0"
xcode: "13.3.0"
resource_class: large
bash-env: &bash-env

View File

@ -1,45 +1,8 @@
import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots';
import { render } from '@testing-library/react-native';
jest.mock('rn-fetch-blob', () => ({
fs: {
dirs: {
DocumentDir: '/data/com.rocket.chat/documents',
DownloadDir: '/data/com.rocket.chat/downloads'
},
exists: jest.fn(() => null)
},
fetch: jest.fn(() => null),
config: jest.fn(() => null)
}));
jest.mock('react-native-file-viewer', () => ({
open: jest.fn(() => null)
}));
jest.mock('../app/lib/database', () => jest.fn(() => null));
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
jest.mock('react-native-mmkv-storage', () => {
return {
Loader: jest.fn().mockImplementation(() => {
return {
setProcessingMode: jest.fn().mockImplementation(() => {
return {
withEncryption: jest.fn().mockImplementation(() => {
return {
initialize: jest.fn()
};
})
};
})
};
}),
create: jest.fn(),
MODES: { MULTI_PROCESS: '' }
};
});
const converter = new Stories2SnapsConverter();
initStoryshots({

View File

@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.26.0"
versionName "4.26.2"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]

View File

@ -1,8 +1,7 @@
import { Action } from 'redux';
import { MESSAGES } from './actionsTypes';
type IMessage = Record<string, string>;
import { IMessage } from '../definitions';
interface IReplyBroadcast extends Action {
message: IMessage;

View File

@ -19,7 +19,7 @@ const mentions = {
mentionOtherColor: '#F3BE08'
};
export const themes: any = {
export const colors = {
light: {
backgroundColor: '#ffffff',
focusedBackground: '#ffffff',
@ -174,3 +174,5 @@ export const themes: any = {
...mentions
}
};
export const themes: any = colors;

View File

@ -2,7 +2,7 @@ import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
import ActionSheet from './ActionSheet';
export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void };
export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void; danger?: boolean };
export type TActionSheetOptions = {
options: TActionSheetOptionsItem[];

View File

@ -8,6 +8,7 @@ import { avatarURL } from '../../utils/avatar';
import { SubscriptionType } from '../../definitions/ISubscription';
import Emoji from '../markdown/Emoji';
import { IAvatar } from './interfaces';
import { useTheme } from '../../theme';
const Avatar = React.memo(
({
@ -18,7 +19,6 @@ const Avatar = React.memo(
user,
onPress,
emoji,
theme,
getCustomEmoji,
avatarETag,
isStatic,
@ -34,6 +34,8 @@ const Avatar = React.memo(
return null;
}
const { theme } = useTheme();
const avatarStyle = {
width: size,
height: size,
@ -44,7 +46,7 @@ const Avatar = React.memo(
if (emoji) {
image = (
<Emoji
theme={theme!}
theme={theme}
baseUrl={server}
getCustomEmoji={getCustomEmoji}
isMessageContainsOnlyEmoji

View File

@ -5,13 +5,11 @@ import { Observable, Subscription } from 'rxjs';
import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login';
import { TSubscriptionModel, TUserModel } from '../../definitions';
import { IApplicationState, TSubscriptionModel, TUserModel } from '../../definitions';
import Avatar from './Avatar';
import { IAvatar } from './interfaces';
class AvatarContainer extends React.Component<IAvatar, any> {
private mounted: boolean;
private subscription?: Subscription;
static defaultProps = {
@ -21,16 +19,11 @@ class AvatarContainer extends React.Component<IAvatar, any> {
constructor(props: IAvatar) {
super(props);
this.mounted = false;
this.state = { avatarETag: '' };
this.init();
}
componentDidMount() {
this.mounted = true;
}
componentDidUpdate(prevProps: any) {
componentDidUpdate(prevProps: IAvatar) {
const { text, type } = this.props;
if (prevProps.text !== text || prevProps.type !== type) {
this.init();
@ -88,12 +81,7 @@ class AvatarContainer extends React.Component<IAvatar, any> {
const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
this.subscription = observable.subscribe(r => {
const { avatarETag } = r;
if (this.mounted) {
this.setState({ avatarETag });
} else {
// @ts-ignore
this.state.avatarETag = avatarETag;
}
this.setState({ avatarETag });
});
}
};
@ -105,12 +93,12 @@ class AvatarContainer extends React.Component<IAvatar, any> {
}
}
const mapStateToProps = (state: any) => ({
const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state),
server: state.share.server.server || state.server.server,
serverVersion: state.share.server.version || state.server.version,
blockUnauthenticatedAccess:
state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess ??
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
true
});

View File

@ -16,12 +16,11 @@ export interface IAvatar {
id?: string;
token?: string;
};
theme?: string;
onPress?: () => void;
getCustomEmoji?: TGetCustomEmoji;
avatarETag?: string;
isStatic?: boolean | string;
rid?: string;
blockUnauthenticatedAccess?: boolean;
serverVersion: string;
serverVersion: string | null;
}

View File

@ -8,6 +8,7 @@ const CustomEmoji = React.memo(
<FastImage
style={style}
source={{
// @ts-ignore
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`,
priority: FastImage.priority.high
}}

View File

@ -56,6 +56,7 @@ class EmojiCategory extends React.Component<Partial<IEmojiCategory>> {
contentContainerStyle={{ marginHorizontal }}
// rerender FlatList in case of width changes
key={`emoji-category-${width}`}
// @ts-ignore
keyExtractor={item => (item && item.isCustom && item.content) || item}
data={emojis}
extraData={this.props}

View File

@ -109,6 +109,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojiRecord: any;
try {
// @ts-ignore
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
} catch (error) {
// Do nothing

View File

@ -285,7 +285,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
toValue: height,
duration: 300,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true
useNativeDriver: false
}).start();
};

View File

@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { withTheme } from '../../theme';
import { useTheme } from '../../theme';
import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons';
import shortnameToUnicode from '../../utils/shortnameToUnicode';
@ -10,26 +10,30 @@ import database from '../../lib/database';
import { Button } from '../ActionSheet';
import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles';
import { IEmoji } from '../../definitions/IEmoji';
import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji';
import { TAnyMessageModel } from '../../definitions';
import { IEmoji } from '../../definitions/IEmoji';
interface IHeader {
handleReaction: Function;
type TItem = TFrequentlyUsedEmojiModel | string;
export interface IHeader {
handleReaction: (emoji: TItem, message: TAnyMessageModel) => void;
server: string;
message: object;
message: TAnyMessageModel;
isMasterDetail: boolean;
theme?: string;
}
type TOnReaction = ({ emoji }: { emoji: TItem }) => void;
interface THeaderItem {
item: IEmoji;
onReaction: Function;
item: TItem;
onReaction: TOnReaction;
server: string;
theme: string;
}
interface THeaderFooter {
onReaction: any;
onReaction: TOnReaction;
theme: string;
}
@ -62,25 +66,32 @@ const styles = StyleSheet.create({
}
});
const keyExtractor = (item: any) => item?.id || item;
const keyExtractor = (item: TItem) => {
const emojiModel = item as TFrequentlyUsedEmojiModel;
return (emojiModel.id ? emojiModel.content : item) as string;
};
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
const HeaderItem = React.memo(({ item, onReaction, server, theme }: THeaderItem) => (
<Button
testID={`message-actions-emoji-${item.content || item}`}
onPress={() => onReaction({ emoji: `:${item.content || item}:` })}
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
theme={theme}>
{item?.isCustom ? (
<CustomEmoji style={styles.customEmoji} emoji={item} baseUrl={server} />
) : (
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${item.content || item}:`)}</Text>
)}
</Button>
));
const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => {
const emojiModel = item as TFrequentlyUsedEmojiModel;
const emoji = (emojiModel.id ? emojiModel.content : item) as string;
return (
<Button
testID={`message-actions-emoji-${emoji}`}
onPress={() => onReaction({ emoji: `:${emoji}:` })}
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
theme={theme}>
{emojiModel?.isCustom ? (
<CustomEmoji style={styles.customEmoji} emoji={emojiModel as IEmoji} baseUrl={server} />
) : (
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${emoji}:`)}</Text>
)}
</Button>
);
};
const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
<Button
testID='add-reaction'
onPress={onReaction}
@ -88,17 +99,19 @@ const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
theme={theme}>
<CustomIcon name='reaction-add' size={24} color={themes[theme].bodyText} />
</Button>
));
);
const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => {
const [items, setItems] = useState<(TFrequentlyUsedEmojiModel | string)[]>([]);
const { width, height }: any = useDimensions();
const Header = React.memo(({ handleReaction, server, message, isMasterDetail }: IHeader) => {
const [items, setItems] = useState<TItem[]>([]);
const { width, height } = useDimensions();
const { theme } = useTheme();
// TODO: create custom hook to re-render based on screen size
const setEmojis = async () => {
try {
const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojis: (TFrequentlyUsedEmojiModel | string)[] = await freqEmojiCollection.query().fetch();
let freqEmojis: TItem[] = await freqEmojiCollection.query().fetch();
const isLandscape = width > height;
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
@ -115,22 +128,21 @@ const Header = React.memo(({ handleReaction, server, message, isMasterDetail, th
setEmojis();
}, []);
const onReaction = ({ emoji }: { emoji: IEmoji }) => handleReaction(emoji, message);
const onReaction: TOnReaction = ({ emoji }) => handleReaction(emoji, message);
const renderItem = useCallback(
({ item }) => <HeaderItem item={item} onReaction={onReaction} server={server} theme={theme!} />,
[]
const renderItem = ({ item }: { item: TItem }) => (
<HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />
);
const renderFooter = useCallback(() => <HeaderFooter onReaction={onReaction} theme={theme!} />, []);
const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />;
return (
<View style={[styles.container, { backgroundColor: themes[theme!].focusedBackground }]}>
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
<FlatList
data={items}
renderItem={renderItem}
ListFooterComponent={renderFooter}
style={{ backgroundColor: themes[theme!].focusedBackground }}
style={{ backgroundColor: themes[theme].focusedBackground }}
keyExtractor={keyExtractor}
showsHorizontalScrollIndicator={false}
scrollEnabled={false}
@ -140,4 +152,4 @@ const Header = React.memo(({ handleReaction, server, message, isMasterDetail, th
);
});
export default withTheme(Header);
export default Header;

View File

@ -1,5 +1,6 @@
import React, { forwardRef, useImperativeHandle } from 'react';
import { Alert, Clipboard, Share } from 'react-native';
import { Alert, Share } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import { connect } from 'react-redux';
import moment from 'moment';
@ -12,33 +13,33 @@ import { getMessageTranslation } from '../message/utils';
import { LISTENER } from '../Toast';
import EventEmitter from '../../utils/events';
import { showConfirmationAlert } from '../../utils/info';
import { useActionSheet } from '../ActionSheet';
import Header, { HEADER_HEIGHT } from './Header';
import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet';
import Header, { HEADER_HEIGHT, IHeader } from './Header';
import events from '../../utils/log/events';
import { ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
export interface IMessageActions {
room: TSubscriptionModel;
tmid?: string;
user: Pick<ILoggedUser, 'id'>;
editInit: Function;
reactionInit: Function;
onReactionPress: Function;
replyInit: Function;
editInit: (message: TAnyMessageModel) => void;
reactionInit: (message: TAnyMessageModel) => void;
onReactionPress: (shortname: string, messageId: string) => void;
replyInit: (message: TAnyMessageModel, mention: boolean) => void;
isMasterDetail: boolean;
isReadOnly: boolean;
Message_AllowDeleting: boolean;
Message_AllowDeleting_BlockDeleteInMinutes: number;
Message_AllowEditing: boolean;
Message_AllowEditing_BlockEditInMinutes: number;
Message_AllowPinning: boolean;
Message_AllowStarring: boolean;
Message_Read_Receipt_Store_Users: boolean;
Message_AllowDeleting?: boolean;
Message_AllowDeleting_BlockDeleteInMinutes?: number;
Message_AllowEditing?: boolean;
Message_AllowEditing_BlockEditInMinutes?: number;
Message_AllowPinning?: boolean;
Message_AllowStarring?: boolean;
Message_Read_Receipt_Store_Users?: boolean;
server: string;
editMessagePermission: [];
deleteMessagePermission: [];
forceDeleteMessagePermission: [];
pinMessagePermission: [];
editMessagePermission?: string[];
deleteMessagePermission?: string[];
forceDeleteMessagePermission?: string[];
pinMessagePermission?: string[];
}
const MessageActions = React.memo(
@ -68,9 +69,14 @@ const MessageActions = React.memo(
pinMessagePermission
}: IMessageActions,
ref
): any => {
let permissions: any = {};
const { showActionSheet, hideActionSheet }: any = useActionSheet();
) => {
let permissions = {
hasEditPermission: false,
hasDeletePermission: false,
hasForceDeletePermission: false,
hasPinPermission: false
};
const { showActionSheet, hideActionSheet } = useActionSheet();
const getPermissions = async () => {
try {
@ -87,9 +93,9 @@ const MessageActions = React.memo(
}
};
const isOwn = (message: any) => message.u && message.u._id === user.id;
const isOwn = (message: TAnyMessageModel) => message.u && message.u._id === user.id;
const allowEdit = (message: any) => {
const allowEdit = (message: TAnyMessageModel) => {
if (isReadOnly) {
return false;
}
@ -104,7 +110,7 @@ const MessageActions = React.memo(
if (message.ts != null) {
msgTs = moment(message.ts);
}
let currentTsDiff: any;
let currentTsDiff = 0;
if (msgTs != null) {
currentTsDiff = moment().diff(msgTs, 'minutes');
}
@ -113,7 +119,7 @@ const MessageActions = React.memo(
return true;
};
const allowDelete = (message: any) => {
const allowDelete = (message: TAnyMessageModel) => {
if (isReadOnly) {
return false;
}
@ -135,7 +141,7 @@ const MessageActions = React.memo(
if (message.ts != null) {
msgTs = moment(message.ts);
}
let currentTsDiff: any;
let currentTsDiff = 0;
if (msgTs != null) {
currentTsDiff = moment().diff(msgTs, 'minutes');
}
@ -144,19 +150,19 @@ const MessageActions = React.memo(
return true;
};
const getPermalink = (message: any) => RocketChat.getPermalinkMessage(message);
const getPermalink = (message: TAnyMessageModel) => RocketChat.getPermalinkMessage(message);
const handleReply = (message: any) => {
const handleReply = (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_REPLY);
replyInit(message, true);
};
const handleEdit = (message: any) => {
const handleEdit = (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_EDIT);
editInit(message);
};
const handleCreateDiscussion = (message: any) => {
const handleCreateDiscussion = (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_DISCUSSION);
const params = { message, channel: room, showCloseModal: true };
if (isMasterDetail) {
@ -166,7 +172,7 @@ const MessageActions = React.memo(
}
};
const handleUnread = async (message: any) => {
const handleUnread = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_UNREAD);
const { id: messageId, ts } = message;
const { rid } = room;
@ -178,7 +184,7 @@ const MessageActions = React.memo(
const subRecord = await subCollection.find(rid);
await db.write(async () => {
try {
await subRecord.update(sub => (sub.lastOpen = ts));
await subRecord.update(sub => (sub.lastOpen = ts as Date)); // TODO: reevaluate IMessage
} catch {
// do nothing
}
@ -191,42 +197,44 @@ const MessageActions = React.memo(
}
};
const handlePermalink = async (message: any) => {
const handlePermalink = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_PERMALINK);
try {
const permalink: any = await getPermalink(message);
Clipboard.setString(permalink);
const permalink = await getPermalink(message);
Clipboard.setString(permalink ?? '');
EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') });
} catch {
logEvent(events.ROOM_MSG_ACTION_PERMALINK_F);
}
};
const handleCopy = async (message: any) => {
const handleCopy = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_COPY);
await Clipboard.setString(message?.attachments?.[0]?.description || message.msg);
await Clipboard.setString((message?.attachments?.[0]?.description || message.msg) ?? '');
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
};
const handleShare = async (message: any) => {
const handleShare = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_SHARE);
try {
const permalink: any = await getPermalink(message);
Share.share({ message: permalink });
const permalink = await getPermalink(message);
if (permalink) {
Share.share({ message: permalink });
}
} catch {
logEvent(events.ROOM_MSG_ACTION_SHARE_F);
}
};
const handleQuote = (message: any) => {
const handleQuote = (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_QUOTE);
replyInit(message, false);
};
const handleStar = async (message: any) => {
const handleStar = async (message: TAnyMessageModel) => {
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
try {
await RocketChat.toggleStarMessage(message.id, message.starred);
await RocketChat.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') });
} catch (e) {
logEvent(events.ROOM_MSG_ACTION_STAR_F);
@ -234,20 +242,21 @@ const MessageActions = React.memo(
}
};
const handlePin = async (message: any) => {
const handlePin = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_PIN);
try {
await RocketChat.togglePinMessage(message.id, message.pinned);
await RocketChat.togglePinMessage(message.id, message.pinned as boolean); // TODO: reevaluate `message.pinned` type on IMessage
} catch (e) {
logEvent(events.ROOM_MSG_ACTION_PIN_F);
log(e);
}
};
const handleReaction = (shortname: any, message: any) => {
const handleReaction: IHeader['handleReaction'] = (shortname, message) => {
logEvent(events.ROOM_MSG_ACTION_REACTION);
if (shortname) {
onReactionPress(shortname, message.id);
// TODO: evaluate unification with IEmoji
onReactionPress(shortname as any, message.id);
} else {
reactionInit(message);
}
@ -255,7 +264,7 @@ const MessageActions = React.memo(
hideActionSheet();
};
const handleReadReceipt = (message: any) => {
const handleReadReceipt = (message: TAnyMessageModel) => {
if (isMasterDetail) {
Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } });
} else {
@ -290,7 +299,7 @@ const MessageActions = React.memo(
}
};
const handleReport = async (message: any) => {
const handleReport = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_REPORT);
try {
await RocketChat.reportMessage(message.id);
@ -301,14 +310,14 @@ const MessageActions = React.memo(
}
};
const handleDelete = (message: any) => {
const handleDelete = (message: TAnyMessageModel) => {
showConfirmationAlert({
message: I18n.t('You_will_not_be_able_to_recover_this_message'),
confirmationText: I18n.t('Delete'),
onPress: async () => {
try {
logEvent(events.ROOM_MSG_ACTION_DELETE);
await RocketChat.deleteMessage(message.id, message.subscription.id);
await RocketChat.deleteMessage(message.id, message.subscription ? message.subscription.id : '');
} catch (e) {
logEvent(events.ROOM_MSG_ACTION_DELETE_F);
log(e);
@ -318,7 +327,7 @@ const MessageActions = React.memo(
};
const getOptions = (message: TAnyMessageModel) => {
let options: any = [];
let options: TActionSheetOptionsItem[] = [];
// Reply
if (!isReadOnly) {
@ -462,16 +471,15 @@ const MessageActions = React.memo(
}
)
);
const mapStateToProps = (state: any) => ({
const mapStateToProps = (state: IApplicationState) => ({
server: state.server.server,
Message_AllowDeleting: state.settings.Message_AllowDeleting,
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes,
Message_AllowEditing: state.settings.Message_AllowEditing,
Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes,
Message_AllowPinning: state.settings.Message_AllowPinning,
Message_AllowStarring: state.settings.Message_AllowStarring,
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users,
Message_AllowDeleting: state.settings.Message_AllowDeleting as boolean,
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes as number,
Message_AllowEditing: state.settings.Message_AllowEditing as boolean,
Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes as number,
Message_AllowPinning: state.settings.Message_AllowPinning as boolean,
Message_AllowStarring: state.settings.Message_AllowStarring as boolean,
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users as boolean,
isMasterDetail: state.app.isMasterDetail,
editMessagePermission: state.permissions['edit-message'],
deleteMessagePermission: state.permissions['delete-message'],

View File

@ -1,12 +1,13 @@
import FastImage from '@rocket.chat/react-native-fast-image';
import React, { useContext, useState } from 'react';
import { TouchableOpacity } from 'react-native';
import FastImage from '@rocket.chat/react-native-fast-image';
import styles from '../styles';
import { CustomIcon } from '../../../lib/Icons';
import { themes } from '../../../constants/colors';
import MessageboxContext from '../Context';
import { CustomIcon } from '../../../lib/Icons';
import { useTheme } from '../../../theme';
import ActivityIndicator from '../../ActivityIndicator';
import MessageboxContext from '../Context';
import styles from '../styles';
interface IMessageBoxCommandsPreviewItem {
item: {
@ -14,13 +15,13 @@ interface IMessageBoxCommandsPreviewItem {
id: string;
value: string;
};
theme?: string;
}
const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => {
const Item = ({ item }: IMessageBoxCommandsPreviewItem) => {
const context = useContext(MessageboxContext);
const { onPressCommandPreview } = context;
const [loading, setLoading] = useState(true);
const { theme } = useTheme();
return (
<TouchableOpacity
@ -37,7 +38,7 @@ const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => {
{loading ? <ActivityIndicator /> : null}
</FastImage>
) : (
<CustomIcon name='attach' size={36} color={themes[theme!].actionTintColor} />
<CustomIcon name='attach' size={36} color={themes[theme].actionTintColor} />
)}
</TouchableOpacity>
);

View File

@ -1,30 +1,30 @@
import { dequal } from 'dequal';
import React from 'react';
import { FlatList } from 'react-native';
import { dequal } from 'dequal';
import Item from './Item';
import styles from '../styles';
import { themes } from '../../../constants/colors';
import { withTheme } from '../../../theme';
import { IPreviewItem } from '../../../definitions';
import { useTheme } from '../../../theme';
import styles from '../styles';
import Item from './Item';
interface IMessageBoxCommandsPreview {
commandPreview: IPreviewItem[];
showCommandPreview: boolean;
theme?: string;
}
const CommandsPreview = React.memo(
({ theme, commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
({ commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
if (!showCommandPreview) {
return null;
}
const { theme } = useTheme();
return (
<FlatList
testID='commandbox-container'
style={[styles.mentionList, { backgroundColor: themes[theme!].messageboxBackground }]}
style={[styles.mentionList, { backgroundColor: themes[theme].messageboxBackground }]}
data={commandPreview}
renderItem={({ item }) => <Item item={item} theme={theme} />}
renderItem={({ item }) => <Item item={item} />}
keyExtractor={(item: any) => item.id}
keyboardShouldPersistTaps='always'
horizontal
@ -33,9 +33,6 @@ const CommandsPreview = React.memo(
);
},
(prevProps, nextProps) => {
if (prevProps.theme !== nextProps.theme) {
return false;
}
if (prevProps.showCommandPreview !== nextProps.showCommandPreview) {
return false;
}
@ -46,4 +43,4 @@ const CommandsPreview = React.memo(
}
);
export default withTheme(CommandsPreview);
export default CommandsPreview;

View File

@ -1,5 +1,4 @@
import React from 'react';
// @ts-ignore
const MessageboxContext = React.createContext<any>();
const MessageboxContext = React.createContext<any>(null);
export default MessageboxContext;

View File

@ -7,6 +7,7 @@ import EmojiPicker from '../EmojiPicker';
import styles from './styles';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import { IEmoji } from '../../definitions/IEmoji';
interface IMessageBoxEmojiKeyboard {
theme: string;
@ -21,7 +22,7 @@ export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiK
this.baseUrl = state.share.server.server || state.server.server;
}
onEmojiSelected = (emoji: any) => {
onEmojiSelected = (emoji: IEmoji) => {
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
};

View File

@ -3,7 +3,6 @@ import React from 'react';
import { CancelEditingButton, ToggleEmojiButton } from './buttons';
interface IMessageBoxLeftButtons {
theme: string;
showEmojiKeyboard: boolean;
openEmoji(): void;
closeEmoji(): void;
@ -11,13 +10,11 @@ interface IMessageBoxLeftButtons {
editCancel(): void;
}
const LeftButtons = React.memo(
({ theme, showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji }: IMessageBoxLeftButtons) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} theme={theme} />;
}
return <ToggleEmojiButton show={showEmojiKeyboard} open={openEmoji} close={closeEmoji} theme={theme} />;
const LeftButtons = React.memo(({ showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji }: IMessageBoxLeftButtons) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} />;
}
);
return <ToggleEmojiButton show={showEmojiKeyboard} open={openEmoji} close={closeEmoji} />;
});
export default LeftButtons;

View File

@ -5,23 +5,20 @@ import { ActionsButton, CancelEditingButton } from './buttons';
import styles from './styles';
interface IMessageBoxLeftButtons {
theme: string;
showMessageBoxActions(): void;
editing: boolean;
editCancel(): void;
isActionsEnabled: boolean;
}
const LeftButtons = React.memo(
({ theme, showMessageBoxActions, editing, editCancel, isActionsEnabled }: IMessageBoxLeftButtons) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} theme={theme} />;
}
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
}
return <View style={styles.buttonsWhitespace} />;
const LeftButtons = React.memo(({ showMessageBoxActions, editing, editCancel, isActionsEnabled }: IMessageBoxLeftButtons) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} />;
}
);
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} />;
}
return <View style={styles.buttonsWhitespace} />;
});
export default LeftButtons;

View File

@ -4,30 +4,33 @@ import { Text, TouchableOpacity } from 'react-native';
import styles from '../styles';
import I18n from '../../../i18n';
import { themes } from '../../../constants/colors';
import { useTheme } from '../../../theme';
interface IMessageBoxFixedMentionItem {
item: {
username: string;
};
onPress: Function;
theme: string;
}
const FixedMentionItem = ({ item, onPress, theme }: IMessageBoxFixedMentionItem) => (
<TouchableOpacity
style={[
styles.mentionItem,
{
backgroundColor: themes[theme].auxiliaryBackground,
borderTopColor: themes[theme].separatorColor
}
]}
onPress={() => onPress(item)}>
<Text style={[styles.fixedMentionAvatar, { color: themes[theme].titleText }]}>{item.username}</Text>
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>
{item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')}
</Text>
</TouchableOpacity>
);
const FixedMentionItem = ({ item, onPress }: IMessageBoxFixedMentionItem) => {
const { theme } = useTheme();
return (
<TouchableOpacity
style={[
styles.mentionItem,
{
backgroundColor: themes[theme].auxiliaryBackground,
borderTopColor: themes[theme].separatorColor
}
]}
onPress={() => onPress(item)}>
<Text style={[styles.fixedMentionAvatar, { color: themes[theme].titleText }]}>{item.username}</Text>
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>
{item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')}
</Text>
</TouchableOpacity>
);
};
export default FixedMentionItem;

View File

@ -1,12 +1,11 @@
import React, { useContext } from 'react';
import { Text } from 'react-native';
import PropTypes from 'prop-types';
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
import styles from '../styles';
import MessageboxContext from '../Context';
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
import { IEmoji } from '../../../definitions/IEmoji';
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
import MessageboxContext from '../Context';
import styles from '../styles';
interface IMessageBoxMentionEmoji {
item: IEmoji;
@ -22,8 +21,4 @@ const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
};
MentionEmoji.propTypes = {
item: PropTypes.object
};
export default MentionEmoji;

View File

@ -1,16 +1,23 @@
import React, { useContext } from 'react';
import { View, Text, ActivityIndicator, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
import { MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
import styles from '../styles';
import sharedStyles from '../../../views/Styles';
import I18n from '../../../i18n';
import { themes } from '../../../constants/colors';
import I18n from '../../../i18n';
import { CustomIcon } from '../../../lib/Icons';
import { useTheme } from '../../../theme';
import sharedStyles from '../../../views/Styles';
import { MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
import MessageboxContext from '../Context';
import styles from '../styles';
const MentionHeaderList = ({ trackingType, hasMentions, theme, loading }) => {
interface IMentionHeaderList {
trackingType: string;
hasMentions: boolean;
loading: boolean;
}
const MentionHeaderList = ({ trackingType, hasMentions, loading }: IMentionHeaderList) => {
const { theme } = useTheme();
const context = useContext(MessageboxContext);
const { onPressNoMatchCanned } = context;
@ -39,11 +46,4 @@ const MentionHeaderList = ({ trackingType, hasMentions, theme, loading }) => {
return null;
};
MentionHeaderList.propTypes = {
trackingType: PropTypes.string,
hasMentions: PropTypes.bool,
theme: PropTypes.string,
loading: PropTypes.bool
};
export default MentionHeaderList;

View File

@ -1,14 +1,15 @@
import React, { useContext } from 'react';
import { Text, TouchableOpacity } from 'react-native';
import styles from '../styles';
import Avatar from '../../Avatar';
import MessageboxContext from '../Context';
import FixedMentionItem from './FixedMentionItem';
import MentionEmoji from './MentionEmoji';
import { MENTIONS_TRACKING_TYPE_EMOJIS, MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
import { themes } from '../../../constants/colors';
import { IEmoji } from '../../../definitions/IEmoji';
import { useTheme } from '../../../theme';
import Avatar from '../../Avatar';
import { MENTIONS_TRACKING_TYPE_CANNED, MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_EMOJIS } from '../constants';
import MessageboxContext from '../Context';
import styles from '../styles';
import FixedMentionItem from './FixedMentionItem';
import MentionEmoji from './MentionEmoji';
interface IMessageBoxMentionItem {
item: {
@ -21,11 +22,48 @@ interface IMessageBoxMentionItem {
text: string;
} & IEmoji;
trackingType: string;
theme: string;
}
const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
const MentionItemContent = React.memo(({ trackingType, item }: IMessageBoxMentionItem) => {
const { theme } = useTheme();
switch (trackingType) {
case MENTIONS_TRACKING_TYPE_EMOJIS:
return (
<>
<MentionEmoji item={item} />
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>:{item.name || item}:</Text>
</>
);
case MENTIONS_TRACKING_TYPE_COMMANDS:
return (
<>
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
</>
);
case MENTIONS_TRACKING_TYPE_CANNED:
return (
<>
<Text style={[styles.cannedItem, { color: themes[theme].titleText }]}>!{item.shortcut}</Text>
<Text numberOfLines={1} style={[styles.cannedMentionText, { color: themes[theme].auxiliaryTintColor }]}>
{item.text}
</Text>
</>
);
default:
return (
<>
<Avatar style={styles.avatar} text={item.username || item.name} size={30} type={item.t} />
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.username || item.name || item}</Text>
</>
);
}
});
const MentionItem = ({ item, trackingType }: IMessageBoxMentionItem) => {
const context = useContext(MessageboxContext);
const { theme } = useTheme();
const { onPressMention } = context;
const defineTestID = (type: string) => {
@ -44,43 +82,7 @@ const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
const testID = defineTestID(trackingType);
if (item.username === 'all' || item.username === 'here') {
return <FixedMentionItem item={item} onPress={onPressMention} theme={theme} />;
}
let content = (
<>
<Avatar style={styles.avatar} text={item.username || item.name} size={30} type={item.t} />
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.username || item.name || item}</Text>
</>
);
if (trackingType === MENTIONS_TRACKING_TYPE_EMOJIS) {
content = (
<>
<MentionEmoji item={item} />
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>:{item.name || item}:</Text>
</>
);
}
if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) {
content = (
<>
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
</>
);
}
if (trackingType === MENTIONS_TRACKING_TYPE_CANNED) {
content = (
<>
<Text style={[styles.cannedItem, { color: themes[theme].titleText }]}>!{item.shortcut}</Text>
<Text numberOfLines={1} style={[styles.cannedMentionText, { color: themes[theme].auxiliaryTintColor }]}>
{item.text}
</Text>
</>
);
return <FixedMentionItem item={item} onPress={onPressMention} />;
}
return (
@ -94,7 +96,7 @@ const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
]}
onPress={() => onPressMention(item)}
testID={testID}>
{content}
<MentionItemContent item={item} trackingType={trackingType} />
</TouchableOpacity>
);
};

View File

@ -6,29 +6,30 @@ import MentionHeaderList from './MentionHeaderList';
import styles from '../styles';
import MentionItem from './MentionItem';
import { themes } from '../../../constants/colors';
import { useTheme } from '../../../theme';
interface IMessageBoxMentions {
mentions: any[];
trackingType: string;
theme: string;
loading: boolean;
}
const Mentions = React.memo(
({ mentions, trackingType, theme, loading }: IMessageBoxMentions) => {
({ mentions, trackingType, loading }: IMessageBoxMentions) => {
if (!trackingType) {
return null;
}
const { theme } = useTheme();
return (
<View testID='messagebox-container'>
<FlatList
style={[styles.mentionList, { backgroundColor: themes[theme].auxiliaryBackground }]}
ListHeaderComponent={() => (
<MentionHeaderList trackingType={trackingType} hasMentions={mentions.length > 0} theme={theme} loading={loading} />
<MentionHeaderList trackingType={trackingType} hasMentions={mentions.length > 0} loading={loading} />
)}
data={mentions}
extraData={mentions}
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} theme={theme} />}
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} />}
keyExtractor={item => item.rid || item.name || item.command || item.shortcut || item}
keyboardShouldPersistTaps='always'
/>
@ -39,9 +40,6 @@ const Mentions = React.memo(
if (prevProps.loading !== nextProps.loading) {
return false;
}
if (prevProps.theme !== nextProps.theme) {
return false;
}
if (prevProps.trackingType !== nextProps.trackingType) {
return false;
}

View File

@ -47,23 +47,17 @@ const RECORDING_MODE = {
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX
};
const formatTime = function (seconds: any) {
let minutes: any = Math.floor(seconds / 60);
seconds %= 60;
if (minutes < 10) {
minutes = `0${minutes}`;
}
if (seconds < 10) {
seconds = `0${seconds}`;
}
return `${minutes}:${seconds}`;
const formatTime = function (time: number) {
const minutes = Math.floor(time / 60);
const seconds = time % 60;
const min = minutes < 10 ? `0${minutes}` : minutes;
const sec = seconds < 10 ? `0${seconds}` : seconds;
return `${min}:${sec}`;
};
export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAudioProps, any> {
private isRecorderBusy: boolean;
private recording: any;
private recording!: Audio.Recording;
private LastDuration: number;
constructor(props: IMessageBoxRecordAudioProps) {
@ -112,7 +106,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
return false;
};
onRecordingStatusUpdate = (status: any) => {
onRecordingStatusUpdate = (status: Audio.RecordingStatus) => {
this.setState({
isRecording: status.isRecording,
recordingDurationMillis: status.durationMillis
@ -157,7 +151,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
await this.recording.stopAndUnloadAsync();
const fileURI = this.recording.getURI();
const fileData = await getInfoAsync(fileURI);
const fileData = await getInfoAsync(fileURI as string);
const fileInfo = {
name: `${Date.now()}.m4a`,
mime: 'audio/aac',

View File

@ -8,6 +8,8 @@ import { CustomIcon } from '../../lib/Icons';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { IMessage } from '../../definitions/IMessage';
import { useTheme } from '../../theme';
import { IApplicationState } from '../../definitions';
const styles = StyleSheet.create({
container: {
@ -49,16 +51,15 @@ interface IMessageBoxReplyPreview {
baseUrl: string;
username: string;
getCustomEmoji: Function;
theme: string;
useRealName: boolean;
}
const ReplyPreview = React.memo(
({ message, Message_TimeFormat, replying, close, theme, useRealName }: IMessageBoxReplyPreview) => {
({ message, Message_TimeFormat, replying, close, useRealName }: IMessageBoxReplyPreview) => {
if (!replying) {
return null;
}
const { theme } = useTheme();
const time = moment(message.ts).format(Message_TimeFormat);
return (
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>
@ -75,16 +76,14 @@ const ReplyPreview = React.memo(
</View>
);
},
(prevProps: any, nextProps: any) =>
prevProps.replying === nextProps.replying &&
prevProps.theme === nextProps.theme &&
prevProps.message.id === nextProps.message.id
(prevProps: IMessageBoxReplyPreview, nextProps: IMessageBoxReplyPreview) =>
prevProps.replying === nextProps.replying && prevProps.message.id === nextProps.message.id
);
const mapStateToProps = (state: any) => ({
Message_TimeFormat: state.settings.Message_TimeFormat,
const mapStateToProps = (state: IApplicationState) => ({
Message_TimeFormat: state.settings.Message_TimeFormat as string,
baseUrl: state.server.server,
useRealName: state.settings.UI_Use_Real_Name
useRealName: state.settings.UI_Use_Real_Name as boolean
});
export default connect(mapStateToProps)(ReplyPreview);

View File

@ -5,24 +5,20 @@ import { ActionsButton, SendButton } from './buttons';
import styles from './styles';
interface IMessageBoxRightButtons {
theme: string;
showSend: boolean;
submit(): void;
showMessageBoxActions(): void;
isActionsEnabled: boolean;
}
const RightButtons = React.memo(
({ theme, showSend, submit, showMessageBoxActions, isActionsEnabled }: IMessageBoxRightButtons) => {
if (showSend) {
return <SendButton onPress={submit} theme={theme} />;
}
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
}
return <View style={styles.buttonsWhitespace} />;
const RightButtons = React.memo(({ showSend, submit, showMessageBoxActions, isActionsEnabled }: IMessageBoxRightButtons) => {
if (showSend) {
return <SendButton onPress={submit} />;
}
);
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} />;
}
return <View style={styles.buttonsWhitespace} />;
});
export default RightButtons;

View File

@ -3,16 +3,15 @@ import React from 'react';
import { SendButton } from './buttons';
interface IMessageBoxRightButtons {
theme: string;
showSend: boolean;
submit(): void;
}
const RightButtons = React.memo(({ theme, showSend, submit }: IMessageBoxRightButtons) => {
const RightButtons = ({ showSend, submit }: IMessageBoxRightButtons) => {
if (showSend) {
return <SendButton theme={theme} onPress={submit} />;
return <SendButton onPress={submit} />;
}
return null;
});
};
export default RightButtons;

View File

@ -3,12 +3,11 @@ import React from 'react';
import BaseButton from './BaseButton';
interface IActionsButton {
theme: string;
onPress(): void;
}
const ActionsButton = React.memo(({ theme, onPress }: IActionsButton) => (
<BaseButton onPress={onPress} testID='messagebox-actions' accessibilityLabel='Message_actions' icon='add' theme={theme} />
));
const ActionsButton = ({ onPress }: IActionsButton) => (
<BaseButton onPress={onPress} testID='messagebox-actions' accessibilityLabel='Message_actions' icon='add' />
);
export default ActionsButton;

View File

@ -1,13 +1,13 @@
import React from 'react';
import { BorderlessButton } from 'react-native-gesture-handler';
import React from 'react';
import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons';
import styles from '../styles';
import I18n from '../../../i18n';
import i18n from '../../../i18n';
import { CustomIcon } from '../../../lib/Icons';
import { useTheme } from '../../../theme';
import { themes } from '../../../constants/colors';
interface IBaseButton {
theme: string;
onPress(): void;
testID: string;
accessibilityLabel: string;
@ -15,16 +15,18 @@ interface IBaseButton {
color: string;
}
const BaseButton = React.memo(({ onPress, testID, accessibilityLabel, icon, theme, color }: Partial<IBaseButton>) => (
<BorderlessButton
onPress={onPress}
style={styles.actionButton}
testID={testID}
// @ts-ignore
accessibilityLabel={I18n.t(accessibilityLabel)}
accessibilityTraits='button'>
<CustomIcon name={icon} size={24} color={color ?? themes[theme!].auxiliaryTintColor} />
</BorderlessButton>
));
const BaseButton = ({ accessibilityLabel, icon, color, ...props }: Partial<IBaseButton>) => {
const { theme } = useTheme();
return (
<BorderlessButton
{...props}
style={styles.actionButton}
// @ts-ignore
accessibilityLabel={i18n.t(accessibilityLabel)}
accessibilityTraits='button'>
<CustomIcon name={icon} size={24} color={color || themes[theme].auxiliaryTintColor} />
</BorderlessButton>
);
};
export default BaseButton;

View File

@ -3,18 +3,11 @@ import React from 'react';
import BaseButton from './BaseButton';
interface ICancelEditingButton {
theme: string;
onPress(): void;
}
const CancelEditingButton = React.memo(({ theme, onPress }: ICancelEditingButton) => (
<BaseButton
onPress={onPress}
testID='messagebox-cancel-editing'
accessibilityLabel='Cancel_editing'
icon='close'
theme={theme}
/>
));
const CancelEditingButton = ({ onPress }: ICancelEditingButton) => (
<BaseButton onPress={onPress} testID='messagebox-cancel-editing' accessibilityLabel='Cancel_editing' icon='close' />
);
export default CancelEditingButton;

View File

@ -1,22 +1,24 @@
import React from 'react';
import BaseButton from './BaseButton';
import { themes } from '../../../constants/colors';
import { useTheme } from '../../../theme';
import BaseButton from './BaseButton';
interface ISendButton {
theme: string;
onPress(): void;
}
const SendButton = React.memo(({ theme, onPress }: ISendButton) => (
<BaseButton
onPress={onPress}
testID='messagebox-send-message'
accessibilityLabel='Send_message'
icon='send-filled'
theme={theme}
color={themes[theme].tintColor}
/>
));
const SendButton = ({ onPress }: ISendButton) => {
const { theme } = useTheme();
return (
<BaseButton
onPress={onPress}
testID='messagebox-send-message'
accessibilityLabel='Send_message'
icon='send-filled'
color={themes[theme].tintColor}
/>
);
};
export default SendButton;

View File

@ -3,33 +3,18 @@ import React from 'react';
import BaseButton from './BaseButton';
interface IToggleEmojiButton {
theme: string;
show: boolean;
open(): void;
close(): void;
}
const ToggleEmojiButton = React.memo(({ theme, show, open, close }: IToggleEmojiButton) => {
const ToggleEmojiButton = ({ show, open, close }: IToggleEmojiButton) => {
if (show) {
return (
<BaseButton
onPress={close}
testID='messagebox-close-emoji'
accessibilityLabel='Close_emoji_selector'
icon='keyboard'
theme={theme}
/>
<BaseButton onPress={close} testID='messagebox-close-emoji' accessibilityLabel='Close_emoji_selector' icon='keyboard' />
);
}
return (
<BaseButton
onPress={open}
testID='messagebox-open-emoji'
accessibilityLabel='Open_emoji_selector'
icon='emoji'
theme={theme}
/>
);
});
return <BaseButton onPress={open} testID='messagebox-open-emoji' accessibilityLabel='Open_emoji_selector' icon='emoji' />;
};
export default ToggleEmojiButton;

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Alert, Keyboard, NativeModules, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker, { Image, ImageOrVideo } from 'react-native-image-crop-picker';
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
import { dequal } from 'dequal';
import DocumentPicker from 'react-native-document-picker';
import { Q } from '@nozbe/watermelondb';
@ -50,7 +50,8 @@ import { sanitizeLikeString } from '../../lib/database/utils';
import { CustomIcon } from '../../lib/Icons';
import { IMessage } from '../../definitions/IMessage';
import { forceJpgExtension } from './forceJpgExtension';
import { IPreviewItem, IUser } from '../../definitions';
import { IBaseScreen, IPreviewItem, IUser, TSubscriptionModel, TThreadModel } from '../../definitions';
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
if (isAndroid) {
require('./EmojiKeyboard');
@ -63,18 +64,18 @@ const imagePickerConfig = {
forceJpg: true
};
const libraryPickerConfig = {
const libraryPickerConfig: Options = {
multiple: true,
compressVideoPreset: 'Passthrough',
mediaType: 'any',
forceJpg: true
};
const videoPickerConfig = {
const videoPickerConfig: Options = {
mediaType: 'video'
};
export interface IMessageBoxProps {
export interface IMessageBoxProps extends IBaseScreen<MasterDetailInsideStackParamList, any> {
rid: string;
baseUrl: string;
message: IMessage;
@ -97,7 +98,6 @@ export interface IMessageBoxProps {
theme: string;
replyCancel(): void;
showSend: boolean;
navigation: any;
children: JSX.Element;
isMasterDetail: boolean;
showActionSheet: Function;
@ -118,7 +118,7 @@ interface IMessageBoxState {
commandPreview: IPreviewItem[];
showCommandPreview: boolean;
command: {
appId?: any;
appId?: string;
};
tshow: boolean;
mentionLoading: boolean;
@ -132,17 +132,15 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
private focused: boolean;
private options: any;
private imagePickerConfig: Options;
private imagePickerConfig: any;
private libraryPickerConfig: Options;
private libraryPickerConfig: any;
private videoPickerConfig: Options;
private videoPickerConfig: any;
private room!: TSubscriptionModel;
private room: any;
private thread: any;
private thread!: TThreadModel;
private unsubscribeFocus: any;
@ -713,7 +711,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
chooseFromLibrary = async () => {
logEvent(events.ROOM_BOX_ACTION_LIBRARY);
try {
let attachments = (await ImagePicker.openPicker(this.libraryPickerConfig)) as ImageOrVideo[];
// The type can be video or photo, however the lib understands that it is just one of them.
let attachments = (await ImagePicker.openPicker(this.libraryPickerConfig)) as unknown as ImageOrVideo[];
attachments = attachments.map(att => forceJpgExtension(att));
this.openShareView(attachments);
} catch (e) {
@ -757,12 +756,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
openShareView = (attachments: any) => {
const { message, replyCancel, replyWithMention } = this.props;
// Start a thread with an attachment
let { thread } = this;
let value: TThreadModel | IMessage = this.thread;
if (replyWithMention) {
thread = message;
value = message;
replyCancel();
}
Navigation.navigate('ShareView', { room: this.room, thread, attachments });
Navigation.navigate('ShareView', { room: this.room, value, attachments });
};
createDiscussion = () => {
@ -1058,7 +1057,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const commandsPreviewAndMentions = !recording ? (
<>
<CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} />
<Mentions mentions={mentions} trackingType={trackingType} theme={theme} loading={mentionLoading} />
<Mentions mentions={mentions} trackingType={trackingType} loading={mentionLoading} />
</>
) : null;
@ -1069,7 +1068,6 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
username={user.username}
replying={replying}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
) : null;

View File

@ -8,7 +8,7 @@ import I18n from '../i18n';
import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import { useTheme, withTheme } from '../theme';
import { TGetCustomEmoji } from '../definitions/IEmoji';
import { TMessageModel, ILoggedUser } from '../definitions';
import SafeAreaView from './SafeAreaView';
@ -61,38 +61,37 @@ const styles = StyleSheet.create({
const standardEmojiStyle = { fontSize: 20 };
const customEmojiStyle = { width: 20, height: 20 };
interface IItem {
interface ISharedFields {
user?: Pick<ILoggedUser, 'username'>;
baseUrl: string;
getCustomEmoji: TGetCustomEmoji;
}
interface IItem extends ISharedFields {
item: {
usernames: any;
usernames: string[];
emoji: string;
};
user?: Pick<ILoggedUser, 'username'>;
baseUrl?: string;
getCustomEmoji?: TGetCustomEmoji;
theme?: string;
}
interface IModalContent {
interface IModalContent extends ISharedFields {
message?: TMessageModel;
onClose: Function;
onClose: () => void;
theme: string;
}
interface IReactionsModal {
message?: any;
user?: Pick<ILoggedUser, 'username'>;
interface IReactionsModal extends ISharedFields {
message?: TMessageModel;
isVisible: boolean;
onClose(): void;
baseUrl: string;
getCustomEmoji?: TGetCustomEmoji;
theme: string;
}
const Item = React.memo(({ item, user, baseUrl, getCustomEmoji, theme }: IItem) => {
const Item = React.memo(({ item, user, baseUrl, getCustomEmoji }: IItem) => {
const { theme } = useTheme();
const count = item.usernames.length;
let usernames = item.usernames
.slice(0, 3)
.map((username: any) => (username === user?.username ? I18n.t('you') : username))
.map((username: string) => (username === user?.username ? I18n.t('you') : username))
.join(', ');
if (count > 3) {
usernames = `${usernames} ${I18n.t('and_more')} ${count - 3}`;
@ -106,15 +105,15 @@ const Item = React.memo(({ item, user, baseUrl, getCustomEmoji, theme }: IItem)
content={item.emoji}
standardEmojiStyle={standardEmojiStyle}
customEmojiStyle={customEmojiStyle}
baseUrl={baseUrl!}
getCustomEmoji={getCustomEmoji!}
baseUrl={baseUrl}
getCustomEmoji={getCustomEmoji}
/>
</View>
<View style={styles.peopleItemContainer}>
<Text style={[styles.reactCount, { color: themes[theme!].buttonText }]}>
<Text style={[styles.reactCount, { color: themes[theme].buttonText }]}>
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
</Text>
<Text style={[styles.peopleReacted, { color: themes[theme!].buttonText }]}>{usernames}</Text>
<Text style={[styles.peopleReacted, { color: themes[theme].buttonText }]}>{usernames}</Text>
</View>
</View>
);
@ -143,18 +142,21 @@ const ModalContent = React.memo(({ message, onClose, ...props }: IModalContent)
});
const ReactionsModal = React.memo(
({ isVisible, onClose, theme, ...props }: IReactionsModal) => (
<Modal
isVisible={isVisible}
onBackdropPress={onClose}
onBackButtonPress={onClose}
backdropOpacity={0.8}
onSwipeComplete={onClose}
swipeDirection={['up', 'left', 'right', 'down']}>
<ModalContent onClose={onClose} theme={theme} {...props} />
</Modal>
),
(prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible && prevProps.theme === nextProps.theme
({ isVisible, onClose, ...props }: IReactionsModal) => {
const { theme } = useTheme();
return (
<Modal
isVisible={isVisible}
onBackdropPress={onClose}
onBackButtonPress={onClose}
backdropOpacity={0.8}
onSwipeComplete={onClose}
swipeDirection={['up', 'left', 'right', 'down']}>
<ModalContent onClose={onClose} theme={theme} {...props} />
</Modal>
);
},
(prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible
);
ReactionsModal.displayName = 'ReactionsModal';

View File

@ -6,8 +6,8 @@ import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { MarkdownPreview } from '../markdown';
import RoomTypeIcon from '../RoomTypeIcon';
import { withTheme } from '../../theme';
import { TUserStatus } from '../../definitions';
import { useTheme } from '../../theme';
const HIT_SLOP = {
top: 5,
@ -44,9 +44,8 @@ const styles = StyleSheet.create({
type TRoomHeaderSubTitle = {
usersTyping: [];
theme: string;
subtitle: string;
renderFunc: any;
renderFunc?: () => React.ReactElement;
scale: number;
};
@ -55,7 +54,6 @@ type TRoomHeaderHeaderTitle = {
tmid: string;
prid: string;
scale: number;
theme: string;
testID: string;
};
@ -77,7 +75,8 @@ interface IRoomHeader {
testID: string;
}
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, theme, scale }: TRoomHeaderSubTitle) => {
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeaderSubTitle) => {
const { theme } = useTheme();
const fontSize = getSubTitleSize(scale);
// typing
if (usersTyping.length) {
@ -108,7 +107,8 @@ const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, theme, scale }
return null;
});
const HeaderTitle = React.memo(({ title, tmid, prid, scale, theme, testID }: TRoomHeaderHeaderTitle) => {
const HeaderTitle = React.memo(({ title, tmid, prid, scale, testID }: TRoomHeaderHeaderTitle) => {
const { theme } = useTheme();
const titleStyle = { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor };
if (!tmid && !prid) {
return (
@ -133,12 +133,12 @@ const Header = React.memo(
prid,
tmid,
onPress,
theme,
isGroupChat,
teamMain,
testID,
usersTyping = []
}: IRoomHeader) => {
const { theme } = useTheme();
const portrait = height > width;
let scale = 1;
@ -153,7 +153,7 @@ const Header = React.memo(
renderFunc = () => (
<View style={styles.titleContainer}>
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
<Text style={[styles.subtitle, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
<Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
{parentTitle}
</Text>
</View>
@ -168,25 +168,18 @@ const Header = React.memo(
accessibilityLabel={title}
onPress={handleOnPress}
style={styles.container}
// @ts-ignore
disabled={tmid}
disabled={!!tmid}
hitSlop={HIT_SLOP}>
<View style={styles.titleContainer}>
{tmid ? null : (
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
)}
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} theme={theme!} testID={testID} />
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} testID={testID} />
</View>
<SubTitle
usersTyping={tmid ? [] : usersTyping}
subtitle={subtitle}
theme={theme!}
renderFunc={renderFunc}
scale={scale}
/>
<SubTitle usersTyping={tmid ? [] : usersTyping} subtitle={subtitle} renderFunc={renderFunc} scale={scale} />
</TouchableOpacity>
);
}
);
export default withTheme(Header);
export default Header;

View File

@ -1,9 +1,9 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { StyleSheet, ViewProps } from 'react-native';
import { SafeAreaView as SafeAreaContext } from 'react-native-safe-area-context';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import { useTheme } from '../theme';
const styles = StyleSheet.create({
view: {
@ -11,22 +11,24 @@ const styles = StyleSheet.create({
}
});
interface ISafeAreaView {
testID?: string;
theme?: string;
type SupportedChildren = React.ReactElement | React.ReactElement[] | null;
type TSafeAreaViewChildren = SupportedChildren | SupportedChildren[];
interface ISafeAreaView extends ViewProps {
vertical?: boolean;
style?: object;
children: React.ReactNode;
children: TSafeAreaViewChildren;
}
const SafeAreaView = React.memo(({ style, children, testID, theme, vertical = true, ...props }: ISafeAreaView) => (
<SafeAreaContext
style={[styles.view, { backgroundColor: themes[theme!].auxiliaryBackground }, style]}
edges={vertical ? ['right', 'left'] : undefined}
testID={testID}
{...props}>
{children}
</SafeAreaContext>
));
const SafeAreaView = React.memo(({ style, children, vertical = true, ...props }: ISafeAreaView) => {
const { theme } = useTheme();
return (
<SafeAreaContext
style={[styles.view, { backgroundColor: themes[theme].auxiliaryBackground }, style]}
edges={vertical ? ['right', 'left'] : undefined}
{...props}>
{children}
</SafeAreaContext>
);
});
export default withTheme(SafeAreaView);
export default SafeAreaView;

View File

@ -47,11 +47,11 @@ const styles = StyleSheet.create({
interface ISearchBox extends TextInputProps {
value?: string;
hasCancel?: boolean;
onCancelPress?: Function;
onCancelPress?: () => void;
inputRef?: React.Ref<RNTextInput>;
}
const CancelButton = ({ onCancelPress }: { onCancelPress?: Function }) => {
const CancelButton = ({ onCancelPress }: { onCancelPress?: () => void }) => {
const { theme } = useTheme();
return (
<Touchable onPress={onCancelPress} style={styles.cancel}>
@ -84,7 +84,7 @@ const SearchBox = ({ hasCancel, onCancelPress, inputRef, ...props }: ISearchBox)
{...props}
/>
</View>
{hasCancel ? <CancelButton onCancelPress={onCancelPress} /> : null}
{hasCancel && onCancelPress ? <CancelButton onCancelPress={onCancelPress} /> : null}
</View>
);
};

View File

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

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
import DateTimePicker, { Event } from '@react-native-community/datetimepicker';
import Touchable from 'react-native-platform-touchable';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import moment from 'moment';
@ -11,6 +11,7 @@ import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles';
import { CustomIcon } from '../../lib/Icons';
import { isAndroid } from '../../utils/deviceInfo';
import { useTheme } from '../../theme';
import ActivityIndicator from '../ActivityIndicator';
import { IDatePicker } from './interfaces';
@ -36,14 +37,17 @@ const styles = StyleSheet.create({
}
});
export const DatePicker = ({ element, language, action, context, theme, loading, value, error }: IDatePicker) => {
export const DatePicker = ({ element, language, action, context, loading, value, error }: IDatePicker) => {
const { theme } = useTheme();
const [show, onShow] = useState(false);
const initial_date = element?.initial_date;
const placeholder = element?.placeholder;
const [currentDate, onChangeDate] = useState(new Date(initial_date || value));
const onChange = ({ nativeEvent: { timestamp } }: any, date: any) => {
// timestamp as number exists in Event
// @ts-ignore
const onChange = ({ nativeEvent: { timestamp } }: Event, date?: Date) => {
const newDate = date || new Date(timestamp);
onChangeDate(newDate);
action({ value: moment(newDate).format('YYYY-MM-DD') });
@ -52,7 +56,9 @@ export const DatePicker = ({ element, language, action, context, theme, loading,
}
};
let button = <Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} theme={theme} />;
let button = placeholder ? (
<Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} theme={theme} />
) : null;
if (context === BLOCK_CONTEXT.FORM) {
button = (

View File

@ -6,7 +6,7 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import ImageContainer from '../message/Image';
import Navigation from '../../lib/Navigation';
import { IThumb, IImage, IElement } from './interfaces';
import { TThemeMode } from '../../definitions/ITheme';
import { IAttachment } from '../../definitions';
const styles = StyleSheet.create({
image: {
@ -27,23 +27,22 @@ export const Thumb = ({ element, size = 88 }: IThumb) => (
<FastImage style={[{ width: size, height: size }, styles.image]} source={{ uri: element?.imageUrl }} />
);
export const Media = ({ element, theme }: IImage) => {
const showAttachment = (attachment: any) => Navigation.navigate('AttachmentView', { attachment });
export const Media = ({ element }: IImage) => {
const showAttachment = (attachment: IAttachment) => Navigation.navigate('AttachmentView', { attachment });
const imageUrl = element?.imageUrl ?? '';
// @ts-ignore
// TODO: delete ts-ignore after refactor Markdown and ImageContainer
return <ImageContainer file={{ image_url: imageUrl }} imageUrl={imageUrl} showAttachment={showAttachment} theme={theme} />;
return <ImageContainer file={{ image_url: imageUrl }} imageUrl={imageUrl} showAttachment={showAttachment} />;
};
const genericImage = (theme: TThemeMode, element: IElement, context?: number) => {
const genericImage = (element: IElement, context?: number) => {
switch (context) {
case BLOCK_CONTEXT.SECTION:
return <Thumb element={element} />;
case BLOCK_CONTEXT.CONTEXT:
return <ThumbContext element={element} />;
default:
return <Media element={element} theme={theme} />;
return <Media element={element} />;
}
};
export const Image = ({ element, context, theme }: IImage) => genericImage(theme, element, context);
export const Image = ({ element, context }: IImage) => genericImage(element, context);

View File

@ -7,26 +7,23 @@ import { themes } from '../../../constants/colors';
import { textParser } from '../utils';
import { CustomIcon } from '../../../lib/Icons';
import styles from './styles';
import { IItemData } from '.';
interface IChip {
item: {
value: string;
imageUrl: string;
text: string;
};
onSelect: Function;
item: IItemData;
onSelect: (item: IItemData) => void;
style?: object;
theme: string;
}
interface IChips {
items: [];
onSelect: Function;
items: IItemData[];
onSelect: (item: IItemData) => void;
style?: object;
theme: string;
}
const keyExtractor = (item: any) => item.value.toString();
const keyExtractor = (item: IItemData) => item.value.toString();
const Chip = ({ item, onSelect, style, theme }: IChip) => (
<Touchable

View File

@ -9,10 +9,10 @@ import styles from './styles';
interface IInput {
children?: JSX.Element;
onPress: Function;
onPress: () => void;
theme: string;
inputStyle?: object;
disabled?: boolean | object;
disabled?: boolean | null;
placeholder?: string;
loading?: boolean;
innerInputStyle?: object;

View File

@ -8,34 +8,31 @@ import * as List from '../../List';
import { textParser } from '../utils';
import { themes } from '../../../constants/colors';
import styles from './styles';
import { IItemData } from '.';
interface IItem {
item: {
value: { name: string };
text: { text: string };
imageUrl: string;
};
selected: any;
item: IItemData;
selected?: string;
onSelect: Function;
theme: string;
}
interface IItems {
items: [];
selected: [];
items: IItemData[];
selected: string[];
onSelect: Function;
theme: string;
}
const keyExtractor = (item: any) => item.value.toString();
const keyExtractor = (item: IItemData) => item.value.toString();
// RectButton doesn't work on modal (Android)
const Item = ({ item, selected, onSelect, theme }: IItem) => {
const itemName = item.value.name || item.text.text.toLowerCase();
const itemName = item.value || item.text.text.toLowerCase();
return (
<Touchable
testID={`multi-select-item-${itemName}`}
key={item}
key={itemName}
onPress={() => onSelect(item)}
style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}>
<>

View File

@ -1,5 +1,15 @@
import React, { useEffect, useState } from 'react';
import { Animated, Easing, KeyboardAvoidingView, Modal, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
import {
Animated,
Easing,
KeyboardAvoidingView,
Modal,
StyleSheet,
Text,
TouchableWithoutFeedback,
View,
TextStyle
} from 'react-native';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import Button from '../../Button';
@ -8,26 +18,31 @@ import { textParser } from '../utils';
import { themes } from '../../../constants/colors';
import I18n from '../../../i18n';
import { isIOS } from '../../../utils/deviceInfo';
import { useTheme } from '../../../theme';
import { BlockContext, IText } from '../interfaces';
import Chips from './Chips';
import Items from './Items';
import Input from './Input';
import styles from './styles';
export interface IItemData {
value: string;
text: { text: string };
imageUrl?: string;
}
interface IMultiSelect {
options: any[];
options?: IItemData[];
onChange: Function;
placeholder: {
text: string;
};
context?: number;
placeholder?: IText;
context?: BlockContext;
loading?: boolean;
multiselect?: boolean;
onSearch?: () => void;
onClose?: () => void;
inputStyle?: object;
inputStyle?: TextStyle;
value?: any[];
disabled?: boolean | object;
theme: string;
disabled?: boolean | null;
innerInputStyle?: object;
}
@ -54,9 +69,9 @@ export const MultiSelect = React.memo(
onClose = () => {},
disabled,
inputStyle,
theme,
innerInputStyle
}: IMultiSelect) => {
const { theme } = useTheme();
const [selected, select] = useState<any>(Array.isArray(values) ? values : []);
const [open, setOpen] = useState(false);
const [search, onSearchChange] = useState('');
@ -95,7 +110,7 @@ export const MultiSelect = React.memo(
}).start(() => setShowContent(false));
};
const onSelect = (item: any) => {
const onSelect = (item: IItemData) => {
const {
value,
text: { text }

View File

@ -6,6 +6,7 @@ import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../../lib/Icons';
import ActivityIndicator from '../ActivityIndicator';
import { themes } from '../../constants/colors';
import { useTheme } from '../../theme';
import { BUTTON_HIT_SLOP } from '../message/utils';
import * as List from '../List';
import { IOption, IOptions, IOverflow } from './interfaces';
@ -43,9 +44,10 @@ const Options = ({ options, onOptionPress, parser, theme }: IOptions) => (
/>
);
const touchable: { [key: string]: any } = {};
const touchable: { [key: string]: Touchable | null } = {};
export const Overflow = ({ element, loading, action, parser, theme }: IOverflow) => {
export const Overflow = ({ element, loading, action, parser }: IOverflow) => {
const { theme } = useTheme();
const options = element?.options || [];
const blockId = element?.blockId || '';
const [show, onShow] = useState(false);
@ -58,7 +60,7 @@ export const Overflow = ({ element, loading, action, parser, theme }: IOverflow)
return (
<>
<Touchable
ref={(ref: any) => (touchable[blockId] = ref)}
ref={ref => (touchable[blockId] = ref)}
background={Touchable.Ripple(themes[theme].bannerBackground)}
onPress={() => onShow(!show)}
hitSlop={BUTTON_HIT_SLOP}
@ -71,6 +73,7 @@ export const Overflow = ({ element, loading, action, parser, theme }: IOverflow)
</Touchable>
<Popover
isVisible={show}
// fromView exists in Popover Component
/* @ts-ignore*/
fromView={touchable[blockId]}
onRequestClose={() => onShow(false)}>

View File

@ -4,6 +4,7 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import { themes } from '../../constants/colors';
import { IAccessoryComponent, IFields, ISection } from './interfaces';
import { useTheme } from '../../theme';
const styles = StyleSheet.create({
content: {
@ -37,10 +38,14 @@ const Fields = ({ fields, parser, theme }: IFields) => (
const accessoriesRight = ['image', 'overflow'];
export const Section = ({ blockId, appId, text, fields, accessory, parser, theme }: ISection) => (
<View style={[styles.content, accessory && accessoriesRight.includes(accessory.type) ? styles.row : styles.column]}>
{text ? <View style={styles.text}>{parser.text(text)}</View> : null}
{fields ? <Fields fields={fields} theme={theme} parser={parser} /> : null}
{accessory ? <Accessory element={{ blockId, appId, ...accessory }} parser={parser} /> : null}
</View>
);
export const Section = ({ blockId, appId, text, fields, accessory, parser }: ISection) => {
const { theme } = useTheme();
return (
<View style={[styles.content, accessory && accessoriesRight.includes(accessory.type) ? styles.row : styles.column]}>
{text ? <View style={styles.text}>{parser.text(text)}</View> : null}
{fields ? <Fields fields={fields} theme={theme} parser={parser} /> : null}
{accessory ? <Accessory element={{ blockId, appId, ...accessory }} parser={parser} /> : null}
</View>
);
};

View File

@ -8,6 +8,8 @@ import { CustomIcon } from '../../lib/Icons';
import { textParser } from './utils';
import { isAndroid, isIOS } from '../../utils/deviceInfo';
import ActivityIndicator from '../ActivityIndicator';
import { useTheme } from '../../theme';
import { IText, Option } from './interfaces';
const styles = StyleSheet.create({
iosPadding: {
@ -34,19 +36,16 @@ const styles = StyleSheet.create({
});
interface ISelect {
options: {
text: string;
value: string;
}[];
placeholder: string;
options?: Option[];
placeholder?: IText;
onChange: Function;
loading: boolean;
disabled: boolean;
disabled?: boolean;
value: [];
theme: string;
}
export const Select = ({ options = [], placeholder, onChange, loading, disabled, value: initialValue, theme }: ISelect) => {
export const Select = ({ options = [], placeholder, onChange, loading, disabled, value: initialValue }: ISelect) => {
const { theme } = useTheme();
const [selected, setSelected] = useState(!Array.isArray(initialValue) && initialValue);
const items = options.map(option => ({ label: textParser([option.text]), value: option.value }));
const pickerStyle = {
@ -80,6 +79,7 @@ export const Select = ({ options = [], placeholder, onChange, loading, disabled,
}}
Icon={Icon}
textInputProps={{
// style property was Omitted in lib, but can be used normally
// @ts-ignore
style: { ...styles.pickerText, color: selected ? themes[theme].titleText : themes[theme].auxiliaryText }
}}

View File

@ -20,7 +20,7 @@ import { Input } from './Input';
import { DatePicker } from './DatePicker';
import { Overflow } from './Overflow';
import { ThemeContext } from '../../theme';
import { BlockContext, IButton, IInputIndex, IParser, IText } from './interfaces';
import { BlockContext, IActions, IButton, IElement, IInputIndex, IParser, ISection, IText } from './interfaces';
const styles = StyleSheet.create({
input: {
@ -78,35 +78,28 @@ class MessageParser extends UiKitParserMessage {
}
divider() {
const { theme } = useContext(ThemeContext);
// @ts-ignore
return <Divider theme={theme} />;
return <Divider />;
}
section(args: any) {
const { theme } = useContext(ThemeContext);
return <Section {...args} theme={theme} parser={this} />;
section(args: ISection) {
return <Section {...args} parser={this.current} />;
}
actions(args: any) {
const { theme } = useContext(ThemeContext);
return <Actions {...args} theme={theme} parser={this} />;
actions(args: IActions) {
return <Actions {...args} parser={this.current} />;
}
overflow(element: any, context: any) {
const [{ loading }, action]: any = useBlockContext(element, context);
const { theme }: any = useContext(ThemeContext);
return <Overflow element={element} context={context} loading={loading} action={action} theme={theme} parser={this.current} />;
overflow(element: IElement, context: BlockContext) {
const [{ loading }, action] = useBlockContext(element, context);
return <Overflow element={element} context={context} loading={loading} action={action} parser={this.current} />;
}
datePicker(element: any, context: any) {
const [{ loading, value, error, language }, action]: any = useBlockContext(element, context);
const { theme }: any = useContext(ThemeContext);
datePicker(element: IElement, context: BlockContext) {
const [{ loading, value, error, language }, action] = useBlockContext(element, context);
return (
<DatePicker
element={element}
language={language}
theme={theme}
value={value}
action={action}
context={context}
@ -116,9 +109,8 @@ class MessageParser extends UiKitParserMessage {
);
}
image(element: any, context: any) {
const { theme }: any = useContext(ThemeContext);
return <Image element={element} theme={theme} context={context} />;
image(element: IElement, context: BlockContext) {
return <Image element={element} context={context} />;
}
context(args: any) {
@ -126,24 +118,19 @@ class MessageParser extends UiKitParserMessage {
return <Context {...args} theme={theme} parser={this} />;
}
multiStaticSelect(element: any, context: any) {
const [{ loading, value }, action]: any = useBlockContext(element, context);
const { theme } = useContext(ThemeContext);
return (
<MultiSelect {...element} theme={theme} value={value} onChange={action} context={context} loading={loading} multiselect />
);
multiStaticSelect(element: IElement, context: BlockContext) {
const [{ loading, value }, action] = useBlockContext(element, context);
return <MultiSelect {...element} value={value} onChange={action} context={context} loading={loading} multiselect />;
}
staticSelect(element: any, context: any) {
const [{ loading, value }, action]: any = useBlockContext(element, context);
const { theme } = useContext(ThemeContext);
return <Select {...element} theme={theme} value={value} onChange={action} loading={loading} />;
staticSelect(element: IElement, context: BlockContext) {
const [{ loading, value }, action] = useBlockContext(element, context);
return <Select {...element} value={value} onChange={action} loading={loading} />;
}
selectInput(element: any, context: any) {
const [{ loading, value }, action]: any = useBlockContext(element, context);
const { theme } = useContext(ThemeContext);
return <MultiSelect {...element} theme={theme} value={value} onChange={action} context={context} loading={loading} />;
selectInput(element: IElement, context: BlockContext) {
const [{ loading, value }, action] = useBlockContext(element, context);
return <MultiSelect {...element} value={value} onChange={action} context={context} loading={loading} />;
}
}
@ -160,8 +147,8 @@ class ModalParser extends UiKitParserModal {
}
input({ element, blockId, appId, label, description, hint }: IInputIndex, context: number) {
const [{ error }]: any = useBlockContext({ ...element, appId, blockId }, context);
const { theme }: any = useContext(ThemeContext);
const [{ error }] = useBlockContext({ ...element, appId, blockId }, context);
const { theme } = useContext(ThemeContext);
return (
<Input
parser={this.current}
@ -175,17 +162,15 @@ class ModalParser extends UiKitParserModal {
);
}
image(element: any, context: any) {
const { theme }: any = useContext(ThemeContext);
return <Image element={element} theme={theme} context={context} />;
image(element: IElement, context: BlockContext) {
return <Image element={element} context={context} />;
}
plainInput(element: any, context: any) {
const [{ loading, value, error }, action]: any = useBlockContext(element, context);
plainInput(element: IElement, context: BlockContext) {
const [{ loading, value, error }, action] = useBlockContext(element, context);
const { theme } = useContext(ThemeContext);
const { multiline, actionId, placeholder } = element;
return (
// @ts-ignore
<TextInput
key={actionId}
placeholder={plainText(placeholder)}

View File

@ -1,5 +1,3 @@
import { TThemeMode } from '../../definitions/ITheme';
export enum ElementTypes {
IMAGE = 'image',
BUTTON = 'button',
@ -87,10 +85,11 @@ export interface IElement {
imageUrl?: string;
appId?: string;
blockId?: string;
multiline?: boolean;
}
export interface IText {
type: ElementTypes;
type?: ElementTypes;
text: string;
emoji?: boolean;
}
@ -98,12 +97,15 @@ export interface IText {
export interface Option {
text: IText;
value: string;
imageUrl?: string;
}
export interface IButton {
type: ElementTypes;
text: IText;
actionId: string;
blockId: string;
appId: string;
value?: any;
style?: any;
}
@ -177,7 +179,6 @@ export interface IParser {
}
export interface IActions extends Block {
parser?: IParser;
theme: TThemeMode;
}
export interface IContext extends Block {
@ -191,7 +192,6 @@ export interface IDatePicker extends Partial<Block> {
loading: boolean;
value: string;
error: string;
theme: TThemeMode;
}
export interface IInput extends Partial<Block> {
@ -199,7 +199,7 @@ export interface IInput extends Partial<Block> {
description: string;
error: string;
hint: string;
theme: TThemeMode;
theme: string;
}
export interface IInputIndex {
@ -217,8 +217,7 @@ export interface IThumb {
}
export interface IImage {
element: IElement;
theme: TThemeMode;
context?: number;
context?: BlockContext;
}
// UiKit/Overflow
@ -226,14 +225,13 @@ export interface IOverflow extends Partial<Block> {
action: Function;
loading: boolean;
parser: IParser;
theme: TThemeMode;
context: number;
}
interface PropsOption {
onOptionPress: Function;
parser: IParser;
theme: TThemeMode;
theme: string;
}
export interface IOptions extends PropsOption {
options: Option[];
@ -262,12 +260,11 @@ export interface ISection {
text?: IText;
accessory?: IAccessory;
parser: IParser;
theme: TThemeMode;
fields?: any[];
}
export interface IFields {
parser: IParser;
theme: TThemeMode;
theme: string;
fields: any[];
}

View File

@ -2,9 +2,9 @@
import React, { useContext, useState } from 'react';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import { BlockContext } from './interfaces';
import { BlockContext, IText } from './interfaces';
export const textParser = ([{ text }]: any) => text;
export const textParser = ([{ text }]: IText[]) => text;
export const defaultContext: any = {
action: (...args: any) => console.log(args),
@ -27,7 +27,14 @@ type TFunctionReturn = (value: any) => Promise<void>;
type TReturn = [TObjectReturn, TFunctionReturn];
export const useBlockContext = ({ blockId, actionId, appId, initialValue }: any, context: BlockContext): TReturn => {
interface IUseBlockContext {
blockId?: string;
actionId: string;
appId?: string;
initialValue?: string;
}
export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUseBlockContext, context: BlockContext): TReturn => {
const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext);
const { value = initialValue } = values[actionId] || {};
const [loading, setLoading] = useState(false);

View File

@ -18,7 +18,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH
const handlePress = () => {
const index = channels?.findIndex(channel => channel.name === hashtag);
if (index && navToRoomInfo) {
if (typeof index !== 'undefined' && navToRoomInfo) {
const navParam = {
t: 'c',
rid: channels?.[index]._id

View File

@ -1,5 +1,6 @@
import React from 'react';
import { Clipboard, Text } from 'react-native';
import { Text } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import styles from './styles';
import { themes } from '../../constants/colors';

View File

@ -1,6 +1,6 @@
export interface IUserMention {
_id: string;
username: string;
username?: string;
name?: string;
type?: string;
}

View File

@ -1,6 +1,7 @@
import React, { useContext } from 'react';
import { Text, Clipboard } from 'react-native';
import { Text } from 'react-native';
import { Link as LinkProps } from '@rocket.chat/message-parser';
import Clipboard from '@react-native-clipboard/clipboard';
import styles from '../styles';
import I18n from '../../../i18n';

View File

@ -2,7 +2,7 @@ import React, { useContext } from 'react';
import { dequal } from 'dequal';
import { Text } from 'react-native';
import { IMessageAttachments, IMessageAttachedActions } from './interfaces';
import { IMessageAttachments } from './interfaces';
import Image from './Image';
import Audio from './Audio';
import Video from './Video';
@ -13,30 +13,49 @@ import MessageContext from './Context';
import { useTheme } from '../../theme';
import { IAttachment } from '../../definitions';
import CollapsibleQuote from './Components/CollapsibleQuote';
import openLink from '../../utils/openLink';
import { themes } from '../../constants/colors';
const AttachedActions = ({ attachment }: IMessageAttachedActions) => {
export type TElement = {
type: string;
msg?: string;
url?: string;
text: string;
};
const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
if (!attachment.actions) {
return null;
}
const { onAnswerButtonPress } = useContext(MessageContext);
const { theme } = useTheme();
const attachedButtons = attachment.actions.map((element: { type: string; msg: string; text: string }) => {
const attachedButtons = attachment.actions.map((element: TElement) => {
const onPress = () => {
if (element.msg) {
onAnswerButtonPress(element.msg);
}
if (element.url) {
openLink(element.url);
}
};
if (element.type === 'button') {
return <Button theme={theme} onPress={() => onAnswerButtonPress(element.msg)} title={element.text} />;
return <Button theme={theme} onPress={onPress} title={element.text} />;
}
return null;
});
return (
<>
<Text style={styles.text}>{attachment.text}</Text>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{attachment.text}</Text>
{attachedButtons}
</>
);
};
const Attachments = React.memo(
// @ts-ignore
const Attachments: React.FC<IMessageAttachments> = React.memo(
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
if (!attachments || attachments.length === 0) {
return null;
@ -44,7 +63,7 @@ const Attachments = React.memo(
const { theme } = useTheme();
return attachments.map((file: IAttachment, index: number) => {
const attachmentsElements = attachments.map((file: IAttachment, index: number) => {
if (file && file.image_url) {
return (
<Image
@ -54,7 +73,6 @@ const Attachments = React.memo(
getCustomEmoji={getCustomEmoji}
style={style}
isReply={isReply}
theme={theme}
/>
);
}
@ -74,7 +92,6 @@ const Attachments = React.memo(
getCustomEmoji={getCustomEmoji}
style={style}
isReply={isReply}
theme={theme}
/>
);
}
@ -90,6 +107,7 @@ const Attachments = React.memo(
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />;
});
return <>{attachmentsElements}</>;
},
(prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments)
);

View File

@ -1,10 +1,11 @@
import React from 'react';
import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
import { Audio } from 'expo-av';
import { Audio, AVPlaybackStatus } from 'expo-av';
import Slider from '@react-native-community/slider';
import moment from 'moment';
import { dequal } from 'dequal';
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
import { Sound } from 'expo-av/build/Audio/Sound';
import Touchable from './Touchable';
import Markdown from '../markdown';
@ -23,7 +24,7 @@ interface IButton {
paused: boolean;
theme: string;
disabled?: boolean;
onPress: Function;
onPress: () => void;
}
interface IMessageAudioProps {
@ -108,7 +109,7 @@ Button.displayName = 'MessageAudioButton';
class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioState> {
static contextType = MessageContext;
private sound: any;
private sound: Sound;
constructor(props: IMessageAudioProps) {
super(props);
@ -141,7 +142,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
this.setState({ loading: false });
}
shouldComponentUpdate(nextProps: any, nextState: any) {
shouldComponentUpdate(nextProps: IMessageAudioProps, nextState: IMessageAudioState) {
const { currentTime, duration, paused, loading } = this.state;
const { file, theme } = this.props;
if (nextProps.theme !== theme) {
@ -182,7 +183,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
}
}
onPlaybackStatusUpdate = (status: any) => {
onPlaybackStatusUpdate = (status: AVPlaybackStatus) => {
if (status) {
this.onLoad(status);
this.onProgress(status);
@ -190,26 +191,32 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
}
};
onLoad = (data: any) => {
const duration = data.durationMillis / 1000;
this.setState({ duration: duration > 0 ? duration : 0 });
};
onProgress = (data: any) => {
const { duration } = this.state;
const currentTime = data.positionMillis / 1000;
if (currentTime <= duration) {
this.setState({ currentTime });
onLoad = (data: AVPlaybackStatus) => {
if (data.isLoaded && data.durationMillis) {
const duration = data.durationMillis / 1000;
this.setState({ duration: duration > 0 ? duration : 0 });
}
};
onEnd = async (data: any) => {
if (data.didJustFinish) {
try {
await this.sound.stopAsync();
this.setState({ paused: true, currentTime: 0 });
} catch {
// do nothing
onProgress = (data: AVPlaybackStatus) => {
if (data.isLoaded) {
const { duration } = this.state;
const currentTime = data.positionMillis / 1000;
if (currentTime <= duration) {
this.setState({ currentTime });
}
}
};
onEnd = async (data: AVPlaybackStatus) => {
if (data.isLoaded) {
if (data.didJustFinish) {
try {
await this.sound.stopAsync();
this.setState({ paused: true, currentTime: 0 });
} catch {
// do nothing
}
}
}
};
@ -238,7 +245,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
}
};
onValueChange = async (value: any) => {
onValueChange = async (value: number) => {
try {
this.setState({ currentTime: value });
await this.sound.setPositionAsync(value * 1000);

View File

@ -8,15 +8,17 @@ const Blocks = React.memo(({ blocks, id: mid, rid, blockAction }: IMessageBlocks
const appId = blocks[0]?.appId || '';
return React.createElement(
messageBlockWithContext({
action: async ({ actionId, value, blockId }: any) => {
await blockAction({
actionId,
appId,
value,
blockId,
rid,
mid
});
action: async ({ actionId, value, blockId }: { actionId: string; value: string; blockId: string }) => {
if (blockAction) {
await blockAction({
actionId,
appId,
value,
blockId,
rid,
mid
});
}
},
appId,
rid

View File

@ -9,10 +9,13 @@ import I18n from '../../i18n';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
import { IMessageBroadcast } from './interfaces';
import { useTheme } from '../../theme';
const Broadcast = React.memo(({ author, broadcast, theme }: IMessageBroadcast) => {
const Broadcast = React.memo(({ author, broadcast }: IMessageBroadcast) => {
const { user, replyBroadcast } = useContext(MessageContext);
const isOwn = author._id === user.id;
const { theme } = useTheme();
const isOwn = author?._id === user.id;
if (broadcast && !isOwn) {
return (
<View style={styles.buttonContainer}>

View File

@ -8,21 +8,25 @@ import I18n from '../../i18n';
import { CustomIcon } from '../../lib/Icons';
import { themes } from '../../constants/colors';
import { IMessageCallButton } from './interfaces';
import { useTheme } from '../../theme';
const CallButton = React.memo(({ theme, callJitsi }: IMessageCallButton) => (
<View style={styles.buttonContainer}>
<Touchable
onPress={callJitsi}
background={Touchable.Ripple(themes[theme].bannerBackground)}
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
hitSlop={BUTTON_HIT_SLOP}>
<>
<CustomIcon name='camera' size={16} style={styles.buttonIcon} color={themes[theme].buttonText} />
<Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Click_to_join')}</Text>
</>
</Touchable>
</View>
));
const CallButton = React.memo(({ callJitsi }: IMessageCallButton) => {
const { theme } = useTheme();
return (
<View style={styles.buttonContainer}>
<Touchable
onPress={callJitsi}
background={Touchable.Ripple(themes[theme].bannerBackground)}
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
hitSlop={BUTTON_HIT_SLOP}>
<>
<CustomIcon name='camera' size={16} style={styles.buttonIcon} color={themes[theme].buttonText} />
<Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Click_to_join')}</Text>
</>
</Touchable>
</View>
);
});
CallButton.displayName = 'CallButton';

View File

@ -4,19 +4,6 @@ import React from 'react';
import MessageContext from '../../Context';
import CollapsibleQuote from '.';
// For some reason a general mock didn't work, I have to do a search
jest.mock('react-native-mmkv-storage', () => ({
Loader: jest.fn().mockImplementation(() => ({
setProcessingMode: jest.fn().mockImplementation(() => ({
withEncryption: jest.fn().mockImplementation(() => ({
initialize: jest.fn()
}))
}))
})),
create: jest.fn(),
MODES: { MULTI_PROCESS: '' }
}));
const testAttachment = {
ts: '1970-01-01T00:00:00.000Z',
title: 'Engineering (9 today)',

View File

@ -10,17 +10,19 @@ import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME, getInfoMessage } from './utils';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
import Encrypted from './Encrypted';
import { E2E_MESSAGE_TYPE } from '../../lib/encryption/constants';
import { E2E_MESSAGE_TYPE } from '../../lib/constants';
import { IMessageContent } from './interfaces';
import { useTheme } from '../../theme';
const Content = React.memo(
(props: IMessageContent) => {
const { theme } = useTheme();
if (props.isInfo) {
// @ts-ignore
const infoMessage = getInfoMessage({ ...props });
const renderMessageContent = (
<Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]} accessibilityLabel={infoMessage}>
<Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]} accessibilityLabel={infoMessage}>
{infoMessage}
</Text>
);
@ -36,14 +38,12 @@ const Content = React.memo(
return renderMessageContent;
}
const isPreview: any = props.tmid && !props.isThreadRoom;
const isPreview = props.tmid && !props.isThreadRoom;
let content = null;
if (props.isEncrypted) {
content = (
<Text
style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}
accessibilityLabel={I18n.t('Encrypted_message')}>
<Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]} accessibilityLabel={I18n.t('Encrypted_message')}>
{I18n.t('Encrypted_message')}
</Text>
);
@ -65,7 +65,7 @@ const Content = React.memo(
navToRoomInfo={props.navToRoomInfo}
tmid={props.tmid}
useRealName={props.useRealName}
theme={props.theme}
theme={theme}
onLinkPress={onLinkPress}
/>
);
@ -76,13 +76,13 @@ const Content = React.memo(
content = (
<View style={styles.flex}>
<View style={styles.contentContainer}>{content}</View>
<Encrypted type={props.type} theme={props.theme} />
<Encrypted type={props.type} />
</View>
);
}
if (props.isIgnored) {
content = <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Message_Ignored')}</Text>;
content = <Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]}>{I18n.t('Message_Ignored')}</Text>;
}
return <View style={props.isTemp && styles.temp}>{content}</View>;
@ -97,9 +97,6 @@ const Content = React.memo(
if (prevProps.type !== nextProps.type) {
return false;
}
if (prevProps.theme !== nextProps.theme) {
return false;
}
if (prevProps.isEncrypted !== nextProps.isEncrypted) {
return false;
}

View File

@ -10,10 +10,12 @@ import { DISCUSSION } from './constants';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
import { formatDateThreads } from '../../utils/room';
import { IMessageDiscussion } from './interfaces';
import { IMessage } from '../../definitions';
import { useTheme } from '../../theme';
const Discussion = React.memo(
({ msg, dcount, dlm, theme }: IMessageDiscussion) => {
({ msg, dcount, dlm }: Pick<IMessage, 'msg' | 'dcount' | 'dlm'>) => {
const { theme } = useTheme();
let time;
if (dlm) {
time = formatDateThreads(dlm);
@ -50,9 +52,6 @@ const Discussion = React.memo(
if (prevProps.dlm !== nextProps.dlm) {
return false;
}
if (prevProps.theme !== nextProps.theme) {
return false;
}
return true;
}
);

View File

@ -1,19 +1,16 @@
import React, { useContext } from 'react';
import Touchable from './Touchable';
import { E2E_MESSAGE_TYPE } from '../../lib/encryption/constants';
import { E2E_MESSAGE_TYPE } from '../../lib/constants';
import { CustomIcon } from '../../lib/Icons';
import { themes } from '../../constants/colors';
import { BUTTON_HIT_SLOP } from './utils';
import MessageContext from './Context';
import styles from './styles';
import { useTheme } from '../../theme';
interface IMessageEncrypted {
type: string;
theme: string;
}
const Encrypted = React.memo(({ type, theme }: IMessageEncrypted) => {
const Encrypted = React.memo(({ type }: { type: string }) => {
const { theme } = useTheme();
if (type !== E2E_MESSAGE_TYPE) {
return null;
}

View File

@ -13,32 +13,27 @@ import { themes } from '../../constants/colors';
import MessageContext from './Context';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { IAttachment } from '../../definitions';
import { useTheme } from '../../theme';
type TMessageButton = {
children: JSX.Element;
interface IMessageButton {
children: React.ReactElement;
disabled?: boolean;
onPress: Function;
onPress: () => void;
theme: string;
};
type TMessageImage = {
img: string;
theme: string;
};
}
interface IMessageImage {
file: IAttachment;
imageUrl?: string;
showAttachment?: Function;
showAttachment?: (file: IAttachment) => void;
style?: StyleProp<TextStyle>[];
isReply?: boolean;
theme: string;
getCustomEmoji: TGetCustomEmoji;
getCustomEmoji?: TGetCustomEmoji;
}
const ImageProgress = createImageProgress(FastImage);
const Button = React.memo(({ children, onPress, disabled, theme }: TMessageButton) => (
const Button = React.memo(({ children, onPress, disabled, theme }: IMessageButton) => (
<Touchable
disabled={disabled}
onPress={onPress}
@ -48,10 +43,10 @@ const Button = React.memo(({ children, onPress, disabled, theme }: TMessageButto
</Touchable>
));
export const MessageImage = React.memo(({ img, theme }: TMessageImage) => (
export const MessageImage = React.memo(({ imgUri, theme }: { imgUri: string; theme: string }) => (
<ImageProgress
style={[styles.image, { borderColor: themes[theme].borderColor }]}
source={{ uri: encodeURI(img) }}
source={{ uri: encodeURI(imgUri) }}
resizeMode={FastImage.resizeMode.cover}
indicator={Progress.Pie}
indicatorProps={{
@ -61,9 +56,11 @@ export const MessageImage = React.memo(({ img, theme }: TMessageImage) => (
));
const ImageContainer = React.memo(
({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageImage) => {
({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply }: IMessageImage) => {
const { theme } = useTheme();
const { baseUrl, user } = useContext(MessageContext);
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
if (!img) {
return null;
}
@ -88,7 +85,7 @@ const ImageContainer = React.memo(
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
<MessageImage img={img} theme={theme} />
<MessageImage imgUri={img} theme={theme} />
</View>
</Button>
);
@ -96,11 +93,11 @@ const ImageContainer = React.memo(
return (
<Button disabled={isReply} theme={theme} onPress={onPress}>
<MessageImage img={img} theme={theme} />
<MessageImage imgUri={img} theme={theme} />
</Button>
);
},
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file)
);
ImageContainer.displayName = 'MessageImageContainer';

View File

@ -19,6 +19,7 @@ import ReadReceipt from './ReadReceipt';
import CallButton from './CallButton';
import { themes } from '../../constants/colors';
import { IMessage, IMessageInner, IMessageTouchable } from './interfaces';
import { useTheme } from '../../theme';
const MessageInner = React.memo((props: IMessageInner) => {
const { attachments } = props;
@ -85,7 +86,6 @@ const Message = React.memo((props: IMessage) => {
<View style={[styles.container, props.style]}>
{thread}
<View style={styles.flex}>
{/* @ts-ignore */}
<MessageAvatar small {...props} />
<View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}>
<Content {...props} />
@ -98,12 +98,11 @@ const Message = React.memo((props: IMessage) => {
return (
<View style={[styles.container, props.style]}>
<View style={styles.flex}>
{/* @ts-ignore */}
<MessageAvatar {...props} />
<View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}>
<MessageInner {...props} />
</View>
<ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread || false} theme={props.theme} />
<ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread || false} />
</View>
</View>
);
@ -119,12 +118,14 @@ const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
);
}
const { onPress, onLongPress } = useContext(MessageContext);
const { theme } = useTheme();
return (
<Touchable
onLongPress={onLongPress}
onPress={onPress}
disabled={(props.isInfo && !props.isThreadReply) || props.archived || props.isTemp || props.type === 'jitsi_call_started'}
style={{ backgroundColor: props.highlighted ? themes[props.theme].headerBackground : null }}>
style={{ backgroundColor: props.highlighted ? themes[theme].headerBackground : null }}>
<View>
<Message {...props} />
</View>

View File

@ -4,32 +4,30 @@ import Avatar from '../Avatar';
import styles from './styles';
import MessageContext from './Context';
import { IMessageAvatar } from './interfaces';
import { SubscriptionType } from '../../definitions';
const MessageAvatar = React.memo(
({ isHeader, avatar, author, small, navToRoomInfo, emoji, getCustomEmoji, theme }: IMessageAvatar) => {
const { user } = useContext(MessageContext);
if (isHeader && author) {
const navParam = {
t: 'd',
rid: author._id
};
return (
<Avatar
style={small ? styles.avatarSmall : styles.avatar}
text={avatar ? '' : author.username}
size={small ? 20 : 36}
borderRadius={small ? 2 : 4}
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
getCustomEmoji={getCustomEmoji}
avatar={avatar}
emoji={emoji}
theme={theme}
/>
);
}
return null;
const MessageAvatar = React.memo(({ isHeader, avatar, author, small, navToRoomInfo, emoji, getCustomEmoji }: IMessageAvatar) => {
const { user } = useContext(MessageContext);
if (isHeader && author) {
const navParam = {
t: SubscriptionType.DIRECT,
rid: author._id
};
return (
<Avatar
style={small ? styles.avatarSmall : styles.avatar}
text={avatar ? '' : author.username}
size={small ? 20 : 36}
borderRadius={small ? 2 : 4}
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
getCustomEmoji={getCustomEmoji}
avatar={avatar}
emoji={emoji}
/>
);
}
);
return null;
});
MessageAvatar.displayName = 'MessageAvatar';

View File

@ -6,14 +6,12 @@ import styles from './styles';
import { BUTTON_HIT_SLOP } from './utils';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
interface IMessageError {
hasError: boolean;
theme: string;
}
import { useTheme } from '../../theme';
const MessageError = React.memo(
({ hasError, theme }: IMessageError) => {
({ hasError }: { hasError: boolean }) => {
const { theme } = useTheme();
if (!hasError) {
return null;
}
@ -24,7 +22,7 @@ const MessageError = React.memo(
</Touchable>
);
},
(prevProps, nextProps) => prevProps.hasError === nextProps.hasError && prevProps.theme === nextProps.theme
(prevProps, nextProps) => prevProps.hasError === nextProps.hasError
);
MessageError.displayName = 'MessageError';

View File

@ -7,30 +7,28 @@ import styles from './styles';
import Emoji from './Emoji';
import { BUTTON_HIT_SLOP } from './utils';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import { useTheme } from '../../theme';
import MessageContext from './Context';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
interface IMessageAddReaction {
theme: string;
interface IReaction {
_id: string;
emoji: string;
usernames: string[];
}
interface IMessageReaction {
reaction: {
usernames: [];
emoji: object;
};
reaction: IReaction;
getCustomEmoji: TGetCustomEmoji;
theme: string;
}
interface IMessageReactions {
reactions?: object[];
reactions?: IReaction[];
getCustomEmoji: TGetCustomEmoji;
theme: string;
}
const AddReaction = React.memo(({ theme }: IMessageAddReaction) => {
const AddReaction = React.memo(({ theme }: { theme: string }) => {
const { reactionInit } = useContext(MessageContext);
return (
<Touchable
@ -49,7 +47,7 @@ const AddReaction = React.memo(({ theme }: IMessageAddReaction) => {
const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReaction) => {
const { onReactionPress, onReactionLongPress, baseUrl, user } = useContext(MessageContext);
const reacted = reaction.usernames.findIndex((item: IMessageReaction) => item === user.username) !== -1;
const reacted = reaction.usernames.findIndex((item: string) => item === user.username) !== -1;
return (
<Touchable
onPress={() => onReactionPress(reaction.emoji)}
@ -76,13 +74,15 @@ const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReacti
);
});
const Reactions = React.memo(({ reactions, getCustomEmoji, theme }: IMessageReactions) => {
const Reactions = React.memo(({ reactions, getCustomEmoji }: IMessageReactions) => {
const { theme } = useTheme();
if (!Array.isArray(reactions) || reactions.length === 0) {
return null;
}
return (
<View style={styles.reactionsContainer}>
{reactions.map((reaction: any) => (
{reactions.map(reaction => (
<Reaction key={reaction.emoji} reaction={reaction} getCustomEmoji={getCustomEmoji} theme={theme} />
))}
<AddReaction theme={theme} />
@ -94,4 +94,4 @@ Reaction.displayName = 'MessageReaction';
Reactions.displayName = 'MessageReactions';
AddReaction.displayName = 'MessageAddReaction';
export default withTheme(Reactions);
export default Reactions;

View File

@ -3,14 +3,10 @@ import React from 'react';
import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons';
import styles from './styles';
import { useTheme } from '../../theme';
interface IMessageReadReceipt {
isReadReceiptEnabled: boolean;
unread: boolean;
theme: string;
}
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread, theme }: IMessageReadReceipt) => {
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread: boolean }) => {
const { theme } = useTheme();
if (isReadReceiptEnabled && !unread && unread !== null) {
return <CustomIcon name='check' color={themes[theme].tintColor} size={15} style={styles.readReceipt} />;
}

View File

@ -7,15 +7,18 @@ import { themes } from '../../constants/colors';
import I18n from '../../i18n';
import { MarkdownPreview } from '../markdown';
import { IMessageRepliedThread } from './interfaces';
import { useTheme } from '../../theme';
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted }: IMessageRepliedThread) => {
const { theme } = useTheme();
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted, theme }: IMessageRepliedThread) => {
if (!tmid || !isHeader) {
return null;
}
const [msg, setMsg] = useState(isEncrypted ? I18n.t('Encrypted_message') : tmsg);
const fetch = async () => {
const threadName = await fetchThreadName(tmid, id);
const threadName = fetchThreadName ? await fetchThreadName(tmid, id) : '';
setMsg(threadName);
};

View File

@ -77,7 +77,7 @@ const styles = StyleSheet.create({
marginBottom: 4
},
image: {
// @ts-ignore
// @ts-ignore TODO - check with the team, change this to undefined
width: null,
height: 200,
flex: 1,
@ -93,24 +93,6 @@ const styles = StyleSheet.create({
}
});
interface IMessageTitle {
attachment: IAttachment;
timeFormat?: string;
theme: string;
}
interface IMessageDescription {
attachment: IAttachment;
getCustomEmoji: TGetCustomEmoji;
theme: string;
}
interface IMessageFields {
attachment: IAttachment;
theme: string;
getCustomEmoji: TGetCustomEmoji;
}
interface IMessageReply {
attachment: IAttachment;
timeFormat?: string;
@ -118,7 +100,7 @@ interface IMessageReply {
getCustomEmoji: TGetCustomEmoji;
}
const Title = React.memo(({ attachment, timeFormat, theme }: IMessageTitle) => {
const Title = React.memo(({ attachment, timeFormat, theme }: { attachment: IAttachment; timeFormat?: string; theme: string }) => {
const time = attachment.message_link && attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
return (
<View style={styles.authorContainer}>
@ -132,7 +114,7 @@ const Title = React.memo(({ attachment, timeFormat, theme }: IMessageTitle) => {
});
const Description = React.memo(
({ attachment, getCustomEmoji, theme }: IMessageDescription) => {
({ attachment, getCustomEmoji, theme }: { attachment: IAttachment; getCustomEmoji: TGetCustomEmoji; theme: string }) => {
const text = attachment.text || attachment.title;
if (!text) {
return null;
@ -164,7 +146,7 @@ const Description = React.memo(
);
const UrlImage = React.memo(
({ image }: any) => {
({ image }: { image?: string }) => {
if (!image) {
return null;
}
@ -176,7 +158,7 @@ const UrlImage = React.memo(
);
const Fields = React.memo(
({ attachment, theme, getCustomEmoji }: IMessageFields) => {
({ attachment, theme, getCustomEmoji }: { attachment: IAttachment; theme: string; getCustomEmoji: TGetCustomEmoji }) => {
if (!attachment.fields) {
return null;
}
@ -206,12 +188,12 @@ const Fields = React.memo(
const Reply = React.memo(
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
const [loading, setLoading] = useState(false);
const { theme } = useTheme();
if (!attachment) {
return null;
}
const { theme } = useTheme();
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
const onPress = async () => {

View File

@ -7,9 +7,12 @@ import MessageContext from './Context';
import ThreadDetails from '../ThreadDetails';
import I18n from '../../i18n';
import { IMessageThread } from './interfaces';
import { useTheme } from '../../theme';
const Thread = React.memo(
({ msg, tcount, tlm, isThreadRoom, theme, id }: IMessageThread) => {
({ msg, tcount, tlm, isThreadRoom, id }: IMessageThread) => {
const { theme } = useTheme();
if (!tlm || isThreadRoom || tcount === 0) {
return null;
}
@ -38,9 +41,6 @@ const Thread = React.memo(
if (prevProps.tcount !== nextProps.tcount) {
return false;
}
if (prevProps.theme !== nextProps.theme) {
return false;
}
return true;
}
);

View File

@ -1,5 +1,6 @@
import React, { useContext } from 'react';
import { Clipboard, StyleSheet, Text, View } from 'react-native';
import { StyleSheet, Text, View } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import FastImage from '@rocket.chat/react-native-fast-image';
import { dequal } from 'dequal';
@ -7,11 +8,12 @@ import Touchable from './Touchable';
import openLink from '../../utils/openLink';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import { useTheme, withTheme } from '../../theme';
import { LISTENER } from '../Toast';
import EventEmitter from '../../utils/events';
import I18n from '../../i18n';
import MessageContext from './Context';
import { IUrl } from '../../definitions';
const styles = StyleSheet.create({
button: {
@ -49,29 +51,6 @@ const styles = StyleSheet.create({
}
});
interface IMessageUrlContent {
title: string;
description: string;
theme: string;
}
interface IMessageUrl {
url: {
ignoreParse: boolean;
url: string;
image: string;
title: string;
description: string;
};
index: number;
theme: string;
}
interface IMessageUrls {
urls?: any;
theme?: string;
}
const UrlImage = React.memo(
({ image }: { image: string }) => {
if (!image) {
@ -85,7 +64,7 @@ const UrlImage = React.memo(
);
const UrlContent = React.memo(
({ title, description, theme }: IMessageUrlContent) => (
({ title, description, theme }: { title: string; description: string; theme: string }) => (
<View style={styles.textContainer}>
{title ? (
<Text style={[styles.title, { color: themes[theme].tintColor }]} numberOfLines={2}>
@ -114,7 +93,7 @@ const UrlContent = React.memo(
);
const Url = React.memo(
({ url, index, theme }: IMessageUrl) => {
({ url, index, theme }: { url: IUrl; index: number; theme: string }) => {
if (!url || url?.ignoreParse) {
return null;
}
@ -151,14 +130,17 @@ const Url = React.memo(
);
const Urls = React.memo(
({ urls, theme }: IMessageUrls) => {
// TODO - didn't work - (React.ReactElement | null)[] | React.ReactElement | null
({ urls }: { urls?: IUrl[] }): any => {
const { theme } = useTheme();
if (!urls || urls.length === 0) {
return null;
}
return urls.map((url: any, index: number) => <Url url={url} key={url.url} index={index} theme={theme!} />);
return urls.map((url: IUrl, index: number) => <Url url={url} key={url.url} index={index} theme={theme} />);
},
(oldProps, newProps) => dequal(oldProps.urls, newProps.urls) && oldProps.theme === newProps.theme
(oldProps, newProps) => dequal(oldProps.urls, newProps.urls)
);
UrlImage.displayName = 'MessageUrlImage';

View File

@ -3,12 +3,14 @@ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import moment from 'moment';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import { useTheme } from '../../theme';
import MessageError from './MessageError';
import sharedStyles from '../../views/Styles';
import messageStyles from './styles';
import MessageContext from './Context';
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils';
import { SubscriptionType } from '../../definitions';
import { IRoomInfoParam } from '../../views/SearchMessagesView';
const styles = StyleSheet.create({
container: {
@ -49,15 +51,15 @@ interface IMessageUser {
alias?: string;
ts?: Date;
timeFormat?: string;
theme: string;
navToRoomInfo?: Function;
navToRoomInfo?: (navParam: IRoomInfoParam) => void;
type: string;
}
const User = React.memo(
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, type, ...props }: IMessageUser) => {
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, ...props }: IMessageUser) => {
if (isHeader || hasError) {
const { user } = useContext(MessageContext);
const { theme } = useTheme();
const username = (useRealName && author?.name) || author?.username;
const aliasUsername = alias ? (
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
@ -65,8 +67,8 @@ const User = React.memo(
const time = moment(ts).format(timeFormat);
const onUserPress = () => {
navToRoomInfo?.({
t: 'd',
rid: author?._id
t: SubscriptionType.DIRECT,
rid: author?._id || ''
});
};
const isDisabled = author?._id === user.id;
@ -83,7 +85,7 @@ const User = React.memo(
<Text
style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]}
onPress={onUserPress}
// @ts-ignore
// @ts-ignore // TODO - check this prop
disabled={isDisabled}>
{textContent}
</Text>
@ -98,7 +100,7 @@ const User = React.memo(
</Text>
</TouchableOpacity>
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text>
{hasError && <MessageError hasError={hasError} theme={theme} {...props} />}
{hasError ? <MessageError hasError={hasError} {...props} /> : null}
</View>
);
}
@ -108,4 +110,4 @@ const User = React.memo(
User.displayName = 'MessageUser';
export default withTheme(User);
export default User;

View File

@ -16,9 +16,10 @@ import I18n from '../../i18n';
import { IAttachment } from '../../definitions/IAttachment';
import RCActivityIndicator from '../ActivityIndicator';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { useTheme } from '../../theme';
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
const isTypeSupported = (type: any) => SUPPORTED_TYPES.indexOf(type) !== -1;
const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1;
const styles = StyleSheet.create({
button: {
@ -33,23 +34,24 @@ const styles = StyleSheet.create({
interface IMessageVideo {
file: IAttachment;
showAttachment?: Function;
showAttachment?: (file: IAttachment) => void;
getCustomEmoji: TGetCustomEmoji;
style?: StyleProp<TextStyle>[];
isReply?: boolean;
theme: string;
}
const Video = React.memo(
({ file, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageVideo) => {
({ file, showAttachment, getCustomEmoji, style, isReply }: IMessageVideo) => {
const { baseUrl, user } = useContext(MessageContext);
const [loading, setLoading] = useState(false);
const { theme } = useTheme();
if (!baseUrl) {
return null;
}
const onPress = async () => {
if (isTypeSupported(file.video_type) && showAttachment) {
if (file.video_type && isTypeSupported(file.video_type) && showAttachment) {
return showAttachment(file);
}
@ -93,7 +95,7 @@ const Video = React.memo(
</>
);
},
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file)
);
export default Video;

View File

@ -1,16 +1,18 @@
import React from 'react';
import { Keyboard, ViewStyle } from 'react-native';
import { Subscription } from 'rxjs';
import Message from './Message';
import MessageContext from './Context';
import debounce from '../../utils/debounce';
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/constants';
import messagesStatus from '../../constants/messagesStatus';
import { withTheme } from '../../theme';
import { useTheme, withTheme } from '../../theme';
import openLink from '../../utils/openLink';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { TAnyMessageModel } from '../../definitions';
import { IAttachment, TAnyMessageModel } from '../../definitions';
import { IRoomInfoParam } from '../../views/SearchMessagesView';
interface IMessageContainerProps {
item: TAnyMessageModel;
@ -20,7 +22,7 @@ interface IMessageContainerProps {
token: string;
};
msg?: string;
rid?: string;
rid: string;
timeFormat?: string;
style?: ViewStyle;
archived?: boolean;
@ -37,44 +39,35 @@ interface IMessageContainerProps {
isIgnored?: boolean;
highlighted?: boolean;
getCustomEmoji: TGetCustomEmoji;
onLongPress?: Function;
onReactionPress?: Function;
onEncryptedPress?: Function;
onDiscussionPress?: Function;
onThreadPress?: Function;
errorActionsShow?: Function;
replyBroadcast?: Function;
reactionInit?: Function;
fetchThreadName?: Function;
showAttachment?: Function;
onReactionLongPress?: Function;
navToRoomInfo?: Function;
callJitsi?: Function;
blockAction?: Function;
onAnswerButtonPress?: Function;
theme?: string;
onLongPress?: (item: TAnyMessageModel) => void;
onReactionPress?: (emoji: string, id: string) => void;
onEncryptedPress?: () => void;
onDiscussionPress?: (item: TAnyMessageModel) => void;
onThreadPress?: (item: TAnyMessageModel) => void;
errorActionsShow?: (item: TAnyMessageModel) => void;
replyBroadcast?: (item: TAnyMessageModel) => void;
reactionInit?: (item: TAnyMessageModel) => void;
fetchThreadName?: (tmid: string, id: string) => Promise<string | undefined>;
showAttachment: (file: IAttachment) => void;
onReactionLongPress?: (item: TAnyMessageModel) => void;
navToRoomInfo: (navParam: IRoomInfoParam) => void;
callJitsi?: () => void;
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
onAnswerButtonPress?: (message: string, tmid?: string, tshow?: boolean) => void;
threadBadgeColor?: string;
toggleFollowThread?: Function;
jumpToMessage?: Function;
onPress?: Function;
toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
jumpToMessage?: (link: string) => void;
onPress?: () => void;
}
class MessageContainer extends React.Component<IMessageContainerProps> {
interface IMessageContainerState {
isManualUnignored: boolean;
}
class MessageContainer extends React.Component<IMessageContainerProps, IMessageContainerState> {
static defaultProps = {
getCustomEmoji: () => null,
onLongPress: () => {},
onReactionPress: () => {},
onEncryptedPress: () => {},
onDiscussionPress: () => {},
onThreadPress: () => {},
onAnswerButtonPress: () => {},
errorActionsShow: () => {},
replyBroadcast: () => {},
reactionInit: () => {},
fetchThreadName: () => {},
showAttachment: () => {},
onReactionLongPress: () => {},
navToRoomInfo: () => {},
callJitsi: () => {},
blockAction: () => {},
archived: false,
@ -85,7 +78,7 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
state = { isManualUnignored: false };
private subscription: any;
private subscription?: Subscription;
componentDidMount() {
const { item } = this.props;
@ -97,12 +90,9 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
}
}
shouldComponentUpdate(nextProps: any, nextState: any) {
shouldComponentUpdate(nextProps: IMessageContainerProps, nextState: IMessageContainerState) {
const { isManualUnignored } = this.state;
const { theme, threadBadgeColor, isIgnored, highlighted } = this.props;
if (nextProps.theme !== theme) {
return true;
}
const { threadBadgeColor, isIgnored, highlighted } = this.props;
if (nextProps.highlighted !== highlighted) {
return true;
}
@ -169,7 +159,7 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
}
};
onReactionPress = (emoji: any) => {
onReactionPress = (emoji: string) => {
const { onReactionPress, item } = this.props;
if (onReactionPress) {
onReactionPress(emoji, item.id);
@ -228,7 +218,7 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
previousItem.u.username === item.u.username &&
!(previousItem.groupable === false || item.groupable === false || broadcast === true) &&
// @ts-ignore TODO: IMessage vs IMessageFromServer non-sense
item.ts - previousItem.ts < Message_GroupingPeriod! * 1000 &&
item.ts - previousItem.ts < Message_GroupingPeriod * 1000 &&
previousItem.tmid === item.tmid
) {
return false;
@ -303,10 +293,11 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
};
onLinkPress = (link: string): void => {
const { item, theme, jumpToMessage } = this.props;
const isMessageLink = item?.attachments?.findIndex((att: any) => att?.message_link === link) !== -1;
if (isMessageLink) {
return jumpToMessage!(link);
const { theme } = useTheme();
const { item, jumpToMessage } = this.props;
const isMessageLink = item?.attachments?.findIndex((att: IAttachment) => att?.message_link === link) !== -1;
if (isMessageLink && jumpToMessage) {
return jumpToMessage(link);
}
openLink(link, theme);
};
@ -332,7 +323,6 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
callJitsi,
blockAction,
rid,
theme,
threadBadgeColor,
toggleFollowThread,
jumpToMessage,
@ -371,8 +361,8 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
let message = msg;
// "autoTranslateRoom" and "autoTranslateLanguage" are properties from the subscription
// "autoTranslateMessage" is a toggle between "View Original" and "Translate" state
if (autoTranslateRoom && autoTranslateMessage) {
message = getMessageTranslation(item, autoTranslateLanguage!) || message;
if (autoTranslateRoom && autoTranslateMessage && autoTranslateLanguage) {
message = getMessageTranslation(item, autoTranslateLanguage) || message;
}
return (
@ -396,14 +386,15 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
toggleFollowThread,
replies
}}>
{/* @ts-ignore*/}
<Message
id={id}
msg={message}
md={md}
rid={rid!}
rid={rid}
author={u}
ts={ts}
type={t as any}
type={t}
attachments={attachments}
blocks={blocks}
urls={urls}
@ -413,23 +404,20 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
emoji={emoji}
timeFormat={timeFormat}
style={style}
archived={archived!}
broadcast={broadcast!}
archived={archived}
broadcast={broadcast}
useRealName={useRealName}
isReadReceiptEnabled={isReadReceiptEnabled!}
isReadReceiptEnabled={isReadReceiptEnabled}
unread={unread}
role={role}
drid={drid}
dcount={dcount}
// @ts-ignore
dlm={dlm}
tmid={tmid}
tcount={tcount}
// @ts-ignore
tlm={tlm}
tmsg={tmsg}
fetchThreadName={fetchThreadName!}
// @ts-ignore
fetchThreadName={fetchThreadName}
mentions={mentions}
channels={channels}
isIgnored={this.isIgnored}
@ -442,13 +430,12 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
isTemp={this.isTemp}
isEncrypted={this.isEncrypted}
hasError={this.hasError}
showAttachment={showAttachment!}
showAttachment={showAttachment}
getCustomEmoji={getCustomEmoji}
navToRoomInfo={navToRoomInfo!}
callJitsi={callJitsi!}
blockAction={blockAction!}
theme={theme as string}
highlighted={highlighted!}
navToRoomInfo={navToRoomInfo}
callJitsi={callJitsi}
blockAction={blockAction}
highlighted={highlighted}
/>
</MessageContext.Provider>
);

View File

@ -1,64 +1,45 @@
import { MarkdownAST } from '@rocket.chat/message-parser';
import { StyleProp, TextStyle } from 'react-native';
import { ImageStyle } from '@rocket.chat/react-native-fast-image';
import { IUserChannel, IUserMention } from '../markdown/interfaces';
import { IUserChannel } from '../markdown/interfaces';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { IAttachment } from '../../definitions';
export type TMessageType = 'discussion-created' | 'jitsi_call_started';
import { IAttachment, IThread, IUrl, IUserMention, IUserMessage, MessageType, TAnyMessageModel } from '../../definitions';
import { IRoomInfoParam } from '../../views/SearchMessagesView';
export interface IMessageAttachments {
attachments?: IAttachment[];
timeFormat?: string;
style?: StyleProp<TextStyle>[];
isReply?: boolean;
showAttachment?: Function;
showAttachment?: (file: IAttachment) => void;
getCustomEmoji: TGetCustomEmoji;
}
export interface IMessageAttachedActions {
attachment: IAttachment;
}
export interface IMessageAvatar {
isHeader: boolean;
avatar: string;
emoji: string;
author: {
username: string;
_id: string;
};
avatar?: string;
emoji?: string;
author?: IUserMessage;
small?: boolean;
navToRoomInfo: Function;
navToRoomInfo: (navParam: IRoomInfoParam) => void;
getCustomEmoji: TGetCustomEmoji;
theme: string;
}
export interface IMessageBlocks {
blocks: any;
blocks: { appId?: string }[];
id: string;
rid: string;
blockAction: Function;
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
}
export interface IMessageBroadcast {
author: {
_id: string;
};
broadcast: boolean;
theme: string;
author?: IUserMessage;
broadcast?: boolean;
}
export interface IMessageCallButton {
theme: string;
callJitsi: Function;
}
export interface IUser {
id: string;
username: string;
token: string;
name: string;
callJitsi?: () => void;
}
export interface IMessageContent {
@ -69,40 +50,27 @@ export interface IMessageContent {
isThreadRoom: boolean;
msg?: string;
md?: MarkdownAST;
theme: string;
isEdited: boolean;
isEncrypted: boolean;
getCustomEmoji: TGetCustomEmoji;
channels?: IUserChannel[];
mentions?: IUserMention[];
navToRoomInfo?: Function;
navToRoomInfo: (navParam: IRoomInfoParam) => void;
useRealName?: boolean;
isIgnored: boolean;
type: string;
}
export interface IMessageDiscussion {
msg?: string;
dcount?: number;
dlm?: Date;
theme: string;
}
export interface IMessageEmoji {
content: any;
content: string;
baseUrl: string;
standardEmojiStyle: object;
customEmojiStyle: object;
standardEmojiStyle: { fontSize: number };
customEmojiStyle: StyleProp<ImageStyle>;
getCustomEmoji: TGetCustomEmoji;
}
export interface IMessageThread {
msg?: string;
tcount?: number | null;
theme: string;
tlm?: Date;
export interface IMessageThread extends Pick<IThread, 'msg' | 'tcount' | 'tlm' | 'id'> {
isThreadRoom: boolean;
id: string;
}
export interface IMessageTouchable {
@ -110,40 +78,35 @@ export interface IMessageTouchable {
isInfo: boolean;
isThreadReply: boolean;
isTemp: boolean;
archived: boolean;
highlighted: boolean;
theme: string;
ts?: any;
urls?: any;
archived?: boolean;
highlighted?: boolean;
ts?: string | Date;
urls?: IUrl[];
reactions?: any;
alias?: any;
role?: any;
drid?: any;
alias?: string;
role?: string;
drid?: string;
}
export interface IMessageRepliedThread {
tmid?: string;
tmsg?: string;
id: string;
export interface IMessageRepliedThread extends Pick<IThread, 'tmid' | 'tmsg' | 'id'> {
isHeader: boolean;
theme: string;
fetchThreadName: Function;
fetchThreadName?: (tmid: string, id: string) => Promise<string | undefined>;
isEncrypted: boolean;
}
export interface IMessageInner
extends IMessageDiscussion,
IMessageContent,
extends IMessageContent,
IMessageCallButton,
IMessageBlocks,
IMessageThread,
IMessageAttachments,
IMessageBroadcast {
type: TMessageType;
type: MessageType;
blocks: [];
urls?: IUrl[];
}
export interface IMessage extends IMessageRepliedThread, IMessageInner {
export interface IMessage extends IMessageRepliedThread, IMessageInner, IMessageAvatar {
isThreadReply: boolean;
isThreadSequential: boolean;
isInfo: boolean;
@ -151,9 +114,11 @@ export interface IMessage extends IMessageRepliedThread, IMessageInner {
isHeader: boolean;
hasError: boolean;
style: any;
onLongPress: Function;
isReadReceiptEnabled: boolean;
// style: ViewStyle;
onLongPress?: (item: TAnyMessageModel) => void;
isReadReceiptEnabled?: boolean;
unread?: boolean;
theme: string;
isIgnored: boolean;
dcount: number | undefined;
dlm: string | Date | undefined;
}

View File

@ -2,7 +2,7 @@ import { IUser } from './IUser';
export interface IAttachment {
ts?: string | Date;
title: string;
title?: string;
type?: string;
description?: string;
title_link?: string;
@ -19,7 +19,7 @@ export interface IAttachment {
image_size?: number;
author_name?: string;
author_icon?: string;
actions?: [];
actions?: { type: string; msg: string; text: string }[];
message_link?: string;
text?: string;
short?: boolean;

View File

@ -1,8 +1,9 @@
// TODO: evaluate unification with IEmoji
export interface IEmoji {
content: any;
name: string;
extension: any;
isCustom: boolean;
content?: string;
name?: string;
extension?: string;
isCustom?: boolean;
}
export interface ICustomEmoji {

View File

@ -1,5 +1,6 @@
import Model from '@nozbe/watermelondb/Model';
// TODO: evaluate unification with IEmoji
export interface IFrequentlyUsedEmoji {
content?: string;
extension?: string;

View File

@ -6,7 +6,7 @@ import { IAttachment } from './IAttachment';
import { IReaction } from './IReaction';
import { TThreadMessageModel } from './IThreadMessage';
import { TThreadModel } from './IThread';
import { IUrlFromServer } from './IUrl';
import { IUrl, IUrlFromServer } from './IUrl';
export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj' | MessageTypeLoad;
@ -75,7 +75,7 @@ export interface IMessageFromServer {
ts: string | Date; // wm date issue
u: IUserMessage;
_updatedAt: string | Date;
urls?: IUrlFromServer[];
urls?: IUrl[];
mentions?: IUserMention[];
channels?: IUserChannel[];
md?: MarkdownAST;
@ -111,7 +111,7 @@ export interface ILoadMoreMessage {
export interface IMessage extends IMessageFromServer {
id: string;
t?: MessageType;
t: MessageType;
alias?: string;
parseUrls?: boolean;
avatar?: string;

View File

@ -106,6 +106,7 @@ export interface ISubscription {
}
export type TSubscriptionModel = ISubscription & Model;
export type TSubscription = TSubscriptionModel | ISubscription;
// https://github.com/RocketChat/Rocket.Chat/blob/a88a96fcadd925b678ff27ada37075e029f78b5e/definition/ISubscription.ts#L8
export interface IServerSubscription extends IRocketChatRecord {

View File

@ -45,5 +45,4 @@ export interface IUrl extends IUrlFromServer {
title: string;
description: string;
image: string;
url: string;
}

View File

@ -2,6 +2,8 @@ import { RouteProp } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { Dispatch } from 'redux';
import { TColors } from '../theme';
export * from './IAttachment';
export * from './INotification';
export * from './IPreferences';
@ -32,8 +34,10 @@ export interface IBaseScreen<T extends Record<string, object | undefined>, S ext
navigation: StackNavigationProp<T, S>;
route: RouteProp<T, S>;
dispatch: Dispatch;
theme: string;
isMasterDetail: boolean;
// TODO: remove after migrating all Class components
theme: string;
colors: TColors;
}
export * from './redux';

View File

@ -49,6 +49,9 @@ export type UsersEndpoints = {
'users.resetAvatar': {
POST: (params: { userId: string }) => {};
};
'users.removeOtherTokens': {
POST: (params: { userId: string }) => {};
};
'users.getPreferences': {
GET: (params: { userId: IUser['_id'] }) => {
preferences: INotificationPreferences;

View File

@ -7,7 +7,7 @@ import { TNavigationOptions } from './definitions/navigationTypes';
export interface IDimensionsContextProps {
width: number;
height: number;
scale?: number;
scale: number;
fontScale: number;
setDimensions?: ({
width,

View File

@ -9,6 +9,7 @@ import UnreadBadge from '../../../presentation/UnreadBadge';
import RocketChat from '../../../lib/rocketchat';
import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../lib';
import { IUser } from '../../../definitions/IUser';
import Touch from '../../../utils/touch';
interface IOmnichannelStatus {
searching: boolean;
@ -48,7 +49,9 @@ const OmnichannelStatus = memo(({ searching, goQueue, queueSize, inquiryEnabled,
right={() => (
<View style={styles.omnichannelRightContainer}>
{inquiryEnabled ? <UnreadBadge style={styles.queueIcon} unread={queueSize} /> : null}
<Switch value={status} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleLivechat} />
<Touch theme={theme} onPress={toggleLivechat}>
<Switch value={status} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleLivechat} />
</Touch>
</View>
)}
/>

View File

@ -3,7 +3,6 @@ declare module 'commonmark';
declare module 'commonmark-react-renderer';
declare module 'remove-markdown';
declare module 'react-native-image-progress';
declare module 'react-native-platform-touchable';
declare module 'react-native-ui-lib/keyboard';
declare module '@rocket.chat/ui-kit';
declare module '@rocket.chat/sdk';

View File

@ -14,7 +14,7 @@ import parseQuery from './lib/methods/helpers/parseQuery';
import { initializePushNotifications, onNotification } from './notifications/push';
import store from './lib/createStore';
import { toggleAnalyticsEventsReport, toggleCrashErrorsReport } from './utils/log';
import { ThemeContext } from './theme';
import { ThemeContext, TSupportedThemes } from './theme';
import { DimensionsContext } from './dimensions';
import RocketChat from './lib/rocketchat';
import { MIN_WIDTH_MASTER_DETAIL_LAYOUT } from './constants/tablet';
@ -32,7 +32,7 @@ import { isFDroidBuild } from './constants/environment';
import { IThemePreference } from './definitions/ITheme';
import { ICommand } from './definitions/ICommand';
import { initStore } from './lib/auxStore';
import { themes } from './constants/colors';
import { colors, themes } from './constants/colors';
RNScreens.enableScreens();
initStore(store);
@ -45,7 +45,7 @@ interface IDimensions {
}
interface IState {
theme: string;
theme: TSupportedThemes;
themePreferences: IThemePreference;
width: number;
height: number;
@ -215,7 +215,8 @@ export default class Root extends React.Component<{}, IState> {
value={{
theme,
themePreferences,
setTheme: this.setTheme
setTheme: this.setTheme,
colors: colors[theme]
}}>
<DimensionsContext.Provider
value={{

View File

@ -14,3 +14,8 @@ export const E2E_ROOM_TYPES: Record<string, string> = {
d: 'd',
p: 'p'
};
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
export const MIN_ROCKETCHAT_VERSION = '0.70.0';

View File

@ -17,7 +17,7 @@ import {
E2E_PUBLIC_KEY,
E2E_RANDOM_PASSWORD_KEY,
E2E_STATUS
} from './constants';
} from '../constants';
import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
import { EncryptionRoom } from './index';
import { IMessage, ISubscription, TMessageModel, TSubscriptionModel, TThreadMessageModel, TThreadModel } from '../../definitions';

View File

@ -9,7 +9,7 @@ import Deferred from '../../utils/deferred';
import debounce from '../../utils/debounce';
import database from '../database';
import log from '../../utils/log';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from './constants';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../constants';
import {
b64ToBuffer,
bufferToB64,

View File

@ -1,18 +1,10 @@
import random from '../../utils/random';
import { ITriggerAction, IUserInteraction, ModalActions } from '../../containers/UIKit/interfaces';
import { TRocketChat } from '../../definitions/IRocketChat';
import EventEmitter from '../../utils/events';
import fetch from '../../utils/fetch';
import random from '../../utils/random';
import Navigation from '../Navigation';
import sdk from '../rocketchat/services/sdk';
import {
ActionTypes,
ITriggerAction,
ITriggerBlockAction,
ITriggerCancel,
ITriggerSubmitView,
IUserInteraction,
ModalActions
} from '../../containers/UIKit/interfaces';
import { TRocketChat } from '../../definitions/IRocketChat';
const triggersId = new Map();
@ -139,18 +131,3 @@ export function triggerAction(
return reject();
});
}
export default function triggerBlockAction(this: TRocketChat, options: ITriggerBlockAction) {
return triggerAction.call(this, { type: ActionTypes.ACTION, ...options });
}
export async function triggerSubmitView(this: TRocketChat, { viewId, ...options }: ITriggerSubmitView) {
const result = await triggerAction.call(this, { type: ActionTypes.SUBMIT, viewId, ...options });
if (!result || ModalActions.CLOSE === result) {
Navigation.back();
}
}
export function triggerCancel(this: TRocketChat, { view, ...options }: ITriggerCancel) {
return triggerAction.call(this, { type: ActionTypes.CLOSED, view, ...options });
}

View File

@ -1,4 +1,4 @@
import database from '../../database';
import database from '../database';
export default async function clearCache({ server }: { server: string }): Promise<void> {
try {

View File

@ -1,5 +1,5 @@
import { TSubscriptionModel } from '../../../definitions';
import database from '../../database';
import { TSubscriptionModel } from '../../definitions';
import database from '../database';
export default async function getRoom(rid: string): Promise<TSubscriptionModel> {
try {

View File

@ -4,7 +4,7 @@ import { MessageTypeLoad } from '../../constants/messageTypeLoad';
import { IMessage, TMessageModel } from '../../definitions';
import log from '../../utils/log';
import { getMessageById } from '../database/services/Message';
import roomTypeToApiType, { RoomTypes } from '../rocketchat/methods/roomTypeToApiType';
import roomTypeToApiType, { RoomTypes } from './roomTypeToApiType';
import sdk from '../rocketchat/services/sdk';
import { generateLoadMoreId } from '../utils';
import updateMessages from './updateMessages';

View File

@ -8,7 +8,7 @@ import { MessageTypeLoad } from '../../constants/messageTypeLoad';
import { generateLoadMoreId } from '../utils';
import updateMessages from './updateMessages';
import { TMessageModel } from '../../definitions';
import RocketChat from '../rocketchat';
import sdk from '../rocketchat/services/sdk';
const COUNT = 50;
@ -22,7 +22,7 @@ interface ILoadNextMessages {
export default function loadNextMessages(args: ILoadNextMessages): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const data = await RocketChat.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT);
const data = await sdk.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT);
let messages = EJSON.fromJSONValue(data?.messages);
messages = orderBy(messages, 'ts');
if (messages?.length) {

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