Merge branch 'develop' into chore.dehydrate-login-methods-from-rocketchatjs
This commit is contained in:
commit
f8fe01bf6b
|
@ -144,7 +144,7 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode VERSIONCODE as Integer
|
||||
versionName "4.25.0"
|
||||
versionName "4.26.0"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
if (!isFoss) {
|
||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ERoomType } from '../definitions/ERoomType';
|
|||
import { ROOM } from './actionsTypes';
|
||||
|
||||
// TYPE RETURN RELATED
|
||||
type ISelected = Record<string, string>;
|
||||
type ISelected = string[];
|
||||
|
||||
export interface ITransferData {
|
||||
roomId: string;
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import { Action } from 'redux';
|
||||
|
||||
import { ISettings, TSettings } from '../reducers/settings';
|
||||
import { TSettingsState, TSupportedSettings, TSettingsValues } from '../reducers/settings';
|
||||
import { SETTINGS } from './actionsTypes';
|
||||
|
||||
interface IAddSettings extends Action {
|
||||
payload: ISettings;
|
||||
payload: TSettingsState;
|
||||
}
|
||||
|
||||
interface IUpdateSettings extends Action {
|
||||
payload: { id: string; value: TSettings };
|
||||
payload: { id: TSupportedSettings; value: TSettingsValues };
|
||||
}
|
||||
|
||||
export type IActionSettings = IAddSettings & IUpdateSettings;
|
||||
|
||||
export function addSettings(settings: ISettings): IAddSettings {
|
||||
export function addSettings(settings: TSettingsState): IAddSettings {
|
||||
return {
|
||||
type: SETTINGS.ADD,
|
||||
payload: settings
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSettings(id: string, value: TSettings): IUpdateSettings {
|
||||
export function updateSettings(id: TSupportedSettings, value: TSettingsValues): IUpdateSettings {
|
||||
return {
|
||||
type: SETTINGS.UPDATE,
|
||||
payload: { id, value }
|
||||
|
|
|
@ -206,4 +206,4 @@ export default {
|
|||
Canned_Responses_Enable: {
|
||||
type: 'valueAsBoolean'
|
||||
}
|
||||
};
|
||||
} as const;
|
||||
|
|
|
@ -15,19 +15,12 @@ import { showConfirmationAlert } from '../../utils/info';
|
|||
import { useActionSheet } from '../ActionSheet';
|
||||
import Header, { HEADER_HEIGHT } from './Header';
|
||||
import events from '../../utils/log/events';
|
||||
import { TMessageModel } from '../../definitions/IMessage';
|
||||
import { ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
|
||||
interface IMessageActions {
|
||||
room: {
|
||||
rid: string;
|
||||
autoTranslateLanguage: any;
|
||||
autoTranslate: any;
|
||||
reactWhenReadOnly: any;
|
||||
};
|
||||
tmid: string;
|
||||
user: {
|
||||
id: string | number;
|
||||
};
|
||||
export interface IMessageActions {
|
||||
room: TSubscriptionModel;
|
||||
tmid?: string;
|
||||
user: Pick<ILoggedUser, 'id'>;
|
||||
editInit: Function;
|
||||
reactionInit: Function;
|
||||
onReactionPress: Function;
|
||||
|
@ -270,8 +263,11 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handleToggleTranslation = async (message: TMessageModel) => {
|
||||
const handleToggleTranslation = async (message: TAnyMessageModel) => {
|
||||
try {
|
||||
if (!room.autoTranslateLanguage) {
|
||||
return;
|
||||
}
|
||||
const db = database.active;
|
||||
await db.write(async () => {
|
||||
await message.update(m => {
|
||||
|
@ -321,7 +317,7 @@ const MessageActions = React.memo(
|
|||
});
|
||||
};
|
||||
|
||||
const getOptions = (message: TMessageModel) => {
|
||||
const getOptions = (message: TAnyMessageModel) => {
|
||||
let options: any = [];
|
||||
|
||||
// Reply
|
||||
|
@ -447,7 +443,7 @@ const MessageActions = React.memo(
|
|||
return options;
|
||||
};
|
||||
|
||||
const showMessageActions = async (message: TMessageModel) => {
|
||||
const showMessageActions = async (message: TAnyMessageModel) => {
|
||||
logEvent(events.ROOM_SHOW_MSG_ACTIONS);
|
||||
await getPermissions();
|
||||
showActionSheet({
|
||||
|
|
|
@ -73,7 +73,7 @@ const videoPickerConfig = {
|
|||
mediaType: 'video'
|
||||
};
|
||||
|
||||
interface IMessageBoxProps {
|
||||
export interface IMessageBoxProps {
|
||||
rid: string;
|
||||
baseUrl: string;
|
||||
message: IMessage;
|
||||
|
|
|
@ -10,6 +10,7 @@ import sharedStyles from '../views/Styles';
|
|||
import { themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
import { TGetCustomEmoji } from '../definitions/IEmoji';
|
||||
import { TMessageModel, ILoggedUser } from '../definitions';
|
||||
import SafeAreaView from './SafeAreaView';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -65,23 +66,25 @@ interface IItem {
|
|||
usernames: any;
|
||||
emoji: string;
|
||||
};
|
||||
user?: { username: any };
|
||||
user?: Pick<ILoggedUser, 'username'>;
|
||||
baseUrl?: string;
|
||||
getCustomEmoji?: TGetCustomEmoji;
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
interface IModalContent {
|
||||
message?: {
|
||||
reactions: any;
|
||||
};
|
||||
message?: TMessageModel;
|
||||
onClose: Function;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
interface IReactionsModal {
|
||||
message?: any;
|
||||
user?: Pick<ILoggedUser, 'username'>;
|
||||
isVisible: boolean;
|
||||
onClose(): void;
|
||||
baseUrl: string;
|
||||
getCustomEmoji?: TGetCustomEmoji;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,9 +52,9 @@ interface IThreadDetails {
|
|||
|
||||
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style }: IThreadDetails): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
let { tcount } = item;
|
||||
if (tcount && tcount >= 1000) {
|
||||
tcount = '+999';
|
||||
let count: string | number | undefined = item.tcount;
|
||||
if (count && count >= 1000) {
|
||||
count = '+999';
|
||||
}
|
||||
|
||||
let replies: number | string = item?.replies?.length ?? 0;
|
||||
|
@ -70,7 +70,7 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style }: IT
|
|||
<View style={styles.detailContainer}>
|
||||
<CustomIcon name='threads' size={24} color={themes[theme!].auxiliaryText} />
|
||||
<Text style={[styles.detailText, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
|
||||
{tcount}
|
||||
{count}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ const Message = React.memo((props: IMessage) => {
|
|||
<View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}>
|
||||
<MessageInner {...props} />
|
||||
</View>
|
||||
<ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread} theme={props.theme} />
|
||||
<ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread || false} theme={props.theme} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -94,7 +94,7 @@ const styles = StyleSheet.create({
|
|||
|
||||
interface IMessageTitle {
|
||||
attachment: IAttachment;
|
||||
timeFormat: string;
|
||||
timeFormat?: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ interface IMessageFields {
|
|||
|
||||
interface IMessageReply {
|
||||
attachment: IAttachment;
|
||||
timeFormat: string;
|
||||
timeFormat?: string;
|
||||
index: number;
|
||||
theme: string;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
|
|
|
@ -40,7 +40,7 @@ const styles = StyleSheet.create({
|
|||
interface IMessageUser {
|
||||
isHeader?: boolean;
|
||||
hasError?: boolean;
|
||||
useRealName: boolean;
|
||||
useRealName?: boolean;
|
||||
author?: {
|
||||
_id: string;
|
||||
name?: string;
|
||||
|
@ -50,25 +50,26 @@ interface IMessageUser {
|
|||
ts?: Date;
|
||||
timeFormat?: string;
|
||||
theme: string;
|
||||
navToRoomInfo: Function;
|
||||
navToRoomInfo?: Function;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const User = React.memo(
|
||||
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, type, ...props }: IMessageUser) => {
|
||||
if (isHeader || hasError) {
|
||||
const navParam = {
|
||||
t: 'd',
|
||||
rid: author!._id
|
||||
};
|
||||
const { user } = useContext(MessageContext);
|
||||
const username = (useRealName && author!.name) || author!.username;
|
||||
const username = (useRealName && author?.name) || author?.username;
|
||||
const aliasUsername = alias ? (
|
||||
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
|
||||
) : null;
|
||||
const time = moment(ts).format(timeFormat);
|
||||
const onUserPress = () => navToRoomInfo(navParam);
|
||||
const isDisabled = author!._id === user.id;
|
||||
const onUserPress = () => {
|
||||
navToRoomInfo?.({
|
||||
t: 'd',
|
||||
rid: author?._id
|
||||
});
|
||||
};
|
||||
const isDisabled = author?._id === user.id;
|
||||
|
||||
const textContent = (
|
||||
<>
|
||||
|
|
|
@ -10,9 +10,10 @@ import messagesStatus from '../../constants/messagesStatus';
|
|||
import { withTheme } from '../../theme';
|
||||
import openLink from '../../utils/openLink';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { TAnyMessageModel } from '../../definitions';
|
||||
|
||||
interface IMessageContainerProps {
|
||||
item: any;
|
||||
item: TAnyMessageModel;
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
|
@ -20,24 +21,16 @@ interface IMessageContainerProps {
|
|||
};
|
||||
msg?: string;
|
||||
rid?: string;
|
||||
timeFormat: string;
|
||||
timeFormat?: string;
|
||||
style?: ViewStyle;
|
||||
archived?: boolean;
|
||||
broadcast?: boolean;
|
||||
previousItem?: {
|
||||
ts: any;
|
||||
u: any;
|
||||
groupable: any;
|
||||
id: string;
|
||||
tmid: string;
|
||||
status: any;
|
||||
};
|
||||
isHeader: boolean;
|
||||
previousItem?: TAnyMessageModel;
|
||||
baseUrl: string;
|
||||
Message_GroupingPeriod?: number;
|
||||
isReadReceiptEnabled?: boolean;
|
||||
isThreadRoom: boolean;
|
||||
useRealName: boolean;
|
||||
useRealName?: boolean;
|
||||
autoTranslateRoom?: boolean;
|
||||
autoTranslateLanguage?: string;
|
||||
status?: number;
|
||||
|
@ -59,11 +52,11 @@ interface IMessageContainerProps {
|
|||
callJitsi?: Function;
|
||||
blockAction?: Function;
|
||||
onAnswerButtonPress?: Function;
|
||||
theme: string;
|
||||
theme?: string;
|
||||
threadBadgeColor?: string;
|
||||
toggleFollowThread?: Function;
|
||||
jumpToMessage?: Function;
|
||||
onPress: Function;
|
||||
onPress?: Function;
|
||||
}
|
||||
|
||||
class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||
|
@ -222,7 +215,7 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
|||
this.setState({ isManualUnignored: true });
|
||||
};
|
||||
|
||||
get isHeader() {
|
||||
get isHeader(): boolean {
|
||||
const { item, previousItem, broadcast, Message_GroupingPeriod } = this.props;
|
||||
if (this.hasError || (previousItem && previousItem.status === messagesStatus.ERROR)) {
|
||||
return true;
|
||||
|
@ -230,9 +223,11 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
|||
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
|
||||
) {
|
||||
|
@ -244,7 +239,7 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
|||
}
|
||||
}
|
||||
|
||||
get isThreadReply() {
|
||||
get isThreadReply(): boolean {
|
||||
const { item, previousItem, isThreadRoom } = this.props;
|
||||
if (isThreadRoom) {
|
||||
return false;
|
||||
|
@ -255,37 +250,40 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
|||
return false;
|
||||
}
|
||||
|
||||
get isThreadSequential() {
|
||||
get isThreadSequential(): boolean {
|
||||
const { item, isThreadRoom } = this.props;
|
||||
if (isThreadRoom) {
|
||||
return false;
|
||||
}
|
||||
return item.tmid;
|
||||
return !!item.tmid;
|
||||
}
|
||||
|
||||
get isEncrypted() {
|
||||
get isEncrypted(): boolean {
|
||||
const { item } = this.props;
|
||||
const { t, e2e } = item;
|
||||
return t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE;
|
||||
}
|
||||
|
||||
get isInfo() {
|
||||
get isInfo(): boolean {
|
||||
const { item } = this.props;
|
||||
return SYSTEM_MESSAGES.includes(item.t);
|
||||
return (item.t && SYSTEM_MESSAGES.includes(item.t)) ?? false;
|
||||
}
|
||||
|
||||
get isTemp() {
|
||||
get isTemp(): boolean {
|
||||
const { item } = this.props;
|
||||
return item.status === messagesStatus.TEMP || item.status === messagesStatus.ERROR;
|
||||
}
|
||||
|
||||
get isIgnored() {
|
||||
get isIgnored(): boolean {
|
||||
const { isManualUnignored } = this.state;
|
||||
const { isIgnored } = this.props;
|
||||
return isManualUnignored ? false : isIgnored;
|
||||
if (isManualUnignored) {
|
||||
return false;
|
||||
}
|
||||
return isIgnored ?? false;
|
||||
}
|
||||
|
||||
get hasError() {
|
||||
get hasError(): boolean {
|
||||
const { item } = this.props;
|
||||
return item.status === messagesStatus.ERROR;
|
||||
}
|
||||
|
@ -405,13 +403,12 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
|||
rid={rid!}
|
||||
author={u}
|
||||
ts={ts}
|
||||
type={t}
|
||||
type={t as any}
|
||||
attachments={attachments}
|
||||
blocks={blocks}
|
||||
urls={urls}
|
||||
reactions={reactions}
|
||||
alias={alias}
|
||||
/* @ts-ignore*/
|
||||
avatar={avatar}
|
||||
emoji={emoji}
|
||||
timeFormat={timeFormat}
|
||||
|
@ -424,16 +421,19 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
|||
role={role}
|
||||
drid={drid}
|
||||
dcount={dcount}
|
||||
// @ts-ignore
|
||||
dlm={dlm}
|
||||
tmid={tmid}
|
||||
tcount={tcount}
|
||||
// @ts-ignore
|
||||
tlm={tlm}
|
||||
tmsg={tmsg}
|
||||
fetchThreadName={fetchThreadName!}
|
||||
// @ts-ignore
|
||||
mentions={mentions}
|
||||
channels={channels}
|
||||
isIgnored={this.isIgnored!}
|
||||
isEdited={editedBy && !!editedBy.username}
|
||||
isIgnored={this.isIgnored}
|
||||
isEdited={(editedBy && !!editedBy.username) ?? false}
|
||||
isHeader={this.isHeader}
|
||||
isThreadReply={this.isThreadReply}
|
||||
isThreadSequential={this.isThreadSequential}
|
||||
|
@ -447,7 +447,7 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
|||
navToRoomInfo={navToRoomInfo!}
|
||||
callJitsi={callJitsi!}
|
||||
blockAction={blockAction!}
|
||||
theme={theme}
|
||||
theme={theme as string}
|
||||
highlighted={highlighted!}
|
||||
/>
|
||||
</MessageContext.Provider>
|
||||
|
|
|
@ -7,7 +7,7 @@ export type TMessageType = 'discussion-created' | 'jitsi_call_started';
|
|||
|
||||
export interface IMessageAttachments {
|
||||
attachments: any;
|
||||
timeFormat: string;
|
||||
timeFormat?: string;
|
||||
showAttachment: Function;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
theme: string;
|
||||
|
@ -66,26 +66,26 @@ export interface IMessageContent {
|
|||
_id: string;
|
||||
isTemp: boolean;
|
||||
isInfo: boolean;
|
||||
tmid: string;
|
||||
tmid?: string;
|
||||
isThreadRoom: boolean;
|
||||
msg: string;
|
||||
md: MarkdownAST;
|
||||
msg?: string;
|
||||
md?: MarkdownAST;
|
||||
theme: string;
|
||||
isEdited: boolean;
|
||||
isEncrypted: boolean;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
channels: IUserChannel[];
|
||||
mentions: IUserMention[];
|
||||
navToRoomInfo: Function;
|
||||
useRealName: boolean;
|
||||
channels?: IUserChannel[];
|
||||
mentions?: IUserMention[];
|
||||
navToRoomInfo?: Function;
|
||||
useRealName?: boolean;
|
||||
isIgnored: boolean;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface IMessageDiscussion {
|
||||
msg: string;
|
||||
dcount: number;
|
||||
dlm: Date;
|
||||
msg?: string;
|
||||
dcount?: number;
|
||||
dlm?: Date;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
|
@ -98,10 +98,10 @@ export interface IMessageEmoji {
|
|||
}
|
||||
|
||||
export interface IMessageThread {
|
||||
msg: string;
|
||||
tcount: number;
|
||||
msg?: string;
|
||||
tcount?: number;
|
||||
theme: string;
|
||||
tlm: Date;
|
||||
tlm?: Date;
|
||||
isThreadRoom: boolean;
|
||||
id: string;
|
||||
}
|
||||
|
@ -123,8 +123,8 @@ export interface IMessageTouchable {
|
|||
}
|
||||
|
||||
export interface IMessageRepliedThread {
|
||||
tmid: string;
|
||||
tmsg: string;
|
||||
tmid?: string;
|
||||
tmsg?: string;
|
||||
id: string;
|
||||
isHeader: boolean;
|
||||
theme: string;
|
||||
|
@ -154,7 +154,7 @@ export interface IMessage extends IMessageRepliedThread, IMessageInner {
|
|||
style: any;
|
||||
onLongPress: Function;
|
||||
isReadReceiptEnabled: boolean;
|
||||
unread: boolean;
|
||||
unread?: boolean;
|
||||
theme: string;
|
||||
isIgnored: boolean;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@ import { TMessageModel } from '../../definitions/IMessage';
|
|||
import I18n from '../../i18n';
|
||||
import { DISCUSSION } from './constants';
|
||||
|
||||
export const formatMessageCount = (count: number, type: string) => {
|
||||
export const formatMessageCount = (count?: number, type?: string): string => {
|
||||
const discussion = type === DISCUSSION;
|
||||
let text = discussion ? I18n.t('No_messages_yet') : null;
|
||||
if (!count) {
|
||||
return text;
|
||||
}
|
||||
if (count === 1) {
|
||||
text = `${count} ${discussion ? I18n.t('message') : I18n.t('reply')}`;
|
||||
} else if (count > 1 && count < 1000) {
|
||||
|
|
|
@ -21,4 +21,10 @@ export interface IEmojiCategory {
|
|||
tabLabel: string;
|
||||
}
|
||||
|
||||
export type TGetCustomEmoji = (name: string) => IEmoji | null;
|
||||
// TODO: copied from reducers/customEmojis. We can unify later.
|
||||
export interface IReduxEmoji {
|
||||
name: string;
|
||||
extension: any;
|
||||
}
|
||||
|
||||
export type TGetCustomEmoji = (name: string) => any;
|
||||
|
|
|
@ -3,9 +3,25 @@ import { MarkdownAST } from '@rocket.chat/message-parser';
|
|||
|
||||
import { IAttachment } from './IAttachment';
|
||||
import { IReaction } from './IReaction';
|
||||
import {
|
||||
MESSAGE_TYPE_LOAD_MORE,
|
||||
MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK,
|
||||
MESSAGE_TYPE_LOAD_NEXT_CHUNK
|
||||
} from '../constants/messageTypeLoad';
|
||||
import { TThreadMessageModel } from './IThreadMessage';
|
||||
import { TThreadModel } from './IThread';
|
||||
import { IUrlFromServer } from './IUrl';
|
||||
|
||||
export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj';
|
||||
export type MessageType =
|
||||
| 'jitsi_call_started'
|
||||
| 'discussion-created'
|
||||
| 'e2e'
|
||||
| 'load_more'
|
||||
| 'rm'
|
||||
| 'uj'
|
||||
| typeof MESSAGE_TYPE_LOAD_MORE
|
||||
| typeof MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK
|
||||
| typeof MESSAGE_TYPE_LOAD_NEXT_CHUNK;
|
||||
|
||||
export interface IUserMessage {
|
||||
_id: string;
|
||||
|
@ -139,4 +155,5 @@ export interface IMessage extends IMessageFromServer {
|
|||
|
||||
export type TMessageModel = IMessage & Model;
|
||||
|
||||
export type TAnyMessageModel = TMessageModel | TThreadModel | TThreadMessageModel;
|
||||
export type TTypeMessages = IMessageFromServer | ILoadMoreMessage | IMessage;
|
||||
|
|
|
@ -12,7 +12,7 @@ export enum SubscriptionType {
|
|||
DIRECT = 'd',
|
||||
CHANNEL = 'c',
|
||||
OMNICHANNEL = 'l',
|
||||
E2E = 'e2e',
|
||||
E2E = 'e2e', // FIXME: this is not a type of subscription
|
||||
THREAD = 'thread' // FIXME: this is not a type of subscription
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ export interface ISubscription {
|
|||
_updatedAt?: string; // from server
|
||||
v?: IVisitor;
|
||||
f: boolean;
|
||||
t: SubscriptionType;
|
||||
t: string; // TODO: we need to review this type later
|
||||
ts: string | Date;
|
||||
ls: Date;
|
||||
name: string;
|
||||
|
@ -73,14 +73,15 @@ export interface ISubscription {
|
|||
lastThreadSync?: Date;
|
||||
jitsiTimeout?: number;
|
||||
autoTranslate?: boolean;
|
||||
autoTranslateLanguage: string;
|
||||
lastMessage?: ILastMessage;
|
||||
autoTranslateLanguage?: string;
|
||||
lastMessage?: ILastMessage; // TODO: we need to use IMessage here
|
||||
hideUnreadStatus?: boolean;
|
||||
sysMes?: string[] | boolean;
|
||||
uids?: string[];
|
||||
usernames?: string[];
|
||||
visitor?: IVisitor;
|
||||
departmentId?: string;
|
||||
status?: string;
|
||||
servedBy?: IServedBy;
|
||||
livechatData?: any;
|
||||
tags?: string[];
|
||||
|
|
|
@ -2,8 +2,7 @@ import Model from '@nozbe/watermelondb/Model';
|
|||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
|
||||
import { IAttachment } from './IAttachment';
|
||||
import { IEditedBy, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage';
|
||||
import { IReaction } from './IReaction';
|
||||
import { IMessage, IUserChannel, IUserMention, IUserMessage } from './IMessage';
|
||||
import { IUrl } from './IUrl';
|
||||
|
||||
interface IFileThread {
|
||||
|
@ -34,42 +33,9 @@ export interface IThreadResult {
|
|||
tlm?: string | Date;
|
||||
}
|
||||
|
||||
export interface IThread {
|
||||
id: string;
|
||||
export interface IThread extends IMessage {
|
||||
tmsg?: string;
|
||||
msg?: string;
|
||||
t?: MessageType;
|
||||
rid: string;
|
||||
_updatedAt?: string | Date;
|
||||
ts?: string | Date;
|
||||
u?: IUserMessage;
|
||||
alias?: string;
|
||||
parseUrls?: boolean;
|
||||
groupable?: boolean;
|
||||
avatar?: string;
|
||||
emoji?: string;
|
||||
attachments?: IAttachment[];
|
||||
urls?: IUrl[];
|
||||
status?: number;
|
||||
pinned?: boolean;
|
||||
starred?: boolean;
|
||||
editedBy?: IEditedBy;
|
||||
reactions?: IReaction[];
|
||||
role?: string;
|
||||
drid?: string;
|
||||
dcount?: number | string;
|
||||
dlm?: string | Date;
|
||||
tmid?: string;
|
||||
tcount?: number | string;
|
||||
tlm?: string | Date;
|
||||
replies?: string[];
|
||||
mentions?: IUserMention[];
|
||||
channels?: IUserChannel[];
|
||||
unread?: boolean;
|
||||
autoTranslate?: boolean;
|
||||
translations?: any;
|
||||
e2e?: string;
|
||||
subscription?: { id: string };
|
||||
draftMessage?: string;
|
||||
}
|
||||
|
||||
export type TThreadModel = IThread & Model;
|
||||
|
|
|
@ -1,47 +1,9 @@
|
|||
import Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
import { IAttachment } from './IAttachment';
|
||||
import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage';
|
||||
import { IReaction } from './IReaction';
|
||||
import { IUrl } from './IUrl';
|
||||
import { IMessage } from './IMessage';
|
||||
|
||||
export interface IThreadMessage {
|
||||
id: string;
|
||||
_id: string;
|
||||
export interface IThreadMessage extends IMessage {
|
||||
tmsg?: string;
|
||||
msg?: string;
|
||||
t?: MessageType;
|
||||
rid: string;
|
||||
ts: string | Date;
|
||||
u: IUserMessage;
|
||||
alias?: string;
|
||||
parseUrls?: boolean;
|
||||
groupable?: boolean;
|
||||
avatar?: string;
|
||||
emoji?: string;
|
||||
attachments?: IAttachment[];
|
||||
urls?: IUrl[];
|
||||
_updatedAt?: string | Date;
|
||||
status?: number;
|
||||
pinned?: boolean;
|
||||
starred?: boolean;
|
||||
editedBy?: IEditedBy;
|
||||
reactions?: IReaction[];
|
||||
role?: string;
|
||||
drid?: string;
|
||||
dcount?: number;
|
||||
dlm?: string | Date;
|
||||
tmid?: string;
|
||||
tcount?: number;
|
||||
tlm?: string | Date;
|
||||
replies?: string[];
|
||||
mentions?: IUserMention[];
|
||||
channels?: IUserChannel[];
|
||||
unread?: boolean;
|
||||
autoTranslate?: boolean;
|
||||
translations?: ITranslations[];
|
||||
e2e?: string;
|
||||
subscription?: { id: string };
|
||||
}
|
||||
|
||||
export type TThreadMessageModel = IThreadMessage & Model;
|
||||
|
|
|
@ -31,6 +31,7 @@ export interface IBaseScreen<T extends Record<string, object | undefined>, S ext
|
|||
route: RouteProp<T, S>;
|
||||
dispatch: Dispatch;
|
||||
theme: string;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
export * from './redux';
|
||||
|
|
|
@ -28,15 +28,15 @@ import { IRoles } from '../../reducers/roles';
|
|||
import { IRoom } from '../../reducers/room';
|
||||
import { ISelectedUsers } from '../../reducers/selectedUsers';
|
||||
import { IServer } from '../../reducers/server';
|
||||
import { ISettings } from '../../reducers/settings';
|
||||
import { TSettingsState } from '../../reducers/settings';
|
||||
import { IShare } from '../../reducers/share';
|
||||
import { IPermissionsState } from '../../reducers/permissions';
|
||||
import { IEnterpriseModules } from '../../reducers/enterpriseModules';
|
||||
|
||||
export interface IApplicationState {
|
||||
settings: ISettings;
|
||||
meteor: IConnect;
|
||||
settings: TSettingsState;
|
||||
login: ILogin;
|
||||
meteor: IConnect;
|
||||
server: IServer;
|
||||
selectedUsers: ISelectedUsers;
|
||||
app: IApp;
|
||||
|
|
|
@ -75,4 +75,6 @@ export default class Thread extends Model {
|
|||
@json('translations', sanitizer) translations;
|
||||
|
||||
@field('e2e') e2e;
|
||||
|
||||
@field('draft_message') draftMessage;
|
||||
}
|
||||
|
|
|
@ -199,6 +199,15 @@ export default schemaMigrations({
|
|||
columns: [{ name: 'md', type: 'string', isOptional: true }]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 15,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'threads',
|
||||
columns: [{ name: 'draft_message', type: 'string', isOptional: true }]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 14,
|
||||
version: 15,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'subscriptions',
|
||||
|
@ -153,7 +153,8 @@ export default appSchema({
|
|||
{ name: 'unread', type: 'boolean', isOptional: true },
|
||||
{ name: 'auto_translate', type: 'boolean', isOptional: true },
|
||||
{ name: 'translations', type: 'string', isOptional: true },
|
||||
{ name: 'e2e', type: 'string', isOptional: true }
|
||||
{ name: 'e2e', type: 'string', isOptional: true },
|
||||
{ name: 'draft_message', type: 'string', isOptional: true }
|
||||
]
|
||||
}),
|
||||
tableSchema({
|
||||
|
|
|
@ -10,7 +10,7 @@ export const E2E_BANNER_TYPE = {
|
|||
REQUEST_PASSWORD: 'REQUEST_PASSWORD',
|
||||
SAVE_PASSWORD: 'SAVE_PASSWORD'
|
||||
};
|
||||
export const E2E_ROOM_TYPES = {
|
||||
export const E2E_ROOM_TYPES: Record<string, string> = {
|
||||
d: 'd',
|
||||
p: 'p'
|
||||
};
|
||||
|
|
|
@ -43,7 +43,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name:
|
|||
if ((type === ERoomTypes.CHANNEL || type === ERoomTypes.GROUP) && !rid) {
|
||||
// RC 0.72.0
|
||||
// @ts-ignore
|
||||
const result: any = await sdk.get(`channel.info`, params);
|
||||
const result: any = await sdk.get(`${restTypes[type]}.info`, params);
|
||||
if (result.success) {
|
||||
const room = result[type];
|
||||
room.rid = room._id;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IRoom } from '../../definitions';
|
||||
import { IRoom, SubscriptionType } from '../../definitions';
|
||||
import { getSubscriptionByRoomId } from '../database/services/Subscription';
|
||||
import RocketChat from '../rocketchat';
|
||||
|
||||
|
@ -10,7 +10,7 @@ const getRoomInfo = async (rid: string): Promise<Pick<IRoom, 'rid' | 'name' | 'f
|
|||
rid,
|
||||
name: result.name,
|
||||
fname: result.fname,
|
||||
t: result.t
|
||||
t: result.t as SubscriptionType
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import updateMessages from './updateMessages';
|
|||
|
||||
const COUNT = 50;
|
||||
|
||||
async function load({ rid: roomId, latest, t }: { rid: string; latest?: string; t: RoomTypes }) {
|
||||
async function load({ rid: roomId, latest, t }: { rid: string; latest?: Date; t: RoomTypes }) {
|
||||
let params = { roomId, count: COUNT } as { roomId: string; count: number; latest?: string };
|
||||
if (latest) {
|
||||
params = { ...params, latest: new Date(latest).toISOString() };
|
||||
|
@ -32,9 +32,9 @@ async function load({ rid: roomId, latest, t }: { rid: string; latest?: string;
|
|||
export default function loadMessagesForRoom(args: {
|
||||
rid: string;
|
||||
t: RoomTypes;
|
||||
latest: string;
|
||||
loaderItem: TMessageModel;
|
||||
}): Promise<Partial<IMessage>[]> {
|
||||
latest?: Date;
|
||||
loaderItem?: TMessageModel;
|
||||
}): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const data: Partial<IMessage>[] = await load(args);
|
||||
|
@ -52,9 +52,9 @@ export default function loadMessagesForRoom(args: {
|
|||
data.push(loadMoreMessage);
|
||||
}
|
||||
await updateMessages({ rid: args.rid, update: data, loaderItem: args.loaderItem });
|
||||
return resolve(data);
|
||||
return resolve();
|
||||
}
|
||||
return resolve([]);
|
||||
return resolve();
|
||||
} catch (e) {
|
||||
log(e);
|
||||
reject(e);
|
||||
|
|
|
@ -16,7 +16,7 @@ const getLastUpdate = async (rid: string) => {
|
|||
return null;
|
||||
};
|
||||
|
||||
async function load({ rid: roomId, lastOpen }: { rid: string; lastOpen: string }) {
|
||||
async function load({ rid: roomId, lastOpen }: { rid: string; lastOpen: Date }) {
|
||||
let lastUpdate;
|
||||
if (lastOpen) {
|
||||
lastUpdate = new Date(lastOpen).toISOString();
|
||||
|
@ -29,7 +29,7 @@ async function load({ rid: roomId, lastOpen }: { rid: string; lastOpen: string }
|
|||
return result;
|
||||
}
|
||||
|
||||
export default function loadMissedMessages(args: { rid: string; lastOpen: string }): Promise<void> {
|
||||
export default function loadMissedMessages(args: { rid: string; lastOpen: Date }): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const data = await load({ rid: args.rid, lastOpen: args.lastOpen });
|
||||
|
|
|
@ -7,19 +7,19 @@ import { getMessageById } from '../database/services/Message';
|
|||
import { MESSAGE_TYPE_LOAD_NEXT_CHUNK } from '../../constants/messageTypeLoad';
|
||||
import { generateLoadMoreId } from '../utils';
|
||||
import updateMessages from './updateMessages';
|
||||
import { IMessage, TMessageModel } from '../../definitions';
|
||||
import { TMessageModel } from '../../definitions';
|
||||
import RocketChat from '../rocketchat';
|
||||
|
||||
const COUNT = 50;
|
||||
|
||||
interface ILoadNextMessages {
|
||||
rid: string;
|
||||
ts: string;
|
||||
tmid: string;
|
||||
ts: Date;
|
||||
tmid?: string;
|
||||
loaderItem: TMessageModel;
|
||||
}
|
||||
|
||||
export default function loadNextMessages(args: ILoadNextMessages): Promise<IMessage | []> {
|
||||
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);
|
||||
|
@ -39,9 +39,9 @@ export default function loadNextMessages(args: ILoadNextMessages): Promise<IMess
|
|||
messages.push(loadMoreItem);
|
||||
}
|
||||
await updateMessages({ rid: args.rid, update: messages, loaderItem: args.loaderItem });
|
||||
return resolve(messages);
|
||||
return resolve();
|
||||
}
|
||||
return resolve([]);
|
||||
return resolve();
|
||||
} catch (e) {
|
||||
log(e);
|
||||
reject(e);
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import EJSON from 'ejson';
|
||||
|
||||
import database from '../database';
|
||||
import log from '../../utils/log';
|
||||
import { Encryption } from '../encryption';
|
||||
import protectedFunction from './helpers/protectedFunction';
|
||||
import buildMessage from './helpers/buildMessage';
|
||||
|
||||
async function load({ tmid }) {
|
||||
try {
|
||||
// RC 1.0
|
||||
const result = await this.methodCallWrapper('getThreadMessages', { tmid });
|
||||
if (!result) {
|
||||
return [];
|
||||
}
|
||||
return EJSON.fromJSONValue(result);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default function loadThreadMessages({ tmid, rid }) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let data = await load.call(this, { tmid });
|
||||
if (data && data.length) {
|
||||
try {
|
||||
data = data.filter(m => m.tmid).map(m => buildMessage(m));
|
||||
data = await Encryption.decryptMessages(data);
|
||||
const db = database.active;
|
||||
const threadMessagesCollection = db.get('thread_messages');
|
||||
const allThreadMessagesRecords = await threadMessagesCollection.query(Q.where('rid', tmid)).fetch();
|
||||
let threadMessagesToCreate = data.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id));
|
||||
let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => data.find(i2 => i1.id === i2._id));
|
||||
|
||||
threadMessagesToCreate = threadMessagesToCreate.map(threadMessage =>
|
||||
threadMessagesCollection.prepareCreate(
|
||||
protectedFunction(tm => {
|
||||
tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema);
|
||||
Object.assign(tm, threadMessage);
|
||||
tm.subscription.id = rid;
|
||||
tm.rid = threadMessage.tmid;
|
||||
delete threadMessage.tmid;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
threadMessagesToUpdate = threadMessagesToUpdate.map(threadMessage => {
|
||||
const newThreadMessage = data.find(t => t._id === threadMessage.id);
|
||||
return threadMessage.prepareUpdate(
|
||||
protectedFunction(tm => {
|
||||
Object.assign(tm, newThreadMessage);
|
||||
tm.rid = threadMessage.tmid;
|
||||
delete threadMessage.tmid;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await db.action(async () => {
|
||||
await db.batch(...threadMessagesToCreate, ...threadMessagesToUpdate);
|
||||
});
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
return resolve(data);
|
||||
} else {
|
||||
return resolve([]);
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import EJSON from 'ejson';
|
||||
|
||||
import database from '../database';
|
||||
import log from '../../utils/log';
|
||||
import { Encryption } from '../encryption';
|
||||
import protectedFunction from './helpers/protectedFunction';
|
||||
import buildMessage from './helpers/buildMessage';
|
||||
import { TThreadMessageModel } from '../../definitions';
|
||||
import sdk from '../rocketchat/services/sdk';
|
||||
|
||||
async function load({ tmid }: { tmid: string }) {
|
||||
try {
|
||||
// RC 1.0
|
||||
const result = await sdk.methodCallWrapper('getThreadMessages', { tmid });
|
||||
if (!result) {
|
||||
return [];
|
||||
}
|
||||
return EJSON.fromJSONValue(result);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default function loadThreadMessages({ tmid, rid }: { tmid: string; rid: string }) {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
let data = await load({ tmid });
|
||||
if (data && data.length) {
|
||||
try {
|
||||
data = data.filter((m: TThreadMessageModel) => m.tmid).map((m: TThreadMessageModel) => buildMessage(m));
|
||||
data = await Encryption.decryptMessages(data);
|
||||
const db = database.active;
|
||||
const threadMessagesCollection = db.get('thread_messages');
|
||||
const allThreadMessagesRecords = await threadMessagesCollection.query(Q.where('rid', tmid)).fetch();
|
||||
const filterThreadMessagesToCreate = data.filter(
|
||||
(i1: TThreadMessageModel) => !allThreadMessagesRecords.find(i2 => i1._id === i2.id)
|
||||
);
|
||||
const filterThreadMessagesToUpdate = allThreadMessagesRecords.filter(i1 =>
|
||||
data.find((i2: TThreadMessageModel) => i1.id === i2._id)
|
||||
);
|
||||
|
||||
const threadMessagesToCreate = filterThreadMessagesToCreate.map((threadMessage: TThreadMessageModel) =>
|
||||
threadMessagesCollection.prepareCreate(
|
||||
protectedFunction((tm: TThreadMessageModel) => {
|
||||
tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema);
|
||||
Object.assign(tm, threadMessage);
|
||||
if (tm.subscription) {
|
||||
tm.subscription.id = rid;
|
||||
}
|
||||
if (threadMessage.tmid) {
|
||||
tm.rid = threadMessage.tmid;
|
||||
}
|
||||
delete threadMessage.tmid;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const threadMessagesToUpdate = filterThreadMessagesToUpdate.map(threadMessage => {
|
||||
const newThreadMessage = data.find((t: TThreadMessageModel) => t._id === threadMessage.id);
|
||||
return threadMessage.prepareUpdate(
|
||||
protectedFunction((tm: TThreadMessageModel) => {
|
||||
Object.assign(tm, newThreadMessage);
|
||||
if (threadMessage.tmid) {
|
||||
tm.rid = threadMessage.tmid;
|
||||
}
|
||||
delete threadMessage.tmid;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await db.write(async () => {
|
||||
await db.batch(...threadMessagesToCreate, ...threadMessagesToUpdate);
|
||||
});
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
return resolve(data);
|
||||
}
|
||||
return resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
import database from '../database';
|
||||
import log from '../../utils/log';
|
||||
import { TSubscriptionModel } from '../../definitions';
|
||||
import { IRocketChat } from '../../definitions/IRocketChat';
|
||||
import sdk from '../rocketchat/services/sdk';
|
||||
|
||||
export default async function readMessages(this: IRocketChat, rid: string, ls: Date, updateLastOpen = false): Promise<void> {
|
||||
export default async function readMessages(rid: string, ls: Date, updateLastOpen = false): Promise<void> {
|
||||
try {
|
||||
const db = database.active;
|
||||
const subscription = await db.get('subscriptions').find(rid);
|
||||
|
||||
// RC 0.61.0
|
||||
await this.sdk.post('subscriptions.read', { rid });
|
||||
// @ts-ignore
|
||||
await sdk.post('subscriptions.read', { rid });
|
||||
|
||||
await db.write(async () => {
|
||||
try {
|
||||
|
|
|
@ -37,9 +37,9 @@ export async function cancelUpload(item: TUploadModel): Promise<void> {
|
|||
export function sendFileMessage(
|
||||
rid: string,
|
||||
fileInfo: IUpload,
|
||||
tmid: string,
|
||||
tmid: string | undefined,
|
||||
server: string,
|
||||
user: IUser
|
||||
user: Partial<Pick<IUser, 'id' | 'token'>>
|
||||
): Promise<FetchBlobResponse | void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
|
|
|
@ -85,7 +85,13 @@ export async function resendMessage(message: TMessageModel, tmid?: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export default async function (rid: string, msg: string, tmid: string, user: IUser, tshow?: boolean) {
|
||||
export default async function (
|
||||
rid: string,
|
||||
msg: string,
|
||||
tmid: string | undefined,
|
||||
user: Partial<Pick<IUser, 'id' | 'username' | 'name'>>,
|
||||
tshow?: boolean
|
||||
): Promise<void> {
|
||||
try {
|
||||
const db = database.active;
|
||||
const subsCollection = db.get('subscriptions');
|
||||
|
|
|
@ -105,7 +105,9 @@ export default async function updateMessages({
|
|||
threadCollection.prepareCreate(
|
||||
protectedFunction((t: TThreadModel) => {
|
||||
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
|
||||
if (t.subscription) t.subscription.id = sub.id;
|
||||
if (t.subscription) {
|
||||
t.subscription.id = sub.id;
|
||||
}
|
||||
Object.assign(t, thread);
|
||||
})
|
||||
)
|
||||
|
|
|
@ -885,7 +885,7 @@ const RocketChat = {
|
|||
}
|
||||
const autoTranslatePermission = reduxStore.getState().permissions['auto-translate'];
|
||||
const userRoles = reduxStore.getState().login?.user?.roles ?? [];
|
||||
return autoTranslatePermission?.some(role => userRoles.includes(role));
|
||||
return autoTranslatePermission?.some(role => userRoles.includes(role)) ?? false;
|
||||
} catch (e) {
|
||||
log(e);
|
||||
return false;
|
||||
|
|
|
@ -247,7 +247,7 @@ export const convertTeamToChannel = ({ teamId, selected }: { teamId: string; sel
|
|||
return sdk.post('teams.convertToChannel', params);
|
||||
};
|
||||
|
||||
export const joinRoom = (roomId: string, joinCode: string, type: 'c' | 'p'): any => {
|
||||
export const joinRoom = (roomId: string, joinCode: string | null, type: 'c' | 'p'): any => {
|
||||
// TODO: join code
|
||||
// RC 0.48.0
|
||||
if (type === 'p') {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { addSettings, clearSettings, updateSettings } from '../actions/settings';
|
||||
import { mockedStore } from './mockedStore';
|
||||
import { initialState } from './settings';
|
||||
import { initialState, TSettingsState } from './settings';
|
||||
|
||||
describe('test settings reducer', () => {
|
||||
it('should return initial state', () => {
|
||||
|
@ -8,7 +8,11 @@ describe('test settings reducer', () => {
|
|||
expect(state).toEqual(initialState);
|
||||
});
|
||||
|
||||
const settings = { API_Use_REST_For_DDP_Calls: true, FileUpload_MaxFileSize: 600857600, Jitsi_URL_Room_Prefix: 'RocketChat' };
|
||||
const settings: TSettingsState = {
|
||||
API_Use_REST_For_DDP_Calls: true,
|
||||
FileUpload_MaxFileSize: 600857600,
|
||||
Jitsi_URL_Room_Prefix: 'RocketChat'
|
||||
};
|
||||
|
||||
it('should return modified store after call addSettings action', () => {
|
||||
mockedStore.dispatch(addSettings(settings));
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import { IActionSettings } from '../actions/settings';
|
||||
import { SETTINGS } from '../actions/actionsTypes';
|
||||
import settings from '../constants/settings';
|
||||
|
||||
export type TSettings = string | number | boolean;
|
||||
export type TSupportedSettings = keyof typeof settings;
|
||||
export type TSettingsValues = string | number | boolean | string[];
|
||||
|
||||
export type ISettings = Record<string, TSettings>;
|
||||
export type TSettingsState = {
|
||||
[K in TSupportedSettings]?: TSettingsValues;
|
||||
};
|
||||
|
||||
export const initialState: ISettings = {};
|
||||
export const initialState: TSettingsState = {};
|
||||
|
||||
export default (state = initialState, action: IActionSettings): ISettings => {
|
||||
export default (state = initialState, action: IActionSettings): TSettingsState => {
|
||||
switch (action.type) {
|
||||
case SETTINGS.ADD:
|
||||
return {
|
||||
|
|
|
@ -19,34 +19,37 @@ export type ChatsStackParamList = {
|
|||
NewMessageStackNavigator: any;
|
||||
NewMessageStack: undefined;
|
||||
RoomsListView: undefined;
|
||||
RoomView?: {
|
||||
rid: string;
|
||||
t: SubscriptionType;
|
||||
tmid?: string;
|
||||
message?: string;
|
||||
name?: string;
|
||||
fname?: string;
|
||||
prid?: string;
|
||||
room?: ISubscription;
|
||||
jumpToMessageId?: string;
|
||||
jumpToThreadId?: string;
|
||||
roomUserId?: string;
|
||||
};
|
||||
RoomView:
|
||||
| {
|
||||
rid: string;
|
||||
t: SubscriptionType;
|
||||
tmid?: string;
|
||||
message?: object; // TODO: TMessageModel?
|
||||
name?: string;
|
||||
fname?: string;
|
||||
prid?: string;
|
||||
room?: TSubscriptionModel | { rid: string; t: string; name?: string; fname?: string; prid?: string };
|
||||
jumpToMessageId?: string;
|
||||
jumpToThreadId?: string;
|
||||
roomUserId?: string | null;
|
||||
usedCannedResponse?: string;
|
||||
}
|
||||
| undefined; // Navigates back to RoomView already on stack
|
||||
RoomActionsView: {
|
||||
room: ISubscription;
|
||||
room: TSubscriptionModel;
|
||||
member: any;
|
||||
rid: string;
|
||||
t: SubscriptionType;
|
||||
joined: boolean;
|
||||
};
|
||||
SelectListView: {
|
||||
data: IRoom[];
|
||||
data?: IRoom[];
|
||||
title: string;
|
||||
infoText: string;
|
||||
infoText?: string;
|
||||
nextAction: (selected: string[]) => void;
|
||||
showAlert: () => void;
|
||||
isSearch: boolean;
|
||||
onSearch: (text: string) => Partial<IRoom[]>;
|
||||
showAlert?: () => void;
|
||||
isSearch?: boolean;
|
||||
onSearch?: (text: string) => Promise<Partial<IRoom[]> | any>;
|
||||
isRadio?: boolean;
|
||||
};
|
||||
RoomInfoView: {
|
||||
|
@ -240,7 +243,7 @@ export type InsideStackParamList = {
|
|||
isShareExtension: boolean;
|
||||
serverInfo: IServer;
|
||||
text: string;
|
||||
room: ISubscription;
|
||||
room: TSubscriptionModel;
|
||||
thread: any; // TODO: Change
|
||||
};
|
||||
ModalBlockView: {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ChatsStackParamList } from '../stacks/types';
|
||||
import Navigation from '../lib/Navigation';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import { IVisitor, SubscriptionType } from '../definitions/ISubscription';
|
||||
import { ISubscription, IVisitor, SubscriptionType, TSubscriptionModel } from '../definitions/ISubscription';
|
||||
|
||||
export interface IGoRoomItem {
|
||||
interface IGoRoomItem {
|
||||
search?: boolean; // comes from spotlight
|
||||
username?: string;
|
||||
t?: SubscriptionType;
|
||||
|
@ -13,12 +13,14 @@ export interface IGoRoomItem {
|
|||
visitor?: IVisitor;
|
||||
}
|
||||
|
||||
export type TGoRoomItem = IGoRoomItem | TSubscriptionModel | ISubscription;
|
||||
|
||||
const navigate = ({
|
||||
item,
|
||||
isMasterDetail,
|
||||
...props
|
||||
}: {
|
||||
item: IGoRoomItem;
|
||||
item: TGoRoomItem;
|
||||
isMasterDetail: boolean;
|
||||
navigationMethod?: () => ChatsStackParamList;
|
||||
}) => {
|
||||
|
@ -45,13 +47,13 @@ export const goRoom = async ({
|
|||
isMasterDetail = false,
|
||||
...props
|
||||
}: {
|
||||
item: IGoRoomItem;
|
||||
item: TGoRoomItem;
|
||||
isMasterDetail: boolean;
|
||||
navigationMethod?: any;
|
||||
jumpToMessageId?: string;
|
||||
usedCannedResponse?: string;
|
||||
}): Promise<void> => {
|
||||
if (item.t === SubscriptionType.DIRECT && item?.search) {
|
||||
if (!('id' in item) && item.t === SubscriptionType.DIRECT && item?.search) {
|
||||
// if user is using the search we need first to join/create room
|
||||
try {
|
||||
const { username } = item;
|
||||
|
|
|
@ -9,21 +9,18 @@ const canPostReadOnly = async ({ rid }: { rid: string }) => {
|
|||
return permission[0];
|
||||
};
|
||||
|
||||
const isMuted = (room: ISubscription, user: { username: string }) =>
|
||||
room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username);
|
||||
const isMuted = (room: Partial<ISubscription>, username: string) =>
|
||||
room && room.muted && room.muted.find && !!room.muted.find(m => m === username);
|
||||
|
||||
export const isReadOnly = async (
|
||||
room: ISubscription,
|
||||
user: { id?: string; username: string; token?: string }
|
||||
): Promise<boolean> => {
|
||||
export const isReadOnly = async (room: Partial<ISubscription>, username: string): Promise<boolean> => {
|
||||
if (room.archived) {
|
||||
return true;
|
||||
}
|
||||
if (isMuted(room, user)) {
|
||||
if (isMuted(room, username)) {
|
||||
return true;
|
||||
}
|
||||
if (room?.ro) {
|
||||
const allowPost = await canPostReadOnly(room);
|
||||
const allowPost = await canPostReadOnly({ rid: room.rid as string });
|
||||
if (allowPost) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ import moment from 'moment';
|
|||
import { themes } from '../constants/colors';
|
||||
import I18n from '../i18n';
|
||||
import { IAttachment } from '../definitions/IAttachment';
|
||||
import { ISubscription, SubscriptionType } from '../definitions/ISubscription';
|
||||
import { SubscriptionType, TSubscriptionModel } from '../definitions/ISubscription';
|
||||
|
||||
export const isBlocked = (room: ISubscription): boolean => {
|
||||
export const isBlocked = (room: TSubscriptionModel): boolean => {
|
||||
if (room) {
|
||||
const { t, blocked, blocker } = room;
|
||||
if (t === SubscriptionType.DIRECT && (blocked || blocker)) {
|
||||
|
@ -62,4 +62,4 @@ export const getBadgeColor = ({
|
|||
export const makeThreadName = (messageRecord: { id?: string; msg?: string; attachments?: IAttachment[] }): string | undefined =>
|
||||
messageRecord.msg || messageRecord?.attachments?.[0]?.title;
|
||||
|
||||
export const isTeamRoom = ({ teamId, joined }: { teamId: string; joined: boolean }): boolean => !!teamId && joined;
|
||||
export const isTeamRoom = ({ teamId, joined }: { teamId?: string; joined?: boolean }): boolean => (!!teamId && joined) ?? false;
|
||||
|
|
|
@ -115,7 +115,7 @@ const CannedResponseDetail = ({ navigation, route }: ICannedResponseDetailProps)
|
|||
t: room.t,
|
||||
fname: name
|
||||
}),
|
||||
t: room.t,
|
||||
t: room.t as any,
|
||||
roomUserId: RocketChat.getUidDirectMessage(room),
|
||||
usedCannedResponse: item.text
|
||||
};
|
||||
|
|
|
@ -118,7 +118,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
|
|||
t: room.t,
|
||||
fname: name
|
||||
}),
|
||||
t: room.t,
|
||||
t: room.t as any,
|
||||
roomUserId: RocketChat.getUidDirectMessage(room),
|
||||
usedCannedResponse: item.text
|
||||
};
|
||||
|
|
|
@ -37,10 +37,9 @@ interface IDiscussionDetails {
|
|||
|
||||
const DiscussionDetails = ({ item, date }: IDiscussionDetails): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
let { dcount } = item;
|
||||
|
||||
if (dcount && dcount >= 1000) {
|
||||
dcount = '+999';
|
||||
let count: string | number | undefined = item.dcount;
|
||||
if (count && count >= 1000) {
|
||||
count = '+999';
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -49,7 +48,7 @@ const DiscussionDetails = ({ item, date }: IDiscussionDetails): JSX.Element => {
|
|||
<View style={styles.detailContainer}>
|
||||
<CustomIcon name={'discussions'} size={24} color={themes[theme!].auxiliaryText} />
|
||||
<Text style={[styles.detailText, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
|
||||
{dcount}
|
||||
{count}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ const Item = ({ item, onPress }: IItem): JSX.Element => {
|
|||
|
||||
if (item?.ts) {
|
||||
messageTime = moment(item.ts).format('LT');
|
||||
// @ts-ignore TODO: Unify IMessage
|
||||
messageDate = formatDateThreads(item.ts);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ interface IParams {
|
|||
rid: string;
|
||||
t: SubscriptionType;
|
||||
tmid?: string;
|
||||
message?: string;
|
||||
message?: object;
|
||||
name?: string;
|
||||
fname?: string;
|
||||
prid?: string;
|
||||
|
@ -202,7 +202,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
},
|
||||
noDataMsg: I18n.t('No_files'),
|
||||
testID: 'room-files-view',
|
||||
renderItem: (item: IMessageItem) => (
|
||||
renderItem: (item: any) => (
|
||||
<Message
|
||||
{...renderItemCommonProps(item)}
|
||||
item={{
|
||||
|
@ -231,6 +231,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
},
|
||||
noDataMsg: I18n.t('No_mentioned_messages'),
|
||||
testID: 'mentioned-messages-view',
|
||||
// @ts-ignore TODO: unify IMessage
|
||||
renderItem: (item: IMessageItem) => <Message {...renderItemCommonProps(item)} msg={item.msg} theme={theme} />
|
||||
},
|
||||
// Starred Messages Screen
|
||||
|
@ -244,6 +245,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
noDataMsg: I18n.t('No_starred_messages'),
|
||||
testID: 'starred-messages-view',
|
||||
renderItem: (item: IMessageItem) => (
|
||||
// @ts-ignore TODO: unify IMessage
|
||||
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
||||
),
|
||||
action: (message: IMessageItem) => ({
|
||||
|
@ -264,6 +266,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
|||
noDataMsg: I18n.t('No_pinned_messages'),
|
||||
testID: 'pinned-messages-view',
|
||||
renderItem: (item: IMessageItem) => (
|
||||
// @ts-ignore TODO: unify IMessage
|
||||
<Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} />
|
||||
),
|
||||
action: () => ({ title: I18n.t('Unpin'), icon: 'pin', onPress: this.handleActionPress }),
|
||||
|
|
|
@ -1,38 +1,94 @@
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Share, Switch, Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { compareServerVersion } from '../../lib/utils';
|
||||
import Touch from '../../utils/touch';
|
||||
import { setLoading } from '../../actions/selectedUsers';
|
||||
import { closeRoom, leaveRoom } from '../../actions/room';
|
||||
import sharedStyles from '../Styles';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import Status from '../../containers/Status';
|
||||
import * as List from '../../containers/List';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import log, { events, logEvent } from '../../utils/log';
|
||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||
import I18n from '../../i18n';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { setLoading } from '../../actions/selectedUsers';
|
||||
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import * as List from '../../containers/List';
|
||||
import { MarkdownPreview } from '../../containers/markdown';
|
||||
import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
|
||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import Status from '../../containers/Status';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { IApplicationState, IBaseScreen, IRoom, ISubscription, IUser, TSubscriptionModel } from '../../definitions';
|
||||
import { withDimensions } from '../../dimensions';
|
||||
import I18n from '../../i18n';
|
||||
import database from '../../lib/database';
|
||||
import { E2E_ROOM_TYPES } from '../../lib/encryption/constants';
|
||||
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||
import database from '../../lib/database';
|
||||
import { withDimensions } from '../../dimensions';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import { compareServerVersion } from '../../lib/utils';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { withTheme } from '../../theme';
|
||||
import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
|
||||
import log, { events, logEvent } from '../../utils/log';
|
||||
import Touch from '../../utils/touch';
|
||||
import sharedStyles from '../Styles';
|
||||
import styles from './styles';
|
||||
import { ERoomType } from '../../definitions/ERoomType';
|
||||
|
||||
class RoomActionsView extends React.Component {
|
||||
static navigationOptions = ({ navigation, isMasterDetail }) => {
|
||||
const options = {
|
||||
interface IRoomActionsViewProps extends IBaseScreen<ChatsStackParamList, 'RoomActionsView'> {
|
||||
userId: string;
|
||||
jitsiEnabled: boolean;
|
||||
jitsiEnableTeams: boolean;
|
||||
jitsiEnableChannels: boolean;
|
||||
encryptionEnabled: boolean;
|
||||
fontScale: number;
|
||||
serverVersion: string | null;
|
||||
addUserToJoinedRoomPermission?: string[];
|
||||
addUserToAnyCRoomPermission?: string[];
|
||||
addUserToAnyPRoomPermission?: string[];
|
||||
createInviteLinksPermission?: string[];
|
||||
editRoomPermission?: string[];
|
||||
toggleRoomE2EEncryptionPermission?: string[];
|
||||
viewBroadcastMemberListPermission?: string[];
|
||||
transferLivechatGuestPermission?: string[];
|
||||
createTeamPermission?: string[];
|
||||
addTeamChannelPermission?: string[];
|
||||
convertTeamPermission?: string[];
|
||||
viewCannedResponsesPermission?: string[];
|
||||
}
|
||||
|
||||
interface IRoomActionsViewState {
|
||||
room: TSubscriptionModel;
|
||||
membersCount: number;
|
||||
member: Partial<IUser>;
|
||||
joined: boolean;
|
||||
canViewMembers: boolean;
|
||||
canAutoTranslate: boolean;
|
||||
canAddUser: boolean;
|
||||
canInviteUser: boolean;
|
||||
canForwardGuest: boolean;
|
||||
canReturnQueue: boolean;
|
||||
canEdit: boolean;
|
||||
canToggleEncryption: boolean;
|
||||
canCreateTeam: boolean;
|
||||
canAddChannelToTeam: boolean;
|
||||
canConvertTeam: boolean;
|
||||
canViewCannedResponse: boolean;
|
||||
}
|
||||
|
||||
class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomActionsViewState> {
|
||||
private mounted: boolean;
|
||||
private rid: string;
|
||||
private t: string;
|
||||
private joined: boolean;
|
||||
private roomObservable?: Observable<TSubscriptionModel>;
|
||||
private subscription?: Subscription;
|
||||
|
||||
static navigationOptions = ({
|
||||
navigation,
|
||||
isMasterDetail
|
||||
}: Pick<IRoomActionsViewProps, 'navigation' | 'isMasterDetail'>): StackNavigationOptions => {
|
||||
const options: StackNavigationOptions = {
|
||||
title: I18n.t('Actions')
|
||||
};
|
||||
if (isMasterDetail) {
|
||||
|
@ -41,34 +97,7 @@ class RoomActionsView extends React.Component {
|
|||
return options;
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
leaveRoom: PropTypes.func,
|
||||
jitsiEnabled: PropTypes.bool,
|
||||
jitsiEnableTeams: PropTypes.bool,
|
||||
jitsiEnableChannels: PropTypes.bool,
|
||||
encryptionEnabled: PropTypes.bool,
|
||||
setLoadingInvite: PropTypes.func,
|
||||
closeRoom: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
fontScale: PropTypes.number,
|
||||
serverVersion: PropTypes.string,
|
||||
addUserToJoinedRoomPermission: PropTypes.array,
|
||||
addUserToAnyCRoomPermission: PropTypes.array,
|
||||
addUserToAnyPRoomPermission: PropTypes.array,
|
||||
createInviteLinksPermission: PropTypes.array,
|
||||
editRoomPermission: PropTypes.array,
|
||||
toggleRoomE2EEncryptionPermission: PropTypes.array,
|
||||
viewBroadcastMemberListPermission: PropTypes.array,
|
||||
transferLivechatGuestPermission: PropTypes.array,
|
||||
createTeamPermission: PropTypes.array,
|
||||
addTeamChannelPermission: PropTypes.array,
|
||||
convertTeamPermission: PropTypes.array,
|
||||
viewCannedResponsesPermission: PropTypes.array
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IRoomActionsViewProps) {
|
||||
super(props);
|
||||
this.mounted = false;
|
||||
const room = props.route.params?.room;
|
||||
|
@ -77,7 +106,7 @@ class RoomActionsView extends React.Component {
|
|||
this.t = props.route.params?.t;
|
||||
this.joined = props.route.params?.joined;
|
||||
this.state = {
|
||||
room: room || { rid: this.rid, t: this.t },
|
||||
room: room || ({ rid: this.rid, t: this.t } as any),
|
||||
membersCount: 0,
|
||||
member: member || {},
|
||||
joined: !!room,
|
||||
|
@ -100,6 +129,7 @@ class RoomActionsView extends React.Component {
|
|||
if (this.mounted) {
|
||||
this.setState({ room: changes });
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.state.room = changes;
|
||||
}
|
||||
});
|
||||
|
@ -123,7 +153,7 @@ class RoomActionsView extends React.Component {
|
|||
|
||||
if (room && room.t !== 'd' && this.canViewMembers()) {
|
||||
try {
|
||||
const counters = await RocketChat.getRoomCounters(room.rid, room.t);
|
||||
const counters = await RocketChat.getRoomCounters(room.rid, room.t as any);
|
||||
if (counters.success) {
|
||||
this.setState({ membersCount: counters.members, joined: counters.joined });
|
||||
}
|
||||
|
@ -177,9 +207,15 @@ class RoomActionsView extends React.Component {
|
|||
return room.t === 'l' && room.status === 'queued' && !this.joined;
|
||||
}
|
||||
|
||||
onPressTouchable = item => {
|
||||
// TODO: assert params required for navigation
|
||||
onPressTouchable = (item: { route?: keyof ChatsStackParamList; params?: object; event?: Function }) => {
|
||||
const { route, event, params } = item;
|
||||
if (route) {
|
||||
/**
|
||||
* TODO: params can vary too much and ts is going to be happy
|
||||
* Instead of playing with this, we should think on a better `logEvent` function
|
||||
*/
|
||||
// @ts-ignore
|
||||
logEvent(events[`RA_GO_${route.replace('View', '').toUpperCase()}${params.name ? params.name.toUpperCase() : ''}`]);
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate(route, params);
|
||||
|
@ -349,7 +385,7 @@ class RoomActionsView extends React.Component {
|
|||
onPress: async () => {
|
||||
try {
|
||||
await RocketChat.returnLivechat(rid);
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
showErrorAlert(e.reason, I18n.t('Oops'));
|
||||
}
|
||||
}
|
||||
|
@ -364,7 +400,7 @@ class RoomActionsView extends React.Component {
|
|||
const roomUserId = RocketChat.getUidDirectMessage(room);
|
||||
const result = await RocketChat.getUserInfo(roomUserId);
|
||||
if (result.success) {
|
||||
this.setState({ member: result.user });
|
||||
this.setState({ member: result.user as any });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -394,7 +430,7 @@ class RoomActionsView extends React.Component {
|
|||
const { rid, blocker } = room;
|
||||
const { member } = this.state;
|
||||
try {
|
||||
await RocketChat.toggleBlockUser(rid, member._id, !blocker);
|
||||
await RocketChat.toggleBlockUser(rid, member._id as string, !blocker);
|
||||
} catch (e) {
|
||||
logEvent(events.RA_TOGGLE_BLOCK_USER_F);
|
||||
log(e);
|
||||
|
@ -411,9 +447,9 @@ class RoomActionsView extends React.Component {
|
|||
const encrypted = !room.encrypted;
|
||||
try {
|
||||
// Instantly feedback to the user
|
||||
await db.action(async () => {
|
||||
await db.write(async () => {
|
||||
await room.update(
|
||||
protectedFunction(r => {
|
||||
protectedFunction((r: TSubscriptionModel) => {
|
||||
r.encrypted = encrypted;
|
||||
})
|
||||
);
|
||||
|
@ -431,9 +467,9 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
|
||||
// If something goes wrong we go back to the previous value
|
||||
await db.action(async () => {
|
||||
await db.write(async () => {
|
||||
await room.update(
|
||||
protectedFunction(r => {
|
||||
protectedFunction((r: TSubscriptionModel) => {
|
||||
r.encrypted = room.encrypted;
|
||||
})
|
||||
);
|
||||
|
@ -463,28 +499,31 @@ class RoomActionsView extends React.Component {
|
|||
showConfirmationAlert({
|
||||
message: I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: RocketChat.getRoomTitle(room) }),
|
||||
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
|
||||
onPress: () => dispatch(leaveRoom('channel', room))
|
||||
onPress: () => dispatch(leaveRoom(ERoomType.c, room))
|
||||
});
|
||||
};
|
||||
|
||||
convertTeamToChannel = async () => {
|
||||
const { room } = this.state;
|
||||
const { navigation } = this.props;
|
||||
const { navigation, userId } = this.props;
|
||||
|
||||
try {
|
||||
const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: room.u._id });
|
||||
if (!room.teamId) {
|
||||
return;
|
||||
}
|
||||
const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId });
|
||||
|
||||
if (result.rooms?.length) {
|
||||
const teamChannels = result.rooms.map(r => ({
|
||||
const teamChannels = result.rooms.map((r: any) => ({
|
||||
rid: r._id,
|
||||
name: r.name,
|
||||
teamId: r.teamId
|
||||
}));
|
||||
navigation.navigate('SelectListView', {
|
||||
title: 'Converting_Team_To_Channel',
|
||||
data: teamChannels,
|
||||
data: teamChannels as any,
|
||||
infoText: 'Select_Team_Channels_To_Delete',
|
||||
nextAction: data => this.convertTeamToChannelConfirmation(data)
|
||||
nextAction: (data: string[]) => this.convertTeamToChannelConfirmation(data)
|
||||
});
|
||||
} else {
|
||||
this.convertTeamToChannelConfirmation();
|
||||
|
@ -494,12 +533,15 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
handleConvertTeamToChannel = async selected => {
|
||||
handleConvertTeamToChannel = async (selected: string[]) => {
|
||||
logEvent(events.RA_CONVERT_TEAM_TO_CHANNEL);
|
||||
try {
|
||||
const { room } = this.state;
|
||||
const { navigation } = this.props;
|
||||
|
||||
if (!room.teamId) {
|
||||
return;
|
||||
}
|
||||
const result = await RocketChat.convertTeamToChannel({ teamId: room.teamId, selected });
|
||||
|
||||
if (result.success) {
|
||||
|
@ -511,7 +553,7 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
convertTeamToChannelConfirmation = (selected = []) => {
|
||||
convertTeamToChannelConfirmation = (selected: string[] = []) => {
|
||||
showConfirmationAlert({
|
||||
title: I18n.t('Confirmation'),
|
||||
message: I18n.t('You_are_converting_the_team'),
|
||||
|
@ -522,13 +564,16 @@ class RoomActionsView extends React.Component {
|
|||
|
||||
leaveTeam = async () => {
|
||||
const { room } = this.state;
|
||||
const { navigation, dispatch } = this.props;
|
||||
const { navigation, dispatch, userId } = this.props;
|
||||
|
||||
try {
|
||||
const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: room.u._id });
|
||||
if (!room.teamId) {
|
||||
return;
|
||||
}
|
||||
const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId });
|
||||
|
||||
if (result.rooms?.length) {
|
||||
const teamChannels = result.rooms.map(r => ({
|
||||
const teamChannels = result.rooms.map((r: any) => ({
|
||||
rid: r._id,
|
||||
name: r.name,
|
||||
teamId: r.teamId,
|
||||
|
@ -538,21 +583,21 @@ class RoomActionsView extends React.Component {
|
|||
title: 'Leave_Team',
|
||||
data: teamChannels,
|
||||
infoText: 'Select_Team_Channels',
|
||||
nextAction: data => dispatch(leaveRoom('team', room, data)),
|
||||
nextAction: data => dispatch(leaveRoom(ERoomType.t, room, data)),
|
||||
showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_leave'))
|
||||
});
|
||||
} else {
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
|
||||
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
|
||||
onPress: () => dispatch(leaveRoom('team', room))
|
||||
onPress: () => dispatch(leaveRoom(ERoomType.t, room))
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
|
||||
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
|
||||
onPress: () => dispatch(leaveRoom('team', room))
|
||||
onPress: () => dispatch(leaveRoom(ERoomType.t, room))
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -562,7 +607,7 @@ class RoomActionsView extends React.Component {
|
|||
try {
|
||||
const { room } = this.state;
|
||||
const { navigation } = this.props;
|
||||
const result = await RocketChat.convertChannelToTeam({ rid: room.rid, name: room.name, type: room.t });
|
||||
const result = await RocketChat.convertChannelToTeam({ rid: room.rid, name: room.name, type: room.t as any });
|
||||
|
||||
if (result.success) {
|
||||
navigation.navigate('RoomView');
|
||||
|
@ -582,7 +627,7 @@ class RoomActionsView extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
handleMoveToTeam = async selected => {
|
||||
handleMoveToTeam = async (selected: string[]) => {
|
||||
logEvent(events.RA_MOVE_TO_TEAM);
|
||||
try {
|
||||
const { room } = this.state;
|
||||
|
@ -603,14 +648,14 @@ class RoomActionsView extends React.Component {
|
|||
const { navigation } = this.props;
|
||||
const db = database.active;
|
||||
const subCollection = db.get('subscriptions');
|
||||
const teamRooms = await subCollection.query(Q.where('team_main', true));
|
||||
const teamRooms = await subCollection.query(Q.where('team_main', true)).fetch();
|
||||
|
||||
if (teamRooms.length) {
|
||||
const data = teamRooms.map(team => ({
|
||||
rid: team.teamId,
|
||||
t: team.t,
|
||||
name: team.name
|
||||
}));
|
||||
})) as IRoom[]; // TODO: review this usage later
|
||||
navigation.navigate('SelectListView', {
|
||||
title: 'Move_to_Team',
|
||||
infoText: 'Move_Channel_Paragraph',
|
||||
|
@ -637,7 +682,7 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
searchTeam = async onChangeText => {
|
||||
searchTeam = async (onChangeText: string) => {
|
||||
logEvent(events.RA_SEARCH_TEAM);
|
||||
try {
|
||||
const { addTeamChannelPermission, createTeamPermission } = this.props;
|
||||
|
@ -650,9 +695,10 @@ class RoomActionsView extends React.Component {
|
|||
Q.where('name', Q.like(`%${onChangeText}%`)),
|
||||
Q.experimentalTake(QUERY_SIZE),
|
||||
Q.experimentalSortBy('room_updated_at', Q.desc)
|
||||
);
|
||||
)
|
||||
.fetch();
|
||||
|
||||
const asyncFilter = async teamArray => {
|
||||
const asyncFilter = async (teamArray: TSubscriptionModel[]) => {
|
||||
const results = await Promise.all(
|
||||
teamArray.map(async team => {
|
||||
const permissions = await RocketChat.hasPermission([addTeamChannelPermission, createTeamPermission], team.rid);
|
||||
|
@ -698,7 +744,6 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
accessibilityLabel={I18n.t('Room_Info')}
|
||||
accessibilityTraits='button'
|
||||
enabled={!isGroupChat}
|
||||
testID='room-actions-info'
|
||||
theme={theme}>
|
||||
|
@ -708,7 +753,7 @@ class RoomActionsView extends React.Component {
|
|||
<View style={[sharedStyles.status, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<Status size={16} id={member._id} />
|
||||
</View>
|
||||
) : null}
|
||||
) : undefined}
|
||||
</Avatar>
|
||||
<View style={styles.roomTitleContainer}>
|
||||
{room.t === 'd' ? (
|
||||
|
@ -782,7 +827,7 @@ class RoomActionsView extends React.Component {
|
|||
|
||||
// If this room type can be encrypted
|
||||
// If e2e is enabled
|
||||
if (E2E_ROOM_TYPES[room?.t] && encryptionEnabled) {
|
||||
if (E2E_ROOM_TYPES[room.t] && encryptionEnabled) {
|
||||
return (
|
||||
<List.Section>
|
||||
<List.Separator />
|
||||
|
@ -853,7 +898,7 @@ class RoomActionsView extends React.Component {
|
|||
return null;
|
||||
};
|
||||
|
||||
teamChannelActions = (t, room) => {
|
||||
teamChannelActions = (t: string, room: ISubscription) => {
|
||||
const { canEdit, canCreateTeam, canAddChannelToTeam } = this.state;
|
||||
const canConvertToTeam = canEdit && canCreateTeam && !room.teamMain;
|
||||
const canMoveToTeam = canEdit && canAddChannelToTeam && !room.teamId;
|
||||
|
@ -897,7 +942,7 @@ class RoomActionsView extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
teamToChannelActions = (t, room) => {
|
||||
teamToChannelActions = (t: string, room: ISubscription) => {
|
||||
const { canEdit, canConvertTeam } = this.state;
|
||||
const canConvertTeamToChannel = canEdit && canConvertTeam && !!room?.teamMain;
|
||||
|
||||
|
@ -952,7 +997,7 @@ class RoomActionsView extends React.Component {
|
|||
<>
|
||||
<List.Item
|
||||
title='Members'
|
||||
subtitle={membersCount > 0 ? `${membersCount} ${I18n.t('members')}` : null}
|
||||
subtitle={membersCount > 0 ? `${membersCount} ${I18n.t('members')}` : undefined}
|
||||
onPress={() => this.onPressTouchable({ route: 'RoomMembersView', params: { rid, room } })}
|
||||
testID='room-actions-members'
|
||||
left={() => <List.Icon name='team' />}
|
||||
|
@ -1221,10 +1266,11 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
jitsiEnabled: state.settings.Jitsi_Enabled || false,
|
||||
jitsiEnableTeams: state.settings.Jitsi_Enable_Teams || false,
|
||||
jitsiEnableChannels: state.settings.Jitsi_Enable_Channels || false,
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
userId: getUserSelector(state).id,
|
||||
jitsiEnabled: (state.settings.Jitsi_Enabled || false) as boolean,
|
||||
jitsiEnableTeams: (state.settings.Jitsi_Enable_Teams || false) as boolean,
|
||||
jitsiEnableChannels: (state.settings.Jitsi_Enable_Channels || false) as boolean,
|
||||
encryptionEnabled: state.encryption.enabled,
|
||||
serverVersion: state.server.version,
|
||||
isMasterDetail: state.app.isMasterDetail,
|
|
@ -16,7 +16,7 @@ import StatusBar from '../../containers/StatusBar';
|
|||
import RCTextInput from '../../containers/TextInput';
|
||||
import { LISTENER } from '../../containers/Toast';
|
||||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||
import { IApplicationState, IBaseScreen, ISubscription, TSubscriptionModel } from '../../definitions';
|
||||
import { IApplicationState, IBaseScreen, ISubscription, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||
import { ERoomType } from '../../definitions/ERoomType';
|
||||
import I18n from '../../i18n';
|
||||
import database from '../../lib/database';
|
||||
|
@ -372,7 +372,7 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
|||
title: 'Delete_Team',
|
||||
data: teamChannelOwner,
|
||||
infoText: 'Select_channels_to_delete',
|
||||
nextAction: (selected: Record<string, string>) => {
|
||||
nextAction: (selected: string[]) => {
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }),
|
||||
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
|
||||
|
@ -437,7 +437,7 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
|||
onPress: async () => {
|
||||
try {
|
||||
logEvent(events.RI_EDIT_TOGGLE_ARCHIVE);
|
||||
await RocketChat.toggleArchiveRoom(rid, t, !archived);
|
||||
await RocketChat.toggleArchiveRoom(rid, t as SubscriptionType, !archived);
|
||||
} catch (e) {
|
||||
logEvent(events.RI_EDIT_TOGGLE_ARCHIVE_F);
|
||||
log(e);
|
||||
|
|
|
@ -24,7 +24,7 @@ import { getUserSelector } from '../../selectors/login';
|
|||
import { ModalStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||
import { withTheme } from '../../theme';
|
||||
import EventEmitter from '../../utils/events';
|
||||
import { goRoom, IGoRoomItem } from '../../utils/goRoom';
|
||||
import { goRoom, TGoRoomItem } from '../../utils/goRoom';
|
||||
import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
|
||||
import log from '../../utils/log';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
|
@ -469,7 +469,7 @@ class RoomMembersView extends React.Component<IRoomMembersViewProps, IRoomMember
|
|||
}
|
||||
};
|
||||
|
||||
goRoom = (item: IGoRoomItem) => {
|
||||
goRoom = (item: TGoRoomItem) => {
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
if (isMasterDetail) {
|
||||
// @ts-ignore
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BorderlessButton, ScrollView } from 'react-native-gesture-handler';
|
||||
import Modal from 'react-native-modal';
|
||||
|
||||
|
@ -8,10 +7,19 @@ import Markdown, { MarkdownPreview } from '../../containers/markdown';
|
|||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { themes } from '../../constants/colors';
|
||||
import styles from './styles';
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
interface IBannerProps {
|
||||
text?: string;
|
||||
title?: string;
|
||||
bannerClosed?: boolean;
|
||||
closeBanner: () => void;
|
||||
}
|
||||
|
||||
const Banner = React.memo(
|
||||
({ text, title, theme, bannerClosed, closeBanner }) => {
|
||||
({ text, title, bannerClosed, closeBanner }: IBannerProps) => {
|
||||
const [showModal, openModal] = useState(false);
|
||||
const { theme } = useTheme();
|
||||
|
||||
const toggleModal = () => openModal(prevState => !prevState);
|
||||
|
||||
|
@ -47,16 +55,7 @@ const Banner = React.memo(
|
|||
|
||||
return null;
|
||||
},
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.text === nextProps.text && prevProps.theme === nextProps.theme && prevProps.bannerClosed === nextProps.bannerClosed
|
||||
(prevProps, nextProps) => prevProps.text === nextProps.text && prevProps.bannerClosed === nextProps.bannerClosed
|
||||
);
|
||||
|
||||
Banner.propTypes = {
|
||||
text: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
bannerClosed: PropTypes.bool,
|
||||
closeBanner: PropTypes.func
|
||||
};
|
||||
|
||||
export default Banner;
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { ImageBackground, StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
|
@ -10,17 +11,12 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const EmptyRoom = React.memo(({ length, mounted, theme, rid }) => {
|
||||
const EmptyRoom = React.memo(({ length, mounted, rid }: { length: number; mounted: boolean; rid: string }) => {
|
||||
const { theme } = useTheme();
|
||||
if ((length === 0 && mounted) || !rid) {
|
||||
return <ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} />;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
EmptyRoom.propTypes = {
|
||||
length: PropTypes.number.isRequired,
|
||||
mounted: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
rid: PropTypes.string
|
||||
};
|
||||
export default EmptyRoom;
|
|
@ -1,5 +1,4 @@
|
|||
import React, { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { InteractionManager, StyleSheet, Text, View } from 'react-native';
|
||||
import Modal from 'react-native-modal';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -10,6 +9,7 @@ import TextInput from '../../containers/TextInput';
|
|||
import RocketChat from '../../lib/rocketchat';
|
||||
import sharedStyles from '../Styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { IApplicationState } from '../../definitions';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -41,8 +41,16 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
export interface IJoinCodeProps {
|
||||
rid: string;
|
||||
t: string;
|
||||
onJoin: Function;
|
||||
isMasterDetail: boolean;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
const JoinCode = React.memo(
|
||||
forwardRef(({ rid, t, onJoin, isMasterDetail, theme }, ref) => {
|
||||
forwardRef(({ rid, t, onJoin, isMasterDetail, theme }: IJoinCodeProps, ref) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [code, setCode] = useState('');
|
||||
|
@ -53,7 +61,7 @@ const JoinCode = React.memo(
|
|||
|
||||
const joinRoom = async () => {
|
||||
try {
|
||||
await RocketChat.joinRoom(rid, code, t);
|
||||
await RocketChat.joinRoom(rid, code, t as any);
|
||||
onJoin();
|
||||
hide();
|
||||
} catch (e) {
|
||||
|
@ -64,7 +72,8 @@ const JoinCode = React.memo(
|
|||
useImperativeHandle(ref, () => ({ show }));
|
||||
|
||||
return (
|
||||
<Modal transparent avoidKeyboard useNativeDriver isVisible={visible} hideModalContentWhileAnimating>
|
||||
// @ts-ignore TODO: `transparent` seems to exist, but types are incorrect on the lib
|
||||
<Modal transparent={true} avoidKeyboard useNativeDriver isVisible={visible} hideModalContentWhileAnimating>
|
||||
<View style={styles.container} testID='join-code'>
|
||||
<View
|
||||
style={[
|
||||
|
@ -76,14 +85,15 @@ const JoinCode = React.memo(
|
|||
<TextInput
|
||||
value={code}
|
||||
theme={theme}
|
||||
inputRef={e => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
|
||||
// TODO: find a way to type this ref
|
||||
inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
|
||||
returnKeyType='send'
|
||||
autoCapitalize='none'
|
||||
onChangeText={setCode}
|
||||
onSubmitEditing={joinRoom}
|
||||
placeholder={I18n.t('Join_Code')}
|
||||
secureTextEntry
|
||||
error={error && { error: 'error-code-invalid', reason: I18n.t('Code_or_password_invalid') }}
|
||||
error={error ? { error: 'error-code-invalid', reason: I18n.t('Code_or_password_invalid') } : undefined}
|
||||
testID='join-code-input'
|
||||
/>
|
||||
<View style={styles.buttonContainer}>
|
||||
|
@ -111,15 +121,8 @@ const JoinCode = React.memo(
|
|||
);
|
||||
})
|
||||
);
|
||||
JoinCode.propTypes = {
|
||||
rid: PropTypes.string,
|
||||
t: PropTypes.string,
|
||||
onJoin: PropTypes.func,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
export default connect(mapStateToProps, null, null, { forwardRef: true })(JoinCode);
|
|
@ -1,56 +0,0 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { HeaderBackButton } from '@react-navigation/stack';
|
||||
|
||||
import { themes } from '../../constants/colors';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
avatar: {
|
||||
borderRadius: 10,
|
||||
marginHorizontal: 16
|
||||
}
|
||||
});
|
||||
|
||||
const LeftButtons = React.memo(
|
||||
({ tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, isMasterDetail }) => {
|
||||
if (!isMasterDetail || tmid) {
|
||||
const onPress = useCallback(() => navigation.goBack());
|
||||
const label = unreadsCount > 99 ? '+99' : unreadsCount || ' ';
|
||||
const labelLength = label.length ? label.length : 1;
|
||||
const marginLeft = -2 * labelLength;
|
||||
const fontSize = labelLength > 1 ? 14 : 17;
|
||||
return (
|
||||
<HeaderBackButton
|
||||
label={label}
|
||||
onPress={onPress}
|
||||
tintColor={themes[theme].headerTintColor}
|
||||
labelStyle={{ fontSize, marginLeft }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const onPress = useCallback(() => goRoomActionsView(), []);
|
||||
|
||||
if (baseUrl && userId && token) {
|
||||
return <Avatar text={title} size={30} type={t} style={styles.avatar} onPress={onPress} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
LeftButtons.propTypes = {
|
||||
tmid: PropTypes.string,
|
||||
unreadsCount: PropTypes.number,
|
||||
navigation: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
userId: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
t: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
goRoomActionsView: PropTypes.func,
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
export default LeftButtons;
|
|
@ -0,0 +1,72 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { HeaderBackButton, StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import { themes } from '../../constants/colors';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
avatar: {
|
||||
borderRadius: 10,
|
||||
marginHorizontal: 16
|
||||
}
|
||||
});
|
||||
|
||||
interface ILeftButtonsProps {
|
||||
tmid?: string;
|
||||
unreadsCount: number | null;
|
||||
navigation: StackNavigationProp<ChatsStackParamList, 'RoomView'>;
|
||||
baseUrl: string;
|
||||
userId?: string;
|
||||
token?: string;
|
||||
title?: string;
|
||||
t: string;
|
||||
theme: string;
|
||||
goRoomActionsView: Function;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
const LeftButtons = ({
|
||||
tmid,
|
||||
unreadsCount,
|
||||
navigation,
|
||||
baseUrl,
|
||||
userId,
|
||||
token,
|
||||
title,
|
||||
t,
|
||||
theme,
|
||||
goRoomActionsView,
|
||||
isMasterDetail
|
||||
}: ILeftButtonsProps): React.ReactElement | null => {
|
||||
if (!isMasterDetail || tmid) {
|
||||
const onPress = () => navigation.goBack();
|
||||
let label = ' ';
|
||||
let labelLength = 1;
|
||||
let marginLeft = 0;
|
||||
let fontSize = 0;
|
||||
if (unreadsCount) {
|
||||
label = unreadsCount > 99 ? '+99' : unreadsCount.toString() || ' ';
|
||||
labelLength = label.length ? label.length : 1;
|
||||
marginLeft = -2 * labelLength;
|
||||
fontSize = labelLength > 1 ? 14 : 17;
|
||||
}
|
||||
return (
|
||||
<HeaderBackButton
|
||||
label={label}
|
||||
onPress={onPress}
|
||||
tintColor={themes[theme].headerTintColor}
|
||||
labelStyle={{ fontSize, marginLeft }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const onPress = useCallback(() => goRoomActionsView(), []);
|
||||
|
||||
if (baseUrl && userId && token) {
|
||||
return <Avatar text={title} size={30} type={t} style={styles.avatar} onPress={onPress} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default LeftButtons;
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { FlatList, StyleSheet } from 'react-native';
|
||||
import { FlatList, FlatListProps, StyleSheet } from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
@ -17,11 +17,15 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const List = ({ listRef, ...props }) => (
|
||||
export interface IListProps extends FlatListProps<any> {
|
||||
listRef: any;
|
||||
}
|
||||
|
||||
const List = ({ listRef, ...props }: IListProps) => (
|
||||
<AnimatedFlatList
|
||||
testID='room-view-messages'
|
||||
ref={listRef}
|
||||
keyExtractor={item => item.id}
|
||||
keyExtractor={(item: any) => item.id}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
style={styles.list}
|
||||
inverted
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Animated, { call, cond, greaterOrEq, useCode } from 'react-native-reanimated';
|
||||
|
||||
import { themes } from '../../../constants/colors';
|
||||
|
@ -30,11 +29,19 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const NavBottomFAB = ({ y, onPress, isThread }) => {
|
||||
const NavBottomFAB = ({
|
||||
y,
|
||||
onPress,
|
||||
isThread
|
||||
}: {
|
||||
y: Animated.Value<number>;
|
||||
onPress: Function;
|
||||
isThread: boolean;
|
||||
}): React.ReactElement | null => {
|
||||
const { theme } = useTheme();
|
||||
const [show, setShow] = useState(false);
|
||||
const handleOnPress = useCallback(() => onPress());
|
||||
const toggle = v => setShow(v);
|
||||
const handleOnPress = () => onPress();
|
||||
const toggle = (v: boolean) => setShow(v);
|
||||
|
||||
useCode(
|
||||
() =>
|
||||
|
@ -65,10 +72,4 @@ const NavBottomFAB = ({ y, onPress, isThread }) => {
|
|||
);
|
||||
};
|
||||
|
||||
NavBottomFAB.propTypes = {
|
||||
y: Animated.Value,
|
||||
onPress: PropTypes.func,
|
||||
isThread: PropTypes.bool
|
||||
};
|
||||
|
||||
export default NavBottomFAB;
|
|
@ -1,26 +1,27 @@
|
|||
import React from 'react';
|
||||
import { RefreshControl } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import moment from 'moment';
|
||||
import { dequal } from 'dequal';
|
||||
import { Value, event } from 'react-native-reanimated';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { FlatListProps, RefreshControl, ViewToken } from 'react-native';
|
||||
import { event, Value } from 'react-native-reanimated';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { themes } from '../../../constants/colors';
|
||||
import ActivityIndicator from '../../../containers/ActivityIndicator';
|
||||
import { TAnyMessageModel, TMessageModel, TThreadMessageModel, TThreadModel } from '../../../definitions';
|
||||
import database from '../../../lib/database';
|
||||
import RocketChat from '../../../lib/rocketchat';
|
||||
import { compareServerVersion } from '../../../lib/utils';
|
||||
import debounce from '../../../utils/debounce';
|
||||
import { animateNextTransition } from '../../../utils/layoutAnimation';
|
||||
import log from '../../../utils/log';
|
||||
import EmptyRoom from '../EmptyRoom';
|
||||
import { animateNextTransition } from '../../../utils/layoutAnimation';
|
||||
import ActivityIndicator from '../../../containers/ActivityIndicator';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import debounce from '../../../utils/debounce';
|
||||
import { compareServerVersion } from '../../../lib/utils';
|
||||
import List from './List';
|
||||
import List, { IListProps } from './List';
|
||||
import NavBottomFAB from './NavBottomFAB';
|
||||
|
||||
const QUERY_SIZE = 50;
|
||||
|
||||
const onScroll = ({ y }) =>
|
||||
const onScroll = ({ y }: { y: Value<number> }) =>
|
||||
event(
|
||||
[
|
||||
{
|
||||
|
@ -32,44 +33,59 @@ const onScroll = ({ y }) =>
|
|||
{ useNativeDriver: true }
|
||||
);
|
||||
|
||||
class ListContainer extends React.Component {
|
||||
static propTypes = {
|
||||
renderRow: PropTypes.func,
|
||||
rid: PropTypes.string,
|
||||
tmid: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
loading: PropTypes.bool,
|
||||
listRef: PropTypes.func,
|
||||
hideSystemMessages: PropTypes.array,
|
||||
tunread: PropTypes.array,
|
||||
ignored: PropTypes.array,
|
||||
navigation: PropTypes.object,
|
||||
showMessageInMainThread: PropTypes.bool,
|
||||
serverVersion: PropTypes.string
|
||||
};
|
||||
export { IListProps };
|
||||
|
||||
constructor(props) {
|
||||
export interface IListContainerProps {
|
||||
renderRow: Function;
|
||||
rid: string;
|
||||
tmid?: string;
|
||||
theme: string;
|
||||
loading: boolean;
|
||||
listRef: React.RefObject<IListProps>;
|
||||
hideSystemMessages?: string[];
|
||||
tunread?: string[];
|
||||
ignored?: string[];
|
||||
navigation: any; // TODO: type me
|
||||
showMessageInMainThread: boolean;
|
||||
serverVersion: string | null;
|
||||
}
|
||||
|
||||
interface IListContainerState {
|
||||
messages: TAnyMessageModel[];
|
||||
refreshing: boolean;
|
||||
highlightedMessage: string | null;
|
||||
}
|
||||
|
||||
class ListContainer extends React.Component<IListContainerProps, IListContainerState> {
|
||||
private count = 0;
|
||||
private mounted = false;
|
||||
private animated = false;
|
||||
private jumping = false;
|
||||
private y = new Value(0);
|
||||
private onScroll = onScroll({ y: this.y });
|
||||
private unsubscribeFocus: () => void;
|
||||
private viewabilityConfig = {
|
||||
itemVisiblePercentThreshold: 10
|
||||
};
|
||||
private highlightedMessageTimeout: number | undefined | false;
|
||||
private thread?: TThreadModel;
|
||||
private messagesObservable?: Observable<TMessageModel[] | TThreadMessageModel[]>;
|
||||
private messagesSubscription?: Subscription;
|
||||
private viewableItems?: ViewToken[];
|
||||
|
||||
constructor(props: IListContainerProps) {
|
||||
super(props);
|
||||
console.time(`${this.constructor.name} init`);
|
||||
console.time(`${this.constructor.name} mount`);
|
||||
this.count = 0;
|
||||
this.mounted = false;
|
||||
this.animated = false;
|
||||
this.jumping = false;
|
||||
this.state = {
|
||||
messages: [],
|
||||
refreshing: false,
|
||||
highlightedMessage: null
|
||||
};
|
||||
this.y = new Value(0);
|
||||
this.onScroll = onScroll({ y: this.y });
|
||||
this.query();
|
||||
this.unsubscribeFocus = props.navigation.addListener('focus', () => {
|
||||
this.animated = true;
|
||||
});
|
||||
this.viewabilityConfig = {
|
||||
itemVisiblePercentThreshold: 10
|
||||
};
|
||||
console.timeEnd(`${this.constructor.name} init`);
|
||||
}
|
||||
|
||||
|
@ -78,7 +94,7 @@ class ListContainer extends React.Component {
|
|||
console.timeEnd(`${this.constructor.name} mount`);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps: IListContainerProps, nextState: IListContainerState) {
|
||||
const { refreshing, highlightedMessage } = this.state;
|
||||
const { hideSystemMessages, theme, tunread, ignored, loading } = this.props;
|
||||
if (theme !== nextProps.theme) {
|
||||
|
@ -105,7 +121,7 @@ class ListContainer extends React.Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: IListContainerProps) {
|
||||
const { hideSystemMessages } = this.props;
|
||||
if (!dequal(hideSystemMessages, prevProps.hideSystemMessages)) {
|
||||
this.reload();
|
||||
|
@ -114,9 +130,6 @@ class ListContainer extends React.Component {
|
|||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeMessages();
|
||||
if (this.onEndReached && this.onEndReached.stop) {
|
||||
this.onEndReached.stop();
|
||||
}
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
|
@ -158,7 +171,7 @@ class ListContainer extends React.Component {
|
|||
Q.experimentalSortBy('ts', Q.desc),
|
||||
Q.experimentalSkip(0),
|
||||
Q.experimentalTake(this.count)
|
||||
];
|
||||
] as (Q.WhereDescription | Q.Or)[];
|
||||
if (!showMessageInMainThread) {
|
||||
whereClause.push(Q.or(Q.where('tmid', null), Q.where('tshow', Q.eq(true))));
|
||||
}
|
||||
|
@ -170,7 +183,7 @@ class ListContainer extends React.Component {
|
|||
|
||||
if (rid) {
|
||||
this.unsubscribeMessages();
|
||||
this.messagesSubscription = this.messagesObservable.subscribe(messages => {
|
||||
this.messagesSubscription = this.messagesObservable?.subscribe(messages => {
|
||||
if (tmid && this.thread) {
|
||||
messages = [...messages, this.thread];
|
||||
}
|
||||
|
@ -186,6 +199,7 @@ class ListContainer extends React.Component {
|
|||
if (this.mounted) {
|
||||
this.setState({ messages }, () => this.update());
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.state.messages = messages;
|
||||
}
|
||||
// TODO: move it away from here
|
||||
|
@ -246,7 +260,7 @@ class ListContainer extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
getLastMessage = () => {
|
||||
getLastMessage = (): TMessageModel | TThreadMessageModel | null => {
|
||||
const { messages } = this.state;
|
||||
if (messages.length > 0) {
|
||||
return messages[0];
|
||||
|
@ -254,21 +268,23 @@ class ListContainer extends React.Component {
|
|||
return null;
|
||||
};
|
||||
|
||||
handleScrollToIndexFailed = params => {
|
||||
handleScrollToIndexFailed: FlatListProps<any>['onScrollToIndexFailed'] = params => {
|
||||
const { listRef } = this.props;
|
||||
// @ts-ignore
|
||||
listRef.current.getNode().scrollToIndex({ index: params.highestMeasuredFrameIndex, animated: false });
|
||||
};
|
||||
|
||||
jumpToMessage = messageId =>
|
||||
new Promise(async resolve => {
|
||||
jumpToMessage = (messageId: string) =>
|
||||
new Promise<void>(async resolve => {
|
||||
this.jumping = true;
|
||||
const { messages } = this.state;
|
||||
const { listRef } = this.props;
|
||||
const index = messages.findIndex(item => item.id === messageId);
|
||||
if (index > -1) {
|
||||
// @ts-ignore
|
||||
listRef.current.getNode().scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 });
|
||||
await new Promise(res => setTimeout(res, 300));
|
||||
if (!this.viewableItems.map(vi => vi.key).includes(messageId)) {
|
||||
if (!this.viewableItems?.map(vi => vi.key).includes(messageId)) {
|
||||
if (!this.jumping) {
|
||||
return resolve();
|
||||
}
|
||||
|
@ -282,6 +298,7 @@ class ListContainer extends React.Component {
|
|||
}, 10000);
|
||||
await setTimeout(() => resolve(), 300);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
listRef.current.getNode().scrollToIndex({ index: messages.length - 1, animated: false });
|
||||
if (!this.jumping) {
|
||||
return resolve();
|
||||
|
@ -297,6 +314,7 @@ class ListContainer extends React.Component {
|
|||
|
||||
jumpToBottom = () => {
|
||||
const { listRef } = this.props;
|
||||
// @ts-ignore
|
||||
listRef.current.getNode().scrollToOffset({ offset: -100 });
|
||||
};
|
||||
|
||||
|
@ -308,13 +326,13 @@ class ListContainer extends React.Component {
|
|||
return null;
|
||||
};
|
||||
|
||||
renderItem = ({ item, index }) => {
|
||||
renderItem: FlatListProps<any>['renderItem'] = ({ item, index }) => {
|
||||
const { messages, highlightedMessage } = this.state;
|
||||
const { renderRow } = this.props;
|
||||
return renderRow(item, messages[index + 1], highlightedMessage);
|
||||
};
|
||||
|
||||
onViewableItemsChanged = ({ viewableItems }) => {
|
||||
onViewableItemsChanged: FlatListProps<any>['onViewableItemsChanged'] = ({ viewableItems }) => {
|
||||
this.viewableItems = viewableItems;
|
||||
};
|
||||
|
||||
|
@ -325,7 +343,7 @@ class ListContainer extends React.Component {
|
|||
const { theme } = this.props;
|
||||
return (
|
||||
<>
|
||||
<EmptyRoom rid={rid} length={messages.length} mounted={this.mounted} theme={theme} />
|
||||
<EmptyRoom rid={rid} length={messages.length} mounted={this.mounted} />
|
||||
<List
|
||||
onScroll={this.onScroll}
|
||||
scrollEventThrottle={16}
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { ActivityIndicator, StyleSheet, Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../../constants/messageTypeLoad';
|
||||
|
@ -21,7 +20,7 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const LoadMore = ({ load, type, runOnRender }) => {
|
||||
const LoadMore = ({ load, type, runOnRender }: { load: Function; type: string; runOnRender: boolean }): React.ReactElement => {
|
||||
const { theme } = useTheme();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
|
@ -62,10 +61,4 @@ const LoadMore = ({ load, type, runOnRender }) => {
|
|||
);
|
||||
};
|
||||
|
||||
LoadMore.propTypes = {
|
||||
load: PropTypes.func,
|
||||
type: PropTypes.string,
|
||||
runOnRender: PropTypes.bool
|
||||
};
|
||||
|
||||
export default LoadMore;
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Modal from 'react-native-modal';
|
||||
|
@ -9,34 +8,37 @@ import { isAndroid } from '../../utils/deviceInfo';
|
|||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import styles from './styles';
|
||||
import { IApplicationState } from '../../definitions';
|
||||
|
||||
const margin = isAndroid ? 40 : 20;
|
||||
const maxSize = 400;
|
||||
|
||||
class ReactionPicker extends React.Component {
|
||||
static propTypes = {
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
message: PropTypes.object,
|
||||
show: PropTypes.bool,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
reactionClose: PropTypes.func,
|
||||
onEmojiSelected: PropTypes.func,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
interface IReactionPickerProps {
|
||||
baseUrl: string;
|
||||
message?: any;
|
||||
show: boolean;
|
||||
isMasterDetail: boolean;
|
||||
reactionClose: () => void;
|
||||
onEmojiSelected: Function; // TODO: properly type this
|
||||
width: number;
|
||||
height: number;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
class ReactionPicker extends React.Component<IReactionPickerProps> {
|
||||
shouldComponentUpdate(nextProps: IReactionPickerProps) {
|
||||
const { show, width, height } = this.props;
|
||||
return nextProps.show !== show || width !== nextProps.width || height !== nextProps.height;
|
||||
}
|
||||
|
||||
onEmojiSelected = (emoji, shortname) => {
|
||||
onEmojiSelected = (emoji: string, shortname: string) => {
|
||||
// standard emojis: `emoji` is unicode and `shortname` is :joy:
|
||||
// custom emojis: only `emoji` is returned with shortname type (:joy:)
|
||||
// to set reactions, we need shortname type
|
||||
const { onEmojiSelected, message } = this.props;
|
||||
onEmojiSelected(shortname || emoji, message.id);
|
||||
if (message) {
|
||||
onEmojiSelected(shortname || emoji, message.id);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -79,7 +81,7 @@ class ReactionPicker extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
baseUrl: state.server.server,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
|
@ -1,30 +1,43 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { dequal } from 'dequal';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import database from '../../lib/database';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import { events, logEvent } from '../../utils/log';
|
||||
import { isTeamRoom } from '../../utils/room';
|
||||
import { IApplicationState, SubscriptionType, TMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
|
||||
class RightButtonsContainer extends Component {
|
||||
static propTypes = {
|
||||
userId: PropTypes.string,
|
||||
threadsEnabled: PropTypes.bool,
|
||||
rid: PropTypes.string,
|
||||
t: PropTypes.string,
|
||||
tmid: PropTypes.string,
|
||||
teamId: PropTypes.string,
|
||||
navigation: PropTypes.object,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
toggleFollowThread: PropTypes.func,
|
||||
joined: PropTypes.bool,
|
||||
encrypted: PropTypes.bool
|
||||
};
|
||||
interface IRightButtonsProps {
|
||||
userId?: string;
|
||||
threadsEnabled: boolean;
|
||||
rid?: string;
|
||||
t: string;
|
||||
tmid?: string;
|
||||
teamId?: string;
|
||||
isMasterDetail: boolean;
|
||||
toggleFollowThread: Function;
|
||||
joined: boolean;
|
||||
encrypted?: boolean;
|
||||
navigation: StackNavigationProp<ChatsStackParamList, 'RoomView'>;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
interface IRigthButtonsState {
|
||||
isFollowingThread: boolean;
|
||||
tunread: string[];
|
||||
tunreadUser: string[];
|
||||
tunreadGroup: string[];
|
||||
}
|
||||
|
||||
class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsState> {
|
||||
private threadSubscription?: Subscription;
|
||||
private subSubscription?: Subscription;
|
||||
|
||||
constructor(props: IRightButtonsProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isFollowingThread: true,
|
||||
|
@ -56,7 +69,7 @@ class RightButtonsContainer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps: IRightButtonsProps, nextState: IRigthButtonsState) {
|
||||
const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state;
|
||||
const { teamId } = this.props;
|
||||
if (nextProps.teamId !== teamId) {
|
||||
|
@ -86,37 +99,41 @@ class RightButtonsContainer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
observeThread = threadRecord => {
|
||||
const threadObservable = threadRecord.observe();
|
||||
observeThread = (threadRecord: TMessageModel) => {
|
||||
const threadObservable: Observable<TMessageModel> = threadRecord.observe();
|
||||
this.threadSubscription = threadObservable.subscribe(thread => this.updateThread(thread));
|
||||
};
|
||||
|
||||
updateThread = thread => {
|
||||
updateThread = (thread: TMessageModel) => {
|
||||
const { userId } = this.props;
|
||||
this.setState({
|
||||
isFollowingThread: thread.replies && !!thread.replies.find(t => t === userId)
|
||||
isFollowingThread: (thread.replies && !!thread.replies.find(t => t === userId)) ?? false
|
||||
});
|
||||
};
|
||||
|
||||
observeSubscription = subRecord => {
|
||||
observeSubscription = (subRecord: TSubscriptionModel) => {
|
||||
const subObservable = subRecord.observe();
|
||||
this.subSubscription = subObservable.subscribe(sub => {
|
||||
this.updateSubscription(sub);
|
||||
});
|
||||
};
|
||||
|
||||
updateSubscription = sub => {
|
||||
updateSubscription = (sub: TSubscriptionModel) => {
|
||||
this.setState({
|
||||
tunread: sub?.tunread,
|
||||
tunreadUser: sub?.tunreadUser,
|
||||
tunreadGroup: sub?.tunreadGroup
|
||||
tunread: sub?.tunread ?? [],
|
||||
tunreadUser: sub?.tunreadUser ?? [],
|
||||
tunreadGroup: sub?.tunreadGroup ?? []
|
||||
});
|
||||
};
|
||||
|
||||
goTeamChannels = () => {
|
||||
logEvent(events.ROOM_GO_TEAM_CHANNELS);
|
||||
const { navigation, isMasterDetail, teamId } = this.props;
|
||||
if (!teamId) {
|
||||
return;
|
||||
}
|
||||
if (isMasterDetail) {
|
||||
// @ts-ignore TODO: find a way to make this work
|
||||
navigation.navigate('ModalStackNavigator', {
|
||||
screen: 'TeamChannelsView',
|
||||
params: { teamId }
|
||||
|
@ -129,23 +146,31 @@ class RightButtonsContainer extends Component {
|
|||
goThreadsView = () => {
|
||||
logEvent(events.ROOM_GO_THREADS);
|
||||
const { rid, t, navigation, isMasterDetail } = this.props;
|
||||
if (!rid) {
|
||||
return;
|
||||
}
|
||||
if (isMasterDetail) {
|
||||
// @ts-ignore TODO: find a way to make this work
|
||||
navigation.navigate('ModalStackNavigator', { screen: 'ThreadMessagesView', params: { rid, t } });
|
||||
} else {
|
||||
navigation.navigate('ThreadMessagesView', { rid, t });
|
||||
navigation.navigate('ThreadMessagesView', { rid, t: t as SubscriptionType });
|
||||
}
|
||||
};
|
||||
|
||||
goSearchView = () => {
|
||||
logEvent(events.ROOM_GO_SEARCH);
|
||||
const { rid, t, navigation, isMasterDetail, encrypted } = this.props;
|
||||
if (!rid) {
|
||||
return;
|
||||
}
|
||||
if (isMasterDetail) {
|
||||
// @ts-ignore TODO: find a way to make this work
|
||||
navigation.navigate('ModalStackNavigator', {
|
||||
screen: 'SearchMessagesView',
|
||||
params: { rid, showCloseModal: true, encrypted }
|
||||
});
|
||||
} else {
|
||||
navigation.navigate('SearchMessagesView', { rid, t, encrypted });
|
||||
navigation.navigate('SearchMessagesView', { rid, t: t as SubscriptionType, encrypted });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -194,9 +219,9 @@ class RightButtonsContainer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
userId: getUserSelector(state).id,
|
||||
threadsEnabled: state.settings.Threads_enabled,
|
||||
threadsEnabled: state.settings.Threads_enabled as boolean,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import sharedStyles from '../Styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -34,7 +34,8 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const DateSeparator = React.memo(({ ts, unread, theme }) => {
|
||||
const DateSeparator = ({ ts, unread }: { ts: Date | string | null; unread: boolean }): React.ReactElement => {
|
||||
const { theme } = useTheme();
|
||||
const date = ts ? moment(ts).format('LL') : null;
|
||||
const unreadLine = { backgroundColor: themes[theme].dangerColor };
|
||||
const unreadText = { color: themes[theme].dangerColor };
|
||||
|
@ -61,12 +62,6 @@ const DateSeparator = React.memo(({ ts, unread, theme }) => {
|
|||
<View style={[styles.line, unreadLine]} />
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
DateSeparator.propTypes = {
|
||||
ts: PropTypes.instanceOf(Date),
|
||||
unread: PropTypes.bool,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default DateSeparator;
|
|
@ -1,7 +1,7 @@
|
|||
import React, { Component } from 'react';
|
||||
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import database from '../../lib/database';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
@ -11,6 +11,7 @@ import { CustomIcon } from '../../lib/Icons';
|
|||
import { themes } from '../../constants/colors';
|
||||
import sharedStyles from '../Styles';
|
||||
import { withTheme } from '../../theme';
|
||||
import { IUser, TUploadModel } from '../../definitions';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -51,23 +52,26 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class UploadProgress extends Component {
|
||||
static propTypes = {
|
||||
width: PropTypes.number,
|
||||
rid: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
token: PropTypes.string.isRequired
|
||||
}),
|
||||
baseUrl: PropTypes.string.isRequired
|
||||
};
|
||||
interface IUploadProgressProps {
|
||||
width: number;
|
||||
rid: string;
|
||||
user: Pick<IUser, 'id' | 'username' | 'token'>;
|
||||
baseUrl: string;
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
interface IUploadProgressState {
|
||||
uploads: TUploadModel[];
|
||||
}
|
||||
|
||||
class UploadProgress extends Component<IUploadProgressProps, IUploadProgressState> {
|
||||
private mounted = false;
|
||||
private ranInitialUploadCheck = false;
|
||||
private uploadsObservable?: Observable<TUploadModel[]>;
|
||||
private uploadsSubscription?: Subscription;
|
||||
|
||||
constructor(props: IUploadProgressProps) {
|
||||
super(props);
|
||||
this.mounted = false;
|
||||
this.ranInitialUploadCheck = false;
|
||||
this.state = {
|
||||
uploads: []
|
||||
};
|
||||
|
@ -97,6 +101,7 @@ class UploadProgress extends Component {
|
|||
if (this.mounted) {
|
||||
this.setState({ uploads });
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.state.uploads = uploads;
|
||||
}
|
||||
if (!this.ranInitialUploadCheck) {
|
||||
|
@ -112,7 +117,7 @@ class UploadProgress extends Component {
|
|||
if (!RocketChat.isUploadActive(u.path)) {
|
||||
try {
|
||||
const db = database.active;
|
||||
await db.action(async () => {
|
||||
await db.write(async () => {
|
||||
await u.update(() => {
|
||||
u.error = true;
|
||||
});
|
||||
|
@ -124,10 +129,10 @@ class UploadProgress extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
deleteUpload = async item => {
|
||||
deleteUpload = async (item: TUploadModel) => {
|
||||
try {
|
||||
const db = database.active;
|
||||
await db.action(async () => {
|
||||
await db.write(async () => {
|
||||
await item.destroyPermanently();
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -135,7 +140,7 @@ class UploadProgress extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
cancelUpload = async item => {
|
||||
cancelUpload = async (item: TUploadModel) => {
|
||||
try {
|
||||
await RocketChat.cancelUpload(item);
|
||||
} catch (e) {
|
||||
|
@ -143,12 +148,12 @@ class UploadProgress extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
tryAgain = async item => {
|
||||
tryAgain = async (item: TUploadModel) => {
|
||||
const { rid, baseUrl: server, user } = this.props;
|
||||
|
||||
try {
|
||||
const db = database.active;
|
||||
await db.action(async () => {
|
||||
await db.write(async () => {
|
||||
await item.update(() => {
|
||||
item.error = false;
|
||||
});
|
||||
|
@ -159,44 +164,44 @@ class UploadProgress extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
renderItemContent = item => {
|
||||
renderItemContent = (item: TUploadModel) => {
|
||||
const { width, theme } = this.props;
|
||||
|
||||
if (!item.error) {
|
||||
return [
|
||||
<View key='row' style={styles.row}>
|
||||
<CustomIcon name='attach' size={20} color={themes[theme].auxiliaryText} />
|
||||
<CustomIcon name='attach' size={20} color={themes[theme!].auxiliaryText} />
|
||||
<Text
|
||||
style={[styles.descriptionContainer, styles.descriptionText, { color: themes[theme].auxiliaryText }]}
|
||||
style={[styles.descriptionContainer, styles.descriptionText, { color: themes[theme!].auxiliaryText }]}
|
||||
numberOfLines={1}>
|
||||
{I18n.t('Uploading')} {item.name}
|
||||
</Text>
|
||||
<CustomIcon name='close' size={20} color={themes[theme].auxiliaryText} onPress={() => this.cancelUpload(item)} />
|
||||
<CustomIcon name='close' size={20} color={themes[theme!].auxiliaryText} onPress={() => this.cancelUpload(item)} />
|
||||
</View>,
|
||||
<View
|
||||
key='progress'
|
||||
style={[styles.progress, { width: (width * item.progress) / 100, backgroundColor: themes[theme].tintColor }]}
|
||||
style={[styles.progress, { width: (width * (item.progress ?? 0)) / 100, backgroundColor: themes[theme!].tintColor }]}
|
||||
/>
|
||||
];
|
||||
}
|
||||
return (
|
||||
<View style={styles.row}>
|
||||
<CustomIcon name='warning' size={20} color={themes[theme].dangerColor} />
|
||||
<CustomIcon name='warning' size={20} color={themes[theme!].dangerColor} />
|
||||
<View style={styles.descriptionContainer}>
|
||||
<Text style={[styles.descriptionText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
|
||||
<Text style={[styles.descriptionText, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
|
||||
{I18n.t('Error_uploading')} {item.name}
|
||||
</Text>
|
||||
<TouchableOpacity onPress={() => this.tryAgain(item)}>
|
||||
<Text style={[styles.tryAgainButtonText, { color: themes[theme].tintColor }]}>{I18n.t('Try_again')}</Text>
|
||||
<Text style={[styles.tryAgainButtonText, { color: themes[theme!].tintColor }]}>{I18n.t('Try_again')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<CustomIcon name='close' size={20} color={themes[theme].auxiliaryText} onPress={() => this.deleteUpload(item)} />
|
||||
<CustomIcon name='close' size={20} color={themes[theme!].auxiliaryText} onPress={() => this.deleteUpload(item)} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: transform into stateless and update based on its own observable changes
|
||||
renderItem = (item, index) => {
|
||||
renderItem = (item: TUploadModel, index: number) => {
|
||||
const { theme } = this.props;
|
||||
|
||||
return (
|
||||
|
@ -206,8 +211,8 @@ class UploadProgress extends Component {
|
|||
styles.item,
|
||||
index !== 0 ? { marginTop: 10 } : {},
|
||||
{
|
||||
backgroundColor: themes[theme].chatComponentBackground,
|
||||
borderColor: themes[theme].borderColor
|
||||
backgroundColor: themes[theme!].chatComponentBackground,
|
||||
borderColor: themes[theme!].borderColor
|
||||
}
|
||||
]}>
|
||||
{this.renderItemContent(item)}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,41 +0,0 @@
|
|||
import { getMessageById } from '../../../lib/database/services/Message';
|
||||
import { getThreadMessageById } from '../../../lib/database/services/ThreadMessage';
|
||||
import getSingleMessage from '../../../lib/methods/getSingleMessage';
|
||||
|
||||
const getMessageInfo = async messageId => {
|
||||
let result;
|
||||
result = await getMessageById(messageId);
|
||||
if (result) {
|
||||
return {
|
||||
id: result.id,
|
||||
rid: result.subscription.id,
|
||||
tmid: result.tmid,
|
||||
msg: result.msg
|
||||
};
|
||||
}
|
||||
|
||||
result = await getThreadMessageById(messageId);
|
||||
if (result) {
|
||||
return {
|
||||
id: result.id,
|
||||
rid: result.subscription.id,
|
||||
tmid: result.rid,
|
||||
msg: result.msg
|
||||
};
|
||||
}
|
||||
|
||||
result = await getSingleMessage(messageId);
|
||||
if (result) {
|
||||
return {
|
||||
id: result._id,
|
||||
rid: result.rid,
|
||||
tmid: result.tmid,
|
||||
msg: result.msg,
|
||||
fromServer: true
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default getMessageInfo;
|
|
@ -0,0 +1,41 @@
|
|||
import { TMessageModel, TThreadMessageModel } from '../../../definitions';
|
||||
import { getMessageById } from '../../../lib/database/services/Message';
|
||||
import { getThreadMessageById } from '../../../lib/database/services/ThreadMessage';
|
||||
import getSingleMessage from '../../../lib/methods/getSingleMessage';
|
||||
|
||||
const getMessageInfo = async (messageId: string): Promise<TMessageModel | TThreadMessageModel | any | null> => {
|
||||
const message = await getMessageById(messageId);
|
||||
if (message) {
|
||||
return {
|
||||
id: message.id,
|
||||
rid: message?.subscription?.id,
|
||||
tmid: message.tmid,
|
||||
msg: message.msg
|
||||
};
|
||||
}
|
||||
|
||||
const threadMessage = await getThreadMessageById(messageId);
|
||||
if (threadMessage) {
|
||||
return {
|
||||
id: threadMessage.id,
|
||||
rid: threadMessage?.subscription?.id,
|
||||
tmid: threadMessage.rid,
|
||||
msg: threadMessage.msg
|
||||
};
|
||||
}
|
||||
|
||||
const singleMessage: any = await getSingleMessage(messageId);
|
||||
if (singleMessage) {
|
||||
return {
|
||||
id: singleMessage._id,
|
||||
rid: singleMessage.rid,
|
||||
tmid: singleMessage.tmid,
|
||||
msg: singleMessage.msg,
|
||||
fromServer: true
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default getMessageInfo;
|
|
@ -1,10 +0,0 @@
|
|||
import RocketChat from '../../../lib/rocketchat';
|
||||
|
||||
const getMessages = room => {
|
||||
if (room.lastOpen) {
|
||||
return RocketChat.loadMissedMessages(room);
|
||||
} else {
|
||||
return RocketChat.loadMessagesForRoom(room);
|
||||
}
|
||||
};
|
||||
export default getMessages;
|
|
@ -0,0 +1,23 @@
|
|||
import loadMessagesForRoom from '../../../lib/methods/loadMessagesForRoom';
|
||||
import loadMissedMessages from '../../../lib/methods/loadMissedMessages';
|
||||
|
||||
// TODO: clarify latest vs lastOpen
|
||||
const getMessages = ({
|
||||
rid,
|
||||
t,
|
||||
latest,
|
||||
lastOpen,
|
||||
loaderItem
|
||||
}: {
|
||||
rid: string;
|
||||
t?: string;
|
||||
latest?: Date;
|
||||
lastOpen?: Date;
|
||||
loaderItem?: any; // TODO: type this
|
||||
}): Promise<void> => {
|
||||
if (lastOpen) {
|
||||
return loadMissedMessages({ rid, lastOpen });
|
||||
}
|
||||
return loadMessagesForRoom({ rid, t: t as any, latest, loaderItem });
|
||||
};
|
||||
export default getMessages;
|
|
@ -1,27 +0,0 @@
|
|||
import {
|
||||
MESSAGE_TYPE_LOAD_MORE,
|
||||
MESSAGE_TYPE_LOAD_NEXT_CHUNK,
|
||||
MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK
|
||||
} from '../../../constants/messageTypeLoad';
|
||||
import RocketChat from '../../../lib/rocketchat';
|
||||
|
||||
const getMoreMessages = ({ rid, t, tmid, loaderItem }) => {
|
||||
if ([MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK].includes(loaderItem.t)) {
|
||||
return RocketChat.loadMessagesForRoom({
|
||||
rid,
|
||||
t,
|
||||
latest: loaderItem.ts,
|
||||
loaderItem
|
||||
});
|
||||
}
|
||||
|
||||
if (loaderItem.t === MESSAGE_TYPE_LOAD_NEXT_CHUNK) {
|
||||
return RocketChat.loadNextMessages({
|
||||
rid,
|
||||
tmid,
|
||||
ts: loaderItem.ts,
|
||||
loaderItem
|
||||
});
|
||||
}
|
||||
};
|
||||
export default getMoreMessages;
|
|
@ -0,0 +1,40 @@
|
|||
import { MessageType, SubscriptionType, TAnyMessageModel } from '../../../definitions';
|
||||
import loadMessagesForRoom from '../../../lib/methods/loadMessagesForRoom';
|
||||
import loadNextMessages from '../../../lib/methods/loadNextMessages';
|
||||
import {
|
||||
MESSAGE_TYPE_LOAD_MORE,
|
||||
MESSAGE_TYPE_LOAD_NEXT_CHUNK,
|
||||
MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK
|
||||
} from '../../../constants/messageTypeLoad';
|
||||
|
||||
const getMoreMessages = ({
|
||||
rid,
|
||||
t,
|
||||
tmid,
|
||||
loaderItem
|
||||
}: {
|
||||
rid: string;
|
||||
t: SubscriptionType;
|
||||
tmid?: string;
|
||||
loaderItem: TAnyMessageModel;
|
||||
}): Promise<void> => {
|
||||
if ([MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK].includes(loaderItem.t as MessageType)) {
|
||||
return loadMessagesForRoom({
|
||||
rid,
|
||||
t: t as any,
|
||||
latest: loaderItem.ts as Date,
|
||||
loaderItem
|
||||
});
|
||||
}
|
||||
|
||||
if (loaderItem.t === MESSAGE_TYPE_LOAD_NEXT_CHUNK) {
|
||||
return loadNextMessages({
|
||||
rid,
|
||||
tmid,
|
||||
ts: loaderItem.ts as Date,
|
||||
loaderItem
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
export default getMoreMessages;
|
|
@ -1,6 +1,6 @@
|
|||
import RocketChat from '../../../lib/rocketchat';
|
||||
|
||||
// unlike getMessages, sync isn't required for threads, because loadMissedMessages does it already
|
||||
const getThreadMessages = (tmid, rid) => RocketChat.loadThreadMessages({ tmid, rid });
|
||||
const getThreadMessages = (tmid: string, rid: string): Promise<void> => RocketChat.loadThreadMessages({ tmid, rid });
|
||||
|
||||
export default getThreadMessages;
|
|
@ -1,5 +0,0 @@
|
|||
import RocketChat from '../../../lib/rocketchat';
|
||||
|
||||
const readMessages = (rid, newLastOpen) => RocketChat.readMessages(rid, newLastOpen, true);
|
||||
|
||||
export default readMessages;
|
|
@ -0,0 +1,5 @@
|
|||
import RocketChat from '../../../lib/rocketchat';
|
||||
|
||||
const readMessages = (rid: string, newLastOpen: Date): Promise<void> => RocketChat.readMessages(rid, newLastOpen, true);
|
||||
|
||||
export default readMessages;
|
|
@ -257,6 +257,7 @@ class SearchMessagesView extends React.Component<ISearchMessagesViewProps, ISear
|
|||
const { user, baseUrl, theme, useRealName } = this.props;
|
||||
return (
|
||||
<Message
|
||||
// @ts-ignore IMessage | TMessageModel?
|
||||
item={item}
|
||||
baseUrl={baseUrl}
|
||||
user={user}
|
||||
|
|
|
@ -29,7 +29,7 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
interface ISelectListViewState {
|
||||
data: IRoom[];
|
||||
data?: IRoom[];
|
||||
dataFiltered?: IRoom[];
|
||||
isSearching: boolean;
|
||||
selected: string[];
|
||||
|
@ -53,7 +53,7 @@ class SelectListView extends React.Component<ISelectListViewProps, ISelectListVi
|
|||
|
||||
private isSearch: boolean;
|
||||
|
||||
private onSearch: (text: string) => Partial<IRoom[]>;
|
||||
private onSearch?: (text: string) => Promise<Partial<IRoom[]> | any>;
|
||||
|
||||
private isRadio?: boolean;
|
||||
|
||||
|
@ -61,10 +61,10 @@ class SelectListView extends React.Component<ISelectListViewProps, ISelectListVi
|
|||
super(props);
|
||||
const data = props.route?.params?.data;
|
||||
this.title = props.route?.params?.title;
|
||||
this.infoText = props.route?.params?.infoText;
|
||||
this.infoText = props.route?.params?.infoText ?? '';
|
||||
this.nextAction = props.route?.params?.nextAction;
|
||||
this.showAlert = props.route?.params?.showAlert;
|
||||
this.isSearch = props.route?.params?.isSearch;
|
||||
this.showAlert = props.route?.params?.showAlert ?? (() => {});
|
||||
this.isSearch = props.route?.params?.isSearch ?? false;
|
||||
this.onSearch = props.route?.params?.onSearch;
|
||||
this.isRadio = props.route?.params?.isRadio;
|
||||
this.state = {
|
||||
|
@ -122,7 +122,7 @@ class SelectListView extends React.Component<ISelectListViewProps, ISelectListVi
|
|||
search = async (text: string) => {
|
||||
try {
|
||||
this.setState({ isSearching: true });
|
||||
const result = (await this.onSearch(text)) as IRoom[];
|
||||
const result = (await this.onSearch?.(text)) as IRoom[];
|
||||
this.setState({ dataFiltered: result });
|
||||
} catch (e) {
|
||||
log(e);
|
||||
|
|
|
@ -28,8 +28,7 @@ import Preview from './Preview';
|
|||
import Header from './Header';
|
||||
import styles from './styles';
|
||||
import { IAttachment } from './interfaces';
|
||||
import { ISubscription } from '../../definitions/ISubscription';
|
||||
import { IUser } from '../../definitions';
|
||||
import { IUser, TSubscriptionModel } from '../../definitions';
|
||||
|
||||
interface IShareViewState {
|
||||
selected: IAttachment;
|
||||
|
@ -37,7 +36,7 @@ interface IShareViewState {
|
|||
readOnly: boolean;
|
||||
attachments: IAttachment[];
|
||||
text: string;
|
||||
room: ISubscription;
|
||||
room: TSubscriptionModel;
|
||||
thread: any; // change
|
||||
maxFileSize: number;
|
||||
mediaAllowList: string;
|
||||
|
@ -153,7 +152,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
|
|||
getReadOnly = async () => {
|
||||
const { room } = this.state;
|
||||
const { user } = this.props;
|
||||
const readOnly = await isReadOnly(room, user);
|
||||
const readOnly = await isReadOnly(room, user.username);
|
||||
return readOnly;
|
||||
};
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ const Item = ({ item, useRealName, user, badgeColor, onPress, toggleFollowThread
|
|||
const username = (useRealName && item?.u?.name) || item?.u?.username;
|
||||
let time;
|
||||
if (item?.ts) {
|
||||
// @ts-ignore TODO: to be fixed after we unify our types
|
||||
time = formatDateThreads(item.ts);
|
||||
}
|
||||
|
||||
|
|
|
@ -215,9 +215,6 @@ describe('Discussion', () => {
|
|||
await waitFor(element(by.id(`room-view-title-${discussionName}`)))
|
||||
.toExist()
|
||||
.withTimeout(5000);
|
||||
await waitFor(element(by.id('messagebox')))
|
||||
.toBeVisible()
|
||||
.withTimeout(60000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,9 @@ const toBeConverted = `to-be-converted-${data.random}`;
|
|||
const toBeMoved = `to-be-moved-${data.random}`;
|
||||
|
||||
const createChannel = async room => {
|
||||
await waitFor(element(by.id('rooms-list-view-create-channel')))
|
||||
.toBeVisible()
|
||||
.withTimeout(5000);
|
||||
await element(by.id('rooms-list-view-create-channel')).tap();
|
||||
await waitFor(element(by.id('new-message-view')))
|
||||
.toExist()
|
||||
|
|
|
@ -1682,7 +1682,7 @@
|
|||
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 4.25.0;
|
||||
MARKETING_VERSION = 4.26.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;
|
||||
|
@ -1719,7 +1719,7 @@
|
|||
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 4.25.0;
|
||||
MARKETING_VERSION = 4.26.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.25.0</string>
|
||||
<string>4.26.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.25.0</string>
|
||||
<string>4.26.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>KeychainGroup</key>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "rocket-chat-reactnative",
|
||||
"version": "4.25.0",
|
||||
"version": "4.26.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "react-native start",
|
||||
|
|
Loading…
Reference in New Issue