Compare commits

...

59 Commits

Author SHA1 Message Date
AlexAlexandre 2bc39cc4b8 minor tweak 2021-12-21 10:33:31 -03:00
Reinaldo Neto 037c921f46 minor tweak in IThreads 2021-12-21 00:59:55 -03:00
AlexAlexandre b26838f5ca minor tweak 2021-12-20 10:24:27 -03:00
AlexAlexandre 5e26954644 update IMessage 2021-12-17 22:44:17 -03:00
AlexAlexandre 6a9f878d28 fix problem with difference between IRoom and IRoomModel 2021-12-17 22:32:44 -03:00
AlexAlexandre 8192b391ea fix room.observer problem 2021-12-16 21:22:22 -03:00
AlexAlexandre 11c347e222 creating an interface for room state 2021-12-16 20:57:58 -03:00
AlexAlexandre 9d4f99ee84 minor tweaks 2021-12-16 15:15:14 -03:00
AlexAlexandre cdcfbe5f35 updating the IRoom and ISubscriptions 2021-12-16 15:13:12 -03:00
AlexAlexandre 49e30dc723 minor tweaks 2021-12-16 00:02:18 -03:00
AlexAlexandre 69c4e7a80b minor tweak 2021-12-15 23:37:09 -03:00
AlexAlexandre 4b9ed36fa5 minor tweak and fix the ISubscriptions interface 2021-12-15 23:35:33 -03:00
AlexAlexandre f68a0ac145 minor tweak 2021-12-15 23:27:19 -03:00
AlexAlexandre 26d8d5b8dc fix screen navigators 2021-12-15 22:45:34 -03:00
AlexAlexandre 29f554caab minor tweak 2021-12-15 01:15:29 -03:00
AlexAlexandre 60583eb517 minor tweak 2021-12-15 01:07:38 -03:00
AlexAlexandre 18b2f3388d minor tweak 2021-12-15 00:58:11 -03:00
AlexAlexandre 09a8254813 creating ISubscriptions 2021-12-15 00:48:03 -03:00
AlexAlexandre 667f26e7cd minor tweaks 2021-12-15 00:26:26 -03:00
AlexAlexandre d29b661ccc minor tweaks 2021-12-14 23:56:25 -03:00
AlexAlexandre 63a3e91fd9 creating the IThread interface 2021-12-14 23:09:30 -03:00
AlexAlexandre d649eb5f4e refactor: change the room interface 2021-12-14 20:44:49 -03:00
AlexAlexandre 5635fbbebb minor tweak 2021-12-10 12:06:30 -03:00
AlexAlexandre 13ee494d8a minor tweak 2021-12-09 21:10:35 -03:00
AlexAlexandre 91f73a5101 typing replyBroadcast 2021-12-09 16:59:49 -03:00
AlexAlexandre b211b5a948 updating the UploadProgress 2021-12-09 16:57:53 -03:00
AlexAlexandre 5622d1b25d typing the listRef as a flatList and removing the `.getNode()` 2021-12-09 16:43:27 -03:00
AlexAlexandre 2ca5a06c5e updating the IRoomListProps 2021-12-09 15:08:05 -03:00
AlexAlexandre 0694666a21 chore: minor tweak 2021-12-08 13:09:10 -03:00
AlexAlexandre 0cf9405a65 chore: minor tweak 2021-12-08 04:12:58 -03:00
AlexAlexandre 9519ae50e7 chore: minor tweak 2021-12-08 03:10:34 -03:00
AlexAlexandre 475d6be5b0 chore: minor tweak 2021-12-08 02:41:40 -03:00
AlexAlexandre e6a05f4434 chore: minor tweaks 2021-12-08 02:19:44 -03:00
AlexAlexandre 1dc978cc88 chore: resolving problems with the RightButtons 2021-12-08 00:45:19 -03:00
AlexAlexandre e738f5c80f chore: minor tweak 2021-12-07 22:33:06 -03:00
AlexAlexandre 42d1744cc3 chore: removing forgotten property 2021-12-07 21:47:02 -03:00
AlexAlexandre 228e073f58 Merge branch 'develop' into chore/ts-RoomView 2021-12-07 19:34:47 -03:00
AlexAlexandre 2d45e288fb chore: removing forgotten property 2021-12-07 17:04:21 -03:00
AlexAlexandre bd34344a0c chore: minor tweaks 2021-12-07 16:50:32 -03:00
AlexAlexandre 2493cf3f59 chore: minor tweaks 2021-12-07 15:37:34 -03:00
AlexAlexandre ba2b9e862b chore: minor tweaks 2021-12-07 15:13:30 -03:00
AlexAlexandre 244c2a05a9 chore: minor tweaks 2021-12-07 01:18:58 -03:00
AlexAlexandre f68ed794d2 chore: minor tweaks 2021-12-06 23:16:26 -03:00
AlexAlexandre d675828c28 chore: minor tweaks 2021-12-06 21:58:57 -03:00
AlexAlexandre 8a4892e61e chore: changing other components to room requirements 2021-12-06 21:30:49 -03:00
AlexAlexandre 55982566f7 chore: migrate room to ts 2021-12-03 20:19:44 -03:00
AlexAlexandre 161633e299 Merge branch 'develop' into chore/ts-RoomView 2021-12-03 18:28:27 -03:00
AlexAlexandre 8239c4489a chore: migrate services room to ts 2021-12-03 18:26:47 -03:00
AlexAlexandre a6146bd02b chore: Migrate List room to ts 2021-12-03 10:06:25 -03:00
AlexAlexandre dc3e21056e chore: Migrate List room to ts 2021-12-02 19:11:16 -03:00
AlexAlexandre 6e5df501cb chore: Migrate LoadMore room to ts 2021-12-02 18:19:52 -03:00
AlexAlexandre 46cf0f8e6f chore: Migrate RightButtonsContainer room to ts 2021-12-02 17:47:01 -03:00
AlexAlexandre 9b746cf1e0 chore: Migrate LeftButtons room to ts 2021-12-02 17:11:49 -03:00
AlexAlexandre 87f7c15dfa chore: Migrate UploadProgress room to ts 2021-12-02 17:03:56 -03:00
AlexAlexandre b1d1e29d41 Merge branch 'develop' into chore/ts-RoomView 2021-12-02 14:50:55 -03:00
AlexAlexandre 7408cf88fe chore: Migrate JoinCode room to ts 2021-12-02 00:20:03 -03:00
AlexAlexandre 1849c295a2 chore: Migrate ReactionPicker room to ts 2021-12-01 23:52:17 -03:00
AlexAlexandre 3c5d0f127c chore: Migrate separator room to ts 2021-12-01 23:31:02 -03:00
AlexAlexandre 161e667678 chore: start the RoomView migrate to typescript 2021-12-01 23:24:51 -03:00
35 changed files with 701 additions and 349 deletions

View File

@ -15,15 +15,11 @@ import { showConfirmationAlert } from '../../utils/info';
import { useActionSheet } from '../ActionSheet'; import { useActionSheet } from '../ActionSheet';
import Header, { HEADER_HEIGHT } from './Header'; import Header, { HEADER_HEIGHT } from './Header';
import events from '../../utils/log/events'; import events from '../../utils/log/events';
import { IRoom } from '../../definitions/IRoom';
interface IMessageActions { interface IMessageActions {
room: { room: IRoom;
rid: string | number; tmid?: string;
autoTranslateLanguage: any;
autoTranslate: any;
reactWhenReadOnly: any;
};
tmid: string;
user: { user: {
id: string | number; id: string | number;
}; };

View File

@ -69,11 +69,6 @@ interface IRCTextInputProps extends TextInputProps {
} }
export default class RCTextInput extends React.PureComponent<IRCTextInputProps, any> { export default class RCTextInput extends React.PureComponent<IRCTextInputProps, any> {
static defaultProps = {
error: {},
theme: 'light'
};
state = { state = {
showPassword: false showPassword: false
}; };

View File

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

View File

@ -1,3 +1,44 @@
export interface IMessage { import Model from '@nozbe/watermelondb/Model';
msg: string;
import { ISubscriptions } from './ISubscriptions';
export interface IMessage extends ISubscriptions {
id: string;
rid: string;
ts: number;
u: string;
alias: string;
parse_urls: string;
_updated_at: number;
msg?: string;
t?: string;
groupable?: boolean;
avatar?: string;
emoji?: string;
attachments?: string;
urls?: string;
status?: number;
pinned?: boolean;
starred?: boolean;
edited_by?: string;
reactions?: string;
role?: string;
drid?: string;
dcount?: number;
dlm?: number;
tmid?: string;
tcount?: number;
tlm?: number;
replies?: string;
mentions?: string;
channels?: string;
auto_translate?: boolean;
translations?: string;
tmsg?: string;
blocks?: string;
e2e?: string;
tshow?: boolean;
md?: string;
} }
export type TMessageModel = IMessage & Model;

View File

@ -0,0 +1,5 @@
export interface IReaction {
_id: string;
emoji: string;
usernames: string[];
}

View File

@ -1,14 +1,18 @@
import Model from '@nozbe/watermelondb/Model';
import { IRocketChatRecord } from './IRocketChatRecord'; import { IRocketChatRecord } from './IRocketChatRecord';
import { ISubscriptions } from './ISubscriptions';
export enum RoomType { export enum RoomType {
GROUP = 'p', GROUP = 'p',
DIRECT = 'd', DIRECT = 'd',
CHANNEL = 'c', CHANNEL = 'c',
OMNICHANNEL = 'l', OMNICHANNEL = 'l',
THREAD = 'thread' THREAD = 'thread',
E2E_MESSAGE_TYPE = 'e2e'
} }
export interface IRoom extends IRocketChatRecord { export interface IRoom extends IRocketChatRecord, ISubscriptions {
rid: string; rid: string;
t: RoomType; t: RoomType;
name: string; name: string;
@ -20,8 +24,10 @@ export interface IRoom extends IRocketChatRecord {
teamId?: string; teamId?: string;
encrypted?: boolean; encrypted?: boolean;
visitor?: boolean; visitor?: boolean;
autoTranslateLanguage?: boolean; usedCannedResponse?: string;
autoTranslate?: boolean; bannerClosed: boolean;
observe?: Function; lastOpen?: Date;
usedCannedResponse: string; draftMessage?: string;
} }
export type TRoomModel = IRoom & Model;

View File

@ -0,0 +1,33 @@
import Model from '@nozbe/watermelondb/Model';
export interface ISubscriptions {
_id: string;
name: string;
fname: string;
rid: string;
unread: number;
tunread: string;
tunreadUser: string;
tunreadGroup: string;
joinCodeRequired: boolean;
alert: boolean;
userMentions: object;
ls: Date;
jitsiTimeout: number;
ignored: any;
announcement: string;
sysMes: string;
archived: string;
broadcast: string;
autoTranslateLanguage: string;
autoTranslate: boolean;
reactWhenReadOnly: boolean;
f: boolean;
ro: boolean;
blocked: boolean;
blocker: boolean;
muted: boolean;
roles: string;
}
export type TSubscriptionsModel = ISubscriptions & Model;

View File

@ -0,0 +1,45 @@
import Model from '@nozbe/watermelondb/Model';
import { IAttachment } from './IAttachment';
import { IMention } from './IMention';
import { IReaction } from './IReaction';
import { RoomType } from './IRoom';
import { IUrl } from './IUrl';
export interface IThread {
id: string;
msg: string;
t: RoomType;
rid: string;
_updatedAt: Date;
ts: Date;
u: { _id: string; username: string; name: string };
alias: any;
parseUrls: any;
groupable: boolean;
avatar: string;
emoji: any;
attachments: IAttachment[];
urls: IUrl[];
status: number;
pinned: boolean;
starred: boolean;
editedBy: { username: string };
reactions: IReaction[];
role: string;
drid: string;
dcount: number;
dlm: number;
tmid: string;
tcount: number;
tlm: Date;
replies: string[];
mentions: IMention[];
channels: [];
unread: boolean;
autoTranslate: boolean;
translations: any;
e2e: any;
}
export type TThreadModel = IThread & Model;

6
app/definitions/IUrl.ts Normal file
View File

@ -0,0 +1,6 @@
export interface IUrl {
title: string;
description: string;
image: string;
url: string;
}

View File

@ -32,6 +32,7 @@ export type ModalStackParamList = {
rid: string; rid: string;
t: RoomType; t: RoomType;
joined: boolean; joined: boolean;
showCloseModal?: boolean;
}; };
RoomInfoView: { RoomInfoView: {
room: IRoom; room: IRoom;
@ -61,6 +62,9 @@ export type ModalStackParamList = {
t: RoomType; t: RoomType;
encrypted?: boolean; encrypted?: boolean;
showCloseModal?: boolean; showCloseModal?: boolean;
room?: IRoom;
member?: any;
joined?: boolean;
}; };
SelectedUsersView: { SelectedUsersView: {
maxUsers: number; maxUsers: number;

View File

@ -6,9 +6,12 @@ import { IOptionsField } from '../views/NotificationPreferencesView/options';
import { IServer } from '../definitions/IServer'; import { IServer } from '../definitions/IServer';
import { IAttachment } from '../definitions/IAttachment'; import { IAttachment } from '../definitions/IAttachment';
import { IMessage } from '../definitions/IMessage'; import { IMessage } from '../definitions/IMessage';
import { IRoom, RoomType } from '../definitions/IRoom'; import { IRoom, TRoomModel, RoomType } from '../definitions/IRoom';
import { ModalStackParamList } from './MasterDetailStack/types';
export type ChatsStackParamList = { export type ChatsStackParamList = {
ModalStackNavigator: NavigatorScreenParams<ModalStackParamList>;
E2ESaveYourPasswordStackNavigator: NavigatorScreenParams<E2ESaveYourPasswordStackParamList>;
RoomsListView: undefined; RoomsListView: undefined;
RoomView: { RoomView: {
rid: string; rid: string;
@ -18,10 +21,11 @@ export type ChatsStackParamList = {
name?: string; name?: string;
fname?: string; fname?: string;
prid?: string; prid?: string;
room: IRoom; room?: TRoomModel;
jumpToMessageId?: string; jumpToMessageId?: string;
jumpToThreadId?: string; jumpToThreadId?: string;
roomUserId?: string; roomUserId?: string;
usedCannedResponse?: boolean;
}; };
RoomActionsView: { RoomActionsView: {
room: IRoom; room: IRoom;
@ -45,6 +49,7 @@ export type ChatsStackParamList = {
member: any; member: any;
rid: string; rid: string;
t: RoomType; t: RoomType;
showCloseModal?: boolean;
}; };
RoomInfoEditView: { RoomInfoEditView: {
rid: string; rid: string;
@ -79,7 +84,7 @@ export type ChatsStackParamList = {
}; };
AutoTranslateView: { AutoTranslateView: {
rid: string; rid: string;
room: IRoom; room: TRoomModel;
}; };
DirectoryView: undefined; DirectoryView: undefined;
NotificationPrefView: { NotificationPrefView: {
@ -143,6 +148,9 @@ export type ChatsStackParamList = {
}; };
room: IRoom; room: IRoom;
}; };
AttachmentView: {
attachment: IAttachment;
};
}; };
export type ProfileStackParamList = { export type ProfileStackParamList = {

View File

@ -20,7 +20,7 @@ import SafeAreaView from '../../containers/SafeAreaView';
import getThreadName from '../../lib/methods/getThreadName'; import getThreadName from '../../lib/methods/getThreadName';
import styles from './styles'; import styles from './styles';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import { IRoom, RoomType } from '../../definitions/IRoom'; import { IRoom, RoomType, TRoomModel } from '../../definitions/IRoom';
interface IMessagesViewProps { interface IMessagesViewProps {
user: { user: {
@ -78,7 +78,7 @@ interface IParams {
name?: string; name?: string;
fname?: string; fname?: string;
prid?: string; prid?: string;
room: IRoom; room: TRoomModel;
jumpToMessageId?: string; jumpToMessageId?: string;
jumpToThreadId?: string; jumpToThreadId?: string;
roomUserId?: string; roomUserId?: string;

View File

@ -1,6 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import PropTypes from 'prop-types';
import { BorderlessButton, ScrollView } from 'react-native-gesture-handler'; import { BorderlessButton, ScrollView } from 'react-native-gesture-handler';
import Modal from 'react-native-modal'; import Modal from 'react-native-modal';
@ -9,8 +8,16 @@ import { CustomIcon } from '../../lib/Icons';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
interface IRoomBannerProps {
text: string;
title: string;
theme: string;
bannerClosed: boolean;
closeBanner(): void;
}
const Banner = React.memo( const Banner = React.memo(
({ text, title, theme, bannerClosed, closeBanner }) => { ({ text, title, theme, bannerClosed, closeBanner }: IRoomBannerProps) => {
const [showModal, openModal] = useState(false); const [showModal, openModal] = useState(false);
const toggleModal = () => openModal(prevState => !prevState); const toggleModal = () => openModal(prevState => !prevState);
@ -22,6 +29,7 @@ const Banner = React.memo(
style={[styles.bannerContainer, { backgroundColor: themes[theme].bannerBackground }]} style={[styles.bannerContainer, { backgroundColor: themes[theme].bannerBackground }]}
testID='room-view-banner' testID='room-view-banner'
onPress={toggleModal}> onPress={toggleModal}>
{/* @ts-ignore*/}
<Markdown msg={text} theme={theme} numberOfLines={1} style={[styles.bannerText]} preview /> <Markdown msg={text} theme={theme} numberOfLines={1} style={[styles.bannerText]} preview />
<BorderlessButton onPress={closeBanner}> <BorderlessButton onPress={closeBanner}>
<CustomIcon color={themes[theme].auxiliaryText} name='close' size={20} /> <CustomIcon color={themes[theme].auxiliaryText} name='close' size={20} />
@ -37,6 +45,7 @@ const Banner = React.memo(
<View style={[styles.modalView, { backgroundColor: themes[theme].bannerBackground }]}> <View style={[styles.modalView, { backgroundColor: themes[theme].bannerBackground }]}>
<Text style={[styles.bannerModalTitle, { color: themes[theme].auxiliaryText }]}>{title}</Text> <Text style={[styles.bannerModalTitle, { color: themes[theme].auxiliaryText }]}>{title}</Text>
<ScrollView style={styles.modalScrollView}> <ScrollView style={styles.modalScrollView}>
{/* @ts-ignore*/}
<Markdown msg={text} theme={theme} /> <Markdown msg={text} theme={theme} />
</ScrollView> </ScrollView>
</View> </View>
@ -51,12 +60,4 @@ const Banner = React.memo(
prevProps.text === nextProps.text && prevProps.theme === nextProps.theme && prevProps.bannerClosed === nextProps.bannerClosed prevProps.text === nextProps.text && prevProps.theme === nextProps.theme && prevProps.bannerClosed === nextProps.bannerClosed
); );
Banner.propTypes = {
text: PropTypes.string,
title: PropTypes.string,
theme: PropTypes.string,
bannerClosed: PropTypes.bool,
closeBanner: PropTypes.func
};
export default Banner; export default Banner;

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { ImageBackground, StyleSheet } from 'react-native'; import { ImageBackground, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
image: { image: {
@ -10,17 +9,18 @@ const styles = StyleSheet.create({
} }
}); });
const EmptyRoom = React.memo(({ length, mounted, theme, rid }) => { interface IEmptyRoomProps {
length: number;
mounted: boolean;
theme: string;
rid: string;
}
const EmptyRoom = React.memo(({ length, mounted, theme, rid }: IEmptyRoomProps) => {
if ((length === 0 && mounted) || !rid) { if ((length === 0 && mounted) || !rid) {
return <ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} />; return <ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} />;
} }
return null; return null;
}); });
EmptyRoom.propTypes = {
length: PropTypes.number.isRequired,
mounted: PropTypes.bool,
theme: PropTypes.string,
rid: PropTypes.string
};
export default EmptyRoom; export default EmptyRoom;

View File

@ -1,5 +1,4 @@
import React, { forwardRef, useImperativeHandle, useState } from 'react'; import React, { forwardRef, useImperativeHandle, useState } from 'react';
import PropTypes from 'prop-types';
import { InteractionManager, StyleSheet, Text, View } from 'react-native'; import { InteractionManager, StyleSheet, Text, View } from 'react-native';
import Modal from 'react-native-modal'; import Modal from 'react-native-modal';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -41,10 +40,18 @@ const styles = StyleSheet.create({
} }
}); });
interface IRoomJoinCodeProps {
rid: string;
t: string;
onJoin: Function;
isMasterDetail: boolean;
theme: string;
}
const JoinCode = React.memo( const JoinCode = React.memo(
forwardRef(({ rid, t, onJoin, isMasterDetail, theme }, ref) => { forwardRef(({ rid, t, onJoin, isMasterDetail, theme }: IRoomJoinCodeProps, ref) => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [error, setError] = useState(false); const [error, setError] = useState<any>(false);
const [code, setCode] = useState(''); const [code, setCode] = useState('');
const show = () => setVisible(true); const show = () => setVisible(true);
@ -64,7 +71,7 @@ const JoinCode = React.memo(
useImperativeHandle(ref, () => ({ show })); useImperativeHandle(ref, () => ({ show }));
return ( return (
<Modal transparent avoidKeyboard useNativeDriver isVisible={visible} hideModalContentWhileAnimating> <Modal avoidKeyboard useNativeDriver isVisible={visible} hideModalContentWhileAnimating>
<View style={styles.container} testID='join-code'> <View style={styles.container} testID='join-code'>
<View <View
style={[ style={[
@ -76,7 +83,7 @@ const JoinCode = React.memo(
<TextInput <TextInput
value={code} value={code}
theme={theme} theme={theme}
inputRef={e => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())} inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
returnKeyType='send' returnKeyType='send'
autoCapitalize='none' autoCapitalize='none'
onChangeText={setCode} onChangeText={setCode}
@ -111,15 +118,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: any) => ({
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail
}); });
export default connect(mapStateToProps, null, null, { forwardRef: true })(JoinCode); export default connect(mapStateToProps, null, null, { forwardRef: true })(JoinCode);

View File

@ -1,10 +1,10 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import { HeaderBackButton } from '@react-navigation/stack'; import { HeaderBackButton, StackNavigationProp } from '@react-navigation/stack';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import { ChatsStackParamList } from '../../stacks/types';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
avatar: { avatar: {
@ -13,10 +13,36 @@ const styles = StyleSheet.create({
} }
}); });
interface IRoomLeftButtonsProps {
tmid?: string;
unreadsCount: number & string;
navigation: StackNavigationProp<ChatsStackParamList>;
baseUrl: string;
userId: string;
token: string;
title: string;
t: string;
theme: string;
goRoomActionsView: Function;
isMasterDetail: boolean;
}
const LeftButtons = React.memo( const LeftButtons = React.memo(
({ tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, isMasterDetail }) => { ({
tmid,
unreadsCount,
navigation,
baseUrl,
userId,
token,
title,
t,
theme,
goRoomActionsView,
isMasterDetail
}: IRoomLeftButtonsProps) => {
if (!isMasterDetail || tmid) { if (!isMasterDetail || tmid) {
const onPress = useCallback(() => navigation.goBack()); const onPress = useCallback(() => navigation.goBack(), []);
const label = unreadsCount > 99 ? '+99' : unreadsCount || ' '; const label = unreadsCount > 99 ? '+99' : unreadsCount || ' ';
const labelLength = label.length ? label.length : 1; const labelLength = label.length ? label.length : 1;
const marginLeft = -2 * labelLength; const marginLeft = -2 * labelLength;
@ -39,18 +65,4 @@ const LeftButtons = React.memo(
} }
); );
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; export default LeftButtons;

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import { FlatList, StyleSheet } from 'react-native'; import { FlatList, FlatListProps, StyleSheet } from 'react-native';
import Animated from 'react-native-reanimated'; import Animated from 'react-native-reanimated';
import PropTypes from 'prop-types';
import { isIOS } from '../../../utils/deviceInfo'; import { isIOS } from '../../../utils/deviceInfo';
import scrollPersistTaps from '../../../utils/scrollPersistTaps'; import scrollPersistTaps from '../../../utils/scrollPersistTaps';
import { IRoomItem } from '../index';
import { IMessage } from '../../../definitions/IMessage';
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
@ -17,11 +18,16 @@ const styles = StyleSheet.create({
} }
}); });
const List = ({ listRef, ...props }) => ( interface IRoomListProps extends FlatListProps<IRoomItem | IMessage> {
listRef: React.Ref<FlatList>;
}
const List = ({ listRef, ...props }: IRoomListProps): JSX.Element => (
<AnimatedFlatList <AnimatedFlatList
testID='room-view-messages' testID='room-view-messages'
ref={listRef} ref={listRef}
keyExtractor={item => item.id} // @ts-ignore
keyExtractor={(item: IRoomItem) => item.id}
contentContainerStyle={styles.contentContainer} contentContainerStyle={styles.contentContainer}
style={styles.list} style={styles.list}
inverted inverted
@ -35,8 +41,4 @@ const List = ({ listRef, ...props }) => (
/> />
); );
List.propTypes = {
listRef: PropTypes.object
};
export default List; export default List;

View File

@ -1,7 +1,6 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types'; import Animated, { call, cond, greaterOrEq, useCode, Value } from 'react-native-reanimated';
import Animated, { call, cond, greaterOrEq, useCode } from 'react-native-reanimated';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
@ -30,11 +29,17 @@ const styles = StyleSheet.create({
} }
}); });
const NavBottomFAB = ({ y, onPress, isThread }) => { interface IRoomNavBottomFAB {
y: Value<number>;
onPress: Function;
isThread: boolean;
}
const NavBottomFAB = ({ y, onPress, isThread }: IRoomNavBottomFAB) => {
const { theme } = useTheme(); const { theme } = useTheme();
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const handleOnPress = useCallback(() => onPress()); const handleOnPress = useCallback(() => onPress(), []);
const toggle = v => setShow(v); const toggle = (v: boolean) => setShow(v);
useCode( useCode(
() => () =>
@ -65,10 +70,4 @@ const NavBottomFAB = ({ y, onPress, isThread }) => {
); );
}; };
NavBottomFAB.propTypes = {
y: Animated.Value,
onPress: PropTypes.func,
isThread: PropTypes.bool
};
export default NavBottomFAB; export default NavBottomFAB;

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import { RefreshControl } from 'react-native'; import { FlatList, NativeScrollEvent, NativeSyntheticEvent, RefreshControl, ViewToken } from 'react-native';
import PropTypes from 'prop-types';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import moment from 'moment'; import moment from 'moment';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import { Value, event } from 'react-native-reanimated'; import { Value, event } from 'react-native-reanimated';
import { Observable, Subscription } from 'rxjs';
import { StackNavigationProp } from '@react-navigation/stack';
import database from '../../../lib/database'; import database from '../../../lib/database';
import RocketChat from '../../../lib/rocketchat'; import RocketChat from '../../../lib/rocketchat';
@ -17,10 +18,14 @@ import debounce from '../../../utils/debounce';
import { compareServerVersion, methods } from '../../../lib/utils'; import { compareServerVersion, methods } from '../../../lib/utils';
import List from './List'; import List from './List';
import NavBottomFAB from './NavBottomFAB'; import NavBottomFAB from './NavBottomFAB';
import { ChatsStackParamList } from '../../../stacks/types';
import { IRoomItem } from '../index';
import { IThread } from '../../../definitions/IThread';
import { IMessage, TMessageModel } from '../../../definitions/IMessage';
const QUERY_SIZE = 50; const QUERY_SIZE = 50;
const onScroll = ({ y }) => const onScroll = ({ y }: { y: Value<number> }) =>
event( event(
[ [
{ {
@ -32,23 +37,43 @@ const onScroll = ({ y }) =>
{ useNativeDriver: true } { useNativeDriver: true }
); );
class ListContainer extends React.Component { interface IRoomListContainerProps {
static propTypes = { renderRow: Function;
renderRow: PropTypes.func, rid: string;
rid: PropTypes.string, tmid?: string;
tmid: PropTypes.string, theme: string;
theme: PropTypes.string, loading: boolean;
loading: PropTypes.bool, listRef: React.RefObject<FlatList>;
listRef: PropTypes.func, hideSystemMessages: any[];
hideSystemMessages: PropTypes.array, tunread: string;
tunread: PropTypes.array, ignored: [];
ignored: PropTypes.array, navigation: StackNavigationProp<ChatsStackParamList>;
navigation: PropTypes.object, showMessageInMainThread: boolean;
showMessageInMainThread: PropTypes.bool, serverVersion: string;
serverVersion: PropTypes.string }
};
constructor(props) { interface IRoomListContainerState {
messages: TMessageModel[];
refreshing: boolean;
highlightedMessage: string | null;
}
class ListContainer extends React.Component<IRoomListContainerProps, IRoomListContainerState> {
private count: number;
private mounted: boolean;
private animated: boolean;
private jumping: boolean;
private y: Value<number>;
private onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
private unsubscribeFocus: () => void;
private viewabilityConfig: { itemVisiblePercentThreshold: number };
private highlightedMessageTimeout?: ReturnType<typeof setTimeout> | false;
private thread?: IThread;
private messagesObservable?: Observable<TMessageModel[]>;
private messagesSubscription?: Subscription;
private viewableItems?: ViewToken[];
constructor(props: IRoomListContainerProps) {
super(props); super(props);
console.time(`${this.constructor.name} init`); console.time(`${this.constructor.name} init`);
console.time(`${this.constructor.name} mount`); console.time(`${this.constructor.name} mount`);
@ -78,7 +103,7 @@ class ListContainer extends React.Component {
console.timeEnd(`${this.constructor.name} mount`); console.timeEnd(`${this.constructor.name} mount`);
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: IRoomListContainerProps, nextState: IRoomListContainerState) {
const { refreshing, highlightedMessage } = this.state; const { refreshing, highlightedMessage } = this.state;
const { hideSystemMessages, theme, tunread, ignored, loading } = this.props; const { hideSystemMessages, theme, tunread, ignored, loading } = this.props;
if (theme !== nextProps.theme) { if (theme !== nextProps.theme) {
@ -105,7 +130,7 @@ class ListContainer extends React.Component {
return false; return false;
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: IRoomListContainerProps) {
const { hideSystemMessages } = this.props; const { hideSystemMessages } = this.props;
if (!dequal(hideSystemMessages, prevProps.hideSystemMessages)) { if (!dequal(hideSystemMessages, prevProps.hideSystemMessages)) {
this.reload(); this.reload();
@ -114,9 +139,6 @@ class ListContainer extends React.Component {
componentWillUnmount() { componentWillUnmount() {
this.unsubscribeMessages(); this.unsubscribeMessages();
if (this.onEndReached && this.onEndReached.stop) {
this.onEndReached.stop();
}
if (this.unsubscribeFocus) { if (this.unsubscribeFocus) {
this.unsubscribeFocus(); this.unsubscribeFocus();
} }
@ -160,6 +182,7 @@ class ListContainer extends React.Component {
Q.experimentalTake(this.count) Q.experimentalTake(this.count)
]; ];
if (!showMessageInMainThread) { if (!showMessageInMainThread) {
// @ts-ignore
whereClause.push(Q.or(Q.where('tmid', null), Q.where('tshow', Q.eq(true)))); whereClause.push(Q.or(Q.where('tmid', null), Q.where('tshow', Q.eq(true))));
} }
this.messagesObservable = db.collections this.messagesObservable = db.collections
@ -170,8 +193,9 @@ class ListContainer extends React.Component {
if (rid) { if (rid) {
this.unsubscribeMessages(); this.unsubscribeMessages();
this.messagesSubscription = this.messagesObservable.subscribe(messages => { this.messagesSubscription = this.messagesObservable?.subscribe((messages: TMessageModel[]) => {
if (tmid && this.thread) { if (tmid && this.thread) {
// @ts-ignore
messages = [...messages, this.thread]; messages = [...messages, this.thread];
} }
@ -180,12 +204,13 @@ class ListContainer extends React.Component {
* hide system message is enabled * hide system message is enabled
*/ */
if (compareServerVersion(serverVersion, '3.16.0', methods.lowerThan) || hideSystemMessages.length) { if (compareServerVersion(serverVersion, '3.16.0', methods.lowerThan) || hideSystemMessages.length) {
messages = messages.filter(m => !m.t || !hideSystemMessages?.includes(m.t)); messages = messages.filter((m: TMessageModel) => !m.t || !hideSystemMessages?.includes(m.t));
} }
if (this.mounted) { if (this.mounted) {
this.setState({ messages }, () => this.update()); this.setState({ messages }, () => this.update());
} else { } else {
// @ts-ignore
this.state.messages = messages; this.state.messages = messages;
} }
// TODO: move it away from here // TODO: move it away from here
@ -254,21 +279,21 @@ class ListContainer extends React.Component {
return null; return null;
}; };
handleScrollToIndexFailed = params => { handleScrollToIndexFailed = (params: { highestMeasuredFrameIndex: number }) => {
const { listRef } = this.props; const { listRef } = this.props;
listRef.current.getNode().scrollToIndex({ index: params.highestMeasuredFrameIndex, animated: false }); listRef.current?.scrollToIndex({ index: params.highestMeasuredFrameIndex, animated: false });
}; };
jumpToMessage = messageId => jumpToMessage = (messageId: string): Promise<void> =>
new Promise(async resolve => { new Promise(async resolve => {
this.jumping = true; this.jumping = true;
const { messages } = this.state; const { messages } = this.state;
const { listRef } = this.props; const { listRef } = this.props;
const index = messages.findIndex(item => item.id === messageId); const index = messages.findIndex((item: TMessageModel) => item.id === messageId);
if (index > -1) { if (index > -1) {
listRef.current.getNode().scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 }); listRef.current?.scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 });
await new Promise(res => setTimeout(res, 300)); await new Promise(res => setTimeout(res, 300));
if (!this.viewableItems.map(vi => vi.key).includes(messageId)) { if (!this.viewableItems?.map((vi: { key: string }) => vi.key).includes(messageId)) {
if (!this.jumping) { if (!this.jumping) {
return resolve(); return resolve();
} }
@ -282,7 +307,7 @@ class ListContainer extends React.Component {
}, 10000); }, 10000);
await setTimeout(() => resolve(), 300); await setTimeout(() => resolve(), 300);
} else { } else {
listRef.current.getNode().scrollToIndex({ index: messages.length - 1, animated: false }); listRef.current?.scrollToIndex({ index: messages.length - 1, animated: false });
if (!this.jumping) { if (!this.jumping) {
return resolve(); return resolve();
} }
@ -297,7 +322,7 @@ class ListContainer extends React.Component {
jumpToBottom = () => { jumpToBottom = () => {
const { listRef } = this.props; const { listRef } = this.props;
listRef.current.getNode().scrollToOffset({ offset: -100 }); listRef.current?.scrollToOffset({ offset: -100 });
}; };
renderFooter = () => { renderFooter = () => {
@ -308,13 +333,13 @@ class ListContainer extends React.Component {
return null; return null;
}; };
renderItem = ({ item, index }) => { renderItem = ({ item, index }: { item: IRoomItem | IMessage; index: number }) => {
const { messages, highlightedMessage } = this.state; const { messages, highlightedMessage } = this.state;
const { renderRow } = this.props; const { renderRow } = this.props;
return renderRow(item, messages[index + 1], highlightedMessage); return renderRow(item, messages[index + 1], highlightedMessage);
}; };
onViewableItemsChanged = ({ viewableItems }) => { onViewableItemsChanged = ({ viewableItems }: { viewableItems: ViewToken[] }) => {
this.viewableItems = viewableItems; this.viewableItems = viewableItems;
}; };

View File

@ -28,7 +28,7 @@ stories.add('basic', () => (
</> </>
)); ));
const ThemeStory = ({ theme }) => ( const ThemeStory = ({ theme }: { theme: string }) => (
<ThemeContext.Provider value={{ theme }}> <ThemeContext.Provider value={{ theme }}>
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}> <ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}>
<LoadMore load={load} type={MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK} /> <LoadMore load={load} type={MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK} />

View File

@ -1,6 +1,5 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { ActivityIndicator, StyleSheet, Text } from 'react-native'; import { ActivityIndicator, StyleSheet, Text } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../../constants/messageTypeLoad'; import { MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../../constants/messageTypeLoad';
@ -21,7 +20,13 @@ const styles = StyleSheet.create({
} }
}); });
const LoadMore = ({ load, type, runOnRender }) => { interface IRoomLoadMoreProps {
load(): any;
type?: string;
runOnRender?: boolean;
}
const LoadMore = ({ load, type, runOnRender }: IRoomLoadMoreProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -62,10 +67,4 @@ const LoadMore = ({ load, type, runOnRender }) => {
); );
}; };
LoadMore.propTypes = {
load: PropTypes.func,
type: PropTypes.string,
runOnRender: PropTypes.bool
};
export default LoadMore; export default LoadMore;

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native'; import { View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Modal from 'react-native-modal'; import Modal from 'react-native-modal';
@ -9,29 +8,30 @@ import { isAndroid } from '../../utils/deviceInfo';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import styles from './styles'; import styles from './styles';
import { IMessage } from '../../definitions/IMessage';
interface IRoomReactionPickerProps {
baseUrl: string;
message: IMessage;
show: boolean;
isMasterDetail: boolean;
reactionClose(): void;
onEmojiSelected(shortname: string, messageId: string): void;
width: number;
height: number;
theme: string;
}
const margin = isAndroid ? 40 : 20; const margin = isAndroid ? 40 : 20;
const maxSize = 400; const maxSize = 400;
class ReactionPicker extends React.Component { class ReactionPicker extends React.Component<IRoomReactionPickerProps, any> {
static propTypes = { shouldComponentUpdate(nextProps: IRoomReactionPickerProps) {
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
};
shouldComponentUpdate(nextProps) {
const { show, width, height } = this.props; const { show, width, height } = this.props;
return nextProps.show !== show || width !== nextProps.width || height !== nextProps.height; 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: // standard emojis: `emoji` is unicode and `shortname` is :joy:
// custom emojis: only `emoji` is returned with shortname type (:joy:) // custom emojis: only `emoji` is returned with shortname type (:joy:)
// to set reactions, we need shortname type // to set reactions, we need shortname type
@ -68,20 +68,17 @@ class ReactionPicker extends React.Component {
} }
]} ]}
testID='reaction-picker'> testID='reaction-picker'>
<EmojiPicker <EmojiPicker onEmojiSelected={this.onEmojiSelected} baseUrl={baseUrl} />
// tabEmojiStyle={tabEmojiStyle}
onEmojiSelected={this.onEmojiSelected}
baseUrl={baseUrl}
/>
</View> </View>
</Modal> </Modal>
) : null; ) : null;
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
baseUrl: state.server.server, baseUrl: state.server.server,
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail
}); });
export default connect(mapStateToProps)(withTheme(ReactionPicker)); // TODO remove this any after merge the HOCs PR
export default connect(mapStateToProps)(withTheme(ReactionPicker)) as any;

View File

@ -1,30 +1,38 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import { Subscription } from 'rxjs';
import { StackNavigationProp } from '@react-navigation/stack';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import database from '../../lib/database'; import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import { events, logEvent } from '../../utils/log'; import { events, logEvent } from '../../utils/log';
import { isTeamRoom } from '../../utils/room'; import { isTeamRoom } from '../../utils/room';
import { ChatsStackParamList } from '../../stacks/types';
import { RoomType } from '../../definitions/IRoom';
import { IThread, TThreadModel } from '../../definitions/IThread';
import { ISubscriptions, TSubscriptionsModel } from '../../definitions/ISubscriptions';
class RightButtonsContainer extends Component { interface IRoomRightButtonsContainerProps {
static propTypes = { userId: string;
userId: PropTypes.string, threadsEnabled: boolean;
threadsEnabled: PropTypes.bool, rid: string;
rid: PropTypes.string, t: RoomType;
t: PropTypes.string, tmid?: string;
tmid: PropTypes.string, teamId: string;
teamId: PropTypes.string, navigation: StackNavigationProp<ChatsStackParamList>;
navigation: PropTypes.object, isMasterDetail: boolean;
isMasterDetail: PropTypes.bool, toggleFollowThread: Function;
toggleFollowThread: PropTypes.func, joined: boolean;
joined: PropTypes.bool, encrypted: boolean;
encrypted: PropTypes.bool }
};
constructor(props) { class RightButtonsContainer extends Component<IRoomRightButtonsContainerProps, any> {
private threadSubscription?: Subscription;
private subSubscription?: Subscription;
constructor(props: IRoomRightButtonsContainerProps) {
super(props); super(props);
this.state = { this.state = {
isFollowingThread: true, isFollowingThread: true,
@ -56,7 +64,7 @@ class RightButtonsContainer extends Component {
} }
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: IRoomRightButtonsContainerProps, nextState: any) {
const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state; const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state;
const { teamId } = this.props; const { teamId } = this.props;
if (nextProps.teamId !== teamId) { if (nextProps.teamId !== teamId) {
@ -86,26 +94,26 @@ class RightButtonsContainer extends Component {
} }
} }
observeThread = threadRecord => { observeThread = (threadRecord: TThreadModel) => {
const threadObservable = threadRecord.observe(); const threadObservable = threadRecord.observe();
this.threadSubscription = threadObservable.subscribe(thread => this.updateThread(thread)); this.threadSubscription = threadObservable.subscribe(thread => this.updateThread(thread));
}; };
updateThread = thread => { updateThread = (thread: IThread) => {
const { userId } = this.props; const { userId } = this.props;
this.setState({ this.setState({
isFollowingThread: thread.replies && !!thread.replies.find(t => t === userId) isFollowingThread: thread.replies && !!thread.replies.find((t: string) => t === userId)
}); });
}; };
observeSubscription = subRecord => { observeSubscription = (subRecord: TSubscriptionsModel) => {
const subObservable = subRecord.observe(); const subObservable = subRecord.observe();
this.subSubscription = subObservable.subscribe(sub => { this.subSubscription = subObservable.subscribe(sub => {
this.updateSubscription(sub); this.updateSubscription(sub);
}); });
}; };
updateSubscription = sub => { updateSubscription = (sub: ISubscriptions) => {
this.setState({ this.setState({
tunread: sub?.tunread, tunread: sub?.tunread,
tunreadUser: sub?.tunreadUser, tunreadUser: sub?.tunreadUser,
@ -142,7 +150,7 @@ class RightButtonsContainer extends Component {
if (isMasterDetail) { if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', { navigation.navigate('ModalStackNavigator', {
screen: 'SearchMessagesView', screen: 'SearchMessagesView',
params: { rid, showCloseModal: true, encrypted } params: { rid, t, showCloseModal: true, encrypted }
}); });
} else { } else {
navigation.navigate('SearchMessagesView', { rid, t, encrypted }); navigation.navigate('SearchMessagesView', { rid, t, encrypted });
@ -194,7 +202,7 @@ class RightButtonsContainer extends Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
userId: getUserSelector(state).id, userId: getUserSelector(state).id,
threadsEnabled: state.settings.Threads_enabled, threadsEnabled: state.settings.Threads_enabled,
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import moment from 'moment'; import moment from 'moment';
import I18n from '../../i18n'; import I18n from '../../i18n';
@ -34,7 +33,13 @@ const styles = StyleSheet.create({
} }
}); });
const DateSeparator = React.memo(({ ts, unread, theme }) => { interface IRoomDateSeparatorProps {
ts?: Date | null;
unread: boolean;
theme: string;
}
const DateSeparator = React.memo(({ ts, unread, theme }: IRoomDateSeparatorProps) => {
const date = ts ? moment(ts).format('LL') : null; const date = ts ? moment(ts).format('LL') : null;
const unreadLine = { backgroundColor: themes[theme].dangerColor }; const unreadLine = { backgroundColor: themes[theme].dangerColor };
const unreadText = { color: themes[theme].dangerColor }; const unreadText = { color: themes[theme].dangerColor };
@ -63,10 +68,4 @@ const DateSeparator = React.memo(({ ts, unread, theme }) => {
); );
}); });
DateSeparator.propTypes = {
ts: PropTypes.instanceOf(Date),
unread: PropTypes.bool,
theme: PropTypes.string
};
export default DateSeparator; export default DateSeparator;

View File

@ -1,7 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import PropTypes from 'prop-types';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { Observable, Subscription } from 'rxjs';
import Model from '@nozbe/watermelondb/Model';
import database from '../../lib/database'; import database from '../../lib/database';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
@ -51,20 +52,34 @@ const styles = StyleSheet.create({
} }
}); });
class UploadProgress extends Component { interface IRoomUploadProgressProps {
static propTypes = { width: number;
width: PropTypes.number, rid: string;
rid: PropTypes.string, theme: string;
theme: PropTypes.string, user: {
user: PropTypes.shape({ id: string;
id: PropTypes.string.isRequired, username: string;
username: PropTypes.string.isRequired, token: string;
token: PropTypes.string.isRequired
}),
baseUrl: PropTypes.string.isRequired
}; };
baseUrl: string;
}
constructor(props) { interface IItem {
name: string;
error: boolean;
progress: number;
path: string;
update(param: () => void): void;
destroyPermanently(): Promise<void>;
}
class UploadProgress extends Component<IRoomUploadProgressProps, any> {
private mounted: boolean;
private ranInitialUploadCheck: boolean;
private uploadsSubscription?: Subscription;
private uploadsObservable?: Observable<Model>;
constructor(props: IRoomUploadProgressProps) {
super(props); super(props);
this.mounted = false; this.mounted = false;
this.ranInitialUploadCheck = false; this.ranInitialUploadCheck = false;
@ -93,10 +108,11 @@ class UploadProgress extends Component {
const db = database.active; const db = database.active;
this.uploadsObservable = db.collections.get('uploads').query(Q.where('rid', rid)).observeWithColumns(['progress', 'error']); this.uploadsObservable = db.collections.get('uploads').query(Q.where('rid', rid)).observeWithColumns(['progress', 'error']);
this.uploadsSubscription = this.uploadsObservable.subscribe(uploads => { this.uploadsSubscription = this.uploadsObservable?.subscribe(uploads => {
if (this.mounted) { if (this.mounted) {
this.setState({ uploads }); this.setState({ uploads });
} else { } else {
// @ts-ignore
this.state.uploads = uploads; this.state.uploads = uploads;
} }
if (!this.ranInitialUploadCheck) { if (!this.ranInitialUploadCheck) {
@ -108,7 +124,7 @@ class UploadProgress extends Component {
uploadCheck = () => { uploadCheck = () => {
this.ranInitialUploadCheck = true; this.ranInitialUploadCheck = true;
const { uploads } = this.state; const { uploads } = this.state;
uploads.forEach(async u => { uploads.forEach(async (u: IItem) => {
if (!RocketChat.isUploadActive(u.path)) { if (!RocketChat.isUploadActive(u.path)) {
try { try {
const db = database.active; const db = database.active;
@ -124,7 +140,7 @@ class UploadProgress extends Component {
}); });
}; };
deleteUpload = async item => { deleteUpload = async (item: IItem) => {
try { try {
const db = database.active; const db = database.active;
await db.action(async () => { await db.action(async () => {
@ -135,7 +151,7 @@ class UploadProgress extends Component {
} }
}; };
cancelUpload = async item => { cancelUpload = async (item: { path: string }) => {
try { try {
await RocketChat.cancelUpload(item); await RocketChat.cancelUpload(item);
} catch (e) { } catch (e) {
@ -143,7 +159,7 @@ class UploadProgress extends Component {
} }
}; };
tryAgain = async item => { tryAgain = async (item: IItem) => {
const { rid, baseUrl: server, user } = this.props; const { rid, baseUrl: server, user } = this.props;
try { try {
@ -159,7 +175,7 @@ class UploadProgress extends Component {
} }
}; };
renderItemContent = item => { renderItemContent = (item: IItem) => {
const { width, theme } = this.props; const { width, theme } = this.props;
if (!item.error) { if (!item.error) {
@ -196,7 +212,7 @@ class UploadProgress extends Component {
}; };
// TODO: transform into stateless and update based on its own observable changes // TODO: transform into stateless and update based on its own observable changes
renderItem = (item, index) => { renderItem = (item: IItem, index: number) => {
const { theme } = this.props; const { theme } = this.props;
return ( return (
@ -217,7 +233,7 @@ class UploadProgress extends Component {
render() { render() {
const { uploads } = this.state; const { uploads } = this.state;
return <ScrollView style={styles.container}>{uploads.map((item, i) => this.renderItem(item, i))}</ScrollView>; return <ScrollView style={styles.container}>{uploads.map((item: IItem, i: number) => this.renderItem(item, i))}</ScrollView>;
} }
} }

View File

@ -1,5 +1,4 @@
import React from 'react'; import React, { ForwardedRef } from 'react';
import PropTypes from 'prop-types';
import { InteractionManager, Text, View } from 'react-native'; import { InteractionManager, Text, View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import parse from 'url-parse'; import parse from 'url-parse';
@ -8,6 +7,8 @@ import * as Haptics from 'expo-haptics';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { StackNavigationProp } from '@react-navigation/stack';
import { NavigatorScreenParams, RouteProp } from '@react-navigation/core';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { replyBroadcast as replyBroadcastAction } from '../../actions/messages'; import { replyBroadcast as replyBroadcastAction } from '../../actions/messages';
@ -64,8 +65,29 @@ import JoinCode from './JoinCode';
import UploadProgress from './UploadProgress'; import UploadProgress from './UploadProgress';
import ReactionPicker from './ReactionPicker'; import ReactionPicker from './ReactionPicker';
import List from './List'; import List from './List';
import { ChatsStackParamList } from '../../stacks/types';
import { IRoom, TRoomModel, RoomType } from '../../definitions/IRoom';
import { IAttachment } from '../../definitions/IAttachment';
import { IThread } from '../../definitions/IThread';
import { ISubscriptions } from '../../definitions/ISubscriptions';
import { ModalStackParamList } from '../../stacks/MasterDetailStack/types';
import { IMessage } from '../../definitions/IMessage';
const stateAttrsUpdate = [ type TStateAttrsUpdate =
| 'joined'
| 'lastOpen'
| 'reactionsModalVisible'
| 'canAutoTranslate'
| 'selectedMessage'
| 'loading'
| 'editing'
| 'replying'
| 'reacting'
| 'readOnly'
| 'member'
| 'showingBlockingLoader';
const stateAttrsUpdate: TStateAttrsUpdate[] = [
'joined', 'joined',
'lastOpen', 'lastOpen',
'reactionsModalVisible', 'reactionsModalVisible',
@ -79,7 +101,30 @@ const stateAttrsUpdate = [
'member', 'member',
'showingBlockingLoader' 'showingBlockingLoader'
]; ];
const roomAttrsUpdate = [
type TRoomAttrsUpdate =
| 'f'
| 'ro'
| 'blocked'
| 'blocker'
| 'archived'
| 'tunread'
| 'muted'
| 'ignored'
| 'jitsiTimeout'
| 'announcement'
| 'sysMes'
| 'topic'
| 'name'
| 'fname'
| 'roles'
| 'bannerClosed'
| 'visitor'
| 'joinCodeRequired'
| 'teamMain'
| 'teamId';
const roomAttrsUpdate: TRoomAttrsUpdate[] = [
'f', 'f',
'ro', 'ro',
'blocked', 'blocked',
@ -102,52 +147,135 @@ const roomAttrsUpdate = [
'teamId' 'teamId'
]; ];
class RoomView extends React.Component { interface IRoomViewProps {
static propTypes = { navigation: StackNavigationProp<ChatsStackParamList, 'RoomView'>;
navigation: PropTypes.object, route: RouteProp<ChatsStackParamList, 'RoomView'>;
route: PropTypes.object, user: {
user: PropTypes.shape({ id: string;
id: PropTypes.string.isRequired, username: string;
username: PropTypes.string.isRequired, token: string;
token: PropTypes.string.isRequired, showMessageInMainThread: boolean;
showMessageInMainThread: PropTypes.bool
}),
appState: PropTypes.string,
useRealName: PropTypes.bool,
isAuthenticated: PropTypes.bool,
Message_GroupingPeriod: PropTypes.number,
Message_TimeFormat: PropTypes.string,
Message_Read_Receipt_Enabled: PropTypes.bool,
Hide_System_Messages: PropTypes.array,
baseUrl: PropTypes.string,
serverVersion: PropTypes.string,
customEmojis: PropTypes.object,
isMasterDetail: PropTypes.bool,
theme: PropTypes.string,
replyBroadcast: PropTypes.func,
width: PropTypes.number,
height: PropTypes.number,
insets: PropTypes.object
}; };
appState: string;
useRealName: boolean;
isAuthenticated: boolean;
Message_GroupingPeriod: number;
Message_TimeFormat: string;
Message_Read_Receipt_Enabled: boolean;
Hide_System_Messages: [];
baseUrl: string;
serverVersion: string;
customEmojis: { [key: string]: string };
isMasterDetail: boolean;
theme: string;
replyBroadcast(message: string): void;
width: number;
height: number;
insets: {
left: number;
right: number;
};
}
constructor(props) { interface IRoomViewState {
joined: boolean;
room: TRoomModel;
roomUpdate: Partial<IRoom>;
member: {
statusText?: string;
};
lastOpen: Date | null;
reactionsModalVisible: boolean;
selectedMessage: {};
canAutoTranslate: boolean;
loading: boolean;
showingBlockingLoader: boolean;
editing: boolean;
replying: boolean;
replyWithMention: boolean;
reacting: boolean;
readOnly: boolean;
unreadsCount: number | null;
roomUserId: string;
}
export interface IRoomItem {
id: string;
t: string;
rid: string;
tmid?: string;
ts: Date;
status?: string;
u?: { _id: string };
loaderItem: {
t: string;
ts: Date;
};
}
interface INavToThread {
id?: string;
tmsg?: string;
t?: string;
e2e?: string;
tmid?: string;
tlm?: string;
}
interface IBlockAction {
actionId: string;
appId: string;
value: string;
blockId: string;
rid: string;
mid: string;
}
class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
private rid: string;
private t: RoomType;
private tmid?: string;
private jumpToMessageId?: string;
private jumpToThreadId?: string;
private messagebox: React.RefObject<typeof MessageBox>;
private list: React.RefObject<List>;
private joinCode?: React.ForwardedRef<typeof JoinCode>;
private flatList: any;
private mounted: boolean;
private sub?: RoomClass;
private offset?: number;
private didMountInteraction?: { cancel: () => void };
private willBlurListener?: { remove(): void };
private subSubscription?: { unsubscribe(): void };
private queryUnreads?: { unsubscribe(): void };
private retryInit?: number;
private retryInitTimeout?: ReturnType<typeof setTimeout>;
private retryFindCount?: number;
private retryFindTimeout?: ReturnType<typeof setTimeout>;
private messageErrorActions?: React.ForwardedRef<typeof MessageErrorActions>;
private messageActions?: React.ForwardedRef<typeof MessageActions>;
constructor(props: IRoomViewProps) {
super(props); super(props);
console.time(`${this.constructor.name} init`); console.time(`${this.constructor.name} init`);
console.time(`${this.constructor.name} mount`); console.time(`${this.constructor.name} mount`);
this.rid = props.route.params?.rid; this.rid = props.route.params.rid;
this.t = props.route.params?.t; this.t = props.route.params.t;
this.tmid = props.route.params?.tmid; this.tmid = props.route.params?.tmid;
const selectedMessage = props.route.params?.message; const selectedMessage = props.route.params?.message;
const name = props.route.params?.name; const name = props.route.params?.name;
const fname = props.route.params?.fname; const fname = props.route.params?.fname;
const prid = props.route.params?.prid; const prid = props.route.params?.prid;
const room = props.route.params?.room ?? { const room =
rid: this.rid, props.route.params?.room ??
t: this.t, ({
name, rid: this.rid,
fname, t: this.t,
prid name,
}; fname,
prid
} as TRoomModel);
this.jumpToMessageId = props.route.params?.jumpToMessageId; this.jumpToMessageId = props.route.params?.jumpToMessageId;
this.jumpToThreadId = props.route.params?.jumpToThreadId; this.jumpToThreadId = props.route.params?.jumpToThreadId;
const roomUserId = props.route.params?.roomUserId ?? RocketChat.getUidDirectMessage(room); const roomUserId = props.route.params?.roomUserId ?? RocketChat.getUidDirectMessage(room);
@ -172,7 +300,7 @@ class RoomView extends React.Component {
}; };
this.setHeader(); this.setHeader();
if (room && room.observe) { if (room && room.observe()) {
this.observeRoom(room); this.observeRoom(room);
} else if (this.rid) { } else if (this.rid) {
this.findAndObserveRoom(this.rid); this.findAndObserveRoom(this.rid);
@ -224,7 +352,7 @@ class RoomView extends React.Component {
console.timeEnd(`${this.constructor.name} mount`); console.timeEnd(`${this.constructor.name} mount`);
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: IRoomViewProps, nextState: IRoomViewState) {
const { state } = this; const { state } = this;
const { roomUpdate, member } = state; const { roomUpdate, member } = state;
const { appState, theme, insets, route } = this.props; const { appState, theme, insets, route } = this.props;
@ -250,7 +378,7 @@ class RoomView extends React.Component {
return roomAttrsUpdate.some(key => !dequal(nextState.roomUpdate[key], roomUpdate[key])); return roomAttrsUpdate.some(key => !dequal(nextState.roomUpdate[key], roomUpdate[key]));
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps: IRoomViewProps, prevState: IRoomViewState) {
const { roomUpdate } = this.state; const { roomUpdate } = this.state;
const { appState, insets, route } = this.props; const { appState, insets, route } = this.props;
@ -304,7 +432,8 @@ class RoomView extends React.Component {
this.mounted = false; this.mounted = false;
if (!editing && this.messagebox && this.messagebox.current) { if (!editing && this.messagebox && this.messagebox.current) {
const { text } = this.messagebox.current; const { text } = this.messagebox.current;
let obj; let obj = room; // TODO - test the threadsCollection.find return to change this any;
if (this.tmid) { if (this.tmid) {
try { try {
const threadsCollection = db.get('threads'); const threadsCollection = db.get('threads');
@ -312,13 +441,13 @@ class RoomView extends React.Component {
} catch (e) { } catch (e) {
// Do nothing // Do nothing
} }
} else {
obj = room;
} }
if (obj) { if (obj) {
try { try {
await db.action(async () => { await db.action(async () => {
await obj.update(r => { await obj.update(r => {
// TODO - change this any
r.draftMessage = text; r.draftMessage = text;
}); });
}); });
@ -360,7 +489,7 @@ class RoomView extends React.Component {
const prid = room?.prid; const prid = room?.prid;
const isGroupChat = RocketChat.isGroupChat(room); const isGroupChat = RocketChat.isGroupChat(room);
let title = route.params?.name; let title = route.params?.name;
let parentTitle; let parentTitle: string;
if ((room.id || room.rid) && !tmid) { if ((room.id || room.rid) && !tmid) {
title = RocketChat.getRoomTitle(room); title = RocketChat.getRoomTitle(room);
} }
@ -397,7 +526,8 @@ class RoomView extends React.Component {
headerLeft: () => ( headerLeft: () => (
<LeftButtons <LeftButtons
tmid={tmid} tmid={tmid}
unreadsCount={unreadsCount} // TODO - remove this type after craete the Room state
unreadsCount={unreadsCount as number & string}
navigation={navigation} navigation={navigation}
baseUrl={baseUrl} baseUrl={baseUrl}
userId={userId} userId={userId}
@ -430,11 +560,10 @@ class RoomView extends React.Component {
<RightButtons <RightButtons
rid={rid} rid={rid}
tmid={tmid} tmid={tmid}
teamId={teamId} teamId={teamId!}
teamMain={teamMain}
joined={joined} joined={joined}
t={t} t={t}
encrypted={encrypted} encrypted={encrypted!}
navigation={navigation} navigation={navigation}
toggleFollowThread={this.toggleFollowThread} toggleFollowThread={this.toggleFollowThread}
/> />
@ -442,7 +571,7 @@ class RoomView extends React.Component {
}); });
}; };
goRoomActionsView = screen => { goRoomActionsView = (screen?: 'SearchMessagesView' | 'RoomActionsView') => {
logEvent(events.ROOM_GO_RA); logEvent(events.ROOM_GO_RA);
const { room, member, joined } = this.state; const { room, member, joined } = this.state;
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
@ -493,7 +622,8 @@ class RoomView extends React.Component {
} else { } else {
this.setLastOpen(null); this.setLastOpen(null);
} }
RoomServices.readMessages(room.rid, newLastOpen, true).catch(e => console.log(e)); // RoomServices.readMessages(room.rid, newLastOpen, true).catch(e => console.log(e)); this function receives true automatic
RoomServices.readMessages(room.rid, newLastOpen).catch(e => console.log(e));
} }
} }
@ -503,7 +633,7 @@ class RoomView extends React.Component {
this.setState({ canAutoTranslate, member, loading: false }); this.setState({ canAutoTranslate, member, loading: false });
} catch (e) { } catch (e) {
this.setState({ loading: false }); this.setState({ loading: false });
this.retryInit = this.retryInit + 1 || 1; this.retryInit = this.retryInit! + 1 || 1;
if (this.retryInit <= 1) { if (this.retryInit <= 1) {
this.retryInitTimeout = setTimeout(() => { this.retryInitTimeout = setTimeout(() => {
this.init(); this.init();
@ -533,7 +663,7 @@ class RoomView extends React.Component {
return {}; return {};
}; };
findAndObserveRoom = async rid => { findAndObserveRoom = async (rid: string) => {
try { try {
const db = database.active; const db = database.active;
const subCollection = await db.get('subscriptions'); const subCollection = await db.get('subscriptions');
@ -551,7 +681,7 @@ class RoomView extends React.Component {
if (this.rid) { if (this.rid) {
// We navigate to RoomView before the Room is inserted to the local db // We navigate to RoomView before the Room is inserted to the local db
// So we retry just to make sure we have the right content // So we retry just to make sure we have the right content
this.retryFindCount = this.retryFindCount + 1 || 1; this.retryFindCount = this.retryFindCount! + 1 || 1;
if (this.retryFindCount <= 3) { if (this.retryFindCount <= 3) {
this.retryFindTimeout = setTimeout(() => { this.retryFindTimeout = setTimeout(() => {
this.findAndObserveRoom(rid); this.findAndObserveRoom(rid);
@ -569,27 +699,32 @@ class RoomView extends React.Component {
delete this.sub; delete this.sub;
}; };
observeRoom = room => { observeRoom = (room: TRoomModel) => {
const observable = room.observe(); if (room.observe) {
this.subSubscription = observable.subscribe(changes => { const observable = room.observe();
const roomUpdate = roomAttrsUpdate.reduce((ret, attr) => { this.subSubscription = observable.subscribe(changes => {
ret[attr] = changes[attr]; const roomUpdate = roomAttrsUpdate.reduce((ret: any, attr) => {
return ret; ret[attr] = changes[attr];
}, {}); return ret;
if (this.mounted) { }, {});
this.internalSetState({ room: changes, roomUpdate }); if (this.mounted) {
} else { this.internalSetState({ room: changes, roomUpdate });
this.state.room = changes; } else {
this.state.roomUpdate = roomUpdate; // @ts-ignore
} this.state.room = changes;
}); // @ts-ignore
this.state.roomUpdate = roomUpdate;
}
});
}
}; };
errorActionsShow = message => { errorActionsShow = (message: string) => {
// @ts-ignore
this.messageErrorActions?.showMessageErrorActions(message); this.messageErrorActions?.showMessageErrorActions(message);
}; };
onEditInit = message => { onEditInit = (message: { id: string; subscription: { id: string }; attachments: IAttachment[]; msg: string }) => {
const newMessage = { const newMessage = {
id: message.id, id: message.id,
subscription: { subscription: {
@ -604,7 +739,7 @@ class RoomView extends React.Component {
this.setState({ selectedMessage: {}, editing: false }); this.setState({ selectedMessage: {}, editing: false });
}; };
onEditRequest = async message => { onEditRequest = async (message: string) => {
this.setState({ selectedMessage: {}, editing: false }); this.setState({ selectedMessage: {}, editing: false });
try { try {
await RocketChat.editMessage(message); await RocketChat.editMessage(message);
@ -613,7 +748,7 @@ class RoomView extends React.Component {
} }
}; };
onReplyInit = (message, mention) => { onReplyInit = (message: IMessage, mention: boolean) => {
this.setState({ this.setState({
selectedMessage: message, selectedMessage: message,
replying: true, replying: true,
@ -625,7 +760,7 @@ class RoomView extends React.Component {
this.setState({ selectedMessage: {}, replying: false, replyWithMention: false }); this.setState({ selectedMessage: {}, replying: false, replyWithMention: false });
}; };
onReactionInit = message => { onReactionInit = (message: string) => {
this.setState({ selectedMessage: message, reacting: true }); this.setState({ selectedMessage: message, reacting: true });
}; };
@ -633,16 +768,17 @@ class RoomView extends React.Component {
this.setState({ selectedMessage: {}, reacting: false }); this.setState({ selectedMessage: {}, reacting: false });
}; };
onMessageLongPress = message => { onMessageLongPress = (message: string) => {
// @ts-ignore
this.messageActions?.showMessageActions(message); this.messageActions?.showMessageActions(message);
}; };
showAttachment = attachment => { showAttachment = (attachment: IAttachment) => {
const { navigation } = this.props; const { navigation } = this.props;
navigation.navigate('AttachmentView', { attachment }); navigation.navigate('AttachmentView', { attachment });
}; };
onReactionPress = async (shortname, messageId) => { onReactionPress = async (shortname: string, messageId: string) => {
try { try {
await RocketChat.setReaction(shortname, messageId); await RocketChat.setReaction(shortname, messageId);
this.onReactionClose(); this.onReactionClose();
@ -652,7 +788,7 @@ class RoomView extends React.Component {
} }
}; };
onReactionLongPress = message => { onReactionLongPress = (message: string) => {
this.setState({ selectedMessage: message, reactionsModalVisible: true }); this.setState({ selectedMessage: message, reactionsModalVisible: true });
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}; };
@ -665,7 +801,7 @@ class RoomView extends React.Component {
logEvent(events.ROOM_ENCRYPTED_PRESS); logEvent(events.ROOM_ENCRYPTED_PRESS);
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
const screen = { screen: 'E2EHowItWorksView', params: { showCloseModal: true } }; const screen: NavigatorScreenParams<ModalStackParamList> = { screen: 'E2EHowItWorksView', params: { showCloseModal: true } };
if (isMasterDetail) { if (isMasterDetail) {
return navigation.navigate('ModalStackNavigator', screen); return navigation.navigate('ModalStackNavigator', screen);
@ -674,13 +810,13 @@ class RoomView extends React.Component {
}; };
onDiscussionPress = debounce( onDiscussionPress = debounce(
item => { (item: IThread) => {
const { navigation } = this.props; const { navigation } = this.props;
navigation.push('RoomView', { navigation.push('RoomView', {
rid: item.drid, rid: item.drid,
prid: item.rid, prid: item.rid,
name: item.msg, name: item.msg,
t: 'p' t: RoomType.GROUP
}); });
}, },
1000, 1000,
@ -695,7 +831,7 @@ class RoomView extends React.Component {
.query(Q.where('archived', false), Q.where('open', true), Q.where('rid', Q.notEq(this.rid))) .query(Q.where('archived', false), Q.where('open', true), Q.where('rid', Q.notEq(this.rid)))
.observeWithColumns(['unread']); .observeWithColumns(['unread']);
this.queryUnreads = observable.subscribe(data => { this.queryUnreads = observable.subscribe((data: ISubscriptions[]) => {
const { unreadsCount } = this.state; const { unreadsCount } = this.state;
const newUnreadsCount = data.filter(s => s.unread > 0).reduce((a, b) => a + (b.unread || 0), 0); const newUnreadsCount = data.filter(s => s.unread > 0).reduce((a, b) => a + (b.unread || 0), 0);
if (unreadsCount !== newUnreadsCount) { if (unreadsCount !== newUnreadsCount) {
@ -704,9 +840,9 @@ class RoomView extends React.Component {
}); });
}; };
onThreadPress = debounce(item => this.navToThread(item), 1000, true); onThreadPress = debounce((item: IRoomItem) => this.navToThread(item), 1000, true);
shouldNavigateToRoom = message => { shouldNavigateToRoom = (message: { tmid: string; rid: string }) => {
if (message.tmid && message.tmid === this.tmid) { if (message.tmid && message.tmid === this.tmid) {
return false; return false;
} }
@ -716,7 +852,7 @@ class RoomView extends React.Component {
return true; return true;
}; };
jumpToMessageByUrl = async messageUrl => { jumpToMessageByUrl = async (messageUrl: string) => {
if (!messageUrl) { if (!messageUrl) {
return; return;
} }
@ -732,10 +868,10 @@ class RoomView extends React.Component {
} }
}; };
jumpToMessage = async messageId => { jumpToMessage = async (messageId?: string) => {
try { try {
this.setState({ showingBlockingLoader: true }); this.setState({ showingBlockingLoader: true });
const message = await RoomServices.getMessageInfo(messageId); const message = await RoomServices.getMessageInfo(messageId!);
if (!message) { if (!message) {
return; return;
@ -755,8 +891,8 @@ class RoomView extends React.Component {
if (message.fromServer && !message.tmid) { if (message.fromServer && !message.tmid) {
await RocketChat.loadSurroundingMessages({ messageId, rid: this.rid }); await RocketChat.loadSurroundingMessages({ messageId, rid: this.rid });
} }
await Promise.race([this.list.current.jumpToMessage(message.id), new Promise(res => setTimeout(res, 5000))]); await Promise.race([this.list.current?.jumpToMessage(message.id), new Promise(res => setTimeout(res, 5000))]);
this.list.current.cancelJumpToMessage(); this.list.current?.cancelJumpToMessage();
} }
} catch (e) { } catch (e) {
log(e); log(e);
@ -765,7 +901,7 @@ class RoomView extends React.Component {
} }
}; };
replyBroadcast = message => { replyBroadcast = (message: string) => {
const { replyBroadcast } = this.props; const { replyBroadcast } = this.props;
replyBroadcast(message); replyBroadcast(message);
}; };
@ -775,7 +911,7 @@ class RoomView extends React.Component {
EventEmitter.removeListener('connected', this.handleConnected); EventEmitter.removeListener('connected', this.handleConnected);
}; };
handleRoomRemoved = ({ rid }) => { handleRoomRemoved = ({ rid }: { rid: string }) => {
const { room } = this.state; const { room } = this.state;
if (rid === this.rid) { if (rid === this.rid) {
Navigation.navigate('RoomsListView'); Navigation.navigate('RoomsListView');
@ -784,14 +920,15 @@ class RoomView extends React.Component {
} }
}; };
internalSetState = (...args) => { internalSetState = (...args: any) => {
if (!this.mounted) { if (!this.mounted) {
return; return;
} }
// @ts-ignore
this.setState(...args); this.setState(...args);
}; };
sendMessage = (message, tmid, tshow) => { sendMessage = (message: string, tmid: string, tshow: string) => {
logEvent(events.ROOM_SEND_MESSAGE); logEvent(events.ROOM_SEND_MESSAGE);
const { user } = this.props; const { user } = this.props;
RocketChat.sendMessage(this.rid, message, this.tmid || tmid, user, tshow).then(() => { RocketChat.sendMessage(this.rid, message, this.tmid || tmid, user, tshow).then(() => {
@ -803,7 +940,7 @@ class RoomView extends React.Component {
}); });
}; };
getCustomEmoji = name => { getCustomEmoji = (name: string) => {
const { customEmojis } = this.props; const { customEmojis } = this.props;
const emoji = customEmojis[name]; const emoji = customEmojis[name];
if (emoji) { if (emoji) {
@ -812,7 +949,7 @@ class RoomView extends React.Component {
return null; return null;
}; };
setLastOpen = lastOpen => this.setState({ lastOpen }); setLastOpen = (lastOpen: Date | null) => this.setState({ lastOpen });
onJoin = () => { onJoin = () => {
this.internalSetState({ this.internalSetState({
@ -831,7 +968,8 @@ class RoomView extends React.Component {
} else { } else {
const { joinCodeRequired } = room; const { joinCodeRequired } = room;
if (joinCodeRequired) { if (joinCodeRequired) {
this.joinCode.current?.show(); // @ts-ignore
this.joinCode?.current?.show();
} else { } else {
await RocketChat.joinRoom(this.rid, null, this.t); await RocketChat.joinRoom(this.rid, null, this.t);
this.onJoin(); this.onJoin();
@ -842,9 +980,9 @@ class RoomView extends React.Component {
} }
}; };
getThreadName = (tmid, messageId) => getThreadName(this.rid, tmid, messageId); getThreadName = (tmid: string, messageId?: string) => getThreadName(this.rid, tmid, messageId);
toggleFollowThread = async (isFollowingThread, tmid) => { toggleFollowThread = async (isFollowingThread: boolean, tmid: string) => {
try { try {
await RocketChat.toggleFollowMessage(tmid ?? this.tmid, !isFollowingThread); await RocketChat.toggleFollowMessage(tmid ?? this.tmid, !isFollowingThread);
EventEmitter.emit(LISTENER, { message: isFollowingThread ? I18n.t('Unfollowed_thread') : I18n.t('Following_thread') }); EventEmitter.emit(LISTENER, { message: isFollowingThread ? I18n.t('Unfollowed_thread') : I18n.t('Following_thread') });
@ -853,13 +991,13 @@ class RoomView extends React.Component {
} }
}; };
getBadgeColor = messageId => { getBadgeColor = (messageId?: string) => {
const { room } = this.state; const { room } = this.state;
const { theme } = this.props; const { theme } = this.props;
return getBadgeColor({ subscription: room, theme, messageId }); return getBadgeColor({ subscription: room, theme, messageId });
}; };
navToRoomInfo = navParam => { navToRoomInfo = (navParam: ChatsStackParamList['RoomInfoView']) => {
const { navigation, user, isMasterDetail } = this.props; const { navigation, user, isMasterDetail } = this.props;
logEvent(events[`ROOM_GO_${navParam.t === 'd' ? 'USER' : 'ROOM'}_INFO`]); logEvent(events[`ROOM_GO_${navParam.t === 'd' ? 'USER' : 'ROOM'}_INFO`]);
if (navParam.rid === user.id) { if (navParam.rid === user.id) {
@ -873,7 +1011,7 @@ class RoomView extends React.Component {
} }
}; };
navToThread = async item => { navToThread = async (item: INavToThread) => {
const { roomUserId } = this.state; const { roomUserId } = this.state;
const { navigation } = this.props; const { navigation } = this.props;
@ -894,7 +1032,7 @@ class RoomView extends React.Component {
rid: this.rid, rid: this.rid,
tmid: item.tmid, tmid: item.tmid,
name, name,
t: 'thread', t: RoomType.THREAD,
roomUserId, roomUserId,
jumpToMessageId: item.id jumpToMessageId: item.id
}); });
@ -905,15 +1043,15 @@ class RoomView extends React.Component {
rid: this.rid, rid: this.rid,
tmid: item.id, tmid: item.id,
name: makeThreadName(item), name: makeThreadName(item),
t: 'thread', t: RoomType.THREAD,
roomUserId roomUserId
}); });
} }
}; };
navToRoom = async message => { navToRoom = async (message: { rid: string; id: string }) => {
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
const roomInfo = await getRoomInfo(message.rid); const roomInfo = (await getRoomInfo(message.rid)) as IRoom;
return goRoom({ return goRoom({
item: roomInfo, item: roomInfo,
isMasterDetail, isMasterDetail,
@ -932,12 +1070,12 @@ class RoomView extends React.Component {
} }
}; };
handleCommands = ({ event }) => { handleCommands = ({ event }: { event: { input: string } }) => {
if (this.rid) { if (this.rid) {
const { input } = event; const { input } = event;
if (handleCommandScroll(event)) { if (handleCommandScroll(event)) {
const offset = input === 'UIKeyInputUpArrow' ? 100 : -100; const offset: number = input === 'UIKeyInputUpArrow' ? 100 : -100;
this.offset += offset; this.offset! += offset;
this.flatList?.scrollToOffset({ offset: this.offset }); this.flatList?.scrollToOffset({ offset: this.offset });
} else if (handleCommandRoomActions(event)) { } else if (handleCommandRoomActions(event)) {
this.goRoomActionsView(); this.goRoomActionsView();
@ -945,14 +1083,14 @@ class RoomView extends React.Component {
this.goRoomActionsView('SearchMessagesView'); this.goRoomActionsView('SearchMessagesView');
} else if (handleCommandReplyLatest(event)) { } else if (handleCommandReplyLatest(event)) {
if (this.list && this.list.current) { if (this.list && this.list.current) {
const message = this.list.current.getLastMessage(); const message = this.list.current.getLastMessage() as IMessage;
this.onReplyInit(message, false); this.onReplyInit(message, false);
} }
} }
} }
}; };
blockAction = ({ actionId, appId, value, blockId, rid, mid }) => blockAction = ({ actionId, appId, value, blockId, rid, mid }: IBlockAction) =>
RocketChat.triggerBlockAction({ RocketChat.triggerBlockAction({
blockId, blockId,
actionId, actionId,
@ -971,7 +1109,7 @@ class RoomView extends React.Component {
try { try {
const db = database.active; const db = database.active;
await db.action(async () => { await db.action(async () => {
await room.update(r => { await room.update((r: IRoom) => {
r.bannerClosed = true; r.bannerClosed = true;
}); });
}); });
@ -980,12 +1118,12 @@ class RoomView extends React.Component {
} }
}; };
isIgnored = message => { isIgnored = (message: IRoomItem) => {
const { room } = this.state; const { room } = this.state;
return room?.ignored?.includes?.(message?.u?._id) ?? false; return room?.ignored?.includes?.(message?.u?._id) ?? false;
}; };
onLoadMoreMessages = loaderItem => onLoadMoreMessages = (loaderItem: IRoomItem) =>
RoomServices.getMoreMessages({ RoomServices.getMoreMessages({
rid: this.rid, rid: this.rid,
tmid: this.tmid, tmid: this.tmid,
@ -993,7 +1131,7 @@ class RoomView extends React.Component {
loaderItem loaderItem
}); });
renderItem = (item, previousItem, highlightedMessage) => { renderItem = (item: IRoomItem, previousItem: IRoomItem, highlightedMessage: string) => {
const { room, lastOpen, canAutoTranslate } = this.state; const { room, lastOpen, canAutoTranslate } = this.state;
const { user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme } = const { user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme } =
this.props; this.props;
@ -1004,7 +1142,7 @@ class RoomView extends React.Component {
dateSeparator = item.ts; dateSeparator = item.ts;
showUnreadSeparator = moment(item.ts).isAfter(lastOpen); showUnreadSeparator = moment(item.ts).isAfter(lastOpen);
} else { } else {
showUnreadSeparator = lastOpen && moment(item.ts).isSameOrAfter(lastOpen) && moment(previousItem.ts).isBefore(lastOpen); showUnreadSeparator = lastOpen! && moment(item.ts).isSameOrAfter(lastOpen) && moment(previousItem.ts).isBefore(lastOpen);
if (!moment(item.ts).isSame(previousItem.ts, 'day')) { if (!moment(item.ts).isSame(previousItem.ts, 'day')) {
dateSeparator = item.ts; dateSeparator = item.ts;
} }
@ -1150,7 +1288,7 @@ class RoomView extends React.Component {
return ( return (
<> <>
<MessageActions <MessageActions
ref={ref => (this.messageActions = ref)} ref={(ref: ForwardedRef<typeof MessageActions>) => (this.messageActions = ref)}
tmid={this.tmid} tmid={this.tmid}
room={room} room={room}
user={user} user={user}
@ -1160,7 +1298,10 @@ class RoomView extends React.Component {
onReactionPress={this.onReactionPress} onReactionPress={this.onReactionPress}
isReadOnly={readOnly} isReadOnly={readOnly}
/> />
<MessageErrorActions ref={ref => (this.messageErrorActions = ref)} tmid={this.tmid} /> <MessageErrorActions
ref={(ref: ForwardedRef<typeof MessageErrorActions>) => (this.messageErrorActions = ref)}
tmid={this.tmid}
/>
</> </>
); );
}; };
@ -1175,7 +1316,6 @@ class RoomView extends React.Component {
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='room-view'> <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='room-view'>
<StatusBar /> <StatusBar />
<Banner <Banner
rid={rid}
title={I18n.t('Announcement')} title={I18n.t('Announcement')}
text={announcement} text={announcement}
bannerClosed={bannerClosed} bannerClosed={bannerClosed}
@ -1186,7 +1326,6 @@ class RoomView extends React.Component {
ref={this.list} ref={this.list}
listRef={this.flatList} listRef={this.flatList}
rid={rid} rid={rid}
t={t}
tmid={this.tmid} tmid={this.tmid}
theme={theme} theme={theme}
tunread={room?.tunread} tunread={room?.tunread}
@ -1225,7 +1364,7 @@ class RoomView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
user: getUserSelector(state), user: getUserSelector(state),
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background', appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
@ -1240,8 +1379,8 @@ const mapStateToProps = state => ({
Hide_System_Messages: state.settings.Hide_System_Messages Hide_System_Messages: state.settings.Hide_System_Messages
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = (dispatch: any) => ({
replyBroadcast: message => dispatch(replyBroadcastAction(message)) replyBroadcast: (message: any) => dispatch(replyBroadcastAction(message))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomView)))); export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomView))));

View File

@ -2,7 +2,7 @@ import { getMessageById } from '../../../lib/database/services/Message';
import { getThreadMessageById } from '../../../lib/database/services/ThreadMessage'; import { getThreadMessageById } from '../../../lib/database/services/ThreadMessage';
import getSingleMessage from '../../../lib/methods/getSingleMessage'; import getSingleMessage from '../../../lib/methods/getSingleMessage';
const getMessageInfo = async messageId => { const getMessageInfo = async (messageId: string) => {
let result; let result;
result = await getMessageById(messageId); result = await getMessageById(messageId);
if (result) { if (result) {

View File

@ -1,10 +1,10 @@
import RocketChat from '../../../lib/rocketchat'; import RocketChat from '../../../lib/rocketchat';
import { IRoom } from '../../../definitions/IRoom';
const getMessages = room => { const getMessages = (room: IRoom): Promise<void> => {
if (room.lastOpen) { if (room.lastOpen) {
return RocketChat.loadMissedMessages(room); return RocketChat.loadMissedMessages(room);
} else {
return RocketChat.loadMessagesForRoom(room);
} }
return RocketChat.loadMessagesForRoom(room);
}; };
export default getMessages; export default getMessages;

View File

@ -5,7 +5,17 @@ import {
} from '../../../constants/messageTypeLoad'; } from '../../../constants/messageTypeLoad';
import RocketChat from '../../../lib/rocketchat'; import RocketChat from '../../../lib/rocketchat';
const getMoreMessages = ({ rid, t, tmid, loaderItem }) => { interface IGetMoreMessages {
rid: string;
t?: string;
tmid?: string;
loaderItem: {
t: string;
ts: Date;
};
}
const getMoreMessages = ({ rid, t, tmid, loaderItem }: IGetMoreMessages) => {
if ([MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK].includes(loaderItem.t)) { if ([MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK].includes(loaderItem.t)) {
return RocketChat.loadMessagesForRoom({ return RocketChat.loadMessagesForRoom({
rid, rid,

View File

@ -1,6 +1,6 @@
import RocketChat from '../../../lib/rocketchat'; import RocketChat from '../../../lib/rocketchat';
// unlike getMessages, sync isn't required for threads, because loadMissedMessages does it already // 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) => RocketChat.loadThreadMessages({ tmid, rid });
export default getThreadMessages; export default getThreadMessages;

View File

@ -1,5 +0,0 @@
import RocketChat from '../../../lib/rocketchat';
const readMessages = (rid, newLastOpen) => RocketChat.readMessages(rid, newLastOpen, true);
export default readMessages;

View File

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

View File

@ -950,7 +950,7 @@ SPEC CHECKSUMS:
EXVideoThumbnails: 442c3abadb51a81551a3b53705b7560de390e6f7 EXVideoThumbnails: 442c3abadb51a81551a3b53705b7560de390e6f7
EXWebBrowser: 76783ba5dcb8699237746ecf41a9643d428a4cc5 EXWebBrowser: 76783ba5dcb8699237746ecf41a9643d428a4cc5
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
FBReactNativeSpec: 686ac17e193dcf7d5df4d772b224504dd2f3ad81 FBReactNativeSpec: 110d69378fce79af38271c39894b59fec7890221
Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892 Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892
FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4 FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4
FirebaseCore: 8cd4f8ea22075e0ee582849b1cf79d8816506085 FirebaseCore: 8cd4f8ea22075e0ee582849b1cf79d8816506085