Chore: Migrate ThreadMessagesView to Typescript (#3538)

Co-authored-by: AlexAlexandre <alexalexandrejr@gmail.com>
This commit is contained in:
Reinaldo Neto 2022-01-12 10:53:06 -03:00 committed by GitHub
parent a2b92f5e70
commit cd9ce58660
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 185 additions and 130 deletions

View File

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

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

@ -18,7 +18,7 @@ export type ChatsStackParamList = {
name?: string; name?: string;
fname?: string; fname?: string;
prid?: string; prid?: string;
room: ISubscription; room?: ISubscription;
jumpToMessageId?: string; jumpToMessageId?: string;
jumpToThreadId?: string; jumpToThreadId?: string;
roomUserId?: string; roomUserId?: string;

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
@ -23,7 +22,14 @@ const styles = StyleSheet.create({
} }
}); });
const DropdownItem = React.memo(({ theme, onPress, iconName, text }) => ( interface IDropdownItem {
text: string;
iconName: string;
theme: string;
onPress: () => void;
}
const DropdownItem = React.memo(({ theme, onPress, iconName, text }: IDropdownItem) => (
<Touch theme={theme} onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }}> <Touch theme={theme} onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }}>
<View style={styles.container}> <View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{text}</Text> <Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{text}</Text>
@ -32,11 +38,4 @@ const DropdownItem = React.memo(({ theme, onPress, iconName, text }) => (
</Touch> </Touch>
)); ));
DropdownItem.propTypes = {
text: PropTypes.string,
iconName: PropTypes.string,
theme: PropTypes.string,
onPress: PropTypes.func
};
export default withTheme(DropdownItem); export default withTheme(DropdownItem);

View File

@ -1,17 +1,16 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import DropdownItem from './DropdownItem'; import DropdownItem from './DropdownItem';
const DropdownItemFilter = ({ currentFilter, value, onPress }) => ( interface IDropdownItemFilter {
currentFilter: string;
value: string;
onPress: (value: string) => void;
}
const DropdownItemFilter = ({ currentFilter, value, onPress }: IDropdownItemFilter): JSX.Element => (
<DropdownItem text={I18n.t(value)} iconName={currentFilter === value ? 'check' : null} onPress={() => onPress(value)} /> <DropdownItem text={I18n.t(value)} iconName={currentFilter === value ? 'check' : null} onPress={() => onPress(value)} />
); );
DropdownItemFilter.propTypes = {
currentFilter: PropTypes.string,
value: PropTypes.string,
onPress: PropTypes.func
};
export default DropdownItemFilter; export default DropdownItemFilter;

View File

@ -1,17 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { FILTER } from '../filters'; import { Filter } from '../filters';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import DropdownItem from './DropdownItem'; import DropdownItem from './DropdownItem';
const DropdownItemHeader = ({ currentFilter, onPress }) => { interface IDropdownItemHeader {
currentFilter: Filter;
onPress: () => void;
}
const DropdownItemHeader = ({ currentFilter, onPress }: IDropdownItemHeader): JSX.Element => {
let text; let text;
switch (currentFilter) { switch (currentFilter) {
case FILTER.FOLLOWING: case Filter.Following:
text = I18n.t('Threads_displaying_following'); text = I18n.t('Threads_displaying_following');
break; break;
case FILTER.UNREAD: case Filter.Unread:
text = I18n.t('Threads_displaying_unread'); text = I18n.t('Threads_displaying_unread');
break; break;
default: default:
@ -21,9 +25,4 @@ const DropdownItemHeader = ({ currentFilter, onPress }) => {
return <DropdownItem text={text} iconName='filter' onPress={onPress} />; return <DropdownItem text={text} iconName='filter' onPress={onPress} />;
}; };
DropdownItemHeader.propTypes = {
currentFilter: PropTypes.string,
onPress: PropTypes.func
};
export default DropdownItemHeader; export default DropdownItemHeader;

View File

@ -1,30 +1,31 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Animated, Easing, TouchableWithoutFeedback } from 'react-native'; import { Animated, Easing, TouchableWithoutFeedback } from 'react-native';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { withTheme } from '../../../theme'; import { withTheme } from '../../../theme';
import { headerHeight } from '../../../containers/Header'; import { headerHeight } from '../../../containers/Header';
import * as List from '../../../containers/List'; import * as List from '../../../containers/List';
import { FILTER } from '../filters'; import { Filter } from '../filters';
import DropdownItemFilter from './DropdownItemFilter'; import DropdownItemFilter from './DropdownItemFilter';
import DropdownItemHeader from './DropdownItemHeader'; import DropdownItemHeader from './DropdownItemHeader';
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;
class Dropdown extends React.Component { interface IDropdownProps {
static propTypes = { isMasterDetail: boolean;
isMasterDetail: PropTypes.bool, theme: string;
theme: PropTypes.string, insets: EdgeInsets;
insets: PropTypes.object, currentFilter: Filter;
currentFilter: PropTypes.string, onClose: () => void;
onClose: PropTypes.func, onFilterSelected: (value: string) => void;
onFilterSelected: PropTypes.func }
};
constructor(props) { class Dropdown extends React.Component<IDropdownProps> {
private animatedValue: Animated.Value;
constructor(props: IDropdownProps) {
super(props); super(props);
this.animatedValue = new Animated.Value(0); this.animatedValue = new Animated.Value(0);
} }
@ -85,9 +86,9 @@ class Dropdown extends React.Component {
]}> ]}>
<DropdownItemHeader currentFilter={currentFilter} onPress={this.close} /> <DropdownItemHeader currentFilter={currentFilter} onPress={this.close} />
<List.Separator /> <List.Separator />
<DropdownItemFilter currentFilter={currentFilter} value={FILTER.ALL} onPress={onFilterSelected} /> <DropdownItemFilter currentFilter={currentFilter} value={Filter.All} onPress={onFilterSelected} />
<DropdownItemFilter currentFilter={currentFilter} value={FILTER.FOLLOWING} onPress={onFilterSelected} /> <DropdownItemFilter currentFilter={currentFilter} value={Filter.Following} onPress={onFilterSelected} />
<DropdownItemFilter currentFilter={currentFilter} value={FILTER.UNREAD} onPress={onFilterSelected} /> <DropdownItemFilter currentFilter={currentFilter} value={Filter.Unread} onPress={onFilterSelected} />
</Animated.View> </Animated.View>
</> </>
); );

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
@ -10,6 +9,7 @@ import { themes } from '../../constants/colors';
import Markdown from '../../containers/markdown'; import Markdown from '../../containers/markdown';
import { formatDateThreads, makeThreadName } from '../../utils/room'; import { formatDateThreads, makeThreadName } from '../../utils/room';
import ThreadDetails from '../../containers/ThreadDetails'; import ThreadDetails from '../../containers/ThreadDetails';
import { TThreadModel } from '../../definitions/IThread';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -56,7 +56,18 @@ const styles = StyleSheet.create({
} }
}); });
const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, toggleFollowThread }) => { interface IItem {
item: TThreadModel;
baseUrl: string;
theme: string;
useRealName: boolean;
user: any;
badgeColor: string;
onPress: (item: TThreadModel) => void;
toggleFollowThread: (isFollowing: boolean, id: string) => void;
}
const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, toggleFollowThread }: IItem) => {
const username = (useRealName && item?.u?.name) || item?.u?.username; const username = (useRealName && item?.u?.name) || item?.u?.username;
let time; let time;
if (item?.ts) { if (item?.ts) {
@ -69,16 +80,7 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to
testID={`thread-messages-view-${item.msg}`} testID={`thread-messages-view-${item.msg}`}
style={{ backgroundColor: themes[theme].backgroundColor }}> style={{ backgroundColor: themes[theme].backgroundColor }}>
<View style={styles.container}> <View style={styles.container}>
<Avatar <Avatar style={styles.avatar} text={item?.u?.username} size={36} borderRadius={4} theme={theme} />
style={styles.avatar}
text={item?.u?.username}
size={36}
borderRadius={4}
baseUrl={baseUrl}
userId={user?.id}
token={user?.token}
theme={theme}
/>
<View style={styles.contentContainer}> <View style={styles.contentContainer}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}> <Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>
@ -87,10 +89,11 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View> </View>
<View style={styles.messageContainer}> <View style={styles.messageContainer}>
{/* @ts-ignore */}
<Markdown <Markdown
msg={makeThreadName(item)} msg={makeThreadName(item)}
baseUrl={baseUrl} baseUrl={baseUrl}
username={username} username={username!}
theme={theme} theme={theme}
numberOfLines={2} numberOfLines={2}
style={[styles.markdown]} style={[styles.markdown]}
@ -105,15 +108,4 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to
); );
}; };
Item.propTypes = {
item: PropTypes.object,
baseUrl: PropTypes.string,
theme: PropTypes.string,
useRealName: PropTypes.bool,
user: PropTypes.object,
badgeColor: PropTypes.string,
onPress: PropTypes.func,
toggleFollowThread: PropTypes.func
};
export default withTheme(Item); export default withTheme(Item);

View File

@ -1,5 +0,0 @@
export const FILTER = {
ALL: 'All',
FOLLOWING: 'Following',
UNREAD: 'Unread'
};

View File

@ -0,0 +1,5 @@
export enum Filter {
All = 'All',
Following = 'Following',
Unread = 'Unread'
}

View File

@ -1,11 +1,14 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { HeaderBackButton } from '@react-navigation/stack'; import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import { Observable, Subscription } from 'rxjs';
import Model from '@nozbe/watermelondb/Model';
import Database from '@nozbe/watermelondb/Database';
import ActivityIndicator from '../../containers/ActivityIndicator'; import ActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n'; import I18n from '../../i18n';
@ -30,27 +33,62 @@ import { getHeaderTitlePosition } from '../../containers/Header';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import { LISTENER } from '../../containers/Toast'; import { LISTENER } from '../../containers/Toast';
import SearchHeader from '../../containers/SearchHeader'; import SearchHeader from '../../containers/SearchHeader';
import { FILTER } from './filters'; import { ChatsStackParamList } from '../../stacks/types';
import { IThreadResult, TThreadModel } from '../../definitions/IThread';
import { Filter } from './filters';
import DropdownItemHeader from './Dropdown/DropdownItemHeader'; import DropdownItemHeader from './Dropdown/DropdownItemHeader';
import Dropdown from './Dropdown'; import Dropdown from './Dropdown';
import Item from './Item'; import Item from './Item';
import styles from './styles'; import styles from './styles';
import { SubscriptionType, TSubscriptionModel } from '../../definitions/ISubscription';
const API_FETCH_COUNT = 50; const API_FETCH_COUNT = 50;
class ThreadMessagesView extends React.Component { interface IResultFetch {
static propTypes = { threads: IThreadResult[];
user: PropTypes.object, count: number;
navigation: PropTypes.object, offset: number;
route: PropTypes.object, total: number;
baseUrl: PropTypes.string, success: boolean;
useRealName: PropTypes.bool, }
theme: PropTypes.string,
isMasterDetail: PropTypes.bool,
insets: PropTypes.object
};
constructor(props) { interface IThreadMessagesViewState {
loading: boolean;
end: boolean;
messages: any[];
displayingThreads: TThreadModel[];
subscription: TSubscriptionModel;
showFilterDropdown: boolean;
currentFilter: Filter;
isSearching: boolean;
searchText: string;
}
interface IThreadMessagesViewProps {
navigation: StackNavigationProp<ChatsStackParamList, 'ThreadMessagesView'>;
route: RouteProp<ChatsStackParamList, 'ThreadMessagesView'>;
user: any;
baseUrl: string;
useRealName: boolean;
theme: string;
isMasterDetail: boolean;
insets: EdgeInsets;
}
class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThreadMessagesViewState> {
private mounted: boolean;
private rid: string;
private t: string;
private subSubscription: any;
private messagesSubscription?: Subscription;
private messagesObservable!: Observable<Model>;
constructor(props: IThreadMessagesViewProps) {
super(props); super(props);
this.mounted = false; this.mounted = false;
this.rid = props.route.params?.rid; this.rid = props.route.params?.rid;
@ -60,9 +98,9 @@ class ThreadMessagesView extends React.Component {
end: false, end: false,
messages: [], messages: [],
displayingThreads: [], displayingThreads: [],
subscription: {}, subscription: {} as TSubscriptionModel,
showFilterDropdown: false, showFilterDropdown: false,
currentFilter: FILTER.ALL, currentFilter: Filter.All,
isSearching: false, isSearching: false,
searchText: '' searchText: ''
}; };
@ -76,7 +114,7 @@ class ThreadMessagesView extends React.Component {
this.init(); this.init();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: IThreadMessagesViewProps) {
const { insets } = this.props; const { insets } = this.props;
if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) { if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) {
this.setHeader(); this.setHeader();
@ -93,7 +131,7 @@ class ThreadMessagesView extends React.Component {
} }
} }
getHeader = () => { getHeader = (): StackNavigationOptions => {
const { isSearching } = this.state; const { isSearching } = this.state;
const { navigation, isMasterDetail, insets, theme } = this.props; const { navigation, isMasterDetail, insets, theme } = this.props;
@ -115,7 +153,7 @@ class ThreadMessagesView extends React.Component {
}; };
} }
const options = { const options: StackNavigationOptions = {
headerLeft: () => ( headerLeft: () => (
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} /> <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />
), ),
@ -150,7 +188,7 @@ class ThreadMessagesView extends React.Component {
const db = database.active; const db = database.active;
// subscription query // subscription query
const subscription = await db.collections.get('subscriptions').find(this.rid); const subscription = (await db.collections.get('subscriptions').find(this.rid)) as TSubscriptionModel;
const observable = subscription.observe(); const observable = subscription.observe();
this.subSubscription = observable.subscribe(data => { this.subSubscription = observable.subscribe(data => {
this.setState({ subscription: data }); this.setState({ subscription: data });
@ -162,7 +200,7 @@ class ThreadMessagesView extends React.Component {
} }
}; };
subscribeMessages = (subscription, searchText) => { subscribeMessages = (subscription?: TSubscriptionModel, searchText?: string) => {
try { try {
const db = database.active; const db = database.active;
@ -180,13 +218,17 @@ class ThreadMessagesView extends React.Component {
.get('threads') .get('threads')
.query(...whereClause) .query(...whereClause)
.observeWithColumns(['updated_at']); .observeWithColumns(['updated_at']);
this.messagesSubscription = this.messagesObservable.subscribe(messages => {
// TODO: Refactor when migrate messages
this.messagesSubscription = this.messagesObservable.subscribe((messages: any) => {
const { currentFilter } = this.state; const { currentFilter } = this.state;
const displayingThreads = this.getFilteredThreads(messages, subscription, currentFilter); const displayingThreads = this.getFilteredThreads(messages, subscription!, currentFilter);
if (this.mounted) { if (this.mounted) {
this.setState({ messages, displayingThreads }); this.setState({ messages, displayingThreads });
} else { } else {
// @ts-ignore
this.state.messages = messages; this.state.messages = messages;
// @ts-ignore
this.state.displayingThreads = displayingThreads; this.state.displayingThreads = displayingThreads;
} }
}); });
@ -212,7 +254,15 @@ class ThreadMessagesView extends React.Component {
} }
}; };
updateThreads = async ({ update, remove, lastThreadSync }) => { updateThreads = async ({
update,
remove,
lastThreadSync
}: {
update: IThreadResult[];
remove?: IThreadResult[];
lastThreadSync: Date;
}) => {
const { subscription } = this.state; const { subscription } = this.state;
// if there's no subscription, manage data on this.state.messages // if there's no subscription, manage data on this.state.messages
// note: sync will never be called without subscription // note: sync will never be called without subscription
@ -222,21 +272,23 @@ class ThreadMessagesView extends React.Component {
} }
try { try {
const db = database.active; const db: Database = database.active;
const threadsCollection = db.get('threads'); const threadsCollection = db.get('threads');
const allThreadsRecords = await subscription.threads.fetch(); // TODO: Refactor when migrate room
let threadsToCreate = []; // @ts-ignore
let threadsToUpdate = []; const allThreadsRecords = (await subscription.threads.fetch()) as TThreadModel[];
let threadsToDelete = []; let threadsToCreate: any[] = [];
let threadsToUpdate: any[] = [];
let threadsToDelete: any[] = [];
if (update && update.length) { if (update && update.length) {
update = update.map(m => buildMessage(m)); update = update.map(m => buildMessage(m));
// filter threads // filter threads
threadsToCreate = update.filter(i1 => !allThreadsRecords.find(i2 => i1._id === i2.id)); threadsToCreate = update.filter(i1 => allThreadsRecords.find((i2: { id: string }) => i1._id === i2.id));
threadsToUpdate = allThreadsRecords.filter(i1 => update.find(i2 => i1.id === i2._id)); threadsToUpdate = allThreadsRecords.filter((i1: { id: string }) => update.find(i2 => i1.id === i2._id));
threadsToCreate = threadsToCreate.map(thread => threadsToCreate = threadsToCreate.map(thread =>
threadsCollection.prepareCreate( threadsCollection.prepareCreate(
protectedFunction(t => { protectedFunction((t: any) => {
t._raw = sanitizedRaw({ id: thread._id }, threadsCollection.schema); t._raw = sanitizedRaw({ id: thread._id }, threadsCollection.schema);
t.subscription.set(subscription); t.subscription.set(subscription);
Object.assign(t, thread); Object.assign(t, thread);
@ -246,7 +298,7 @@ class ThreadMessagesView extends React.Component {
threadsToUpdate = threadsToUpdate.map(thread => { threadsToUpdate = threadsToUpdate.map(thread => {
const newThread = update.find(t => t._id === thread.id); const newThread = update.find(t => t._id === thread.id);
return thread.prepareUpdate( return thread.prepareUpdate(
protectedFunction(t => { protectedFunction((t: any) => {
Object.assign(t, newThread); Object.assign(t, newThread);
}) })
); );
@ -254,16 +306,16 @@ class ThreadMessagesView extends React.Component {
} }
if (remove && remove.length) { if (remove && remove.length) {
threadsToDelete = allThreadsRecords.filter(i1 => remove.find(i2 => i1.id === i2._id)); threadsToDelete = allThreadsRecords.filter((i1: { id: string }) => remove.find(i2 => i1.id === i2._id));
threadsToDelete = threadsToDelete.map(t => t.prepareDestroyPermanently()); threadsToDelete = threadsToDelete.map(t => t.prepareDestroyPermanently());
} }
await db.action(async () => { await db.write(async () => {
await db.batch( await db.batch(
...threadsToCreate, ...threadsToCreate,
...threadsToUpdate, ...threadsToUpdate,
...threadsToDelete, ...threadsToDelete,
subscription.prepareUpdate(s => { subscription.prepareUpdate((s: any) => {
s.lastThreadSync = lastThreadSync; s.lastThreadSync = lastThreadSync;
}) })
); );
@ -274,7 +326,7 @@ class ThreadMessagesView extends React.Component {
}; };
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
load = debounce(async lastThreadSync => { load = debounce(async (lastThreadSync: Date) => {
const { loading, end, messages, searchText } = this.state; const { loading, end, messages, searchText } = this.state;
if (end || loading || !this.mounted) { if (end || loading || !this.mounted) {
return; return;
@ -283,7 +335,7 @@ class ThreadMessagesView extends React.Component {
this.setState({ loading: true }); this.setState({ loading: true });
try { try {
const result = await RocketChat.getThreadsList({ const result: IResultFetch = await RocketChat.getThreadsList({
rid: this.rid, rid: this.rid,
count: API_FETCH_COUNT, count: API_FETCH_COUNT,
offset: messages.length, offset: messages.length,
@ -303,7 +355,7 @@ class ThreadMessagesView extends React.Component {
}, 300); }, 300);
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
sync = async updatedSince => { sync = async (updatedSince: Date) => {
this.setState({ loading: true }); this.setState({ loading: true });
try { try {
@ -336,13 +388,13 @@ class ThreadMessagesView extends React.Component {
}); });
}; };
onSearchChangeText = debounce(searchText => { onSearchChangeText = debounce((searchText: string) => {
const { subscription } = this.state; const { subscription } = this.state;
this.setState({ searchText }, () => this.subscribeMessages(subscription, searchText)); this.setState({ searchText }, () => this.subscribeMessages(subscription, searchText));
}, 300); }, 300);
onThreadPress = debounce( onThreadPress = debounce(
item => { (item: any) => {
const { subscription } = this.state; const { subscription } = this.state;
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
if (isMasterDetail) { if (isMasterDetail) {
@ -352,7 +404,7 @@ class ThreadMessagesView extends React.Component {
rid: item.subscription.id, rid: item.subscription.id,
tmid: item.id, tmid: item.id,
name: makeThreadName(item), name: makeThreadName(item),
t: 'thread', t: SubscriptionType.THREAD,
roomUserId: RocketChat.getUidDirectMessage(subscription) roomUserId: RocketChat.getUidDirectMessage(subscription)
}); });
}, },
@ -360,20 +412,21 @@ class ThreadMessagesView extends React.Component {
true true
); );
getBadgeColor = item => { getBadgeColor = (item: TThreadModel) => {
const { subscription } = this.state; const { subscription } = this.state;
const { theme } = this.props; const { theme } = this.props;
return getBadgeColor({ subscription, theme, messageId: item?.id }); return getBadgeColor({ subscription, theme, messageId: item?.id });
}; };
// helper to query threads // helper to query threads
getFilteredThreads = (messages, subscription, currentFilter) => { getFilteredThreads = (messages: any, subscription: TSubscriptionModel, currentFilter?: Filter): TThreadModel[] => {
// const { currentFilter } = this.state; // const { currentFilter } = this.state;
const { user } = this.props; const { user } = this.props;
if (currentFilter === FILTER.FOLLOWING) { if (currentFilter === Filter.Following) {
return messages?.filter(item => item?.replies?.find(u => u === user.id)); return messages?.filter((item: { replies: any[] }) => item?.replies?.find(u => u === user.id));
} else if (currentFilter === FILTER.UNREAD) { }
return messages?.filter(item => subscription?.tunread?.includes(item?.id)); if (currentFilter === Filter.Unread) {
return messages?.filter((item: { id: string }) => subscription?.tunread?.includes(item?.id));
} }
return messages; return messages;
}; };
@ -389,13 +442,13 @@ class ThreadMessagesView extends React.Component {
closeFilterDropdown = () => this.setState({ showFilterDropdown: false }); closeFilterDropdown = () => this.setState({ showFilterDropdown: false });
onFilterSelected = filter => { onFilterSelected = (filter: Filter) => {
const { messages, subscription } = this.state; const { messages, subscription } = this.state;
const displayingThreads = this.getFilteredThreads(messages, subscription, filter); const displayingThreads = this.getFilteredThreads(messages, subscription, filter);
this.setState({ currentFilter: filter, displayingThreads }); this.setState({ currentFilter: filter, displayingThreads });
}; };
toggleFollowThread = async (isFollowingThread, tmid) => { toggleFollowThread = async (isFollowingThread: boolean, tmid: string) => {
try { try {
await RocketChat.toggleFollowMessage(tmid, !isFollowingThread); await RocketChat.toggleFollowMessage(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') });
@ -404,7 +457,7 @@ class ThreadMessagesView extends React.Component {
} }
}; };
renderItem = ({ item }) => { renderItem = ({ item }: { item: TThreadModel }) => {
const { user, navigation, baseUrl, useRealName } = this.props; const { user, navigation, baseUrl, useRealName } = this.props;
const badgeColor = this.getBadgeColor(item); const badgeColor = this.getBadgeColor(item);
return ( return (
@ -442,9 +495,9 @@ class ThreadMessagesView extends React.Component {
const { theme } = this.props; const { theme } = this.props;
if (!messages?.length || !displayingThreads?.length) { if (!messages?.length || !displayingThreads?.length) {
let text; let text;
if (currentFilter === FILTER.FOLLOWING) { if (currentFilter === Filter.Following) {
text = I18n.t('No_threads_following'); text = I18n.t('No_threads_following');
} else if (currentFilter === FILTER.UNREAD) { } else if (currentFilter === Filter.Unread) {
text = I18n.t('No_threads_unread'); text = I18n.t('No_threads_unread');
} else { } else {
text = I18n.t('No_threads'); text = I18n.t('No_threads');
@ -494,7 +547,7 @@ class ThreadMessagesView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
baseUrl: state.server.server, baseUrl: state.server.server,
user: getUserSelector(state), user: getUserSelector(state),
useRealName: state.settings.UI_Use_Real_Name, useRealName: state.settings.UI_Use_Real_Name,

View File

@ -25,6 +25,6 @@ export default StyleSheet.create({
borderBottomWidth: StyleSheet.hairlineWidth borderBottomWidth: StyleSheet.hairlineWidth
}, },
backdrop: { backdrop: {
...StyleSheet.absoluteFill ...StyleSheet.absoluteFillObject
} }
}); });