Migrate to TypeScript, update interfaces and some changes to container components
This commit is contained in:
parent
8ccce58ddd
commit
ecee12c9d0
|
@ -1,19 +1,19 @@
|
|||
export interface IAvatar {
|
||||
server: string;
|
||||
server?: string;
|
||||
style: any;
|
||||
text: string;
|
||||
avatar: string;
|
||||
avatar?: string;
|
||||
emoji: string;
|
||||
size: number;
|
||||
borderRadius: number;
|
||||
type: string;
|
||||
children: JSX.Element;
|
||||
type?: string;
|
||||
children?: JSX.Element;
|
||||
user: {
|
||||
id: string;
|
||||
token: string;
|
||||
};
|
||||
theme: string;
|
||||
onPress(): void;
|
||||
onPress?(): void;
|
||||
getCustomEmoji(): any;
|
||||
avatarETag: string;
|
||||
isStatic: boolean;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
|
@ -41,8 +42,9 @@ const styles = StyleSheet.create({
|
|||
|
||||
interface IThreadDetails {
|
||||
item: {
|
||||
tcount: number | string;
|
||||
replies: any;
|
||||
tcount?: number | string;
|
||||
dcount?: number | string;
|
||||
replies?: any;
|
||||
id: string;
|
||||
};
|
||||
user: {
|
||||
|
@ -50,16 +52,25 @@ interface IThreadDetails {
|
|||
};
|
||||
badgeColor: string;
|
||||
toggleFollowThread: Function;
|
||||
thread: boolean;
|
||||
style: object;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, theme }: IThreadDetails) => {
|
||||
let { tcount } = item;
|
||||
if (tcount >= 1000) {
|
||||
tcount = '+999';
|
||||
} else if (tcount >= 100) {
|
||||
tcount = '+99';
|
||||
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, thread, style, theme }: IThreadDetails) => {
|
||||
let { tcount, dcount } = item;
|
||||
if (thread) {
|
||||
if (tcount! >= 1000) {
|
||||
tcount = '+999';
|
||||
} else if (tcount! >= 100) {
|
||||
tcount = '+99';
|
||||
}
|
||||
}
|
||||
|
||||
if (dcount! >= 1000) {
|
||||
dcount = '+999';
|
||||
} else if (dcount! >= 100) {
|
||||
dcount = '+99';
|
||||
}
|
||||
|
||||
let replies = item?.replies?.length ?? 0;
|
||||
|
@ -75,30 +86,34 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
|
|||
<View style={[styles.container, style]}>
|
||||
<View style={styles.detailsContainer}>
|
||||
<View style={styles.detailContainer}>
|
||||
<CustomIcon name='threads' size={24} color={themes[theme].auxiliaryText} />
|
||||
<CustomIcon name={thread ? 'threads' : 'discussions'} size={24} color={themes[theme].auxiliaryText} />
|
||||
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
|
||||
{tcount}
|
||||
{thread ? tcount : dcount}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.detailContainer}>
|
||||
<CustomIcon name='user' size={24} color={themes[theme].auxiliaryText} />
|
||||
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
|
||||
{replies}
|
||||
</Text>
|
||||
</View>
|
||||
{thread ? (
|
||||
<View style={styles.detailContainer}>
|
||||
<CustomIcon name='user' size={24} color={themes[theme].auxiliaryText} />
|
||||
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
|
||||
{replies}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<View style={styles.badgeContainer}>
|
||||
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
|
||||
<Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}>
|
||||
<CustomIcon
|
||||
size={24}
|
||||
name={isFollowing ? 'notification' : 'notification-disabled'}
|
||||
color={themes[theme].auxiliaryTintColor}
|
||||
/>
|
||||
</Touchable>
|
||||
</View>
|
||||
{thread ? (
|
||||
<View style={styles.badgeContainer}>
|
||||
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
|
||||
<Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}>
|
||||
<CustomIcon
|
||||
size={24}
|
||||
name={isFollowing ? 'notification' : 'notification-disabled'}
|
||||
color={themes[theme].auxiliaryTintColor}
|
||||
/>
|
||||
</Touchable>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,25 +23,25 @@ import { isValidURL } from '../../utils/url';
|
|||
|
||||
interface IMarkdownProps {
|
||||
msg: string;
|
||||
getCustomEmoji: Function;
|
||||
getCustomEmoji?: Function;
|
||||
baseUrl: string;
|
||||
username: string;
|
||||
tmid: string;
|
||||
isEdited: boolean;
|
||||
numberOfLines: number;
|
||||
customEmojis: boolean;
|
||||
useRealName: boolean;
|
||||
channels: {
|
||||
tmid?: string;
|
||||
isEdited?: boolean;
|
||||
numberOfLines?: number;
|
||||
customEmojis?: boolean;
|
||||
useRealName?: boolean;
|
||||
channels?: {
|
||||
name: string;
|
||||
_id: number;
|
||||
}[];
|
||||
mentions: object[];
|
||||
navToRoomInfo: Function;
|
||||
preview: boolean;
|
||||
theme: string;
|
||||
testID: string;
|
||||
style: any;
|
||||
onLinkPress: Function;
|
||||
mentions?: object[];
|
||||
navToRoomInfo?: Function;
|
||||
preview?: boolean;
|
||||
theme?: string;
|
||||
testID?: string;
|
||||
style?: any;
|
||||
onLinkPress?: Function;
|
||||
}
|
||||
|
||||
type TLiteral = {
|
||||
|
|
|
@ -807,21 +807,17 @@ const RocketChat = {
|
|||
encrypted
|
||||
});
|
||||
},
|
||||
getDiscussions({
|
||||
roomId, offset, count, text
|
||||
}) {
|
||||
getDiscussions({ roomId, offset, count, text }) {
|
||||
const params = {
|
||||
roomId,
|
||||
offset,
|
||||
count,
|
||||
text
|
||||
...(text && { text })
|
||||
};
|
||||
// RC 2.4.0
|
||||
return this.sdk.get('chat.getDiscussions', params);
|
||||
},
|
||||
createTeam({
|
||||
name, users, type, readOnly, broadcast, encrypted
|
||||
}) {
|
||||
createTeam({ name, users, type, readOnly, broadcast, encrypted }) {
|
||||
const params = {
|
||||
name,
|
||||
users,
|
||||
|
|
|
@ -1,48 +1,76 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { HeaderBackButton } from '@react-navigation/stack';
|
||||
|
||||
import ActivityIndicator from '../containers/ActivityIndicator';
|
||||
import I18n from '../i18n';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import log from '../utils/log';
|
||||
import debounce from '../utils/debounce';
|
||||
import { themes } from '../constants/colors';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import * as List from '../containers/List';
|
||||
import BackgroundContainer from '../containers/BackgroundContainer';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
import { getHeaderTitlePosition } from '../containers/Header';
|
||||
|
||||
import { useTheme } from '../theme';
|
||||
import Message from '../containers/message';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import SearchHeader from '../containers/SearchHeader';
|
||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||
import I18n from '../../i18n';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import log from '../../utils/log';
|
||||
import debounce from '../../utils/debounce';
|
||||
import { themes } from '../../constants/colors';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import * as List from '../../containers/List';
|
||||
import BackgroundContainer from '../../containers/BackgroundContainer';
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import { getHeaderTitlePosition } from '../../containers/Header';
|
||||
import { useTheme } from '../../theme';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import SearchHeader from '../../containers/SearchHeader';
|
||||
import Item from '../ThreadMessagesView/Item';
|
||||
import styles from './styles';
|
||||
|
||||
const API_FETCH_COUNT = 50;
|
||||
|
||||
const DiscussionsView = ({ navigation, route }) => {
|
||||
const rid = route.params?.rid;
|
||||
const canAutoTranslate = route.params?.canAutoTranslate;
|
||||
const autoTranslate = route.params?.autoTranslate;
|
||||
const autoTranslateLanguage = route.params?.autoTranslateLanguage;
|
||||
const navToRoomInfo = route.params?.navToRoomInfo;
|
||||
interface IDiscussionsViewProps {
|
||||
navigation: any;
|
||||
route: {
|
||||
params?: {
|
||||
rid: string;
|
||||
canAutoTranslate: boolean;
|
||||
autoTranslate: boolean;
|
||||
autoTranslateLanguage: string;
|
||||
navToRoomInfo: Function;
|
||||
};
|
||||
};
|
||||
item: {
|
||||
msg: string;
|
||||
};
|
||||
}
|
||||
|
||||
const user = useSelector(state => state.login?.user);
|
||||
const baseUrl = useSelector(state => state.server.server);
|
||||
const useRealName = useSelector(state => state.settings.UI_Use_Real_Name);
|
||||
const Message_TimeFormat = useSelector(state => state.settings.Message_TimeFormat);
|
||||
const isMasterDetail = useSelector(state => state.app.isMasterDetail);
|
||||
interface IState {
|
||||
login?: {
|
||||
user: object;
|
||||
};
|
||||
server: {
|
||||
server: string;
|
||||
};
|
||||
settings: {
|
||||
UI_Use_Real_Name: boolean;
|
||||
Message_TimeFormat: string;
|
||||
};
|
||||
app: {
|
||||
isMasterDetail: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const DiscussionsView = ({ navigation, route }: IDiscussionsViewProps): JSX.Element => {
|
||||
const rid = route.params?.rid;
|
||||
|
||||
const user = useSelector((state: IState) => state.login?.user);
|
||||
const baseUrl = useSelector((state: IState) => state.server?.server);
|
||||
const useRealName = useSelector((state: IState) => state.settings?.UI_Use_Real_Name);
|
||||
const isMasterDetail = useSelector((state: IState) => state.app?.isMasterDetail);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [discussions, setDiscussions] = useState([]);
|
||||
const [search, setSearch] = useState([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [searchTotal, setSearchTotal] = useState(0);
|
||||
|
||||
const { theme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
@ -64,7 +92,7 @@ const DiscussionsView = ({ navigation, route }) => {
|
|||
if (result.success) {
|
||||
if (isSearching) {
|
||||
setSearch(result.messages);
|
||||
setTotal(result.total);
|
||||
setSearchTotal(result.total);
|
||||
} else {
|
||||
setDiscussions(result.messages);
|
||||
setTotal(result.total);
|
||||
|
@ -77,7 +105,7 @@ const DiscussionsView = ({ navigation, route }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const onSearchChangeText = debounce(async text => {
|
||||
const onSearchChangeText = debounce(async (text: string) => {
|
||||
setIsSearching(true);
|
||||
await load(text);
|
||||
}, 300);
|
||||
|
@ -119,25 +147,24 @@ const DiscussionsView = ({ navigation, route }) => {
|
|||
|
||||
const options = {
|
||||
headerLeft: () => (
|
||||
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />
|
||||
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme!].headerTintColor} />
|
||||
),
|
||||
headerTitleAlign: 'center',
|
||||
headerTitle: I18n.t('Discussions'),
|
||||
headerTitleContainerStyle: {
|
||||
left: null,
|
||||
right: null
|
||||
}
|
||||
},
|
||||
headerRight: () => (
|
||||
<HeaderButton.Container>
|
||||
<HeaderButton.Item iconName='search' onPress={onSearchPress} />
|
||||
</HeaderButton.Container>
|
||||
)
|
||||
};
|
||||
|
||||
if (isMasterDetail) {
|
||||
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
|
||||
}
|
||||
|
||||
options.headerRight = () => (
|
||||
<HeaderButton.Container>
|
||||
<HeaderButton.Item iconName='search' onPress={onSearchPress} />
|
||||
</HeaderButton.Container>
|
||||
);
|
||||
return options;
|
||||
};
|
||||
|
||||
|
@ -151,7 +178,7 @@ const DiscussionsView = ({ navigation, route }) => {
|
|||
}, [navigation, isSearching]);
|
||||
|
||||
const onDiscussionPress = debounce(
|
||||
item => {
|
||||
(item: any) => {
|
||||
navigation.push('RoomView', {
|
||||
rid: item.drid,
|
||||
prid: item.rid,
|
||||
|
@ -163,20 +190,19 @@ const DiscussionsView = ({ navigation, route }) => {
|
|||
true
|
||||
);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<Message
|
||||
item={item}
|
||||
user={user}
|
||||
rid={rid}
|
||||
navToRoomInfo={navToRoomInfo}
|
||||
onDiscussionPress={onDiscussionPress}
|
||||
baseUrl={baseUrl}
|
||||
timeFormat={Message_TimeFormat}
|
||||
useRealName={useRealName}
|
||||
autoTranslateRoom={canAutoTranslate && autoTranslate}
|
||||
autoTranslateLanguage={autoTranslateLanguage}
|
||||
const renderItem = ({ item }: any) => (
|
||||
<Item
|
||||
{...{
|
||||
item,
|
||||
user,
|
||||
navigation,
|
||||
baseUrl,
|
||||
useRealName
|
||||
}}
|
||||
onPress={onDiscussionPress}
|
||||
/>
|
||||
);
|
||||
|
||||
if (!discussions?.length) {
|
||||
return <BackgroundContainer loading={loading} text={I18n.t('No_discussions')} />;
|
||||
}
|
||||
|
@ -187,14 +213,15 @@ const DiscussionsView = ({ navigation, route }) => {
|
|||
<FlatList
|
||||
data={isSearching ? search : discussions}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item.msg}
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
keyExtractor={(item: any) => item.msg}
|
||||
style={{ backgroundColor: themes[theme!].backgroundColor }}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
onEndReachedThreshold={0.5}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={10}
|
||||
initialNumToRender={7}
|
||||
removeClippedSubviews={isIOS}
|
||||
onEndReached={() => total > API_FETCH_COUNT ?? load()}
|
||||
onEndReached={() => (isSearching ? searchTotal > API_FETCH_COUNT ?? load() : total > API_FETCH_COUNT ?? load())}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null}
|
||||
scrollIndicatorInsets={{ right: 1 }}
|
||||
|
@ -203,12 +230,4 @@ const DiscussionsView = ({ navigation, route }) => {
|
|||
);
|
||||
};
|
||||
|
||||
DiscussionsView.propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
item: PropTypes.shape({
|
||||
msg: PropTypes.string
|
||||
})
|
||||
};
|
||||
|
||||
export default DiscussionsView;
|
|
@ -0,0 +1,7 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
contentContainer: {
|
||||
marginBottom: 30
|
||||
}
|
||||
});
|
|
@ -56,7 +56,7 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, toggleFollowThread }) => {
|
||||
const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, toggleFollowThread, thread }) => {
|
||||
const username = (useRealName && item?.u?.name) || item?.u?.username;
|
||||
let time;
|
||||
if (item?.ts) {
|
||||
|
@ -66,19 +66,10 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to
|
|||
return (
|
||||
<Touchable
|
||||
onPress={() => onPress(item)}
|
||||
testID={`thread-messages-view-${item.msg}`}
|
||||
testID={thread ? `thread-messages-view-${item.msg}` : `discussions-view-${item.msg}`}
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
<View style={styles.container}>
|
||||
<Avatar
|
||||
style={styles.avatar}
|
||||
text={item?.u?.username}
|
||||
size={36}
|
||||
borderRadius={4}
|
||||
baseUrl={baseUrl}
|
||||
userId={user?.id}
|
||||
token={user?.token}
|
||||
theme={theme}
|
||||
/>
|
||||
<Avatar style={styles.avatar} text={item?.u?.username} size={36} borderRadius={4} user={user} theme={theme} />
|
||||
<View style={styles.contentContainer}>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>
|
||||
|
@ -98,7 +89,13 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to
|
|||
/>
|
||||
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
|
||||
</View>
|
||||
<ThreadDetails item={item} user={user} toggleFollowThread={toggleFollowThread} style={styles.threadDetails} />
|
||||
<ThreadDetails
|
||||
item={item}
|
||||
user={user}
|
||||
toggleFollowThread={toggleFollowThread}
|
||||
thread={thread}
|
||||
style={styles.threadDetails}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Touchable>
|
||||
|
@ -113,7 +110,8 @@ Item.propTypes = {
|
|||
user: PropTypes.object,
|
||||
badgeColor: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
toggleFollowThread: PropTypes.func
|
||||
toggleFollowThread: PropTypes.func,
|
||||
thread: PropTypes.bool
|
||||
};
|
||||
|
||||
export default withTheme(Item);
|
||||
|
|
|
@ -106,7 +106,14 @@ class ThreadMessagesView extends React.Component {
|
|||
<HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} />
|
||||
</HeaderButton.Container>
|
||||
),
|
||||
headerTitle: () => <SearchHeader onSearchChangeText={this.onSearchChangeText} placeholder='Search' theme={theme} testID='thread-messages-view-search-header' />,
|
||||
headerTitle: () => (
|
||||
<SearchHeader
|
||||
onSearchChangeText={this.onSearchChangeText}
|
||||
placeholder='Search'
|
||||
theme={theme}
|
||||
testID='thread-messages-view-search-header'
|
||||
/>
|
||||
),
|
||||
headerTitleContainerStyle: {
|
||||
left: headerTitlePosition.left,
|
||||
right: headerTitlePosition.right
|
||||
|
@ -417,6 +424,7 @@ class ThreadMessagesView extends React.Component {
|
|||
useRealName,
|
||||
badgeColor
|
||||
}}
|
||||
thread
|
||||
onPress={this.onThreadPress}
|
||||
toggleFollowThread={this.toggleFollowThread}
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue