Skip to content
Draft
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
39 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
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
135 changes: 131 additions & 4 deletions android/app/src/main/java/chat/rocket/reactnative/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -9,16 +9,21 @@ 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

import chat.rocket.reactnative.notification.NotificationIntentHandler

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,21 +32,143 @@ 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)
}

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

VideoConfModule.storePendingAction(this, jsonData)

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

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<android.os.PersistableBundle>("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)
}
Expand Down
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 lastVisitedRoomName = useAppSelector((state: IApplicationState) => state.rooms.lastVisitedName);

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({ recentRoomName: lastVisitedRoomName });
}, [lastVisitedRoomName]);

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']);
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
};
}
15 changes: 14 additions & 1 deletion app/actions/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ 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 type IRoomsAction = IRoomsRequest & ISetSearch & IRoomsFailure & IRoomsLastVisited;

export function roomsRequest(
params: {
Expand Down Expand Up @@ -45,3 +50,11 @@ 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
};
}
2 changes: 2 additions & 0 deletions app/definitions/redux/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -76,6 +77,7 @@ export interface IApplicationState {
troubleshootingNotification: ITroubleshootingNotification;
supportedVersions: ISupportedVersionsState;
inAppFeedback: IInAppFeedbackState;
ui: IUIState;
}

export type TApplicationActions = TActionActiveUsers &
Expand Down
5 changes: 5 additions & 0 deletions app/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}}",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -693,6 +695,7 @@
"Receive_Group_Mentions_Info": "Receive @all and @here mentions",
"Receive_Notification": "Receive notification",
"Receive_notifications_from": "Receive notifications from {{name}}",
"Recent_Rooms": "Recent Rooms",
"Recently_used": "Recently used",
"Record_audio_message": "Record audio message",
"Recording_audio_in_progress": "Recording audio message",
Expand Down Expand Up @@ -840,6 +843,7 @@
"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",
"Star": "Star",
Expand Down Expand Up @@ -977,6 +981,7 @@
"Waiting_for_answer": "Waiting for answer",
"Waiting_for_network": "Waiting for network...",
"Waiting_for_server_connection": "Waiting for server connection",
"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?",
Expand Down
Loading