Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8b794e0
feat: initial setup
divyanshu-patil Jan 6, 2026
214a4be
feat: added native deeplinking implementation for ios
divyanshu-patil Jan 6, 2026
2f4ef8b
refactor: quickactions
divyanshu-patil Jan 6, 2026
8ec0c3b
fixed navigation in expo managed quick actions
divyanshu-patil Jan 6, 2026
f22a810
feat: quick actions android
divyanshu-patil Jan 7, 2026
acca319
Merge remote-tracking branch 'upstream/develop' into feat/quickaction…
divyanshu-patil Jan 7, 2026
de9516d
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.R…
divyanshu-patil Jan 8, 2026
6bbdd4c
feat: added prev server
divyanshu-patil Jan 8, 2026
26c02ec
feat: search
divyanshu-patil Jan 8, 2026
034636e
Merge branch 'develop' into feat/quickactions_native
divyanshu-patil Jan 8, 2026
3f56cdf
refactor: folders in ios
divyanshu-patil Jan 8, 2026
8d628f8
feat: contact us option
divyanshu-patil Jan 9, 2026
7f980b9
Merge branch 'develop' into feat/quickactions_native
divyanshu-patil Jan 9, 2026
b99d1da
feat: store last visited room id
divyanshu-patil Jan 9, 2026
dc7349d
cleanup: delete empty file
divyanshu-patil Jan 9, 2026
b85f1af
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.R…
divyanshu-patil Jan 9, 2026
c369283
pod install
divyanshu-patil Jan 9, 2026
fc0c06e
feat: get room and navigate to room
divyanshu-patil Jan 9, 2026
498cf70
feat: added dynamic room name
divyanshu-patil Jan 9, 2026
2e8a7b3
fix: android navigation not working recent room
divyanshu-patil Jan 9, 2026
56ff591
fix: roomname not updating in recent quickaction
divyanshu-patil Jan 9, 2026
5ad13db
feat: i18n
divyanshu-patil Jan 11, 2026
47f01f9
fix: cycle dependency error on goRoom | migrate to event architecture…
divyanshu-patil Jan 11, 2026
c3643e9
action: organized translations
divyanshu-patil Jan 11, 2026
79dbb31
cleanup: removed logs, removed unused code
divyanshu-patil Jan 11, 2026
9225833
fix: coderrabit suggestions
divyanshu-patil Jan 11, 2026
63c8f1e
cleanup: unused files
divyanshu-patil Jan 11, 2026
b6fc07e
fix: coderabbit suggestions
divyanshu-patil Jan 11, 2026
7b1f981
fix: recent room visited clearing in background state
divyanshu-patil Jan 11, 2026
b077f16
fix: eslint
divyanshu-patil Jan 11, 2026
b8534cf
refactor: revert start search hook
divyanshu-patil Jan 12, 2026
8835afd
feat: added tests
divyanshu-patil Jan 12, 2026
0def82c
fix, chore: duplicate search firing, used rid in quickactions saga fo…
divyanshu-patil Jan 12, 2026
53f02bb
fix: removed redundant calls to getRoom and roomsStoreLastVisited
divyanshu-patil Jan 12, 2026
252ae5c
cleanup: removed outdated code on MainActivity.kt
divyanshu-patil Jan 12, 2026
824ba8f
cleanup: removed debug comments
divyanshu-patil Jan 12, 2026
5e3f247
refactor: made android logs debug only
divyanshu-patil Jan 12, 2026
0cc9be2
refactor: Use Locale.ROOT for locale-stable casing.
divyanshu-patil Jan 12, 2026
36ea7e0
feat: add recent 3 rooms in quick action
divyanshu-patil Jan 14, 2026
d9cc646
Merge branch 'develop' into feat/quickactions_native
divyanshu-patil Jan 17, 2026
b898cec
feat: multi-server rooms in quickactions
divyanshu-patil Jan 17, 2026
cb62922
fix: linting errors
divyanshu-patil Jan 17, 2026
f9d04ab
snapshot update
divyanshu-patil Jan 17, 2026
91a012f
fix: rooms with same id not appearing simultaneously in the quickaction
divyanshu-patil Jan 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 102 additions & 7 deletions android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
package chat.rocket.reactnative

import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
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 chat.rocket.reactnative.notification.NotificationIntentHandler

import androidx.core.net.toUri
import java.util.Locale

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]
Expand All @@ -27,22 +31,113 @@ class MainActivity : ReactActivity() {
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)

override fun onCreate(savedInstanceState: Bundle?) {
// quick actions handling
intent?.let {
embedQuickActionIntoLinking(it)
}

RNBootSplash.init(this, R.style.BootTheme)
super.onCreate(null)

// Handle notification intents
intent?.let { NotificationIntentHandler.handleIntent(this, it) }
}

public override fun onNewIntent(intent: Intent) {
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)

// 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

if (BuildConfig.DEBUG){
android.util.Log.d("RocketChat.QuickAction", "Original action: $action")
}

// Handle Expo quick actions
if (action == "expo.modules.quickactions.SHORTCUT") {
// Get the PersistableBundle
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) {

// 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")

if (shortcutId != null) {
val uri = "rocketchat://quick-action/$shortcutId".toUri()

if (BuildConfig.DEBUG) {
android.util.Log.d("RocketChat.QuickAction", "Converted to: $uri")
}

intent.action = Intent.ACTION_VIEW
intent.data = uri
setIntent(intent)

if (BuildConfig.DEBUG) {
android.util.Log.d(
"RocketChat.QuickAction",
"Intent set with data: ${getIntent().data}"
)
}
}
} else {
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.")) {
if (BuildConfig.DEBUG) {
android.util.Log.d("RocketChat.QuickAction", "Not a quick action, skipping")
}
return
}

val quickAction = action
.removePrefix("chat.rocket.reactnative.")
.lowercase(Locale.ROOT)
.replace('_', '-')

val uri = "rocketchat://quick-action/$quickAction".toUri()

intent.action = Intent.ACTION_VIEW
intent.data = uri
setIntent(intent)

if (BuildConfig.DEBUG){
android.util.Log.d("RocketChat.QuickAction", "Intent set with data: ${getIntent().data}")
}
}
}
5 changes: 5 additions & 0 deletions android/app/src/main/res/drawable/ic_quickaction_add.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/black" android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/>

</vector>
5 changes: 5 additions & 0 deletions android/app/src/main/res/drawable/ic_quickaction_contact.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/black" android:pathData="M11,23.59v-3.6c-5.01,-0.26 -9,-4.42 -9,-9.49C2,5.26 6.26,1 11.5,1S21,5.26 21,10.5c0,4.95 -3.44,9.93 -8.57,12.4l-1.43,0.69zM11.5,3C7.36,3 4,6.36 4,10.5S7.36,18 11.5,18L13,18v2.3c3.64,-2.3 6,-6.08 6,-9.8C19,6.36 15.64,3 11.5,3zM10.5,14.5h2v2h-2zM12.5,13h-2c0,-3.25 3,-3 3,-5 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2h-2c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,2.5 -3,2.75 -3,5z" android:strokeWidth="0"/>

</vector>
5 changes: 5 additions & 0 deletions android/app/src/main/res/drawable/ic_quickaction_find.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="512" android:viewportWidth="512" android:width="24dp">

<path android:fillColor="@android:color/black" android:pathData="M416,208c0,45.9 -14.9,88.3 -40,122.7L502.6,457.4c12.5,12.5 12.5,32.8 0,45.3s-32.8,12.5 -45.3,0L330.7,376c-34.4,25.2 -76.8,40 -122.7,40C93.1,416 0,322.9 0,208S93.1,0 208,0S416,93.1 416,208zM208,352a144,144 0,1 0,0 -288,144 144,0 1,0 0,288z" android:strokeWidth="0"/>

</vector>
9 changes: 9 additions & 0 deletions android/app/src/main/res/drawable/ic_quickaction_recent.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M75,75L41,41C25.9,25.9 0,36.6 0,57.9L0,168c0,13.3 10.7,24 24,24l110.1,0c21.4,0 32.1,-25.9 17,-41l-30.8,-30.8C155,85.5 203,64 256,64c106,0 192,86 192,192s-86,192 -192,192c-40.8,0 -78.6,-12.7 -109.7,-34.4c-14.5,-10.1 -34.4,-6.6 -44.6,7.9s-6.6,34.4 7.9,44.6C151.2,495 201.7,512 256,512c141.4,0 256,-114.6 256,-256S397.4,0 256,0C185.3,0 121.3,28.7 75,75zM256,128c-13.3,0 -24,10.7 -24,24l0,104c0,6.4 2.5,12.5 7,17l72,72c9.4,9.4 24.6,9.4 33.9,0s9.4,-24.6 0,-33.9l-65,-65 0,-94.1c0,-13.3 -10.7,-24 -24,-24z"
android:fillColor="@android:color/black"/>
</vector>
23 changes: 21 additions & 2 deletions app/AppContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
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, 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';
import { RootEnum } from './definitions';
import { type IApplicationState, RootEnum } from './definitions';
// Stacks
import AuthLoadingView from './views/AuthLoadingView';
// SetUsername Stack
Expand All @@ -19,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 { NAVIGATION } from './actions/actionsTypes';

const createStackNavigator = createNativeStackNavigator;

Expand All @@ -34,6 +37,17 @@ const SetUsernameStack = () => (
const Stack = createStackNavigator<StackParamList>();
const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => {
const { theme } = useContext(ThemeContext);
const dispatch = useDispatch();
const recentRooms = useAppSelector((state: IApplicationState) => state.rooms.recentRooms);

useEffect(() => {
registerQuickActions();

return () => {
unregisterQuickActions();
};
}, []);

useEffect(() => {
if (root) {
const state = Navigation.navigationRef.current?.getRootState();
Expand All @@ -43,6 +57,10 @@ const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: bool
}
}, [root]);

useEffect(() => {
updateQuickActions({ recentRooms });
}, [recentRooms]);

if (!root) {
return null;
}
Expand All @@ -55,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;
Expand Down
5 changes: 4 additions & 1 deletion app/actions/actionsTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function createRequestTypes(base = {}, types = defaultTypes): Record<string, str
export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_SERVICES', 'SET_PREFERENCE', 'SET_LOCAL_AUTHENTICATED']);
export const SHARE = createRequestTypes('SHARE', ['SET_PARAMS']);
export const USER = createRequestTypes('USER', ['SET', 'CLEAR']);
export const ROOMS = createRequestTypes('ROOMS', [...defaultTypes, 'REFRESH']);
export const ROOMS = createRequestTypes('ROOMS', [...defaultTypes, 'REFRESH', 'STORE_LAST_VISITED', 'STORE_RECENT_ROOMS']);
export const ROOM = createRequestTypes('ROOM', [
'SUBSCRIBE',
'UNSUBSCRIBE',
Expand Down Expand Up @@ -60,6 +60,9 @@ 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 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';
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'CLEAR']);
Expand Down
16 changes: 16 additions & 0 deletions app/actions/quickActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type Action } from 'redux';

import { QUICK_ACTIONS } from './actionsTypes';

interface IQuickActionParams {
action: string;
}
interface IQuickAction extends Action {
params: Partial<IQuickActionParams>;
}
export function quickActionHandle(params: Partial<IQuickActionParams>): IQuickAction {
return {
type: QUICK_ACTIONS.QUICK_ACTION_HANDLE,
params
};
}
27 changes: 26 additions & 1 deletion app/actions/rooms.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type Action } from 'redux';

import { ROOMS } from './actionsTypes';
import { type IRecentRoomsStore } from '../reducers/rooms';

export interface IRoomsRequest extends Action {
params: any;
Expand All @@ -14,7 +15,16 @@ export interface IRoomsFailure extends Action {
err: Record<string, any> | string;
}

export type IRoomsAction = IRoomsRequest & ISetSearch & IRoomsFailure;
export interface IRoomsLastVisited extends Action {
lastVisitedRoomId: string;
lastVisitedRoomName: string;
}

export interface IRecentRooms extends Action {
recentRooms: IRecentRoomsStore[];
}

export type IRoomsAction = IRoomsRequest & ISetSearch & IRoomsFailure & IRoomsLastVisited & IRecentRooms;

export function roomsRequest(
params: {
Expand Down Expand Up @@ -45,3 +55,18 @@ export function roomsRefresh(): Action {
type: ROOMS.REFRESH
};
}

export function roomsStoreLastVisited(rid: string, name: string): IRoomsLastVisited {
return {
type: ROOMS.STORE_LAST_VISITED,
lastVisitedRoomId: rid,
lastVisitedRoomName: name
};
}

export function roomsStoreRecentRooms(recentRooms: IRecentRoomsStore[]): IRecentRooms {
return {
type: ROOMS.STORE_RECENT_ROOMS,
recentRooms
};
}
Loading