diff --git a/app/containers/Avatar/interfaces.ts b/app/containers/Avatar/interfaces.ts index 692c4d0a..2ff48146 100644 --- a/app/containers/Avatar/interfaces.ts +++ b/app/containers/Avatar/interfaces.ts @@ -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; diff --git a/app/containers/ThreadDetails.tsx b/app/containers/ThreadDetails.tsx index 86f670f0..054d7595 100644 --- a/app/containers/ThreadDetails.tsx +++ b/app/containers/ThreadDetails.tsx @@ -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 - + - {tcount} + {thread ? tcount : dcount} - - - - {replies} - - + {thread ? ( + + + + {replies} + + + ) : null} - - {badgeColor ? : null} - toggleFollowThread?.(isFollowing, item.id)}> - - - + {thread ? ( + + {badgeColor ? : null} + toggleFollowThread?.(isFollowing, item.id)}> + + + + ) : null} ); }; diff --git a/app/containers/markdown/index.tsx b/app/containers/markdown/index.tsx index d3ce8453..0aec122f 100644 --- a/app/containers/markdown/index.tsx +++ b/app/containers/markdown/index.tsx @@ -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 = { diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index b9af9e87..0604d6d4 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -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, diff --git a/app/views/DiscussionsView.js b/app/views/DiscussionsView/index.tsx similarity index 57% rename from app/views/DiscussionsView.js rename to app/views/DiscussionsView/index.tsx index e5286379..81881674 100644 --- a/app/views/DiscussionsView.js +++ b/app/views/DiscussionsView/index.tsx @@ -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: () => ( - navigation.pop()} tintColor={themes[theme].headerTintColor} /> + navigation.pop()} tintColor={themes[theme!].headerTintColor} /> ), headerTitleAlign: 'center', headerTitle: I18n.t('Discussions'), headerTitleContainerStyle: { left: null, right: null - } + }, + headerRight: () => ( + + + + ) }; if (isMasterDetail) { options.headerLeft = () => ; } - - options.headerRight = () => ( - - - - ); 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 }) => ( - ( + ); + if (!discussions?.length) { return ; } @@ -187,14 +213,15 @@ const DiscussionsView = ({ navigation, route }) => { 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 ? : 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; diff --git a/app/views/DiscussionsView/styles.ts b/app/views/DiscussionsView/styles.ts new file mode 100644 index 00000000..c6478a8b --- /dev/null +++ b/app/views/DiscussionsView/styles.ts @@ -0,0 +1,7 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + contentContainer: { + marginBottom: 30 + } +}); diff --git a/app/views/ThreadMessagesView/Item.js b/app/views/ThreadMessagesView/Item.js index 1caef2c2..f10abe27 100644 --- a/app/views/ThreadMessagesView/Item.js +++ b/app/views/ThreadMessagesView/Item.js @@ -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 ( onPress(item)} - testID={`thread-messages-view-${item.msg}`} + testID={thread ? `thread-messages-view-${item.msg}` : `discussions-view-${item.msg}`} style={{ backgroundColor: themes[theme].backgroundColor }}> - + @@ -98,7 +89,13 @@ const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, to /> {badgeColor ? : null} - + @@ -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); diff --git a/app/views/ThreadMessagesView/index.js b/app/views/ThreadMessagesView/index.js index 99605899..5951ba8b 100644 --- a/app/views/ThreadMessagesView/index.js +++ b/app/views/ThreadMessagesView/index.js @@ -106,7 +106,14 @@ class ThreadMessagesView extends React.Component { ), - headerTitle: () => , + headerTitle: () => ( + + ), 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} />