vn-verdnaturachat/app/containers/message/index.tsx

450 lines
11 KiB
TypeScript

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 { getMessageTranslation } from './utils';
import { TSupportedThemes, withTheme } from '../../theme';
import openLink from '../../utils/openLink';
import { IAttachment, TAnyMessageModel, TGetCustomEmoji } from '../../definitions';
import { IRoomInfoParam } from '../../views/SearchMessagesView';
import { E2E_MESSAGE_TYPE, E2E_STATUS, messagesStatus } from '../../lib/constants';
interface IMessageContainerProps {
item: TAnyMessageModel;
user: {
id: string;
username: string;
token: string;
};
msg?: string;
rid: string;
timeFormat?: string;
style?: ViewStyle;
archived?: boolean;
broadcast?: boolean;
previousItem?: TAnyMessageModel;
baseUrl: string;
Message_GroupingPeriod?: number;
isReadReceiptEnabled?: boolean;
isThreadRoom: boolean;
isSystemMessage?: boolean;
useRealName?: boolean;
autoTranslateRoom?: boolean;
autoTranslateLanguage?: string;
status?: number;
isIgnored?: boolean;
highlighted?: boolean;
getCustomEmoji: TGetCustomEmoji;
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?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
jumpToMessage?: (link: string) => void;
onPress?: () => void;
theme: TSupportedThemes;
}
interface IMessageContainerState {
isManualUnignored: boolean;
}
class MessageContainer extends React.Component<IMessageContainerProps, IMessageContainerState> {
static defaultProps = {
getCustomEmoji: () => null,
onLongPress: () => {},
callJitsi: () => {},
blockAction: () => {},
archived: false,
broadcast: false,
isIgnored: false,
theme: 'light' as TSupportedThemes
};
state = { isManualUnignored: false };
private subscription?: Subscription;
componentDidMount() {
const { item } = this.props;
if (item && item.observe) {
const observable = item.observe();
this.subscription = observable.subscribe(() => {
this.forceUpdate();
});
}
}
shouldComponentUpdate(nextProps: IMessageContainerProps, nextState: IMessageContainerState) {
const { isManualUnignored } = this.state;
const { threadBadgeColor, isIgnored, highlighted } = this.props;
if (nextProps.highlighted !== highlighted) {
return true;
}
if (nextProps.threadBadgeColor !== threadBadgeColor) {
return true;
}
if (nextProps.isIgnored !== isIgnored) {
return true;
}
if (nextState.isManualUnignored !== isManualUnignored) {
return true;
}
return false;
}
componentWillUnmount() {
if (this.subscription && this.subscription.unsubscribe) {
this.subscription.unsubscribe();
}
}
onPress = debounce(
() => {
const { onPress } = this.props;
if (this.isIgnored) {
return this.onIgnoredMessagePress();
}
if (onPress) {
return onPress();
}
const { item, isThreadRoom } = this.props;
Keyboard.dismiss();
if ((item.tlm || item.tmid) && !isThreadRoom) {
this.onThreadPress();
}
const { onDiscussionPress } = this.props;
if (item.dlm && onDiscussionPress) {
onDiscussionPress(item);
}
},
300,
true
);
onLongPress = () => {
const { archived, onLongPress, item } = this.props;
if (this.isInfo || this.hasError || this.isEncrypted || archived) {
return;
}
if (onLongPress) {
onLongPress(item);
}
};
onErrorPress = () => {
const { errorActionsShow, item } = this.props;
if (errorActionsShow) {
errorActionsShow(item);
}
};
onReactionPress = (emoji: string) => {
const { onReactionPress, item } = this.props;
if (onReactionPress) {
onReactionPress(emoji, item.id);
}
};
onReactionLongPress = () => {
const { onReactionLongPress, item } = this.props;
if (onReactionLongPress) {
onReactionLongPress(item);
}
};
onEncryptedPress = () => {
const { onEncryptedPress } = this.props;
if (onEncryptedPress) {
onEncryptedPress();
}
};
onDiscussionPress = () => {
const { onDiscussionPress, item } = this.props;
if (onDiscussionPress) {
onDiscussionPress(item);
}
};
onThreadPress = () => {
const { onThreadPress, item } = this.props;
if (onThreadPress) {
onThreadPress(item);
}
};
onAnswerButtonPress = (msg: string) => {
const { onAnswerButtonPress } = this.props;
if (onAnswerButtonPress) {
onAnswerButtonPress(msg, undefined, false);
}
};
onIgnoredMessagePress = () => {
this.setState({ isManualUnignored: true });
};
get isHeader(): boolean {
const { item, previousItem, broadcast, Message_GroupingPeriod } = this.props;
if (this.hasError || (previousItem && previousItem.status === messagesStatus.ERROR)) {
return true;
}
try {
if (
previousItem &&
// @ts-ignore TODO: IMessage vs IMessageFromServer non-sense
previousItem.ts.toDateString() === item.ts.toDateString() &&
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 &&
previousItem.tmid === item.tmid
) {
return false;
}
return true;
} catch (error) {
return true;
}
}
get isThreadReply(): boolean {
const { item, previousItem, isThreadRoom } = this.props;
if (isThreadRoom) {
return false;
}
if (previousItem && item.tmid && previousItem.tmid !== item.tmid && previousItem.id !== item.tmid) {
return true;
}
return false;
}
get isThreadSequential(): boolean {
const { item, isThreadRoom } = this.props;
if (isThreadRoom) {
return false;
}
return !!item.tmid;
}
get isEncrypted(): boolean {
const { item } = this.props;
const { t, e2e } = item;
return t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE;
}
get isInfo(): string | boolean {
const { item } = this.props;
if (['e2e', 'discussion-created'].includes(item.t)) {
return false;
}
return item.t;
}
get isTemp(): boolean {
const { item } = this.props;
return item.status === messagesStatus.TEMP || item.status === messagesStatus.ERROR;
}
get isIgnored(): boolean {
const { isManualUnignored } = this.state;
const { isIgnored } = this.props;
if (isManualUnignored) {
return false;
}
return isIgnored ?? false;
}
get hasError(): boolean {
const { item } = this.props;
return item.status === messagesStatus.ERROR;
}
reactionInit = () => {
const { reactionInit, item } = this.props;
if (reactionInit) {
reactionInit(item);
}
};
replyBroadcast = () => {
const { replyBroadcast, item } = this.props;
if (replyBroadcast) {
replyBroadcast(item);
}
};
onLinkPress = (link: string): void => {
const { item, jumpToMessage, theme } = this.props;
const isMessageLink = item?.attachments?.findIndex((att: IAttachment) => att?.message_link === link) !== -1;
if (isMessageLink && jumpToMessage) {
return jumpToMessage(link);
}
openLink(link, theme);
};
render() {
const {
item,
user,
style,
archived,
baseUrl,
useRealName,
broadcast,
fetchThreadName,
showAttachment,
timeFormat,
isReadReceiptEnabled,
autoTranslateRoom,
autoTranslateLanguage,
navToRoomInfo,
getCustomEmoji,
isThreadRoom,
callJitsi,
blockAction,
rid,
threadBadgeColor,
toggleFollowThread,
jumpToMessage,
highlighted
} = this.props;
const {
id,
msg,
ts,
attachments,
urls,
reactions,
t,
avatar,
emoji,
u,
alias,
editedBy,
role,
drid,
dcount,
dlm,
tmid,
tcount,
tlm,
tmsg,
mentions,
channels,
unread,
blocks,
autoTranslate: autoTranslateMessage,
replies,
md,
comment
} = item;
let message = msg;
// "autoTranslateRoom" and "autoTranslateLanguage" are properties from the subscription
// "autoTranslateMessage" is a toggle between "View Original" and "Translate" state
if (autoTranslateRoom && autoTranslateMessage && autoTranslateLanguage) {
message = getMessageTranslation(item, autoTranslateLanguage) || message;
}
return (
<MessageContext.Provider
value={{
user,
baseUrl,
onPress: this.onPress,
onLongPress: this.onLongPress,
reactionInit: this.reactionInit,
onErrorPress: this.onErrorPress,
replyBroadcast: this.replyBroadcast,
onReactionPress: this.onReactionPress,
onEncryptedPress: this.onEncryptedPress,
onDiscussionPress: this.onDiscussionPress,
onReactionLongPress: this.onReactionLongPress,
onLinkPress: this.onLinkPress,
onAnswerButtonPress: this.onAnswerButtonPress,
jumpToMessage,
threadBadgeColor,
toggleFollowThread,
replies
}}>
{/* @ts-ignore*/}
<Message
id={id}
msg={message}
md={md}
rid={rid}
author={u}
ts={ts}
type={t}
attachments={attachments}
blocks={blocks}
urls={urls}
reactions={reactions}
alias={alias}
avatar={avatar}
emoji={emoji}
timeFormat={timeFormat}
style={style}
archived={archived}
broadcast={broadcast}
useRealName={useRealName}
isReadReceiptEnabled={isReadReceiptEnabled}
unread={unread}
role={role}
drid={drid}
dcount={dcount}
dlm={dlm}
tmid={tmid}
tcount={tcount}
tlm={tlm}
tmsg={tmsg}
fetchThreadName={fetchThreadName}
mentions={mentions}
channels={channels}
isIgnored={this.isIgnored}
isEdited={(editedBy && !!editedBy.username) ?? false}
isHeader={this.isHeader}
isThreadReply={this.isThreadReply}
isThreadSequential={this.isThreadSequential}
isThreadRoom={isThreadRoom}
isInfo={this.isInfo}
isTemp={this.isTemp}
isEncrypted={this.isEncrypted}
hasError={this.hasError}
showAttachment={showAttachment}
getCustomEmoji={getCustomEmoji}
navToRoomInfo={navToRoomInfo}
callJitsi={callJitsi}
blockAction={blockAction}
highlighted={highlighted}
comment={comment}
/>
</MessageContext.Provider>
);
}
}
export default withTheme(MessageContainer);