Chore: Evaluate RoomView - TypeScript (#4134)

* Chore: Evaluate RoomView - TypeScript

* fix messagebox and list refs

* fix the refs

* refactor other refs

* remove any from privates

* storyshot tweak
This commit is contained in:
Reinaldo Neto 2022-05-02 19:58:23 -03:00 committed by GitHub
parent cb1dabbc16
commit a84d4e9534
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 77 additions and 67 deletions

View File

@ -7,8 +7,8 @@ export type TActionSheetOptionsItem = { title: string; icon: TIconsName; onPress
export type TActionSheetOptions = { export type TActionSheetOptions = {
options: TActionSheetOptionsItem[]; options: TActionSheetOptionsItem[];
headerHeight: number; headerHeight?: number;
customHeader: React.ReactElement | null; customHeader?: React.ReactElement | null;
hasCancel?: boolean; hasCancel?: boolean;
}; };
interface IActionSheetProvider { interface IActionSheetProvider {

View File

@ -19,7 +19,7 @@ import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel }
import { getPermalinkMessage, hasPermission } from '../../lib/methods'; import { getPermalinkMessage, hasPermission } from '../../lib/methods';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
export interface IMessageActions { export interface IMessageActionsProps {
room: TSubscriptionModel; room: TSubscriptionModel;
tmid?: string; tmid?: string;
user: Pick<ILoggedUser, 'id'>; user: Pick<ILoggedUser, 'id'>;
@ -43,8 +43,12 @@ export interface IMessageActions {
pinMessagePermission?: string[]; pinMessagePermission?: string[];
} }
export interface IMessageActions {
showMessageActions: (message: TAnyMessageModel) => Promise<void>;
}
const MessageActions = React.memo( const MessageActions = React.memo(
forwardRef( forwardRef<IMessageActions, IMessageActionsProps>(
( (
{ {
room, room,
@ -68,7 +72,7 @@ const MessageActions = React.memo(
deleteMessagePermission, deleteMessagePermission,
forceDeleteMessagePermission, forceDeleteMessagePermission,
pinMessagePermission pinMessagePermission
}: IMessageActions, },
ref ref
) => { ) => {
let permissions = { let permissions = {

View File

@ -128,7 +128,7 @@ interface IMessageBoxState {
} }
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> { class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
private text: string; public text: string;
private selection: { start: number; end: number }; private selection: { start: number; end: number };
@ -1182,4 +1182,6 @@ const dispatchToProps = {
typing: (rid: any, status: any) => userTypingAction(rid, status) typing: (rid: any, status: any) => userTypingAction(rid, status)
}; };
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox)) as any; export type MessageBoxType = MessageBox;
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox));

View File

@ -9,9 +9,12 @@ import log from '../utils/log';
import { TMessageModel } from '../definitions'; import { TMessageModel } from '../definitions';
import { resendMessage } from '../lib/methods'; import { resendMessage } from '../lib/methods';
const MessageErrorActions = forwardRef(({ tmid }: { tmid: string }, ref) => { export interface IMessageErrorActions {
// TODO - remove this any after merge ActionSheet evaluate showMessageErrorActions: (message: TMessageModel) => void;
const { showActionSheet }: any = useActionSheet(); }
const MessageErrorActions = forwardRef<IMessageErrorActions, { tmid?: string }>(({ tmid }, ref) => {
const { showActionSheet } = useActionSheet();
const handleResend = protectedFunction(async (message: TMessageModel) => { const handleResend = protectedFunction(async (message: TMessageModel) => {
await resendMessage(message, tmid); await resendMessage(message, tmid);

View File

@ -50,8 +50,12 @@ export interface IJoinCodeProps {
theme: TSupportedThemes; theme: TSupportedThemes;
} }
export interface IJoinCode {
show: () => void;
}
const JoinCode = React.memo( const JoinCode = React.memo(
forwardRef(({ rid, t, onJoin, isMasterDetail, theme }: IJoinCodeProps, ref) => { forwardRef<IJoinCode, IJoinCodeProps>(({ rid, t, onJoin, isMasterDetail, theme }, ref) => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [code, setCode] = useState(''); const [code, setCode] = useState('');
@ -125,4 +129,5 @@ const JoinCode = React.memo(
const mapStateToProps = (state: IApplicationState) => ({ const mapStateToProps = (state: IApplicationState) => ({
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

@ -17,8 +17,10 @@ const styles = StyleSheet.create({
} }
}); });
export type TListRef = React.RefObject<FlatList & { getNode: () => FlatList }>;
export interface IListProps extends FlatListProps<any> { export interface IListProps extends FlatListProps<any> {
listRef: any; listRef: TListRef;
} }
const List = ({ listRef, ...props }: IListProps) => ( const List = ({ listRef, ...props }: IListProps) => (

View File

@ -16,7 +16,7 @@ import debounce from '../../../utils/debounce';
import { animateNextTransition } from '../../../utils/layoutAnimation'; import { animateNextTransition } from '../../../utils/layoutAnimation';
import log from '../../../utils/log'; import log from '../../../utils/log';
import EmptyRoom from '../EmptyRoom'; import EmptyRoom from '../EmptyRoom';
import List, { IListProps } from './List'; import List, { IListProps, TListRef } from './List';
import NavBottomFAB from './NavBottomFAB'; import NavBottomFAB from './NavBottomFAB';
import { loadMissedMessages, loadThreadMessages } from '../../../lib/methods'; import { loadMissedMessages, loadThreadMessages } from '../../../lib/methods';
import { Services } from '../../../lib/services'; import { Services } from '../../../lib/services';
@ -43,7 +43,7 @@ export interface IListContainerProps {
tmid?: string; tmid?: string;
theme: TSupportedThemes; theme: TSupportedThemes;
loading: boolean; loading: boolean;
listRef: React.RefObject<IListProps>; listRef: TListRef;
hideSystemMessages?: string[]; hideSystemMessages?: string[];
tunread?: string[]; tunread?: string[];
ignored?: string[]; ignored?: string[];
@ -272,8 +272,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
handleScrollToIndexFailed: FlatListProps<any>['onScrollToIndexFailed'] = params => { handleScrollToIndexFailed: FlatListProps<any>['onScrollToIndexFailed'] = params => {
const { listRef } = this.props; const { listRef } = this.props;
// @ts-ignore listRef.current?.getNode().scrollToIndex({ index: params.highestMeasuredFrameIndex, animated: false });
listRef.current.getNode().scrollToIndex({ index: params.highestMeasuredFrameIndex, animated: false });
}; };
jumpToMessage = (messageId: string) => jumpToMessage = (messageId: string) =>
@ -283,8 +282,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
const { listRef } = this.props; const { listRef } = this.props;
const index = messages.findIndex(item => item.id === messageId); const index = messages.findIndex(item => item.id === messageId);
if (index > -1) { if (index > -1) {
// @ts-ignore listRef.current?.getNode().scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 });
listRef.current.getNode().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 => vi.key).includes(messageId)) {
if (!this.jumping) { if (!this.jumping) {
@ -300,8 +298,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
}, 10000); }, 10000);
await setTimeout(() => resolve(), 300); await setTimeout(() => resolve(), 300);
} else { } else {
// @ts-ignore listRef.current?.getNode().scrollToIndex({ index: messages.length - 1, animated: false });
listRef.current.getNode().scrollToIndex({ index: messages.length - 1, animated: false });
if (!this.jumping) { if (!this.jumping) {
return resolve(); return resolve();
} }
@ -316,8 +313,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
jumpToBottom = () => { jumpToBottom = () => {
const { listRef } = this.props; const { listRef } = this.props;
// @ts-ignore listRef.current?.getNode().scrollToOffset({ offset: -100 });
listRef.current.getNode().scrollToOffset({ offset: -100 });
}; };
renderFooter = () => { renderFooter = () => {
@ -367,4 +363,6 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
} }
} }
export type ListContainerType = ListContainer;
export default ListContainer; export default ListContainer;

View File

@ -15,8 +15,8 @@ import { replyBroadcast } from '../../actions/messages';
import database from '../../lib/database'; import database from '../../lib/database';
import Message from '../../containers/message'; import Message from '../../containers/message';
import MessageActions, { IMessageActions } from '../../containers/MessageActions'; import MessageActions, { IMessageActions } from '../../containers/MessageActions';
import MessageErrorActions from '../../containers/MessageErrorActions'; import MessageErrorActions, { IMessageErrorActions } from '../../containers/MessageErrorActions';
import MessageBox, { IMessageBoxProps } from '../../containers/MessageBox'; import MessageBox, { MessageBoxType } from '../../containers/MessageBox';
import log, { events, logEvent } from '../../utils/log'; import log, { events, logEvent } from '../../utils/log';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import I18n from '../../i18n'; import I18n from '../../i18n';
@ -58,10 +58,10 @@ import Separator from './Separator';
import RightButtons from './RightButtons'; import RightButtons from './RightButtons';
import LeftButtons from './LeftButtons'; import LeftButtons from './LeftButtons';
import styles from './styles'; import styles from './styles';
import JoinCode, { IJoinCodeProps } from './JoinCode'; import JoinCode, { IJoinCode } from './JoinCode';
import UploadProgress from './UploadProgress'; import UploadProgress from './UploadProgress';
import ReactionPicker from './ReactionPicker'; import ReactionPicker from './ReactionPicker';
import List, { IListContainerProps, IListProps } from './List'; import List, { ListContainerType } from './List';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import { import {
IApplicationState, IApplicationState,
@ -80,6 +80,8 @@ import {
} from '../../definitions'; } from '../../definitions';
import { ICustomEmojis } from '../../reducers/customEmojis'; import { ICustomEmojis } from '../../reducers/customEmojis';
import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants'; import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants';
import { TListRef } from './List/List';
import { ModalStackParamList } from '../../stacks/MasterDetailStack/types';
import { import {
callJitsi, callJitsi,
canAutoTranslate as canAutoTranslateMethod, canAutoTranslate as canAutoTranslateMethod,
@ -94,6 +96,8 @@ import {
} from '../../lib/methods'; } from '../../lib/methods';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
type TStateAttrsUpdate = keyof IRoomViewState;
const stateAttrsUpdate = [ const stateAttrsUpdate = [
'joined', 'joined',
'lastOpen', 'lastOpen',
@ -107,7 +111,10 @@ const stateAttrsUpdate = [
'readOnly', 'readOnly',
'member', 'member',
'showingBlockingLoader' 'showingBlockingLoader'
]; ] as TStateAttrsUpdate[];
type TRoomUpdate = keyof TSubscriptionModel;
const roomAttrsUpdate = [ const roomAttrsUpdate = [
'f', 'f',
'ro', 'ro',
@ -130,7 +137,7 @@ const roomAttrsUpdate = [
'teamMain', 'teamMain',
'teamId', 'teamId',
'onHold' 'onHold'
] as const; ] as TRoomUpdate[];
interface IRoomViewProps extends IBaseScreen<ChatsStackParamList, 'RoomView'> { interface IRoomViewProps extends IBaseScreen<ChatsStackParamList, 'RoomView'> {
user: Pick<ILoggedUser, 'id' | 'username' | 'token' | 'showMessageInMainThread'>; user: Pick<ILoggedUser, 'id' | 'username' | 'token' | 'showMessageInMainThread'>;
@ -151,14 +158,12 @@ interface IRoomViewProps extends IBaseScreen<ChatsStackParamList, 'RoomView'> {
insets: EdgeInsets; insets: EdgeInsets;
} }
type TRoomUpdate = typeof roomAttrsUpdate[number];
interface IRoomViewState { interface IRoomViewState {
[key: string]: any; [key: string]: any;
joined: boolean; joined: boolean;
room: TSubscriptionModel | { rid: string; t: string; name?: string; fname?: string; prid?: string; joinCodeRequired?: boolean }; room: TSubscriptionModel | { rid: string; t: string; name?: string; fname?: string; prid?: string; joinCodeRequired?: boolean };
roomUpdate: { roomUpdate: {
[K in TRoomUpdate]?: any; // TODO: get type from TSubscriptionModel [K in TRoomUpdate]?: any;
}; };
member: any; member: any;
lastOpen: Date | null; lastOpen: Date | null;
@ -182,23 +187,27 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
private tmid?: string; private tmid?: string;
private jumpToMessageId?: string; private jumpToMessageId?: string;
private jumpToThreadId?: string; private jumpToThreadId?: string;
// TODO: review these refs private messagebox: React.RefObject<MessageBoxType>;
private messagebox: React.RefObject<IMessageBoxProps>; private list: React.RefObject<ListContainerType>;
private list: React.RefObject<IListContainerProps>; private joinCode: React.RefObject<IJoinCode>;
private joinCode: React.RefObject<IJoinCodeProps>; private flatList: TListRef;
private flatList: React.RefObject<IListProps>;
private mounted: boolean; private mounted: boolean;
private sub?: any;
private offset = 0; private offset = 0;
private didMountInteraction: any;
private subSubscription?: Subscription; private subSubscription?: Subscription;
private queryUnreads?: Subscription; private queryUnreads?: Subscription;
private retryInit = 0; private retryInit = 0;
private retryInitTimeout?: number; private retryInitTimeout?: number;
private retryFindCount = 0; private retryFindCount = 0;
private retryFindTimeout?: number; private retryFindTimeout?: number;
private messageErrorActions?: React.RefObject<any>; // TODO: type me private messageErrorActions?: IMessageErrorActions | null;
private messageActions?: React.RefObject<IMessageActions>; private messageActions?: IMessageActions | null;
// Type of InteractionManager.runAfterInteractions
private didMountInteraction?: {
then: (onfulfilled?: (() => any) | undefined, onrejected?: (() => any) | undefined) => Promise<any>;
done: (...args: any[]) => any;
cancel: () => void;
};
private sub?: RoomClass;
constructor(props: IRoomViewProps) { constructor(props: IRoomViewProps) {
super(props); super(props);
@ -312,6 +321,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
if (member.statusText !== nextState.member.statusText) { if (member.statusText !== nextState.member.statusText) {
return true; return true;
} }
const stateUpdated = stateAttrsUpdate.some(key => nextState[key] !== state[key]); const stateUpdated = stateAttrsUpdate.some(key => nextState[key] !== state[key]);
if (stateUpdated) { if (stateUpdated) {
return true; return true;
@ -340,8 +350,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
if (appState === 'foreground' && appState !== prevProps.appState && this.rid) { if (appState === 'foreground' && appState !== prevProps.appState && this.rid) {
// Fire List.query() just to keep observables working // Fire List.query() just to keep observables working
if (this.list && this.list.current) { if (this.list && this.list.current) {
// @ts-ignore TODO: is this working? this.list.current?.query();
this.list.current?.query?.();
} }
} }
// If it's not direct message // If it's not direct message
@ -379,7 +388,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
const db = database.active; const db = database.active;
this.mounted = false; this.mounted = false;
if (!editing && this.messagebox && this.messagebox.current) { if (!editing && this.messagebox && this.messagebox.current) {
// @ts-ignore
const { text } = this.messagebox.current; const { text } = this.messagebox.current;
let obj: TSubscriptionModel | TThreadModel | null = null; let obj: TSubscriptionModel | TThreadModel | null = null;
if (this.tmid) { if (this.tmid) {
@ -394,9 +402,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
} }
if (obj) { if (obj) {
try { try {
const object = obj;
await db.write(async () => { await db.write(async () => {
// FIXME: why do I need to tell ts this is non null if we have that if condition above? await object.update(r => {
await obj!.update(r => {
r.draftMessage = text; r.draftMessage = text;
}); });
}); });
@ -543,20 +551,18 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
}); });
}; };
goRoomActionsView = (screen?: string) => { goRoomActionsView = (screen?: keyof ModalStackParamList) => {
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;
if (isMasterDetail) { if (isMasterDetail) {
// @ts-ignore TODO: find a way to make it work // @ts-ignore
navigation.navigate('ModalStackNavigator', { navigation.navigate('ModalStackNavigator', {
// @ts-ignore
screen: screen ?? 'RoomActionsView', screen: screen ?? 'RoomActionsView',
params: { params: {
rid: this.rid as string, rid: this.rid as string,
t: this.t as SubscriptionType, t: this.t as SubscriptionType,
// @ts-ignore room: room as ISubscription,
room,
member, member,
showCloseModal: !!screen, showCloseModal: !!screen,
joined joined
@ -695,7 +701,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
}; };
errorActionsShow = (message: TAnyMessageModel) => { errorActionsShow = (message: TAnyMessageModel) => {
// @ts-ignore
this.messageErrorActions?.showMessageErrorActions(message); this.messageErrorActions?.showMessageErrorActions(message);
}; };
@ -745,7 +750,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
}; };
onMessageLongPress = (message: TAnyMessageModel) => { onMessageLongPress = (message: TAnyMessageModel) => {
// @ts-ignore
this.messageActions?.showMessageActions(message); this.messageActions?.showMessageActions(message);
}; };
@ -875,10 +879,8 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
if (message.fromServer && !message.tmid && this.rid) { if (message.fromServer && !message.tmid && this.rid) {
await loadSurroundingMessages({ messageId, rid: this.rid }); await loadSurroundingMessages({ messageId, rid: this.rid });
} }
// @ts-ignore 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();
// @ts-ignore
this.list.current.cancelJumpToMessage();
} }
} catch (e) { } catch (e) {
log(e); log(e);
@ -920,8 +922,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
const { user } = this.props; const { user } = this.props;
sendMessage(rid, message, this.tmid || tmid, user, tshow).then(() => { sendMessage(rid, message, this.tmid || tmid, user, tshow).then(() => {
if (this.list && this.list.current) { if (this.list && this.list.current) {
// @ts-ignore this.list.current?.update();
this.list.current.update();
} }
this.setLastOpen(null); this.setLastOpen(null);
Review.pushPositiveEvent(); Review.pushPositiveEvent();
@ -959,7 +960,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
} else { } else {
const { joinCodeRequired, rid } = room; const { joinCodeRequired, rid } = room;
if (joinCodeRequired) { if (joinCodeRequired) {
// @ts-ignore
this.joinCode.current?.show(); this.joinCode.current?.show();
} else { } else {
await Services.joinRoom(rid, null, this.t as any); await Services.joinRoom(rid, null, this.t as any);
@ -1102,17 +1102,17 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
if (handleCommandScroll(event)) { if (handleCommandScroll(event)) {
const offset = input === 'UIKeyInputUpArrow' ? 100 : -100; const offset = input === 'UIKeyInputUpArrow' ? 100 : -100;
this.offset += offset; this.offset += offset;
// @ts-ignore this.flatList?.current?.scrollToOffset({ offset: this.offset });
this.flatList?.scrollToOffset({ offset: this.offset });
} else if (handleCommandRoomActions(event)) { } else if (handleCommandRoomActions(event)) {
this.goRoomActionsView(); this.goRoomActionsView();
} else if (handleCommandSearchMessages(event)) { } else if (handleCommandSearchMessages(event)) {
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) {
// @ts-ignore
const message = this.list.current.getLastMessage(); const message = this.list.current.getLastMessage();
this.onReplyInit(message, false); if (message) {
this.onReplyInit(message, false);
}
} }
} }
} }
@ -1360,7 +1360,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
return ( return (
<> <>
<MessageActions <MessageActions
// @ts-ignore
ref={ref => (this.messageActions = ref)} ref={ref => (this.messageActions = ref)}
tmid={this.tmid} tmid={this.tmid}
room={room} room={room}
@ -1371,7 +1370,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
onReactionPress={this.onReactionPress} onReactionPress={this.onReactionPress}
isReadOnly={readOnly} isReadOnly={readOnly}
/> />
{/* @ts-ignore TODO: missing interface on MessageErrorActions */}
<MessageErrorActions ref={ref => (this.messageErrorActions = ref)} tmid={this.tmid} /> <MessageErrorActions ref={ref => (this.messageErrorActions = ref)} tmid={this.tmid} />
</> </>
); );
@ -1396,11 +1394,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
<StatusBar /> <StatusBar />
<Banner title={I18n.t('Announcement')} text={announcement} bannerClosed={bannerClosed} closeBanner={this.closeBanner} /> <Banner title={I18n.t('Announcement')} text={announcement} bannerClosed={bannerClosed} closeBanner={this.closeBanner} />
<List <List
// @ts-ignore
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={tunread} tunread={tunread}