diff --git a/app/containers/InAppNotification/NotifierComponent.tsx b/app/containers/InAppNotification/NotifierComponent.tsx index b46c1b51a..8b32ae1c6 100644 --- a/app/containers/InAppNotification/NotifierComponent.tsx +++ b/app/containers/InAppNotification/NotifierComponent.tsx @@ -12,7 +12,6 @@ import { themes } from '../../lib/constants'; import { useTheme } from '../../theme'; import { ROW_HEIGHT } from '../RoomItem'; import { goRoom } from '../../lib/methods/helpers/goRoom'; -import Navigation from '../../lib/navigation/appNavigation'; import { useOrientation } from '../../dimensions'; import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions'; @@ -98,12 +97,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie prid }; - if (isMasterDetail) { - Navigation.navigate('DrawerNavigator'); - } else { - Navigation.navigate('RoomsListView'); - } - goRoom({ item, isMasterDetail, jumpToMessageId: _id }); + goRoom({ item, isMasterDetail, jumpToMessageId: _id, popToRoot: true }); hideNotification(); }; @@ -124,6 +118,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie onPress={onPress} hitSlop={BUTTON_HIT_SLOP} background={Touchable.SelectableBackgroundBorderless()} + testID={`in-app-notification-${text}`} > <> diff --git a/app/containers/InAppNotification/index.tsx b/app/containers/InAppNotification/index.tsx index 34354e632..d6581f27f 100644 --- a/app/containers/InAppNotification/index.tsx +++ b/app/containers/InAppNotification/index.tsx @@ -1,56 +1,50 @@ import React, { memo, useEffect } from 'react'; import { Easing, Notifier, NotifierRoot } from 'react-native-notifier'; -import { connect } from 'react-redux'; -import { dequal } from 'dequal'; import NotifierComponent, { INotifierComponent } from './NotifierComponent'; import EventEmitter from '../../lib/methods/helpers/events'; import Navigation from '../../lib/navigation/appNavigation'; import { getActiveRoute } from '../../lib/methods/helpers/navigation'; -import { IApplicationState } from '../../definitions'; -import { IRoom } from '../../reducers/room'; +import { useAppSelector } from '../../lib/hooks'; export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp'; -const InAppNotification = memo( - ({ rooms, appState }: { rooms: IRoom['rooms']; appState: string }) => { - const show = (notification: INotifierComponent['notification']) => { - if (appState !== 'foreground') { +const InAppNotification = memo(() => { + const { appState, subscribedRoom } = useAppSelector(state => ({ + subscribedRoom: state.room.subscribedRoom, + appState: state.app.ready && state.app.foreground ? 'foreground' : 'background' + })); + + const show = (notification: INotifierComponent['notification']) => { + if (appState !== 'foreground') { + return; + } + + const { payload } = notification; + const state = Navigation.navigationRef.current?.getRootState(); + const route = getActiveRoute(state); + if (payload.rid) { + if (payload.rid === subscribedRoom || route?.name === 'JitsiMeetView') { return; } - - const { payload } = notification; - const state = Navigation.navigationRef.current?.getRootState(); - const route = getActiveRoute(state); - if (payload.rid) { - if (rooms.includes(payload.rid) || route?.name === 'JitsiMeetView') { - return; + Notifier.showNotification({ + showEasing: Easing.inOut(Easing.quad), + Component: NotifierComponent, + componentProps: { + notification } - Notifier.showNotification({ - showEasing: Easing.inOut(Easing.quad), - Component: NotifierComponent, - componentProps: { - notification - } - }); - } + }); + } + }; + + useEffect(() => { + const listener = EventEmitter.addEventListener(INAPP_NOTIFICATION_EMITTER, show); + return () => { + EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener); }; + }, [subscribedRoom, appState]); - useEffect(() => { - const listener = EventEmitter.addEventListener(INAPP_NOTIFICATION_EMITTER, show); - return () => { - EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener); - }; - }, [rooms]); - - return ; - }, - (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms) -); - -const mapStateToProps = (state: IApplicationState) => ({ - rooms: state.room.rooms, - appState: state.app.ready && state.app.foreground ? 'foreground' : 'background' + return ; }); -export default connect(mapStateToProps)(InAppNotification); +export default InAppNotification; diff --git a/app/containers/MessageBox/index.tsx b/app/containers/MessageBox/index.tsx index 306c95825..a7e6823db 100644 --- a/app/containers/MessageBox/index.tsx +++ b/app/containers/MessageBox/index.tsx @@ -237,7 +237,7 @@ class MessageBox extends Component { async componentDidMount() { const db = database.active; - const { rid, tmid, navigation, sharing, usedCannedResponse, isMasterDetail } = this.props; + const { rid, tmid, navigation, sharing, usedCannedResponse } = this.props; let msg; try { const threadsCollection = db.get('threads'); @@ -272,7 +272,7 @@ class MessageBox extends Component { EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands); } - if (isMasterDetail && usedCannedResponse) { + if (usedCannedResponse) { this.onChangeText(usedCannedResponse); } diff --git a/app/containers/markdown/Hashtag.tsx b/app/containers/markdown/Hashtag.tsx index fd90c74b4..5cd50030b 100644 --- a/app/containers/markdown/Hashtag.tsx +++ b/app/containers/markdown/Hashtag.tsx @@ -1,14 +1,11 @@ import React from 'react'; import { StyleProp, Text, TextStyle } from 'react-native'; -import { useNavigation } from '@react-navigation/native'; -import { StackNavigationProp } from '@react-navigation/stack'; import { themes } from '../../lib/constants'; import { useTheme } from '../../theme'; import { IUserChannel } from './interfaces'; import styles from './styles'; import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription'; -import { ChatsStackParamList } from '../../stacks/types'; import { useAppSelector } from '../../lib/hooks'; import { goRoom } from '../../lib/methods/helpers/goRoom'; @@ -22,7 +19,6 @@ interface IHashtag { const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IHashtag) => { const { theme } = useTheme(); const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); - const navigation = useNavigation>(); const handlePress = async () => { const index = channels?.findIndex(channel => channel.name === hashtag); @@ -33,7 +29,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH }; const room = navParam.rid && (await getSubscriptionByRoomId(navParam.rid)); if (room) { - goRoom({ item: room, isMasterDetail, navigationMethod: isMasterDetail ? navigation.replace : navigation.push }); + goRoom({ item: room, isMasterDetail }); } else { navToRoomInfo(navParam); } diff --git a/app/ee/omnichannel/views/QueueListView.tsx b/app/ee/omnichannel/views/QueueListView.tsx index 20025ff15..8ebb3cb39 100644 --- a/app/ee/omnichannel/views/QueueListView.tsx +++ b/app/ee/omnichannel/views/QueueListView.tsx @@ -73,19 +73,14 @@ const QueueListView = React.memo(() => { const onPressItem = (item = {} as IOmnichannelRoom) => { logEvent(events.QL_GO_ROOM); - if (isMasterDetail) { - navigation.navigate('DrawerNavigator'); - } else { - navigation.navigate('RoomsListView'); - } - goRoom({ item: { ...item, // we're calling v as visitor on our mergeSubscriptionsRooms visitor: item.v }, - isMasterDetail + isMasterDetail, + popToRoot: true }); }; diff --git a/app/lib/methods/helpers/goRoom.ts b/app/lib/methods/helpers/goRoom.ts index a57d2e19f..0e0be7ce6 100644 --- a/app/lib/methods/helpers/goRoom.ts +++ b/app/lib/methods/helpers/goRoom.ts @@ -1,4 +1,5 @@ -import { ChatsStackParamList } from '../../../stacks/types'; +import { CommonActions } from '@react-navigation/native'; + import Navigation from '../../navigation/appNavigation'; import { IOmnichannelRoom, SubscriptionType, IVisitor, TSubscriptionModel, ISubscription } from '../../../definitions'; import { getRoomTitle, getUidDirectMessage } from './helpers'; @@ -19,19 +20,14 @@ export type TGoRoomItem = IGoRoomItem | TSubscriptionModel | ISubscription | IOm const navigate = ({ item, isMasterDetail, + popToRoot, ...props }: { item: TGoRoomItem; isMasterDetail: boolean; - navigationMethod?: () => ChatsStackParamList; + popToRoot: boolean; }) => { - let navigationMethod = props.navigationMethod ?? Navigation.navigate; - - if (isMasterDetail) { - navigationMethod = Navigation.replace; - } - - navigationMethod('RoomView', { + const routeParams = { rid: item.rid, name: getRoomTitle(item), t: item.t, @@ -40,6 +36,44 @@ const navigate = ({ visitor: item.visitor, roomUserId: getUidDirectMessage(item), ...props + }; + + if (isMasterDetail) { + if (popToRoot) { + Navigation.navigate('DrawerNavigator'); + } + return Navigation.dispatch((state: any) => { + const routesRoomView = state.routes.filter((r: any) => r.name !== 'RoomView'); + return CommonActions.reset({ + ...state, + routes: [ + ...routesRoomView, + { + name: 'RoomView', + params: routeParams + } + ], + index: routesRoomView.length + }); + }); + } + + if (popToRoot) { + Navigation.navigate('RoomsListView'); + } + return Navigation.dispatch((state: any) => { + const routesRoomsListView = state.routes.filter((r: any) => r.name === 'RoomsListView'); + return CommonActions.reset({ + ...state, + routes: [ + ...routesRoomsListView, + { + name: 'RoomView', + params: routeParams + } + ], + index: routesRoomsListView.length + }); }); }; @@ -51,13 +85,14 @@ interface IOmnichannelRoomVisitor extends IOmnichannelRoom { export const goRoom = async ({ item, isMasterDetail = false, + popToRoot = false, ...props }: { item: TGoRoomItem; isMasterDetail: boolean; - navigationMethod?: any; jumpToMessageId?: string; usedCannedResponse?: string; + popToRoot?: boolean; }): Promise => { if (!('id' in item) && item.t === SubscriptionType.DIRECT && item?.search) { // if user is using the search we need first to join/create room @@ -72,6 +107,7 @@ export const goRoom = async ({ t: SubscriptionType.DIRECT }, isMasterDetail, + popToRoot, ...props }); } @@ -80,5 +116,5 @@ export const goRoom = async ({ } } - return navigate({ item, isMasterDetail, ...props }); + return navigate({ item, isMasterDetail, popToRoot, ...props }); }; diff --git a/app/lib/methods/subscriptions/room.ts b/app/lib/methods/subscriptions/room.ts index 11d333be3..56d87d284 100644 --- a/app/lib/methods/subscriptions/room.ts +++ b/app/lib/methods/subscriptions/room.ts @@ -125,8 +125,8 @@ export default class RoomSubscription { if (ev === 'typing') { const { user } = reduxStore.getState().login; const { UI_Use_Real_Name } = reduxStore.getState().settings; - const { rooms } = reduxStore.getState().room; - if (rooms[0] !== _rid) { + const { subscribedRoom } = reduxStore.getState().room; + if (subscribedRoom !== _rid) { return; } const [name, typing] = ddpMessage.fields.args; diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 37505ed63..5bbeffe3d 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -197,8 +197,8 @@ const createOrUpdateSubscription = async (subscription: ISubscription, room: ISe } } - const { rooms } = store.getState().room; - if (tmp.lastMessage && !rooms.includes(tmp.rid)) { + const { subscribedRoom } = store.getState().room; + if (tmp.lastMessage && subscribedRoom !== tmp.rid) { const lastMessage = buildMessage(tmp.lastMessage); const messagesCollection = db.get('messages'); let messageRecord = {} as TMessageModel | null; diff --git a/app/lib/navigation/appNavigation.ts b/app/lib/navigation/appNavigation.ts index 7d2f73756..404e185a9 100644 --- a/app/lib/navigation/appNavigation.ts +++ b/app/lib/navigation/appNavigation.ts @@ -21,11 +21,16 @@ function popToTop() { navigationRef.current?.dispatch(StackActions.popToTop()); } +function dispatch(params: any) { + navigationRef.current?.dispatch(params); +} + export default { navigationRef, routeNameRef, navigate, back, replace, - popToTop + popToTop, + dispatch }; diff --git a/app/reducers/room.test.ts b/app/reducers/room.test.ts index 0d383091a..683bf2881 100644 --- a/app/reducers/room.test.ts +++ b/app/reducers/room.test.ts @@ -12,13 +12,13 @@ describe('test room reducer', () => { it('should return modified store after subscribeRoom', () => { mockedStore.dispatch(subscribeRoom('GENERAL')); const state = mockedStore.getState().room; - expect(state.rooms).toEqual(['GENERAL']); + expect(state.subscribedRoom).toEqual('GENERAL'); }); it('should return empty store after remove unsubscribeRoom', () => { mockedStore.dispatch(unsubscribeRoom('GENERAL')); const state = mockedStore.getState().room; - expect(state.rooms).toEqual([]); + expect(state.subscribedRoom).toEqual(''); }); it('should return initial state after leaveRoom', () => { diff --git a/app/reducers/room.ts b/app/reducers/room.ts index e5b9815b4..3462247d3 100644 --- a/app/reducers/room.ts +++ b/app/reducers/room.ts @@ -6,13 +6,13 @@ export type IRoomRecord = string[]; export interface IRoom { rid: string; isDeleting: boolean; - rooms: IRoomRecord; + subscribedRoom: string; } export const initialState: IRoom = { rid: '', isDeleting: false, - rooms: [] + subscribedRoom: '' }; export default function (state = initialState, action: TActionsRoom): IRoom { @@ -20,12 +20,12 @@ export default function (state = initialState, action: TActionsRoom): IRoom { case ROOM.SUBSCRIBE: return { ...state, - rooms: [action.rid, ...state.rooms] + subscribedRoom: action.rid }; case ROOM.UNSUBSCRIBE: return { ...state, - rooms: state.rooms.filter(rid => rid !== action.rid) + subscribedRoom: state.subscribedRoom === action.rid ? '' : state.subscribedRoom }; case ROOM.LEAVE: return { diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index a7b138862..ed81d82ac 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -4,7 +4,6 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { CREATE_CHANNEL, LOGIN } from '../actions/actionsTypes'; import { createChannelFailure, createChannelSuccess } from '../actions/createChannel'; import { showErrorAlert } from '../lib/methods/helpers/info'; -import Navigation from '../lib/navigation/appNavigation'; import database from '../lib/database'; import I18n from '../i18n'; import { events, logEvent } from '../lib/methods/helpers/log'; @@ -78,10 +77,7 @@ const handleRequest = function* handleRequest({ data }) { const handleSuccess = function* handleSuccess({ data }) { const isMasterDetail = yield select(state => state.app.isMasterDetail); - if (isMasterDetail) { - Navigation.navigate('DrawerNavigator'); - } - goRoom({ item: data, isMasterDetail }); + goRoom({ item: data, isMasterDetail, popToRoot: true }); }; const handleFailure = function handleFailure({ err, isTeam }) { diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index 47093d164..474e4a7f2 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -1,7 +1,6 @@ import { all, delay, put, select, take, takeLatest } from 'redux-saga/effects'; import UserPreferences from '../lib/methods/userPreferences'; -import Navigation from '../lib/navigation/appNavigation'; import * as types from '../actions/actionsTypes'; import { selectServerRequest, serverInitAdd } from '../actions/server'; import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'; @@ -36,14 +35,6 @@ const handleInviteLink = function* handleInviteLink({ params, requireLogin = fal } }; -const popToRoot = function popToRoot({ isMasterDetail }) { - if (isMasterDetail) { - Navigation.navigate('DrawerNavigator'); - } else { - Navigation.navigate('RoomsListView'); - } -}; - const navigate = function* navigate({ params }) { yield put(appStart({ root: RootEnum.ROOT_INSIDE })); if (params.path || params.rid) { @@ -65,27 +56,9 @@ const navigate = function* navigate({ params }) { }; const isMasterDetail = yield select(state => state.app.isMasterDetail); - const focusedRooms = yield select(state => state.room.rooms); const jumpToMessageId = params.messageId; - if (focusedRooms.includes(room.rid)) { - // if there's one room on the list or last room is the one - if (focusedRooms.length === 1 || focusedRooms[0] === room.rid) { - if (jumpToThreadId) { - // With this conditional when there is a jumpToThreadId we can avoid the thread open again - // above other thread and the room could call again the thread - popToRoot({ isMasterDetail }); - } - yield goRoom({ item, isMasterDetail, jumpToMessageId, jumpToThreadId }); - } else { - popToRoot({ isMasterDetail }); - yield goRoom({ item, isMasterDetail, jumpToMessageId, jumpToThreadId }); - } - } else { - popToRoot({ isMasterDetail }); - yield goRoom({ item, isMasterDetail, jumpToMessageId, jumpToThreadId }); - } - + yield goRoom({ item, isMasterDetail, jumpToMessageId, jumpToThreadId, popToRoot: true }); if (params.isCall) { callJitsi(item); } diff --git a/app/sagas/messages.js b/app/sagas/messages.js index 6940f2409..ddae85213 100644 --- a/app/sagas/messages.js +++ b/app/sagas/messages.js @@ -1,7 +1,6 @@ import { select, takeLatest } from 'redux-saga/effects'; import { Q } from '@nozbe/watermelondb'; -import Navigation from '../lib/navigation/appNavigation'; import { MESSAGES } from '../actions/actionsTypes'; import database from '../lib/database'; import log from '../lib/methods/helpers/log'; @@ -16,18 +15,13 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) { const subscriptions = yield subsCollection.query(Q.where('name', username)).fetch(); const isMasterDetail = yield select(state => state.app.isMasterDetail); - if (isMasterDetail) { - Navigation.navigate('DrawerNavigator'); - } else { - Navigation.navigate('RoomsListView'); - } if (subscriptions.length) { - goRoom({ item: subscriptions[0], isMasterDetail, message }); + goRoom({ item: subscriptions[0], isMasterDetail, popToRoot: true, message }); } else { const result = yield Services.createDirectMessage(username); if (result?.success) { - goRoom({ item: result?.room, isMasterDetail, message }); + goRoom({ item: result?.room, isMasterDetail, popToRoot: true, message }); } } } catch (e) { diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js index 156c5607e..6f2bc73da 100644 --- a/app/sagas/rooms.js +++ b/app/sagas/rooms.js @@ -58,13 +58,13 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) { const subsToCreate = subscriptions.filter(i1 => !existingSubs.find(i2 => i1._id === i2._id)); const subsToDelete = existingSubs.filter(i1 => !subscriptions.find(i2 => i1._id === i2._id)); - const openedRooms = yield select(state => state.room.rooms); + const subscribedRoom = yield select(state => state.room.subscribedRoom); const lastMessages = subscriptions /** Checks for opened rooms and filter them out. * It prevents this process to try persisting the same last message on the room messages fetch. * This race condition is easy to reproduce on push notification tap. */ - .filter(sub => !openedRooms.includes(sub.rid)) + .filter(sub => subscribedRoom !== sub.rid) .map(sub => sub.lastMessage && buildMessage(sub.lastMessage)) .filter(lm => lm); const lastMessagesIds = lastMessages.map(lm => lm._id).filter(lm => lm); diff --git a/app/views/AddExistingChannelView.tsx b/app/views/AddExistingChannelView.tsx index ea4f5be67..88aa83f15 100644 --- a/app/views/AddExistingChannelView.tsx +++ b/app/views/AddExistingChannelView.tsx @@ -17,7 +17,6 @@ import { TSupportedThemes, withTheme } from '../theme'; import SafeAreaView from '../containers/SafeAreaView'; import { sendLoadingEvent } from '../containers/Loading'; import { animateNextTransition } from '../lib/methods/helpers/layoutAnimation'; -import { goRoom } from '../lib/methods/helpers/goRoom'; import { showErrorAlert } from '../lib/methods/helpers/info'; import { ChatsStackParamList } from '../stacks/types'; import { TSubscriptionModel, SubscriptionType, IApplicationState } from '../definitions'; @@ -126,7 +125,7 @@ class AddExistingChannelView extends React.Component { const { selected } = this.state; - const { isMasterDetail } = this.props; + const { navigation } = this.props; sendLoadingEvent({ visible: true }); try { @@ -134,9 +133,8 @@ class AddExistingChannelView extends React.Component state.app); - const { rooms } = useAppSelector(state => state.room); useEffect(() => { navigation.setOptions({ @@ -107,31 +104,9 @@ const CannedResponseDetail = ({ navigation, route }: ICannedResponseDetailProps) const navigateToRoom = (item: ICannedResponse) => { const { room } = route.params; - const { name } = room; - const params = { - rid: room.rid, - name: getRoomTitle({ - t: room.t, - fname: name - }), - t: room.t, - roomUserId: getUidDirectMessage(room), - usedCannedResponse: item.text - }; if (room.rid) { - // if it's on master detail layout, we close the modal and replace RoomView - if (isMasterDetail) { - Navigation.navigate('DrawerNavigator'); - goRoom({ item: params, isMasterDetail }); - } else { - let navigate = navigation.push; - // if this is a room focused - if (rooms.includes(room.rid)) { - ({ navigate } = navigation); - } - navigate('RoomView', params); - } + goRoom({ item: room, isMasterDetail, popToRoot: true, usedCannedResponse: item.text }); } }; diff --git a/app/views/CannedResponsesListView/index.tsx b/app/views/CannedResponsesListView/index.tsx index 385454750..100e64569 100644 --- a/app/views/CannedResponsesListView/index.tsx +++ b/app/views/CannedResponsesListView/index.tsx @@ -12,7 +12,6 @@ import ActivityIndicator from '../../containers/ActivityIndicator'; import SearchHeader from '../../containers/SearchHeader'; import BackgroundContainer from '../../containers/BackgroundContainer'; import { useTheme } from '../../theme'; -import Navigation from '../../lib/navigation/appNavigation'; import { goRoom } from '../../lib/methods/helpers/goRoom'; import * as HeaderButton from '../../containers/HeaderButton'; import * as List from '../../containers/List'; @@ -24,7 +23,7 @@ import DropdownItemHeader from './Dropdown/DropdownItemHeader'; import styles from './styles'; import { ICannedResponse } from '../../definitions/ICannedResponse'; import { ChatsStackParamList } from '../../stacks/types'; -import { getRoomTitle, getUidDirectMessage, useDebounce } from '../../lib/methods/helpers'; +import { useDebounce } from '../../lib/methods/helpers'; import { Services } from '../../lib/services'; import { ILivechatDepartment } from '../../definitions/ILivechatDepartment'; import { useAppSelector } from '../../lib/hooks'; @@ -73,7 +72,6 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView const { theme } = useTheme(); const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); - const rooms = useAppSelector(state => state.room.rooms); const getRoomFromDb = async () => { const { rid } = route.params; @@ -107,34 +105,8 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView }; const navigateToRoom = (item: ICannedResponse) => { - if (!room) { - return; - } - const { name } = room; - const params = { - rid: room.rid, - name: getRoomTitle({ - t: room.t, - fname: name - }), - t: room.t, - roomUserId: getUidDirectMessage(room), - usedCannedResponse: item.text - }; - - if (room.rid) { - // if it's on master detail layout, we close the modal and replace RoomView - if (isMasterDetail) { - Navigation.navigate('DrawerNavigator'); - goRoom({ item: params, isMasterDetail }); - } else { - let navigate = navigation.push; - // if this is a room focused - if (rooms.includes(room.rid)) { - ({ navigate } = navigation); - } - navigate('RoomView', params); - } + if (room?.rid) { + goRoom({ item: room, isMasterDetail, popToRoot: true, usedCannedResponse: item.text }); } }; diff --git a/app/views/CreateDiscussionView/index.tsx b/app/views/CreateDiscussionView/index.tsx index 6d781d291..9ce8e1fbe 100644 --- a/app/views/CreateDiscussionView/index.tsx +++ b/app/views/CreateDiscussionView/index.tsx @@ -12,7 +12,6 @@ import StatusBar from '../../containers/StatusBar'; import { withTheme } from '../../theme'; import { getUserSelector } from '../../selectors/login'; import { FormTextInput } from '../../containers/TextInput'; -import Navigation from '../../lib/navigation/appNavigation'; import { createDiscussionRequest, ICreateDiscussionRequestData } from '../../actions/createDiscussion'; import SafeAreaView from '../../containers/SafeAreaView'; import { goRoom } from '../../lib/methods/helpers/goRoom'; @@ -60,18 +59,13 @@ class CreateChannelView extends React.Component { - const { navigation, isMasterDetail } = this.props; - if (isMasterDetail) { - navigation.navigate('DrawerNavigator'); - } else { - navigation.navigate('RoomsListView'); - } - goRoom({ item, isMasterDetail }); + const { isMasterDetail } = this.props; + goRoom({ item, isMasterDetail, popToRoot: true }); }; onPressItem = async (item: IServerRoom) => { diff --git a/app/views/NewMessageView/index.tsx b/app/views/NewMessageView/index.tsx index 8f05d5809..f393a9f91 100644 --- a/app/views/NewMessageView/index.tsx +++ b/app/views/NewMessageView/index.tsx @@ -74,10 +74,7 @@ const NewMessageView = () => { const goRoom = useCallback( (item: TGoRoomItem) => { logEvent(events.NEW_MSG_CHAT_WITH_USER); - - if (isMasterDetail) { - navigation.pop(); - } + navigation.pop(); goRoomMethod({ item, isMasterDetail }); }, [isMasterDetail, navigation] diff --git a/app/views/RoomInfoView/index.tsx b/app/views/RoomInfoView/index.tsx index 9c7c9dde7..2744c0cc6 100644 --- a/app/views/RoomInfoView/index.tsx +++ b/app/views/RoomInfoView/index.tsx @@ -87,7 +87,7 @@ interface IRoomInfoViewProps { StackNavigationProp >; route: RouteProp; - rooms: string[]; + subscribedRoom: string; theme: TSupportedThemes; isMasterDetail: boolean; jitsiEnabled: boolean; @@ -353,7 +353,7 @@ class RoomInfoView extends React.Component { logEvent(events.RI_GO_ROOM_USER); const { room } = this.state; - const { rooms, navigation, isMasterDetail } = this.props; + const { navigation, isMasterDetail, subscribedRoom } = this.props; const params = { rid: room.rid, name: getRoomTitle(room), @@ -362,18 +362,14 @@ class RoomInfoView extends React.Component ({ - rooms: state.room.rooms, + subscribedRoom: state.room.subscribedRoom, isMasterDetail: state.app.isMasterDetail, jitsiEnabled: (state.settings.Jitsi_Enabled as boolean) || false, editRoomPermission: state.permissions['edit-room'], diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts index ff622dbf4..d1a7e8945 100644 --- a/app/views/RoomMembersView/helpers.ts +++ b/app/views/RoomMembersView/helpers.ts @@ -15,12 +15,7 @@ import { RoomTypes } from '../../lib/methods'; export type TRoomType = SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL; const handleGoRoom = (item: TGoRoomItem, isMasterDetail: boolean): void => { - if (isMasterDetail) { - appNavigation.navigate('DrawerNavigator'); - } else { - appNavigation.popToTop(); - } - goRoom({ item, isMasterDetail }); + goRoom({ item, isMasterDetail, popToRoot: true }); }; export const fetchRole = (role: string, selectedUser: TUserModel, roomRoles: any): boolean => { diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 6a889943b..978372aa9 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -43,7 +43,6 @@ import SafeAreaView from '../../containers/SafeAreaView'; import { withDimensions } from '../../dimensions'; import { takeInquiry, takeResume } from '../../ee/omnichannel/lib'; import { sendLoadingEvent } from '../../containers/Loading'; -import { goRoom, TGoRoomItem } from '../../lib/methods/helpers/goRoom'; import getThreadName from '../../lib/methods/getThreadName'; import getRoomInfo from '../../lib/methods/getRoomInfo'; import { ContainerTypes } from '../../containers/UIKit/interfaces'; @@ -101,6 +100,7 @@ import { } from '../../lib/methods/helpers'; import { Services } from '../../lib/services'; import { withActionSheet, IActionSheetProvider } from '../../containers/ActionSheet'; +import { goRoom, TGoRoomItem } from '../../lib/methods/helpers/goRoom'; type TStateAttrsUpdate = keyof IRoomViewState; @@ -910,15 +910,15 @@ class RoomView extends React.Component { onDiscussionPress = debounce( async (item: TAnyMessageModel) => { - const { navigation } = this.props; + const { isMasterDetail } = this.props; if (!item.drid) return; const sub = await getRoomInfo(item.drid); - navigation.push('RoomView', { - rid: item.drid as string, - prid: item?.subscription?.id, - name: item.msg, - t: (sub?.t as SubscriptionType) || (this.t as SubscriptionType) - }); + if (sub) { + goRoom({ + item: sub as TGoRoomItem, + isMasterDetail + }); + } }, 1000, true @@ -987,6 +987,11 @@ class RoomView extends React.Component { } else { this.navToThread(message); } + } else if (!message.tmid && message.rid === this.rid && this.t === 'thread' && !message.replies) { + /** + * if the user is within a thread and the message that he is trying to jump to, is a message in the main room + */ + return this.navToRoom(message); } else { /** * if it's from server, we don't have it saved locally and so we fetch surroundings @@ -1198,12 +1203,12 @@ class RoomView extends React.Component { }; navToRoom = async (message: TAnyMessageModel) => { - const { navigation, isMasterDetail } = this.props; + const { isMasterDetail } = this.props; const roomInfo = await getRoomInfo(message.rid); + return goRoom({ item: roomInfo as TGoRoomItem, isMasterDetail, - navigationMethod: navigation.push, jumpToMessageId: message.id }); }; diff --git a/app/views/RoomsListView/index.tsx b/app/views/RoomsListView/index.tsx index 84062f715..3ff5ff6a6 100644 --- a/app/views/RoomsListView/index.tsx +++ b/app/views/RoomsListView/index.tsx @@ -6,8 +6,10 @@ 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 { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { Header } from '@react-navigation/elements'; +import { CompositeNavigationProp, RouteProp } from '@react-navigation/native'; +import { Dispatch } from 'redux'; import database from '../../lib/database'; import RoomItem, { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from '../../containers/RoomItem'; @@ -20,7 +22,7 @@ import StatusBar from '../../containers/StatusBar'; import ActivityIndicator from '../../containers/ActivityIndicator'; import { serverInitAdd } from '../../actions/server'; import { animateNextTransition } from '../../lib/methods/helpers/layoutAnimation'; -import { withTheme } from '../../theme'; +import { TSupportedThemes, withTheme } from '../../theme'; import EventEmitter from '../../lib/methods/helpers/events'; import { themedHeader } from '../../lib/methods/helpers/navigation'; import { @@ -39,20 +41,12 @@ import { goRoom } from '../../lib/methods/helpers/goRoom'; import SafeAreaView from '../../containers/SafeAreaView'; import { withDimensions } from '../../dimensions'; import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry'; -import { - IApplicationState, - IBaseScreen, - ISubscription, - IUser, - RootEnum, - SubscriptionType, - TSubscriptionModel -} from '../../definitions'; +import { IApplicationState, ISubscription, IUser, RootEnum, SubscriptionType, TSubscriptionModel } from '../../definitions'; import styles from './styles'; import ServerDropdown from './ServerDropdown'; import ListHeader, { TEncryptionBanner } from './ListHeader'; import RoomsListHeaderView from './Header'; -import { ChatsStackParamList } from '../../stacks/types'; +import { ChatsStackParamList, DrawerParamList } from '../../stacks/types'; import { RoomTypes, search } from '../../lib/methods'; import { getRoomAvatar, @@ -67,7 +61,16 @@ import { import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes } from '../../lib/constants'; import { Services } from '../../lib/services'; -interface IRoomsListViewProps extends IBaseScreen { +type TNavigation = CompositeNavigationProp< + StackNavigationProp, + CompositeNavigationProp, StackNavigationProp> +>; + +interface IRoomsListViewProps { + navigation: TNavigation; + route: RouteProp; + theme: TSupportedThemes; + dispatch: Dispatch; [key: string]: IUser | string | boolean | ISubscription[] | number | object | TEncryptionBanner; user: IUser; server: string; @@ -83,7 +86,7 @@ interface IRoomsListViewProps extends IBaseScreen { 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)) { + const { subscribedRoom } = this.props; + + if (currentItem?.rid === item.rid || subscribedRoom === item.rid) { return; } // Only mark room as focused when in master detail layout @@ -1040,7 +1043,7 @@ const mapStateToProps = (state: IApplicationState) => ({ showUnread: state.sortPreferences.showUnread, useRealName: state.settings.UI_Use_Real_Name, StoreLastMessage: state.settings.Store_Last_Message, - rooms: state.room.rooms, + subscribedRoom: state.room.subscribedRoom, queueSize: getInquiryQueueSelector(state).length, inquiryEnabled: state.inquiry.enabled, encryptionBanner: state.encryption.banner, diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 357c6e2ed..a6bc411f4 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -322,7 +322,7 @@ class TeamChannelsView extends React.Component { logEvent(events.TC_GO_ROOM); - const { navigation, isMasterDetail } = this.props; + const { isMasterDetail } = this.props; try { let params = {}; const result = await Services.getRoomInfo(item._id); @@ -335,10 +335,7 @@ class TeamChannelsView extends React.Component { + let dmCreatedRid: string; + + before(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + const result = await post(`im.create`, { username: data.users.alternate.username }); + dmCreatedRid = result.data.room.rid; + }); + + describe('receive in RoomsListView', () => { + const text = 'Message in DM'; + it('should have rooms list screen', async () => { + await expect(element(by.id('rooms-list-view'))).toBeVisible(); + }); + + it('should send direct message from user alternate to user regular', async () => { + await sleep(1000); + await sendMessage(data.users.alternate, dmCreatedRid, text); + }); + + it('should tap on InApp Notification', async () => { + await waitFor(element(by.id(`in-app-notification-${text}`))) + .toExist() + .withTimeout(2000); + await sleep(500); + await element(by.id(`in-app-notification-${text}`)).tap(); + await sleep(500); + await expect(element(by.id('room-header'))).toExist(); + await expect(element(by.id(`room-view-title-${data.users.alternate.username}`))).toExist(); + }); + }); + + describe('receive in another room', () => { + const text = 'Another msg'; + it('should back to RoomsListView and open the channel Detox Public', async () => { + await tapBack(); + await sleep(500); + await element(by.id(`rooms-list-view-item-${data.userRegularChannels.detoxpublic.name}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await expect(element(by.id(`room-view-title-${data.userRegularChannels.detoxpublic.name}`))).toExist(); + }); + + it('should receive and tap InAppNotification in another room', async () => { + await sendMessage(data.users.alternate, dmCreatedRid, text); + await waitFor(element(by.id(`in-app-notification-${text}`))) + .toExist() + .withTimeout(2000); + await sleep(500); + await element(by.id(`in-app-notification-${text}`)).tap(); + await sleep(500); + await expect(element(by.id('room-header'))).toExist(); + await expect(element(by.id(`room-view-title-${data.users.alternate.username}`))).toExist(); + }); + + it('should back to RoomsListView', async () => { + await tapBack(); + await sleep(500); + await expect(element(by.id('rooms-list-view'))).toBeVisible(); + }); + }); +}); diff --git a/e2e/tests/room/09-jumptomessage.spec.ts b/e2e/tests/room/09-jumptomessage.spec.ts index c2aea7ffb..c1ad32a05 100644 --- a/e2e/tests/room/09-jumptomessage.spec.ts +++ b/e2e/tests/room/09-jumptomessage.spec.ts @@ -200,7 +200,7 @@ describe('Room', () => { const expectThreadMessages = async (message: string) => { await waitFor(element(by.id('room-view-title-thread 1'))) .toExist() - .withTimeout(5000); + .withTimeout(10000); await waitFor(element(by[textMatcher](message)).atIndex(0)) .toExist() .withTimeout(10000); diff --git a/e2e/tests/team/02-team.spec.ts b/e2e/tests/team/02-team.spec.ts index 382ed1ddf..5e22550a8 100644 --- a/e2e/tests/team/02-team.spec.ts +++ b/e2e/tests/team/02-team.spec.ts @@ -199,6 +199,14 @@ describe('Team', () => { }); it('should add existing channel to team', async () => { + await navigateToRoom(team); + await waitFor(element(by.id('room-view-header-team-channels'))) + .toExist() + .withTimeout(5000); + await element(by.id('room-view-header-team-channels')).tap(); + await waitFor(element(by.id('team-channels-view'))) + .toExist() + .withTimeout(5000); await element(by.id('team-channels-view-create')).tap(); await waitFor(element(by.id('add-channel-team-view'))) .toExist()