diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 440766f18..8c3ab8d18 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -29,7 +29,7 @@ android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:exported="true" android:label="@string/app_name" - android:launchMode="singleTop" + android:launchMode="singleTask" android:windowSoftInputMode="adjustResize"> diff --git a/app/containers/InAppNotification/index.js b/app/containers/InAppNotification/index.js index 53e4bb264..7d22b127a 100644 --- a/app/containers/InAppNotification/index.js +++ b/app/containers/InAppNotification/index.js @@ -11,8 +11,12 @@ import { getActiveRoute } from '../../utils/navigation'; export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp'; -const InAppNotification = memo(({ rooms }) => { +const InAppNotification = memo(({ rooms, appState }) => { const show = (notification) => { + if (appState !== 'foreground') { + return; + } + const { payload } = notification; const state = Navigation.navigationRef.current?.getRootState(); const route = getActiveRoute(state); @@ -41,11 +45,13 @@ const InAppNotification = memo(({ rooms }) => { }, (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms)); const mapStateToProps = state => ({ - rooms: state.room.rooms + rooms: state.room.rooms, + appState: state.app.ready && state.app.foreground ? 'foreground' : 'background' }); InAppNotification.propTypes = { - rooms: PropTypes.array + rooms: PropTypes.array, + appState: PropTypes.string }; export default connect(mapStateToProps)(InAppNotification); diff --git a/app/index.js b/app/index.js index 8bf338dad..048323a5b 100644 --- a/app/index.js +++ b/app/index.js @@ -112,16 +112,25 @@ export default class Root extends React.Component { init = async() => { UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme); - const [notification, deepLinking] = await Promise.all([initializePushNotifications(), Linking.getInitialURL()]); - const parsedDeepLinkingURL = parseDeepLinking(deepLinking); store.dispatch(appInitLocalSettings()); + + // Open app from push notification + const notification = await initializePushNotifications(); if (notification) { onNotification(notification); - } else if (parsedDeepLinkingURL) { - store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); - } else { - store.dispatch(appInit()); + return; } + + // Open app from deep linking + const deepLinking = await Linking.getInitialURL(); + const parsedDeepLinkingURL = parseDeepLinking(deepLinking); + if (parsedDeepLinkingURL) { + store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); + return; + } + + // Open app from app icon + store.dispatch(appInit()); } getMasterDetail = (width) => { diff --git a/app/lib/methods/subscriptions/rooms.js b/app/lib/methods/subscriptions/rooms.js index f4b5c7b1d..b79e422a6 100644 --- a/app/lib/methods/subscriptions/rooms.js +++ b/app/lib/methods/subscriptions/rooms.js @@ -407,7 +407,7 @@ export default function subscribeRooms() { }; connectedListener = this.sdk.onStreamData('connected', handleConnection); - disconnectedListener = this.sdk.onStreamData('close', handleConnection); + // disconnectedListener = this.sdk.onStreamData('close', handleConnection); streamListener = this.sdk.onStreamData('stream-notify-user', handleStreamMessageReceived); try { diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 53c792100..693c5eb78 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -177,9 +177,16 @@ const RocketChat = { } this.controller = new AbortController(); }, + checkAndReopen() { + return this?.sdk?.checkAndReopen(); + }, connect({ server, user, logoutOnError = false }) { return new Promise((resolve) => { - if (!this.sdk || this.sdk.client.host !== server) { + if (this?.sdk?.client?.host === server) { + return resolve(); + } else { + this.sdk?.disconnect?.(); + this.sdk = null; database.setActiveDB(server); } reduxStore.dispatch(connectRequest()); @@ -208,11 +215,6 @@ const RocketChat = { EventEmitter.emit('INQUIRY_UNSUBSCRIBE'); - if (this.sdk) { - this.sdk.disconnect(); - this.sdk = null; - } - if (this.code) { this.code = null; } @@ -240,6 +242,10 @@ const RocketChat = { sdkConnect(); + this.connectedListener = this.sdk.onStreamData('connecting', () => { + reduxStore.dispatch(connectRequest()); + }); + this.connectedListener = this.sdk.onStreamData('connected', () => { reduxStore.dispatch(connectSuccess()); }); diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index ff10bd76b..21e0695fb 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -31,6 +31,14 @@ 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: ROOT_INSIDE })); if (params.path) { @@ -38,19 +46,28 @@ const navigate = function* navigate({ params }) { if (type !== 'invite') { const room = yield RocketChat.canOpenRoom(params); if (room) { - const isMasterDetail = yield select(state => state.app.isMasterDetail); - if (isMasterDetail) { - Navigation.navigate('DrawerNavigator'); - } else { - Navigation.navigate('RoomsListView'); - } const item = { name, t: roomTypes[type], roomUserId: RocketChat.getUidDirectMessage(room), ...room }; - yield goRoom({ item, isMasterDetail }); + + const isMasterDetail = yield select(state => state.app.isMasterDetail); + const focusedRooms = yield select(state => state.room.rooms); + + 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) { + yield goRoom({ item, isMasterDetail }); + } else { + popToRoot({ isMasterDetail }); + yield goRoom({ item, isMasterDetail }); + } + } else { + popToRoot({ isMasterDetail }); + yield goRoom({ item, isMasterDetail }); + } if (params.isCall) { RocketChat.callJitsi(item); @@ -121,10 +138,10 @@ const handleOpen = function* handleOpen({ params }) { } else { // search if deep link's server already exists try { - const servers = yield serversCollection.find(host); - if (servers && user) { + const hostServerRecord = yield serversCollection.find(host); + if (hostServerRecord && user) { yield localAuthenticate(host); - yield put(selectServerRequest(host)); + yield put(selectServerRequest(host, hostServerRecord.version, true, true)); yield take(types.LOGIN.SUCCESS); yield navigate({ params }); return; diff --git a/app/sagas/state.js b/app/sagas/state.js index 8b072c0a9..5b86af2ef 100644 --- a/app/sagas/state.js +++ b/app/sagas/state.js @@ -12,13 +12,14 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() { if (appRoot === ROOT_OUTSIDE) { return; } - const auth = yield select(state => state.login.isAuthenticated); - if (!auth) { + const login = yield select(state => state.login); + const server = yield select(state => state.server); + if (!login.isAuthenticated || login.isFetching || server.connecting || server.loading || server.changingServer) { return; } try { - const server = yield select(state => state.server.server); - yield localAuthenticate(server); + yield localAuthenticate(server.server); + RocketChat.checkAndReopen(); setBadgeCount(); return yield RocketChat.setUserPresenceOnline(); } catch (e) { @@ -31,14 +32,6 @@ const appHasComeBackToBackground = function* appHasComeBackToBackground() { if (appRoot === ROOT_OUTSIDE) { return; } - const auth = yield select(state => state.login.isAuthenticated); - if (!auth) { - return; - } - const localAuthenticated = yield select(state => state.login.isLocalAuthenticated); - if (!localAuthenticated) { - return; - } try { const server = yield select(state => state.server.server); yield saveLastLocalAuthenticationSession(server); diff --git a/app/utils/localAuthentication.js b/app/utils/localAuthentication.js index f6fd19da4..6e6d40dfb 100644 --- a/app/utils/localAuthentication.js +++ b/app/utils/localAuthentication.js @@ -102,9 +102,6 @@ export const localAuthenticate = async(server) => { // if screen lock is enabled if (serverRecord?.autoLock) { - // set isLocalAuthenticated to false - store.dispatch(setLocalAuthenticated(false)); - // Make sure splash screen has been hidden RNBootSplash.hide(); @@ -118,6 +115,9 @@ export const localAuthenticate = async(server) => { // if last authenticated session is older than configured auto lock time, authentication is required if (diffToLastSession >= serverRecord?.autoLockTime) { + // set isLocalAuthenticated to false + store.dispatch(setLocalAuthenticated(false)); + let hasBiometry = false; // if biometry is enabled on the app diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index 6e40b78d1..996847c91 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -89,7 +89,6 @@ const shouldUpdateProps = [ 'showUnread', 'useRealName', 'StoreLastMessage', - 'appState', 'theme', 'isMasterDetail', 'refreshing', @@ -126,7 +125,6 @@ class RoomsListView extends React.Component { showUnread: PropTypes.bool, refreshing: PropTypes.bool, StoreLastMessage: PropTypes.bool, - appState: PropTypes.string, theme: PropTypes.string, toggleSortDropdown: PropTypes.func, openSearchHeader: PropTypes.func, @@ -135,7 +133,6 @@ class RoomsListView extends React.Component { roomsRequest: PropTypes.func, closeServerDropdown: PropTypes.func, useRealName: PropTypes.bool, - connected: PropTypes.bool, isMasterDetail: PropTypes.bool, rooms: PropTypes.array, width: PropTypes.number, @@ -276,9 +273,6 @@ class RoomsListView extends React.Component { groupByType, showFavorites, showUnread, - appState, - connected, - roomsRequest, rooms, isMasterDetail, insets @@ -294,12 +288,6 @@ class RoomsListView extends React.Component { ) ) { this.getSubscriptions(); - } else if ( - appState === 'foreground' - && appState !== prevProps.appState - && connected - ) { - roomsRequest(); } // Update current item in case of another action triggers an update on rooms reducer if (isMasterDetail && item?.rid !== rooms[0] && !dequal(rooms, prevProps.rooms)) { @@ -319,6 +307,9 @@ class RoomsListView extends React.Component { if (this.unsubscribeBlur) { this.unsubscribeBlur(); } + if (this.backHandler && this.backHandler.remove) { + this.backHandler.remove(); + } if (isTablet) { EventEmitter.removeListener(KEY_COMMAND, this.handleCommands); } @@ -1018,7 +1009,6 @@ const mapStateToProps = state => ({ isMasterDetail: state.app.isMasterDetail, server: state.server.server, changingServer: state.server.changingServer, - connected: state.server.connected, searchText: state.rooms.searchText, loadingServer: state.server.loading, showServerDropdown: state.rooms.showServerDropdown, @@ -1029,7 +1019,6 @@ const mapStateToProps = state => ({ showFavorites: state.sortPreferences.showFavorites, showUnread: state.sortPreferences.showUnread, useRealName: state.settings.UI_Use_Real_Name, - appState: state.app.ready && state.app.foreground ? 'foreground' : 'background', StoreLastMessage: state.settings.Store_Last_Message, rooms: state.room.rooms, queueSize: getInquiryQueueSelector(state).length, diff --git a/ios/ReplyNotification.swift b/ios/ReplyNotification.swift index 87b8cc439..771a1056d 100644 --- a/ios/ReplyNotification.swift +++ b/ios/ReplyNotification.swift @@ -48,6 +48,9 @@ class ReplyNotification: RNNotificationEventHandler { } } } + } else { + let body = RNNotificationParser.parseNotificationResponse(response) + RNEventEmitter.sendEvent(RNNotificationOpened, body: body) } } } diff --git a/ios/RocketChatRN-Bridging-Header.h b/ios/RocketChatRN-Bridging-Header.h index 0c1ed67a4..142bd0845 100644 --- a/ios/RocketChatRN-Bridging-Header.h +++ b/ios/RocketChatRN-Bridging-Header.h @@ -7,6 +7,8 @@ #import #import #import +#import +#import #import #import #import diff --git a/package.json b/package.json index d41564f0b..901e70955 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "reselect": "4.0.0", "rn-extensions-share": "RocketChat/rn-extensions-share", "rn-fetch-blob": "0.12.0", - "rn-root-view": "^1.0.3", + "rn-root-view": "1.0.3", "semver": "7.3.2", "ua-parser-js": "^0.7.21", "url-parse": "^1.4.7", diff --git a/yarn.lock b/yarn.lock index bc0d65951..633d519bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2274,7 +2274,7 @@ "@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile": version "1.0.0-mobile" - resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/0a97c818e60670d7660868ea107b96e5ebb631af" + resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/0241e2fc0c29827c51655f2d46d96e7a7720d2b6" dependencies: js-sha256 "^0.9.0" lru-cache "^4.1.1" @@ -13788,7 +13788,7 @@ rn-host-detect@1.2.0: resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.2.0.tgz#8b0396fc05631ec60c1cb8789e5070cdb04d0da0" integrity sha512-btNg5kzHcjZZ7t7mvvV/4wNJ9e3MPgrWivkRgWURzXL0JJ0pwWlU4zrbmdlz3HHzHOxhBhHB4D+/dbMFfu4/4A== -rn-root-view@^1.0.3: +rn-root-view@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/rn-root-view/-/rn-root-view-1.0.3.tgz#a2cddc717278cb2175fb29b7c006e407b7f0d0e2" integrity sha512-BIKm8hY5q8+pxK9B5ugYjqutoI9xn2JfxIZKWoaFmAl1bOIM4oXjwFQrRM1e6lFgzz99MN6Mf2dK3Alsywnvvw==