Migrate to TypeScript, update interfaces and some changes to container components

This commit is contained in:
Gerzon Z 2021-09-17 15:15:26 -04:00
parent 8ccce58ddd
commit ecee12c9d0
8 changed files with 173 additions and 130 deletions

View File

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

View File

@ -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,17 +52,26 @@ 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) {
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) {
} else if (tcount! >= 100) {
tcount = '+99';
}
}
if (dcount! >= 1000) {
dcount = '+999';
} else if (dcount! >= 100) {
dcount = '+99';
}
let replies = item?.replies?.length ?? 0;
if (replies >= 1000) {
@ -75,20 +86,23 @@ 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>
{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>
{thread ? (
<View style={styles.badgeContainer}>
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
<Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}>
@ -99,6 +113,7 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
/>
</Touchable>
</View>
) : null}
</View>
);
};

View File

@ -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 = {

View File

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

View File

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

View File

@ -0,0 +1,7 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
contentContainer: {
marginBottom: 30
}
});

View File

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

View File

@ -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}
/>