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 { export interface IAvatar {
server: string; server?: string;
style: any; style: any;
text: string; text: string;
avatar: string; avatar?: string;
emoji: string; emoji: string;
size: number; size: number;
borderRadius: number; borderRadius: number;
type: string; type?: string;
children: JSX.Element; children?: JSX.Element;
user: { user: {
id: string; id: string;
token: string; token: string;
}; };
theme: string; theme: string;
onPress(): void; onPress?(): void;
getCustomEmoji(): any; getCustomEmoji(): any;
avatarETag: string; avatarETag: string;
isStatic: boolean; isStatic: boolean;

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React from 'react'; import React from 'react';
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';
@ -41,8 +42,9 @@ const styles = StyleSheet.create({
interface IThreadDetails { interface IThreadDetails {
item: { item: {
tcount: number | string; tcount?: number | string;
replies: any; dcount?: number | string;
replies?: any;
id: string; id: string;
}; };
user: { user: {
@ -50,16 +52,25 @@ interface IThreadDetails {
}; };
badgeColor: string; badgeColor: string;
toggleFollowThread: Function; toggleFollowThread: Function;
thread: boolean;
style: object; style: object;
theme: string; theme: string;
} }
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, theme }: IThreadDetails) => { const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, thread, style, theme }: IThreadDetails) => {
let { tcount } = item; let { tcount, dcount } = item;
if (tcount >= 1000) { if (thread) {
tcount = '+999'; if (tcount! >= 1000) {
} else if (tcount >= 100) { tcount = '+999';
tcount = '+99'; } else if (tcount! >= 100) {
tcount = '+99';
}
}
if (dcount! >= 1000) {
dcount = '+999';
} else if (dcount! >= 100) {
dcount = '+99';
} }
let replies = item?.replies?.length ?? 0; 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.container, style]}>
<View style={styles.detailsContainer}> <View style={styles.detailsContainer}>
<View style={styles.detailContainer}> <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}> <Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
{tcount} {thread ? tcount : dcount}
</Text> </Text>
</View> </View>
<View style={styles.detailContainer}> {thread ? (
<CustomIcon name='user' size={24} color={themes[theme].auxiliaryText} /> <View style={styles.detailContainer}>
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> <CustomIcon name='user' size={24} color={themes[theme].auxiliaryText} />
{replies} <Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
</Text> {replies}
</View> </Text>
</View>
) : null}
</View> </View>
<View style={styles.badgeContainer}> {thread ? (
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null} <View style={styles.badgeContainer}>
<Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}> {badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
<CustomIcon <Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}>
size={24} <CustomIcon
name={isFollowing ? 'notification' : 'notification-disabled'} size={24}
color={themes[theme].auxiliaryTintColor} name={isFollowing ? 'notification' : 'notification-disabled'}
/> color={themes[theme].auxiliaryTintColor}
</Touchable> />
</View> </Touchable>
</View>
) : null}
</View> </View>
); );
}; };

View File

@ -23,25 +23,25 @@ import { isValidURL } from '../../utils/url';
interface IMarkdownProps { interface IMarkdownProps {
msg: string; msg: string;
getCustomEmoji: Function; getCustomEmoji?: Function;
baseUrl: string; baseUrl: string;
username: string; username: string;
tmid: string; tmid?: string;
isEdited: boolean; isEdited?: boolean;
numberOfLines: number; numberOfLines?: number;
customEmojis: boolean; customEmojis?: boolean;
useRealName: boolean; useRealName?: boolean;
channels: { channels?: {
name: string; name: string;
_id: number; _id: number;
}[]; }[];
mentions: object[]; mentions?: object[];
navToRoomInfo: Function; navToRoomInfo?: Function;
preview: boolean; preview?: boolean;
theme: string; theme?: string;
testID: string; testID?: string;
style: any; style?: any;
onLinkPress: Function; onLinkPress?: Function;
} }
type TLiteral = { type TLiteral = {

View File

@ -807,21 +807,17 @@ const RocketChat = {
encrypted encrypted
}); });
}, },
getDiscussions({ getDiscussions({ roomId, offset, count, text }) {
roomId, offset, count, text
}) {
const params = { const params = {
roomId, roomId,
offset, offset,
count, count,
text ...(text && { text })
}; };
// RC 2.4.0 // RC 2.4.0
return this.sdk.get('chat.getDiscussions', params); return this.sdk.get('chat.getDiscussions', params);
}, },
createTeam({ createTeam({ name, users, type, readOnly, broadcast, encrypted }) {
name, users, type, readOnly, broadcast, encrypted
}) {
const params = { const params = {
name, name,
users, users,

View File

@ -1,48 +1,76 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { HeaderBackButton } from '@react-navigation/stack'; import { HeaderBackButton } from '@react-navigation/stack';
import ActivityIndicator from '../containers/ActivityIndicator'; import ActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../i18n'; import I18n from '../../i18n';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import log from '../utils/log'; import log from '../../utils/log';
import debounce from '../utils/debounce'; import debounce from '../../utils/debounce';
import { themes } from '../constants/colors'; import { themes } from '../../constants/colors';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import * as HeaderButton from '../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../containers/List'; import * as List from '../../containers/List';
import BackgroundContainer from '../containers/BackgroundContainer'; import BackgroundContainer from '../../containers/BackgroundContainer';
import { isIOS } from '../utils/deviceInfo'; import { isIOS } from '../../utils/deviceInfo';
import { getHeaderTitlePosition } from '../containers/Header'; import { getHeaderTitlePosition } from '../../containers/Header';
import { useTheme } from '../../theme';
import { useTheme } from '../theme'; import RocketChat from '../../lib/rocketchat';
import Message from '../containers/message'; import SearchHeader from '../../containers/SearchHeader';
import RocketChat from '../lib/rocketchat'; import Item from '../ThreadMessagesView/Item';
import SearchHeader from '../containers/SearchHeader'; import styles from './styles';
const API_FETCH_COUNT = 50; const API_FETCH_COUNT = 50;
const DiscussionsView = ({ navigation, route }) => { interface IDiscussionsViewProps {
const rid = route.params?.rid; navigation: any;
const canAutoTranslate = route.params?.canAutoTranslate; route: {
const autoTranslate = route.params?.autoTranslate; params?: {
const autoTranslateLanguage = route.params?.autoTranslateLanguage; rid: string;
const navToRoomInfo = route.params?.navToRoomInfo; canAutoTranslate: boolean;
autoTranslate: boolean;
autoTranslateLanguage: string;
navToRoomInfo: Function;
};
};
item: {
msg: string;
};
}
const user = useSelector(state => state.login?.user); interface IState {
const baseUrl = useSelector(state => state.server.server); login?: {
const useRealName = useSelector(state => state.settings.UI_Use_Real_Name); user: object;
const Message_TimeFormat = useSelector(state => state.settings.Message_TimeFormat); };
const isMasterDetail = useSelector(state => state.app.isMasterDetail); 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 [loading, setLoading] = useState(false);
const [discussions, setDiscussions] = useState([]); const [discussions, setDiscussions] = useState([]);
const [search, setSearch] = useState([]); const [search, setSearch] = useState([]);
const [isSearching, setIsSearching] = useState(false); const [isSearching, setIsSearching] = useState(false);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [searchTotal, setSearchTotal] = useState(0);
const { theme } = useTheme(); const { theme } = useTheme();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
@ -64,7 +92,7 @@ const DiscussionsView = ({ navigation, route }) => {
if (result.success) { if (result.success) {
if (isSearching) { if (isSearching) {
setSearch(result.messages); setSearch(result.messages);
setTotal(result.total); setSearchTotal(result.total);
} else { } else {
setDiscussions(result.messages); setDiscussions(result.messages);
setTotal(result.total); setTotal(result.total);
@ -77,7 +105,7 @@ const DiscussionsView = ({ navigation, route }) => {
} }
}; };
const onSearchChangeText = debounce(async text => { const onSearchChangeText = debounce(async (text: string) => {
setIsSearching(true); setIsSearching(true);
await load(text); await load(text);
}, 300); }, 300);
@ -119,25 +147,24 @@ const DiscussionsView = ({ navigation, route }) => {
const options = { const options = {
headerLeft: () => ( headerLeft: () => (
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} /> <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme!].headerTintColor} />
), ),
headerTitleAlign: 'center', headerTitleAlign: 'center',
headerTitle: I18n.t('Discussions'), headerTitle: I18n.t('Discussions'),
headerTitleContainerStyle: { headerTitleContainerStyle: {
left: null, left: null,
right: null right: null
} },
headerRight: () => (
<HeaderButton.Container>
<HeaderButton.Item iconName='search' onPress={onSearchPress} />
</HeaderButton.Container>
)
}; };
if (isMasterDetail) { if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />; options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
} }
options.headerRight = () => (
<HeaderButton.Container>
<HeaderButton.Item iconName='search' onPress={onSearchPress} />
</HeaderButton.Container>
);
return options; return options;
}; };
@ -151,7 +178,7 @@ const DiscussionsView = ({ navigation, route }) => {
}, [navigation, isSearching]); }, [navigation, isSearching]);
const onDiscussionPress = debounce( const onDiscussionPress = debounce(
item => { (item: any) => {
navigation.push('RoomView', { navigation.push('RoomView', {
rid: item.drid, rid: item.drid,
prid: item.rid, prid: item.rid,
@ -163,20 +190,19 @@ const DiscussionsView = ({ navigation, route }) => {
true true
); );
const renderItem = ({ item }) => ( const renderItem = ({ item }: any) => (
<Message <Item
item={item} {...{
user={user} item,
rid={rid} user,
navToRoomInfo={navToRoomInfo} navigation,
onDiscussionPress={onDiscussionPress} baseUrl,
baseUrl={baseUrl} useRealName
timeFormat={Message_TimeFormat} }}
useRealName={useRealName} onPress={onDiscussionPress}
autoTranslateRoom={canAutoTranslate && autoTranslate}
autoTranslateLanguage={autoTranslateLanguage}
/> />
); );
if (!discussions?.length) { if (!discussions?.length) {
return <BackgroundContainer loading={loading} text={I18n.t('No_discussions')} />; return <BackgroundContainer loading={loading} text={I18n.t('No_discussions')} />;
} }
@ -187,14 +213,15 @@ const DiscussionsView = ({ navigation, route }) => {
<FlatList <FlatList
data={isSearching ? search : discussions} data={isSearching ? search : discussions}
renderItem={renderItem} renderItem={renderItem}
keyExtractor={item => item.msg} keyExtractor={(item: any) => item.msg}
style={{ backgroundColor: themes[theme].backgroundColor }} style={{ backgroundColor: themes[theme!].backgroundColor }}
contentContainerStyle={styles.contentContainer}
onEndReachedThreshold={0.5} onEndReachedThreshold={0.5}
maxToRenderPerBatch={5} maxToRenderPerBatch={5}
windowSize={10} windowSize={10}
initialNumToRender={7} initialNumToRender={7}
removeClippedSubviews={isIOS} removeClippedSubviews={isIOS}
onEndReached={() => total > API_FETCH_COUNT ?? load()} onEndReached={() => (isSearching ? searchTotal > API_FETCH_COUNT ?? load() : total > API_FETCH_COUNT ?? load())}
ItemSeparatorComponent={List.Separator} ItemSeparatorComponent={List.Separator}
ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null} ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null}
scrollIndicatorInsets={{ right: 1 }} 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; 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; const username = (useRealName && item?.u?.name) || item?.u?.username;
let time; let time;
if (item?.ts) { if (item?.ts) {
@ -66,19 +66,10 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to
return ( return (
<Touchable <Touchable
onPress={() => onPress(item)} 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 }}> 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} user={user} 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}>
@ -98,7 +89,13 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to
/> />
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null} {badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
</View> </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>
</View> </View>
</Touchable> </Touchable>
@ -113,7 +110,8 @@ Item.propTypes = {
user: PropTypes.object, user: PropTypes.object,
badgeColor: PropTypes.string, badgeColor: PropTypes.string,
onPress: PropTypes.func, onPress: PropTypes.func,
toggleFollowThread: PropTypes.func toggleFollowThread: PropTypes.func,
thread: PropTypes.bool
}; };
export default withTheme(Item); export default withTheme(Item);

View File

@ -106,7 +106,14 @@ class ThreadMessagesView extends React.Component {
<HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} /> <HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} />
</HeaderButton.Container> </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: { headerTitleContainerStyle: {
left: headerTitlePosition.left, left: headerTitlePosition.left,
right: headerTitlePosition.right right: headerTitlePosition.right
@ -417,6 +424,7 @@ class ThreadMessagesView extends React.Component {
useRealName, useRealName,
badgeColor badgeColor
}} }}
thread
onPress={this.onThreadPress} onPress={this.onThreadPress}
toggleFollowThread={this.toggleFollowThread} toggleFollowThread={this.toggleFollowThread}
/> />