From 8b794e0a9422ac939f934bcba7fdfbe38a7ff3f8 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Tue, 6 Jan 2026 22:09:38 +0530 Subject: [PATCH 01/38] feat: initial setup --- app/AppContainer.tsx | 6 ++ app/lib/navigation/addServer.ts/index.ts | 16 +++++ app/lib/quickActions/index.ts | 83 ++++++++++++++++++++++ ios/Podfile | 1 + ios/Podfile.lock | 10 ++- ios/RocketChatRN.xcodeproj/project.pbxproj | 8 +-- ios/RocketChatRN/Info.plist | 24 +++++++ package.json | 1 + yarn.lock | 24 +++++++ 9 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 app/lib/navigation/addServer.ts/index.ts create mode 100644 app/lib/quickActions/index.ts diff --git a/app/AppContainer.tsx b/app/AppContainer.tsx index 001b9d33a0d..3cba09b9a78 100644 --- a/app/AppContainer.tsx +++ b/app/AppContainer.tsx @@ -3,6 +3,7 @@ import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { connect } from 'react-redux'; +import { registerQuickActions } from './lib/quickActions'; import type { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes'; import Navigation from './lib/navigation/appNavigation'; import { defaultHeader, getActiveRouteName, navigationTheme } from './lib/methods/helpers/navigation'; @@ -34,6 +35,11 @@ const SetUsernameStack = () => ( const Stack = createStackNavigator(); const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => { const { theme } = useContext(ThemeContext); + + useEffect(() => { + registerQuickActions(); + }, []); + useEffect(() => { if (root) { const state = Navigation.navigationRef.current?.getRootState(); diff --git a/app/lib/navigation/addServer.ts/index.ts b/app/lib/navigation/addServer.ts/index.ts new file mode 100644 index 00000000000..33d1d177444 --- /dev/null +++ b/app/lib/navigation/addServer.ts/index.ts @@ -0,0 +1,16 @@ +import { batch } from 'react-redux'; + +import { appStart } from '../../../actions/app'; +import { serverInitAdd } from '../../../actions/server'; +import { RootEnum } from '../../../definitions'; +import store from '../../store'; +import { logEvent, events } from '../../methods/helpers/log'; + +export function navigateToAddServer(previousServer: string) { + logEvent(events.RL_ADD_SERVER); + + batch(() => { + store.dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE })); + store.dispatch(serverInitAdd(previousServer)); + }); +} diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts new file mode 100644 index 00000000000..02dc73b76b4 --- /dev/null +++ b/app/lib/quickActions/index.ts @@ -0,0 +1,83 @@ +import * as QuickActions from 'expo-quick-actions'; +import { InteractionManager, AppState } from 'react-native'; + +import { navigateToAddServer } from '../navigation/addServer.ts'; +import store from '../store'; + +let pendingQuickAction: string | null = null; +let quickActionConsumed = false; + +let registered = false; + +let currentAppState = AppState.currentState; + +AppState.addEventListener('change', nextState => { + if (currentAppState === 'active' && nextState !== 'active') { + quickActionConsumed = false; + } + + if (nextState === 'active') { + consumePendingQuickAction(); + } + + currentAppState = nextState; +}); + +export function registerQuickActions() { + if (registered) { + return; + } + registered = true; + + QuickActions.setItems([ + { id: 'add-server', title: 'Add Server', icon: 'plus', href: '' }, + { id: 'search', title: 'Search', icon: 'search', href: '' }, + { id: 'recent', title: 'Recent Rooms', icon: 'clock', href: '' } + ]); + + QuickActions.addListener(action => { + console.log(action, 'quickactions======================='); + if (!action?.id) { + console.log('return block'); + return; + } + console.log('else block'); + + pendingQuickAction = action.id; + }); +} + +function consumePendingQuickAction() { + if (!pendingQuickAction || quickActionConsumed) { + return; + } + + quickActionConsumed = true; + + console.log('consume block'); + + const action = pendingQuickAction; + pendingQuickAction = null; + + InteractionManager.runAfterInteractions(() => { + handleQuickAction(action); + }); +} + +function handleQuickAction(id: string) { + switch (id) { + case 'add-server': + const state = store.getState(); + const server = state?.server?.server; + navigateToAddServer(server); + break; + + case 'search': + console.log('search ========================='); + break; + + case 'recent': + console.log('recent ========================='); + break; + } +} diff --git a/ios/Podfile b/ios/Podfile index 4d852a82cd3..61ffa429e1d 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -9,6 +9,7 @@ prepare_react_native_project! def all_pods pod 'simdjson', path: '../node_modules/@nozbe/simdjson', modular_headers: true pod 'react-native-mmkv', path: '../node_modules/react-native-mmkv', modular_headers: true + pod 'ExpoQuickActions', path: '../node_modules/expo-quick-actions/ios' $RNFirebaseAnalyticsWithoutAdIdSupport = true use_expo_modules! diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 34cc3e5cd90..25a7d767da0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -117,6 +117,8 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - ExpoQuickActions (6.0.0): + - ExpoModulesCore - ExpoSystemUI (5.0.7): - ExpoModulesCore - ExpoVideoThumbnails (9.1.2): @@ -2652,6 +2654,7 @@ DEPENDENCIES: - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - ExpoLocalAuthentication (from `../node_modules/expo-local-authentication/ios`) - ExpoModulesCore (from `../node_modules/expo-modules-core`) + - ExpoQuickActions (from `../node_modules/expo-quick-actions/ios`) - ExpoSystemUI (from `../node_modules/expo-system-ui/ios`) - ExpoVideoThumbnails (from `../node_modules/expo-video-thumbnails/ios`) - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) @@ -2829,6 +2832,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-local-authentication/ios" ExpoModulesCore: :path: "../node_modules/expo-modules-core" + ExpoQuickActions: + :path: "../node_modules/expo-quick-actions/ios" ExpoSystemUI: :path: "../node_modules/expo-system-ui/ios" ExpoVideoThumbnails: @@ -3059,6 +3064,7 @@ SPEC CHECKSUMS: ExpoKeepAwake: 213acedecafb6fda8c0ffedad22ee9e2903400c5 ExpoLocalAuthentication: f69863a1822e42db67a311ce839ecbac70e7fa65 ExpoModulesCore: b4fdeaceca6a4360d4a75fbae335907427c1df6b + ExpoQuickActions: 31a70aa6a606128de4416a4830e09cfabfe6667f ExpoSystemUI: 82c970cf8495449698e7343b4f78a0d04bcec9ee ExpoVideoThumbnails: 2a448a23eb4eeb860d92ded372fec6e6a7a0cdcb ExpoWebBrowser: d81a92137919312afc5628d47951dc7a35554f90 @@ -3185,9 +3191,9 @@ SPEC CHECKSUMS: SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 WatermelonDB: 4c846c8cb94eef3cba90fa034d15310163226703 - Yoga: dfabf1234ccd5ac41d1b1d43179f024366ae9831 + Yoga: 2a3a4c38a8441b6359d5e5914d35db7b2b67aebd ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: 199f6fbbe6fb415c822cca992e6152000ac55b3e +PODFILE CHECKSUM: dd876c3ef913a49cdfbdb46236ad1b6a54b56724 COCOAPODS: 1.15.2 diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index cca807529bd..ae57baf9f3c 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -1713,7 +1713,7 @@ inputFileListPaths = ( ); inputPaths = ( - "$TARGET_BUILD_DIR/$INFOPLIST_PATH", + $TARGET_BUILD_DIR/$INFOPLIST_PATH, ); name = "Upload source maps to Bugsnag"; outputFileListPaths = ( @@ -1799,7 +1799,7 @@ inputFileListPaths = ( ); inputPaths = ( - "$TARGET_BUILD_DIR/$INFOPLIST_PATH", + $TARGET_BUILD_DIR/$INFOPLIST_PATH, ); name = "Upload source maps to Bugsnag"; outputFileListPaths = ( @@ -2664,7 +2664,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - "$PODS_CONFIGURATION_BUILD_DIR/Firebase", + $PODS_CONFIGURATION_BUILD_DIR/Firebase, ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -2740,7 +2740,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - "$PODS_CONFIGURATION_BUILD_DIR/Firebase", + $PODS_CONFIGURATION_BUILD_DIR/Firebase, ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index dbee5af49dc..b4833fe651c 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -108,5 +108,29 @@ UIViewControllerBasedStatusBarAppearance +UIApplicationShortcutItems + + + UIApplicationShortcutItemType + add-server + + UIApplicationShortcutItemTitle + Add Server + + UIApplicationShortcutItemIconType + UIApplicationShortcutIconTypeAdd + + + + UIApplicationShortcutItemType + search + + UIApplicationShortcutItemTitle + Search Rooms + + UIApplicationShortcutItemIconType + UIApplicationShortcutIconTypeSearch + + diff --git a/package.json b/package.json index d1d72997c88..5da437c019b 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "expo-local-authentication": "16.0.3", "expo-navigation-bar": "^4.2.4", "expo-notifications": "0.32.11", + "expo-quick-actions": "^6.0.0", "expo-status-bar": "^2.2.3", "expo-system-ui": "^5.0.7", "expo-video-thumbnails": "9.1.2", diff --git a/yarn.lock b/yarn.lock index 5f334cbad37..ba2d6210f9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8578,6 +8578,15 @@ expo-notifications@0.32.11: expo-application "~7.0.7" expo-constants "~18.0.8" +expo-quick-actions@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/expo-quick-actions/-/expo-quick-actions-6.0.0.tgz#57c2c664896fcb50190521cec8966c9373389356" + integrity sha512-jxfvuZSiBxlRAggMibfw17L6w/1xsleB1AKJUC8QWQmGZq099hET4ycvZK9szaX4hOk5hqbkTxSLxTCk8qmPLw== + dependencies: + "@expo/image-utils" "^0.8.7" + schema-utils "^4.3.2" + sf-symbols-typescript "^2.1.0" + expo-status-bar@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-2.2.3.tgz#09385a866732328e0af3b4588c4f349a15fd7cd0" @@ -13505,6 +13514,16 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" +schema-utils@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" + integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -13668,6 +13687,11 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +sf-symbols-typescript@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz#926d6e0715e3d8784cadf7658431e36581254208" + integrity sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw== + shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" From 214a4be773e15afab1d9e2ec425ef7ea77acd82f Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Wed, 7 Jan 2026 01:49:42 +0530 Subject: [PATCH 02/38] feat: added native deeplinking implementation for ios --- app/actions/actionsTypes.ts | 7 +++- app/actions/deepLinking.ts | 17 ++++++++ app/index.tsx | 22 ++++++++++- app/lib/quickActions/getInitialQuickAction.ts | 21 ++++++++++ app/reducers/quickActions.ts | 39 +++++++++++++++++++ app/sagas/index.js | 2 + app/sagas/quickActions.ts | 25 ++++++++++++ .../QuickActionsConnector.mm | 18 +++++++++ ios/AppDelegate.swift | 19 +++++++++ ios/QuickActionsConnector.h | 4 ++ ios/QuickActionsStore.h | 5 +++ ios/QuickActionsStore.mm | 7 ++++ ios/RocketChatRN-Bridging-Header.h | 1 + ios/RocketChatRN.xcodeproj/project.pbxproj | 16 ++++++++ 14 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 app/lib/quickActions/getInitialQuickAction.ts create mode 100644 app/reducers/quickActions.ts create mode 100644 app/sagas/quickActions.ts create mode 100644 ios/#import @interface QuickActionsConnector : NSObject @end #import ; } +interface IDeepLinkingQuickAction extends Action { + params: Partial; +} + export function deepLinkingOpen(params: Partial): IDeepLinkingOpen { return { type: DEEP_LINKING.OPEN, @@ -23,6 +32,14 @@ export function deepLinkingOpen(params: Partial): IDeepLinkingOpen { }; } +export function deepLinkingQuickAction(params: Partial): IDeepLinkingQuickAction { + console.log('call to deep linking quick actions'); + return { + type: DEEP_LINKING.QUICK_ACTION, + params + }; +} + export function deepLinkingClickCallPush(params: any): IDeepLinkingOpen { return { type: DEEP_LINKING.OPEN_VIDEO_CONF, diff --git a/app/index.tsx b/app/index.tsx index 88f8347445f..d0b2db618d5 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -9,7 +9,8 @@ import { KeyboardProvider } from 'react-native-keyboard-controller'; import ResponsiveLayoutProvider from './lib/hooks/useResponsiveLayout/useResponsiveLayout'; import AppContainer from './AppContainer'; import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app'; -import { deepLinkingOpen } from './actions/deepLinking'; +import { getInitialQuickAction } from './lib/quickActions/getInitialQuickAction'; +import { deepLinkingOpen, deepLinkingQuickAction } from './actions/deepLinking'; import { ActionSheetProvider } from './containers/ActionSheet'; import InAppNotification from './containers/InAppNotification'; import Loading from './containers/Loading'; @@ -61,6 +62,14 @@ interface IState { const parseDeepLinking = (url: string) => { if (url) { + if (url.startsWith('rocketchat://quick-action/')) { + const action = url.replace('rocketchat://quick-action/', ''); + return { + type: 'quick-action', + action + }; + } + url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, ''); const regex = /^(room|auth|invite|shareextension)\?/; const match = url.match(regex); @@ -111,7 +120,10 @@ export default class Root extends React.Component<{}, IState> { this.listenerTimeout = setTimeout(() => { Linking.addEventListener('url', ({ url }) => { const parsedDeepLinkingURL = parseDeepLinking(url); - if (parsedDeepLinkingURL) { + console.log(parsedDeepLinkingURL, 'deeplink url========================'); + if (parsedDeepLinkingURL?.type === 'quick-action') { + store.dispatch(deepLinkingQuickAction(parsedDeepLinkingURL)); + } else if (parsedDeepLinkingURL) { store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); } }); @@ -151,6 +163,12 @@ export default class Root extends React.Component<{}, IState> { // Open app from deep linking const deepLinking = await Linking.getInitialURL(); const parsedDeepLinkingURL = parseDeepLinking(deepLinking!); + const quickAction = await getInitialQuickAction(); + + if (quickAction) { + store.dispatch(deepLinkingQuickAction({ type: 'quick-action', action: quickAction })); + return; + } if (parsedDeepLinkingURL) { store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); return; diff --git a/app/lib/quickActions/getInitialQuickAction.ts b/app/lib/quickActions/getInitialQuickAction.ts new file mode 100644 index 00000000000..6ace0a3ce27 --- /dev/null +++ b/app/lib/quickActions/getInitialQuickAction.ts @@ -0,0 +1,21 @@ +import { NativeModules } from 'react-native'; + +const { QuickActionsConnector } = NativeModules; + +/** + * Reads the initial iOS quick action (cold / background launch). + * Returns the action type once, then clears it on native side. + */ +export async function getInitialQuickAction(): Promise { + console.log(QuickActionsConnector, 'connector======================='); + if (!QuickActionsConnector?.getInitialQuickAction) { + return null; + } + + try { + const action = await QuickActionsConnector.getInitialQuickAction(); + return action ?? null; + } catch { + return null; + } +} diff --git a/app/reducers/quickActions.ts b/app/reducers/quickActions.ts new file mode 100644 index 00000000000..fab69ff8acc --- /dev/null +++ b/app/reducers/quickActions.ts @@ -0,0 +1,39 @@ +import { type Action } from 'redux'; + +import { DEEP_LINKING } from '../actions/actionsTypes'; + +interface IQuickActionsState { + lastAction: string | null; + handled: boolean; +} +export const initialState: IQuickActionsState = { + lastAction: null, + handled: false +}; + +interface IQuickActionAction extends Action { + payload: { + action: string; + }; +} + +type QuickActionsAction = IQuickActionAction | Action; + +export default function quickActions(state = initialState, action: QuickActionsAction) { + switch (action.type) { + case DEEP_LINKING.QUICK_ACTION: + console.log('call to reducer quick action'); + return { + ...state, + lastAction: (action as IQuickActionAction).payload.action, + handled: false + }; + case DEEP_LINKING.QUICK_ACTION_HANDLED: + return { + ...state, + handled: true + }; + default: + return state; + } +} diff --git a/app/sagas/index.js b/app/sagas/index.js index b60cc211837..7a167f50204 100644 --- a/app/sagas/index.js +++ b/app/sagas/index.js @@ -15,10 +15,12 @@ import createDiscussion from './createDiscussion'; import encryption from './encryption'; import videoConf from './videoConf'; import troubleshootingNotification from './troubleshootingNotification'; +import quickActions from './quickActions'; const root = function* root() { yield all([ init(), + quickActions(), createChannel(), rooms(), room(), diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts new file mode 100644 index 00000000000..393d4b7c9a1 --- /dev/null +++ b/app/sagas/quickActions.ts @@ -0,0 +1,25 @@ +import { takeEvery } from 'redux-saga/effects'; +import { type Action } from 'redux'; + +import * as types from '../actions/actionsTypes'; +import { navigateToAddServer } from '../lib/navigation/addServer.ts'; +import store from '../lib/store'; + +interface IQuickActionOpen extends Action { + params: any; +} + +function* handleQuickActionOpen(action: IQuickActionOpen): Generator { + console.log(action.params.action); + switch (action.params.action) { + case 'add-server': + const state = store.getState(); + const server = state?.server?.server; + navigateToAddServer(server); + break; + } +} + +export default function* root(): Generator { + yield takeEvery(types.DEEP_LINKING.QUICK_ACTION, handleQuickActionOpen); +} diff --git a/ios/#import @interface QuickActionsConnector : NSObject @end #import @interface QuickActionsConnector : NSObject @end #import @interface QuickActionsConnector : NSObject @end #import Void + ) { + RCSetPendingQuickActionType(shortcutItem.type) + completionHandler(true) + } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { diff --git a/ios/QuickActionsConnector.h b/ios/QuickActionsConnector.h new file mode 100644 index 00000000000..9f6bc2446ad --- /dev/null +++ b/ios/QuickActionsConnector.h @@ -0,0 +1,4 @@ +#import + +@interface QuickActionsConnector : NSObject +@end diff --git a/ios/QuickActionsStore.h b/ios/QuickActionsStore.h new file mode 100644 index 00000000000..4c99b5771cd --- /dev/null +++ b/ios/QuickActionsStore.h @@ -0,0 +1,5 @@ +#import + +FOUNDATION_EXPORT NSString * _Nullable RCPendingQuickActionType; + +FOUNDATION_EXPORT void RCSetPendingQuickActionType(NSString * _Nullable type); diff --git a/ios/QuickActionsStore.mm b/ios/QuickActionsStore.mm new file mode 100644 index 00000000000..987362354fa --- /dev/null +++ b/ios/QuickActionsStore.mm @@ -0,0 +1,7 @@ +#import "QuickActionsStore.h" + +NSString * _Nullable RCPendingQuickActionType = nil; + +void RCSetPendingQuickActionType(NSString * _Nullable type) { + RCPendingQuickActionType = type; +} diff --git a/ios/RocketChatRN-Bridging-Header.h b/ios/RocketChatRN-Bridging-Header.h index 40e9146f471..20a4b23ff93 100644 --- a/ios/RocketChatRN-Bridging-Header.h +++ b/ios/RocketChatRN-Bridging-Header.h @@ -10,3 +10,4 @@ #import #import #import +#import "QuickActionsStore.h" diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index ae57baf9f3c..866c356dcd5 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -10,6 +10,10 @@ 0745F30D29A18A45DDDF8568 /* Pods_defaults_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5DA03EEFD8CEA0E9578CEFA /* Pods_defaults_NotificationService.framework */; }; 0C6E2DE448364EA896869ADF /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B37C79D9BD0742CE936B6982 /* libc++.tbd */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 16A6D54E2F0D95FC000FCD09 /* QuickActionsStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16A6D54D2F0D95FC000FCD09 /* QuickActionsStore.mm */; }; + 16A6D54F2F0D95FC000FCD09 /* QuickActionsStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16A6D54D2F0D95FC000FCD09 /* QuickActionsStore.mm */; }; + 16A6D5522F0D969B000FCD09 /* QuickActionsConnector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16A6D5512F0D969B000FCD09 /* QuickActionsConnector.mm */; }; + 16A6D5532F0D969B000FCD09 /* QuickActionsConnector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16A6D5512F0D969B000FCD09 /* QuickActionsConnector.mm */; }; 1E01C81C2511208400FEF824 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C81B2511208400FEF824 /* URL+Extensions.swift */; }; 1E01C8212511301400FEF824 /* PushResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C8202511301400FEF824 /* PushResponse.swift */; }; 1E01C8252511303100FEF824 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C8242511303100FEF824 /* Notification.swift */; }; @@ -469,6 +473,10 @@ 13B07F961A680F5B00A75B9A /* Rocket.Chat Experimental.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Rocket.Chat Experimental.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RocketChatRN/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RocketChatRN/Info.plist; sourceTree = ""; }; + 16A6D54C2F0D95E3000FCD09 /* QuickActionsStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuickActionsStore.h; sourceTree = ""; }; + 16A6D54D2F0D95FC000FCD09 /* QuickActionsStore.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = QuickActionsStore.mm; sourceTree = ""; }; + 16A6D5502F0D9674000FCD09 /* QuickActionsConnector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QuickActionsConnector.h; sourceTree = ""; }; + 16A6D5512F0D969B000FCD09 /* QuickActionsConnector.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = QuickActionsConnector.mm; path = "#import @interface QuickActionsConnector : NSObject @end #import Date: Wed, 7 Jan 2026 02:36:23 +0530 Subject: [PATCH 03/38] refactor: quickactions --- app/actions/actionsTypes.ts | 8 +--- app/actions/deepLinking.ts | 17 ------- app/actions/quickActions.ts | 18 ++++++++ app/index.tsx | 22 +++++---- app/lib/navigation/addServer.ts/index.ts | 8 ++-- app/lib/quickActions/getInitialQuickAction.ts | 2 +- app/lib/quickActions/index.ts | 42 +++++------------ app/lib/store/index.ts | 1 + app/reducers/quickActions.ts | 8 ++-- app/sagas/quickActions.ts | 45 ++++++++++++++----- 10 files changed, 86 insertions(+), 85 deletions(-) create mode 100644 app/actions/quickActions.ts diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index 994b13a98fa..7daec1d2d7c 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -59,12 +59,8 @@ export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DI export const LOGOUT = 'LOGOUT'; // logout is always success export const DELETE_ACCOUNT = 'DELETE_ACCOUNT'; export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']); -export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', [ - 'OPEN', - 'OPEN_VIDEO_CONF', - 'QUICK_ACTION', - 'QUICK_ACTION_HANDLED' -]); +export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN', 'OPEN_VIDEO_CONF']); +export const QUICK_ACTIONS = createRequestTypes('QUICK_ACTIONS', ['QUICK_ACTION_HANDLE', 'QUICK_ACTION_HANDLED']); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS'; export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'CLEAR']); diff --git a/app/actions/deepLinking.ts b/app/actions/deepLinking.ts index c8290d8aa35..7572cebe4ee 100644 --- a/app/actions/deepLinking.ts +++ b/app/actions/deepLinking.ts @@ -12,19 +12,10 @@ interface IParams { token: string; } -interface IQuickActionParams { - type: string; - action: string; -} - interface IDeepLinkingOpen extends Action { params: Partial; } -interface IDeepLinkingQuickAction extends Action { - params: Partial; -} - export function deepLinkingOpen(params: Partial): IDeepLinkingOpen { return { type: DEEP_LINKING.OPEN, @@ -32,14 +23,6 @@ export function deepLinkingOpen(params: Partial): IDeepLinkingOpen { }; } -export function deepLinkingQuickAction(params: Partial): IDeepLinkingQuickAction { - console.log('call to deep linking quick actions'); - return { - type: DEEP_LINKING.QUICK_ACTION, - params - }; -} - export function deepLinkingClickCallPush(params: any): IDeepLinkingOpen { return { type: DEEP_LINKING.OPEN_VIDEO_CONF, diff --git a/app/actions/quickActions.ts b/app/actions/quickActions.ts new file mode 100644 index 00000000000..35955fc3b40 --- /dev/null +++ b/app/actions/quickActions.ts @@ -0,0 +1,18 @@ +import { type Action } from 'redux'; + +import { QUICK_ACTIONS } from './actionsTypes'; + +interface IQuickActionParams { + type: string; + action: string; +} +interface IQuickAction extends Action { + params: Partial; +} +export function quickActionHandle(params: Partial): IQuickAction { + console.log('call to quick actions'); + return { + type: QUICK_ACTIONS.QUICK_ACTION_HANDLE, + params + }; +} diff --git a/app/index.tsx b/app/index.tsx index d0b2db618d5..65f53afab1d 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -9,8 +9,9 @@ import { KeyboardProvider } from 'react-native-keyboard-controller'; import ResponsiveLayoutProvider from './lib/hooks/useResponsiveLayout/useResponsiveLayout'; import AppContainer from './AppContainer'; import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app'; -import { getInitialQuickAction } from './lib/quickActions/getInitialQuickAction'; -import { deepLinkingOpen, deepLinkingQuickAction } from './actions/deepLinking'; +import { getRecentQuickAction } from './lib/quickActions/getInitialQuickAction'; +import { deepLinkingOpen } from './actions/deepLinking'; +import { quickActionHandle } from './actions/quickActions'; import { ActionSheetProvider } from './containers/ActionSheet'; import InAppNotification from './containers/InAppNotification'; import Loading from './containers/Loading'; @@ -118,12 +119,15 @@ export default class Root extends React.Component<{}, IState> { componentDidMount() { this.listenerTimeout = setTimeout(() => { - Linking.addEventListener('url', ({ url }) => { + Linking.addEventListener('url', async ({ url }) => { const parsedDeepLinkingURL = parseDeepLinking(url); - console.log(parsedDeepLinkingURL, 'deeplink url========================'); - if (parsedDeepLinkingURL?.type === 'quick-action') { - store.dispatch(deepLinkingQuickAction(parsedDeepLinkingURL)); - } else if (parsedDeepLinkingURL) { + const quickAction = await getRecentQuickAction(); + console.log(quickAction, 'inside the listerner========================='); + if (quickAction) { + store.dispatch(quickActionHandle({ type: 'quick-action', action: quickAction })); + return; + } + if (parsedDeepLinkingURL) { store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); } }); @@ -163,10 +167,10 @@ export default class Root extends React.Component<{}, IState> { // Open app from deep linking const deepLinking = await Linking.getInitialURL(); const parsedDeepLinkingURL = parseDeepLinking(deepLinking!); - const quickAction = await getInitialQuickAction(); + const quickAction = await getRecentQuickAction(); if (quickAction) { - store.dispatch(deepLinkingQuickAction({ type: 'quick-action', action: quickAction })); + store.dispatch(quickActionHandle({ type: 'quick-action', action: quickAction })); return; } if (parsedDeepLinkingURL) { diff --git a/app/lib/navigation/addServer.ts/index.ts b/app/lib/navigation/addServer.ts/index.ts index 33d1d177444..0afdfb1c392 100644 --- a/app/lib/navigation/addServer.ts/index.ts +++ b/app/lib/navigation/addServer.ts/index.ts @@ -3,14 +3,14 @@ import { batch } from 'react-redux'; import { appStart } from '../../../actions/app'; import { serverInitAdd } from '../../../actions/server'; import { RootEnum } from '../../../definitions'; -import store from '../../store'; import { logEvent, events } from '../../methods/helpers/log'; +import { type AppDispatch } from '../../store'; -export function navigateToAddServer(previousServer: string) { +export function navigateToAddServer(dispatch: AppDispatch, previousServer: string) { logEvent(events.RL_ADD_SERVER); batch(() => { - store.dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE })); - store.dispatch(serverInitAdd(previousServer)); + dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE })); + dispatch(serverInitAdd(previousServer)); }); } diff --git a/app/lib/quickActions/getInitialQuickAction.ts b/app/lib/quickActions/getInitialQuickAction.ts index 6ace0a3ce27..d9b22c54737 100644 --- a/app/lib/quickActions/getInitialQuickAction.ts +++ b/app/lib/quickActions/getInitialQuickAction.ts @@ -6,7 +6,7 @@ const { QuickActionsConnector } = NativeModules; * Reads the initial iOS quick action (cold / background launch). * Returns the action type once, then clears it on native side. */ -export async function getInitialQuickAction(): Promise { +export async function getRecentQuickAction(): Promise { console.log(QuickActionsConnector, 'connector======================='); if (!QuickActionsConnector?.getInitialQuickAction) { return null; diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index 02dc73b76b4..4548915b252 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -3,24 +3,19 @@ import { InteractionManager, AppState } from 'react-native'; import { navigateToAddServer } from '../navigation/addServer.ts'; import store from '../store'; - -let pendingQuickAction: string | null = null; -let quickActionConsumed = false; +import { getRecentQuickAction } from './getInitialQuickAction'; let registered = false; -let currentAppState = AppState.currentState; - -AppState.addEventListener('change', nextState => { - if (currentAppState === 'active' && nextState !== 'active') { - quickActionConsumed = false; - } - +AppState.addEventListener('change', async nextState => { if (nextState === 'active') { - consumePendingQuickAction(); + const nativeAction = await getRecentQuickAction(); + if (nativeAction) { + InteractionManager.runAfterInteractions(() => { + handleQuickAction(nativeAction); + }); + } } - - currentAppState = nextState; }); export function registerQuickActions() { @@ -29,6 +24,8 @@ export function registerQuickActions() { } registered = true; + console.log('quickactions registered======================='); + QuickActions.setItems([ { id: 'add-server', title: 'Add Server', icon: 'plus', href: '' }, { id: 'search', title: 'Search', icon: 'search', href: '' }, @@ -42,25 +39,6 @@ export function registerQuickActions() { return; } console.log('else block'); - - pendingQuickAction = action.id; - }); -} - -function consumePendingQuickAction() { - if (!pendingQuickAction || quickActionConsumed) { - return; - } - - quickActionConsumed = true; - - console.log('consume block'); - - const action = pendingQuickAction; - pendingQuickAction = null; - - InteractionManager.runAfterInteractions(() => { - handleQuickAction(action); }); } diff --git a/app/lib/store/index.ts b/app/lib/store/index.ts index 3ae917484ac..cfbc18a12e1 100644 --- a/app/lib/store/index.ts +++ b/app/lib/store/index.ts @@ -27,6 +27,7 @@ if (__DEV__) { } const store = createStore(reducers, enhancers); +export type AppDispatch = typeof store.dispatch; sagaMiddleware.run(sagas); export default store; diff --git a/app/reducers/quickActions.ts b/app/reducers/quickActions.ts index fab69ff8acc..4a2e3000b32 100644 --- a/app/reducers/quickActions.ts +++ b/app/reducers/quickActions.ts @@ -1,6 +1,6 @@ import { type Action } from 'redux'; -import { DEEP_LINKING } from '../actions/actionsTypes'; +import { QUICK_ACTIONS } from '../actions/actionsTypes'; interface IQuickActionsState { lastAction: string | null; @@ -11,7 +11,7 @@ export const initialState: IQuickActionsState = { handled: false }; -interface IQuickActionAction extends Action { +interface IQuickActionAction extends Action { payload: { action: string; }; @@ -21,14 +21,14 @@ type QuickActionsAction = IQuickActionAction | Action; export default function quickActions(state = initialState, action: QuickActionsAction) { switch (action.type) { - case DEEP_LINKING.QUICK_ACTION: + case QUICK_ACTIONS.QUICK_ACTION_HANDLE: console.log('call to reducer quick action'); return { ...state, lastAction: (action as IQuickActionAction).payload.action, handled: false }; - case DEEP_LINKING.QUICK_ACTION_HANDLED: + case QUICK_ACTIONS.QUICK_ACTION_HANDLED: return { ...state, handled: true diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 393d4b7c9a1..c5839a8d2d9 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -1,25 +1,46 @@ -import { takeEvery } from 'redux-saga/effects'; +import { select, takeEvery, put, take } from 'redux-saga/effects'; import { type Action } from 'redux'; -import * as types from '../actions/actionsTypes'; -import { navigateToAddServer } from '../lib/navigation/addServer.ts'; -import store from '../lib/store'; +import { QUICK_ACTIONS, APP } from '../actions/actionsTypes'; +import { appStart } from '../actions/app'; +import { serverInitAdd } from '../actions/server'; +import { RootEnum } from '../definitions'; interface IQuickActionOpen extends Action { - params: any; + params?: { + action?: string; + }; + payload?: { + action?: string; + }; } function* handleQuickActionOpen(action: IQuickActionOpen): Generator { - console.log(action.params.action); - switch (action.params.action) { - case 'add-server': - const state = store.getState(); - const server = state?.server?.server; - navigateToAddServer(server); + // yield take(APP.READY); + + // const state = yield select(); + // if (!state?.quickActions || state.quickActions.handled) { + // return; + // } + const quickAction = action.params?.action ?? action.payload?.action; + + if (!quickAction) { + return; + } + + switch (quickAction) { + case 'add-server': { + const server = yield select(state => state?.server?.server); + console.log('it reached here=========='); + yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); + yield put(serverInitAdd(server)); break; + } } + + yield put({ type: QUICK_ACTIONS.QUICK_ACTION_HANDLED }); } export default function* root(): Generator { - yield takeEvery(types.DEEP_LINKING.QUICK_ACTION, handleQuickActionOpen); + yield takeEvery(QUICK_ACTIONS.QUICK_ACTION_HANDLE, handleQuickActionOpen); } From 8ec0c3b6580192d60317fdf8f62d6f28aa6a8650 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Wed, 7 Jan 2026 02:41:55 +0530 Subject: [PATCH 04/38] fixed navigation in expo managed quick actions --- app/lib/quickActions/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index 4548915b252..12717322aad 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -1,9 +1,9 @@ import * as QuickActions from 'expo-quick-actions'; import { InteractionManager, AppState } from 'react-native'; -import { navigateToAddServer } from '../navigation/addServer.ts'; import store from '../store'; import { getRecentQuickAction } from './getInitialQuickAction'; +import { quickActionHandle } from '../../actions/quickActions'; let registered = false; @@ -45,9 +45,11 @@ export function registerQuickActions() { function handleQuickAction(id: string) { switch (id) { case 'add-server': - const state = store.getState(); - const server = state?.server?.server; - navigateToAddServer(server); + store.dispatch( + quickActionHandle({ + action: 'add-server' + }) + ); break; case 'search': From f22a8107ec0d49bc3eb302f3f4c08210dac25451 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Wed, 7 Jan 2026 16:29:08 +0530 Subject: [PATCH 05/38] feat: quick actions android --- .../chat/rocket/reactnative/MainActivity.kt | 106 +++++++++++++++--- app/index.tsx | 9 +- app/lib/quickActions/index.ts | 2 +- app/sagas/quickActions.ts | 3 + 4 files changed, 102 insertions(+), 18 deletions(-) diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt index 502ae26784f..41888db4eb2 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt @@ -1,5 +1,5 @@ package chat.rocket.reactnative - + import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled @@ -9,18 +9,19 @@ import android.os.Bundle import com.zoontek.rnbootsplash.RNBootSplash import android.content.Intent import android.content.res.Configuration +import android.net.Uri import chat.rocket.reactnative.notification.VideoConfModule import chat.rocket.reactnative.notification.VideoConfNotification import com.google.gson.GsonBuilder - + class MainActivity : ReactActivity() { - + /** * Returns the name of the main component registered from JavaScript. This is used to schedule * rendering of the component. */ override fun getMainComponentName(): String = "RocketChatRN" - + /** * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] @@ -29,19 +30,26 @@ class MainActivity : ReactActivity() { DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) override fun onCreate(savedInstanceState: Bundle?) { + intent?.let { + embedQuickActionIntoLinking(it) + } + RNBootSplash.init(this, R.style.BootTheme) super.onCreate(null) - + // Handle video conf action from notification - intent?.let { handleVideoConfIntent(it) } + intent?.let { + handleVideoConfIntent(it) + } } - + public override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) // Handle video conf action when activity is already running + embedQuickActionIntoLinking(intent) handleVideoConfIntent(intent) } - + private fun handleVideoConfIntent(intent: Intent) { if (intent.getBooleanExtra("videoConfAction", false)) { val notificationId = intent.getIntExtra("notificationId", 0) @@ -51,14 +59,14 @@ class MainActivity : ReactActivity() { val callerName = intent.getStringExtra("callerName") ?: "" val host = intent.getStringExtra("host") ?: "" val callId = intent.getStringExtra("callId") ?: "" - + android.util.Log.d("RocketChat.MainActivity", "Handling video conf intent - event: $event, rid: $rid, host: $host, callId: $callId") - + // Cancel the notification if (notificationId != 0) { VideoConfNotification.cancelById(this, notificationId) } - + // Store action for JS to pick up - include all required fields val data = mapOf( "notificationType" to "videoconf", @@ -71,19 +79,87 @@ class MainActivity : ReactActivity() { "name" to callerName ) ) - + val gson = GsonBuilder().create() val jsonData = gson.toJson(data) - + android.util.Log.d("RocketChat.MainActivity", "Storing video conf action: $jsonData") - + VideoConfModule.storePendingAction(this, jsonData) - + // Clear the video conf flag to prevent re-processing intent.removeExtra("videoConfAction") } } + private fun embedQuickActionIntoLinking(intent: Intent) { + val action = intent.action ?: return + + android.util.Log.d("RocketChat.QuickAction", "Original action: $action") + + // Handle Expo quick actions + if (action == "expo.modules.quickactions.SHORTCUT") { + // Get the PersistableBundle + val shortcutData = intent.getParcelableExtra("shortcut_data") + + if (shortcutData != null) { + // Log all keys in the bundle + // kept for debugging later + // android.util.Log.d("RocketChat.QuickAction", "=== Shortcut Data Bundle ===") + // for (key in shortcutData.keySet()) { + // val value = shortcutData.get(key) + // android.util.Log.d("RocketChat.QuickAction", "Key: $key, Value: $value") + // } + // android.util.Log.d("RocketChat.QuickAction", "============================") + + // Try to get the shortcut ID from various possible keys + val shortcutId = shortcutData.getString("id") + ?: shortcutData.getString("shortcutId") + ?: shortcutData.getString("android.intent.extra.shortcut.ID") + + android.util.Log.d("RocketChat.QuickAction", "Expo shortcut ID: $shortcutId") + + if (shortcutId != null) { + val uri = Uri.parse("rocketchat://quick-action/$shortcutId") + + android.util.Log.d("RocketChat.QuickAction", "Converted to: $uri") + + intent.action = Intent.ACTION_VIEW + intent.data = uri + setIntent(intent) + + android.util.Log.d("RocketChat.QuickAction", "Intent set with data: ${getIntent().data}") + } + } else { + android.util.Log.d("RocketChat.QuickAction", "No shortcut_data bundle found") + } + return + } + + // Handle non-Expo quick actions + // we does not need this as currently we are setting it from expo only + // TODO: remove later + if (!action.startsWith("chat.rocket.reactnative.")) { + android.util.Log.d("RocketChat.QuickAction", "Not a quick action, skipping") + return + } + + val quickAction = action + .removePrefix("chat.rocket.reactnative.") + .lowercase() + .replace('_', '-') + + val uri = Uri.parse("rocketchat://quick-action/$quickAction") + + android.util.Log.d("RocketChat.QuickAction", "Converted to: $uri") + + intent.action = Intent.ACTION_VIEW + intent.data = uri + setIntent(intent) + + android.util.Log.d("RocketChat.QuickAction", "Intent set with data: ${getIntent().data}") + } + override fun invokeDefaultOnBackPressed() { moveTaskToBack(true) } diff --git a/app/index.tsx b/app/index.tsx index 65f53afab1d..5010e6d5ffa 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -123,8 +123,8 @@ export default class Root extends React.Component<{}, IState> { const parsedDeepLinkingURL = parseDeepLinking(url); const quickAction = await getRecentQuickAction(); console.log(quickAction, 'inside the listerner========================='); - if (quickAction) { - store.dispatch(quickActionHandle({ type: 'quick-action', action: quickAction })); + if (parsedDeepLinkingURL?.type === 'quick-action') { + store.dispatch(quickActionHandle({ type: 'quick-action', action: parsedDeepLinkingURL.action })); return; } if (parsedDeepLinkingURL) { @@ -168,11 +168,16 @@ export default class Root extends React.Component<{}, IState> { const deepLinking = await Linking.getInitialURL(); const parsedDeepLinkingURL = parseDeepLinking(deepLinking!); const quickAction = await getRecentQuickAction(); + console.log(deepLinking, 'deeplinking========================'); if (quickAction) { store.dispatch(quickActionHandle({ type: 'quick-action', action: quickAction })); return; } + if (parsedDeepLinkingURL?.type === 'quick-action') { + store.dispatch(quickActionHandle({ type: 'quick-action', action: parsedDeepLinkingURL.action })); + return; + } if (parsedDeepLinkingURL) { store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); return; diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index 12717322aad..305a52ff650 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -38,7 +38,7 @@ export function registerQuickActions() { console.log('return block'); return; } - console.log('else block'); + handleQuickAction(action.id); }); } diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index c5839a8d2d9..29a3fbbe646 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -1,4 +1,5 @@ import { select, takeEvery, put, take } from 'redux-saga/effects'; +import { Alert } from 'react-native'; import { type Action } from 'redux'; import { QUICK_ACTIONS, APP } from '../actions/actionsTypes'; @@ -36,6 +37,8 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { yield put(serverInitAdd(server)); break; } + default: + Alert.alert('Other Quick Action', `this is ${quickAction} action`); } yield put({ type: QUICK_ACTIONS.QUICK_ACTION_HANDLED }); From 6bbdd4ca6bdc8be2ca330c4db9be8fd6ec21ab12 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Thu, 8 Jan 2026 13:57:56 +0530 Subject: [PATCH 06/38] feat: added prev server --- app/actions/actionsTypes.ts | 8 +- .../{addServer.ts => addServer}/index.ts | 0 app/reducers/quickActions.ts | 39 ++- app/sagas/quickActions.ts | 7 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 246 +----------------- 5 files changed, 59 insertions(+), 241 deletions(-) rename app/lib/navigation/{addServer.ts => addServer}/index.ts (100%) diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index 7daec1d2d7c..ca7a552dc41 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -60,7 +60,13 @@ export const LOGOUT = 'LOGOUT'; // logout is always success export const DELETE_ACCOUNT = 'DELETE_ACCOUNT'; export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']); export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN', 'OPEN_VIDEO_CONF']); -export const QUICK_ACTIONS = createRequestTypes('QUICK_ACTIONS', ['QUICK_ACTION_HANDLE', 'QUICK_ACTION_HANDLED']); +export const QUICK_ACTIONS = createRequestTypes('QUICK_ACTIONS', [ + 'QUICK_ACTION_HANDLE', + 'QUICK_ACTION_HANDLED', + 'SET_PENDING_QUICK_ACTION', + 'CLEAR_PENDING', + 'MARK_LAUNCHED_FROM_QUICK_ACTION' +]); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS'; export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'CLEAR']); diff --git a/app/lib/navigation/addServer.ts/index.ts b/app/lib/navigation/addServer/index.ts similarity index 100% rename from app/lib/navigation/addServer.ts/index.ts rename to app/lib/navigation/addServer/index.ts diff --git a/app/reducers/quickActions.ts b/app/reducers/quickActions.ts index 4a2e3000b32..792a62ddb34 100644 --- a/app/reducers/quickActions.ts +++ b/app/reducers/quickActions.ts @@ -5,34 +5,61 @@ import { QUICK_ACTIONS } from '../actions/actionsTypes'; interface IQuickActionsState { lastAction: string | null; handled: boolean; + pending: string | null; + launchedFromQuickAction: boolean; } + export const initialState: IQuickActionsState = { lastAction: null, - handled: false + handled: false, + pending: null, + launchedFromQuickAction: false }; -interface IQuickActionAction extends Action { +interface IQuickActionPayloadAction extends Action { payload: { action: string; }; } -type QuickActionsAction = IQuickActionAction | Action; +type QuickActionsAction = IQuickActionPayloadAction | Action; -export default function quickActions(state = initialState, action: QuickActionsAction) { +export default function quickActions(state = initialState, action: QuickActionsAction): IQuickActionsState { switch (action.type) { case QUICK_ACTIONS.QUICK_ACTION_HANDLE: console.log('call to reducer quick action'); return { ...state, - lastAction: (action as IQuickActionAction).payload.action, + lastAction: (action as IQuickActionPayloadAction).payload.action, handled: false }; + + case QUICK_ACTIONS.SET_PENDING_QUICK_ACTION: + return { + ...state, + pending: (action as IQuickActionPayloadAction).payload.action + }; + case QUICK_ACTIONS.QUICK_ACTION_HANDLED: return { ...state, - handled: true + handled: true, + pending: null }; + + case QUICK_ACTIONS.MARK_LAUNCHED_FROM_QUICK_ACTION: + return { + ...state, + launchedFromQuickAction: true + }; + + case QUICK_ACTIONS.CLEAR_PENDING: + return { + ...state, + pending: null, + launchedFromQuickAction: false + }; + default: return state; } diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 29a3fbbe646..252f96e822b 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -6,6 +6,8 @@ import { QUICK_ACTIONS, APP } from '../actions/actionsTypes'; import { appStart } from '../actions/app'; import { serverInitAdd } from '../actions/server'; import { RootEnum } from '../definitions'; +import UserPreferences from '../lib/methods/userPreferences'; +import { CURRENT_SERVER } from '../lib/constants/keys'; interface IQuickActionOpen extends Action { params?: { @@ -31,10 +33,11 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { switch (quickAction) { case 'add-server': { - const server = yield select(state => state?.server?.server); + const server = UserPreferences.getString(CURRENT_SERVER); + console.log('it reached here=========='); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); - yield put(serverInitAdd(server)); + yield put(serverInitAdd(server || '')); break; } default: diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index e4fc3f47e2d..57a4b818022 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -1390,7 +1390,7 @@ 7AAB3E48257E6A6E00707CF6 /* Embed App Extensions */, 1ED1ECE32B8699DD00F6620C /* Embed Watch Content */, 6E2B7753E1396AE4448B35AE /* [CP] Embed Pods Frameworks */, - 3070587F81C003406057D006 /* [CP] Copy Pods Resources */, + 6931B3875D0CA69F22785138 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1603,102 +1603,25 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 4EF35507D275D88665224EED /* [CP] Copy Pods Resources */ = { + 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/BugsnagReactNative/Bugsnag.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Bugsnag.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", + "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 589729E8381BA997CD19EF19 /* [Expo] Configure project */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-RocketChatRN/expo-configure-project.sh\"\n"; - }; - 69EE0EAB4655CCB0698B6026 /* [CP] Embed Pods Frameworks */ = { + 4CCE5B7235CA003F286BD050 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1716,112 +1639,26 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - $TARGET_BUILD_DIR/$INFOPLIST_PATH, - ); - name = "Upload source maps to Bugsnag"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n"; - }; - 7AA5C63E23E30D110005C4A7 /* Start Packager */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Start Packager"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; - }; - 7AAB3E13257E6A6E00707CF6 /* Start Packager */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Start Packager"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; - }; - 7AAB3E46257E6A6E00707CF6 /* Bundle React Native code and images */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 12; - files = ( - ); - inputPaths = ( - ); - name = "Bundle React Native code and images"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; - }; - 7AAB3E4B257E6A6E00707CF6 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"Target architectures: $ARCHS\"\n\nAPP_PATH=\"${TARGET_BUILD_DIR}/${WRAPPER_NAME}\"\n\nfind \"$APP_PATH\" -name '*.framework' -type d | while read -r FRAMEWORK\ndo\nFRAMEWORK_EXECUTABLE_NAME=$(defaults read \"$FRAMEWORK/Info.plist\" CFBundleExecutable)\nFRAMEWORK_EXECUTABLE_PATH=\"$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME\"\necho \"Executable is $FRAMEWORK_EXECUTABLE_PATH\"\necho $(lipo -info \"$FRAMEWORK_EXECUTABLE_PATH\")\n\nFRAMEWORK_TMP_PATH=\"$FRAMEWORK_EXECUTABLE_PATH-tmp\"\n\n# remove simulator's archs if location is not simulator's directory\ncase \"${TARGET_BUILD_DIR}\" in\n*\"iphonesimulator\")\necho \"No need to remove archs\"\n;;\n*)\nif $(lipo \"$FRAMEWORK_EXECUTABLE_PATH\" -verify_arch \"i386\") ; then\nlipo -output \"$FRAMEWORK_TMP_PATH\" -remove \"i386\" \"$FRAMEWORK_EXECUTABLE_PATH\"\necho \"i386 architecture removed\"\nrm \"$FRAMEWORK_EXECUTABLE_PATH\"\nmv \"$FRAMEWORK_TMP_PATH\" \"$FRAMEWORK_EXECUTABLE_PATH\"\nfi\nif $(lipo \"$FRAMEWORK_EXECUTABLE_PATH\" -verify_arch \"x86_64\") ; then\nlipo -output \"$FRAMEWORK_TMP_PATH\" -remove \"x86_64\" \"$FRAMEWORK_EXECUTABLE_PATH\"\necho \"x86_64 architecture removed\"\nrm \"$FRAMEWORK_EXECUTABLE_PATH\"\nmv \"$FRAMEWORK_TMP_PATH\" \"$FRAMEWORK_EXECUTABLE_PATH\"\nfi\n;;\nesac\n\necho \"Completed for executable $FRAMEWORK_EXECUTABLE_PATH\"\necho $\n\ndone\n"; - }; - 7AAE9EB32891A0D20024F559 /* Upload source maps to Bugsnag */ = { + 589729E8381BA997CD19EF19 /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( - $TARGET_BUILD_DIR/$INFOPLIST_PATH, ); - name = "Upload source maps to Bugsnag"; + name = "[Expo] Configure project"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n"; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-RocketChatRN/expo-configure-project.sh\"\n"; }; - 7B5EE97580C4626E59AEA53C /* [CP] Copy Pods Resources */ = { + 6931B3875D0CA69F22785138 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1897,61 +1734,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 4CCE5B7235CA003F286BD050 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 589729E8381BA997CD19EF19 /* [Expo] Configure project */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-RocketChatRN/expo-configure-project.sh\"\n"; - }; 69520DF942793F987B1AA05B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2054,7 +1836,7 @@ inputFileListPaths = ( ); inputPaths = ( - "$TARGET_BUILD_DIR/$INFOPLIST_PATH", + $TARGET_BUILD_DIR/$INFOPLIST_PATH, ); name = "Upload source maps to Bugsnag"; outputFileListPaths = ( From 26c02ecdec93186f85fef177d39c1432ffe56b14 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Fri, 9 Jan 2026 01:28:41 +0530 Subject: [PATCH 07/38] feat: search --- .../main/res/drawable/ic_quickaction_add.xml | 5 +++ .../main/res/drawable/ic_quickaction_find.xml | 5 +++ .../res/drawable/ic_quickaction_recent.xml | 9 ++++ app/actions/actionsTypes.ts | 1 + app/definitions/redux/index.ts | 2 + app/lib/quickActions/index.ts | 38 ++++++++++++++--- app/reducers/index.js | 4 ++ app/reducers/quickActions.ts | 6 +++ app/reducers/ui.ts | 29 +++++++++++++ app/sagas/quickActions.ts | 25 +++++++++-- app/views/RoomsListView/hooks/useSearch.ts | 24 +++++++++-- ios/RocketChatRN/Info.plist | 42 ++++--------------- 12 files changed, 144 insertions(+), 46 deletions(-) create mode 100644 android/app/src/main/res/drawable/ic_quickaction_add.xml create mode 100644 android/app/src/main/res/drawable/ic_quickaction_find.xml create mode 100644 android/app/src/main/res/drawable/ic_quickaction_recent.xml create mode 100644 app/reducers/ui.ts diff --git a/android/app/src/main/res/drawable/ic_quickaction_add.xml b/android/app/src/main/res/drawable/ic_quickaction_add.xml new file mode 100644 index 00000000000..ab03689d18d --- /dev/null +++ b/android/app/src/main/res/drawable/ic_quickaction_add.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_quickaction_find.xml b/android/app/src/main/res/drawable/ic_quickaction_find.xml new file mode 100644 index 00000000000..a0e11aa0f02 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_quickaction_find.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_quickaction_recent.xml b/android/app/src/main/res/drawable/ic_quickaction_recent.xml new file mode 100644 index 00000000000..f18f24197e2 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_quickaction_recent.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index ca7a552dc41..2bcef9e3af0 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -67,6 +67,7 @@ export const QUICK_ACTIONS = createRequestTypes('QUICK_ACTIONS', [ 'CLEAR_PENDING', 'MARK_LAUNCHED_FROM_QUICK_ACTION' ]); +export const UI = createRequestTypes('UI', ['TRIGGER_SEARCH', 'CLEAR_TRIGGERED_SEARCH']); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS'; export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'CLEAR']); diff --git a/app/definitions/redux/index.ts b/app/definitions/redux/index.ts index f32d7041e69..002699268a2 100644 --- a/app/definitions/redux/index.ts +++ b/app/definitions/redux/index.ts @@ -48,6 +48,7 @@ import { type IRooms } from '../../reducers/rooms'; import { type IPreferences } from '../IPreferences'; import { type ICustomEmojis } from '../IEmoji'; import { type IUsersTyping } from '../../reducers/usersTyping'; +import { type IUIState } from '../../reducers/ui'; export interface IApplicationState { settings: TSettingsState; @@ -76,6 +77,7 @@ export interface IApplicationState { troubleshootingNotification: ITroubleshootingNotification; supportedVersions: ISupportedVersionsState; inAppFeedback: IInAppFeedbackState; + ui: IUIState; } export type TApplicationActions = TActionActiveUsers & diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index 305a52ff650..08c656e3b2a 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -1,5 +1,5 @@ import * as QuickActions from 'expo-quick-actions'; -import { InteractionManager, AppState } from 'react-native'; +import { InteractionManager, AppState, Platform } from 'react-native'; import store from '../store'; import { getRecentQuickAction } from './getInitialQuickAction'; @@ -27,9 +27,27 @@ export function registerQuickActions() { console.log('quickactions registered======================='); QuickActions.setItems([ - { id: 'add-server', title: 'Add Server', icon: 'plus', href: '' }, - { id: 'search', title: 'Search', icon: 'search', href: '' }, - { id: 'recent', title: 'Recent Rooms', icon: 'clock', href: '' } + { + id: 'add-server', + title: 'Add Server', + icon: Platform.select({ ios: 'symbol:plus', android: 'ic_quickaction_add' }), + href: '' + }, + { + id: 'search', + title: 'Search', + icon: Platform.select({ ios: 'symbol:magnifyingglass', android: 'ic_quickaction_find' }), + href: '' + }, + { + id: 'recent', + title: 'Recent Rooms', + icon: Platform.select({ + ios: 'symbol:clock.arrow.trianglehead.counterclockwise.rotate.90', + android: 'ic_quickaction_recent' + }), + href: '' + } ]); QuickActions.addListener(action => { @@ -53,11 +71,19 @@ function handleQuickAction(id: string) { break; case 'search': - console.log('search ========================='); + store.dispatch( + quickActionHandle({ + action: 'search' + }) + ); break; case 'recent': - console.log('recent ========================='); + store.dispatch( + quickActionHandle({ + action: 'recent' + }) + ); break; } } diff --git a/app/reducers/index.js b/app/reducers/index.js index 4258685f94f..3653d31f0b1 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -26,6 +26,8 @@ import usersRoles from './usersRoles'; import troubleshootingNotification from './troubleshootingNotification'; import supportedVersions from './supportedVersions'; import inAppFeedback from './inAppFeedback'; +import quickActions from './quickActions'; +import ui from './ui'; export default combineReducers({ settings, @@ -35,8 +37,10 @@ export default combineReducers({ selectedUsers, createChannel, app, + ui, room, rooms, + quickActions, sortPreferences, share, customEmojis, diff --git a/app/reducers/quickActions.ts b/app/reducers/quickActions.ts index 792a62ddb34..03c0fea5a07 100644 --- a/app/reducers/quickActions.ts +++ b/app/reducers/quickActions.ts @@ -28,6 +28,9 @@ export default function quickActions(state = initialState, action: QuickActionsA switch (action.type) { case QUICK_ACTIONS.QUICK_ACTION_HANDLE: console.log('call to reducer quick action'); + if (!('payload' in action) || !action.payload?.action) { + return state; + } return { ...state, lastAction: (action as IQuickActionPayloadAction).payload.action, @@ -35,6 +38,9 @@ export default function quickActions(state = initialState, action: QuickActionsA }; case QUICK_ACTIONS.SET_PENDING_QUICK_ACTION: + if (!('payload' in action) || !action.payload?.action) { + return state; + } return { ...state, pending: (action as IQuickActionPayloadAction).payload.action diff --git a/app/reducers/ui.ts b/app/reducers/ui.ts new file mode 100644 index 00000000000..fc357bc1509 --- /dev/null +++ b/app/reducers/ui.ts @@ -0,0 +1,29 @@ +import { type Action } from 'redux'; + +import { UI } from '../actions/actionsTypes'; + +export interface IUIState { + triggerSearch: boolean; +} + +export const initialState: IUIState = { + triggerSearch: false +}; + +export default function ui(state = initialState, action: Action): IUIState { + console.log(action, 'action========================'); + switch (action.type) { + case UI.TRIGGER_SEARCH: + return { + ...state, + triggerSearch: true + }; + case UI.CLEAR_TRIGGERED_SEARCH: + return { + ...state, + triggerSearch: false + }; + default: + return state; + } +} diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 252f96e822b..32d17361f4d 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -1,13 +1,14 @@ -import { select, takeEvery, put, take } from 'redux-saga/effects'; +import { select, takeEvery, put, take, type Effect } from 'redux-saga/effects'; import { Alert } from 'react-native'; import { type Action } from 'redux'; -import { QUICK_ACTIONS, APP } from '../actions/actionsTypes'; -import { appStart } from '../actions/app'; +import { QUICK_ACTIONS, APP, UI } from '../actions/actionsTypes'; +import { appStart, appInit } from '../actions/app'; import { serverInitAdd } from '../actions/server'; import { RootEnum } from '../definitions'; import UserPreferences from '../lib/methods/userPreferences'; import { CURRENT_SERVER } from '../lib/constants/keys'; +import Navigation from '../lib/navigation/appNavigation'; interface IQuickActionOpen extends Action { params?: { @@ -18,6 +19,15 @@ interface IQuickActionOpen extends Action { }; } +function* waitForAppReady(): Generator { + const isReady: boolean = yield select((state: any) => state.app.ready); + + if (!isReady) { + yield put(appInit()); + yield take(APP.READY); + } +} + function* handleQuickActionOpen(action: IQuickActionOpen): Generator { // yield take(APP.READY); @@ -40,6 +50,15 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { yield put(serverInitAdd(server || '')); break; } + case 'search': + yield waitForAppReady(); + const currentRoute = Navigation.getCurrentRoute(); + + if (currentRoute?.name !== 'RoomsListView') { + Navigation.navigate('RoomsListView'); + } + yield put({ type: UI.TRIGGER_SEARCH }); + break; default: Alert.alert('Other Quick Action', `this is ${quickAction} action`); } diff --git a/app/views/RoomsListView/hooks/useSearch.ts b/app/views/RoomsListView/hooks/useSearch.ts index 97c7a0d2a12..35ac4a8169a 100644 --- a/app/views/RoomsListView/hooks/useSearch.ts +++ b/app/views/RoomsListView/hooks/useSearch.ts @@ -1,10 +1,13 @@ -import { useCallback, useReducer } from 'react'; +import { useCallback, useEffect, useReducer } from 'react'; import { AccessibilityInfo } from 'react-native'; +import { useDispatch } from 'react-redux'; import { type IRoomItem } from '../../../containers/RoomItem/interfaces'; import { search as searchLib } from '../../../lib/methods/search'; import { useDebounce } from '../../../lib/methods/helpers/debounce'; import i18n from '../../../i18n'; +import { useAppSelector } from '../../../lib/hooks/useAppSelector'; +import { UI } from '../../../actions/actionsTypes'; interface SearchState { searchEnabled: boolean; @@ -59,7 +62,8 @@ export const useSearch = () => { 'use memo'; const [state, dispatch] = useReducer(searchReducer, initialState); - + const triggerSearch = useAppSelector(state => state.ui?.triggerSearch); + const storeDispatch = useDispatch(); const announceSearchResultsForAccessibility = (count: number) => { if (count < 1) { AccessibilityInfo.announceForAccessibility(i18n.t('No_results_found')); @@ -80,13 +84,25 @@ export const useSearch = () => { const startSearch = useCallback(() => { dispatch({ type: 'START_SEARCH' }); - search(''); - }, [search]); + }, []); const stopSearch = useCallback(() => { dispatch({ type: 'STOP_SEARCH' }); }, []); + useEffect(() => { + if (triggerSearch) { + startSearch(); + storeDispatch({ type: UI.CLEAR_TRIGGERED_SEARCH }); + } + }, [startSearch, storeDispatch, triggerSearch]); + + useEffect(() => { + if (state.searchEnabled) { + search(''); + } + }, [state.searchEnabled, search]); + return { searching: state.searching, searchEnabled: state.searchEnabled, diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index 6ee8d9fc42b..57d83fd56d1 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -11,8 +11,8 @@ apiKey apiKeyValue - CADisableMinimumFrameDurationOnPhone - + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion en CFBundleDisplayName @@ -60,13 +60,13 @@ LSRequiresIPhoneOS - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSAllowsLocalNetworking - - + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + NSCameraUsageDescription Take photos to share with other users NSFaceIDUsageDescription @@ -108,30 +108,6 @@ UIViewControllerBasedStatusBarAppearance -UIApplicationShortcutItems - - - UIApplicationShortcutItemType - add-server - - UIApplicationShortcutItemTitle - Add Server - - UIApplicationShortcutItemIconType - UIApplicationShortcutIconTypeAdd - - - - UIApplicationShortcutItemType - search - - UIApplicationShortcutItemTitle - Search Rooms - - UIApplicationShortcutItemIconType - UIApplicationShortcutIconTypeSearch - - NSUserActivityTypes INSendMessageIntent From 3f56cdf18a35daaf785202a733e93f23382ee633 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Fri, 9 Jan 2026 02:06:53 +0530 Subject: [PATCH 08/38] refactor: folders in ios --- .../QuickActionsConnector.mm | 0 ios/RocketChatRN.xcodeproj/project.pbxproj | 22 +++++++------------ 2 files changed, 8 insertions(+), 14 deletions(-) rename ios/{#import @interface QuickActionsConnector : NSObject @end #import }/QuickActionsConnector.mm (100%) diff --git a/ios/#import @interface QuickActionsConnector : NSObject @end #import @interface QuickActionsConnector : NSObject @end #import Date: Fri, 9 Jan 2026 17:00:09 +0530 Subject: [PATCH 09/38] feat: contact us option --- .../res/drawable/ic_quickaction_contact.xml | 5 ++ app/lib/quickActions/index.ts | 53 ++++++++----------- app/sagas/quickActions.ts | 5 ++ app/views/SettingsView/index.tsx | 32 +++++------ 4 files changed, 48 insertions(+), 47 deletions(-) create mode 100644 android/app/src/main/res/drawable/ic_quickaction_contact.xml diff --git a/android/app/src/main/res/drawable/ic_quickaction_contact.xml b/android/app/src/main/res/drawable/ic_quickaction_contact.xml new file mode 100644 index 00000000000..2e92649f3bc --- /dev/null +++ b/android/app/src/main/res/drawable/ic_quickaction_contact.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index 08c656e3b2a..cd361fcf933 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -27,18 +27,18 @@ export function registerQuickActions() { console.log('quickactions registered======================='); QuickActions.setItems([ - { - id: 'add-server', - title: 'Add Server', - icon: Platform.select({ ios: 'symbol:plus', android: 'ic_quickaction_add' }), - href: '' - }, { id: 'search', title: 'Search', icon: Platform.select({ ios: 'symbol:magnifyingglass', android: 'ic_quickaction_find' }), href: '' }, + { + id: 'add-server', + title: 'Add Server', + icon: Platform.select({ ios: 'symbol:plus', android: 'ic_quickaction_add' }), + href: '' + }, { id: 'recent', title: 'Recent Rooms', @@ -47,6 +47,17 @@ export function registerQuickActions() { android: 'ic_quickaction_recent' }), href: '' + }, + { + id: 'contact', + title: Platform.select({ android: 'Contact us', ios: 'Something wrong?' }) ?? 'Contact us', + subtitle: "We're here to help", // ios only + icon: Platform.select({ + // ios: 'symbol:person.crop.circle.badge.questionmark', // alternate option i liked + ios: 'symbol:envelope', + android: 'ic_quickaction_contact' + }), + href: '' } ]); @@ -61,29 +72,9 @@ export function registerQuickActions() { } function handleQuickAction(id: string) { - switch (id) { - case 'add-server': - store.dispatch( - quickActionHandle({ - action: 'add-server' - }) - ); - break; - - case 'search': - store.dispatch( - quickActionHandle({ - action: 'search' - }) - ); - break; - - case 'recent': - store.dispatch( - quickActionHandle({ - action: 'recent' - }) - ); - break; - } + store.dispatch( + quickActionHandle({ + action: id + }) + ); } diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 32d17361f4d..239dbe7c393 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -9,6 +9,7 @@ import { RootEnum } from '../definitions'; import UserPreferences from '../lib/methods/userPreferences'; import { CURRENT_SERVER } from '../lib/constants/keys'; import Navigation from '../lib/navigation/appNavigation'; +import { sendEmail } from '../views/SettingsView'; interface IQuickActionOpen extends Action { params?: { @@ -59,6 +60,10 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { } yield put({ type: UI.TRIGGER_SEARCH }); break; + case 'contact': + sendEmail(); + yield waitForAppReady(); // if user navigates back to app just init it + break; default: Alert.alert('Other Quick Action', `this is ${quickAction} action`); } diff --git a/app/views/SettingsView/index.tsx b/app/views/SettingsView/index.tsx index 169f7d6da02..5abc1b957a7 100644 --- a/app/views/SettingsView/index.tsx +++ b/app/views/SettingsView/index.tsx @@ -33,6 +33,22 @@ import SidebarView from '../SidebarView'; type TLogScreenName = 'SE_GO_LANGUAGE' | 'SE_GO_DEFAULTBROWSER' | 'SE_GO_THEME' | 'SE_GO_PROFILE' | 'SE_GO_SECURITYPRIVACY'; +export const sendEmail = async () => { + logEvent(events.SE_CONTACT_US); + const subject = encodeURI('Rocket.Chat Mobile App Support'); + const email = encodeURI('support@rocket.chat'); + const description = encodeURI(` + version: ${getReadableVersion} + device: ${getDeviceModel} + `); + try { + await Linking.openURL(`mailto:${email}?subject=${subject}&body=${description}`); + } catch (e) { + logEvent(events.SE_CONTACT_US_F); + showErrorAlert(I18n.t('error-email-send-failed', { message: 'support@rocket.chat' })); + } +}; + const SettingsView = (): React.ReactElement => { 'use memo'; @@ -86,22 +102,6 @@ const SettingsView = (): React.ReactElement => { navigation.navigate(screen); }; - const sendEmail = async () => { - logEvent(events.SE_CONTACT_US); - const subject = encodeURI('Rocket.Chat Mobile App Support'); - const email = encodeURI('support@rocket.chat'); - const description = encodeURI(` - version: ${getReadableVersion} - device: ${getDeviceModel} - `); - try { - await Linking.openURL(`mailto:${email}?subject=${subject}&body=${description}`); - } catch (e) { - logEvent(events.SE_CONTACT_US_F); - showErrorAlert(I18n.t('error-email-send-failed', { message: 'support@rocket.chat' })); - } - }; - const shareApp = () => { let message; if (isAndroid) { From b99d1dafcbbb943e305aabb941b2756c03e0c08c Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Fri, 9 Jan 2026 19:53:10 +0530 Subject: [PATCH 10/38] feat: store last visited room id --- app/actions/actionsTypes.ts | 2 +- app/actions/rooms.ts | 13 ++++++++++++- app/lib/constants/keys.ts | 1 + app/lib/methods/helpers/goRoom.ts | 6 ++++++ app/lib/methods/helpers/storeLastVisitedRoom.ts | 10 ++++++++++ app/lib/methods/storeLastVisitedRoom.ts | 0 app/reducers/rooms.ts | 10 +++++++++- app/reducers/ui.ts | 1 - app/sagas/init.js | 8 +++++++- app/sagas/quickActions.ts | 13 ++++++++++++- 10 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 app/lib/methods/helpers/storeLastVisitedRoom.ts create mode 100644 app/lib/methods/storeLastVisitedRoom.ts diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index 2bcef9e3af0..29fd6288f2a 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -12,7 +12,7 @@ function createRequestTypes(base = {}, types = defaultTypes): Record | string; } -export type IRoomsAction = IRoomsRequest & ISetSearch & IRoomsFailure; +export interface IRoomsLastVisited extends Action { + lastVisitedRoomId: string; +} + +export type IRoomsAction = IRoomsRequest & ISetSearch & IRoomsFailure & IRoomsLastVisited; export function roomsRequest( params: { @@ -45,3 +49,10 @@ export function roomsRefresh(): Action { type: ROOMS.REFRESH }; } + +export function roomsStoreLastVisited(rid: string): IRoomsLastVisited { + return { + type: ROOMS.STORE_LAST_VISITED, + lastVisitedRoomId: rid + }; +} diff --git a/app/lib/constants/keys.ts b/app/lib/constants/keys.ts index 8c6dc1da7d2..9efe0fd5776 100644 --- a/app/lib/constants/keys.ts +++ b/app/lib/constants/keys.ts @@ -25,3 +25,4 @@ export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY'; export const TOKEN_KEY = 'reactnativemeteor_usertoken'; export const CURRENT_SERVER = 'currentServer'; export const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY'; +export const LAST_VISITED_ROOM_KEY = 'RC_LAST_VISITED_ROOM_KEY'; diff --git a/app/lib/methods/helpers/goRoom.ts b/app/lib/methods/helpers/goRoom.ts index 6119420898f..e6308f9844a 100644 --- a/app/lib/methods/helpers/goRoom.ts +++ b/app/lib/methods/helpers/goRoom.ts @@ -12,6 +12,9 @@ import { import { getRoomTitle, getUidDirectMessage } from './helpers'; import { createDirectMessage } from '../../services/restApi'; import { emitErrorCreateDirectMessage } from './emitErrorCreateDirectMessage'; +import store from '../../store'; +import { roomsStoreLastVisited } from '../../../actions/rooms'; +import { storeLastVisitedRoomId } from './storeLastVisitedRoom'; interface IGoRoomItem { search?: boolean; // comes from spotlight @@ -98,6 +101,8 @@ export const goRoom = async ({ const { username } = item; const result = await createDirectMessage(username as string); if (result.success && result?.room?._id) { + store.dispatch(roomsStoreLastVisited(result.room._id)); + console.log('storing last visited room ===================='); return navigate({ item: { rid: result.room._id, @@ -120,6 +125,7 @@ export const goRoom = async ({ */ let _item = item; if (item.rid) { + storeLastVisitedRoomId(item.rid); const sub = await getSubscriptionByRoomId(item.rid); if (sub) { _item = sub; diff --git a/app/lib/methods/helpers/storeLastVisitedRoom.ts b/app/lib/methods/helpers/storeLastVisitedRoom.ts new file mode 100644 index 00000000000..860bc20c03c --- /dev/null +++ b/app/lib/methods/helpers/storeLastVisitedRoom.ts @@ -0,0 +1,10 @@ +import { LAST_VISITED_ROOM_KEY } from '../../constants/keys'; +import { roomsStoreLastVisited } from '../../../actions/rooms'; +import store from '../../store'; +import UserPreferences from '../userPreferences'; + +export const storeLastVisitedRoomId = (rid: string) => { + UserPreferences.setString(LAST_VISITED_ROOM_KEY, rid); + store.dispatch(roomsStoreLastVisited(rid)); + console.log('storing last visited room ===================='); +}; diff --git a/app/lib/methods/storeLastVisitedRoom.ts b/app/lib/methods/storeLastVisitedRoom.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/reducers/rooms.ts b/app/reducers/rooms.ts index de2ef250c35..0d9f88023aa 100644 --- a/app/reducers/rooms.ts +++ b/app/reducers/rooms.ts @@ -6,13 +6,15 @@ export interface IRooms { refreshing: boolean; failure: boolean; errorMessage: Record | string; + lastVisitedRid: string; } export const initialState: IRooms = { isFetching: false, refreshing: false, failure: false, - errorMessage: {} + errorMessage: {}, + lastVisitedRid: '' }; export default function rooms(state = initialState, action: IRoomsAction): IRooms { @@ -44,6 +46,12 @@ export default function rooms(state = initialState, action: IRoomsAction): IRoom isFetching: true, refreshing: true }; + case ROOMS.STORE_LAST_VISITED: + return { + ...state, + lastVisitedRid: action.lastVisitedRoomId + }; + default: return state; } diff --git a/app/reducers/ui.ts b/app/reducers/ui.ts index fc357bc1509..b305accfee7 100644 --- a/app/reducers/ui.ts +++ b/app/reducers/ui.ts @@ -11,7 +11,6 @@ export const initialState: IUIState = { }; export default function ui(state = initialState, action: Action): IUIState { - console.log(action, 'action========================'); switch (action.type) { case UI.TRIGGER_SEARCH: return { diff --git a/app/sagas/init.js b/app/sagas/init.js index d9d6024abe8..251485e3efb 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -2,7 +2,7 @@ import { call, put, select, takeLatest } from 'redux-saga/effects'; import RNBootSplash from 'react-native-bootsplash'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { CURRENT_SERVER, TOKEN_KEY } from '../lib/constants/keys'; +import { CURRENT_SERVER, LAST_VISITED_ROOM_KEY, TOKEN_KEY } from '../lib/constants/keys'; import UserPreferences from '../lib/methods/userPreferences'; import { selectServerRequest } from '../actions/server'; import { setAllPreferences } from '../actions/sortPreferences'; @@ -15,6 +15,7 @@ import { RootEnum } from '../definitions'; import { getSortPreferences } from '../lib/methods/userPreferencesMethods'; import { deepLinkingClickCallPush } from '../actions/deepLinking'; import { getServerById } from '../lib/database/services/Server'; +import { roomsStoreLastVisited } from '../actions/rooms'; export const initLocalSettings = function* initLocalSettings() { const sortPreferences = getSortPreferences(); @@ -51,6 +52,11 @@ const restore = function* restore() { return; } yield put(selectServerRequest(server, serverRecord.version)); + const lastVisitedRid = UserPreferences.getString(LAST_VISITED_ROOM_KEY); + + if (lastVisitedRid) { + yield put(roomsStoreLastVisited(lastVisitedRid)); + } } yield put(appReady({})); diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 239dbe7c393..37646fe5431 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -5,11 +5,12 @@ import { type Action } from 'redux'; import { QUICK_ACTIONS, APP, UI } from '../actions/actionsTypes'; import { appStart, appInit } from '../actions/app'; import { serverInitAdd } from '../actions/server'; -import { RootEnum } from '../definitions'; +import { IApplicationState, RootEnum } from '../definitions'; import UserPreferences from '../lib/methods/userPreferences'; import { CURRENT_SERVER } from '../lib/constants/keys'; import Navigation from '../lib/navigation/appNavigation'; import { sendEmail } from '../views/SettingsView'; +import { goRoom } from '../lib/methods/helpers/goRoom'; interface IQuickActionOpen extends Action { params?: { @@ -64,6 +65,16 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { sendEmail(); yield waitForAppReady(); // if user navigates back to app just init it break; + case 'recent': + yield waitForAppReady(); + // goRoom() + console.log('room==========='); + console.log( + yield select((state: IApplicationState) => state.rooms.lastVisitedRid), + 'last visited room ====================' + ); + Alert.alert('last visited room', yield select((state: IApplicationState) => state.rooms.lastVisitedRid)); + break; default: Alert.alert('Other Quick Action', `this is ${quickAction} action`); } From dc7349d788764a137b9b0d0566b51b050b67e129 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Fri, 9 Jan 2026 19:54:39 +0530 Subject: [PATCH 11/38] cleanup: delete empty file --- app/lib/methods/storeLastVisitedRoom.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/lib/methods/storeLastVisitedRoom.ts diff --git a/app/lib/methods/storeLastVisitedRoom.ts b/app/lib/methods/storeLastVisitedRoom.ts deleted file mode 100644 index e69de29bb2d..00000000000 From c369283ea3a19026aa04e9d1a17c041ac713fdba Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sat, 10 Jan 2026 00:13:19 +0530 Subject: [PATCH 12/38] pod install --- ios/RocketChatRN.xcodeproj/project.pbxproj | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 35eef3974e5..a0b2806e2a1 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -3191,7 +3191,10 @@ ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -3255,7 +3258,10 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; From fc0c06e0054ed7dbd063c8cb47a4e9f3694eb27b Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sat, 10 Jan 2026 01:22:29 +0530 Subject: [PATCH 13/38] feat: get room and navigate to room --- app/sagas/quickActions.ts | 41 ++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 37646fe5431..00a08d7d57e 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -1,16 +1,17 @@ -import { select, takeEvery, put, take, type Effect } from 'redux-saga/effects'; -import { Alert } from 'react-native'; +import { select, takeEvery, put, take, type Effect, call } from 'redux-saga/effects'; +import { Alert, InteractionManager } from 'react-native'; import { type Action } from 'redux'; import { QUICK_ACTIONS, APP, UI } from '../actions/actionsTypes'; import { appStart, appInit } from '../actions/app'; import { serverInitAdd } from '../actions/server'; -import { IApplicationState, RootEnum } from '../definitions'; +import { type IApplicationState, RootEnum, type TSubscriptionModel } from '../definitions'; import UserPreferences from '../lib/methods/userPreferences'; import { CURRENT_SERVER } from '../lib/constants/keys'; import Navigation from '../lib/navigation/appNavigation'; import { sendEmail } from '../views/SettingsView'; import { goRoom } from '../lib/methods/helpers/goRoom'; +import { getRoom } from '../lib/methods/getRoom'; interface IQuickActionOpen extends Action { params?: { @@ -30,6 +31,16 @@ function* waitForAppReady(): Generator { } } +function* waitForRoomInDB(rid: string): Generator { + try { + yield call(getRoom, rid); + } catch { + yield take(APP.START); + } + + return yield call(getRoom, rid); +} + function* handleQuickActionOpen(action: IQuickActionOpen): Generator { // yield take(APP.READY); @@ -65,16 +76,24 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { sendEmail(); yield waitForAppReady(); // if user navigates back to app just init it break; - case 'recent': + case 'recent': { yield waitForAppReady(); - // goRoom() - console.log('room==========='); - console.log( - yield select((state: IApplicationState) => state.rooms.lastVisitedRid), - 'last visited room ====================' - ); - Alert.alert('last visited room', yield select((state: IApplicationState) => state.rooms.lastVisitedRid)); + + const rid: string = yield select((state: IApplicationState) => state.rooms.lastVisitedRid); + + if (!rid) return; + + try { + const room: TSubscriptionModel = yield call(waitForRoomInDB, rid); + console.log(room, 'room============================'); + yield call(goRoom, { item: { rid: room.id }, isMasterDetail: true }); + } catch (e) { + console.log(e); + Alert.alert('Error', 'Error finding room in this server, try switching server'); + } + break; + } default: Alert.alert('Other Quick Action', `this is ${quickAction} action`); } From 498cf7075fcc1b0d64930829e23013900a6891df Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sat, 10 Jan 2026 01:34:45 +0530 Subject: [PATCH 14/38] feat: added dynamic room name --- app/AppContainer.tsx | 16 ++++++++++++--- app/lib/quickActions/index.ts | 38 +++++++++++++++++------------------ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/app/AppContainer.tsx b/app/AppContainer.tsx index 3cba09b9a78..f5143288b39 100644 --- a/app/AppContainer.tsx +++ b/app/AppContainer.tsx @@ -3,11 +3,11 @@ import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { connect } from 'react-redux'; -import { registerQuickActions } from './lib/quickActions'; +import { registerQuickActions, updateQuickActions } from './lib/quickActions'; import type { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes'; import Navigation from './lib/navigation/appNavigation'; import { defaultHeader, getActiveRouteName, navigationTheme } from './lib/methods/helpers/navigation'; -import { RootEnum } from './definitions'; +import { type IApplicationState, RootEnum } from './definitions'; // Stacks import AuthLoadingView from './views/AuthLoadingView'; // SetUsername Stack @@ -20,6 +20,8 @@ import { ThemeContext } from './theme'; import { setCurrentScreen } from './lib/methods/helpers/log'; import { themes } from './lib/constants/colors'; import { emitter } from './lib/methods/helpers'; +import { useAppSelector } from './lib/hooks/useAppSelector'; +import { getRoom } from './lib/methods/getRoom'; const createStackNavigator = createNativeStackNavigator; @@ -35,7 +37,7 @@ const SetUsernameStack = () => ( const Stack = createStackNavigator(); const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => { const { theme } = useContext(ThemeContext); - + const lastVisitedRid = useAppSelector((state: IApplicationState) => state.rooms.lastVisitedRid); useEffect(() => { registerQuickActions(); }, []); @@ -49,6 +51,14 @@ const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: bool } }, [root]); + useEffect(() => { + const updateAsync = async () => { + const room = await getRoom(lastVisitedRid); + updateQuickActions({ recentRoomName: room.name }); + }; + updateAsync(); + }, [lastVisitedRid]); + if (!root) { return null; } diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index cd361fcf933..e6abf9daa8b 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -18,48 +18,48 @@ AppState.addEventListener('change', async nextState => { } }); -export function registerQuickActions() { - if (registered) { - return; - } - registered = true; - - console.log('quickactions registered======================='); - +export function updateQuickActions({ recentRoomName }: { recentRoomName?: string } = {}) { QuickActions.setItems([ { id: 'search', title: 'Search', - icon: Platform.select({ ios: 'symbol:magnifyingglass', android: 'ic_quickaction_find' }), - href: '' + icon: Platform.select({ ios: 'symbol:magnifyingglass', android: 'ic_quickaction_find' }) }, { id: 'add-server', title: 'Add Server', - icon: Platform.select({ ios: 'symbol:plus', android: 'ic_quickaction_add' }), - href: '' + icon: Platform.select({ ios: 'symbol:plus', android: 'ic_quickaction_add' }) }, { id: 'recent', - title: 'Recent Rooms', + title: recentRoomName ?? 'Recent Rooms', + subtitle: recentRoomName ? 'Last visited' : undefined, icon: Platform.select({ ios: 'symbol:clock.arrow.trianglehead.counterclockwise.rotate.90', android: 'ic_quickaction_recent' - }), - href: '' + }) }, { id: 'contact', title: Platform.select({ android: 'Contact us', ios: 'Something wrong?' }) ?? 'Contact us', - subtitle: "We're here to help", // ios only + subtitle: "We're here to help", icon: Platform.select({ - // ios: 'symbol:person.crop.circle.badge.questionmark', // alternate option i liked ios: 'symbol:envelope', android: 'ic_quickaction_contact' - }), - href: '' + }) } ]); +} + +export function registerQuickActions() { + if (registered) { + return; + } + registered = true; + + console.log('quickactions registered======================='); + + updateQuickActions(); QuickActions.addListener(action => { console.log(action, 'quickactions======================='); From 2e8a7b3217b89fb371ceb4bb38397c5d736dba39 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sat, 10 Jan 2026 02:39:10 +0530 Subject: [PATCH 15/38] fix: android navigation not working recent room --- app/AppContainer.tsx | 5 ++++- app/actions/actionsTypes.ts | 1 + app/sagas/quickActions.ts | 13 +++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/AppContainer.tsx b/app/AppContainer.tsx index f5143288b39..5452ff82c3a 100644 --- a/app/AppContainer.tsx +++ b/app/AppContainer.tsx @@ -1,7 +1,7 @@ import React, { useContext, memo, useEffect } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { connect } from 'react-redux'; +import { connect, useDispatch } from 'react-redux'; import { registerQuickActions, updateQuickActions } from './lib/quickActions'; import type { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes'; @@ -22,6 +22,7 @@ import { themes } from './lib/constants/colors'; import { emitter } from './lib/methods/helpers'; import { useAppSelector } from './lib/hooks/useAppSelector'; import { getRoom } from './lib/methods/getRoom'; +import { NAVIGATION } from './actions/actionsTypes'; const createStackNavigator = createNativeStackNavigator; @@ -37,6 +38,7 @@ const SetUsernameStack = () => ( const Stack = createStackNavigator(); const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => { const { theme } = useContext(ThemeContext); + const dispatch = useDispatch(); const lastVisitedRid = useAppSelector((state: IApplicationState) => state.rooms.lastVisitedRid); useEffect(() => { registerQuickActions(); @@ -71,6 +73,7 @@ const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: bool ref={Navigation.navigationRef} onReady={() => { emitter.emit('navigationReady'); + dispatch({ type: NAVIGATION.NAVIGATION_READY }); }} onStateChange={state => { const previousRouteName = Navigation.routeNameRef.current; diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index 29fd6288f2a..5c3fee82f1e 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -67,6 +67,7 @@ export const QUICK_ACTIONS = createRequestTypes('QUICK_ACTIONS', [ 'CLEAR_PENDING', 'MARK_LAUNCHED_FROM_QUICK_ACTION' ]); +export const NAVIGATION = createRequestTypes('NAVIGATION', ['NAVIGATION_READY']); export const UI = createRequestTypes('UI', ['TRIGGER_SEARCH', 'CLEAR_TRIGGERED_SEARCH']); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS'; diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 00a08d7d57e..6a965f556c0 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -1,8 +1,8 @@ import { select, takeEvery, put, take, type Effect, call } from 'redux-saga/effects'; -import { Alert, InteractionManager } from 'react-native'; +import { Alert } from 'react-native'; import { type Action } from 'redux'; -import { QUICK_ACTIONS, APP, UI } from '../actions/actionsTypes'; +import { QUICK_ACTIONS, APP, UI, NAVIGATION } from '../actions/actionsTypes'; import { appStart, appInit } from '../actions/app'; import { serverInitAdd } from '../actions/server'; import { type IApplicationState, RootEnum, type TSubscriptionModel } from '../definitions'; @@ -41,6 +41,14 @@ function* waitForRoomInDB(rid: string): Generator { return yield call(getRoom, rid); } +function* waitForNavigationReady(): Generator { + if (Navigation.navigationRef.current) { + return; + } + + yield take(NAVIGATION.NAVIGATION_READY); +} + function* handleQuickActionOpen(action: IQuickActionOpen): Generator { // yield take(APP.READY); @@ -86,6 +94,7 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { try { const room: TSubscriptionModel = yield call(waitForRoomInDB, rid); console.log(room, 'room============================'); + yield waitForNavigationReady(); yield call(goRoom, { item: { rid: room.id }, isMasterDetail: true }); } catch (e) { console.log(e); From 56ff591a2b8448fda71aeed2274862a5f04ee31b Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sat, 10 Jan 2026 03:12:43 +0530 Subject: [PATCH 16/38] fix: roomname not updating in recent quickaction --- app/AppContainer.tsx | 11 +++-------- app/actions/rooms.ts | 6 ++++-- app/lib/constants/keys.ts | 3 ++- app/lib/methods/helpers/goRoom.ts | 11 ++++++++--- app/lib/methods/helpers/storeLastVisitedRoom.ts | 9 +++++---- app/reducers/rooms.ts | 7 +++++-- app/sagas/init.js | 9 +++++---- app/sagas/quickActions.ts | 2 ++ 8 files changed, 34 insertions(+), 24 deletions(-) diff --git a/app/AppContainer.tsx b/app/AppContainer.tsx index 5452ff82c3a..0800b16475c 100644 --- a/app/AppContainer.tsx +++ b/app/AppContainer.tsx @@ -21,7 +21,6 @@ import { setCurrentScreen } from './lib/methods/helpers/log'; import { themes } from './lib/constants/colors'; import { emitter } from './lib/methods/helpers'; import { useAppSelector } from './lib/hooks/useAppSelector'; -import { getRoom } from './lib/methods/getRoom'; import { NAVIGATION } from './actions/actionsTypes'; const createStackNavigator = createNativeStackNavigator; @@ -39,7 +38,7 @@ const Stack = createStackNavigator(); const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => { const { theme } = useContext(ThemeContext); const dispatch = useDispatch(); - const lastVisitedRid = useAppSelector((state: IApplicationState) => state.rooms.lastVisitedRid); + const lastVisitedRoomName = useAppSelector((state: IApplicationState) => state.rooms.lastVisitedName); useEffect(() => { registerQuickActions(); }, []); @@ -54,12 +53,8 @@ const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: bool }, [root]); useEffect(() => { - const updateAsync = async () => { - const room = await getRoom(lastVisitedRid); - updateQuickActions({ recentRoomName: room.name }); - }; - updateAsync(); - }, [lastVisitedRid]); + updateQuickActions({ recentRoomName: lastVisitedRoomName }); + }, [lastVisitedRoomName]); if (!root) { return null; diff --git a/app/actions/rooms.ts b/app/actions/rooms.ts index 86dd0669f30..cea3b1fa6b4 100644 --- a/app/actions/rooms.ts +++ b/app/actions/rooms.ts @@ -16,6 +16,7 @@ export interface IRoomsFailure extends Action { export interface IRoomsLastVisited extends Action { lastVisitedRoomId: string; + lastVisitedRoomName: string; } export type IRoomsAction = IRoomsRequest & ISetSearch & IRoomsFailure & IRoomsLastVisited; @@ -50,9 +51,10 @@ export function roomsRefresh(): Action { }; } -export function roomsStoreLastVisited(rid: string): IRoomsLastVisited { +export function roomsStoreLastVisited(rid: string, name: string): IRoomsLastVisited { return { type: ROOMS.STORE_LAST_VISITED, - lastVisitedRoomId: rid + lastVisitedRoomId: rid, + lastVisitedRoomName: name }; } diff --git a/app/lib/constants/keys.ts b/app/lib/constants/keys.ts index 9efe0fd5776..9428262c319 100644 --- a/app/lib/constants/keys.ts +++ b/app/lib/constants/keys.ts @@ -25,4 +25,5 @@ export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY'; export const TOKEN_KEY = 'reactnativemeteor_usertoken'; export const CURRENT_SERVER = 'currentServer'; export const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY'; -export const LAST_VISITED_ROOM_KEY = 'RC_LAST_VISITED_ROOM_KEY'; +export const LAST_VISITED_ROOM_ID_KEY = 'RC_LAST_VISITED_ROOM_ID_KEY'; +export const LAST_VISITED_ROOM_Name_KEY = 'RC_LAST_VISITED_ROOM_Name_KEY'; diff --git a/app/lib/methods/helpers/goRoom.ts b/app/lib/methods/helpers/goRoom.ts index e6308f9844a..e66c7714e8d 100644 --- a/app/lib/methods/helpers/goRoom.ts +++ b/app/lib/methods/helpers/goRoom.ts @@ -14,7 +14,8 @@ import { createDirectMessage } from '../../services/restApi'; import { emitErrorCreateDirectMessage } from './emitErrorCreateDirectMessage'; import store from '../../store'; import { roomsStoreLastVisited } from '../../../actions/rooms'; -import { storeLastVisitedRoomId } from './storeLastVisitedRoom'; +import { storeLastVisitedRoom } from './storeLastVisitedRoom'; +import { getRoom } from '../getRoom'; interface IGoRoomItem { search?: boolean; // comes from spotlight @@ -101,7 +102,10 @@ export const goRoom = async ({ const { username } = item; const result = await createDirectMessage(username as string); if (result.success && result?.room?._id) { - store.dispatch(roomsStoreLastVisited(result.room._id)); + try { + const room = await getRoom(result?.room?.rid || ''); + store.dispatch(roomsStoreLastVisited(result.room._id, room.name)); + } catch {} console.log('storing last visited room ===================='); return navigate({ item: { @@ -125,7 +129,8 @@ export const goRoom = async ({ */ let _item = item; if (item.rid) { - storeLastVisitedRoomId(item.rid); + const roomName = await getRoom(item.rid); + storeLastVisitedRoom(item.rid, roomName.name); const sub = await getSubscriptionByRoomId(item.rid); if (sub) { _item = sub; diff --git a/app/lib/methods/helpers/storeLastVisitedRoom.ts b/app/lib/methods/helpers/storeLastVisitedRoom.ts index 860bc20c03c..ea4b86cb06e 100644 --- a/app/lib/methods/helpers/storeLastVisitedRoom.ts +++ b/app/lib/methods/helpers/storeLastVisitedRoom.ts @@ -1,10 +1,11 @@ -import { LAST_VISITED_ROOM_KEY } from '../../constants/keys'; +import { LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_Name_KEY } from '../../constants/keys'; import { roomsStoreLastVisited } from '../../../actions/rooms'; import store from '../../store'; import UserPreferences from '../userPreferences'; -export const storeLastVisitedRoomId = (rid: string) => { - UserPreferences.setString(LAST_VISITED_ROOM_KEY, rid); - store.dispatch(roomsStoreLastVisited(rid)); +export const storeLastVisitedRoom = (rid: string, name: string) => { + UserPreferences.setString(LAST_VISITED_ROOM_ID_KEY, rid); + UserPreferences.setString(LAST_VISITED_ROOM_Name_KEY, name); + store.dispatch(roomsStoreLastVisited(rid, name)); console.log('storing last visited room ===================='); }; diff --git a/app/reducers/rooms.ts b/app/reducers/rooms.ts index 0d9f88023aa..4664b63cee5 100644 --- a/app/reducers/rooms.ts +++ b/app/reducers/rooms.ts @@ -7,6 +7,7 @@ export interface IRooms { failure: boolean; errorMessage: Record | string; lastVisitedRid: string; + lastVisitedName: string; } export const initialState: IRooms = { @@ -14,7 +15,8 @@ export const initialState: IRooms = { refreshing: false, failure: false, errorMessage: {}, - lastVisitedRid: '' + lastVisitedRid: '', + lastVisitedName: '' }; export default function rooms(state = initialState, action: IRoomsAction): IRooms { @@ -49,7 +51,8 @@ export default function rooms(state = initialState, action: IRoomsAction): IRoom case ROOMS.STORE_LAST_VISITED: return { ...state, - lastVisitedRid: action.lastVisitedRoomId + lastVisitedRid: action.lastVisitedRoomId, + lastVisitedName: action.lastVisitedRoomName }; default: diff --git a/app/sagas/init.js b/app/sagas/init.js index 251485e3efb..d2500e41227 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -2,7 +2,7 @@ import { call, put, select, takeLatest } from 'redux-saga/effects'; import RNBootSplash from 'react-native-bootsplash'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { CURRENT_SERVER, LAST_VISITED_ROOM_KEY, TOKEN_KEY } from '../lib/constants/keys'; +import { CURRENT_SERVER, LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_Name_KEY, TOKEN_KEY } from '../lib/constants/keys'; import UserPreferences from '../lib/methods/userPreferences'; import { selectServerRequest } from '../actions/server'; import { setAllPreferences } from '../actions/sortPreferences'; @@ -52,10 +52,11 @@ const restore = function* restore() { return; } yield put(selectServerRequest(server, serverRecord.version)); - const lastVisitedRid = UserPreferences.getString(LAST_VISITED_ROOM_KEY); + const lastVisitedRid = UserPreferences.getString(LAST_VISITED_ROOM_ID_KEY); + const lastVisitedRoomName = UserPreferences.getString(LAST_VISITED_ROOM_Name_KEY); - if (lastVisitedRid) { - yield put(roomsStoreLastVisited(lastVisitedRid)); + if (lastVisitedRid && lastVisitedRoomName) { + yield put(roomsStoreLastVisited(lastVisitedRid, lastVisitedRoomName)); } } diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 6a965f556c0..4b00c7e588e 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -12,6 +12,7 @@ import Navigation from '../lib/navigation/appNavigation'; import { sendEmail } from '../views/SettingsView'; import { goRoom } from '../lib/methods/helpers/goRoom'; import { getRoom } from '../lib/methods/getRoom'; +import { roomsStoreLastVisited } from '../actions/rooms'; interface IQuickActionOpen extends Action { params?: { @@ -95,6 +96,7 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { const room: TSubscriptionModel = yield call(waitForRoomInDB, rid); console.log(room, 'room============================'); yield waitForNavigationReady(); + yield put(roomsStoreLastVisited(room.id, room.name)); yield call(goRoom, { item: { rid: room.id }, isMasterDetail: true }); } catch (e) { console.log(e); From 5ad13db3cf81c245a80299e90b38d72f9e16587c Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sun, 11 Jan 2026 19:20:32 +0530 Subject: [PATCH 17/38] feat: i18n --- app/i18n/locales/en.json | 5 +++++ app/i18n/locales/pt-BR.json | 5 +++++ app/lib/quickActions/index.ts | 13 +++++++------ app/sagas/quickActions.ts | 3 ++- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 20adc12eb90..041036ac0e5 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -328,6 +328,7 @@ "Enter_manually": "Enter manually", "Enter_the_code": "Enter the code we just emailed you.", "Error_Download_file": "Error while downloading file", + "Error_finding_room":"Error finding room in this server\ntry switching server", "Error_incorrect_password": "Incorrect password", "Error_play_video": "There was an error while playing this video", "Error_prefix": "Error: {{message}}", @@ -453,6 +454,7 @@ "Last_updated": "Last updated", "Last_updated_at": "Last updated at", "Last_updated_on": "Last updated on", + "Last_visited_room": "Last visited", "last-owner-can-not-be-removed": "Last owner cannot be removed", "Leader": "Leader", "Learn_more": "Learn more", @@ -694,6 +696,7 @@ "Receive_Notification": "Receive notification", "Receive_notifications_from": "Receive notifications from {{name}}", "Recently_used": "Recently used", + "Recent_Rooms": "Recent Rooms", "Record_audio_message": "Record audio message", "Recording_audio_in_progress": "Recording audio message", "Register": "Register", @@ -842,6 +845,7 @@ "Smileys_and_people": "Smileys and people", "Sort_by": "Sort by", "Sound": "Sound", + "Something_Wrong?":"Something Wrong?", "Star": "Star", "Starred": "Starred", "Start_a_call": "Start a call", @@ -978,6 +982,7 @@ "Waiting_for_network": "Waiting for network...", "Waiting_for_server_connection": "Waiting for server connection", "Websocket_disabled": "Websocket is disabled for this workspace.\n{{contact}}", + "We_are_here_to_help": "We're here to help", "What_are_you_doing_right_now": "What are you doing right now?", "Whats_the_password_for_your_certificate": "What's the password for your certificate?", "Why_do_you_want_to_report": "Why do you want to report?", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index cbd4dfb4eae..f9398ba9879 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -319,6 +319,7 @@ "Enter_manually": "Inserir manualmente", "Enter_the_code": "Insira o código que acabamos de enviar por e-mail.", "Error_Download_file": "Erro ao baixar o arquivo", + "Error_finding_room":"Erro ao encontrar uma sala neste servidor\ntente mudar de servidor.", "Error_incorrect_password": "Senha incorreta", "Error_play_video": "Houve um erro ao reproduzir esse vídeo", "Error_prefix": "Erro: {{message}}", @@ -443,6 +444,7 @@ "Last_updated": "Última atualização", "Last_updated_at": "Última atualização em", "Last_updated_on": "Última atualização em", + "Last_visited_room": "Última visita", "last-owner-can-not-be-removed": "O último dono não pode ser removido", "Leader": "Líder", "Learn_more": "Saiba mais", @@ -682,6 +684,7 @@ "Receive_Notification": "Receber notificação", "Receive_notifications_from": "Receber notificação de {{name}}", "Recently_used": "Usados recentemente", + "Recent_Rooms": "Quartos recentes", "Record_audio_message": "Gravar mensagem de áudio", "Register": "Registrar", "Registration_Succeeded": "Registrado com sucesso!", @@ -823,6 +826,7 @@ "Smileys_and_people": "Carinhas e pessoas", "Sort_by": "Ordenar por", "Sound": "Som da notificação", + "Something_Wrong?":"Algo errado?", "Star": "Favorito", "Starred": "Mensagens Favoritas", "Start_a_call": "Inicie uma chamada", @@ -960,6 +964,7 @@ "Waiting_for_network": "Aguardando rede...", "Waiting_for_server_connection": "Aguardando conexão com o servidor", "Websocket_disabled": "Websocket está desativado para essa workspace.\n{{contact}}", + "We_are_here_to_help": "Estamos aqui para ajudar.", "What_are_you_doing_right_now": "O que você está fazendo agora?", "Whats_the_password_for_your_certificate": "Qual é a senha para o seu certificado?", "Why_do_you_want_to_report": "Por que você deseja reportar?", diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index e6abf9daa8b..fabfb7d903c 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -4,6 +4,7 @@ import { InteractionManager, AppState, Platform } from 'react-native'; import store from '../store'; import { getRecentQuickAction } from './getInitialQuickAction'; import { quickActionHandle } from '../../actions/quickActions'; +import I18n from '../../i18n'; let registered = false; @@ -22,18 +23,18 @@ export function updateQuickActions({ recentRoomName }: { recentRoomName?: string QuickActions.setItems([ { id: 'search', - title: 'Search', + title: I18n.t('Search'), icon: Platform.select({ ios: 'symbol:magnifyingglass', android: 'ic_quickaction_find' }) }, { id: 'add-server', - title: 'Add Server', + title: I18n.t('Add_Server'), icon: Platform.select({ ios: 'symbol:plus', android: 'ic_quickaction_add' }) }, { id: 'recent', - title: recentRoomName ?? 'Recent Rooms', - subtitle: recentRoomName ? 'Last visited' : undefined, + title: recentRoomName ?? I18n.t('Recent_Rooms'), + subtitle: recentRoomName ? I18n.t('Last_visited_room') : undefined, icon: Platform.select({ ios: 'symbol:clock.arrow.trianglehead.counterclockwise.rotate.90', android: 'ic_quickaction_recent' @@ -41,8 +42,8 @@ export function updateQuickActions({ recentRoomName }: { recentRoomName?: string }, { id: 'contact', - title: Platform.select({ android: 'Contact us', ios: 'Something wrong?' }) ?? 'Contact us', - subtitle: "We're here to help", + title: Platform.select({ android: I18n.t('Contact_us'), ios: I18n.t('Something_Wrong?') }) ?? I18n.t('Contact_us'), + subtitle: I18n.t('We_are_here_to_help'), icon: Platform.select({ ios: 'symbol:envelope', android: 'ic_quickaction_contact' diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 4b00c7e588e..fd9428528af 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -13,6 +13,7 @@ import { sendEmail } from '../views/SettingsView'; import { goRoom } from '../lib/methods/helpers/goRoom'; import { getRoom } from '../lib/methods/getRoom'; import { roomsStoreLastVisited } from '../actions/rooms'; +import I18n from '../i18n'; interface IQuickActionOpen extends Action { params?: { @@ -100,7 +101,7 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { yield call(goRoom, { item: { rid: room.id }, isMasterDetail: true }); } catch (e) { console.log(e); - Alert.alert('Error', 'Error finding room in this server, try switching server'); + Alert.alert(I18n.t('Room_not_found'), I18n.t('Error_finding_room')); } break; From 47f01f99c809bff9ef28bef98c42506319287e2c Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sun, 11 Jan 2026 19:24:31 +0530 Subject: [PATCH 18/38] fix: cycle dependency error on goRoom | migrate to event architecture instead of store.dispatch --- app/lib/methods/helpers/emitter.ts | 4 ++++ app/lib/methods/helpers/goRoom.ts | 22 +++++++++++++------ app/sagas/index.js | 2 ++ app/sagas/quickActions.ts | 4 ++-- app/sagas/roomVisited.ts | 35 ++++++++++++++++++++++++++++++ app/sagas/rooms.js | 12 +++++----- 6 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 app/sagas/roomVisited.ts diff --git a/app/lib/methods/helpers/emitter.ts b/app/lib/methods/helpers/emitter.ts index 268ca8f0a96..6516246c6f2 100644 --- a/app/lib/methods/helpers/emitter.ts +++ b/app/lib/methods/helpers/emitter.ts @@ -17,6 +17,10 @@ export type TEmitterEvents = TDynamicMediaDownloadEvents & { setComposerHeightThread: number; audioFocused: string; navigationReady: undefined; + roomVisited: { + rid: string; + name: string; + }; }; export type TKeyEmitterEvent = keyof TEmitterEvents; diff --git a/app/lib/methods/helpers/goRoom.ts b/app/lib/methods/helpers/goRoom.ts index e66c7714e8d..37655c5ca5f 100644 --- a/app/lib/methods/helpers/goRoom.ts +++ b/app/lib/methods/helpers/goRoom.ts @@ -12,10 +12,10 @@ import { import { getRoomTitle, getUidDirectMessage } from './helpers'; import { createDirectMessage } from '../../services/restApi'; import { emitErrorCreateDirectMessage } from './emitErrorCreateDirectMessage'; -import store from '../../store'; -import { roomsStoreLastVisited } from '../../../actions/rooms'; -import { storeLastVisitedRoom } from './storeLastVisitedRoom'; +// import store from '../../store'; +// import { roomsStoreLastVisited } from '../../../actions/rooms'; import { getRoom } from '../getRoom'; +import { emitter } from './emitter'; interface IGoRoomItem { search?: boolean; // comes from spotlight @@ -104,8 +104,13 @@ export const goRoom = async ({ if (result.success && result?.room?._id) { try { const room = await getRoom(result?.room?.rid || ''); - store.dispatch(roomsStoreLastVisited(result.room._id, room.name)); - } catch {} + emitter.emit('roomVisited', { + rid: result.room._id, + name: room.name + }); + } catch { + // do nothing + } console.log('storing last visited room ===================='); return navigate({ item: { @@ -129,8 +134,11 @@ export const goRoom = async ({ */ let _item = item; if (item.rid) { - const roomName = await getRoom(item.rid); - storeLastVisitedRoom(item.rid, roomName.name); + const room = await getRoom(item.rid); + emitter.emit('roomVisited', { + rid: room.rid, + name: room.name + }); const sub = await getSubscriptionByRoomId(item.rid); if (sub) { _item = sub; diff --git a/app/sagas/index.js b/app/sagas/index.js index 7a167f50204..ebdbbe175ff 100644 --- a/app/sagas/index.js +++ b/app/sagas/index.js @@ -16,11 +16,13 @@ import encryption from './encryption'; import videoConf from './videoConf'; import troubleshootingNotification from './troubleshootingNotification'; import quickActions from './quickActions'; +import roomVisited from './roomVisited'; const root = function* root() { yield all([ init(), quickActions(), + roomVisited(), createChannel(), rooms(), room(), diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index fd9428528af..0af18118db4 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -89,12 +89,12 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { case 'recent': { yield waitForAppReady(); - const rid: string = yield select((state: IApplicationState) => state.rooms.lastVisitedRid); + const rid = (yield select((state: IApplicationState) => state.rooms.lastVisitedRid)) as string | undefined; if (!rid) return; try { - const room: TSubscriptionModel = yield call(waitForRoomInDB, rid); + const room = (yield call(waitForRoomInDB, rid)) as TSubscriptionModel; console.log(room, 'room============================'); yield waitForNavigationReady(); yield put(roomsStoreLastVisited(room.id, room.name)); diff --git a/app/sagas/roomVisited.ts b/app/sagas/roomVisited.ts new file mode 100644 index 00000000000..44204996518 --- /dev/null +++ b/app/sagas/roomVisited.ts @@ -0,0 +1,35 @@ +import { eventChannel } from 'redux-saga'; +import { call, put, take, fork } from 'redux-saga/effects'; +import type { SagaIterator } from 'redux-saga'; + +import { emitter } from '../lib/methods/helpers'; +import { roomsStoreLastVisited } from '../actions/rooms'; +import UserPreferences from '../lib/methods/userPreferences'; +import { LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_Name_KEY } from '../lib/constants/keys'; + +function createRoomVisitedChannel() { + return eventChannel<{ rid: string; name: string }>(emit => { + const handler = ({ rid, name }: { rid: string; name: string }) => { + emit({ rid, name }); + }; + + emitter.on('roomVisited', handler); + return () => emitter.off('roomVisited', handler); + }); +} + +function* watchRoomVisited(): SagaIterator { + const channel = yield call(createRoomVisitedChannel); + + while (true) { + const { rid, name } = yield take(channel); + yield put(roomsStoreLastVisited(rid, name)); + + UserPreferences.setString(LAST_VISITED_ROOM_ID_KEY, rid); + UserPreferences.setString(LAST_VISITED_ROOM_Name_KEY, name); + } +} + +export default function* roomVisited(): SagaIterator { + yield fork(watchRoomVisited); +} diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js index 39058a03228..174325121f7 100644 --- a/app/sagas/rooms.js +++ b/app/sagas/rooms.js @@ -18,7 +18,7 @@ const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) { const serverRecord = yield serversCollection.find(server); return serversDB.write(async () => { - await serverRecord.update((record) => { + await serverRecord.update(record => { record.roomsUpdatedAt = newRoomsUpdatedAt; }); }); @@ -75,12 +75,12 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) { const allRecords = [ ...subsToCreate.map(subscription => - subCollection.prepareCreate((s) => { + subCollection.prepareCreate(s => { s._raw = sanitizedRaw({ id: subscription.rid }, subCollection.schema); return Object.assign(s, subscription); }) ), - ...subsToUpdate.map((subscription) => { + ...subsToUpdate.map(subscription => { try { const newSub = subscriptions.find(s => s._id === subscription._id); if (!newSub) { @@ -99,7 +99,7 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) { return null; } }), - ...subsToDelete.map((subscription) => { + ...subsToDelete.map(subscription => { try { return subscription.prepareDestroyPermanently(); } catch (e) { @@ -108,13 +108,13 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) { } }), ...messagesToCreate.map(message => - messagesCollection.prepareCreate((m) => { + messagesCollection.prepareCreate(m => { m._raw = sanitizedRaw({ id: message._id }, messagesCollection.schema); m.subscription.id = message.rid; return Object.assign(m, message); }) ), - ...messagesToUpdate.map((message) => { + ...messagesToUpdate.map(message => { const newMessage = lastMessages.find(m => m._id === message.id); return message.prepareUpdate(() => { try { From c3643e97c02748f86213767aaf2bb87b1d4b8da2 Mon Sep 17 00:00:00 2001 From: divyanshu-patil Date: Sun, 11 Jan 2026 13:55:32 +0000 Subject: [PATCH 19/38] action: organized translations --- app/i18n/locales/en.json | 8 ++++---- app/i18n/locales/pt-BR.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 041036ac0e5..772163ce9f2 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -328,7 +328,7 @@ "Enter_manually": "Enter manually", "Enter_the_code": "Enter the code we just emailed you.", "Error_Download_file": "Error while downloading file", - "Error_finding_room":"Error finding room in this server\ntry switching server", + "Error_finding_room": "Error finding room in this server\ntry switching server", "Error_incorrect_password": "Incorrect password", "Error_play_video": "There was an error while playing this video", "Error_prefix": "Error: {{message}}", @@ -695,8 +695,8 @@ "Receive_Group_Mentions_Info": "Receive @all and @here mentions", "Receive_Notification": "Receive notification", "Receive_notifications_from": "Receive notifications from {{name}}", - "Recently_used": "Recently used", "Recent_Rooms": "Recent Rooms", + "Recently_used": "Recently used", "Record_audio_message": "Record audio message", "Recording_audio_in_progress": "Recording audio message", "Register": "Register", @@ -843,9 +843,9 @@ "Slash_Topic_Description": "Set topic", "Slash_Topic_Params": "Topic message", "Smileys_and_people": "Smileys and people", + "Something_Wrong?": "Something Wrong?", "Sort_by": "Sort by", "Sound": "Sound", - "Something_Wrong?":"Something Wrong?", "Star": "Star", "Starred": "Starred", "Start_a_call": "Start a call", @@ -981,8 +981,8 @@ "Waiting_for_answer": "Waiting for answer", "Waiting_for_network": "Waiting for network...", "Waiting_for_server_connection": "Waiting for server connection", - "Websocket_disabled": "Websocket is disabled for this workspace.\n{{contact}}", "We_are_here_to_help": "We're here to help", + "Websocket_disabled": "Websocket is disabled for this workspace.\n{{contact}}", "What_are_you_doing_right_now": "What are you doing right now?", "Whats_the_password_for_your_certificate": "What's the password for your certificate?", "Why_do_you_want_to_report": "Why do you want to report?", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index f9398ba9879..b1a16ba5cb5 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -319,7 +319,7 @@ "Enter_manually": "Inserir manualmente", "Enter_the_code": "Insira o código que acabamos de enviar por e-mail.", "Error_Download_file": "Erro ao baixar o arquivo", - "Error_finding_room":"Erro ao encontrar uma sala neste servidor\ntente mudar de servidor.", + "Error_finding_room": "Erro ao encontrar uma sala neste servidor\ntente mudar de servidor.", "Error_incorrect_password": "Senha incorreta", "Error_play_video": "Houve um erro ao reproduzir esse vídeo", "Error_prefix": "Erro: {{message}}", @@ -683,8 +683,8 @@ "Receive_Group_Mentions_Info": "Receber menções @all e @here", "Receive_Notification": "Receber notificação", "Receive_notifications_from": "Receber notificação de {{name}}", - "Recently_used": "Usados recentemente", "Recent_Rooms": "Quartos recentes", + "Recently_used": "Usados recentemente", "Record_audio_message": "Gravar mensagem de áudio", "Register": "Registrar", "Registration_Succeeded": "Registrado com sucesso!", @@ -824,9 +824,9 @@ "Slash_Topic_Description": "Definir tópico", "Slash_Topic_Params": "Mensagem do tópico", "Smileys_and_people": "Carinhas e pessoas", + "Something_Wrong?": "Algo errado?", "Sort_by": "Ordenar por", "Sound": "Som da notificação", - "Something_Wrong?":"Algo errado?", "Star": "Favorito", "Starred": "Mensagens Favoritas", "Start_a_call": "Inicie uma chamada", @@ -963,8 +963,8 @@ "Waiting_for_answer": "Esperando por resposta", "Waiting_for_network": "Aguardando rede...", "Waiting_for_server_connection": "Aguardando conexão com o servidor", - "Websocket_disabled": "Websocket está desativado para essa workspace.\n{{contact}}", "We_are_here_to_help": "Estamos aqui para ajudar.", + "Websocket_disabled": "Websocket está desativado para essa workspace.\n{{contact}}", "What_are_you_doing_right_now": "O que você está fazendo agora?", "Whats_the_password_for_your_certificate": "Qual é a senha para o seu certificado?", "Why_do_you_want_to_report": "Por que você deseja reportar?", From 79dbb31e7032f1641f322e01a273c973c9e49e90 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sun, 11 Jan 2026 20:07:13 +0530 Subject: [PATCH 20/38] cleanup: removed logs, removed unused code --- app/actions/actionsTypes.ts | 8 +---- app/actions/quickActions.ts | 2 -- app/index.tsx | 29 ++++++++++------- app/lib/methods/helpers/goRoom.ts | 13 ++++++-- .../methods/helpers/storeLastVisitedRoom.ts | 11 ------- ...QuickAction.ts => getRecentQuickAction.ts} | 1 - app/lib/quickActions/index.ts | 6 +--- app/reducers/quickActions.ts | 32 ++----------------- app/sagas/quickActions.ts | 10 ------ 9 files changed, 31 insertions(+), 81 deletions(-) delete mode 100644 app/lib/methods/helpers/storeLastVisitedRoom.ts rename app/lib/quickActions/{getInitialQuickAction.ts => getRecentQuickAction.ts} (87%) diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index 5c3fee82f1e..4343679c6d8 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -60,13 +60,7 @@ export const LOGOUT = 'LOGOUT'; // logout is always success export const DELETE_ACCOUNT = 'DELETE_ACCOUNT'; export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']); export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN', 'OPEN_VIDEO_CONF']); -export const QUICK_ACTIONS = createRequestTypes('QUICK_ACTIONS', [ - 'QUICK_ACTION_HANDLE', - 'QUICK_ACTION_HANDLED', - 'SET_PENDING_QUICK_ACTION', - 'CLEAR_PENDING', - 'MARK_LAUNCHED_FROM_QUICK_ACTION' -]); +export const QUICK_ACTIONS = createRequestTypes('QUICK_ACTIONS', ['QUICK_ACTION_HANDLE', 'QUICK_ACTION_HANDLED']); export const NAVIGATION = createRequestTypes('NAVIGATION', ['NAVIGATION_READY']); export const UI = createRequestTypes('UI', ['TRIGGER_SEARCH', 'CLEAR_TRIGGERED_SEARCH']); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); diff --git a/app/actions/quickActions.ts b/app/actions/quickActions.ts index 35955fc3b40..01b3602ae5e 100644 --- a/app/actions/quickActions.ts +++ b/app/actions/quickActions.ts @@ -3,14 +3,12 @@ import { type Action } from 'redux'; import { QUICK_ACTIONS } from './actionsTypes'; interface IQuickActionParams { - type: string; action: string; } interface IQuickAction extends Action { params: Partial; } export function quickActionHandle(params: Partial): IQuickAction { - console.log('call to quick actions'); return { type: QUICK_ACTIONS.QUICK_ACTION_HANDLE, params diff --git a/app/index.tsx b/app/index.tsx index 5010e6d5ffa..20eed013ddb 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -9,7 +9,7 @@ import { KeyboardProvider } from 'react-native-keyboard-controller'; import ResponsiveLayoutProvider from './lib/hooks/useResponsiveLayout/useResponsiveLayout'; import AppContainer from './AppContainer'; import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app'; -import { getRecentQuickAction } from './lib/quickActions/getInitialQuickAction'; +import { getRecentQuickAction } from './lib/quickActions/getRecentQuickAction'; import { deepLinkingOpen } from './actions/deepLinking'; import { quickActionHandle } from './actions/quickActions'; import { ActionSheetProvider } from './containers/ActionSheet'; @@ -63,6 +63,10 @@ interface IState { const parseDeepLinking = (url: string) => { if (url) { + /** + * android only + * restores quick action from intent / Linking + */ if (url.startsWith('rocketchat://quick-action/')) { const action = url.replace('rocketchat://quick-action/', ''); return { @@ -119,14 +123,8 @@ export default class Root extends React.Component<{}, IState> { componentDidMount() { this.listenerTimeout = setTimeout(() => { - Linking.addEventListener('url', async ({ url }) => { + Linking.addEventListener('url', ({ url }) => { const parsedDeepLinkingURL = parseDeepLinking(url); - const quickAction = await getRecentQuickAction(); - console.log(quickAction, 'inside the listerner========================='); - if (parsedDeepLinkingURL?.type === 'quick-action') { - store.dispatch(quickActionHandle({ type: 'quick-action', action: parsedDeepLinkingURL.action })); - return; - } if (parsedDeepLinkingURL) { store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); } @@ -167,17 +165,24 @@ export default class Root extends React.Component<{}, IState> { // Open app from deep linking const deepLinking = await Linking.getInitialURL(); const parsedDeepLinkingURL = parseDeepLinking(deepLinking!); - const quickAction = await getRecentQuickAction(); - console.log(deepLinking, 'deeplinking========================'); + /** + * ios only + * fetched recent quick action stored on native side + * automatically cleared after we call to `getRecentQuickAction()` + */ + const quickAction = await getRecentQuickAction(); if (quickAction) { - store.dispatch(quickActionHandle({ type: 'quick-action', action: quickAction })); + store.dispatch(quickActionHandle({ action: quickAction })); return; } + + // android quick action handling via intent if (parsedDeepLinkingURL?.type === 'quick-action') { - store.dispatch(quickActionHandle({ type: 'quick-action', action: parsedDeepLinkingURL.action })); + store.dispatch(quickActionHandle({ action: parsedDeepLinkingURL.action })); return; } + if (parsedDeepLinkingURL) { store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); return; diff --git a/app/lib/methods/helpers/goRoom.ts b/app/lib/methods/helpers/goRoom.ts index 37655c5ca5f..25e4ddf035d 100644 --- a/app/lib/methods/helpers/goRoom.ts +++ b/app/lib/methods/helpers/goRoom.ts @@ -12,8 +12,6 @@ import { import { getRoomTitle, getUidDirectMessage } from './helpers'; import { createDirectMessage } from '../../services/restApi'; import { emitErrorCreateDirectMessage } from './emitErrorCreateDirectMessage'; -// import store from '../../store'; -// import { roomsStoreLastVisited } from '../../../actions/rooms'; import { getRoom } from '../getRoom'; import { emitter } from './emitter'; @@ -103,7 +101,13 @@ export const goRoom = async ({ const result = await createDirectMessage(username as string); if (result.success && result?.room?._id) { try { + // storing last visited room const room = await getRoom(result?.room?.rid || ''); + + /** + * store.dispatch causing dependency cycle error here + * using emitter based flow to prevent it + */ emitter.emit('roomVisited', { rid: result.room._id, name: room.name @@ -111,7 +115,7 @@ export const goRoom = async ({ } catch { // do nothing } - console.log('storing last visited room ===================='); + return navigate({ item: { rid: result.room._id, @@ -135,10 +139,13 @@ export const goRoom = async ({ let _item = item; if (item.rid) { const room = await getRoom(item.rid); + + // storing last visited room emitter.emit('roomVisited', { rid: room.rid, name: room.name }); + const sub = await getSubscriptionByRoomId(item.rid); if (sub) { _item = sub; diff --git a/app/lib/methods/helpers/storeLastVisitedRoom.ts b/app/lib/methods/helpers/storeLastVisitedRoom.ts deleted file mode 100644 index ea4b86cb06e..00000000000 --- a/app/lib/methods/helpers/storeLastVisitedRoom.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_Name_KEY } from '../../constants/keys'; -import { roomsStoreLastVisited } from '../../../actions/rooms'; -import store from '../../store'; -import UserPreferences from '../userPreferences'; - -export const storeLastVisitedRoom = (rid: string, name: string) => { - UserPreferences.setString(LAST_VISITED_ROOM_ID_KEY, rid); - UserPreferences.setString(LAST_VISITED_ROOM_Name_KEY, name); - store.dispatch(roomsStoreLastVisited(rid, name)); - console.log('storing last visited room ===================='); -}; diff --git a/app/lib/quickActions/getInitialQuickAction.ts b/app/lib/quickActions/getRecentQuickAction.ts similarity index 87% rename from app/lib/quickActions/getInitialQuickAction.ts rename to app/lib/quickActions/getRecentQuickAction.ts index d9b22c54737..90a194bec32 100644 --- a/app/lib/quickActions/getInitialQuickAction.ts +++ b/app/lib/quickActions/getRecentQuickAction.ts @@ -7,7 +7,6 @@ const { QuickActionsConnector } = NativeModules; * Returns the action type once, then clears it on native side. */ export async function getRecentQuickAction(): Promise { - console.log(QuickActionsConnector, 'connector======================='); if (!QuickActionsConnector?.getInitialQuickAction) { return null; } diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index fabfb7d903c..6dc17f3ff7e 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -2,7 +2,7 @@ import * as QuickActions from 'expo-quick-actions'; import { InteractionManager, AppState, Platform } from 'react-native'; import store from '../store'; -import { getRecentQuickAction } from './getInitialQuickAction'; +import { getRecentQuickAction } from './getRecentQuickAction'; import { quickActionHandle } from '../../actions/quickActions'; import I18n from '../../i18n'; @@ -58,14 +58,10 @@ export function registerQuickActions() { } registered = true; - console.log('quickactions registered======================='); - updateQuickActions(); QuickActions.addListener(action => { - console.log(action, 'quickactions======================='); if (!action?.id) { - console.log('return block'); return; } handleQuickAction(action.id); diff --git a/app/reducers/quickActions.ts b/app/reducers/quickActions.ts index 03c0fea5a07..74a839fb02f 100644 --- a/app/reducers/quickActions.ts +++ b/app/reducers/quickActions.ts @@ -5,15 +5,11 @@ import { QUICK_ACTIONS } from '../actions/actionsTypes'; interface IQuickActionsState { lastAction: string | null; handled: boolean; - pending: string | null; - launchedFromQuickAction: boolean; } export const initialState: IQuickActionsState = { lastAction: null, - handled: false, - pending: null, - launchedFromQuickAction: false + handled: false }; interface IQuickActionPayloadAction extends Action { @@ -27,7 +23,6 @@ type QuickActionsAction = IQuickActionPayloadAction | Action; export default function quickActions(state = initialState, action: QuickActionsAction): IQuickActionsState { switch (action.type) { case QUICK_ACTIONS.QUICK_ACTION_HANDLE: - console.log('call to reducer quick action'); if (!('payload' in action) || !action.payload?.action) { return state; } @@ -37,33 +32,10 @@ export default function quickActions(state = initialState, action: QuickActionsA handled: false }; - case QUICK_ACTIONS.SET_PENDING_QUICK_ACTION: - if (!('payload' in action) || !action.payload?.action) { - return state; - } - return { - ...state, - pending: (action as IQuickActionPayloadAction).payload.action - }; - case QUICK_ACTIONS.QUICK_ACTION_HANDLED: return { ...state, - handled: true, - pending: null - }; - - case QUICK_ACTIONS.MARK_LAUNCHED_FROM_QUICK_ACTION: - return { - ...state, - launchedFromQuickAction: true - }; - - case QUICK_ACTIONS.CLEAR_PENDING: - return { - ...state, - pending: null, - launchedFromQuickAction: false + handled: true }; default: diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 0af18118db4..35663955f71 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -52,12 +52,6 @@ function* waitForNavigationReady(): Generator { } function* handleQuickActionOpen(action: IQuickActionOpen): Generator { - // yield take(APP.READY); - - // const state = yield select(); - // if (!state?.quickActions || state.quickActions.handled) { - // return; - // } const quickAction = action.params?.action ?? action.payload?.action; if (!quickAction) { @@ -68,7 +62,6 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { case 'add-server': { const server = UserPreferences.getString(CURRENT_SERVER); - console.log('it reached here=========='); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); yield put(serverInitAdd(server || '')); break; @@ -95,7 +88,6 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { try { const room = (yield call(waitForRoomInDB, rid)) as TSubscriptionModel; - console.log(room, 'room============================'); yield waitForNavigationReady(); yield put(roomsStoreLastVisited(room.id, room.name)); yield call(goRoom, { item: { rid: room.id }, isMasterDetail: true }); @@ -106,8 +98,6 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { break; } - default: - Alert.alert('Other Quick Action', `this is ${quickAction} action`); } yield put({ type: QUICK_ACTIONS.QUICK_ACTION_HANDLED }); From 922583375926605d59ea767ea5e72da84de63d1e Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sun, 11 Jan 2026 22:00:52 +0530 Subject: [PATCH 21/38] fix: coderrabit suggestions --- app/AppContainer.tsx | 7 +++++- app/lib/constants/keys.ts | 2 +- app/lib/methods/helpers/goRoom.ts | 16 ++++++++----- app/lib/quickActions/index.ts | 40 +++++++++++++++++++------------ app/sagas/init.js | 4 ++-- app/sagas/quickActions.ts | 31 ++++++++++++++++++------ app/sagas/roomVisited.ts | 4 ++-- ios/Podfile.lock | 4 ++-- ios/QuickActionsConnector.mm | 3 +-- ios/QuickActionsStore.h | 5 +++- ios/QuickActionsStore.mm | 24 +++++++++++++++++-- package.json | 2 +- yarn.lock | 29 ++++++++++++++++------ 13 files changed, 122 insertions(+), 49 deletions(-) diff --git a/app/AppContainer.tsx b/app/AppContainer.tsx index 0800b16475c..bc8e2106159 100644 --- a/app/AppContainer.tsx +++ b/app/AppContainer.tsx @@ -3,7 +3,7 @@ import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { connect, useDispatch } from 'react-redux'; -import { registerQuickActions, updateQuickActions } from './lib/quickActions'; +import { registerQuickActions, unregisterQuickActions, updateQuickActions } from './lib/quickActions'; import type { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes'; import Navigation from './lib/navigation/appNavigation'; import { defaultHeader, getActiveRouteName, navigationTheme } from './lib/methods/helpers/navigation'; @@ -39,8 +39,13 @@ const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: bool const { theme } = useContext(ThemeContext); const dispatch = useDispatch(); const lastVisitedRoomName = useAppSelector((state: IApplicationState) => state.rooms.lastVisitedName); + useEffect(() => { registerQuickActions(); + + return () => { + unregisterQuickActions(); + }; }, []); useEffect(() => { diff --git a/app/lib/constants/keys.ts b/app/lib/constants/keys.ts index 9428262c319..454cb080085 100644 --- a/app/lib/constants/keys.ts +++ b/app/lib/constants/keys.ts @@ -26,4 +26,4 @@ export const TOKEN_KEY = 'reactnativemeteor_usertoken'; export const CURRENT_SERVER = 'currentServer'; export const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY'; export const LAST_VISITED_ROOM_ID_KEY = 'RC_LAST_VISITED_ROOM_ID_KEY'; -export const LAST_VISITED_ROOM_Name_KEY = 'RC_LAST_VISITED_ROOM_Name_KEY'; +export const LAST_VISITED_ROOM_NAME_KEY = 'RC_LAST_VISITED_ROOM_NAME_KEY'; diff --git a/app/lib/methods/helpers/goRoom.ts b/app/lib/methods/helpers/goRoom.ts index 25e4ddf035d..ae83ee335bc 100644 --- a/app/lib/methods/helpers/goRoom.ts +++ b/app/lib/methods/helpers/goRoom.ts @@ -138,13 +138,17 @@ export const goRoom = async ({ */ let _item = item; if (item.rid) { - const room = await getRoom(item.rid); + try { + const room = await getRoom(item.rid); - // storing last visited room - emitter.emit('roomVisited', { - rid: room.rid, - name: room.name - }); + // storing last visited room + emitter.emit('roomVisited', { + rid: room.rid, + name: room.name + }); + } catch { + // do nothing + } const sub = await getSubscriptionByRoomId(item.rid); if (sub) { diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index 6dc17f3ff7e..089da06d470 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -8,19 +8,11 @@ import I18n from '../../i18n'; let registered = false; -AppState.addEventListener('change', async nextState => { - if (nextState === 'active') { - const nativeAction = await getRecentQuickAction(); - if (nativeAction) { - InteractionManager.runAfterInteractions(() => { - handleQuickAction(nativeAction); - }); - } - } -}); +let quickActionSubscription: { remove(): void } | null = null; +let appStateSubscription: { remove(): void } | null = null; -export function updateQuickActions({ recentRoomName }: { recentRoomName?: string } = {}) { - QuickActions.setItems([ +export async function updateQuickActions({ recentRoomName }: { recentRoomName?: string } = {}) { + await QuickActions.setItems([ { id: 'search', title: I18n.t('Search'), @@ -52,20 +44,38 @@ export function updateQuickActions({ recentRoomName }: { recentRoomName?: string ]); } -export function registerQuickActions() { +export async function registerQuickActions() { if (registered) { return; } registered = true; - updateQuickActions(); + await updateQuickActions(); - QuickActions.addListener(action => { + quickActionSubscription = QuickActions.addListener(action => { if (!action?.id) { return; } handleQuickAction(action.id); }); + + appStateSubscription = AppState.addEventListener('change', async nextState => { + if (nextState === 'active') { + const nativeAction = await getRecentQuickAction(); + if (nativeAction) { + InteractionManager.runAfterInteractions(() => { + handleQuickAction(nativeAction); + }); + } + } + }); +} + +export function unregisterQuickActions() { + quickActionSubscription?.remove(); + appStateSubscription?.remove(); + quickActionSubscription = null; + registered = false; } function handleQuickAction(id: string) { diff --git a/app/sagas/init.js b/app/sagas/init.js index d2500e41227..1bb9147867d 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -2,7 +2,7 @@ import { call, put, select, takeLatest } from 'redux-saga/effects'; import RNBootSplash from 'react-native-bootsplash'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { CURRENT_SERVER, LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_Name_KEY, TOKEN_KEY } from '../lib/constants/keys'; +import { CURRENT_SERVER, LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_NAME_KEY, TOKEN_KEY } from '../lib/constants/keys'; import UserPreferences from '../lib/methods/userPreferences'; import { selectServerRequest } from '../actions/server'; import { setAllPreferences } from '../actions/sortPreferences'; @@ -53,7 +53,7 @@ const restore = function* restore() { } yield put(selectServerRequest(server, serverRecord.version)); const lastVisitedRid = UserPreferences.getString(LAST_VISITED_ROOM_ID_KEY); - const lastVisitedRoomName = UserPreferences.getString(LAST_VISITED_ROOM_Name_KEY); + const lastVisitedRoomName = UserPreferences.getString(LAST_VISITED_ROOM_NAME_KEY); if (lastVisitedRid && lastVisitedRoomName) { yield put(roomsStoreLastVisited(lastVisitedRid, lastVisitedRoomName)); diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 35663955f71..c3539a2c31b 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -1,4 +1,4 @@ -import { select, takeEvery, put, take, type Effect, call } from 'redux-saga/effects'; +import { select, takeEvery, put, take, type Effect, call, race, delay } from 'redux-saga/effects'; import { Alert } from 'react-native'; import { type Action } from 'redux'; @@ -25,7 +25,7 @@ interface IQuickActionOpen extends Action { } function* waitForAppReady(): Generator { - const isReady: boolean = yield select((state: any) => state.app.ready); + const isReady: boolean = yield select((state: IApplicationState) => state.app.ready); if (!isReady) { yield put(appInit()); @@ -37,7 +37,15 @@ function* waitForRoomInDB(rid: string): Generator { try { yield call(getRoom, rid); } catch { - yield take(APP.START); + // Wait for APP.START OR timeout + const { timeout } = yield race({ + started: take(APP.START), + timeout: delay(3000) + }); + + if (timeout) { + throw new Error('Timed out waiting for APP.START'); + } } return yield call(getRoom, rid); @@ -66,7 +74,7 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { yield put(serverInitAdd(server || '')); break; } - case 'search': + case 'search': { yield waitForAppReady(); const currentRoute = Navigation.getCurrentRoute(); @@ -75,16 +83,21 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { } yield put({ type: UI.TRIGGER_SEARCH }); break; - case 'contact': + } + case 'contact': { sendEmail(); yield waitForAppReady(); // if user navigates back to app just init it break; + } case 'recent': { yield waitForAppReady(); const rid = (yield select((state: IApplicationState) => state.rooms.lastVisitedRid)) as string | undefined; - if (!rid) return; + if (!rid) { + showRoomNotFoundError(); + break; + } try { const room = (yield call(waitForRoomInDB, rid)) as TSubscriptionModel; @@ -93,7 +106,7 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { yield call(goRoom, { item: { rid: room.id }, isMasterDetail: true }); } catch (e) { console.log(e); - Alert.alert(I18n.t('Room_not_found'), I18n.t('Error_finding_room')); + showRoomNotFoundError(); } break; @@ -103,6 +116,10 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { yield put({ type: QUICK_ACTIONS.QUICK_ACTION_HANDLED }); } +const showRoomNotFoundError = () => { + Alert.alert(I18n.t('Room_not_found'), I18n.t('Error_finding_room')); +}; + export default function* root(): Generator { yield takeEvery(QUICK_ACTIONS.QUICK_ACTION_HANDLE, handleQuickActionOpen); } diff --git a/app/sagas/roomVisited.ts b/app/sagas/roomVisited.ts index 44204996518..88902f9544e 100644 --- a/app/sagas/roomVisited.ts +++ b/app/sagas/roomVisited.ts @@ -5,7 +5,7 @@ import type { SagaIterator } from 'redux-saga'; import { emitter } from '../lib/methods/helpers'; import { roomsStoreLastVisited } from '../actions/rooms'; import UserPreferences from '../lib/methods/userPreferences'; -import { LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_Name_KEY } from '../lib/constants/keys'; +import { LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_NAME_KEY } from '../lib/constants/keys'; function createRoomVisitedChannel() { return eventChannel<{ rid: string; name: string }>(emit => { @@ -26,7 +26,7 @@ function* watchRoomVisited(): SagaIterator { yield put(roomsStoreLastVisited(rid, name)); UserPreferences.setString(LAST_VISITED_ROOM_ID_KEY, rid); - UserPreferences.setString(LAST_VISITED_ROOM_Name_KEY, name); + UserPreferences.setString(LAST_VISITED_ROOM_NAME_KEY, name); } } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5a46478d13e..c94a12a53a7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -117,7 +117,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - ExpoQuickActions (6.0.0): + - ExpoQuickActions (5.0.0): - ExpoModulesCore - ExpoSystemUI (5.0.7): - ExpoModulesCore @@ -3064,7 +3064,7 @@ SPEC CHECKSUMS: ExpoKeepAwake: 213acedecafb6fda8c0ffedad22ee9e2903400c5 ExpoLocalAuthentication: f69863a1822e42db67a311ce839ecbac70e7fa65 ExpoModulesCore: b4fdeaceca6a4360d4a75fbae335907427c1df6b - ExpoQuickActions: 31a70aa6a606128de4416a4830e09cfabfe6667f + ExpoQuickActions: fdbda7f5874aed3dd2b1d891ec00ab3300dc7541 ExpoSystemUI: 82c970cf8495449698e7343b4f78a0d04bcec9ee ExpoVideoThumbnails: 2a448a23eb4eeb860d92ded372fec6e6a7a0cdcb ExpoWebBrowser: d81a92137919312afc5628d47951dc7a35554f90 diff --git a/ios/QuickActionsConnector.mm b/ios/QuickActionsConnector.mm index 35f6aca68b6..085aff3f34a 100644 --- a/ios/QuickActionsConnector.mm +++ b/ios/QuickActionsConnector.mm @@ -10,8 +10,7 @@ @implementation QuickActionsConnector (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject ) { - NSString *action = RCPendingQuickActionType; - RCPendingQuickActionType = nil; + NSString *action = RCConsumePendingQuickActionType(); resolve(action); } diff --git a/ios/QuickActionsStore.h b/ios/QuickActionsStore.h index 4c99b5771cd..34764e99c73 100644 --- a/ios/QuickActionsStore.h +++ b/ios/QuickActionsStore.h @@ -1,5 +1,8 @@ #import -FOUNDATION_EXPORT NSString * _Nullable RCPendingQuickActionType; +NS_ASSUME_NONNULL_BEGIN FOUNDATION_EXPORT void RCSetPendingQuickActionType(NSString * _Nullable type); +FOUNDATION_EXPORT NSString * _Nullable RCConsumePendingQuickActionType(void); + +NS_ASSUME_NONNULL_END diff --git a/ios/QuickActionsStore.mm b/ios/QuickActionsStore.mm index 987362354fa..d1cf298bcce 100644 --- a/ios/QuickActionsStore.mm +++ b/ios/QuickActionsStore.mm @@ -1,7 +1,27 @@ #import "QuickActionsStore.h" -NSString * _Nullable RCPendingQuickActionType = nil; +static NSString * _Nullable RCPendingQuickActionType = nil; +static dispatch_queue_t rcQuickActionQueue; + +__attribute__((constructor)) +static void RCInitQuickActionQueue(void) { + rcQuickActionQueue = dispatch_queue_create( + "chat.rocket.quickactions.queue", + DISPATCH_QUEUE_SERIAL + ); +} void RCSetPendingQuickActionType(NSString * _Nullable type) { - RCPendingQuickActionType = type; + dispatch_async(rcQuickActionQueue, ^{ + RCPendingQuickActionType = type; + }); +} + +NSString * _Nullable RCConsumePendingQuickActionType(void) { + __block NSString *type = nil; + dispatch_sync(rcQuickActionQueue, ^{ + type = RCPendingQuickActionType; + RCPendingQuickActionType = nil; + }); + return type; } diff --git a/package.json b/package.json index fd0432ef0e3..9d3f1465ebb 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "expo-local-authentication": "16.0.3", "expo-navigation-bar": "^4.2.4", "expo-notifications": "0.32.11", - "expo-quick-actions": "^6.0.0", + "expo-quick-actions": "^5.0.0", "expo-status-bar": "^2.2.3", "expo-system-ui": "^5.0.7", "expo-video-thumbnails": "9.1.2", diff --git a/yarn.lock b/yarn.lock index ba2d6210f9b..2afa4b1d100 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2934,6 +2934,21 @@ temp-dir "~2.0.0" unique-string "~2.0.0" +"@expo/image-utils@~0.7.4": + version "0.7.6" + resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.7.6.tgz#b8442bef770e1c7b39997d57f666bffeeced0a7a" + integrity sha512-GKnMqC79+mo/1AFrmAcUcGfbsXXTRqOMNS1umebuevl3aaw+ztsYEFEiuNhHZW7PQ3Xs3URNT513ZxKhznDscw== + dependencies: + "@expo/spawn-async" "^1.7.2" + chalk "^4.0.0" + getenv "^2.0.0" + jimp-compact "0.16.1" + parse-png "^2.1.0" + resolve-from "^5.0.0" + semver "^7.6.0" + temp-dir "~2.0.0" + unique-string "~2.0.0" + "@expo/json-file@^10.0.8", "@expo/json-file@~10.0.7": version "10.0.8" resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-10.0.8.tgz#05e524d1ecc0011db0a6d66b525ea2f58cfe6d43" @@ -8578,13 +8593,13 @@ expo-notifications@0.32.11: expo-application "~7.0.7" expo-constants "~18.0.8" -expo-quick-actions@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/expo-quick-actions/-/expo-quick-actions-6.0.0.tgz#57c2c664896fcb50190521cec8966c9373389356" - integrity sha512-jxfvuZSiBxlRAggMibfw17L6w/1xsleB1AKJUC8QWQmGZq099hET4ycvZK9szaX4hOk5hqbkTxSLxTCk8qmPLw== +expo-quick-actions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/expo-quick-actions/-/expo-quick-actions-5.0.0.tgz#6b3c5c21190607f779be1588ebaf119577a3c120" + integrity sha512-NSsDhfbal11gXsHkJbvYVw7x0QUCKrEth2kBBKZUv03dX4J7ZPADSV89LyEpOVYXCkrw6LuanlEtKavg/BFaRA== dependencies: - "@expo/image-utils" "^0.8.7" - schema-utils "^4.3.2" + "@expo/image-utils" "~0.7.4" + schema-utils "^4.2.0" sf-symbols-typescript "^2.1.0" expo-status-bar@^2.2.3: @@ -13514,7 +13529,7 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -schema-utils@^4.3.2: +schema-utils@^4.2.0: version "4.3.3" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== From 63c8f1e875ef906555f9dd3621a3e26aeba57206 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sun, 11 Jan 2026 23:45:20 +0530 Subject: [PATCH 22/38] cleanup: unused files --- app/lib/navigation/addServer/index.ts | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 app/lib/navigation/addServer/index.ts diff --git a/app/lib/navigation/addServer/index.ts b/app/lib/navigation/addServer/index.ts deleted file mode 100644 index 0afdfb1c392..00000000000 --- a/app/lib/navigation/addServer/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { batch } from 'react-redux'; - -import { appStart } from '../../../actions/app'; -import { serverInitAdd } from '../../../actions/server'; -import { RootEnum } from '../../../definitions'; -import { logEvent, events } from '../../methods/helpers/log'; -import { type AppDispatch } from '../../store'; - -export function navigateToAddServer(dispatch: AppDispatch, previousServer: string) { - logEvent(events.RL_ADD_SERVER); - - batch(() => { - dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE })); - dispatch(serverInitAdd(previousServer)); - }); -} From b6fc07e81d541d372852dc9b72bbf0ea14111e5f Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sun, 11 Jan 2026 23:59:14 +0530 Subject: [PATCH 23/38] fix: coderabbit suggestions --- app/lib/methods/logout.ts | 11 ++++++++++- app/sagas/quickActions.ts | 2 +- app/sagas/selectServer.ts | 2 ++ ios/QuickActionsStore.mm | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/lib/methods/logout.ts b/app/lib/methods/logout.ts index 69a9643ec2d..080d038549e 100644 --- a/app/lib/methods/logout.ts +++ b/app/lib/methods/logout.ts @@ -2,7 +2,7 @@ import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; import type Model from '@nozbe/watermelondb/Model'; import { getDeviceToken } from '../notifications'; -import { isSsl } from './helpers'; +import { emitter, isSsl } from './helpers'; import { BASIC_AUTH_KEY } from './helpers/fetch'; import database, { getDatabase } from '../database'; import log from './helpers/log'; @@ -24,6 +24,14 @@ function removeServerKeys({ server, userId }: { server: string; userId?: string UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); } +function removeLastVisitedRoomKeys() { + // clearing the state + emitter.emit('roomVisited', { + rid: '', + name: '' + }); +} + export async function removeServerData({ server }: { server: string }): Promise { try { const batch: Model[] = []; @@ -117,4 +125,5 @@ export async function logout({ server }: { server: string }): Promise { await removeServerData({ server }); await removeCurrentServer(); await removeServerDatabase({ server }); + removeLastVisitedRoomKeys(); } diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index c3539a2c31b..8674ceca60f 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -85,7 +85,7 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { break; } case 'contact': { - sendEmail(); + yield call(sendEmail); yield waitForAppReady(); // if user navigates back to app just init it break; } diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts index 5373f6b0fcf..c7cbbedf509 100644 --- a/app/sagas/selectServer.ts +++ b/app/sagas/selectServer.ts @@ -43,6 +43,7 @@ import { appSelector } from '../lib/hooks/useAppSelector'; import { getServerById } from '../lib/database/services/Server'; import { getLoggedUserById } from '../lib/database/services/LoggedUser'; import SSLPinning from '../lib/methods/helpers/sslPinning'; +import { roomsStoreLastVisited } from '../actions/rooms'; const getServerVersion = function (version: string | null) { let validVersion = valid(version); @@ -216,6 +217,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch // we'll set serverVersion as metadata for bugsnag logServerVersion(serverVersion); yield put(selectServerSuccess({ server, version: serverVersion, name: serverInfo?.name || 'Rocket.Chat' })); + yield put(roomsStoreLastVisited('', '')); } catch (e) { yield put(selectServerFailure()); log(e); diff --git a/ios/QuickActionsStore.mm b/ios/QuickActionsStore.mm index d1cf298bcce..c3183db1858 100644 --- a/ios/QuickActionsStore.mm +++ b/ios/QuickActionsStore.mm @@ -12,7 +12,7 @@ static void RCInitQuickActionQueue(void) { } void RCSetPendingQuickActionType(NSString * _Nullable type) { - dispatch_async(rcQuickActionQueue, ^{ + dispatch_sync(rcQuickActionQueue, ^{ RCPendingQuickActionType = type; }); } From 7b1f981fed70ac479de43399cc302c6594d893a5 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 01:28:30 +0530 Subject: [PATCH 24/38] fix: recent room visited clearing in background state --- app/sagas/selectServer.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts index c7cbbedf509..5373f6b0fcf 100644 --- a/app/sagas/selectServer.ts +++ b/app/sagas/selectServer.ts @@ -43,7 +43,6 @@ import { appSelector } from '../lib/hooks/useAppSelector'; import { getServerById } from '../lib/database/services/Server'; import { getLoggedUserById } from '../lib/database/services/LoggedUser'; import SSLPinning from '../lib/methods/helpers/sslPinning'; -import { roomsStoreLastVisited } from '../actions/rooms'; const getServerVersion = function (version: string | null) { let validVersion = valid(version); @@ -217,7 +216,6 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch // we'll set serverVersion as metadata for bugsnag logServerVersion(serverVersion); yield put(selectServerSuccess({ server, version: serverVersion, name: serverInfo?.name || 'Rocket.Chat' })); - yield put(roomsStoreLastVisited('', '')); } catch (e) { yield put(selectServerFailure()); log(e); From b077f16294e3aa4abd03068ce71776b1e5468949 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 01:36:17 +0530 Subject: [PATCH 25/38] fix: eslint --- app/sagas/quickActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 8674ceca60f..0bece0efc0b 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -38,10 +38,10 @@ function* waitForRoomInDB(rid: string): Generator { yield call(getRoom, rid); } catch { // Wait for APP.START OR timeout - const { timeout } = yield race({ + const { timeout } = (yield race({ started: take(APP.START), timeout: delay(3000) - }); + })) as { started?: unknown; timeout?: true }; if (timeout) { throw new Error('Timed out waiting for APP.START'); From b8534cfabf985ffe80c0e4550ab71d5176ffdb57 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 13:57:09 +0530 Subject: [PATCH 26/38] refactor: revert start search hook --- app/views/RoomsListView/hooks/useSearch.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/RoomsListView/hooks/useSearch.ts b/app/views/RoomsListView/hooks/useSearch.ts index 35ac4a8169a..bb09ffb75b4 100644 --- a/app/views/RoomsListView/hooks/useSearch.ts +++ b/app/views/RoomsListView/hooks/useSearch.ts @@ -84,7 +84,8 @@ export const useSearch = () => { const startSearch = useCallback(() => { dispatch({ type: 'START_SEARCH' }); - }, []); + search(''); + }, [search]); const stopSearch = useCallback(() => { dispatch({ type: 'STOP_SEARCH' }); From 8835afd13a816d550218e6c77de8969d57c55db3 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 14:00:28 +0530 Subject: [PATCH 27/38] feat: added tests --- app/reducers/quickactions.test.ts | 67 +++++++++++++++++++++++++++++++ app/reducers/rooms.test.ts | 11 ++++- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 app/reducers/quickactions.test.ts diff --git a/app/reducers/quickactions.test.ts b/app/reducers/quickactions.test.ts new file mode 100644 index 00000000000..f004dcb3b42 --- /dev/null +++ b/app/reducers/quickactions.test.ts @@ -0,0 +1,67 @@ +import { mockedStore } from './mockedStore'; +import { initialState } from './quickActions'; +import { QUICK_ACTIONS } from '../actions/actionsTypes'; + +describe('test quickActions reducer', () => { + it('should return initial state', () => { + const state = mockedStore.getState().quickActions; + expect(state).toEqual(initialState); + }); + + it('should set lastAction and handled=false on QUICK_ACTION_HANDLE', () => { + mockedStore.dispatch({ + type: QUICK_ACTIONS.QUICK_ACTION_HANDLE, + payload: { + action: 'NEW_MESSAGE' + } + }); + + const state = mockedStore.getState().quickActions; + + expect(state.lastAction).toEqual('NEW_MESSAGE'); + expect(state.handled).toEqual(false); + }); + + it('should not modify state if QUICK_ACTION_HANDLE has no payload', () => { + const prevState = mockedStore.getState().quickActions; + + mockedStore.dispatch({ + type: QUICK_ACTIONS.QUICK_ACTION_HANDLE + }); + + const state = mockedStore.getState().quickActions; + expect(state).toEqual(prevState); + }); + + it('should not modify state if QUICK_ACTION_HANDLE payload has no action', () => { + const prevState = mockedStore.getState().quickActions; + + mockedStore.dispatch({ + type: QUICK_ACTIONS.QUICK_ACTION_HANDLE, + payload: {} + }); + + const state = mockedStore.getState().quickActions; + expect(state).toEqual(prevState); + }); + + it('should set handled=true on QUICK_ACTION_HANDLED', () => { + mockedStore.dispatch({ + type: QUICK_ACTIONS.QUICK_ACTION_HANDLED + }); + + const state = mockedStore.getState().quickActions; + expect(state.handled).toEqual(true); + }); + + it('should return same state for unknown action', () => { + const prevState = mockedStore.getState().quickActions; + + mockedStore.dispatch({ + type: 'UNKNOWN_ACTION' + }); + + const state = mockedStore.getState().quickActions; + expect(state).toEqual(prevState); + }); +}); diff --git a/app/reducers/rooms.test.ts b/app/reducers/rooms.test.ts index 1f84137c701..a1d0718adc8 100644 --- a/app/reducers/rooms.test.ts +++ b/app/reducers/rooms.test.ts @@ -1,4 +1,4 @@ -import { roomsFailure, roomsRefresh, roomsRequest, roomsSuccess } from '../actions/rooms'; +import { roomsFailure, roomsRefresh, roomsRequest, roomsStoreLastVisited, roomsSuccess } from '../actions/rooms'; import { mockedStore } from './mockedStore'; import { initialState } from './rooms'; @@ -34,4 +34,13 @@ describe('test selectedUsers reducer', () => { const state = mockedStore.getState().rooms; expect(state.errorMessage).toEqual('error'); }); + + it('should handle storeLastVisited', () => { + mockedStore.dispatch(roomsStoreLastVisited('room-id-123', 'general')); + + const state = mockedStore.getState().rooms; + + expect(state.lastVisitedRid).toEqual('room-id-123'); + expect(state.lastVisitedName).toEqual('general'); + }); }); From 0def82c7cabc06fbb0add65c3f4d3373c5271dab Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 14:19:21 +0530 Subject: [PATCH 28/38] fix, chore: duplicate search firing, used rid in quickactions saga for consistency --- app/sagas/quickActions.ts | 5 +++-- app/views/RoomsListView/hooks/useSearch.ts | 6 ------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 0bece0efc0b..fe18767aa35 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -102,8 +102,9 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { try { const room = (yield call(waitForRoomInDB, rid)) as TSubscriptionModel; yield waitForNavigationReady(); - yield put(roomsStoreLastVisited(room.id, room.name)); - yield call(goRoom, { item: { rid: room.id }, isMasterDetail: true }); + yield put(roomsStoreLastVisited(room.rid, room.name)); + const isMasterDetail = yield select((state: IApplicationState) => state.app.isMasterDetail); + yield call(goRoom, { item: { rid: room.rid }, isMasterDetail }); } catch (e) { console.log(e); showRoomNotFoundError(); diff --git a/app/views/RoomsListView/hooks/useSearch.ts b/app/views/RoomsListView/hooks/useSearch.ts index bb09ffb75b4..5f239121ada 100644 --- a/app/views/RoomsListView/hooks/useSearch.ts +++ b/app/views/RoomsListView/hooks/useSearch.ts @@ -98,12 +98,6 @@ export const useSearch = () => { } }, [startSearch, storeDispatch, triggerSearch]); - useEffect(() => { - if (state.searchEnabled) { - search(''); - } - }, [state.searchEnabled, search]); - return { searching: state.searching, searchEnabled: state.searchEnabled, From 53f02bbb34f2bd436b5c1a90a414b803dd5721c7 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 14:30:31 +0530 Subject: [PATCH 29/38] fix: removed redundant calls to getRoom and roomsStoreLastVisited --- app/sagas/quickActions.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index fe18767aa35..2f6ff084145 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -12,7 +12,6 @@ import Navigation from '../lib/navigation/appNavigation'; import { sendEmail } from '../views/SettingsView'; import { goRoom } from '../lib/methods/helpers/goRoom'; import { getRoom } from '../lib/methods/getRoom'; -import { roomsStoreLastVisited } from '../actions/rooms'; import I18n from '../i18n'; interface IQuickActionOpen extends Action { @@ -35,7 +34,8 @@ function* waitForAppReady(): Generator { function* waitForRoomInDB(rid: string): Generator { try { - yield call(getRoom, rid); + const room = (yield call(getRoom, rid)) as TSubscriptionModel; + return room; } catch { // Wait for APP.START OR timeout const { timeout } = (yield race({ @@ -102,7 +102,6 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { try { const room = (yield call(waitForRoomInDB, rid)) as TSubscriptionModel; yield waitForNavigationReady(); - yield put(roomsStoreLastVisited(room.rid, room.name)); const isMasterDetail = yield select((state: IApplicationState) => state.app.isMasterDetail); yield call(goRoom, { item: { rid: room.rid }, isMasterDetail }); } catch (e) { From 252ae5c386be7b002e2e216e92c974d33e4dbf39 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 20:17:36 +0530 Subject: [PATCH 30/38] cleanup: removed outdated code on MainActivity.kt --- .../chat/rocket/reactnative/MainActivity.kt | 115 ++++++------------ 1 file changed, 37 insertions(+), 78 deletions(-) diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt index 5539e04dec9..4e9807bd9aa 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt @@ -6,16 +6,14 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnable import com.facebook.react.defaults.DefaultReactActivityDelegate import android.os.Bundle +import android.os.Build +import android.os.PersistableBundle import com.zoontek.rnbootsplash.RNBootSplash import android.content.Intent import android.content.res.Configuration -import android.net.Uri -import chat.rocket.reactnative.notification.VideoConfModule -import chat.rocket.reactnative.notification.VideoConfNotification -import com.google.gson.GsonBuilder - import chat.rocket.reactnative.notification.NotificationIntentHandler - +import androidx.core.net.toUri + class MainActivity : ReactActivity() { /** @@ -32,6 +30,7 @@ class MainActivity : ReactActivity() { DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) override fun onCreate(savedInstanceState: Bundle?) { + // quick actions handling intent?.let { embedQuickActionIntoLinking(it) } @@ -39,68 +38,25 @@ class MainActivity : ReactActivity() { RNBootSplash.init(this, R.style.BootTheme) super.onCreate(null) - // Handle video conf action from notification - intent?.let { - handleVideoConfIntent(it) - } - // Handle notification intents intent?.let { NotificationIntentHandler.handleIntent(this, it) } } public override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - // Handle video conf action when activity is already running - embedQuickActionIntoLinking(intent) - handleVideoConfIntent(intent) - } - - private fun handleVideoConfIntent(intent: Intent) { - if (intent.getBooleanExtra("videoConfAction", false)) { - val notificationId = intent.getIntExtra("notificationId", 0) - val event = intent.getStringExtra("event") ?: return - val rid = intent.getStringExtra("rid") ?: "" - val callerId = intent.getStringExtra("callerId") ?: "" - val callerName = intent.getStringExtra("callerName") ?: "" - val host = intent.getStringExtra("host") ?: "" - val callId = intent.getStringExtra("callId") ?: "" - - android.util.Log.d("RocketChat.MainActivity", "Handling video conf intent - event: $event, rid: $rid, host: $host, callId: $callId") - - // Cancel the notification - if (notificationId != 0) { - VideoConfNotification.cancelById(this, notificationId) - } - - // Store action for JS to pick up - include all required fields - val data = mapOf( - "notificationType" to "videoconf", - "rid" to rid, - "event" to event, - "host" to host, - "callId" to callId, - "caller" to mapOf( - "_id" to callerId, - "name" to callerName - ) - ) - - val gson = GsonBuilder().create() - val jsonData = gson.toJson(data) - - android.util.Log.d("RocketChat.MainActivity", "Storing video conf action: $jsonData") + setIntent(intent) - VideoConfModule.storePendingAction(this, jsonData) + // quick actions handling + embedQuickActionIntoLinking(intent) - // Clear the video conf flag to prevent re-processing - intent.removeExtra("videoConfAction") - } - setIntent(intent) - // Handle notification intents when activity is already running NotificationIntentHandler.handleIntent(this, intent) } + override fun invokeDefaultOnBackPressed() { + moveTaskToBack(true) + } + private fun embedQuickActionIntoLinking(intent: Intent) { val action = intent.action ?: return @@ -109,27 +65,38 @@ class MainActivity : ReactActivity() { // Handle Expo quick actions if (action == "expo.modules.quickactions.SHORTCUT") { // Get the PersistableBundle - val shortcutData = intent.getParcelableExtra("shortcut_data") + val shortcutData: PersistableBundle? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra( + "shortcut_data", + PersistableBundle::class.java + ) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra("shortcut_data") + } if (shortcutData != null) { - // Log all keys in the bundle - // kept for debugging later - // android.util.Log.d("RocketChat.QuickAction", "=== Shortcut Data Bundle ===") - // for (key in shortcutData.keySet()) { - // val value = shortcutData.get(key) - // android.util.Log.d("RocketChat.QuickAction", "Key: $key, Value: $value") - // } - // android.util.Log.d("RocketChat.QuickAction", "============================") + /** + * Log all keys in the bundle + * kept for debugging later + */ + /* + android.util.Log.d("RocketChat.QuickAction", "=== Shortcut Data Bundle ===") + for (key in shortcutData.keySet()) { + val value = shortcutData.get(key) + android.util.Log.d("RocketChat.QuickAction", "Key: $key, Value: $value") + } + android.util.Log.d("RocketChat.QuickAction", "============================") + */ // Try to get the shortcut ID from various possible keys val shortcutId = shortcutData.getString("id") ?: shortcutData.getString("shortcutId") ?: shortcutData.getString("android.intent.extra.shortcut.ID") - android.util.Log.d("RocketChat.QuickAction", "Expo shortcut ID: $shortcutId") - if (shortcutId != null) { - val uri = Uri.parse("rocketchat://quick-action/$shortcutId") + val uri = "rocketchat://quick-action/$shortcutId".toUri() android.util.Log.d("RocketChat.QuickAction", "Converted to: $uri") @@ -145,9 +112,7 @@ class MainActivity : ReactActivity() { return } - // Handle non-Expo quick actions - // we does not need this as currently we are setting it from expo only - // TODO: remove later + // skip for non-Expo quick actions (app launches) if (!action.startsWith("chat.rocket.reactnative.")) { android.util.Log.d("RocketChat.QuickAction", "Not a quick action, skipping") return @@ -158,9 +123,7 @@ class MainActivity : ReactActivity() { .lowercase() .replace('_', '-') - val uri = Uri.parse("rocketchat://quick-action/$quickAction") - - android.util.Log.d("RocketChat.QuickAction", "Converted to: $uri") + val uri = "rocketchat://quick-action/$quickAction".toUri() intent.action = Intent.ACTION_VIEW intent.data = uri @@ -168,8 +131,4 @@ class MainActivity : ReactActivity() { android.util.Log.d("RocketChat.QuickAction", "Intent set with data: ${getIntent().data}") } - - override fun invokeDefaultOnBackPressed() { - moveTaskToBack(true) - } } \ No newline at end of file From 824ba8fd500540f6b41cf01ad5c6b180bef761db Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 20:28:29 +0530 Subject: [PATCH 31/38] cleanup: removed debug comments --- .../java/chat/rocket/reactnative/MainActivity.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt index 4e9807bd9aa..c5b729acaf9 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt @@ -77,18 +77,6 @@ class MainActivity : ReactActivity() { } if (shortcutData != null) { - /** - * Log all keys in the bundle - * kept for debugging later - */ - /* - android.util.Log.d("RocketChat.QuickAction", "=== Shortcut Data Bundle ===") - for (key in shortcutData.keySet()) { - val value = shortcutData.get(key) - android.util.Log.d("RocketChat.QuickAction", "Key: $key, Value: $value") - } - android.util.Log.d("RocketChat.QuickAction", "============================") - */ // Try to get the shortcut ID from various possible keys val shortcutId = shortcutData.getString("id") From 5e3f247d4d22aba55d536e4a8d6cf87e9203ba0c Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 20:47:25 +0530 Subject: [PATCH 32/38] refactor: made android logs debug only --- .../chat/rocket/reactnative/MainActivity.kt | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt index c5b729acaf9..06228deef3c 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt @@ -60,7 +60,9 @@ class MainActivity : ReactActivity() { private fun embedQuickActionIntoLinking(intent: Intent) { val action = intent.action ?: return - android.util.Log.d("RocketChat.QuickAction", "Original action: $action") + if (BuildConfig.DEBUG){ + android.util.Log.d("RocketChat.QuickAction", "Original action: $action") + } // Handle Expo quick actions if (action == "expo.modules.quickactions.SHORTCUT") { @@ -86,23 +88,34 @@ class MainActivity : ReactActivity() { if (shortcutId != null) { val uri = "rocketchat://quick-action/$shortcutId".toUri() - android.util.Log.d("RocketChat.QuickAction", "Converted to: $uri") + if (BuildConfig.DEBUG) { + android.util.Log.d("RocketChat.QuickAction", "Converted to: $uri") + } intent.action = Intent.ACTION_VIEW intent.data = uri setIntent(intent) - android.util.Log.d("RocketChat.QuickAction", "Intent set with data: ${getIntent().data}") + if (BuildConfig.DEBUG) { + android.util.Log.d( + "RocketChat.QuickAction", + "Intent set with data: ${getIntent().data}" + ) + } } } else { - android.util.Log.d("RocketChat.QuickAction", "No shortcut_data bundle found") + if (BuildConfig.DEBUG){ + android.util.Log.d("RocketChat.QuickAction", "No shortcut_data bundle found") + } } return } // skip for non-Expo quick actions (app launches) if (!action.startsWith("chat.rocket.reactnative.")) { - android.util.Log.d("RocketChat.QuickAction", "Not a quick action, skipping") + if (BuildConfig.DEBUG) { + android.util.Log.d("RocketChat.QuickAction", "Not a quick action, skipping") + } return } @@ -117,6 +130,8 @@ class MainActivity : ReactActivity() { intent.data = uri setIntent(intent) - android.util.Log.d("RocketChat.QuickAction", "Intent set with data: ${getIntent().data}") + if (BuildConfig.DEBUG){ + android.util.Log.d("RocketChat.QuickAction", "Intent set with data: ${getIntent().data}") + } } } \ No newline at end of file From 0cc9be21d19421081181ba1bba9f31e4e364adb3 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Mon, 12 Jan 2026 21:48:57 +0530 Subject: [PATCH 33/38] refactor: Use Locale.ROOT for locale-stable casing. --- .../src/main/java/chat/rocket/reactnative/MainActivity.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt index 06228deef3c..da574e2767d 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt @@ -13,6 +13,7 @@ import android.content.Intent import android.content.res.Configuration import chat.rocket.reactnative.notification.NotificationIntentHandler import androidx.core.net.toUri +import java.util.Locale class MainActivity : ReactActivity() { @@ -46,6 +47,11 @@ class MainActivity : ReactActivity() { super.onNewIntent(intent) setIntent(intent) + /* Keep this after super: + - onNewIntent is required for background quick actions + - Expo handles background shortcuts via JS listeners + - Normalizing before super breaks that flow + */ // quick actions handling embedQuickActionIntoLinking(intent) @@ -121,7 +127,7 @@ class MainActivity : ReactActivity() { val quickAction = action .removePrefix("chat.rocket.reactnative.") - .lowercase() + .lowercase(Locale.ROOT) .replace('_', '-') val uri = "rocketchat://quick-action/$quickAction".toUri() From 36ea7e055f7fa45dee0fae81b02b85eb92ae3782 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Thu, 15 Jan 2026 02:36:02 +0530 Subject: [PATCH 34/38] feat: add recent 3 rooms in quick action --- app/AppContainer.tsx | 6 +++--- app/actions/actionsTypes.ts | 2 +- app/actions/rooms.ts | 14 ++++++++++++- app/lib/constants/keys.ts | 1 + app/lib/quickActions/index.ts | 36 +++++++++------------------------- app/reducers/rooms.ts | 37 +++++++++++++++++++++++++++++++++-- app/sagas/init.js | 15 ++++++++++++-- app/sagas/quickActions.ts | 11 ++++++++--- app/sagas/roomVisited.ts | 12 ++++++++++-- 9 files changed, 93 insertions(+), 41 deletions(-) diff --git a/app/AppContainer.tsx b/app/AppContainer.tsx index bc8e2106159..8eaabb90f8d 100644 --- a/app/AppContainer.tsx +++ b/app/AppContainer.tsx @@ -38,7 +38,7 @@ const Stack = createStackNavigator(); const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => { const { theme } = useContext(ThemeContext); const dispatch = useDispatch(); - const lastVisitedRoomName = useAppSelector((state: IApplicationState) => state.rooms.lastVisitedName); + const recentRooms = useAppSelector((state: IApplicationState) => state.rooms.recentRooms); useEffect(() => { registerQuickActions(); @@ -58,8 +58,8 @@ const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: bool }, [root]); useEffect(() => { - updateQuickActions({ recentRoomName: lastVisitedRoomName }); - }, [lastVisitedRoomName]); + updateQuickActions({ recentRooms }); + }, [recentRooms]); if (!root) { return null; diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index 4343679c6d8..3b7bb7acac1 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -12,7 +12,7 @@ function createRequestTypes(base = {}, types = defaultTypes): Record ({ + id: `recent/${room.rid}`, + title: room.name, icon: Platform.select({ ios: 'symbol:clock.arrow.trianglehead.counterclockwise.rotate.90', android: 'ic_quickaction_recent' }) - }, - { - id: 'contact', - title: Platform.select({ android: I18n.t('Contact_us'), ios: I18n.t('Something_Wrong?') }) ?? I18n.t('Contact_us'), - subtitle: I18n.t('We_are_here_to_help'), - icon: Platform.select({ - ios: 'symbol:envelope', - android: 'ic_quickaction_contact' - }) - } - ]); + })) + .reverse(); + await QuickActions.setItems(quickActionItems); } export async function registerQuickActions() { diff --git a/app/reducers/rooms.ts b/app/reducers/rooms.ts index 4664b63cee5..24e432501b1 100644 --- a/app/reducers/rooms.ts +++ b/app/reducers/rooms.ts @@ -1,6 +1,10 @@ import { type IRoomsAction } from '../actions/rooms'; import { ROOMS } from '../actions/actionsTypes'; +export interface IRecentRoomsStore { + rid: string; + name: string; +} export interface IRooms { isFetching: boolean; refreshing: boolean; @@ -8,6 +12,7 @@ export interface IRooms { errorMessage: Record | string; lastVisitedRid: string; lastVisitedName: string; + recentRooms: IRecentRoomsStore[]; } export const initialState: IRooms = { @@ -16,7 +21,8 @@ export const initialState: IRooms = { failure: false, errorMessage: {}, lastVisitedRid: '', - lastVisitedName: '' + lastVisitedName: '', + recentRooms: [] }; export default function rooms(state = initialState, action: IRoomsAction): IRooms { @@ -49,10 +55,37 @@ export default function rooms(state = initialState, action: IRoomsAction): IRoom refreshing: true }; case ROOMS.STORE_LAST_VISITED: + const newRoom = { rid: action.lastVisitedRoomId, name: action.lastVisitedRoomName }; + + const existingIndex = state.recentRooms.findIndex(room => room.rid === newRoom.rid); + let updatedRecentRooms: IRecentRoomsStore[]; + + if (existingIndex !== -1) { + // Move existing room to end + updatedRecentRooms = [ + ...state.recentRooms.slice(0, existingIndex), + ...state.recentRooms.slice(existingIndex + 1), + newRoom + ]; + } else { + // Add new room + updatedRecentRooms = [...state.recentRooms, newRoom]; + } + + if (updatedRecentRooms.length > 3) { + updatedRecentRooms = updatedRecentRooms.slice(-3); + } + return { ...state, lastVisitedRid: action.lastVisitedRoomId, - lastVisitedName: action.lastVisitedRoomName + lastVisitedName: action.lastVisitedRoomName, + recentRooms: updatedRecentRooms + }; + case ROOMS.STORE_RECENT_ROOMS: + return { + ...state, + recentRooms: action.recentRooms }; default: diff --git a/app/sagas/init.js b/app/sagas/init.js index 1bb9147867d..8f587531355 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -2,7 +2,13 @@ import { call, put, select, takeLatest } from 'redux-saga/effects'; import RNBootSplash from 'react-native-bootsplash'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { CURRENT_SERVER, LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_NAME_KEY, TOKEN_KEY } from '../lib/constants/keys'; +import { + CURRENT_SERVER, + LAST_VISITED_ROOM_ID_KEY, + LAST_VISITED_ROOM_NAME_KEY, + RECENT_VISITED_ROOMS_KEY, + TOKEN_KEY +} from '../lib/constants/keys'; import UserPreferences from '../lib/methods/userPreferences'; import { selectServerRequest } from '../actions/server'; import { setAllPreferences } from '../actions/sortPreferences'; @@ -15,7 +21,7 @@ import { RootEnum } from '../definitions'; import { getSortPreferences } from '../lib/methods/userPreferencesMethods'; import { deepLinkingClickCallPush } from '../actions/deepLinking'; import { getServerById } from '../lib/database/services/Server'; -import { roomsStoreLastVisited } from '../actions/rooms'; +import { roomsStoreLastVisited, roomsStoreRecentRooms } from '../actions/rooms'; export const initLocalSettings = function* initLocalSettings() { const sortPreferences = getSortPreferences(); @@ -54,6 +60,11 @@ const restore = function* restore() { yield put(selectServerRequest(server, serverRecord.version)); const lastVisitedRid = UserPreferences.getString(LAST_VISITED_ROOM_ID_KEY); const lastVisitedRoomName = UserPreferences.getString(LAST_VISITED_ROOM_NAME_KEY); + const recentRooms = JSON.parse(UserPreferences.getString(RECENT_VISITED_ROOMS_KEY)); + + if (recentRooms) { + yield put(roomsStoreRecentRooms(recentRooms)); + } if (lastVisitedRid && lastVisitedRoomName) { yield put(roomsStoreLastVisited(lastVisitedRid, lastVisitedRoomName)); diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 2f6ff084145..54438a4bcbc 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -60,12 +60,17 @@ function* waitForNavigationReady(): Generator { } function* handleQuickActionOpen(action: IQuickActionOpen): Generator { - const quickAction = action.params?.action ?? action.payload?.action; + const quickActionFromParams = action.params?.action ?? action.payload?.action; - if (!quickAction) { + if (!quickActionFromParams) { return; } + const actionWithId = quickActionFromParams.split('/'); + + const quickAction = actionWithId[0]; + const roomId = actionWithId[1]; + switch (quickAction) { case 'add-server': { const server = UserPreferences.getString(CURRENT_SERVER); @@ -92,7 +97,7 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { case 'recent': { yield waitForAppReady(); - const rid = (yield select((state: IApplicationState) => state.rooms.lastVisitedRid)) as string | undefined; + const rid = roomId; if (!rid) { showRoomNotFoundError(); diff --git a/app/sagas/roomVisited.ts b/app/sagas/roomVisited.ts index 88902f9544e..5f089bf68b4 100644 --- a/app/sagas/roomVisited.ts +++ b/app/sagas/roomVisited.ts @@ -1,11 +1,15 @@ import { eventChannel } from 'redux-saga'; -import { call, put, take, fork } from 'redux-saga/effects'; +import { call, put, take, fork, select } from 'redux-saga/effects'; import type { SagaIterator } from 'redux-saga'; import { emitter } from '../lib/methods/helpers'; import { roomsStoreLastVisited } from '../actions/rooms'; import UserPreferences from '../lib/methods/userPreferences'; -import { LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_NAME_KEY } from '../lib/constants/keys'; +import { LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_NAME_KEY, RECENT_VISITED_ROOMS_KEY } from '../lib/constants/keys'; +import { IApplicationState } from 'definitions'; +import { IRooms } from 'reducers/rooms'; + +const getRecentRooms = (state: IApplicationState) => state.rooms.recentRooms; function createRoomVisitedChannel() { return eventChannel<{ rid: string; name: string }>(emit => { @@ -25,6 +29,10 @@ function* watchRoomVisited(): SagaIterator { const { rid, name } = yield take(channel); yield put(roomsStoreLastVisited(rid, name)); + const recentRooms: IRooms['recentRooms'] = yield select(getRecentRooms); + + UserPreferences.setString(RECENT_VISITED_ROOMS_KEY, JSON.stringify(recentRooms)); + UserPreferences.setString(LAST_VISITED_ROOM_ID_KEY, rid); UserPreferences.setString(LAST_VISITED_ROOM_NAME_KEY, name); } From b898cec14882f53f897098589f785f7cad89255a Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sat, 17 Jan 2026 18:41:16 +0530 Subject: [PATCH 35/38] feat: multi-server rooms in quickactions --- app/lib/methods/getServerList.ts | 10 ++++++++ app/lib/quickActions/index.ts | 42 +++++++++++++++++++++++++------- app/reducers/rooms.ts | 7 +++++- app/sagas/quickActions.ts | 20 ++++++++++++--- 4 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 app/lib/methods/getServerList.ts diff --git a/app/lib/methods/getServerList.ts b/app/lib/methods/getServerList.ts new file mode 100644 index 00000000000..de053a8f90e --- /dev/null +++ b/app/lib/methods/getServerList.ts @@ -0,0 +1,10 @@ +import { type TServerModel } from '../../definitions'; +import database from '../database'; + +export const getServersList = async (): Promise => { + const serversDB = database.servers; + + const servers: TServerModel[] = await serversDB.get('servers').query().fetch(); + + return servers; +}; diff --git a/app/lib/quickActions/index.ts b/app/lib/quickActions/index.ts index 1cc0d0e4e7d..6abab0275b6 100644 --- a/app/lib/quickActions/index.ts +++ b/app/lib/quickActions/index.ts @@ -4,8 +4,9 @@ import { InteractionManager, AppState, Platform } from 'react-native'; import store from '../store'; import { getRecentQuickAction } from './getRecentQuickAction'; import { quickActionHandle } from '../../actions/quickActions'; +import { type IRecentRoomsStore } from '../../reducers/rooms'; +import { getServersList } from '../methods/getServerList'; import I18n from '../../i18n'; -import { IRecentRoomsStore } from 'reducers/rooms'; let registered = false; @@ -13,16 +14,39 @@ let quickActionSubscription: { remove(): void } | null = null; let appStateSubscription: { remove(): void } | null = null; export async function updateQuickActions({ recentRooms }: { recentRooms: IRecentRoomsStore[] } = { recentRooms: [] }) { - const quickActionItems: QuickActions.Action[] = recentRooms - .map(room => ({ - id: `recent/${room.rid}`, - title: room.name, + const serverList = (await getServersList()).map(server => server._raw.id); + const recentRoomQuickActions: QuickActions.Action[] = recentRooms + .map(room => { + if (!room.server) return null; + if (!serverList.includes(room.server)) return null; + + return { + id: `recent/${encodeURIComponent(room.server)}/${room.rid}`, + title: room.name, + subtitle: room.server, + icon: Platform.select({ + ios: 'symbol:clock.arrow.trianglehead.counterclockwise.rotate.90', + android: 'ic_quickaction_recent', + default: undefined + }) + } as QuickActions.Action; + }) + .filter(item => item !== null) + .reverse(); + + const quickActionItems = [ + ...recentRoomQuickActions, + { + id: 'contact', + title: Platform.select({ android: I18n.t('Contact_us'), ios: I18n.t('Something_Wrong?') }) ?? I18n.t('Contact_us'), + subtitle: I18n.t('We_are_here_to_help'), icon: Platform.select({ - ios: 'symbol:clock.arrow.trianglehead.counterclockwise.rotate.90', - android: 'ic_quickaction_recent' + ios: 'symbol:envelope', + android: 'ic_quickaction_contact' }) - })) - .reverse(); + } + ]; + await QuickActions.setItems(quickActionItems); } diff --git a/app/reducers/rooms.ts b/app/reducers/rooms.ts index 24e432501b1..7aa5c08c637 100644 --- a/app/reducers/rooms.ts +++ b/app/reducers/rooms.ts @@ -1,10 +1,14 @@ import { type IRoomsAction } from '../actions/rooms'; import { ROOMS } from '../actions/actionsTypes'; +import UserPreferences from '../lib/methods/userPreferences'; +import { CURRENT_SERVER } from '../lib/constants/keys'; export interface IRecentRoomsStore { rid: string; name: string; + server: string | null; } + export interface IRooms { isFetching: boolean; refreshing: boolean; @@ -55,7 +59,8 @@ export default function rooms(state = initialState, action: IRoomsAction): IRoom refreshing: true }; case ROOMS.STORE_LAST_VISITED: - const newRoom = { rid: action.lastVisitedRoomId, name: action.lastVisitedRoomName }; + const server = UserPreferences.getString(CURRENT_SERVER); + const newRoom = { rid: action.lastVisitedRoomId, name: action.lastVisitedRoomName, server }; const existingIndex = state.recentRooms.findIndex(room => room.rid === newRoom.rid); let updatedRecentRooms: IRecentRoomsStore[]; diff --git a/app/sagas/quickActions.ts b/app/sagas/quickActions.ts index 54438a4bcbc..91955aa08a8 100644 --- a/app/sagas/quickActions.ts +++ b/app/sagas/quickActions.ts @@ -4,8 +4,8 @@ import { type Action } from 'redux'; import { QUICK_ACTIONS, APP, UI, NAVIGATION } from '../actions/actionsTypes'; import { appStart, appInit } from '../actions/app'; -import { serverInitAdd } from '../actions/server'; -import { type IApplicationState, RootEnum, type TSubscriptionModel } from '../definitions'; +import { selectServerRequest, serverInitAdd } from '../actions/server'; +import { type IApplicationState, RootEnum, type TServerModel, type TSubscriptionModel } from '../definitions'; import UserPreferences from '../lib/methods/userPreferences'; import { CURRENT_SERVER } from '../lib/constants/keys'; import Navigation from '../lib/navigation/appNavigation'; @@ -13,6 +13,7 @@ import { sendEmail } from '../views/SettingsView'; import { goRoom } from '../lib/methods/helpers/goRoom'; import { getRoom } from '../lib/methods/getRoom'; import I18n from '../i18n'; +import { getServerById } from '../lib/database/services/Server'; interface IQuickActionOpen extends Action { params?: { @@ -59,6 +60,11 @@ function* waitForNavigationReady(): Generator { yield take(NAVIGATION.NAVIGATION_READY); } +function* switchServer(targetServer: string): Generator { + const server = (yield call(getServerById, targetServer)) as TServerModel; + yield put(selectServerRequest(server._raw.id, server.version, true, true)); +} + function* handleQuickActionOpen(action: IQuickActionOpen): Generator { const quickActionFromParams = action.params?.action ?? action.payload?.action; @@ -69,7 +75,6 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { const actionWithId = quickActionFromParams.split('/'); const quickAction = actionWithId[0]; - const roomId = actionWithId[1]; switch (quickAction) { case 'add-server': { @@ -97,13 +102,20 @@ function* handleQuickActionOpen(action: IQuickActionOpen): Generator { case 'recent': { yield waitForAppReady(); - const rid = roomId; + const targetServer = decodeURIComponent(actionWithId[1]); + const currentServer: string = yield select((state: IApplicationState) => state.server.server); + + const rid = actionWithId[2]; if (!rid) { showRoomNotFoundError(); break; } + if (currentServer !== targetServer) { + yield call(switchServer, targetServer); + } + try { const room = (yield call(waitForRoomInDB, rid)) as TSubscriptionModel; yield waitForNavigationReady(); From cb629221f82b989a1452c07d80c70f0f23ed1339 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sat, 17 Jan 2026 18:45:33 +0530 Subject: [PATCH 36/38] fix: linting errors --- app/sagas/roomVisited.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/sagas/roomVisited.ts b/app/sagas/roomVisited.ts index 5f089bf68b4..fcb772c3da7 100644 --- a/app/sagas/roomVisited.ts +++ b/app/sagas/roomVisited.ts @@ -6,8 +6,8 @@ import { emitter } from '../lib/methods/helpers'; import { roomsStoreLastVisited } from '../actions/rooms'; import UserPreferences from '../lib/methods/userPreferences'; import { LAST_VISITED_ROOM_ID_KEY, LAST_VISITED_ROOM_NAME_KEY, RECENT_VISITED_ROOMS_KEY } from '../lib/constants/keys'; -import { IApplicationState } from 'definitions'; -import { IRooms } from 'reducers/rooms'; +import { type IApplicationState } from '../definitions'; +import { type IRooms } from '../reducers/rooms'; const getRecentRooms = (state: IApplicationState) => state.rooms.recentRooms; From f9d04ab34432061a3cdb2789ccd4c8509da01b82 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sat, 17 Jan 2026 18:45:44 +0530 Subject: [PATCH 37/38] snapshot update --- .../Avatar/__snapshots__/Avatar.test.tsx.snap | 64 ++++++++++++++----- .../Chip/__snapshots__/Chip.test.tsx.snap | 12 +++- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap index 97d9de5ea35..2e201a58eb5 100644 --- a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap +++ b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap @@ -37,7 +37,9 @@ exports[`Story Snapshots: AvatarExternalProviderUrl should match snapshot 1`] = source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://hips.hearstapps.com/hmg-prod/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg?crop=0.752xw:1.00xh;0.175xw,0&resize=1200:*&format=png&size=112", }, ] @@ -92,7 +94,9 @@ exports[`Story Snapshots: AvatarPath should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=112", }, ] @@ -147,7 +151,9 @@ exports[`Story Snapshots: AvatarRoomId should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/room/devWBbYr7inwupPqK?format=png&size=112", }, ] @@ -202,7 +208,9 @@ exports[`Story Snapshots: AvatarText should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -257,7 +265,9 @@ exports[`Story Snapshots: AvatarUrl should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg", }, ] @@ -312,7 +322,9 @@ exports[`Story Snapshots: Channel should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/@general?format=png&size=112", }, ] @@ -367,7 +379,9 @@ exports[`Story Snapshots: Children should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -462,7 +476,9 @@ exports[`Story Snapshots: CustomBorderRadius should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -519,7 +535,9 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -574,7 +592,9 @@ exports[`Story Snapshots: Direct should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=112", }, ] @@ -696,7 +716,9 @@ exports[`Story Snapshots: RoomAvatarExternalProviderUrl should match snapshot 1` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://cdn.pensador.com/img/authors/ho/me/homer-simpson-l.jpg?format=png&size=112", }, ] @@ -751,7 +773,9 @@ exports[`Story Snapshots: Static should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg", }, ] @@ -840,7 +864,9 @@ exports[`Story Snapshots: Touchable should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -896,7 +922,9 @@ exports[`Story Snapshots: WithETag should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=112&etag=5ag8KffJcZj9m5rCv", }, ] @@ -951,7 +979,9 @@ exports[`Story Snapshots: WithoutETag should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=112", }, ] @@ -1006,7 +1036,9 @@ exports[`Story Snapshots: WrongServer should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://google.com/avatar/Avatar?format=png&size=112", }, ] diff --git a/app/containers/Chip/__snapshots__/Chip.test.tsx.snap b/app/containers/Chip/__snapshots__/Chip.test.tsx.snap index 58de1a6452d..8a26f96352e 100644 --- a/app/containers/Chip/__snapshots__/Chip.test.tsx.snap +++ b/app/containers/Chip/__snapshots__/Chip.test.tsx.snap @@ -207,7 +207,9 @@ exports[`Story Snapshots: ChipText should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=56", }, ] @@ -389,7 +391,9 @@ exports[`Story Snapshots: ChipWithShortText should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=56", }, ] @@ -799,7 +803,9 @@ exports[`Story Snapshots: ChipWithoutIcon should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=56", }, ] From 91a012f7accf5b46ba94b22b053a56c0b68ab4d1 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Sat, 17 Jan 2026 18:57:12 +0530 Subject: [PATCH 38/38] fix: rooms with same id not appearing simultaneously in the quickaction --- app/reducers/rooms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/reducers/rooms.ts b/app/reducers/rooms.ts index 7aa5c08c637..17dcb8e23bf 100644 --- a/app/reducers/rooms.ts +++ b/app/reducers/rooms.ts @@ -62,7 +62,7 @@ export default function rooms(state = initialState, action: IRoomsAction): IRoom const server = UserPreferences.getString(CURRENT_SERVER); const newRoom = { rid: action.lastVisitedRoomId, name: action.lastVisitedRoomName, server }; - const existingIndex = state.recentRooms.findIndex(room => room.rid === newRoom.rid); + const existingIndex = state.recentRooms.findIndex(room => room.rid === newRoom.rid && room.server === newRoom.server); let updatedRecentRooms: IRecentRoomsStore[]; if (existingIndex !== -1) {