diff --git a/app/commands.ts b/app/commands.ts index b1aee847c..0624ac3db 100644 --- a/app/commands.ts +++ b/app/commands.ts @@ -143,8 +143,8 @@ export const deleteKeyCommands = (): void => KeyCommands.deleteKeyCommands(keyCo export const KEY_COMMAND = 'KEY_COMMAND'; -interface IKeyCommandEvent extends NativeSyntheticEvent { - input: string; +export interface IKeyCommandEvent extends NativeSyntheticEvent { + input: number & string; modifierFlags: string | number; } diff --git a/app/containers/List/ListItem.tsx b/app/containers/List/ListItem.tsx index 87abfd2dd..79b3dcbf7 100644 --- a/app/containers/List/ListItem.tsx +++ b/app/containers/List/ListItem.tsx @@ -135,7 +135,7 @@ const Button = React.memo(({ onPress, backgroundColor, underlay )); -interface IListItem extends IListItemContent, IListButtonPress { +interface IListItem extends IListItemContent, IListItemButton { backgroundColor?: string; } diff --git a/app/definitions/IRoom.ts b/app/definitions/IRoom.ts index 1f943cdb3..ed215e5f9 100644 --- a/app/definitions/IRoom.ts +++ b/app/definitions/IRoom.ts @@ -42,6 +42,9 @@ export interface IRoom { latest?: string; default?: true; featured?: true; + muted?: string[]; + teamId?: string; + ignored?: string; } export enum OmnichannelSourceType { diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts index cf10b68e9..f794b3c53 100644 --- a/app/definitions/ISubscription.ts +++ b/app/definitions/ISubscription.ts @@ -50,7 +50,7 @@ export interface ISubscription { lr: string; userMentions: number; groupMentions: number; - tunread?: string[]; + tunread: string[]; tunreadUser?: string[]; tunreadGroup?: string[]; roomUpdatedAt: Date | number; @@ -90,6 +90,7 @@ export interface ISubscription { avatarETag?: string; teamId?: string; teamMain?: boolean; + separator?: boolean; // https://nozbe.github.io/WatermelonDB/Relation.html#relation-api messages: Relation; threads: Relation; diff --git a/app/definitions/IUser.ts b/app/definitions/IUser.ts index 461b52fb4..474a2105e 100644 --- a/app/definitions/IUser.ts +++ b/app/definitions/IUser.ts @@ -130,6 +130,7 @@ export interface IUser extends IRocketChatRecord, Omit | null = null; export function getUserPresence(uid: string) { if (!usersTimer) { usersTimer = setTimeout(() => { diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index 0ccf22034..c7353d4a0 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -1,6 +1,7 @@ import sdk from './sdk'; import { TEAM_TYPE } from '../../../definitions/ITeam'; import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType'; +import { SubscriptionType } from '../../../definitions'; export const createChannel = ({ name, @@ -500,7 +501,7 @@ export const toggleRoomOwner = ({ isOwner }: { roomId: string; - t: RoomTypes; + t: SubscriptionType; userId: string; isOwner: boolean; }): any => { @@ -523,7 +524,7 @@ export const toggleRoomLeader = ({ isLeader }: { roomId: string; - t: RoomTypes; + t: SubscriptionType; userId: string; isLeader: boolean; }): any => { @@ -546,7 +547,7 @@ export const toggleRoomModerator = ({ isModerator }: { roomId: string; - t: RoomTypes; + t: SubscriptionType; userId: string; isModerator: boolean; }): any => { @@ -562,7 +563,7 @@ export const toggleRoomModerator = ({ return sdk.post(`${roomTypeToApiType(t)}.removeModerator`, { roomId, userId }); }; -export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: RoomTypes; userId: string }): any => +export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: SubscriptionType; userId: string }): any => // RC 0.48.0 // TODO: missing definitions from server // @ts-ignore @@ -619,7 +620,7 @@ export const getSingleMessage = (msgId: string) => // RC 0.47.0 sdk.get('chat.getMessage', { msgId }); -export const getRoomRoles = (roomId: string, type: RoomTypes): any => +export const getRoomRoles = (roomId: string, type: SubscriptionType): any => // RC 0.65.0 // TODO: missing definitions from server // @ts-ignore diff --git a/app/stacks/MasterDetailStack/types.ts b/app/stacks/MasterDetailStack/types.ts index 50240de7d..533b7f381 100644 --- a/app/stacks/MasterDetailStack/types.ts +++ b/app/stacks/MasterDetailStack/types.ts @@ -4,6 +4,7 @@ import { NavigatorScreenParams } from '@react-navigation/core'; import { IAttachment } from '../../definitions/IAttachment'; import { IMessage } from '../../definitions/IMessage'; import { ISubscription, SubscriptionType } from '../../definitions/ISubscription'; +import { TRoomModel } from '../../definitions'; export type MasterDetailChatsStackParamList = { RoomView: { @@ -44,9 +45,9 @@ export type ModalStackParamList = { title: string; infoText: string; nextAction: Function; - showAlert: boolean; - isSearch: boolean; - onSearch: Function; + showAlert: () => void | boolean; + isSearch?: boolean; + onSearch?: Function; isRadio?: boolean; }; RoomInfoEditView: { @@ -54,7 +55,7 @@ export type ModalStackParamList = { }; RoomMembersView: { rid: string; - room: ISubscription; + room: TRoomModel; }; DiscussionsView: { rid: string; diff --git a/app/stacks/types.ts b/app/stacks/types.ts index 7e5e571b5..e843ca9a2 100644 --- a/app/stacks/types.ts +++ b/app/stacks/types.ts @@ -9,10 +9,17 @@ import { IAttachment } from '../definitions/IAttachment'; import { IMessage } from '../definitions/IMessage'; import { ISubscription, SubscriptionType, TSubscriptionModel } from '../definitions/ISubscription'; import { ICannedResponse } from '../definitions/ICannedResponse'; +import { ModalStackParamList } from './MasterDetailStack/types'; export type ChatsStackParamList = { + ModalStackNavigator: NavigatorScreenParams; + E2ESaveYourPasswordStackNavigator: NavigatorScreenParams; + E2EEnterYourPasswordStackNavigator: NavigatorScreenParams; + SettingsView: any; + NewMessageStackNavigator: any; + NewMessageStack: undefined; RoomsListView: undefined; - RoomView: { + RoomView?: { rid: string; t: SubscriptionType; tmid?: string; diff --git a/app/utils/debounce.ts b/app/utils/debounce.ts index e0c28b239..dd41e6ed0 100644 --- a/app/utils/debounce.ts +++ b/app/utils/debounce.ts @@ -1,5 +1,5 @@ export default function debounce(func: Function, wait?: number, immediate?: boolean) { - let timeout: number | null; + let timeout: ReturnType | null; function _debounce(...args: any[]) { // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-this-alias diff --git a/app/utils/events.ts b/app/utils/events.ts index fc0b975ad..d32588d5f 100644 --- a/app/utils/events.ts +++ b/app/utils/events.ts @@ -3,6 +3,7 @@ import log from './log'; type TEventEmitterEmmitArgs = | { rid: string } + | { server: string } | { message: string } | { method: string } | { invalid: boolean } diff --git a/app/utils/goRoom.ts b/app/utils/goRoom.ts index 0a49bb215..5a25ee564 100644 --- a/app/utils/goRoom.ts +++ b/app/utils/goRoom.ts @@ -1,14 +1,24 @@ import { ChatsStackParamList } from '../stacks/types'; import Navigation from '../lib/Navigation'; import RocketChat from '../lib/rocketchat'; -import { ISubscription, SubscriptionType } from '../definitions/ISubscription'; +import { IVisitor, SubscriptionType } from '../definitions/ISubscription'; + +export interface IGoRoomItem { + search?: boolean; // comes from spotlight + username?: string; + t?: SubscriptionType; + rid?: string; + name?: string; + prid?: string; + visitor?: IVisitor; +} const navigate = ({ item, isMasterDetail, ...props }: { - item: IItem; + item: IGoRoomItem; isMasterDetail: boolean; navigationMethod?: () => ChatsStackParamList; }) => { @@ -30,17 +40,12 @@ const navigate = ({ }); }; -interface IItem extends Partial { - search?: boolean; // comes from spotlight - username?: string; -} - export const goRoom = async ({ item, isMasterDetail = false, ...props }: { - item: IItem; + item: IGoRoomItem; isMasterDetail: boolean; navigationMethod?: any; jumpToMessageId?: string; @@ -50,9 +55,8 @@ export const goRoom = async ({ // if user is using the search we need first to join/create room try { const { username } = item; - // @ts-ignore - const result = await RocketChat.createDirectMessage(username); - if (result.success) { + const result = await RocketChat.createDirectMessage(username as string); + if (result.success && result?.room?._id) { return navigate({ item: { rid: result.room._id, diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.tsx similarity index 79% rename from app/views/RoomMembersView/index.js rename to app/views/RoomMembersView/index.tsx index 3f936fba6..bed9cccbb 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.tsx @@ -1,75 +1,82 @@ +import { Q } from '@nozbe/watermelondb'; import React from 'react'; -import PropTypes from 'prop-types'; import { FlatList } from 'react-native'; import { connect } from 'react-redux'; -import { Q } from '@nozbe/watermelondb'; +import { Observable, Subscription } from 'rxjs'; -import * as List from '../../containers/List'; -import UserItem from '../../presentation/UserItem'; -import scrollPersistTaps from '../../utils/scrollPersistTaps'; -import RocketChat from '../../lib/rocketchat'; -import database from '../../lib/database'; -import { LISTENER } from '../../containers/Toast'; -import EventEmitter from '../../utils/events'; -import log from '../../utils/log'; -import I18n from '../../i18n'; -import SearchBox from '../../containers/SearchBox'; -import protectedFunction from '../../lib/methods/helpers/protectedFunction'; -import * as HeaderButton from '../../containers/HeaderButton'; -import StatusBar from '../../containers/StatusBar'; -import ActivityIndicator from '../../containers/ActivityIndicator'; -import { withTheme } from '../../theme'; import { themes } from '../../constants/colors'; -import { getUserSelector } from '../../selectors/login'; import { withActionSheet } from '../../containers/ActionSheet'; -import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; +import ActivityIndicator from '../../containers/ActivityIndicator'; +import * as HeaderButton from '../../containers/HeaderButton'; +import * as List from '../../containers/List'; import SafeAreaView from '../../containers/SafeAreaView'; -import { goRoom } from '../../utils/goRoom'; +import SearchBox from '../../containers/SearchBox'; +import StatusBar from '../../containers/StatusBar'; +import { LISTENER } from '../../containers/Toast'; +import { IApplicationState, IBaseScreen, IUser, SubscriptionType, TRoomModel, TUserModel } from '../../definitions'; +import I18n from '../../i18n'; +import database from '../../lib/database'; import { CustomIcon } from '../../lib/Icons'; +import protectedFunction from '../../lib/methods/helpers/protectedFunction'; +import RocketChat from '../../lib/rocketchat'; +import UserItem from '../../presentation/UserItem'; +import { getUserSelector } from '../../selectors/login'; +import { ModalStackParamList } from '../../stacks/MasterDetailStack/types'; +import { withTheme } from '../../theme'; +import EventEmitter from '../../utils/events'; +import { goRoom, IGoRoomItem } from '../../utils/goRoom'; +import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; +import log from '../../utils/log'; +import scrollPersistTaps from '../../utils/scrollPersistTaps'; import styles from './styles'; const PAGE_SIZE = 25; -const PERMISSION_MUTE_USER = 'mute-user'; -const PERMISSION_SET_LEADER = 'set-leader'; -const PERMISSION_SET_OWNER = 'set-owner'; -const PERMISSION_SET_MODERATOR = 'set-moderator'; -const PERMISSION_REMOVE_USER = 'remove-user'; -const PERMISSION_EDIT_TEAM_MEMBER = 'edit-team-member'; -const PERMISION_VIEW_ALL_TEAMS = 'view-all-teams'; -const PERMISSION_VIEW_ALL_TEAM_CHANNELS = 'view-all-team-channels'; - -class RoomMembersView extends React.Component { - static propTypes = { - navigation: PropTypes.object, - route: PropTypes.object, - rid: PropTypes.string, - members: PropTypes.array, - baseUrl: PropTypes.string, - room: PropTypes.object, - user: PropTypes.shape({ - id: PropTypes.string, - token: PropTypes.string, - roles: PropTypes.array - }), - showActionSheet: PropTypes.func, - theme: PropTypes.string, - isMasterDetail: PropTypes.bool, - useRealName: PropTypes.bool, - muteUserPermission: PropTypes.array, - setLeaderPermission: PropTypes.array, - setOwnerPermission: PropTypes.array, - setModeratorPermission: PropTypes.array, - removeUserPermission: PropTypes.array, - editTeamMemberPermission: PropTypes.array, - viewAllTeamChannelsPermission: PropTypes.array, - viewAllTeamsPermission: PropTypes.array +interface IRoomMembersViewProps extends IBaseScreen { + rid: string; + members: string[]; + baseUrl: string; + room: TRoomModel; + user: { + id: string; + token: string; + roles: string[]; }; + showActionSheet: (params: any) => {}; // TODO: this work? + theme: string; + isMasterDetail: boolean; + useRealName: boolean; + muteUserPermission: string[]; + setLeaderPermission: string[]; + setOwnerPermission: string[]; + setModeratorPermission: string[]; + removeUserPermission: string[]; + editTeamMemberPermission: string[]; + viewAllTeamChannelsPermission: string[]; + viewAllTeamsPermission: string[]; +} - constructor(props) { +interface IRoomMembersViewState { + isLoading: boolean; + allUsers: boolean; + filtering: boolean; + rid: string; + members: TUserModel[]; + membersFiltered: TUserModel[]; + room: TRoomModel; + end: boolean; +} + +class RoomMembersView extends React.Component { + private mounted: boolean; + private permissions: any; // TODO: fix when get props from api + private roomObservable!: Observable; + private subscription!: Subscription; + private roomRoles: any; + + constructor(props: IRoomMembersViewProps) { super(props); this.mounted = false; - this.MUTE_INDEX = 0; this.permissions = {}; const rid = props.route.params?.rid; const room = props.route.params?.room; @@ -80,7 +87,7 @@ class RoomMembersView extends React.Component { rid, members: [], membersFiltered: [], - room: room || {}, + room: room || ({} as TRoomModel), end: false }; if (room && room.observe) { @@ -89,7 +96,7 @@ class RoomMembersView extends React.Component { if (this.mounted) { this.setState({ room: changes }); } else { - this.state.room = changes; + this.setState({ room: changes }); } }); } @@ -101,6 +108,7 @@ class RoomMembersView extends React.Component { this.mounted = true; this.fetchMembers(); + // @ts-ignore - TODO: room param is wrong if (RocketChat.isGroupChat(room)) { return; } @@ -129,16 +137,16 @@ class RoomMembersView extends React.Component { ); this.permissions = { - [PERMISSION_MUTE_USER]: result[0], - [PERMISSION_SET_LEADER]: result[1], - [PERMISSION_SET_OWNER]: result[2], - [PERMISSION_SET_MODERATOR]: result[3], - [PERMISSION_REMOVE_USER]: result[4], + 'mute-user': result[0], + 'set-leader': result[1], + 'set-owner': result[2], + 'set-moderator': result[3], + 'remove-user': result[4], ...(room.teamMain ? { - [PERMISSION_EDIT_TEAM_MEMBER]: result[5], - [PERMISSION_VIEW_ALL_TEAM_CHANNELS]: result[6], - [PERMISION_VIEW_ALL_TEAMS]: result[7] + 'edit-team-member': result[5], + 'view-all-team-channels': result[6], + 'view-all-teams': result[7] } : {}) }; @@ -169,20 +177,20 @@ class RoomMembersView extends React.Component { }); }; - onSearchChangeText = protectedFunction(text => { + onSearchChangeText = protectedFunction((text: string) => { const { members } = this.state; - let membersFiltered = []; + let membersFiltered: TUserModel[] = []; text = text.trim(); if (members && members.length > 0 && text) { membersFiltered = members.filter( - m => m.username.toLowerCase().match(text.toLowerCase()) || m.name.toLowerCase().match(text.toLowerCase()) + m => m.username.toLowerCase().match(text.toLowerCase()) || m.name?.toLowerCase().match(text.toLowerCase()) ); } this.setState({ filtering: !!text, membersFiltered }); }); - navToDirectMessage = async item => { + navToDirectMessage = async (item: IUser) => { try { const db = database.active; const subsCollection = db.get('subscriptions'); @@ -193,7 +201,7 @@ class RoomMembersView extends React.Component { } else { const result = await RocketChat.createDirectMessage(item.username); if (result.success) { - this.goRoom({ rid: result.room?._id, name: item.username, t: 'd' }); + this.goRoom({ rid: result.room?._id as string, name: item.username, t: SubscriptionType.DIRECT }); } } } catch (e) { @@ -201,15 +209,15 @@ class RoomMembersView extends React.Component { } }; - handleRemoveFromTeam = async selectedUser => { + handleRemoveFromTeam = async (selectedUser: TUserModel) => { try { const { navigation } = this.props; const { room } = this.state; - const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: selectedUser._id }); + const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId as string, userId: selectedUser._id }); if (result.rooms?.length) { - const teamChannels = result.rooms.map(r => ({ + const teamChannels = result.rooms.map((r: any) => ({ rid: r._id, name: r.name, teamId: r.teamId, @@ -219,7 +227,7 @@ class RoomMembersView extends React.Component { title: 'Remove_Member', infoText: 'Remove_User_Team_Channels', data: teamChannels, - nextAction: selected => this.removeFromTeam(selectedUser, selected), + nextAction: (selected: any) => this.removeFromTeam(selectedUser, selected), showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_remove')) }); } else { @@ -238,7 +246,7 @@ class RoomMembersView extends React.Component { } }; - removeFromTeam = async (selectedUser, selected) => { + removeFromTeam = async (selectedUser: IUser, selected?: any) => { try { const { members, membersFiltered, room } = this.state; const { navigation } = this.props; @@ -258,9 +266,10 @@ class RoomMembersView extends React.Component { members: newMembers, membersFiltered: newMembersFiltered }); + // @ts-ignore - This is just to force a reload navigation.navigate('RoomMembersView'); } - } catch (e) { + } catch (e: any) { log(e); showErrorAlert( e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }), @@ -269,11 +278,11 @@ class RoomMembersView extends React.Component { } }; - onPressUser = selectedUser => { + onPressUser = (selectedUser: TUserModel) => { const { room } = this.state; const { showActionSheet, user, theme } = this.props; - const options = [ + const options: {}[] = [ { icon: 'message', title: I18n.t('Direct_message'), @@ -315,7 +324,7 @@ class RoomMembersView extends React.Component { // Owner if (this.permissions['set-owner']) { - const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); + const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id); const isOwner = userRoleResult?.roles.includes('owner'); options.push({ icon: 'shield-check', @@ -335,7 +344,7 @@ class RoomMembersView extends React.Component { // Leader if (this.permissions['set-leader']) { - const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); + const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id); const isLeader = userRoleResult?.roles.includes('leader'); options.push({ icon: 'shield-alt', @@ -355,7 +364,7 @@ class RoomMembersView extends React.Component { // Moderator if (this.permissions['set-moderator']) { - const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id); + const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id); const isModerator = userRoleResult?.roles.includes('moderator'); options.push({ icon: 'shield', @@ -460,9 +469,10 @@ class RoomMembersView extends React.Component { } }; - goRoom = item => { + goRoom = (item: IGoRoomItem) => { const { navigation, isMasterDetail } = this.props; if (isMasterDetail) { + // @ts-ignore navigation.navigate('DrawerNavigator'); } else { navigation.popToTop(); @@ -470,12 +480,12 @@ class RoomMembersView extends React.Component { goRoom({ item, isMasterDetail }); }; - getUserDisplayName = user => { + getUserDisplayName = (user: TUserModel) => { const { useRealName } = this.props; return (useRealName ? user.name : user.username) || user.username; }; - handleMute = async user => { + handleMute = async (user: TUserModel) => { const { rid } = this.state; try { await RocketChat.toggleMuteUserInRoom(rid, user?.username, !user?.muted); @@ -487,7 +497,7 @@ class RoomMembersView extends React.Component { } }; - handleOwner = async (selectedUser, isOwner) => { + handleOwner = async (selectedUser: TUserModel, isOwner: boolean) => { try { const { room } = this.state; await RocketChat.toggleRoomOwner({ @@ -511,7 +521,7 @@ class RoomMembersView extends React.Component { this.fetchRoomMembersRoles(); }; - handleLeader = async (selectedUser, isLeader) => { + handleLeader = async (selectedUser: TUserModel, isLeader: boolean) => { try { const { room } = this.state; await RocketChat.toggleRoomLeader({ @@ -535,7 +545,7 @@ class RoomMembersView extends React.Component { this.fetchRoomMembersRoles(); }; - handleModerator = async (selectedUser, isModerator) => { + handleModerator = async (selectedUser: TUserModel, isModerator: boolean) => { try { const { room } = this.state; await RocketChat.toggleRoomModerator({ @@ -559,7 +569,7 @@ class RoomMembersView extends React.Component { this.fetchRoomMembersRoles(); }; - handleIgnore = async (selectedUser, ignore) => { + handleIgnore = async (selectedUser: TUserModel, ignore: boolean) => { try { const { room } = this.state; await RocketChat.ignoreUser({ @@ -574,7 +584,7 @@ class RoomMembersView extends React.Component { } }; - handleRemoveUserFromRoom = async selectedUser => { + handleRemoveUserFromRoom = async (selectedUser: TUserModel) => { try { const { room, members, membersFiltered } = this.state; const userId = selectedUser._id; @@ -592,17 +602,15 @@ class RoomMembersView extends React.Component { renderSearchBar = () => this.onSearchChangeText(text)} testID='room-members-view-search' />; - renderItem = ({ item }) => { - const { baseUrl, user, theme } = this.props; + renderItem = ({ item }: { item: TUserModel }) => { + const { theme } = this.props; return ( this.onPressUser(item)} - baseUrl={baseUrl} testID={`room-members-view-item-${item.username}`} - user={user} theme={theme} /> ); @@ -638,19 +646,19 @@ class RoomMembersView extends React.Component { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: IApplicationState) => ({ baseUrl: state.server.server, user: getUserSelector(state), isMasterDetail: state.app.isMasterDetail, useRealName: state.settings.UI_Use_Real_Name, - muteUserPermission: state.permissions[PERMISSION_MUTE_USER], - setLeaderPermission: state.permissions[PERMISSION_SET_LEADER], - setOwnerPermission: state.permissions[PERMISSION_SET_OWNER], - setModeratorPermission: state.permissions[PERMISSION_SET_MODERATOR], - removeUserPermission: state.permissions[PERMISSION_REMOVE_USER], - editTeamMemberPermission: state.permissions[PERMISSION_EDIT_TEAM_MEMBER], - viewAllTeamChannelsPermission: state.permissions[PERMISSION_VIEW_ALL_TEAM_CHANNELS], - viewAllTeamsPermission: state.permissions[PERMISION_VIEW_ALL_TEAMS] + muteUserPermission: state.permissions['mute-user'], + setLeaderPermission: state.permissions['set-leader'], + setOwnerPermission: state.permissions['set-owner'], + setModeratorPermission: state.permissions['set-moderator'], + removeUserPermission: state.permissions['remove-user'], + editTeamMemberPermission: state.permissions['edit-team-member'], + viewAllTeamChannelsPermission: state.permissions['view-all-team-channels'], + viewAllTeamsPermission: state.permissions['view-all-teams'] }); export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView))); diff --git a/app/views/RoomMembersView/styles.js b/app/views/RoomMembersView/styles.ts similarity index 100% rename from app/views/RoomMembersView/styles.js rename to app/views/RoomMembersView/styles.ts diff --git a/app/views/RoomsListView/Header/Header.js b/app/views/RoomsListView/Header/Header.tsx similarity index 77% rename from app/views/RoomsListView/Header/Header.js rename to app/views/RoomsListView/Header/Header.tsx index 65aa36792..dc90a50e1 100644 --- a/app/views/RoomsListView/Header/Header.js +++ b/app/views/RoomsListView/Header/Header.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, TextInputProps, TouchableOpacity, TouchableOpacityProps, View } from 'react-native'; import TextInput from '../../../presentation/TextInput'; import I18n from '../../../i18n'; @@ -31,19 +30,32 @@ const styles = StyleSheet.create({ } }); +interface IRoomHeader { + connecting: boolean; + connected: boolean; + isFetching: boolean; + serverName: string; + server: string; + showServerDropdown: boolean; + showSearchHeader: boolean; + theme: string; + onSearchChangeText: TextInputProps['onChangeText']; + onPress: TouchableOpacityProps['onPress']; +} + const Header = React.memo( ({ connecting, connected, isFetching, - serverName, + serverName = 'Rocket.Chat', server, showServerDropdown, showSearchHeader, theme, onSearchChangeText, onPress - }) => { + }: IRoomHeader) => { const titleColorStyle = { color: themes[theme].headerTitleColor }; const isLight = theme === 'light'; const { isLandscape } = useOrientation(); @@ -79,9 +91,7 @@ const Header = React.memo( - + {serverName} { componentDidMount() { if (isTablet) { EventEmitter.addEventListener(KEY_COMMAND, this.handleCommands); @@ -35,13 +37,13 @@ class RoomsListHeaderView extends PureComponent { } // eslint-disable-next-line react/sort-comp - handleCommands = ({ event }) => { + handleCommands = ({ event }: { event: IKeyCommandEvent }) => { if (handleCommandOpenServerDropdown(event)) { this.onPress(); } }; - onSearchChangeText = text => { + onSearchChangeText = (text: string) => { const { dispatch } = this.props; dispatch(setSearch(text.trim())); }; @@ -76,13 +78,13 @@ class RoomsListHeaderView extends PureComponent { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: IApplicationState) => ({ showServerDropdown: state.rooms.showServerDropdown, showSearchHeader: state.rooms.showSearchHeader, connecting: state.meteor.connecting || state.server.loading, connected: state.meteor.connected, isFetching: state.rooms.isFetching, - serverName: state.settings.Site_Name, + serverName: state.settings.Site_Name as string, server: state.server.server }); diff --git a/app/views/RoomsListView/ListHeader/index.js b/app/views/RoomsListView/ListHeader/index.tsx similarity index 73% rename from app/views/RoomsListView/ListHeader/index.js rename to app/views/RoomsListView/ListHeader/index.tsx index b1aced177..52e435acf 100644 --- a/app/views/RoomsListView/ListHeader/index.js +++ b/app/views/RoomsListView/ListHeader/index.tsx @@ -1,14 +1,27 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { withTheme } from '../../../theme'; import * as List from '../../../containers/List'; import { E2E_BANNER_TYPE } from '../../../lib/encryption/constants'; import { themes } from '../../../constants/colors'; import OmnichannelStatus from '../../../ee/omnichannel/containers/OmnichannelStatus'; +import { IUser } from '../../../definitions'; + +export type TEncryptionBanner = 'REQUEST_PASSWORD' | 'SAVE_PASSWORD'; + +interface IRoomListHeader { + searching: boolean; + goEncryption: () => void; + goQueue: () => void; + queueSize: number; + inquiryEnabled: boolean; + encryptionBanner: TEncryptionBanner; + user: IUser; + theme: string; +} const ListHeader = React.memo( - ({ searching, goEncryption, goQueue, queueSize, inquiryEnabled, encryptionBanner, user, theme }) => { + ({ searching, goEncryption, goQueue, queueSize, inquiryEnabled, encryptionBanner, user, theme }: IRoomListHeader) => { if (searching) { return null; } @@ -35,6 +48,7 @@ const ListHeader = React.memo( ) : null} { + insets?: { + top: number; }; + closeServerDropdown: boolean; + server: string; + isMasterDetail: boolean; +} - constructor(props) { +interface IServerDropdownState { + servers: TServerModel[]; +} + +class ServerDropdown extends Component { + private animatedValue: Animated.Value; + private subscription?: Subscription; + private newServerTimeout?: ReturnType | false; + + constructor(props: IServerDropdownProps) { super(props); this.state = { servers: [] }; this.animatedValue = new Animated.Value(0); @@ -66,7 +74,7 @@ class ServerDropdown extends Component { } } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: IServerDropdownProps) { const { closeServerDropdown } = this.props; if (prevProps.closeServerDropdown !== closeServerDropdown) { this.close(); @@ -105,7 +113,7 @@ class ServerDropdown extends Component { } }; - navToNewServer = previousServer => { + navToNewServer = (previousServer: string) => { const { dispatch } = this.props; batch(() => { dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE })); @@ -122,7 +130,7 @@ class ServerDropdown extends Component { }, ANIMATION_DURATION); }; - select = async (server, version) => { + select = async (server: string, version?: string) => { const { server: currentServer, dispatch, isMasterDetail } = this.props; this.close(); if (currentServer !== server) { @@ -145,7 +153,7 @@ class ServerDropdown extends Component { } }; - remove = server => + remove = (server: string) => showConfirmationAlert({ message: I18n.t('This_will_remove_all_data_from_this_server'), confirmationText: I18n.t('Delete'), @@ -159,10 +167,10 @@ class ServerDropdown extends Component { } }); - handleCommands = ({ event }) => { + handleCommands = ({ event }: { event: IKeyCommandEvent }) => { const { servers } = this.state; const { navigation } = this.props; - const { input } = event; + const { input } = event as unknown as { input: number }; if (handleCommandSelectServer(event)) { if (servers[input - 1]) { this.select(servers[input - 1].id); @@ -171,7 +179,7 @@ class ServerDropdown extends Component { } }; - renderServer = ({ item }) => { + renderServer = ({ item }: { item: { id: string; iconURL: string; name: string; version: string } }) => { const { server, theme } = this.props; return ( @@ -255,7 +263,7 @@ class ServerDropdown extends Component { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: IApplicationState) => ({ closeServerDropdown: state.rooms.closeServerDropdown, server: state.server.server, isMasterDetail: state.app.isMasterDetail diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.tsx similarity index 82% rename from app/views/RoomsListView/index.js rename to app/views/RoomsListView/index.tsx index 0ca1e5fe1..924359007 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { BackHandler, FlatList, Keyboard, RefreshControl, Text, View } from 'react-native'; +import { BackHandler, FlatList, Keyboard, NativeEventSubscription, RefreshControl, Text, View } from 'react-native'; import { batch, connect } from 'react-redux'; import { dequal } from 'dequal'; import Orientation from 'react-native-orientation-locker'; import { Q } from '@nozbe/watermelondb'; import { withSafeAreaInsets } from 'react-native-safe-area-context'; +import { Subscription } from 'rxjs'; +import { StackNavigationOptions } from '@react-navigation/stack'; import database from '../../lib/database'; import RocketChat from '../../lib/rocketchat'; @@ -32,7 +33,8 @@ import { handleCommandSearching, handleCommandSelectRoom, handleCommandShowNewMessage, - handleCommandShowPreferences + handleCommandShowPreferences, + IKeyCommandEvent } from '../../commands'; import { MAX_SIDEBAR_WIDTH } from '../../constants/tablet'; import { getUserSelector } from '../../selectors/login'; @@ -44,12 +46,63 @@ import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import { E2E_BANNER_TYPE } from '../../lib/encryption/constants'; import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry'; import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../../ee/omnichannel/lib'; -import { RootEnum } from '../../definitions'; +import { IApplicationState, IBaseScreen, ISubscription, IUser, RootEnum, TSubscriptionModel } from '../../definitions'; import { DisplayMode, SortBy } from '../../constants/constantDisplayMode'; import styles from './styles'; import ServerDropdown from './ServerDropdown'; -import ListHeader from './ListHeader'; +import ListHeader, { TEncryptionBanner } from './ListHeader'; import RoomsListHeaderView from './Header'; +import { ChatsStackParamList } from '../../stacks/types'; +import { RoomTypes } from '../../lib/rocketchat/methods/roomTypeToApiType'; + +interface IRoomsListViewProps extends IBaseScreen { + [key: string]: any; + user: IUser; + server: string; + searchText: string; + changingServer: boolean; + loadingServer: boolean; + showServerDropdown: boolean; + sortBy: string; + groupByType: boolean; + showFavorites: boolean; + showUnread: boolean; + refreshing: boolean; + StoreLastMessage: boolean; + useRealName: boolean; + isMasterDetail: boolean; + rooms: ISubscription[]; + width: number; + insets: { + left: number; + right: number; + }; + queueSize: number; + inquiryEnabled: boolean; + encryptionBanner: TEncryptionBanner; + showAvatar: boolean; + displayMode: string; + createTeamPermission: []; + createDirectMessagePermission: []; + createPublicChannelPermission: []; + createPrivateChannelPermission: []; + createDiscussionPermission: []; +} + +interface IRoomsListViewState { + searching: boolean; + search: ISubscription[]; + loading: boolean; + chatsUpdate: []; + chats: ISubscription[]; + item: ISubscription; + canCreateRoom: boolean; +} + +interface IRoomItem extends ISubscription { + search?: boolean; + outside?: boolean; +} const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12; const CHATS_HEADER = 'Chats'; @@ -62,11 +115,11 @@ const DM_HEADER = 'Direct_Messages'; const OMNICHANNEL_HEADER = 'Open_Livechats'; const QUERY_SIZE = 20; -const filterIsUnread = s => (s.unread > 0 || s.tunread?.length > 0 || s.alert) && !s.hideUnreadStatus; -const filterIsFavorite = s => s.f; -const filterIsOmnichannel = s => s.t === 'l'; -const filterIsTeam = s => s.teamMain; -const filterIsDiscussion = s => s.prid; +const filterIsUnread = (s: TSubscriptionModel) => (s.unread > 0 || s.tunread?.length > 0 || s.alert) && !s.hideUnreadStatus; +const filterIsFavorite = (s: TSubscriptionModel) => s.f; +const filterIsOmnichannel = (s: TSubscriptionModel) => s.t === 'l'; +const filterIsTeam = (s: TSubscriptionModel) => s.teamMain; +const filterIsDiscussion = (s: TSubscriptionModel) => s.prid; const shouldUpdateProps = [ 'searchText', @@ -91,53 +144,27 @@ const sortPreferencesShouldUpdate = ['sortBy', 'groupByType', 'showFavorites', ' const displayPropsShouldUpdate = ['showAvatar', 'displayMode']; -const getItemLayout = (data, index, height) => ({ +const getItemLayout = (data: ISubscription[] | null | undefined, index: number, height: number) => ({ length: height, offset: height * index, index }); -const keyExtractor = item => item.rid; +const keyExtractor = (item: ISubscription) => item.rid; -class RoomsListView extends React.Component { - static propTypes = { - navigation: PropTypes.object, - user: PropTypes.shape({ - id: PropTypes.string, - username: PropTypes.string, - token: PropTypes.string, - statusLivechat: PropTypes.string, - roles: PropTypes.array - }), - server: PropTypes.string, - searchText: PropTypes.string, - changingServer: PropTypes.bool, - loadingServer: PropTypes.bool, - showServerDropdown: PropTypes.bool, - sortBy: PropTypes.string, - groupByType: PropTypes.bool, - showFavorites: PropTypes.bool, - showUnread: PropTypes.bool, - refreshing: PropTypes.bool, - StoreLastMessage: PropTypes.bool, - theme: PropTypes.string, - useRealName: PropTypes.bool, - isMasterDetail: PropTypes.bool, - rooms: PropTypes.array, - width: PropTypes.number, - insets: PropTypes.object, - queueSize: PropTypes.number, - inquiryEnabled: PropTypes.bool, - encryptionBanner: PropTypes.string, - showAvatar: PropTypes.bool, - displayMode: PropTypes.string, - createTeamPermission: PropTypes.array, - createDirectMessagePermission: PropTypes.array, - createPublicChannelPermission: PropTypes.array, - createPrivateChannelPermission: PropTypes.array, - createDiscussionPermission: PropTypes.array - }; +class RoomsListView extends React.Component { + private animated: boolean; + private mounted: boolean; + private count: number; + private unsubscribeFocus?: () => void; + private unsubscribeBlur?: () => void; + private sortPreferencesChanged?: boolean; + private shouldUpdate?: boolean; + private backHandler?: NativeEventSubscription; + private querySubscription?: Subscription; + private scroll?: FlatList; + private useRealName?: boolean; - constructor(props) { + constructor(props: IRoomsListViewProps) { super(props); console.time(`${this.constructor.name} init`); console.time(`${this.constructor.name} mount`); @@ -151,7 +178,7 @@ class RoomsListView extends React.Component { loading: true, chatsUpdate: [], chats: [], - item: {}, + item: {} as ISubscription, canCreateRoom: false }; this.setHeader(); @@ -192,7 +219,7 @@ class RoomsListView extends React.Component { console.timeEnd(`${this.constructor.name} mount`); } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: IRoomsListViewProps) { const { loadingServer, searchText, server, changingServer } = this.props; // when the server is changed @@ -208,7 +235,7 @@ class RoomsListView extends React.Component { } } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate(nextProps: IRoomsListViewProps, nextState: IRoomsListViewState) { const { chatsUpdate, searching, item, canCreateRoom } = this.state; // eslint-disable-next-line react/destructuring-assignment const propsUpdated = shouldUpdateProps.some(key => nextProps[key] !== this.props[key]); @@ -280,7 +307,7 @@ class RoomsListView extends React.Component { return false; } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: IRoomsListViewProps) { const { sortBy, groupByType, @@ -312,8 +339,9 @@ class RoomsListView extends React.Component { this.getSubscriptions(); } // Update current item in case of another action triggers an update on rooms reducer - if (isMasterDetail && item?.rid !== rooms[0] && !dequal(rooms, prevProps.rooms)) { + if (isMasterDetail && item?.rid !== rooms[0].rid && !dequal(rooms, prevProps.rooms)) { // eslint-disable-next-line react/no-did-update-set-state + // @ts-ignore this.setState({ item: { rid: rooms[0] } }); } if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) { @@ -365,13 +393,13 @@ class RoomsListView extends React.Component { createDiscussionPermission ]; const permissionsToCreate = await RocketChat.hasPermission(permissions); - const canCreateRoom = permissionsToCreate.filter(r => r === true).length > 0; + const canCreateRoom = permissionsToCreate.filter((r: boolean) => r === true).length > 0; this.setState({ canCreateRoom }, () => this.setHeader()); }; getHeader = () => { const { searching, canCreateRoom } = this.state; - const { navigation, isMasterDetail, insets } = this.props; + const { navigation, isMasterDetail, insets, theme } = this.props; const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: searching ? 0 : 3 }); return { @@ -388,11 +416,12 @@ class RoomsListView extends React.Component { onPress={ isMasterDetail ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) - : () => navigation.toggleDrawer() + : // @ts-ignore + () => navigation.toggleDrawer() } /> ), - headerTitle: () => , + headerTitle: () => , headerTitleContainerStyle: { left: headerTitlePosition.left, right: headerTitlePosition.right @@ -412,21 +441,23 @@ class RoomsListView extends React.Component { setHeader = () => { const { navigation } = this.props; - const options = this.getHeader(); + const options = this.getHeader() as Partial; navigation.setOptions(options); }; - internalSetState = (...args) => { + // internalSetState = (...args: { chats: TSubscriptionModel; chatsUpdate: TSubscriptionModel; loading: boolean }[]) => { + internalSetState = (...args: any) => { if (this.animated) { animateNextTransition(); } + // @ts-ignore this.setState(...args); }; - addRoomsGroup = (data, header, allData) => { + addRoomsGroup = (data: TSubscriptionModel[], header: string, allData: TSubscriptionModel[]) => { if (data.length > 0) { if (header) { - allData.push({ rid: header, separator: true }); + allData.push({ rid: header, separator: true } as TSubscriptionModel); } allData = allData.concat(data); } @@ -441,7 +472,7 @@ class RoomsListView extends React.Component { const db = database.active; let observable; - const defaultWhereClause = [Q.where('archived', false), Q.where('open', true)]; + const defaultWhereClause = [Q.where('archived', false), Q.where('open', true)] as (Q.WhereDescription | Q.SortBy)[]; if (sortBy === SortBy.Alphabetical) { defaultWhereClause.push(Q.experimentalSortBy(`${this.useRealName ? 'fname' : 'name'}`, Q.asc)); @@ -466,7 +497,7 @@ class RoomsListView extends React.Component { } this.querySubscription = observable.subscribe(data => { - let tempChats = []; + let tempChats = [] as TSubscriptionModel[]; let chats = data; let chatsUpdate = []; @@ -528,8 +559,11 @@ class RoomsListView extends React.Component { loading: false }); } else { + // @ts-ignore this.state.chats = tempChats; + // @ts-ignore this.state.chatsUpdate = chatsUpdate; + // @ts-ignore this.state.loading = false; } }); @@ -580,7 +614,7 @@ class RoomsListView extends React.Component { }; // eslint-disable-next-line react/sort-comp - search = debounce(async text => { + search = debounce(async (text: string) => { const result = await RocketChat.search({ text }); // if the search was cancelled before the promise is resolved @@ -595,26 +629,26 @@ class RoomsListView extends React.Component { this.scrollToTop(); }, 300); - getRoomTitle = item => RocketChat.getRoomTitle(item); + getRoomTitle = (item: ISubscription) => RocketChat.getRoomTitle(item); - getRoomAvatar = item => RocketChat.getRoomAvatar(item); + getRoomAvatar = (item: ISubscription) => RocketChat.getRoomAvatar(item); - isGroupChat = item => RocketChat.isGroupChat(item); + isGroupChat = (item: ISubscription) => RocketChat.isGroupChat(item); - isRead = item => RocketChat.isRead(item); + isRead = (item: ISubscription) => RocketChat.isRead(item); - isSwipeEnabled = item => !(item?.search || item?.joinCodeRequired || item?.outside); + isSwipeEnabled = (item: IRoomItem) => !(item?.search || item?.joinCodeRequired || item?.outside); - getUserPresence = uid => RocketChat.getUserPresence(uid); + getUserPresence = (uid: string) => RocketChat.getUserPresence(uid); - getUidDirectMessage = room => RocketChat.getUidDirectMessage(room); + getUidDirectMessage = (room: ISubscription) => RocketChat.getUidDirectMessage(room); get isGrouping() { const { showUnread, showFavorites, groupByType } = this.props; return showUnread || showFavorites || groupByType; } - onPressItem = (item = {}) => { + onPressItem = (item = {} as ISubscription) => { const { navigation, isMasterDetail } = this.props; if (!navigation.isFocused()) { return; @@ -630,14 +664,14 @@ class RoomsListView extends React.Component { } }; - toggleFav = async (rid, favorite) => { + toggleFav = async (rid: string, favorite: boolean) => { logEvent(favorite ? events.RL_UNFAVORITE_CHANNEL : events.RL_FAVORITE_CHANNEL); try { const db = database.active; const result = await RocketChat.toggleFavorite(rid, !favorite); if (result.success) { const subCollection = db.get('subscriptions'); - await db.action(async () => { + await db.write(async () => { try { const subRecord = await subCollection.find(rid); await subRecord.update(sub => { @@ -649,12 +683,12 @@ class RoomsListView extends React.Component { }); } } catch (e) { - logEvent(events.RL_TOGGLE_FAVORITE_FAIL); + logEvent(events.RL_TOGGLE_FAVORITE_F); log(e); } }; - toggleRead = async (rid, isRead) => { + toggleRead = async (rid: string, isRead: boolean) => { logEvent(isRead ? events.RL_UNREAD_CHANNEL : events.RL_READ_CHANNEL); try { const db = database.active; @@ -662,7 +696,7 @@ class RoomsListView extends React.Component { if (result.success) { const subCollection = db.get('subscriptions'); - await db.action(async () => { + await db.write(async () => { try { const subRecord = await subCollection.find(rid); await subRecord.update(sub => { @@ -680,14 +714,14 @@ class RoomsListView extends React.Component { } }; - hideChannel = async (rid, type) => { + hideChannel = async (rid: string, type: RoomTypes) => { logEvent(events.RL_HIDE_CHANNEL); try { const db = database.active; const result = await RocketChat.hideRoom(rid, type); if (result.success) { const subCollection = db.get('subscriptions'); - await db.action(async () => { + await db.write(async () => { try { const subRecord = await subCollection.find(rid); await subRecord.destroyPermanently(); @@ -745,10 +779,11 @@ class RoomsListView extends React.Component { } }; - goRoom = ({ item, isMasterDetail }) => { + goRoom = ({ item, isMasterDetail }: { item: ISubscription; isMasterDetail: boolean }) => { logEvent(events.RL_GO_ROOM); const { item: currentItem } = this.state; const { rooms } = this.props; + // @ts-ignore if (currentItem?.rid === item.rid || rooms?.includes(item.rid)) { return; } @@ -759,7 +794,7 @@ class RoomsListView extends React.Component { goRoom({ item, isMasterDetail }); }; - goRoomByIndex = index => { + goRoomByIndex = (index: number) => { const { chats } = this.state; const { isMasterDetail } = this.props; const filteredChats = chats.filter(c => !c.separator); @@ -769,7 +804,7 @@ class RoomsListView extends React.Component { } }; - findOtherRoom = (index, sign) => { + findOtherRoom = (index: number, sign: number): ISubscription | void => { const { chats } = this.state; const otherIndex = index + sign; const otherRoom = chats[otherIndex]; @@ -778,14 +813,13 @@ class RoomsListView extends React.Component { } if (otherRoom.separator) { return this.findOtherRoom(otherIndex, sign); - } else { - return otherRoom; } + return otherRoom; }; // Go to previous or next room based on sign (-1 or 1) // It's used by iPad key commands - goOtherRoom = sign => { + goOtherRoom = (sign: number) => { const { item } = this.state; if (!item) { return; @@ -831,7 +865,7 @@ class RoomsListView extends React.Component { } }; - handleCommands = ({ event }) => { + handleCommands = ({ event }: { event: IKeyCommandEvent }) => { const { navigation, server, isMasterDetail, dispatch } = this.props; const { input } = event; if (handleCommandShowPreferences(event)) { @@ -874,11 +908,11 @@ class RoomsListView extends React.Component { } }; - getScrollRef = ref => (this.scroll = ref); + getScrollRef = (ref: FlatList) => (this.scroll = ref); renderListHeader = () => { const { searching } = this.state; - const { queueSize, inquiryEnabled, encryptionBanner, user } = this.props; + const { queueSize, inquiryEnabled, encryptionBanner, user, theme } = this.props; return ( ); }; @@ -899,11 +934,11 @@ class RoomsListView extends React.Component { return null; } - const options = this.getHeader(); + const options = this.getHeader() as any; return
; }; - renderItem = ({ item }) => { + renderItem = ({ item }: { item: ISubscription }) => { if (item.separator) { return this.renderSectionHeader(item.rid); } @@ -950,7 +985,7 @@ class RoomsListView extends React.Component { ); }; - renderSectionHeader = header => { + renderSectionHeader = (header: string) => { const { theme } = this.props; return ( @@ -1001,13 +1036,15 @@ class RoomsListView extends React.Component { {this.renderHeader()} {this.renderScroll()} - {showServerDropdown ? : null} + {/* TODO - this ts-ignore is here because the route props, on IBaseScreen*/} + {/* @ts-ignore*/} + {showServerDropdown ? : null} ); }; } -const mapStateToProps = state => ({ +const mapStateToProps = (state: IApplicationState) => ({ user: getUserSelector(state), isMasterDetail: state.app.isMasterDetail, server: state.server.server, diff --git a/app/views/RoomsListView/styles.js b/app/views/RoomsListView/styles.ts similarity index 96% rename from app/views/RoomsListView/styles.js rename to app/views/RoomsListView/styles.ts index 8c94c5fdb..8f17a5ef1 100644 --- a/app/views/RoomsListView/styles.js +++ b/app/views/RoomsListView/styles.ts @@ -22,7 +22,7 @@ export default StyleSheet.create({ borderBottomWidth: StyleSheet.hairlineWidth }, backdrop: { - ...StyleSheet.absoluteFill + ...StyleSheet.absoluteFillObject }, queueIcon: { marginHorizontal: 12