Chore: Migrate ThreadMessagesView to Typescript (#3538)
Co-authored-by: AlexAlexandre <alexalexandrejr@gmail.com>
This commit is contained in:
parent
a2b92f5e70
commit
cd9ce58660
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IMention {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
type: string;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IUrl {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
image: string;
|
||||||
|
url: string;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
|
@ -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;
|
|
@ -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;
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
|
@ -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);
|
|
@ -1,5 +0,0 @@
|
||||||
export const FILTER = {
|
|
||||||
ALL: 'All',
|
|
||||||
FOLLOWING: 'Following',
|
|
||||||
UNREAD: 'Unread'
|
|
||||||
};
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export enum Filter {
|
||||||
|
All = 'All',
|
||||||
|
Following = 'Following',
|
||||||
|
Unread = 'Unread'
|
||||||
|
}
|
|
@ -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,
|
|
@ -25,6 +25,6 @@ export default StyleSheet.create({
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth
|
borderBottomWidth: StyleSheet.hairlineWidth
|
||||||
},
|
},
|
||||||
backdrop: {
|
backdrop: {
|
||||||
...StyleSheet.absoluteFill
|
...StyleSheet.absoluteFillObject
|
||||||
}
|
}
|
||||||
});
|
});
|
Loading…
Reference in New Issue