Skip to content
Merged
Show file tree
Hide file tree
Changes from 109 commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
a722c69
Initial commit
Rohit3523 Jun 10, 2026
06d87c9
action: organized translations
Rohit3523 Jun 10, 2026
f2a4f52
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 10, 2026
21a3b24
More translation
Rohit3523 Jun 10, 2026
939a20c
another one ;-;
Rohit3523 Jun 10, 2026
5e99b20
more changes
Rohit3523 Jun 10, 2026
8cf4f31
more lint fix
Rohit3523 Jun 10, 2026
eb726ff
action: organized translations
Rohit3523 Jun 10, 2026
7bab135
rerun ci
Rohit3523 Jun 10, 2026
61c6a6f
revert
Rohit3523 Jun 10, 2026
9833cc5
fix status update in room action and user info
Rohit3523 Jun 10, 2026
a39a305
added story...
Rohit3523 Jun 10, 2026
fb840e2
update style
Rohit3523 Jun 10, 2026
39307c4
remove status icon from avatar
Rohit3523 Jun 10, 2026
4d7a42b
added default presence in header and other screen
Rohit3523 Jun 10, 2026
27b711e
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 10, 2026
857ebe6
lint fix
Rohit3523 Jun 10, 2026
4ff2cf4
using dayjs instead of date...
Rohit3523 Jun 10, 2026
20fa898
i18n fix
Rohit3523 Jun 10, 2026
231d6a4
makinf code rabbit happy
Rohit3523 Jun 10, 2026
397de8c
translation fix for Until
Rohit3523 Jun 10, 2026
babc5a9
changes
Rohit3523 Jun 10, 2026
ae442af
revert logic
Rohit3523 Jun 10, 2026
e0c2318
minor change
Rohit3523 Jun 10, 2026
6c4f06a
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 10, 2026
1fbd54d
Remove view because it was not in use
Rohit3523 Jun 11, 2026
05c876a
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 11, 2026
20001d5
lint fix
Rohit3523 Jun 11, 2026
1a56516
improve style and change date format
Rohit3523 Jun 11, 2026
5723523
user can't set away status manually and display only when user is away
Rohit3523 Jun 11, 2026
9837a00
Merge branch 'feat/presence-sync' of https://github.com/RocketChat/Ro…
Rohit3523 Jun 11, 2026
c656077
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 11, 2026
75bb2a4
Improve accessibility label
Rohit3523 Jun 11, 2026
382e6e5
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 12, 2026
4b56474
lint fix
Rohit3523 Jun 12, 2026
2e93202
Merge branch 'feat/presence-sync' of https://github.com/RocketChat/Ro…
Rohit3523 Jun 12, 2026
26ee9b8
snapshot update
Rohit3523 Jun 12, 2026
786c6cf
more improvements
Rohit3523 Jun 15, 2026
4d8286b
ui improve
Rohit3523 Jun 15, 2026
ec25a8a
show expire time in edit status
Rohit3523 Jun 15, 2026
c4994ef
story fix
Rohit3523 Jun 15, 2026
0a4f77c
lint fix
Rohit3523 Jun 15, 2026
e3a13b6
fix subtitle accessibility issue
Rohit3523 Jun 15, 2026
fa3c8ef
revert logic
Rohit3523 Jun 15, 2026
b6e0b4d
ratelimit toast was showing no text
Rohit3523 Jun 15, 2026
77a2956
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 15, 2026
de8ee2e
use rest api instead of methodcall for set status
Rohit3523 Jun 15, 2026
fddd071
Added api return type
Rohit3523 Jun 15, 2026
f3343f9
fix type...
Rohit3523 Jun 15, 2026
78ae8e3
fake time in jest
Rohit3523 Jun 15, 2026
97520c9
added componentDidUpdate to make the logic simple...
Rohit3523 Jun 16, 2026
126ac6f
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 16, 2026
175f926
member is no longer slate...
Rohit3523 Jun 16, 2026
b00209b
story fix
Rohit3523 Jun 16, 2026
2e8f500
remove padding from story, seems unrelated
Rohit3523 Jun 16, 2026
99dbded
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 16, 2026
a124e4e
lint fix
Rohit3523 Jun 16, 2026
3cfb867
fix lint
Rohit3523 Jun 16, 2026
bfd2efb
fix fake time
Rohit3523 Jun 16, 2026
e465f5d
lint fix
Rohit3523 Jun 16, 2026
b85607d
improve
Rohit3523 Jun 16, 2026
775a0f4
Using ejson in normalizeStatusExpiresAt
Rohit3523 Jun 17, 2026
e53a879
statusview changes
Rohit3523 Jun 17, 2026
ac2bef8
clearafter picker changes
Rohit3523 Jun 17, 2026
0007eda
stop rerendering but with a weird logic...
Rohit3523 Jun 17, 2026
f556439
another optimisation
Rohit3523 Jun 17, 2026
6cc87b9
Improve...
Rohit3523 Jun 17, 2026
4b27d70
Story for roominfoviewavatar
Rohit3523 Jun 17, 2026
083dd60
Added align item to make it look good i mean the edit button
Rohit3523 Jun 17, 2026
b0a1a44
added line height
Rohit3523 Jun 17, 2026
d362cfb
combined status row...
Rohit3523 Jun 17, 2026
00f1dab
improve ts
Rohit3523 Jun 17, 2026
26dec67
lint fix
Rohit3523 Jun 17, 2026
77c30ba
improve
Rohit3523 Jun 17, 2026
8cc6d0a
lint fix
Rohit3523 Jun 17, 2026
be094b3
until i18n and formatStatusExpiry fix
Rohit3523 Jun 17, 2026
d063038
remove duplicate status from room header accessibility label
Rohit3523 Jun 17, 2026
4df50f9
lint fix
Rohit3523 Jun 17, 2026
e669b23
story improve
Rohit3523 Jun 17, 2026
d9f4a68
added normalizeStatusExpiresAt in rest as well
Rohit3523 Jun 17, 2026
d1b228c
Added version comment in user.setStatus
Rohit3523 Jun 17, 2026
9a0f189
remove duplicate story
Rohit3523 Jun 17, 2026
9cb76ec
lint fix
Rohit3523 Jun 17, 2026
cf101d6
improve inline style
Rohit3523 Jun 17, 2026
8c40847
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 17, 2026
0e6ead5
remove testid
Rohit3523 Jun 18, 2026
ac5e8ac
Merge branch 'feat/presence-sync' of https://github.com/RocketChat/Ro…
Rohit3523 Jun 18, 2026
170b971
added undefined check in formatStatusExpiry
Rohit3523 Jun 18, 2026
9ba18d6
improve statusrow component conditional rendering
Rohit3523 Jun 18, 2026
6695653
added story for status row
Rohit3523 Jun 18, 2026
34637a8
remove duplicate normalizeStatusExpiresAt
Rohit3523 Jun 18, 2026
1e1c6bf
added shallowEqual in activeUserStatus
Rohit3523 Jun 18, 2026
dcab767
passing loggedUserId in getUidDirectMessage
Rohit3523 Jun 18, 2026
537de0e
added unit test
Rohit3523 Jun 18, 2026
2829e2f
lint fix
Rohit3523 Jun 18, 2026
abb107a
lint fix
Rohit3523 Jun 18, 2026
3490c4f
remove conditional formatStatusExpiry
Rohit3523 Jun 18, 2026
44b8479
memo formatStatusExpiry
Rohit3523 Jun 18, 2026
6ba39a1
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 18, 2026
09feeab
Improve
Rohit3523 Jun 19, 2026
50feae4
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 19, 2026
eb1970e
Move clearafter picker to seperate component
Rohit3523 Jun 19, 2026
9aa4116
revert unwanted change
Rohit3523 Jun 19, 2026
f130591
more changes
Rohit3523 Jun 19, 2026
390a832
added use meo
Rohit3523 Jun 19, 2026
6f02c89
more test
Rohit3523 Jun 19, 2026
db923b9
more test
Rohit3523 Jun 19, 2026
e72e798
lint fix
Rohit3523 Jun 19, 2026
314c42d
Merge branch 'develop' into feat/presence-sync
Rohit3523 Jun 22, 2026
8cd4269
wrap onConfirm in act
Rohit3523 Jun 22, 2026
09b25cb
passing fake time in unit test
Rohit3523 Jun 22, 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
58 changes: 58 additions & 0 deletions app/containers/RoomHeader/RoomHeader.stories.tsx

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't get the last 3. Can you explain?
IIRC Figma mentioned the clock only for some places (without the expiration).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have improved the story

Explanation
1-4 = normal status
5 = Passing future expire time to make sure clock icon is visible
6 = passing 1 hour future time to make sure clock icon is visible
7 = passing past time to make sure clock icon is not visible - added for edge case ;-;

Image

Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,61 @@ export const Themes = () => (
<ThemeStory theme='black' />
</>
);

export const DM_Status = () => {
const futureExpiry = '2030-06-15T19:00:00.000Z';
const now = new Date().getTime();
const hourLaterExpiry = new Date(now + 3600000).toISOString();
const expiredExpiry = new Date(now - 3600000).toISOString();
return (
<>
<HeaderExample
title={() => <RoomHeader title='John Doe' type='d' roomUserId='user1' subtitle='Online' status='online' />}
/>
<HeaderExample title={() => <RoomHeader title='John Doe' type='d' roomUserId='user2' subtitle='Away' status='away' />} />
<HeaderExample title={() => <RoomHeader title='John Doe' type='d' roomUserId='user3' subtitle='Busy' status='busy' />} />
<HeaderExample
title={() => <RoomHeader title='John Doe' type='d' roomUserId='user4' subtitle='Offline' status='offline' />}
/>
<HeaderExample
title={() => <RoomHeader title='John Doe' type='d' roomUserId='user5' subtitle='In a meeting' status='online' />}
/>
<HeaderExample
title={() => (
<RoomHeader
title='John Doe'
type='d'
roomUserId='user6'
subtitle='In a meeting'
status='online'
statusExpiresAt={futureExpiry}
/>
)}
/>
<HeaderExample
title={() => (
<RoomHeader
title='John Doe'
type='d'
roomUserId='user9'
subtitle='having lunch (clock icon should be visible)'
status='away'
statusExpiresAt={hourLaterExpiry}
/>
)}
/>
<HeaderExample
title={() => (
<RoomHeader
title='John Doe'
type='d'
roomUserId='user8'
subtitle='In a meeting (past expiry — clock icon should be hidden)'
status='online'
statusExpiresAt={expiredExpiry}
/>
)}
/>
</>
);
};
3 changes: 3 additions & 0 deletions app/containers/RoomHeader/RoomHeader.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { generateSnapshots } from '../../../.rnstorybook/generateSnapshots';
import * as stories from './RoomHeader.stories';

jest.useFakeTimers();
jest.setSystemTime(new Date('2026-06-15T12:00:00.000Z'));

generateSnapshots(stories);
35 changes: 32 additions & 3 deletions app/containers/RoomHeader/RoomHeader.tsx
Comment thread
diegolmello marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import I18n from '../../i18n';
import sharedStyles from '../../views/Styles';
import { MarkdownPreview } from '../markdown';
import RoomTypeIcon from '../RoomTypeIcon';
import { CustomIcon } from '../CustomIcon';
import { type TUserStatus, type IOmnichannelSource, type ISubscription } from '../../definitions';
import { formatStatusExpiry } from '../../lib/methods/helpers/formatStatusExpiry';
import { useTheme } from '../../theme';
import { useAppSelector } from '../../lib/hooks/useAppSelector';
import useStatusAccessibilityLabel from '../../lib/hooks/useStatusAccessibilityLabel';
Expand Down Expand Up @@ -45,12 +47,16 @@ const styles = StyleSheet.create({
},
typingUsers: {
...sharedStyles.textSemibold
},
clockIcon: {
marginRight: 4
}
});

type TRoomHeaderSubTitle = {
usersTyping: IUsersTyping;
subtitle?: string;
formattedStatusExpiry?: string;
renderFunc?: () => ReactElement;
scale: number;
};
Expand All @@ -66,6 +72,7 @@ type TRoomHeaderHeaderTitle = {
interface IRoomHeader {
title?: string;
subtitle?: string;
statusExpiresAt?: string;
type: string;
width: number;
height: number;
Expand All @@ -87,7 +94,7 @@ interface IRoomHeader {

type IRoomHeaderProps = IRoomHeader;

const SubTitle = memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeaderSubTitle) => {
const SubTitle = memo(({ usersTyping, subtitle, formattedStatusExpiry, renderFunc, scale }: TRoomHeaderSubTitle) => {
const { colors } = useTheme();
const fontSize = getSubTitleSize(scale);
// typing
Expand All @@ -113,6 +120,14 @@ const SubTitle = memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeader

// subtitle
if (subtitle) {
if (formattedStatusExpiry) {
return (
<View style={styles.titleContainer}>
<CustomIcon name='clock' size={fontSize} color={colors.fontSecondaryInfo} style={styles.clockIcon} />
<MarkdownPreview msg={subtitle} style={[styles.subtitle, { fontSize, color: colors.fontSecondaryInfo }]} />
</View>
);
}
return <MarkdownPreview msg={subtitle} style={[styles.subtitle, { fontSize, color: colors.fontSecondaryInfo }]} />;
}

Expand All @@ -138,6 +153,7 @@ const HeaderTitle = memo(({ title, tmid, prid, scale, testID }: TRoomHeaderHeade
const Header = ({
title,
subtitle,
statusExpiresAt,
parentTitle,
type,
status,
Expand Down Expand Up @@ -173,8 +189,15 @@ const Header = ({
// Only move focus to the header for accessibility navigation (screen reader or physical
// keyboard); regular touch users shouldn't have focus yanked onto the header on room open.
const autoFocusHeader = useIsAccessibilityNavigationEnabled();
const formattedStatusExpiry = formatStatusExpiry(statusExpiresAt);
const subtitleAccessibilityLabel = tmid ? parentTitle : subtitle;
const accessibilityLabel = `${statusAccessibilityLabel} ${title} ${subtitleAccessibilityLabel || ''}.`;
const fullSubtitleAccessibilityLabel = formattedStatusExpiry
? `${subtitleAccessibilityLabel || ''}, ${formattedStatusExpiry}`
: subtitleAccessibilityLabel || '';
const accessibilityLabel =
type === 'd' && subtitleAccessibilityLabel === statusAccessibilityLabel
? `${title}. ${fullSubtitleAccessibilityLabel}.`
: `${statusAccessibilityLabel} ${title} ${fullSubtitleAccessibilityLabel}.`;

if (!portrait && !tmid && !isMasterDetail) {
if (usersTyping.length > 0 || subtitle) {
Expand Down Expand Up @@ -231,7 +254,13 @@ const Header = ({
)}
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} testID={testID} />
</View>
<SubTitle usersTyping={tmid ? [] : usersTyping} subtitle={subtitle} renderFunc={renderFunc} scale={scale} />
<SubTitle
usersTyping={tmid ? [] : usersTyping}
subtitle={subtitle}
formattedStatusExpiry={formattedStatusExpiry}
renderFunc={renderFunc}
scale={scale}
/>
</TouchableOpacity>
</KeyboardFocusView>
);
Expand Down
167 changes: 167 additions & 0 deletions app/containers/RoomHeader/RoomHeaderContainer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { type ReactNode } from 'react';
import { render } from '@testing-library/react-native';
import { Provider } from 'react-redux';

import { mockedStore } from '../../reducers/mockedStore';
import { setActiveUsers } from '../../actions/activeUsers';
import { connectSuccess, connectRequest, disconnect } from '../../actions/connect';
import { initStore } from '../../lib/store/auxStore';
import { selectServerSuccess } from '../../actions/server';
import RoomHeaderContainer from './index';

const mockChild = jest.fn<any, [Record<string, unknown>]>(() => null);
jest.mock('./RoomHeader', () => ({
__esModule: true,
default: (props: Record<string, unknown>) => {
mockChild(props);
return null;
}
}));

const Wrapper = ({ children }: { children: ReactNode }) => <Provider store={mockedStore}>{children}</Provider>;

const defaultProps = {
title: 'John Doe',
type: 'd',
roomUserId: 'user-123',
onPress: jest.fn()
};

const renderContainer = (props: Record<string, unknown> = {}) =>
render(<RoomHeaderContainer {...defaultProps} {...props} />, { wrapper: Wrapper });

describe('RoomHeaderContainer', () => {
beforeAll(() => {
initStore(mockedStore);
});

beforeEach(() => {
jest.clearAllMocks();
mockedStore.dispatch(disconnect());
});

const connect = () => {
mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '6.0.0', name: 'Test' }));
mockedStore.dispatch(connectSuccess());
};

it('should pass subtitle as Connecting when meteor is connecting for non-DM room', () => {
mockedStore.dispatch(connectRequest());
mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '6.0.0', name: 'Test' }));

renderContainer({ type: 'c', roomUserId: undefined });

const props = mockChild.mock.calls[0][0];
expect(props.subtitle).toBe('Connecting...');
});

it('should pass subtitle as Waiting for network... when not connected for non-DM room', () => {
mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '6.0.0', name: 'Test' }));

renderContainer({ type: 'c', roomUserId: undefined });

const props = mockChild.mock.calls[0][0];
expect(props.subtitle).toBe('Waiting for network...');
});

it('should pass presence status for DM room when connected and activeUser exists', () => {
connect();
mockedStore.dispatch(
setActiveUsers({ 'user-123': { status: 'online', statusText: 'Working from home', statusExpiresAt: undefined } })
);

renderContainer();

const props = mockChild.mock.calls[0][0];
expect(props.subtitle).toBe('Working from home');
expect(props.type).toBe('d');
});

it('should pass statusExpiresAt for DM room when activeUser has it', () => {
connect();
mockedStore.dispatch(
setActiveUsers({
'user-123': { status: 'away', statusText: 'In a meeting', statusExpiresAt: '2026-06-20T13:00:00.000Z' }
})
);

renderContainer();

const props = mockChild.mock.calls[0][0];
expect(props.subtitle).toBe('In a meeting');
expect(props.statusExpiresAt).toBe('2026-06-20T13:00:00.000Z');
});

it('should use presence label when statusText is empty', () => {
connect();
mockedStore.dispatch(setActiveUsers({ 'user-123': { status: 'busy', statusText: '', statusExpiresAt: undefined } }));

renderContainer();

const props = mockChild.mock.calls[0][0];
expect(props.subtitle).toBe('Busy');
});

it('should not pass statusExpiresAt for non-DM room', () => {
connect();
mockedStore.dispatch(
setActiveUsers({ 'user-123': { status: 'away', statusText: 'Away', statusExpiresAt: '2026-06-20T13:00:00.000Z' } })
);

renderContainer({ type: 'c', roomUserId: undefined });

const props = mockChild.mock.calls[0][0];
expect(props.statusExpiresAt).toBeUndefined();
});

it('should pass subtitleProp when connected and no activeUser', () => {
connect();

renderContainer({ subtitle: 'Custom subtitle', roomUserId: undefined, type: 'c' });

const props = mockChild.mock.calls[0][0];
expect(props.subtitle).toBe('Custom subtitle');
});

it('should pass roomUserId to RoomHeader', () => {
connect();

renderContainer();

const props = mockChild.mock.calls[0][0];
expect(props.roomUserId).toBe('user-123');
});

it('should handle activeUser with offline status', () => {
connect();
mockedStore.dispatch(setActiveUsers({ 'user-123': { status: 'offline', statusText: '', statusExpiresAt: undefined } }));

renderContainer();

const props = mockChild.mock.calls[0][0];
expect(props.subtitle).toBe('Offline');
});

it('should use statusText even when status is undefined', () => {
connect();
mockedStore.dispatch(
setActiveUsers({
'user-123': { status: undefined, statusText: 'Custom text', statusExpiresAt: undefined } as any
})
);

renderContainer();

const props = mockChild.mock.calls[0][0];
expect(props.subtitle).toBe('Custom text');
});

it('should pass onPress to RoomHeader', () => {
connect();

renderContainer();

const props = mockChild.mock.calls[0][0];
expect(props.onPress).toBe(defaultProps.onPress);
});
});
Loading
Loading