import React from 'react'; import { Keyboard, Alert } from 'react-native'; import PropTypes from 'prop-types'; import { Q } from '@nozbe/watermelondb'; import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { connect } from 'react-redux'; import { FlatList } from 'react-native-gesture-handler'; import StatusBar from '../containers/StatusBar'; import RoomHeader from '../containers/RoomHeader'; import { withTheme } from '../theme'; import SearchHeader from './ThreadMessagesView/SearchHeader'; import log, { events, logEvent } from '../utils/log'; import database from '../lib/database'; import { getUserSelector } from '../selectors/login'; import { getHeaderTitlePosition } from '../containers/Header'; import * as HeaderButton from '../containers/HeaderButton'; import BackgroundContainer from '../containers/BackgroundContainer'; import SafeAreaView from '../containers/SafeAreaView'; import ActivityIndicator from '../containers/ActivityIndicator'; import RoomItem, { ROW_HEIGHT } from '../presentation/RoomItem'; import RocketChat from '../lib/rocketchat'; import { withDimensions } from '../dimensions'; import { isIOS } from '../utils/deviceInfo'; import debounce from '../utils/debounce'; import { showErrorAlert } from '../utils/info'; import { goRoom } from '../utils/goRoom'; import I18n from '../i18n'; import { withActionSheet } from '../containers/ActionSheet'; import { deleteRoom as deleteRoomAction } from '../actions/room'; import { CustomIcon } from '../lib/Icons'; import { themes } from '../constants/colors'; const API_FETCH_COUNT = 25; const getItemLayout = (data, index) => ({ length: data.length, offset: ROW_HEIGHT * index, index }); const keyExtractor = item => item._id; class TeamChannelsView extends React.Component { static propTypes = { route: PropTypes.object, navigation: PropTypes.object, isMasterDetail: PropTypes.bool, insets: PropTypes.object, theme: PropTypes.string, useRealName: PropTypes.bool, width: PropTypes.number, StoreLastMessage: PropTypes.bool, addTeamChannelPermission: PropTypes.array, removeTeamChannelPermission: PropTypes.array, showActionSheet: PropTypes.func, deleteRoom: PropTypes.func } constructor(props) { super(props); this.teamId = props.route.params?.teamId; this.state = { loading: true, loadingMore: false, data: [], isSearching: false, searchText: '', search: [], end: false, showCreate: false }; this.loadTeam(); this.setHeader(); } componentDidMount() { this.load(); } loadTeam = async() => { const { addTeamChannelPermission } = this.props; const { loading, data } = this.state; const db = database.active; try { const subCollection = db.get('subscriptions'); this.teamChannels = await subCollection.query( Q.where('team_id', Q.eq(this.teamId)) ); this.team = this.teamChannels?.find(channel => channel.teamMain); this.setHeader(); if (!this.team) { throw new Error(); } const permissions = await RocketChat.hasPermission([addTeamChannelPermission], this.team.rid); if (permissions[0]) { this.setState({ showCreate: true }, () => this.setHeader()); } if (loading && data.length) { this.setState({ loading: false }); } } catch { const { navigation } = this.props; navigation.pop(); showErrorAlert(I18n.t('Team_not_found')); } } load = debounce(async() => { const { loadingMore, data, search, isSearching, searchText, end } = this.state; const length = isSearching ? search.length : data.length; if (loadingMore || end) { return; } this.setState({ loadingMore: true }); try { const result = await RocketChat.getTeamListRoom({ teamId: this.teamId, offset: length, count: API_FETCH_COUNT, type: 'all', filter: searchText }); if (result.success) { const newState = { loading: false, loadingMore: false, end: result.rooms.length < API_FETCH_COUNT }; if (isSearching) { newState.search = [...search, ...result.rooms]; } else { newState.data = [...data, ...result.rooms]; } this.setState(newState); } else { this.setState({ loading: false, loadingMore: false }); } } catch (e) { log(e); this.setState({ loading: false, loadingMore: false }); } }, 300) setHeader = () => { const { isSearching, showCreate, data } = this.state; const { navigation, isMasterDetail, insets } = this.props; const { team } = this; if (!team) { return; } const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 2 }); if (isSearching) { return { headerTitleAlign: 'left', headerLeft: () => ( ), headerTitle: () => , headerTitleContainerStyle: { left: headerTitlePosition.left, right: headerTitlePosition.right }, headerRight: () => null }; } const options = { headerShown: true, headerTitleAlign: 'left', headerTitleContainerStyle: { left: headerTitlePosition.left, right: headerTitlePosition.right }, headerTitle: () => ( ) }; if (isMasterDetail) { options.headerLeft = () => ; } options.headerRight = () => ( { showCreate ? navigation.navigate('AddChannelTeamView', { teamId: this.teamId, teamChannels: data })} /> : null} ); navigation.setOptions(options); } onSearchPress = () => { logEvent(events.TC_SEARCH); this.setState({ isSearching: true }, () => this.setHeader()); } onSearchChangeText = debounce((searchText) => { this.setState({ searchText, search: [], loading: !!searchText, loadingMore: false, end: false }, () => { if (searchText) { this.load(); } }); }, 300) onCancelSearchPress = () => { logEvent(events.TC_CANCEL_SEARCH); const { isSearching } = this.state; if (!isSearching) { return; } Keyboard.dismiss(); this.setState({ searchText: null, isSearching: false, search: [], loadingMore: false, end: false }, () => { this.setHeader(); }); }; goRoomActionsView = (screen) => { logEvent(events.TC_GO_ACTIONS); const { team } = this; const { navigation, isMasterDetail } = this.props; if (isMasterDetail) { navigation.navigate('ModalStackNavigator', { screen: screen ?? 'RoomActionsView', params: { rid: team.rid, t: team.t, room: team, showCloseModal: false } }); } else { navigation.navigate('RoomActionsView', { rid: team.rid, t: team.t, room: team }); } } getRoomTitle = item => RocketChat.getRoomTitle(item) getRoomAvatar = item => RocketChat.getRoomAvatar(item) onPressItem = debounce(async(item) => { logEvent(events.TC_GO_ROOM); const { navigation, isMasterDetail } = this.props; try { let params = {}; if (item.rid) { params = item; } else { const { room } = await RocketChat.getRoomInfo(item._id); params = { rid: item._id, name: RocketChat.getRoomTitle(room), joinCodeRequired: room.joinCodeRequired, t: room.t, teamId: room.teamId }; } if (isMasterDetail) { navigation.pop(); } goRoom({ item: params, isMasterDetail, navigationMethod: navigation.push }); } catch (e) { // do nothing } }, 1000, true); options = (item) => { const { theme } = this.props; const isAutoJoinChecked = item.teamDefault; const autoJoinIcon = isAutoJoinChecked ? 'checkbox-checked' : 'checkbox-unchecked'; const autoJoinIconColor = isAutoJoinChecked ? themes[theme].tintActive : themes[theme].auxiliaryTintColor; return ([ { title: I18n.t('Auto-join'), icon: item.t === 'p' ? 'channel-private' : 'channel-public', onPress: () => this.toggleAutoJoin(item), right: () => }, { title: I18n.t('Remove_from_Team'), icon: 'close', danger: true, onPress: () => this.remove(item) }, { title: I18n.t('Delete'), icon: 'delete', danger: true, onPress: () => this.delete(item) } ]); } toggleAutoJoin = async(item) => { try { const { data } = this.state; const result = await RocketChat.updateTeamRoom({ roomId: item._id, isDefault: !item.teamDefault }); if (result.success) { const newData = data.map((i) => { if (i._id === item._id) { i.teamDefault = !i.teamDefault; } return i; }); this.setState({ data: newData }); } } catch (e) { log(e); } } remove = (item) => { Alert.alert( I18n.t('Confirmation'), I18n.t('Delete_Team_Room_Warning'), [ { text: I18n.t('Cancel'), style: 'cancel' }, { text: I18n.t('Yes_action_it', { action: I18n.t('remove') }), style: 'destructive', onPress: () => this.removeRoom(item) } ], { cancelable: false } ); } removeRoom = async(item) => { try { const { data } = this.state; const result = await RocketChat.removeTeamRoom({ roomId: item._id, teamId: this.team.teamId }); if (result.success) { const newData = data.filter(room => result.room._id !== room._id); this.setState({ data: newData }); } } catch (e) { log(e); } } delete = (item) => { const { deleteRoom } = this.props; Alert.alert( I18n.t('Are_you_sure_question_mark'), I18n.t('Delete_Room_Warning'), [ { text: I18n.t('Cancel'), style: 'cancel' }, { text: I18n.t('Yes_action_it', { action: I18n.t('delete') }), style: 'destructive', onPress: () => deleteRoom(item._id, item.t) } ], { cancelable: false } ); } showChannelActions = async(item) => { logEvent(events.ROOM_SHOW_BOX_ACTIONS); const { showActionSheet, removeTeamChannelPermission } = this.props; const permissions = await RocketChat.hasPermission([removeTeamChannelPermission], this.team.rid); if (!permissions[0]) { return; } showActionSheet({ options: this.options(item) }); } renderItem = ({ item }) => { const { StoreLastMessage, useRealName, theme, width } = this.props; return ( ); }; renderFooter = () => { const { loadingMore } = this.state; const { theme } = this.props; if (loadingMore) { return ; } return null; } renderScroll = () => { const { loading, data, search, isSearching, searchText } = this.state; if (loading) { return ; } if (isSearching && !search.length) { return ; } if (!isSearching && !data.length) { return ; } return ( this.load()} onEndReachedThreshold={0.5} ListFooterComponent={this.renderFooter} /> ); }; render() { console.count(`${ this.constructor.name }.render calls`); return ( {this.renderScroll()} ); } } const mapStateToProps = state => ({ baseUrl: state.server.server, user: getUserSelector(state), useRealName: state.settings.UI_Use_Real_Name, isMasterDetail: state.app.isMasterDetail, StoreLastMessage: state.settings.Store_Last_Message, addTeamChannelPermission: state.permissions['add-team-channel'], removeTeamChannelPermission: state.permissions['remove-team-channel'] }); const mapDispatchToProps = dispatch => ({ deleteRoom: (rid, t) => dispatch(deleteRoomAction(rid, t)) }); export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withSafeAreaInsets(withTheme(withActionSheet(TeamChannelsView)))));