Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fca8b8e
chore: Bump version to 4.73.0 (#7318)
diegolmello May 11, 2026
d460448
fix: render Attachment file name in Thread Message preview when body …
diegolmello May 14, 2026
825d547
fix: preference value changes causing reset to other option (#7313)
Rohit3523 May 14, 2026
7f1d9de
fix: do not encrypt messages when workspace E2E is disabled (#7324)
OtavioStasiak May 14, 2026
b2bec0e
chore: migrate package manager from yarn to pnpm (#7303)
diegolmello May 18, 2026
3d01def
fix: emoji picker scroll does not reach the end (#7312)
OtavioStasiak May 19, 2026
73c5a34
chore: upgrade to react-native-mmkv v4 (#6991)
Rohit3523 May 20, 2026
66571ed
chore: begin Experimental app sunset (#7320)
diegolmello May 20, 2026
e43a4ec
feat(a11y): alt text (#7163)
OtavioStasiak May 20, 2026
054e84c
chore: stop publishing Experimental app (#7321)
diegolmello May 20, 2026
35e5ad1
chore: delete Experimental code paths and assets (#7322)
diegolmello May 21, 2026
21b0fbb
refactor(native): flatten "official" naming across Android, iOS, CI (…
diegolmello May 21, 2026
2b85573
chore(ci): point build workflows at renamed GitHub environments (#7335)
diegolmello May 21, 2026
8431ff2
feat(a11y): Message actions announcement and back focus (#7229)
OtavioStasiak May 21, 2026
85ab3f4
fix: ActionSheet cuts off the bottom content (#7326)
OtavioStasiak May 25, 2026
deb23d9
fix(android): set RC Mobile User-Agent on push notification avatar fe…
diegolmello May 25, 2026
612dd1a
chore(ci): drop iOS upload-hold gate from PR build flow (#7344)
diegolmello May 25, 2026
6b21875
fix: don't cancel login saga on 2s timeout (stranded login screen) (#…
diegolmello May 26, 2026
540d5e5
fix: prevent crash on Android devices without telecom feature (#7334)
diegolmello May 26, 2026
d0de42f
fix: Drain lost hangups on WebSocket reconnect (#7345)
diegolmello May 26, 2026
18a1039
fix: SAML login fails due to double login race (#7343)
Rohit3523 May 26, 2026
dfdd5fd
fix(a11y): replace composer auto-focus with header focus in RoomView …
OtavioStasiak May 26, 2026
9658b94
chore: align local defaults with chat.rocket.android / chat.rocket.io…
diegolmello May 27, 2026
41444f4
fix(android/voip): start microphone FGS for outgoing calls so audio s…
diegolmello May 27, 2026
3961d4d
fix(ci): restore iOS upload on develop after upload-hold removal (#7352)
diegolmello May 27, 2026
815ff6c
fix: load 50 messages with hideSystemMessages as true on old workspac…
OtavioStasiak May 28, 2026
e43a181
fix: Deeplink auth when host is already connected (#7358)
yash-rajpal Jun 1, 2026
cd6f2a8
fix: re-subscribe rooms stream after forced socket reopen (#7362)
diegolmello Jun 1, 2026
07aee52
chore: Bump version to 4.74.0 (#7369)
diegolmello Jun 1, 2026
bf2ab6e
chore: Bump version to 4.73.1
diegolmello Jun 1, 2026
cfc2687
Merge remote-tracking branch 'origin/develop' into 4.73.1
diegolmello Jun 1, 2026
ff5ec0c
chore: upgrade @react-native-picker/picker to 2.11.4 (#7357)
Rohit3523 Jun 1, 2026
bbc25d0
docs: Update README.md (#7356)
aytaj-ismayilzada Jun 2, 2026
70d087b
perf(markdown): reduce room-open render cost with parse cache and com…
diegolmello Jun 2, 2026
f9168c8
chore: migrate DirectoryView to Hooks (#7359)
OtavioStasiak Jun 2, 2026
91fa530
perf(call): cut HeaderCallButton mount cost by consolidating call hoo…
diegolmello Jun 2, 2026
80989f5
fix: keep live socket on deeplink login (prevent orphan WebSocket clo…
diegolmello Jun 3, 2026
7605531
chore: remove wt-specific worktree contract from CLAUDE.md (#7365)
diegolmello Jun 3, 2026
e988cbf
Merge branch 'develop' into 4.73.1
diegolmello Jun 8, 2026
df1e2be
chore: Merge master into 4.73.1-master
diegolmello Jun 9, 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
4 changes: 0 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ pnpm storybook:start # Start Metro with Storybook UI
pnpm storybook-generate # Generate story snapshots
```

## Worktree contract

When using worktrees (`wt`), the post-start hook runs `pnpm install` only. `wt step copy-ignored` is opt-in and only used when the worktree will perform a native build (iOS/Android/Pods/Gradle/Bundler caches).

## Code Style

- **Prettier**: tabs, single quotes, 130 char width, no trailing commas, arrow parens avoid, bracket same line
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Also check the [#react-native](https://open.rocket.chat/channel/react-native) co
Are you a dev and would like to help? Found a bug that you would like to report or a missing feature that you would like to work on? Great! We have written down a [Contribution guide](https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/CONTRIBUTING.md) so you can start easily.

## Whitelabel
Do you want to make the app run on your own server only? [Follow our whitelabel documentation.](https://developer.rocket.chat/mobile-app/mobile-app-white-labelling)
Do you want to make the app run on your own server only? [Follow our whitelabel documentation.](https://developer.rocket.chat/mobile-app-white-labelling)

## Engage with us
### Share your story
Expand Down
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.73.0"
versionName "4.73.1"
vectorDrawables.useSupportLibrary = true
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
resValue "string", "rn_config_reader_custom_package", "chat.rocket.reactnative"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,4 @@ class NotificationIntentHandler {
}
}
}

4 changes: 3 additions & 1 deletion app/actions/actionsTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export const ROOM = createRequestTypes('ROOM', [
'FORWARD',
'USER_TYPING',
'HISTORY_REQUEST',
'HISTORY_FINISHED'
'HISTORY_FINISHED',
'HISTORY_UI_LOADER_PUSH',
'HISTORY_UI_LOADER_POP'
]);
export const INQUIRY = createRequestTypes('INQUIRY', [
...defaultTypes,
Expand Down
26 changes: 25 additions & 1 deletion app/actions/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,24 @@ export interface IRoomHistoryFinished extends Action {
loaderId: string;
}

export interface IRoomHistoryUiLoaderPush extends Action {
loaderId: string;
}

export interface IRoomHistoryUiLoaderPop extends Action {
loaderId: string;
}

export type TActionsRoom = TSubscribeRoom &
TUnsubscribeRoom &
ILeaveRoom &
IDeleteRoom &
IForwardRoom &
IUserTyping &
IRoomHistoryRequest &
IRoomHistoryFinished;
IRoomHistoryFinished &
IRoomHistoryUiLoaderPush &
IRoomHistoryUiLoaderPop;

export function subscribeRoom(rid: string): TSubscribeRoom {
return {
Expand Down Expand Up @@ -138,3 +148,17 @@ export function roomHistoryFinished({ loaderId }: { loaderId: string }): IRoomHi
loaderId
};
}

export function roomHistoryUiLoaderPush({ loaderId }: { loaderId: string }): IRoomHistoryUiLoaderPush {
return {
type: ROOM.HISTORY_UI_LOADER_PUSH,
loaderId
};
}

export function roomHistoryUiLoaderPop({ loaderId }: { loaderId: string }): IRoomHistoryUiLoaderPop {
return {
type: ROOM.HISTORY_UI_LOADER_POP,
loaderId
};
}
144 changes: 95 additions & 49 deletions app/containers/markdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Paragraph from './components/Paragraph';
import { Code } from './components/code';
import Heading from './components/Heading';
import log from '../../lib/methods/helpers/log';
import styles from './styles';

export { default as MarkdownPreview } from './components/Preview';

Expand All @@ -35,6 +36,71 @@ interface IMarkdownProps {
textStyle?: StyleProp<TextStyle>;
}

type MarkdownBlock = Root[number];

const PARSE_CACHE_MAX = 200;
const parseCache = new Map<string, Root>();

const parseMessage = (msg: string): Root => {
const cached = parseCache.get(msg);
if (cached) {
return cached;
}

const result = parse(msg);

if (parseCache.size >= PARSE_CACHE_MAX) {
const oldestKey = parseCache.keys().next().value;
if (oldestKey !== undefined) {
parseCache.delete(oldestKey);
}
}

parseCache.set(msg, result);
return result;
};

const resolveTokens = (msg: string, md: Root | undefined, isTranslated?: boolean): Root => {
if (!isTranslated && md) {
return md;
}

return parseMessage(typeof msg === 'string' ? msg : String(msg || ''));
};

const MarkdownBlockView = ({ block }: { block: MarkdownBlock }) => {
'use memo';

switch (block.type) {
case 'BIG_EMOJI':
return <BigEmoji value={block.value} />;
case 'UNORDERED_LIST':
return <UnorderedList value={block.value} />;
case 'ORDERED_LIST':
return <OrderedList value={block.value} />;
case 'TASKS':
return <TaskList value={block.value} />;
case 'QUOTE':
return <Quote value={block.value} />;
case 'PARAGRAPH':
return <Paragraph value={block.value} />;
case 'CODE':
return <Code value={block.value} />;
case 'HEADING':
return <Heading value={block.value} level={block.level} />;
case 'LINE_BREAK':
return <LineBreak />;
// This prop exists, but not even on the web it is treated, so...
// https://github.com/RocketChat/Rocket.Chat/blob/develop/packages/gazzodown/src/Markup.tsx
// case 'LIST_ITEM':
// return <View />;
case 'KATEX':
return <KaTeX value={block.value} />;
default:
return null;
}
};

const Markdown: React.FC<IMarkdownProps> = ({
msg,
md,
Expand All @@ -48,60 +114,40 @@ const Markdown: React.FC<IMarkdownProps> = ({
isTranslated,
textStyle
}: IMarkdownProps) => {
if (!msg) return null;
'use memo';

let tokens: Root | null = null;

if (msg) {
try {
const result = resolveTokens(msg, md, isTranslated);
tokens = isEmpty(result) ? null : result;
} catch (e) {
log(e);
}
}

const contextValue = {
mentions,
channels,
useRealName,
username,
navToRoomInfo,
getCustomEmoji,
onLinkPress,
textStyle
};

let tokens;
try {
tokens = !isTranslated && md ? md : parse(typeof msg === 'string' ? msg : String(msg || ''));
} catch (e) {
log(e);
if (!tokens) {
return null;
}

if (isEmpty(tokens)) return null;
return (
<View style={{ gap: 2 }}>
<MarkdownContext.Provider
value={{
mentions,
channels,
useRealName,
username,
navToRoomInfo,
getCustomEmoji,
onLinkPress,
textStyle
}}>
{tokens?.map(block => {
switch (block.type) {
case 'BIG_EMOJI':
return <BigEmoji value={block.value} />;
case 'UNORDERED_LIST':
return <UnorderedList value={block.value} />;
case 'ORDERED_LIST':
return <OrderedList value={block.value} />;
case 'TASKS':
return <TaskList value={block.value} />;
case 'QUOTE':
return <Quote value={block.value} />;
case 'PARAGRAPH':
return <Paragraph value={block.value} />;
case 'CODE':
return <Code value={block.value} />;
case 'HEADING':
return <Heading value={block.value} level={block.level} />;
case 'LINE_BREAK':
return <LineBreak />;
// This prop exists, but not even on the web it is treated, so...
// https://github.com/RocketChat/Rocket.Chat/blob/develop/packages/gazzodown/src/Markup.tsx
// case 'LIST_ITEM':
// return <View />;
case 'KATEX':
return <KaTeX value={block.value} />;
default:
return null;
}
})}
<View style={styles.blocks}>
<MarkdownContext.Provider value={contextValue}>
{tokens.map((block, index) => (
<MarkdownBlockView key={`${block.type}-${index}`} block={block} />
))}
</MarkdownContext.Provider>
</View>
);
Expand Down
3 changes: 3 additions & 0 deletions app/containers/markdown/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const codeFontFamily = Platform.select({
});

export default StyleSheet.create({
blocks: {
gap: 2
},
container: {
alignItems: 'flex-start',
flexDirection: 'row'
Expand Down
6 changes: 3 additions & 3 deletions app/lib/hooks/useVideoConf/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCameraPermissions } from 'expo-camera';
import { Camera } from 'expo-camera';
import React, { useMemo } from 'react';

import { useActionSheet } from '../../../containers/ActionSheet';
Expand Down Expand Up @@ -33,7 +33,6 @@ export const useVideoConf = (
const serverVersion = useAppSelector(state => state.server.version);
const { callEnabled, disabledTooltip, roomType } = useVideoConfCall(rid);

const [permission, requestPermission] = useCameraPermissions();
const { showActionSheet } = useActionSheet();

const isServer5OrNewer = useMemo(() => compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0'), [serverVersion]);
Expand Down Expand Up @@ -66,9 +65,10 @@ export const useVideoConf = (
fullContainer: true
});

const permission = await Camera.getCameraPermissionsAsync();
if (!permission?.granted) {
try {
await requestPermission();
await Camera.requestCameraPermissionsAsync();
handleAndroidBltPermission();
} catch (error) {
log(error);
Expand Down
31 changes: 22 additions & 9 deletions app/lib/hooks/useVideoConf/useVideoConfCall.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { shallowEqual } from 'react-redux';

import { SubscriptionType } from '../../../definitions';
import { getUserSelector } from '../../../selectors/login';
Expand All @@ -8,7 +9,6 @@ import { compareServerVersion } from '../../methods/helpers/compareServerVersion
import { isReadOnly } from '../../methods/helpers/isReadOnly';
import { useAppSelector } from '../useAppSelector';
import { usePermissions } from '../usePermissions';
import { useSetting } from '../useSetting';

export const useVideoConfCall = (
rid: string
Expand All @@ -17,18 +17,31 @@ export const useVideoConfCall = (
const [disabledTooltip, setDisabledTooltip] = useState(false);
const [roomType, setRoomType] = useState<SubscriptionType>();

// Read all call-related settings in a single subscription instead of one useSetting per key.
const settings = useAppSelector(
state => ({
jitsiEnabled: state.settings.Jitsi_Enabled,
jitsiEnableTeams: state.settings.Jitsi_Enable_Teams,
jitsiEnableChannels: state.settings.Jitsi_Enable_Channels,
videoConfEnableDMs: state.settings.VideoConf_Enable_DMs,
videoConfEnableChannels: state.settings.VideoConf_Enable_Channels,
videoConfEnableTeams: state.settings.VideoConf_Enable_Teams,
videoConfEnableGroups: state.settings.VideoConf_Enable_Groups,
omnichannelCallProvider: state.settings.Omnichannel_call_provider
}),
shallowEqual
);

// OLD SETTINGS
const jitsiEnabled = useSetting('Jitsi_Enabled');
const jitsiEnableTeams = useSetting('Jitsi_Enable_Teams');
const jitsiEnableChannels = useSetting('Jitsi_Enable_Channels');
const { jitsiEnabled, jitsiEnableTeams, jitsiEnableChannels } = settings;

// NEW SETTINGS
// Only disable video conf if the settings are explicitly FALSE - any falsy value counts as true
const enabledDMs = useSetting('VideoConf_Enable_DMs') !== false;
const enabledChannel = useSetting('VideoConf_Enable_Channels') !== false;
const enabledTeams = useSetting('VideoConf_Enable_Teams') !== false;
const enabledGroups = useSetting('VideoConf_Enable_Groups') !== false;
const enabledLiveChat = useSetting('Omnichannel_call_provider') === 'default-provider';
const enabledDMs = settings.videoConfEnableDMs !== false;
const enabledChannel = settings.videoConfEnableChannels !== false;
const enabledTeams = settings.videoConfEnableTeams !== false;
const enabledGroups = settings.videoConfEnableGroups !== false;
const enabledLiveChat = settings.omnichannelCallProvider === 'default-provider';

const serverVersion = useAppSelector(state => state.server.version);
const isServer5OrNewer = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0');
Expand Down
13 changes: 13 additions & 0 deletions app/lib/methods/helpers/announceSearchResultsForAccessibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AccessibilityInfo } from 'react-native';

import I18n from '../../../i18n';

export const announceSearchResultsForAccessibility = (count: number): void => {
if (count < 1) {
AccessibilityInfo.announceForAccessibility(I18n.t('No_results_found'));
return;
}

const message = count === 1 ? I18n.t('One_result_found') : I18n.t('Search_Results_found', { count: count.toString() });
AccessibilityInfo.announceForAccessibility(message);
};
1 change: 1 addition & 0 deletions app/lib/methods/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export * from './image';
export * from './emitter';
export * from './parseJson';
export * from './fileDownload';
export * from './announceSearchResultsForAccessibility';
2 changes: 1 addition & 1 deletion app/lib/methods/helpers/normalizeDeepLinkingServerHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function normalizeDeepLinkingServerHost(rawHost: string): string {
} else {
host = `https://${host}`;
}
} else {
} else if (!/^http:\/\/localhost(:\d+)?/.test(host)) {
host = host.replace('http://', 'https://');
}
if (host.slice(-1) === '/') {
Expand Down
Loading
Loading