From df977a5395bee386efc0db04cdbf8d2c855a21d5 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 29 Oct 2026 15:34:27 -0300 Subject: [PATCH 1/9] refactor: make RoomType the single room type definition --- app/definitions/IRoom.ts | 8 +++++++- app/lib/methods/getPermalinks.ts | 8 +++----- app/lib/methods/hideRoom.ts | 5 ++--- app/lib/methods/loadMessagesForRoom.ts | 8 ++++---- app/lib/methods/roomTypeToApiType.ts | 10 +++++----- app/lib/services/restApi.ts | 15 ++++++--------- app/views/RoomMembersView/helpers.ts | 16 ++++++++++------ app/views/RoomMembersView/index.tsx | 11 +++++------ app/views/RoomView/services/getMessages.ts | 4 ++-- 9 files changed, 44 insertions(+), 41 deletions(-) diff --git a/app/definitions/IRoom.ts b/app/definitions/IRoom.ts index 2fd78ce4138..b2d898d2c72 100644 --- a/app/definitions/IRoom.ts +++ b/app/definitions/IRoom.ts @@ -153,7 +153,13 @@ export interface IOmnichannelRoom extends Partial]; return `${server}/${roomType}/${isGoupChat ? room.rid : encodeURIComponent(room.name)}?msg=${messageId}`; } export function buildPermalinkChannel(server: string, channel: ISubscription) { - const roomType = roomTypes[channel.t as TRoomType]; + const roomType = roomTypes[channel.t as Exclude]; return `${server}/${roomType}/${encodeURIComponent(channel.name)}`; } diff --git a/app/lib/methods/hideRoom.ts b/app/lib/methods/hideRoom.ts index a1cb6c5d313..3a98f30a139 100644 --- a/app/lib/methods/hideRoom.ts +++ b/app/lib/methods/hideRoom.ts @@ -1,14 +1,13 @@ -import { type SubscriptionType } from '~/definitions'; +import { type RoomType, type SubscriptionType } from '~/definitions'; import database from '../database'; import log, { events, logEvent } from './helpers/log'; import { hideRoom as hideRoomService } from '../services/restApi'; -import { type RoomTypes } from './roomTypeToApiType'; export const hideRoom = async (rid: string, type: SubscriptionType) => { logEvent(events.RL_HIDE_CHANNEL); try { const db = database.active; - const result = await hideRoomService(rid, type as RoomTypes); + const result = await hideRoomService(rid, type as RoomType); if (result.success) { const subCollection = db.get('subscriptions'); await db.write(async () => { diff --git a/app/lib/methods/loadMessagesForRoom.ts b/app/lib/methods/loadMessagesForRoom.ts index 980967e0fdf..fba69172a58 100644 --- a/app/lib/methods/loadMessagesForRoom.ts +++ b/app/lib/methods/loadMessagesForRoom.ts @@ -1,11 +1,11 @@ import dayjs from '../dayjs'; import { MessageTypeLoad } from '../constants/messageTypeLoad'; import { roomHistoryUiLoaderPop, roomHistoryUiLoaderPush } from '~/actions/room'; -import { type IMessage, type TMessageModel } from '~/definitions'; +import { type IMessage, type RoomType, type TMessageModel } from '~/definitions'; import log from './helpers/log'; import { getMessageById } from '../database/services/Message'; import { getSubscriptionByRoomId } from '../database/services/Subscription'; -import { type RoomTypes, roomTypeToApiType } from './roomTypeToApiType'; +import { roomTypeToApiType } from './roomTypeToApiType'; import sdk from '../services/sdk'; import { store } from '../store/auxStore'; import updateMessages from './updateMessages'; @@ -30,7 +30,7 @@ async function resolveHideSystemMessages(rid: string): Promise { async function load(args: { rid: string; latest?: Date; - t: RoomTypes; + t: RoomType; loaderItem?: TMessageModel; onUiLoaderPushed?: (loaderId: string) => void; }): Promise<{ messages: IMessage[]; serverTimestamps: TServerTimestamps; lastBatchWasFull: boolean }> { @@ -116,7 +116,7 @@ async function load(args: { export async function loadMessagesForRoom(args: { rid: string; - t: RoomTypes; + t: RoomType; latest?: Date; loaderItem?: TMessageModel; }): Promise { diff --git a/app/lib/methods/roomTypeToApiType.ts b/app/lib/methods/roomTypeToApiType.ts index b84eb7242c5..5cf6f63cdbb 100644 --- a/app/lib/methods/roomTypeToApiType.ts +++ b/app/lib/methods/roomTypeToApiType.ts @@ -1,11 +1,11 @@ +import { type RoomType } from '~/definitions/IRoom'; + enum ETypes { Channels = 'channels', Im = 'im', Groups = 'groups' } -export type RoomTypes = 'c' | 'd' | 'p' | 'l'; - type ApiTypes = T extends 'c' ? ETypes.Channels : T extends 'd' @@ -16,13 +16,13 @@ type ApiTypes = T extends 'c' ? ETypes.Channels : never; -export const types: { [K in RoomTypes]: ApiTypes } = { +export const types: { [K in RoomType]: ApiTypes } = { c: ETypes.Channels, d: ETypes.Im, p: ETypes.Groups, l: ETypes.Channels }; -export const roomTypeToApiType = (t: T) => types[t]; +export const roomTypeToApiType = (t: T) => types[t]; -export const isRoomType = (t: unknown): t is RoomTypes => typeof t === 'string' && t in types; +export const isRoomType = (t: unknown): t is RoomType => typeof t === 'string' && t in types; diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 39f0c91fa27..f62ce87a562 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -23,7 +23,7 @@ import { type ISpotlight } from '~/definitions/ISpotlight'; import { TEAM_TYPE } from '~/definitions/ITeam'; import { type OperationParams, type ResultFor } from '~/definitions/rest/helpers'; import { type SubscriptionsEndpoints } from '~/definitions/rest/v1/subscriptions'; -import { type RoomTypes, roomTypeToApiType } from '../methods/roomTypeToApiType'; +import { roomTypeToApiType } from '../methods/roomTypeToApiType'; import { uploadUserAvatarMultipart } from '../methods/uploadAvatar/uploadAvatar'; import { compareServerVersion, getBundleId, isIOS } from '../methods/helpers'; import { getDeviceToken } from '../notifications/deviceToken'; @@ -575,11 +575,11 @@ export const toggleBlockUser = (rid: string, blocked: string, block: boolean) => return sdk.methodCallWrapper('unblockUser', { rid, blocked }); }; -export const leaveRoom = (roomId: string, t: RoomTypes) => +export const leaveRoom = (roomId: string, t: RoomType) => // RC 0.48.0 sdk.post(`${roomTypeToApiType(t)}.leave`, { roomId }); -export const deleteRoom = (roomId: string, t: RoomTypes) => +export const deleteRoom = (roomId: string, t: RoomType) => // RC 0.49.0 sdk.post(`${roomTypeToApiType(t)}.delete`, { roomId }); @@ -655,7 +655,7 @@ export const toggleRoomModerator = ({ return sdk.post(`${roomTypeToApiType(type)}.removeModerator`, { roomId, userId }); }; -export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: RoomTypes; userId: string }) => +export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: RoomType; userId: string }) => // RC 0.48.0 sdk.post(`${roomTypeToApiType(t)}.kick`, { roomId, userId }); @@ -673,7 +673,7 @@ export const toggleArchiveRoom = (roomId: string, t: SubscriptionType, archive: return sdk.post(`${roomTypeToApiType(type)}.unarchive`, { roomId }); }; -export const hideRoom = (roomId: string, t: RoomTypes) => +export const hideRoom = (roomId: string, t: RoomType) => // RC 0.48.0 sdk.post(`${roomTypeToApiType(t)}.close`, { roomId }); @@ -750,10 +750,7 @@ export const getSingleMessage = (msgId: string) => // RC 0.47.0 sdk.get('chat.getMessage', { msgId }); -export const getRoomRoles = ( - roomId: string, - type: SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL -) => +export const getRoomRoles = (roomId: string, type: Exclude) => // RC 0.65.0 sdk.get(`${roomTypeToApiType(type)}.roles`, { roomId }); diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts index bc31afe3000..b3a3d940989 100644 --- a/app/views/RoomMembersView/helpers.ts +++ b/app/views/RoomMembersView/helpers.ts @@ -2,7 +2,14 @@ import { Q } from '@nozbe/watermelondb'; import { Alert } from 'react-native'; import { LISTENER } from '~/containers/Toast'; -import { type IGetRoomRoles, type IUser, SubscriptionType, type TSubscriptionModel, type TUserModel } from '~/definitions'; +import { + type IGetRoomRoles, + type IUser, + type RoomType, + SubscriptionType, + type TSubscriptionModel, + type TUserModel +} from '~/definitions'; import I18n from '~/i18n'; import { getRoomTitle, showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; import EventEmitter from '~/lib/methods/helpers/events'; @@ -21,11 +28,8 @@ import { } from '~/lib/services/restApi'; import { createDirectMessage } from '~/lib/methods/createDirectMessage'; import database from '~/lib/database'; -import { type RoomTypes } from '~/lib/methods/roomTypeToApiType'; import { emitErrorCreateDirectMessage } from '~/lib/methods/helpers/emitErrorCreateDirectMessage'; -export type TRoomType = SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL; - const handleGoRoom = (item: TGoRoomItem, isMasterDetail: boolean): void => { goRoom({ item, isMasterDetail }); }; @@ -35,7 +39,7 @@ export const fetchRole = (role: string, selectedUser: TUserModel, roomRoles?: IG return !!userRoleResult?.roles.includes(role); }; -export const fetchRoomMembersRoles = async (roomType: TRoomType, rid: string, updateState: any): Promise => { +export const fetchRoomMembersRoles = async (roomType: Exclude, rid: string, updateState: any): Promise => { try { const type = roomType; const result = await getRoomRoles(rid, type); @@ -218,7 +222,7 @@ export const handleRemoveUserFromRoom = async ( ): Promise => { try { const userId = selectedUser._id; - await removeUserFromRoom({ roomId: room.rid, t: room.t as RoomTypes, userId }); + await removeUserFromRoom({ roomId: room.rid, t: room.t as RoomType, userId }); const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) }); EventEmitter.emit(LISTENER, { message }); callback(); diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 63f1e00ae36..70a26eb5e95 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -13,7 +13,7 @@ import RowSeparator from '~/containers/NativeListRow/components/Separator'; import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import SearchBox from '~/containers/SearchBox'; import UserItem from '~/containers/UserItem'; -import { type IGetRoomRoles, type TSubscriptionModel, type TUserModel } from '~/definitions'; +import { type IGetRoomRoles, type RoomType, type TSubscriptionModel, type TUserModel } from '~/definitions'; import I18n from '~/i18n'; import { stackedSearchBarOptions, nativeHeaderContentInset, translucentHeader } from '~/lib/methods/helpers/navigation'; import { headerRightActions } from '~/lib/methods/helpers/navigation/headerActions'; @@ -40,8 +40,7 @@ import { handleOwner, handleRemoveFromTeam, handleRemoveUserFromRoom, - navToDirectMessage, - type TRoomType + navToDirectMessage } from './helpers'; import styles from './styles'; @@ -312,7 +311,7 @@ const RoomMembersView = (): ReactElement => { title: I18n.t('Owner'), onPress: () => handleOwner(selectedUser, !isOwner, getUserDisplayName(selectedUser), room, () => - fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + fetchRoomMembersRoles(room.t as Exclude, room.rid, updateState) ), right: () => , testID: 'action-sheet-set-owner' @@ -327,7 +326,7 @@ const RoomMembersView = (): ReactElement => { title: I18n.t('Leader'), onPress: () => handleLeader(selectedUser, !isLeader, room, getUserDisplayName(selectedUser), () => - fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + fetchRoomMembersRoles(room.t as Exclude, room.rid, updateState) ), right: () => , testID: 'action-sheet-set-leader' @@ -342,7 +341,7 @@ const RoomMembersView = (): ReactElement => { title: I18n.t('Moderator'), onPress: () => handleModerator(selectedUser, !isModerator, room, getUserDisplayName(selectedUser), () => - fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + fetchRoomMembersRoles(room.t as Exclude, room.rid, updateState) ), right: () => , testID: 'action-sheet-set-moderator' diff --git a/app/views/RoomView/services/getMessages.ts b/app/views/RoomView/services/getMessages.ts index 3e5ce51d72e..e064dca174a 100644 --- a/app/views/RoomView/services/getMessages.ts +++ b/app/views/RoomView/services/getMessages.ts @@ -1,10 +1,10 @@ +import { type RoomType } from '~/definitions'; import { loadMessagesForRoom } from '~/lib/methods/loadMessagesForRoom'; import { loadMissedMessages } from '~/lib/methods/loadMissedMessages'; -import { type RoomTypes } from '~/lib/methods/roomTypeToApiType'; interface IGetMessagesParams { rid: string; - t?: RoomTypes; + t?: RoomType; } const getMessages = ({ rid, t }: IGetMessagesParams): Promise => { From 956a6ff6d4051a0c363533c885f40ad961cb1967 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 29 Oct 2026 15:37:49 -0300 Subject: [PATCH 2/9] refactor: navigate to threads with the room type instead of a thread marker --- app/definitions/ISubscription.ts | 4 +--- app/views/MessagesView/index.tsx | 5 ++--- .../hooks/__tests__/useJumpToMessage.composed.test.tsx | 6 +++--- .../__tests__/useJumpToMessage.lifecycle.test.tsx | 10 +++++----- .../RoomView/hooks/__tests__/useJumpToMessage.test.ts | 8 ++++---- app/views/RoomView/hooks/useJumpToMessage.ts | 4 ++-- app/views/RoomView/services/openThread.ts | 9 +++++---- app/views/SearchMessagesView/index.tsx | 5 ++--- app/views/ThreadMessagesView/index.tsx | 5 ++--- 9 files changed, 26 insertions(+), 30 deletions(-) diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts index e3f6795153b..957d0b0e663 100644 --- a/app/definitions/ISubscription.ts +++ b/app/definitions/ISubscription.ts @@ -14,9 +14,7 @@ export enum SubscriptionType { GROUP = 'p', DIRECT = 'd', CHANNEL = 'c', - OMNICHANNEL = 'l', - E2E = 'e2e', // FIXME: this is not a type of subscription - THREAD = 'thread' // FIXME: this is not a type of subscription + OMNICHANNEL = 'l' } export interface IVisitor { diff --git a/app/views/MessagesView/index.tsx b/app/views/MessagesView/index.tsx index 4444710265b..cbeff28c2d0 100644 --- a/app/views/MessagesView/index.tsx +++ b/app/views/MessagesView/index.tsx @@ -25,7 +25,7 @@ import { type IApplicationState, type IRoomInfoParam, type TMessageModel, - SubscriptionType, + type SubscriptionType, type IAttachment, type IMessage, type TAnyMessageModel, @@ -145,8 +145,7 @@ class MessagesView extends Component { params = { ...params, tmid: item.tmid, - name: await getThreadName(this.rid, item.tmid, item._id), - t: SubscriptionType.THREAD + name: await getThreadName(this.rid, item.tmid, item._id) }; Navigation.push('RoomView', params); } else { diff --git a/app/views/RoomView/hooks/__tests__/useJumpToMessage.composed.test.tsx b/app/views/RoomView/hooks/__tests__/useJumpToMessage.composed.test.tsx index 6547598357a..bbd5438c6a7 100644 --- a/app/views/RoomView/hooks/__tests__/useJumpToMessage.composed.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useJumpToMessage.composed.test.tsx @@ -144,7 +144,7 @@ describe('useJumpToMessage composed entry points', () => { rid: 'rid-1', tmid: 'thread-1', name: 'Thread title', - t: 'thread', + t: 'c', roomUserId: 'user-1', jumpToMessageId: 'reply-1' }); @@ -468,7 +468,7 @@ describe('useJumpToMessage composed entry points', () => { it('uses the existing Thread-to-main destination rules for plain and other-parent Messages', async () => { mockGetMessageInfo.mockResolvedValueOnce({ id: 'plain-main', rid: 'rid-1', replies: undefined }); mockGetMessageInfo.mockResolvedValueOnce({ id: 'other-parent', rid: 'rid-1', replies: ['reply'] }); - const { result } = renderNavigation({ t: 'thread', tmid: 'current-thread' }); + const { result } = renderNavigation({ t: 'c', tmid: 'current-thread' }); await act(async () => await result.current.jumpToMessageByUrl('https://open.rocket.chat/room?msg=plain-main')); await act(async () => await result.current.jumpToMessageByUrl('https://open.rocket.chat/room?msg=other-parent')); @@ -478,7 +478,7 @@ describe('useJumpToMessage composed entry points', () => { it('jumps in place when the target is the current Thread Parent', async () => { const listRef = makeListRef(); mockGetMessageInfo.mockResolvedValue({ id: 'current-thread', rid: 'rid-1', replies: ['reply'] }); - const { result } = renderNavigation({ t: 'thread', tmid: 'current-thread', listContainerRef: listRef }); + const { result } = renderNavigation({ t: 'c', tmid: 'current-thread', listContainerRef: listRef }); await act(async () => await result.current.jumpToMessageByUrl('https://open.rocket.chat/room?msg=current-thread')); expect(mockNavigation.push).not.toHaveBeenCalled(); diff --git a/app/views/RoomView/hooks/__tests__/useJumpToMessage.lifecycle.test.tsx b/app/views/RoomView/hooks/__tests__/useJumpToMessage.lifecycle.test.tsx index 9b2d7c433dc..3911b0cf5e4 100644 --- a/app/views/RoomView/hooks/__tests__/useJumpToMessage.lifecycle.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useJumpToMessage.lifecycle.test.tsx @@ -279,7 +279,7 @@ it('hands loading to a destination Thread, waits for readiness and settles only expect(params).toEqual({ rid: 'room', tmid: 'thread', - t: 'thread', + t: 'c', name: 'Thread title', roomUserId: 'user', jumpToMessageId: 'reply' @@ -339,7 +339,7 @@ it('preserves Message then Thread execution when both initial targets are presen it('keeps the initial Thread destination blocked when a Thread Message target still awaits readiness', async () => { lookup.mockResolvedValue(message('reply', { tmid: 'current' }) as any); - const view = screen({ tmid: 'current', t: 'thread' }, { jumpToMessageId: 'reply', jumpToThreadId: 'other' }); + const view = screen({ tmid: 'current', t: 'c' }, { jumpToMessageId: 'reply', jumpToThreadId: 'other' }); expect(lookup).not.toHaveBeenCalled(); expect(view.navigation.push).not.toHaveBeenCalled(); act(() => view.result.current.onThreadMessagesLoaded()); @@ -349,8 +349,8 @@ it('keeps the initial Thread destination blocked when a Thread Message target st }); it('opens updated Thread route targets immediately without waiting for Thread readiness', async () => { - const view = screen({ tmid: 'unloaded', t: 'thread' }); - view.rerender({ options: { tmid: 'unloaded', t: 'thread' }, params: { jumpToThreadId: 'other' } }); + const view = screen({ tmid: 'unloaded', t: 'c' }); + view.rerender({ options: { tmid: 'unloaded', t: 'c' }, params: { jumpToThreadId: 'other' } }); await flush(); expect(view.navigation.push).toHaveBeenCalledWith('RoomView', expect.objectContaining({ tmid: 'other', jumpToMessageId: '' })); expect(view.navigation.setParams).not.toHaveBeenCalled(); @@ -385,7 +385,7 @@ it('opens a Thread Parent directly without showing or dismissing loading', async expect(view.navigation.push).toHaveBeenCalledWith('RoomView', { rid: 'room', tmid: 'parent', - t: 'thread', + t: 'c', name: 'Parent title', roomUserId: 'user' }); diff --git a/app/views/RoomView/hooks/__tests__/useJumpToMessage.test.ts b/app/views/RoomView/hooks/__tests__/useJumpToMessage.test.ts index ed0494b6fe4..c41462b88bc 100644 --- a/app/views/RoomView/hooks/__tests__/useJumpToMessage.test.ts +++ b/app/views/RoomView/hooks/__tests__/useJumpToMessage.test.ts @@ -82,7 +82,7 @@ describe('useJumpToMessage', () => { rid: 'rid-1', tmid: 'thread-1', name: 'Thread Title', - t: 'thread', + t: 'c', roomUserId: null, jumpToMessageId: 'msg-1' }); @@ -101,7 +101,7 @@ describe('useJumpToMessage', () => { rid: 'rid-1', tmid: 'msg-1', name: 'Thread Name', - t: 'thread', + t: 'c', roomUserId: null }); }); @@ -170,7 +170,7 @@ describe('useJumpToMessage', () => { const list = { isMessageInWindow: jest.fn(() => isMessageInWindow), jumpToMessage: jest.fn(() => Promise.resolve()) }; const { result, unmount } = renderRoomNavigation({ tmid, - t: tmid ? 'thread' : 'c', + t: 'c', listContainerRef: { current: list as any } }); const jump = (url = MESSAGE_URL) => @@ -261,7 +261,7 @@ describe('useJumpToMessage', () => { }; const { result, unmount } = renderRoomNavigation({ tmid: 'thread-1', - t: 'thread', + t: 'c', listContainerRef: { current: list as any } }); const jumping = result.current.jumpToMessageByUrl(MESSAGE_URL); diff --git a/app/views/RoomView/hooks/useJumpToMessage.ts b/app/views/RoomView/hooks/useJumpToMessage.ts index c520b6d3d77..436032aaaab 100644 --- a/app/views/RoomView/hooks/useJumpToMessage.ts +++ b/app/views/RoomView/hooks/useJumpToMessage.ts @@ -124,7 +124,7 @@ export function useJumpToMessage({ }; const openThreadFromHere = (message: TOpenThreadTarget) => - openThread(message, { navigation, rid, roomUserId: roomUserIdRef.current, cancelJumpToMessage }); + openThread(message, { navigation, rid, t, roomUserId: roomUserIdRef.current, cancelJumpToMessage }); const ensureThreadTargetLoaded = async ( message: TGetMessageInfoResult, @@ -156,7 +156,7 @@ export function useJumpToMessage({ } return false; } - if (inThisRoom && t === 'thread' && message.id !== tmid) { + if (inThisRoom && tmid && message.id !== tmid) { await openRoom(message, { isMasterDetail }); return false; } diff --git a/app/views/RoomView/services/openThread.ts b/app/views/RoomView/services/openThread.ts index 97fe0f64890..3f964520dde 100644 --- a/app/views/RoomView/services/openThread.ts +++ b/app/views/RoomView/services/openThread.ts @@ -2,7 +2,7 @@ import { sendLoadingEvent } from '~/containers/Loading'; import I18n from '~/i18n'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '~/lib/constants/keys'; import { makeThreadName } from '~/lib/methods/helpers/room'; -import { SubscriptionType, type TAnyMessageModel } from '~/definitions'; +import { type SubscriptionType, type TAnyMessageModel } from '~/definitions'; import { type IRoomViewProps, type TGetMessageInfoResult } from '../definitions'; import { fetchThreadName } from './fetchThreadName'; @@ -13,6 +13,7 @@ export type TOpenThreadTarget = TAnyMessageModel | { tmid: string } | TGetMessag interface IOpenThreadDeps { navigation: IRoomViewProps['navigation']; rid?: string; + t?: string; roomUserId?: string | null; cancelJumpToMessage: () => void; } @@ -22,7 +23,7 @@ const isUndecryptable = (message: TOpenThreadTarget): boolean => export const openThread = async ( message: TOpenThreadTarget, - { navigation, rid, roomUserId, cancelJumpToMessage }: IOpenThreadDeps + { navigation, rid, t, roomUserId, cancelJumpToMessage }: IOpenThreadDeps ): Promise => { if (!rid) { return; @@ -34,7 +35,7 @@ export const openThread = async ( rid, tmid: message.id, name: makeThreadName(message), - t: SubscriptionType.THREAD, + t: t as SubscriptionType, roomUserId }); } @@ -67,7 +68,7 @@ export const openThread = async ( rid, tmid, name: isUndecryptable(message) ? I18n.t('E2E_Key_Error') : threadName, - t: SubscriptionType.THREAD, + t: t as SubscriptionType, roomUserId, jumpToMessageId }); diff --git a/app/views/SearchMessagesView/index.tsx b/app/views/SearchMessagesView/index.tsx index 829471b2f2b..d3615f671f0 100644 --- a/app/views/SearchMessagesView/index.tsx +++ b/app/views/SearchMessagesView/index.tsx @@ -37,7 +37,7 @@ import { type IUrl, type IAttachment, type IRoomInfoParam, - SubscriptionType + type SubscriptionType } from '~/definitions'; import { searchMessages } from '~/lib/services/restApi'; import { type TNavigation } from '~/stacks/stackType'; @@ -222,8 +222,7 @@ class SearchMessagesView extends Component { const { subscription } = this.state; - const { isMasterDetail } = this.props; + const { isMasterDetail, route } = this.props; Navigation.popToRoom(isMasterDetail); Navigation.push('RoomView', { rid: item.subscription.id, tmid: item.id, name: makeThreadName(item), - t: SubscriptionType.THREAD, + t: route.params.t, roomUserId: getUidDirectMessage(subscription) }); }, From 0aa7d6474e2a5f4939a194b892d0f1fcb05e8419 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 29 Oct 2026 15:42:59 -0300 Subject: [PATCH 3/9] refactor: replace the SubscriptionType enum with RoomType --- app/containers/Avatar/Avatar.tsx | 4 +- .../IncomingCallNotification/index.tsx | 4 +- .../NotifierComponent.stories.tsx | 6 +- .../InAppNotification/NotifierComponent.tsx | 4 +- .../RoomItem/__tests__/Touchable.test.tsx | 4 +- .../RoomItem/getRoomActionsOptions.ts | 4 +- app/containers/RoomItem/interfaces.ts | 16 ++-- .../message/components/MessageAvatar.tsx | 4 +- app/containers/message/components/User.tsx | 4 +- app/definitions/IRoom.ts | 10 +-- app/definitions/ISubscription.ts | 9 +-- app/ee/omnichannel/reducers/inquiry.test.ts | 4 +- app/lib/hooks/useUserData.ts | 6 +- .../useVideoConf/StartACallActionSheet.tsx | 6 +- .../hooks/useVideoConf/useVideoConfCall.ts | 14 ++-- .../createDirectMessageSubscriptionStub.ts | 4 +- app/lib/methods/getMoreMessages.ts | 12 +-- app/lib/methods/getPermalinks.test.ts | 79 ++++++++----------- app/lib/methods/getPermalinks.ts | 4 +- app/lib/methods/getRoomInfo.ts | 4 +- app/lib/methods/helpers/getAvatarUrl.test.ts | 10 +-- app/lib/methods/helpers/getAvatarUrl.ts | 4 +- app/lib/methods/helpers/goRoom.ts | 16 ++-- app/lib/methods/helpers/room.ts | 4 +- app/lib/methods/hideRoom.ts | 6 +- app/lib/methods/search.ts | 4 +- app/lib/methods/subscriptions/rooms.ts | 4 +- app/lib/notifications/index.ts | 6 +- app/lib/services/restApi.ts | 38 ++++----- .../services/voip/navigateToCallRoom.test.ts | 12 +-- app/lib/services/voip/navigateToCallRoom.ts | 4 +- app/stacks/MasterDetailStack/types.ts | 19 ++--- app/stacks/types.ts | 20 ++--- app/views/AddExistingChannelView/index.tsx | 4 +- .../__tests__/filterRoomsByTitle.test.ts | 6 +- app/views/CreateCategoryView/types.ts | 4 +- app/views/CreateDiscussionView/interfaces.ts | 5 +- app/views/DirectoryView/index.tsx | 8 +- app/views/MessagesView/index.tsx | 6 +- app/views/NewMessageView/hooks/useOpenChat.ts | 4 +- .../components/CallSection.stories.tsx | 4 +- .../components/CallSection.test.tsx | 4 +- app/views/RoomActionsView/index.tsx | 6 +- app/views/RoomInfoEditView/index.tsx | 8 +- .../components/RoomInfoButtons.stories.tsx | 4 +- .../components/RoomInfoButtons.test.tsx | 4 +- .../components/RoomInfoButtons.tsx | 4 +- .../components/RoomInfoViewAvatar.stories.tsx | 12 +-- .../components/RoomInfoViewAvatar.tsx | 6 +- .../components/RoomInfoViewBody.tsx | 4 +- .../components/RoomInfoViewTitle.stories.tsx | 16 ++-- .../components/RoomInfoViewTitle.tsx | 6 +- app/views/RoomInfoView/index.tsx | 10 +-- .../__tests__/ActionsSection.test.tsx | 4 +- app/views/RoomMembersView/helpers.ts | 13 +-- .../RoomViewContent/RoomViewContent.tsx | 4 +- app/views/RoomView/definitions.ts | 10 +-- .../useOmnichannelPermissions.test.tsx | 3 +- .../RoomView/hooks/useGoRoomActionsView.ts | 4 +- .../RoomView/hooks/useRoomActionsState.ts | 4 +- .../RoomView/hooks/useRoomMessageHandlers.tsx | 4 +- .../__tests__/navigateToScreen.test.ts | 4 +- app/views/RoomView/services/openThread.ts | 10 +-- .../utils/__tests__/groupRooms.test.ts | 20 ++--- app/views/SearchMessagesView/index.tsx | 6 +- 65 files changed, 251 insertions(+), 309 deletions(-) diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx index f5da333dc0e..a9eb1087271 100644 --- a/app/containers/Avatar/Avatar.tsx +++ b/app/containers/Avatar/Avatar.tsx @@ -6,7 +6,7 @@ import { settings as RocketChatSettings } from '@rocket.chat/sdk'; import Emoji from '../markdown/components/emoji/Emoji'; import { getAvatarURL } from '~/lib/methods/helpers/getAvatarUrl'; import { headers } from '~/lib/methods/helpers/fetch'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { type IAvatar } from './interfaces'; import I18n from '~/i18n'; import Touch from '../Touch'; @@ -30,7 +30,7 @@ const Avatar = memo( text, size = 25, borderRadius = AVATAR_BORDER_RADIUS, - type = SubscriptionType.DIRECT, + type = RoomType.DIRECT, avatarExternalProviderUrl, roomAvatarExternalProviderUrl, cdnPrefix, diff --git a/app/containers/InAppNotification/IncomingCallNotification/index.tsx b/app/containers/InAppNotification/IncomingCallNotification/index.tsx index 055de91a446..7e68a36f738 100644 --- a/app/containers/InAppNotification/IncomingCallNotification/index.tsx +++ b/app/containers/InAppNotification/IncomingCallNotification/index.tsx @@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux'; import { A11y } from 'react-native-a11y-order'; import { acceptCall, cancelCall } from '~/actions/videoConf'; -import { type ISubscription, type SubscriptionType } from '~/definitions'; +import { type ISubscription, type RoomType } from '~/definitions'; import i18n from '~/i18n'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { useEndpointData } from '~/lib/hooks/useEndpointData'; @@ -22,7 +22,7 @@ export interface INotifierComponent { text: string; payload: { sender: { username: string }; - type: SubscriptionType; + type: RoomType; } & Pick; title: string; avatar: string; diff --git a/app/containers/InAppNotification/NotifierComponent.stories.tsx b/app/containers/InAppNotification/NotifierComponent.stories.tsx index 05cc324f029..dd8217cbdee 100644 --- a/app/containers/InAppNotification/NotifierComponent.stories.tsx +++ b/app/containers/InAppNotification/NotifierComponent.stories.tsx @@ -3,7 +3,7 @@ import { type ReactNode } from 'react'; import { ThemeContext, type TSupportedThemes } from '~/theme'; import { themes } from '~/lib/constants/colors'; import NotifierComponent from './NotifierComponent'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { BASE_ROW_HEIGHT, BASE_ROW_HEIGHT_CONDENSED, @@ -27,7 +27,7 @@ const baseNotification = { rid: 'rid-1', name: 'general', sender: { username: 'rocket.cat' }, - type: SubscriptionType.CHANNEL + type: RoomType.CHANNEL }, title: 'general', avatar: 'rocket.cat' @@ -50,7 +50,7 @@ export const DirectMessage = () => ( ...baseNotification, payload: { ...baseNotification.payload, - type: SubscriptionType.DIRECT, + type: RoomType.DIRECT, sender: { username: 'diego.mello' } }, title: 'diego.mello', diff --git a/app/containers/InAppNotification/NotifierComponent.tsx b/app/containers/InAppNotification/NotifierComponent.tsx index 0c806eabe6c..e424904d618 100644 --- a/app/containers/InAppNotification/NotifierComponent.tsx +++ b/app/containers/InAppNotification/NotifierComponent.tsx @@ -8,7 +8,7 @@ import sharedStyles from '~/views/Styles'; import { themes } from '~/lib/constants/colors'; import { useTheme } from '~/theme'; import { goRoom } from '~/lib/methods/helpers/goRoom'; -import { type ISubscription, type SubscriptionType } from '~/definitions'; +import { type ISubscription, type RoomType } from '~/definitions'; import { hideNotification } from '~/lib/methods/helpers/notifications'; import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; import { withMasterDetail } from '~/lib/hooks/useMasterDetail'; @@ -19,7 +19,7 @@ export interface INotifierComponent { text: string; payload: { sender: { username: string }; - type: SubscriptionType; + type: RoomType; message?: { message?: string; msg?: string; t?: string }; } & Pick; title: string; diff --git a/app/containers/RoomItem/__tests__/Touchable.test.tsx b/app/containers/RoomItem/__tests__/Touchable.test.tsx index 82ffb3bac28..0f2908b7a8d 100644 --- a/app/containers/RoomItem/__tests__/Touchable.test.tsx +++ b/app/containers/RoomItem/__tests__/Touchable.test.tsx @@ -6,7 +6,7 @@ import { makeMutable } from 'react-native-reanimated'; import Touchable from '../Touchable'; import { settleSwipeRow, unregisterOpenSwipeItem } from '../utils/openSwipeItem'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; jest.mock('~/lib/hooks/useAppSelector', () => ({ useAppSelector: () => '7.0.0' })); jest.mock('react-native-gesture-handler', () => { @@ -18,7 +18,7 @@ const setup = ({ swipeEnabled = true } = {}) => { const onPress = jest.fn(); const { getByText } = render( ; @@ -66,7 +60,7 @@ export interface ITypeIconProps { interface IRoomItemTouchables { toggleFav?: (rid: string, favorite: boolean) => Promise; toggleRead?: (rid: string, tIsRead: boolean) => Promise; - hideChannel?: (rid: string, type: SubscriptionType) => Promise; + hideChannel?: (rid: string, type: RoomType) => Promise; onPress: (item?: any) => void; onLongPress?: (item?: any) => void; onMoveToCategoryPress?: () => void; @@ -96,7 +90,7 @@ export interface IRoomItemContainerProps extends IBaseRoomItem { export interface IRoomItemProps extends IBaseRoomItem { rid: string; - type: SubscriptionType; + type: RoomType; prid: string; name: string; avatar: string; @@ -126,7 +120,7 @@ export interface IRoomItemProps extends IBaseRoomItem { export interface ILastMessageProps { lastMessage: ILastMessage; - type: SubscriptionType; + type: RoomType; showLastMessage: boolean; username: string; useRealName: boolean; @@ -134,7 +128,7 @@ export interface ILastMessageProps { export interface ITouchableProps extends IRoomItemTouchables { children: ReactElement; - type: SubscriptionType; + type: RoomType; width: number; favorite: boolean; isRead: boolean; diff --git a/app/containers/message/components/MessageAvatar.tsx b/app/containers/message/components/MessageAvatar.tsx index 7c189b3cac8..6ad37d057db 100644 --- a/app/containers/message/components/MessageAvatar.tsx +++ b/app/containers/message/components/MessageAvatar.tsx @@ -4,7 +4,7 @@ import { View } from 'react-native'; import Avatar from '~/containers/Avatar'; import styles from '../styles'; import { type IMessageAvatar } from '../interfaces'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; import { useAvatar, useMessageField, useMessageGrouping } from '../stores/MessageStore'; import { useMessageUser, useNavToRoomInfo } from '../stores/MessageRoomStore'; @@ -31,7 +31,7 @@ const MessageAvatar = ({ small }: IMessageAvatar) => { if (isHeader && author) { const onPress = () => navToRoomInfo?.({ - t: SubscriptionType.DIRECT, + t: RoomType.DIRECT, rid: author._id, itsMe: author._id === user?.id }); diff --git a/app/containers/message/components/User.tsx b/app/containers/message/components/User.tsx index 3629c59d7ca..8235c8f34cf 100644 --- a/app/containers/message/components/User.tsx +++ b/app/containers/message/components/User.tsx @@ -1,6 +1,6 @@ import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { type MessageTypesValues, SubscriptionType } from '~/definitions'; +import { type MessageTypesValues, RoomType } from '~/definitions'; import { useTheme } from '~/theme'; import sharedStyles from '~/views/Styles'; import RightIcons from './RightIcons'; @@ -56,7 +56,7 @@ const User = () => { const onUserPress = () => { navToRoomInfo?.({ - t: SubscriptionType.DIRECT, + t: RoomType.DIRECT, rid: author?._id || '', itsMe }); diff --git a/app/definitions/IRoom.ts b/app/definitions/IRoom.ts index b2d898d2c72..d6a1af1e799 100644 --- a/app/definitions/IRoom.ts +++ b/app/definitions/IRoom.ts @@ -3,7 +3,7 @@ import type Model from '@nozbe/watermelondb/Model'; import { type IMessage } from './IMessage'; import { type IRocketChatRecord } from './IRocketChatRecord'; import { type IServedBy } from './IServedBy'; -import { type ISubscription, type IVisitor, type SubscriptionType } from './ISubscription'; +import { type ISubscription, type IVisitor } from './ISubscription'; import { type IUser, type TNotifications } from './IUser'; interface IRequestTranscript { @@ -19,7 +19,7 @@ export interface IRoomInfoParam { room?: ISubscription; member?: any; rid: string; - t: SubscriptionType; + t: RoomType; joined?: boolean; itsMe?: boolean; } @@ -30,7 +30,7 @@ export interface IRoom { id: string; rid: string; prid: string; - t: SubscriptionType; + t: RoomType; name: string; teamMain: boolean; alert?: boolean; @@ -79,7 +79,7 @@ export interface IRoomSettings { roomDescription?: string; roomTopic?: string; roomAnnouncement?: string; - roomType?: SubscriptionType; + roomType?: RoomType; readOnly?: boolean; reactWhenReadOnly?: boolean; systemMessages?: string[]; @@ -116,7 +116,7 @@ export interface IOmnichannelSource { export interface IOmnichannelRoom extends Partial> { _id: string; rid: string; - t: SubscriptionType.OMNICHANNEL; + t: 'l'; v: IVisitor; email?: { // Data used when the room is created from an email, via email Integration. diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts index 957d0b0e663..3ea0a8202db 100644 --- a/app/definitions/ISubscription.ts +++ b/app/definitions/ISubscription.ts @@ -10,13 +10,6 @@ import { type TThreadMessageModel } from './IThreadMessage'; import { type TUploadModel } from './IUpload'; import { type IUser } from './IUser'; -export enum SubscriptionType { - GROUP = 'p', - DIRECT = 'd', - CHANNEL = 'c', - OMNICHANNEL = 'l' -} - export interface IVisitor { _id?: string; token?: string; @@ -41,7 +34,7 @@ export interface ISubscription { _updatedAt?: string; v?: IVisitor; f: boolean; - t: SubscriptionType; // TODO: we need to review this type later + t: RoomType; ts: string | Date; ls: Date; name: string; diff --git a/app/ee/omnichannel/reducers/inquiry.test.ts b/app/ee/omnichannel/reducers/inquiry.test.ts index 94ff68f5e54..02c9eb54cb9 100644 --- a/app/ee/omnichannel/reducers/inquiry.test.ts +++ b/app/ee/omnichannel/reducers/inquiry.test.ts @@ -9,7 +9,7 @@ import { } from '../actions/inquiry'; import { mockedStore } from '~/reducers/mockedStore'; import { initialState } from './inquiry'; -import { type IOmnichannelRoom, OmnichannelSourceType, SubscriptionType } from '~/definitions'; +import { type IOmnichannelRoom, OmnichannelSourceType, RoomType } from '~/definitions'; describe('test inquiry reduce', () => { const enabledObj = { @@ -29,7 +29,7 @@ describe('test inquiry reduce', () => { token: '123456789', status: 'online' }, - t: SubscriptionType.OMNICHANNEL, + t: RoomType.OMNICHANNEL, queueOrder: '1', estimatedWaitingTimeQueue: '0', estimatedServiceTimeAt: new Date(), diff --git a/app/lib/hooks/useUserData.ts b/app/lib/hooks/useUserData.ts index a637e1f458b..df54e8a1a44 100644 --- a/app/lib/hooks/useUserData.ts +++ b/app/lib/hooks/useUserData.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { getSubscriptionByRoomId } from '../database/services/Subscription'; import { getRoomAvatar, getUidDirectMessage } from '../methods/helpers/helpers'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { getUserInfo } from '../services/restApi'; import { useAppSelector } from './useAppSelector'; @@ -23,7 +23,7 @@ const useUserData = (rid: string) => { username, avatar: avt, type: room?.t || '', - direct: room?.t === SubscriptionType.DIRECT + direct: room?.t === RoomType.DIRECT }); } else { try { @@ -38,7 +38,7 @@ const useUserData = (rid: string) => { username, avatar: user.username, uid: user._id, - type: SubscriptionType.DIRECT, + type: RoomType.DIRECT, direct: true }); } diff --git a/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx b/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx index 86f5c861b3a..be10014a40b 100644 --- a/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx +++ b/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx @@ -10,13 +10,13 @@ import AvatarContainer from '~/containers/Avatar'; import Button from '~/containers/Button'; import { CallHeader } from '~/containers/CallHeader'; import Ringer, { ERingerSounds } from '~/containers/Ringer'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import i18n from '~/i18n'; import { getUserSelector } from '~/selectors/login'; import { useTheme } from '~/theme'; import useUserData from '../useUserData'; -export default function StartACallActionSheet({ rid, roomType }: { rid: string; roomType?: SubscriptionType }): ReactElement { +export default function StartACallActionSheet({ rid, roomType }: { rid: string; roomType?: RoomType }): ReactElement { const { colors } = useTheme(); const [mic, setMic] = useState(true); const [cam, setCam] = useState(false); @@ -44,7 +44,7 @@ export default function StartACallActionSheet({ rid, roomType }: { rid: string; setContainerWidth(e.nativeEvent.layout.width / 2)}> - {calling && roomType === SubscriptionType.DIRECT ? : null} + {calling && roomType === RoomType.DIRECT ? : null} { +export const useVideoConfCall = (rid: string): { callEnabled: boolean; disabledTooltip?: boolean; roomType?: RoomType } => { const [callEnabled, setCallEnabled] = useState(false); const [disabledTooltip, setDisabledTooltip] = useState(false); - const [roomType, setRoomType] = useState(); + const [roomType, setRoomType] = useState(); // Read all call-related settings in a single subscription instead of one useSetting per key. const settings = useAppSelector( @@ -71,9 +69,9 @@ export const useVideoConfCall = ( const isJitsiDisabledForTeams = room.teamMain && !jitsiEnableTeams; const isJitsiDisabledForChannels = !room.teamMain && (room.t === 'p' || room.t === 'c') && !jitsiEnableChannels; - if (room.t === SubscriptionType.DIRECT) return setCallEnabled(!!jitsiEnabled); - if (room.t === SubscriptionType.CHANNEL) return setCallEnabled(!isJitsiDisabledForChannels); - if (room.t === SubscriptionType.GROUP) return setCallEnabled(!isJitsiDisabledForTeams); + if (room.t === RoomType.DIRECT) return setCallEnabled(!!jitsiEnabled); + if (room.t === RoomType.CHANNEL) return setCallEnabled(!isJitsiDisabledForChannels); + if (room.t === RoomType.GROUP) return setCallEnabled(!isJitsiDisabledForTeams); } return setCallEnabled(false); }; diff --git a/app/lib/methods/createDirectMessageSubscriptionStub.ts b/app/lib/methods/createDirectMessageSubscriptionStub.ts index abbaf1dbca2..b09e919f3dd 100644 --- a/app/lib/methods/createDirectMessageSubscriptionStub.ts +++ b/app/lib/methods/createDirectMessageSubscriptionStub.ts @@ -1,7 +1,7 @@ import database from '../database'; import { SUBSCRIPTIONS_TABLE } from '../database/model/Subscription'; import { getSubscriptionByRoomId } from '../database/services/Subscription'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { store as reduxStore } from '../store/auxStore'; import log from './helpers/log'; @@ -62,7 +62,7 @@ export const createDirectMessageSubscriptionStub = async ({ s._raw.id = rid; s._raw._id = rid; s.rid = rid; - s.t = SubscriptionType.DIRECT; + s.t = RoomType.DIRECT; s.name = username; s.fname = fname || username; s.uids = otherUserId ? [currentUserId, otherUserId] : [currentUserId]; diff --git a/app/lib/methods/getMoreMessages.ts b/app/lib/methods/getMoreMessages.ts index f27a4de7509..05472d28cbd 100644 --- a/app/lib/methods/getMoreMessages.ts +++ b/app/lib/methods/getMoreMessages.ts @@ -1,17 +1,9 @@ -import { type SubscriptionType, type TAnyMessageModel } from '~/definitions'; +import { type RoomType, type TAnyMessageModel } from '~/definitions'; import { loadNextMessages } from './loadNextMessages'; import { loadMessagesForRoom } from './loadMessagesForRoom'; import { MessageTypeLoad } from '../constants/messageTypeLoad'; -const getMoreMessages = ({ - rid, - t, - loaderItem -}: { - rid: string; - t: SubscriptionType; - loaderItem: TAnyMessageModel; -}): Promise => { +const getMoreMessages = ({ rid, t, loaderItem }: { rid: string; t: RoomType; loaderItem: TAnyMessageModel }): Promise => { if ([MessageTypeLoad.MORE, MessageTypeLoad.PREVIOUS_CHUNK].includes(loaderItem.t as MessageTypeLoad)) { return loadMessagesForRoom({ rid, diff --git a/app/lib/methods/getPermalinks.test.ts b/app/lib/methods/getPermalinks.test.ts index 7acb04151a1..ccf20995682 100644 --- a/app/lib/methods/getPermalinks.test.ts +++ b/app/lib/methods/getPermalinks.test.ts @@ -1,29 +1,29 @@ import { buildPermalinkChannel, buildPermalinkMessage } from './getPermalinks'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; describe('buildPermalinkChannel', () => { test('normal_name', () => { - expect(buildPermalinkChannel('https://server.com', { name: 'normal_name', rid: '123', t: SubscriptionType.CHANNEL })).toBe( + expect(buildPermalinkChannel('https://server.com', { name: 'normal_name', rid: '123', t: RoomType.CHANNEL })).toBe( 'https://server.com/channel/normal_name' ); - expect(buildPermalinkChannel('https://server.com', { name: 'normal_name', rid: '123', t: SubscriptionType.GROUP })).toBe( + expect(buildPermalinkChannel('https://server.com', { name: 'normal_name', rid: '123', t: RoomType.GROUP })).toBe( 'https://server.com/group/normal_name' ); - expect(buildPermalinkChannel('https://server.com', { name: 'normal_name', rid: '123', t: SubscriptionType.DIRECT })).toBe( + expect(buildPermalinkChannel('https://server.com', { name: 'normal_name', rid: '123', t: RoomType.DIRECT })).toBe( 'https://server.com/direct/normal_name' ); }); test('name with spaces', () => { - expect( - buildPermalinkChannel('https://server.com', { name: 'name with spaces', rid: '123', t: SubscriptionType.CHANNEL }) - ).toBe('https://server.com/channel/name%20with%20spaces'); - expect(buildPermalinkChannel('https://server.com', { name: 'name with spaces', rid: '123', t: SubscriptionType.GROUP })).toBe( + expect(buildPermalinkChannel('https://server.com', { name: 'name with spaces', rid: '123', t: RoomType.CHANNEL })).toBe( + 'https://server.com/channel/name%20with%20spaces' + ); + expect(buildPermalinkChannel('https://server.com', { name: 'name with spaces', rid: '123', t: RoomType.GROUP })).toBe( 'https://server.com/group/name%20with%20spaces' ); - expect( - buildPermalinkChannel('https://server.com', { name: 'name with spaces', rid: '123', t: SubscriptionType.DIRECT }) - ).toBe('https://server.com/direct/name%20with%20spaces'); + expect(buildPermalinkChannel('https://server.com', { name: 'name with spaces', rid: '123', t: RoomType.DIRECT })).toBe( + 'https://server.com/direct/name%20with%20spaces' + ); }); test('Русское название', () => { @@ -31,13 +31,13 @@ describe('buildPermalinkChannel', () => { buildPermalinkChannel('https://server.com', { name: 'Русское название с пробелами', rid: '123', - t: SubscriptionType.CHANNEL + t: RoomType.CHANNEL }) ).toBe( 'https://server.com/channel/%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BF%D1%80%D0%BE%D0%B1%D0%B5%D0%BB%D0%B0%D0%BC%D0%B8' ); expect( - buildPermalinkChannel('https://server.com', { name: 'Русское название с пробелами', rid: '123', t: SubscriptionType.GROUP }) + buildPermalinkChannel('https://server.com', { name: 'Русское название с пробелами', rid: '123', t: RoomType.GROUP }) ).toBe( 'https://server.com/group/%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BF%D1%80%D0%BE%D0%B1%D0%B5%D0%BB%D0%B0%D0%BC%D0%B8' ); @@ -45,7 +45,7 @@ describe('buildPermalinkChannel', () => { buildPermalinkChannel('https://server.com', { name: 'Русское название с пробелами', rid: '123', - t: SubscriptionType.DIRECT + t: RoomType.DIRECT }) ).toBe( 'https://server.com/direct/%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%BE%D0%B5%20%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BF%D1%80%D0%BE%D0%B1%D0%B5%D0%BB%D0%B0%D0%BC%D0%B8' @@ -55,53 +55,38 @@ describe('buildPermalinkChannel', () => { describe('buildPermalinkMessage', () => { test('group chat', () => { - expect( - buildPermalinkMessage('https://server.com', true, { rid: '123', name: 'name', t: SubscriptionType.CHANNEL }, 'zxc') - ).toBe('https://server.com/channel/123?msg=zxc'); - expect( - buildPermalinkMessage('https://server.com', true, { rid: '123', name: 'name', t: SubscriptionType.GROUP }, 'zxc') - ).toBe('https://server.com/group/123?msg=zxc'); - expect( - buildPermalinkMessage('https://server.com', true, { rid: '123', name: 'name', t: SubscriptionType.DIRECT }, 'zxc') - ).toBe('https://server.com/direct/123?msg=zxc'); + expect(buildPermalinkMessage('https://server.com', true, { rid: '123', name: 'name', t: RoomType.CHANNEL }, 'zxc')).toBe( + 'https://server.com/channel/123?msg=zxc' + ); + expect(buildPermalinkMessage('https://server.com', true, { rid: '123', name: 'name', t: RoomType.GROUP }, 'zxc')).toBe( + 'https://server.com/group/123?msg=zxc' + ); + expect(buildPermalinkMessage('https://server.com', true, { rid: '123', name: 'name', t: RoomType.DIRECT }, 'zxc')).toBe( + 'https://server.com/direct/123?msg=zxc' + ); }); test('normal_name', () => { expect( - buildPermalinkMessage('https://server.com', false, { rid: '123', name: 'normal_name', t: SubscriptionType.CHANNEL }, 'zxc') + buildPermalinkMessage('https://server.com', false, { rid: '123', name: 'normal_name', t: RoomType.CHANNEL }, 'zxc') ).toBe('https://server.com/channel/normal_name?msg=zxc'); expect( - buildPermalinkMessage('https://server.com', false, { rid: '123', name: 'normal_name', t: SubscriptionType.GROUP }, 'zxc') + buildPermalinkMessage('https://server.com', false, { rid: '123', name: 'normal_name', t: RoomType.GROUP }, 'zxc') ).toBe('https://server.com/group/normal_name?msg=zxc'); expect( - buildPermalinkMessage('https://server.com', false, { rid: '123', name: 'normal_name', t: SubscriptionType.DIRECT }, 'zxc') + buildPermalinkMessage('https://server.com', false, { rid: '123', name: 'normal_name', t: RoomType.DIRECT }, 'zxc') ).toBe('https://server.com/direct/normal_name?msg=zxc'); }); test('name with spaces', () => { expect( - buildPermalinkMessage( - 'https://server.com', - false, - { rid: '123', name: 'name with spaces', t: SubscriptionType.CHANNEL }, - 'zxc' - ) + buildPermalinkMessage('https://server.com', false, { rid: '123', name: 'name with spaces', t: RoomType.CHANNEL }, 'zxc') ).toBe('https://server.com/channel/name%20with%20spaces?msg=zxc'); expect( - buildPermalinkMessage( - 'https://server.com', - false, - { rid: '123', name: 'name with spaces', t: SubscriptionType.GROUP }, - 'zxc' - ) + buildPermalinkMessage('https://server.com', false, { rid: '123', name: 'name with spaces', t: RoomType.GROUP }, 'zxc') ).toBe('https://server.com/group/name%20with%20spaces?msg=zxc'); expect( - buildPermalinkMessage( - 'https://server.com', - false, - { rid: '123', name: 'name with spaces', t: SubscriptionType.DIRECT }, - 'zxc' - ) + buildPermalinkMessage('https://server.com', false, { rid: '123', name: 'name with spaces', t: RoomType.DIRECT }, 'zxc') ).toBe('https://server.com/direct/name%20with%20spaces?msg=zxc'); }); @@ -110,7 +95,7 @@ describe('buildPermalinkMessage', () => { buildPermalinkMessage( 'https://server.com', false, - { rid: '123', name: 'Русское название с пробелами', t: SubscriptionType.CHANNEL }, + { rid: '123', name: 'Русское название с пробелами', t: RoomType.CHANNEL }, 'zxc' ) ).toBe( @@ -120,7 +105,7 @@ describe('buildPermalinkMessage', () => { buildPermalinkMessage( 'https://server.com', false, - { rid: '123', name: 'Русское название с пробелами', t: SubscriptionType.GROUP }, + { rid: '123', name: 'Русское название с пробелами', t: RoomType.GROUP }, 'zxc' ) ).toBe( @@ -130,7 +115,7 @@ describe('buildPermalinkMessage', () => { buildPermalinkMessage( 'https://server.com', false, - { rid: '123', name: 'Русское название с пробелами', t: SubscriptionType.DIRECT }, + { rid: '123', name: 'Русское название с пробелами', t: RoomType.DIRECT }, 'zxc' ) ).toBe( diff --git a/app/lib/methods/getPermalinks.ts b/app/lib/methods/getPermalinks.ts index 6ea56cb5b75..8deaa27eca8 100644 --- a/app/lib/methods/getPermalinks.ts +++ b/app/lib/methods/getPermalinks.ts @@ -1,11 +1,11 @@ import log from './helpers/log'; -import { type RoomType, type TAnyMessageModel, type TSubscriptionModel, type SubscriptionType } from '~/definitions'; +import { type RoomType, type TAnyMessageModel, type TSubscriptionModel } from '~/definitions'; import { store } from '../store/auxStore'; import { isGroupChat } from './helpers'; import { getRoom } from './getRoom'; interface ISubscription { - t: SubscriptionType; + t: RoomType; name: string; rid: string; } diff --git a/app/lib/methods/getRoomInfo.ts b/app/lib/methods/getRoomInfo.ts index 61aacc78d96..db662f70470 100644 --- a/app/lib/methods/getRoomInfo.ts +++ b/app/lib/methods/getRoomInfo.ts @@ -1,4 +1,4 @@ -import { type IServerSubscription, type RoomType } from '~/definitions'; +import { type IServerSubscription } from '~/definitions'; import { getSubscriptionByRoomId } from '../database/services/Subscription'; import { getRoomInfo as getRoomInfoService } from '../services/restApi'; @@ -17,7 +17,7 @@ const getRoomInfo = async (rid: string): Promise => { rid, name: result.name, fname: result.fname, - t: result.t as RoomType + t: result.t }; } diff --git a/app/lib/methods/helpers/getAvatarUrl.test.ts b/app/lib/methods/helpers/getAvatarUrl.test.ts index 0eff6a5143c..d2f9a82b7f0 100644 --- a/app/lib/methods/helpers/getAvatarUrl.test.ts +++ b/app/lib/methods/helpers/getAvatarUrl.test.ts @@ -1,5 +1,5 @@ import { formatUrl, getAvatarURL } from './getAvatarUrl'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; jest.mock('react-native', () => ({ PixelRatio: { get: () => 1 } })); @@ -61,7 +61,7 @@ describe('getAvatarURL function', () => { }); test('uses the external provider URL for direct messages when the server is older than 6.12.0', () => { - const type = SubscriptionType.DIRECT; + const type = RoomType.DIRECT; const text = 'username123'; const avatarExternalProviderUrl = 'https://external.provider.com/avatar/{username}'; const serverVersion = '6.11.0'; @@ -73,7 +73,7 @@ describe('getAvatarURL function', () => { }); test('routes direct messages through the server when it is 6.12.0 or newer, ignoring the external provider URL', () => { - const type = SubscriptionType.DIRECT; + const type = RoomType.DIRECT; const text = 'username123'; const avatarExternalProviderUrl = 'https://external.provider.com/avatar/{username}'; const server = 'https://mobile.qa.rocket.chat'; @@ -86,7 +86,7 @@ describe('getAvatarURL function', () => { }); test('routes direct messages through the server when the server version is unknown', () => { - const type = SubscriptionType.DIRECT; + const type = RoomType.DIRECT; const text = 'username123'; const avatarExternalProviderUrl = 'https://external.provider.com/avatar/{username}'; const server = 'https://mobile.qa.rocket.chat'; @@ -204,7 +204,7 @@ describe('getAvatarURL function', () => { }); test('trims trailing slashes from external provider URLs on servers older than 6.12.0', () => { - const type = SubscriptionType.DIRECT; + const type = RoomType.DIRECT; const text = 'username123'; const avatarExternalProviderUrl = 'https://external.provider.com/avatar/{username}//'; const serverVersion = '6.11.0'; diff --git a/app/lib/methods/helpers/getAvatarUrl.ts b/app/lib/methods/helpers/getAvatarUrl.ts index 15883a8fc81..2a3a83ccd2e 100644 --- a/app/lib/methods/helpers/getAvatarUrl.ts +++ b/app/lib/methods/helpers/getAvatarUrl.ts @@ -1,6 +1,6 @@ import { PixelRatio } from 'react-native'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { type IAvatar } from '~/containers/Avatar/interfaces'; import { compareServerVersion } from './compareServerVersion'; @@ -32,7 +32,7 @@ export const getAvatarURL = ({ // so we only build the external URL client-side for older servers that lack that proxy. const serverLacksExternalAvatarProxy = compareServerVersion(serverVersion, 'lowerThan', '6.12.0'); let room; - if (type === SubscriptionType.DIRECT) { + if (type === RoomType.DIRECT) { room = text; if (avatarExternalProviderUrl && serverLacksExternalAvatarProxy) { const externalUri = avatarExternalProviderUrl.trim().replace(/\/+$/, '').replace('{username}', room); diff --git a/app/lib/methods/helpers/goRoom.ts b/app/lib/methods/helpers/goRoom.ts index 76b2e589988..0cd571f88b1 100644 --- a/app/lib/methods/helpers/goRoom.ts +++ b/app/lib/methods/helpers/goRoom.ts @@ -2,13 +2,7 @@ import { CommonActions } from '@react-navigation/native'; import { getSubscriptionByRoomId } from '~/lib/database/services/Subscription'; import Navigation from '~/lib/navigation/appNavigation'; -import { - type IOmnichannelRoom, - SubscriptionType, - type IVisitor, - type TSubscriptionModel, - type ISubscription -} from '~/definitions'; +import { type IOmnichannelRoom, RoomType, type IVisitor, type TSubscriptionModel, type ISubscription } from '~/definitions'; import { getRoomTitle, getUidDirectMessage } from './helpers'; import { createDirectMessage } from '../createDirectMessage'; import { emitErrorCreateDirectMessage } from './emitErrorCreateDirectMessage'; @@ -16,7 +10,7 @@ import { emitErrorCreateDirectMessage } from './emitErrorCreateDirectMessage'; interface IGoRoomItem { search?: boolean; // comes from spotlight username?: string; - t?: SubscriptionType; + t?: RoomType; rid?: string; name?: string; prid?: string; @@ -29,7 +23,7 @@ export type TGoRoomItem = IGoRoomItem | TSubscriptionModel | ISubscription | IOm interface RoomRouteParams { rid?: string; name: string; - t?: SubscriptionType; + t?: RoomType; prid?: string; visitor?: IVisitor; joinCodeRequired?: boolean; @@ -105,7 +99,7 @@ export const goRoom = async ({ jumpToMessageId?: string; usedCannedResponse?: string; }): Promise => { - if (!('id' in item) && item.t === SubscriptionType.DIRECT && item?.search) { + if (!('id' in item) && item.t === RoomType.DIRECT && item?.search) { // if user is using the search we need first to join/create room try { const { username } = item; @@ -115,7 +109,7 @@ export const goRoom = async ({ item: { rid: result.room._id, name: username || '', - t: SubscriptionType.DIRECT + t: RoomType.DIRECT }, isMasterDetail, ...props diff --git a/app/lib/methods/helpers/room.ts b/app/lib/methods/helpers/room.ts index e1fe799dc80..6d21db028ea 100644 --- a/app/lib/methods/helpers/room.ts +++ b/app/lib/methods/helpers/room.ts @@ -1,13 +1,13 @@ import dayjs from '~/lib/dayjs'; import { themes } from '~/lib/constants/colors'; import I18n from '~/i18n'; -import { type IAttachment, SubscriptionType } from '~/definitions'; +import { type IAttachment, RoomType } from '~/definitions'; import { type TSupportedThemes } from '~/theme'; export const isBlocked = (room: { t?: string; blocked?: boolean; blocker?: boolean }): boolean => { if (room) { const { t, blocked, blocker } = room; - if (t === SubscriptionType.DIRECT && (blocked || blocker)) { + if (t === RoomType.DIRECT && (blocked || blocker)) { return true; } } diff --git a/app/lib/methods/hideRoom.ts b/app/lib/methods/hideRoom.ts index 3a98f30a139..79dbc9550fc 100644 --- a/app/lib/methods/hideRoom.ts +++ b/app/lib/methods/hideRoom.ts @@ -1,13 +1,13 @@ -import { type RoomType, type SubscriptionType } from '~/definitions'; +import { type RoomType } from '~/definitions'; import database from '../database'; import log, { events, logEvent } from './helpers/log'; import { hideRoom as hideRoomService } from '../services/restApi'; -export const hideRoom = async (rid: string, type: SubscriptionType) => { +export const hideRoom = async (rid: string, type: RoomType) => { logEvent(events.RL_HIDE_CHANNEL); try { const db = database.active; - const result = await hideRoomService(rid, type as RoomType); + const result = await hideRoomService(rid, type); if (result.success) { const subCollection = db.get('subscriptions'); await db.write(async () => { diff --git a/app/lib/methods/search.ts b/app/lib/methods/search.ts index 04f518f47d0..eeb58e0858a 100644 --- a/app/lib/methods/search.ts +++ b/app/lib/methods/search.ts @@ -4,7 +4,7 @@ import { getSubscriptionSearchClause, sanitizeLikeString } from '../database/uti import database from '../database/index'; import { store as reduxStore } from '../store/auxStore'; import { spotlight } from '../services/restApi'; -import { type ISearch, type ISearchLocal, type IUserMessage, SubscriptionType, type TSubscriptionModel } from '~/definitions'; +import { type ISearch, type ISearchLocal, type IUserMessage, RoomType, type TSubscriptionModel } from '~/definitions'; import { isGroupChat, isReadOnly } from './helpers'; import { isE2EEDisabledEncryptedRoom, isMissingRoomE2EEKey } from '../encryption/utils'; @@ -164,7 +164,7 @@ export const searchRemote = async ({ rid: user.username, name: user.username, fname: user.name, - t: SubscriptionType.DIRECT, + t: RoomType.DIRECT, search: true }); }); diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index d32243bfcce..02b2106c8a2 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -26,7 +26,7 @@ import { type TRoomModel, type TThreadMessageModel, type TThreadModel, - SubscriptionType + RoomType } from '~/definitions'; import sdk from '~/lib/services/sdk'; import { type IDDPMessage } from '~/definitions/IDDPMessage'; @@ -254,7 +254,7 @@ const debouncedUpdate = (subscription: ISubscription) => { const room = batch[key] as IRoom; // If the omnichannel's chat is onHold and waitingResponse we shouldn't create or update the chat, // because it should go to Queue - if (room.t === SubscriptionType.OMNICHANNEL && room.onHold && room.waitingResponse) { + if (room.t === RoomType.OMNICHANNEL && room.onHold && room.waitingResponse) { return null; } const subQueueId = getSubQueueId(room._id); diff --git a/app/lib/notifications/index.ts b/app/lib/notifications/index.ts index 24188cca6a3..9ca621c3724 100644 --- a/app/lib/notifications/index.ts +++ b/app/lib/notifications/index.ts @@ -3,7 +3,7 @@ import { Platform } from 'react-native'; import { appInit } from '~/actions/app'; import { deepLinkingClickCallPush, deepLinkingOpen } from '~/actions/deepLinking'; -import { type INotification, SubscriptionType } from '~/definitions'; +import { type INotification, RoomType } from '~/definitions'; import { store } from '../store/auxStore'; import { pushNotificationConfigure, removeAllNotifications, setNotificationsBadgeCount } from './push'; @@ -59,9 +59,9 @@ export const onNotification = (push: INotification): void => { l: 'channels' }; let roomName = name; - if (type === SubscriptionType.DIRECT) { + if (type === RoomType.DIRECT) { roomName = sender?.username ?? name; - } else if (type === SubscriptionType.OMNICHANNEL) { + } else if (type === RoomType.OMNICHANNEL) { roomName = sender?.name ?? name; } diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index f62ce87a562..ebf24614cc5 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -14,8 +14,7 @@ import { type IRoomNotifications, type IServerRoom, type ISidebarCategory, - type RoomType, - type SubscriptionType + type RoomType } from '~/definitions'; import { type TParams } from '~/definitions/ILivechatEditView'; import { type ILivechatTag } from '~/definitions/ILivechatTag'; @@ -383,10 +382,7 @@ export const toggleReadStatus = ( return sdk.post(endpoint, payload); }; -export const getRoomCounters = ( - roomId: string, - t: SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL -) => +export const getRoomCounters = (roomId: string, t: Exclude) => // RC 0.65.0 sdk.get(`${roomTypeToApiType(t)}.counters`, { roomId }); @@ -602,11 +598,11 @@ export const toggleRoomOwner = ({ isOwner }: { roomId: string; - t: SubscriptionType; + t: RoomType; userId: string; isOwner: boolean; }) => { - const type = t as SubscriptionType.CHANNEL; + const type = t as 'c'; if (isOwner) { // RC 0.49.4 return sdk.post(`${roomTypeToApiType(type)}.addOwner`, { roomId, userId }); @@ -622,11 +618,11 @@ export const toggleRoomLeader = ({ isLeader }: { roomId: string; - t: SubscriptionType; + t: RoomType; userId: string; isLeader: boolean; }) => { - const type = t as SubscriptionType.CHANNEL; + const type = t as 'c'; if (isLeader) { // RC 0.58.0 return sdk.post(`${roomTypeToApiType(type)}.addLeader`, { roomId, userId }); @@ -642,11 +638,11 @@ export const toggleRoomModerator = ({ isModerator }: { roomId: string; - t: SubscriptionType; + t: RoomType; userId: string; isModerator: boolean; }) => { - const type = t as SubscriptionType.CHANNEL; + const type = t as 'c'; if (isModerator) { // RC 0.49.4 return sdk.post(`${roomTypeToApiType(type)}.addModerator`, { roomId, userId }); @@ -663,8 +659,8 @@ export const ignoreUser = ({ rid, userId, ignore }: { rid: string; userId: strin // RC 0.64.0 sdk.get('chat.ignoreUser', { rid, userId, ignore }); -export const toggleArchiveRoom = (roomId: string, t: SubscriptionType, archive: boolean) => { - const type = t as SubscriptionType.CHANNEL | SubscriptionType.GROUP; +export const toggleArchiveRoom = (roomId: string, t: RoomType, archive: boolean) => { + const type = t as 'c' | 'p'; if (archive) { // RC 0.48.0 return sdk.post(`${roomTypeToApiType(type)}.archive`, { roomId }); @@ -685,7 +681,7 @@ export const saveRoomSettings = ( roomDescription?: string; roomTopic?: string; roomAnnouncement?: string; - roomType?: SubscriptionType; + roomType?: RoomType; readOnly?: boolean; reactWhenReadOnly?: boolean; systemMessages?: string[]; @@ -822,8 +818,8 @@ export const getUsernameSuggestion = () => // RC 0.65.0 sdk.get('users.getUsernameSuggestion'); -export const getFiles = (roomId: string, type: SubscriptionType, offset: number) => { - const t = type as SubscriptionType.DIRECT | SubscriptionType.CHANNEL | SubscriptionType.GROUP; +export const getFiles = (roomId: string, type: RoomType, offset: number) => { + const t = type as Exclude; // RC 0.59.0 return sdk.get(`${roomTypeToApiType(t)}.files`, { roomId, @@ -841,13 +837,13 @@ export const getMessages = ({ pinned }: { roomId: string; - type: SubscriptionType; + type: RoomType; offset: number; mentionIds?: string[]; starredIds?: string[]; pinned?: boolean; }) => { - const t = type as SubscriptionType.DIRECT | SubscriptionType.CHANNEL | SubscriptionType.GROUP; + const t = type as Exclude; const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0')) { @@ -1276,12 +1272,12 @@ export const getRoomMembers = async ({ rid: string; allUsers: boolean; type: 'all' | 'online'; - roomType: SubscriptionType; + roomType: RoomType; filter: string; skip: number; limit: number; }) => { - const t = roomType as SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.DIRECT; + const t = roomType as Exclude; const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.16.0')) { const params = { diff --git a/app/lib/services/voip/navigateToCallRoom.test.ts b/app/lib/services/voip/navigateToCallRoom.test.ts index 85ee069edf8..78a10c60560 100644 --- a/app/lib/services/voip/navigateToCallRoom.test.ts +++ b/app/lib/services/voip/navigateToCallRoom.test.ts @@ -2,7 +2,7 @@ import { goRoom } from '~/lib/methods/helpers/goRoom'; import Navigation from '~/lib/navigation/appNavigation'; import { useCallStore } from './useCallStore'; import { navigateToCallRoom } from './navigateToCallRoom'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; jest.mock('./useCallStore', () => ({ useCallStore: { @@ -73,7 +73,7 @@ describe('navigateToCallRoom', () => { await navigateToCallRoom({ isMasterDetail: true }); expect(mockGoRoom).toHaveBeenCalledWith({ - item: { rid: 'rid-1', name: 'u', t: SubscriptionType.DIRECT }, + item: { rid: 'rid-1', name: 'u', t: RoomType.DIRECT }, isMasterDetail: true }); }); @@ -109,7 +109,7 @@ describe('navigateToCallRoom', () => { expect(toggleFocus).toHaveBeenCalledTimes(1); expect(mockGoRoom).toHaveBeenCalledWith({ - item: { rid: 'rid-1', name: 'alice', t: SubscriptionType.DIRECT }, + item: { rid: 'rid-1', name: 'alice', t: RoomType.DIRECT }, isMasterDetail: true }); expect(toggleFocus.mock.invocationCallOrder[0]).toBeLessThan(mockGoRoom.mock.invocationCallOrder[0]); @@ -129,7 +129,7 @@ describe('navigateToCallRoom', () => { expect(toggleFocus).not.toHaveBeenCalled(); expect(mockGoRoom).toHaveBeenCalledWith({ - item: { rid: 'rid-1', name: 'alice', t: SubscriptionType.DIRECT }, + item: { rid: 'rid-1', name: 'alice', t: RoomType.DIRECT }, isMasterDetail: true }); }); @@ -149,7 +149,7 @@ describe('navigateToCallRoom', () => { expect(mockNavigation.navigate).toHaveBeenCalledWith('ChatsStackNavigator'); expect(mockGoRoom).toHaveBeenCalledWith({ - item: { rid: 'rid-1', name: 'alice', t: SubscriptionType.DIRECT }, + item: { rid: 'rid-1', name: 'alice', t: RoomType.DIRECT }, isMasterDetail: true }); expect(mockNavigation.navigate.mock.invocationCallOrder[0]).toBeLessThan(mockGoRoom.mock.invocationCallOrder[0]); @@ -221,7 +221,7 @@ describe('navigateToCallRoom', () => { await navigateToCallRoom({ isMasterDetail: false }); expect(mockGoRoom).toHaveBeenCalledWith({ - item: { rid: 'rid-1', name: 'alice', t: SubscriptionType.DIRECT }, + item: { rid: 'rid-1', name: 'alice', t: RoomType.DIRECT }, isMasterDetail: false }); }); diff --git a/app/lib/services/voip/navigateToCallRoom.ts b/app/lib/services/voip/navigateToCallRoom.ts index 1aea43af497..f8fcc5ca2e4 100644 --- a/app/lib/services/voip/navigateToCallRoom.ts +++ b/app/lib/services/voip/navigateToCallRoom.ts @@ -1,4 +1,4 @@ -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { goRoom } from '~/lib/methods/helpers/goRoom'; import Navigation from '~/lib/navigation/appNavigation'; import { useCallStore } from './useCallStore'; @@ -34,7 +34,7 @@ export async function navigateToCallRoom({ isMasterDetail }: { isMasterDetail: b item: { rid: roomId, name: username, - t: SubscriptionType.DIRECT + t: RoomType.DIRECT }, isMasterDetail }); diff --git a/app/stacks/MasterDetailStack/types.ts b/app/stacks/MasterDetailStack/types.ts index a893059f309..679d1eabb6a 100644 --- a/app/stacks/MasterDetailStack/types.ts +++ b/app/stacks/MasterDetailStack/types.ts @@ -4,7 +4,8 @@ import { type IAttachment } from '~/definitions/IAttachment'; import { type ILivechatDepartment } from '~/definitions/ILivechatDepartment'; import { type ILivechatTag } from '~/definitions/ILivechatTag'; import { type IMessage, type TAnyMessageModel } from '~/definitions/IMessage'; -import { type ISubscription, type SubscriptionType, type TSubscriptionModel } from '~/definitions/ISubscription'; +import { type RoomType } from '~/definitions/IRoom'; +import { type ISubscription, type TSubscriptionModel } from '~/definitions/ISubscription'; import { type TChangeAvatarViewContext } from '~/definitions/TChangeAvatarViewContext'; import { type CategoryViewParams } from '~/views/CategoryView'; import { type CategorySettingsViewParams } from '~/views/CategorySettingsView'; @@ -19,7 +20,7 @@ import { type ConfirmCategoryViewParams } from '~/views/CreateCategoryView/Confi export type MasterDetailChatsStackParamList = { RoomView: { rid: string; - t: SubscriptionType; + t: RoomType; tmid?: string; message?: string; name?: string; @@ -42,7 +43,7 @@ export type ModalStackParamList = { room: ISubscription; member?: any; rid: string; - t: SubscriptionType; + t: RoomType; joined: boolean; omnichannelPermissions?: { canForwardGuest: boolean; @@ -55,7 +56,7 @@ export type ModalStackParamList = { room?: ISubscription; member?: any; rid: string; - t: SubscriptionType; + t: RoomType; showCloseModal?: boolean; fromRid?: string; itsMe?: boolean; @@ -74,7 +75,7 @@ export type ModalStackParamList = { context: TChangeAvatarViewContext; titleHeader?: string; room?: ISubscription; - t?: SubscriptionType; + t?: RoomType; }; RoomInfoEditView: { rid: string; @@ -86,11 +87,11 @@ export type ModalStackParamList = { }; DiscussionsView: { rid: string; - t: SubscriptionType; + t: RoomType; }; SearchMessagesView: { rid: string; - t: SubscriptionType; + t: RoomType; encrypted?: boolean; showCloseModal?: boolean; }; @@ -117,7 +118,7 @@ export type ModalStackParamList = { }; MessagesView: { rid: string; - t: SubscriptionType; + t: RoomType; name: string; }; AutoTranslateView: { @@ -163,7 +164,7 @@ export type ModalStackParamList = { }; ThreadMessagesView: { rid: string; - t: SubscriptionType; + t: RoomType; }; TeamChannelsView: { teamId: string; diff --git a/app/stacks/types.ts b/app/stacks/types.ts index 15cb40b0710..492ae9d588a 100644 --- a/app/stacks/types.ts +++ b/app/stacks/types.ts @@ -8,7 +8,7 @@ import { type IMessage, type IServer, type ISubscription, - type SubscriptionType, + type RoomType, type TAnyMessageModel, type TChangeAvatarViewContext, type TDataSelect, @@ -47,7 +47,7 @@ export type ChatsStackParamList = { RoomView: | { rid: string; - t: SubscriptionType; + t: RoomType; tmid?: string; messageId?: string; name?: string; @@ -66,7 +66,7 @@ export type ChatsStackParamList = { room: TSubscriptionModel; member?: any; rid: string; - t: SubscriptionType; + t: RoomType; joined: boolean; omnichannelPermissions?: { canForwardGuest: boolean; @@ -90,7 +90,7 @@ export type ChatsStackParamList = { room?: ISubscription; member?: any; rid: string; - t: SubscriptionType; + t: RoomType; showCloseModal?: boolean; fromRid?: string; itsMe?: boolean; @@ -105,11 +105,11 @@ export type ChatsStackParamList = { }; DiscussionsView: { rid: string; - t: SubscriptionType; + t: RoomType; }; SearchMessagesView: { rid: string; - t: SubscriptionType; + t: RoomType; encrypted?: boolean; showCloseModal?: boolean; }; @@ -129,7 +129,7 @@ export type ChatsStackParamList = { }; MessagesView: { rid: string; - t: SubscriptionType; + t: RoomType; name: string; }; AutoTranslateView: { @@ -159,7 +159,7 @@ export type ChatsStackParamList = { }; ThreadMessagesView: { rid: string; - t: SubscriptionType; + t: RoomType; }; TeamChannelsView: { teamId: string; @@ -199,7 +199,7 @@ export type ChatsStackParamList = { context: TChangeAvatarViewContext; titleHeader?: string; room?: ISubscription; - t?: SubscriptionType; + t?: RoomType; }; ReportUserView: { username: string; @@ -217,7 +217,7 @@ export type ProfileStackParamList = { context: TChangeAvatarViewContext; titleHeader?: string; room?: ISubscription; - t?: SubscriptionType; + t?: RoomType; }; ChangePasswordView: undefined; }; diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index c689ac31fe5..479797a3223 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -16,7 +16,7 @@ import SafeAreaView from '~/containers/SafeAreaView'; import { sendLoadingEvent } from '~/containers/Loading'; import { showErrorAlert } from '~/lib/methods/helpers/info'; import { type ChatsStackParamList } from '~/stacks/types'; -import { type TSubscriptionModel, SubscriptionType } from '~/definitions'; +import { type TSubscriptionModel, RoomType } from '~/definitions'; import { compareServerVersion, getRoomTitle, hasPermission, useDebounce } from '~/lib/methods/helpers'; import { addRoomsToTeam } from '~/lib/services/restApi'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; @@ -161,7 +161,7 @@ const AddExistingChannelView = () => { } renderItem={({ item, index }: { item: TSubscriptionModel; index: number }) => { // TODO: reuse logic inside RoomTypeIcon - const icon = item.t === SubscriptionType.GROUP && !item?.teamId ? 'channel-private' : 'channel-public'; + const icon = item.t === RoomType.GROUP && !item?.teamId ? 'channel-private' : 'channel-public'; return ( { it('returns every room for a blank search', () => { diff --git a/app/views/CreateCategoryView/types.ts b/app/views/CreateCategoryView/types.ts index 328f0e7383d..a073f67e0bf 100644 --- a/app/views/CreateCategoryView/types.ts +++ b/app/views/CreateCategoryView/types.ts @@ -1,8 +1,8 @@ -import { type SubscriptionType } from '~/definitions'; +import { type RoomType } from '~/definitions'; export interface ICategoryRoom { rid: string; title: string; avatar: string; - t: SubscriptionType; + t: RoomType; } diff --git a/app/views/CreateDiscussionView/interfaces.ts b/app/views/CreateDiscussionView/interfaces.ts index cb8cff9b3aa..0ae3e5de9cd 100644 --- a/app/views/CreateDiscussionView/interfaces.ts +++ b/app/views/CreateDiscussionView/interfaces.ts @@ -1,10 +1,11 @@ import { type NewMessageStackParamList } from '~/stacks/types'; -import { type ISubscription, type SubscriptionType } from '~/definitions/ISubscription'; +import { type RoomType } from '~/definitions/IRoom'; +import { type ISubscription } from '~/definitions/ISubscription'; import { type IBaseScreen, type IMessage, type ISearchLocal, type IUser } from '~/definitions'; export interface IResult { rid: string; - t: SubscriptionType; + t: RoomType; prid: string; } diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index 18797a787cf..6ea2b52693a 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -21,7 +21,7 @@ import { headerLeftCloseModal } from '~/lib/methods/helpers/navigation/headerAct import { useTheme } from '~/theme'; import SafeAreaView from '~/containers/SafeAreaView'; import { goRoom as goRoomMethod, type TGoRoomItem } from '~/lib/methods/helpers/goRoom'; -import { type IServerRoom, SubscriptionType } from '~/definitions'; +import { type IServerRoom, RoomType } from '~/definitions'; import styles from './styles'; import Options from './Options'; import DirectoryRow from './DirectoryRow'; @@ -169,7 +169,7 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { if (type === 'users') { const result = await createDirectMessage(item.username as string); if (result.success) { - goRoom({ rid: result.room._id, name: item.username, t: SubscriptionType.DIRECT }); + goRoom({ rid: result.room._id, name: item.username, t: RoomType.DIRECT }); } return; } @@ -185,7 +185,7 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { rid: item._id, name: item.name, joinCodeRequired: result.joinCodeRequired, - t: item.t as SubscriptionType, + t: item.t, search: true }); } @@ -193,7 +193,7 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { goRoom({ rid: item._id, name: item.name, - t: item.t as SubscriptionType, + t: item.t, search: true, teamMain: item.teamMain, teamId: item.teamId diff --git a/app/views/MessagesView/index.tsx b/app/views/MessagesView/index.tsx index cbeff28c2d0..b0ee99fe3f1 100644 --- a/app/views/MessagesView/index.tsx +++ b/app/views/MessagesView/index.tsx @@ -25,7 +25,7 @@ import { type IApplicationState, type IRoomInfoParam, type TMessageModel, - type SubscriptionType, + type RoomType, type IAttachment, type IMessage, type TAnyMessageModel, @@ -65,7 +65,7 @@ interface IMessagesViewState { interface IParams { rid: string; - t: SubscriptionType; + t: RoomType; tmid?: string; message?: TMessageModel; name?: string; @@ -78,7 +78,7 @@ interface IParams { class MessagesView extends Component { private rid: string; - private t: SubscriptionType; + private t: RoomType; private content: any; constructor(props: IMessagesViewProps) { diff --git a/app/views/NewMessageView/hooks/useOpenChat.ts b/app/views/NewMessageView/hooks/useOpenChat.ts index 232386b9114..1b6467f0a34 100644 --- a/app/views/NewMessageView/hooks/useOpenChat.ts +++ b/app/views/NewMessageView/hooks/useOpenChat.ts @@ -2,7 +2,7 @@ import { useNavigation } from '@react-navigation/native'; import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useCallback } from 'react'; -import { type ISearch, SubscriptionType, type TSubscriptionModel } from '~/definitions'; +import { type ISearch, RoomType, type TSubscriptionModel } from '~/definitions'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { assignRoomToCategory } from '~/lib/methods/assignRoomToCategory'; import { createDirectMessage } from '~/lib/methods/createDirectMessage'; @@ -34,7 +34,7 @@ const openChatInCategory = async (chat: TNewMessageChat, categoryId: string, isM return; } assignRoomToCategory(rid, categoryId); - await goRoom({ item: { rid, name: chat.name, t: SubscriptionType.DIRECT }, isMasterDetail }); + await goRoom({ item: { rid, name: chat.name, t: RoomType.DIRECT }, isMasterDetail }); } catch (error: any) { emitErrorCreateDirectMessage(error?.data); } diff --git a/app/views/RoomActionsView/components/CallSection.stories.tsx b/app/views/RoomActionsView/components/CallSection.stories.tsx index 5015ede2e6a..8e4fa182232 100644 --- a/app/views/RoomActionsView/components/CallSection.stories.tsx +++ b/app/views/RoomActionsView/components/CallSection.stories.tsx @@ -3,7 +3,7 @@ import { type ComponentType, type ReactNode } from 'react'; import CallSection from './CallSection'; import type { ISubscription, TSubscriptionModel } from '~/definitions'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { clearEnterpriseModules, setEnterpriseModules } from '~/actions/enterpriseModules'; import { setPermissions } from '~/actions/permissions'; import { setUser } from '~/actions/login'; @@ -24,7 +24,7 @@ const createMockRoom = (overrides: Partial = {}): TSubscriptionMo _id: 'room1', rid: 'room1', id: 'room1', - t: SubscriptionType.DIRECT, + t: RoomType.DIRECT, name: 'john.doe', fname: 'John Doe', uids: ['abc', 'user123'], diff --git a/app/views/RoomActionsView/components/CallSection.test.tsx b/app/views/RoomActionsView/components/CallSection.test.tsx index 6abfd3a6e43..497ce1c0555 100644 --- a/app/views/RoomActionsView/components/CallSection.test.tsx +++ b/app/views/RoomActionsView/components/CallSection.test.tsx @@ -7,7 +7,7 @@ import { mockedStore } from '~/reducers/mockedStore'; import * as stories from './CallSection.stories'; import { generateSnapshots } from '~/.rnstorybook/generateSnapshots'; import type { ISubscription, TSubscriptionModel } from '~/definitions'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import i18n from '~/i18n'; import * as restApi from '~/lib/services/restApi'; @@ -57,7 +57,7 @@ const createMockRoom = (overrides: Partial = {}): TSubscriptionMo _id: 'room1', rid: 'room1', id: 'room1', - t: SubscriptionType.DIRECT, + t: RoomType.DIRECT, name: 'john.doe', fname: 'John Doe', uids: ['abc', 'user123'], diff --git a/app/views/RoomActionsView/index.tsx b/app/views/RoomActionsView/index.tsx index c5d89f77be2..2417890ec7c 100644 --- a/app/views/RoomActionsView/index.tsx +++ b/app/views/RoomActionsView/index.tsx @@ -22,7 +22,7 @@ import { type IBaseScreen, type ISubscription, type IUser, - SubscriptionType, + RoomType, type TSubscriptionModel } from '~/definitions'; import { type IActiveUser } from '~/reducers/activeUsers'; @@ -224,7 +224,7 @@ class RoomActionsView extends Component diff --git a/app/views/RoomInfoEditView/index.tsx b/app/views/RoomInfoEditView/index.tsx index 426dfba2012..4b5927905f4 100644 --- a/app/views/RoomInfoEditView/index.tsx +++ b/app/views/RoomInfoEditView/index.tsx @@ -16,7 +16,7 @@ import SafeAreaView from '~/containers/SafeAreaView'; import { ControlledFormTextInput } from '~/containers/TextInput'; import { LISTENER } from '~/containers/Toast'; import { MultiSelect } from '~/containers/UIKit/MultiSelect'; -import { type IBaseScreen, type IRoomSettings, type ISubscription, SubscriptionType } from '~/definitions'; +import { type IBaseScreen, type IRoomSettings, type ISubscription, RoomType } from '~/definitions'; import I18n from '~/i18n'; import KeyboardView from '~/containers/KeyboardView'; import { type ModalStackParamList } from '~/stacks/MasterDetailStack/types'; @@ -167,8 +167,8 @@ const RoomInfoEditView = ({ navigation, route }: IRoomInfoEditViewProps) => { params.roomAnnouncement = announcement; } // Room Type - if ((room.t === SubscriptionType.CHANNEL) !== t) { - params.roomType = t ? ('c' as SubscriptionType) : ('p' as SubscriptionType); + if ((room.t === RoomType.CHANNEL) !== t) { + params.roomType = t ? 'c' : 'p'; } // Read Only if (room.ro !== readOnly) { @@ -242,7 +242,7 @@ const RoomInfoEditView = ({ navigation, route }: IRoomInfoEditViewProps) => { onPress: async () => { try { logEvent(events.RI_EDIT_TOGGLE_ARCHIVE); - await toggleArchiveRoom(rid, t as SubscriptionType, !archived); + await toggleArchiveRoom(rid, t, !archived); } catch (e) { logEvent(events.RI_EDIT_TOGGLE_ARCHIVE_F); log(e); diff --git a/app/views/RoomInfoView/components/RoomInfoButtons.stories.tsx b/app/views/RoomInfoView/components/RoomInfoButtons.stories.tsx index dd1fadab4d4..659e1d95bef 100644 --- a/app/views/RoomInfoView/components/RoomInfoButtons.stories.tsx +++ b/app/views/RoomInfoView/components/RoomInfoButtons.stories.tsx @@ -3,7 +3,7 @@ import { type ComponentType, type ReactNode } from 'react'; import { RoomInfoButtons } from './RoomInfoButtons'; import type { ISubscription } from '~/definitions'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { clearEnterpriseModules, setEnterpriseModules } from '~/actions/enterpriseModules'; import { setPermissions } from '~/actions/permissions'; import { setUser } from '~/actions/login'; @@ -24,7 +24,7 @@ const createMockRoom = (overrides: Partial = {}): ISubscription = _id: 'room1', rid: 'room1', id: 'room1', - t: SubscriptionType.DIRECT, + t: RoomType.DIRECT, name: 'john.doe', fname: 'John Doe', uids: ['abc', 'user123'], diff --git a/app/views/RoomInfoView/components/RoomInfoButtons.test.tsx b/app/views/RoomInfoView/components/RoomInfoButtons.test.tsx index 8c6a2fbd41e..281e64db125 100644 --- a/app/views/RoomInfoView/components/RoomInfoButtons.test.tsx +++ b/app/views/RoomInfoView/components/RoomInfoButtons.test.tsx @@ -7,7 +7,7 @@ import { mockedStore } from '~/reducers/mockedStore'; import * as stories from './RoomInfoButtons.stories'; import { generateSnapshots } from '~/.rnstorybook/generateSnapshots'; import type { ISubscription } from '~/definitions'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; const mockShowInitCallActionSheet = jest.fn(); const mockShowActionSheet = jest.fn(); @@ -41,7 +41,7 @@ const createMockRoom = (overrides: Partial = {}): ISubscription = _id: 'room1', rid: 'room1', id: 'room1', - t: SubscriptionType.DIRECT, + t: RoomType.DIRECT, name: 'john.doe', fname: 'John Doe', uids: ['abc', 'user123'], diff --git a/app/views/RoomInfoView/components/RoomInfoButtons.tsx b/app/views/RoomInfoView/components/RoomInfoButtons.tsx index b1c3c6ee987..2909f0a2acd 100644 --- a/app/views/RoomInfoView/components/RoomInfoButtons.tsx +++ b/app/views/RoomInfoView/components/RoomInfoButtons.tsx @@ -2,7 +2,7 @@ import { View } from 'react-native'; import { type ReactElement } from 'react'; import { type TIconsName } from '~/containers/CustomIcon'; -import { type ISubscription, SubscriptionType } from '~/definitions'; +import { type ISubscription, RoomType } from '~/definitions'; import i18n from '~/i18n'; import { useVideoConf } from '~/lib/hooks/useVideoConf'; import styles from '../styles'; @@ -60,7 +60,7 @@ export const RoomInfoButtons = ({ // Following the web behavior, when is a DM with myself, shouldn't appear block or ignore option const isDmWithMyself = room?.uids?.filter((uid: string) => uid !== roomUserId).length === 0; - const isFromDm = room?.t === SubscriptionType.DIRECT; + const isFromDm = room?.t === RoomType.DIRECT; const isDirectFromSaved = isDirect && fromRid && room; const isIgnored = room?.ignored?.includes?.(roomUserId || ''); const isBlocked = room?.blocker; diff --git a/app/views/RoomInfoView/components/RoomInfoViewAvatar.stories.tsx b/app/views/RoomInfoView/components/RoomInfoViewAvatar.stories.tsx index cdc7aff5018..e783e22a704 100644 --- a/app/views/RoomInfoView/components/RoomInfoViewAvatar.stories.tsx +++ b/app/views/RoomInfoView/components/RoomInfoViewAvatar.stories.tsx @@ -2,7 +2,7 @@ import { type ReactNode } from 'react'; import { View, StyleSheet } from 'react-native'; import RoomInfoViewAvatar from './RoomInfoViewAvatar'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; const styles = StyleSheet.create({ container: { @@ -22,18 +22,12 @@ export default { export const Default = () => ( - {}} - /> + {}} /> ); export const WithEdit = () => ( - {}} /> + {}} /> ); diff --git a/app/views/RoomInfoView/components/RoomInfoViewAvatar.tsx b/app/views/RoomInfoView/components/RoomInfoViewAvatar.tsx index 2aaac5047dd..6c6c9c2f046 100644 --- a/app/views/RoomInfoView/components/RoomInfoViewAvatar.tsx +++ b/app/views/RoomInfoView/components/RoomInfoViewAvatar.tsx @@ -1,7 +1,7 @@ import { type ReactElement } from 'react'; import { AvatarWithEdit } from '~/containers/Avatar'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import styles from '../styles'; import I18n from '~/i18n'; @@ -13,12 +13,12 @@ const RoomInfoViewAvatar = ({ handleEditAvatar }: { showEdit: boolean; - type: SubscriptionType; + type: RoomType; username: string; rid?: string; handleEditAvatar: () => void; }): ReactElement => { - const showAvatarEdit = showEdit && type !== SubscriptionType.OMNICHANNEL; + const showAvatarEdit = showEdit && type !== RoomType.OMNICHANNEL; return ( ; } - if (room?.t === SubscriptionType.OMNICHANNEL && room) { + if (room?.t === RoomType.OMNICHANNEL && room) { return ; } diff --git a/app/views/RoomInfoView/components/RoomInfoViewTitle.stories.tsx b/app/views/RoomInfoView/components/RoomInfoViewTitle.stories.tsx index f545c3979db..eb7675766db 100644 --- a/app/views/RoomInfoView/components/RoomInfoViewTitle.stories.tsx +++ b/app/views/RoomInfoView/components/RoomInfoViewTitle.stories.tsx @@ -2,7 +2,7 @@ import { type ReactNode } from 'react'; import { View, StyleSheet } from 'react-native'; import RoomInfoViewTitle from './RoomInfoViewTitle'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; const styles = StyleSheet.create({ container: { @@ -24,16 +24,16 @@ export default { export const DM_Status = () => ( <> - + - + - + - + ( userId='user5' status='online' statusText='In a meeting' - type={SubscriptionType.DIRECT} + type={RoomType.DIRECT} /> @@ -53,7 +53,7 @@ export const DM_Status = () => ( status='online' statusText='In a meeting' statusExpiresAt={futureExpiry} - type={SubscriptionType.DIRECT} + type={RoomType.DIRECT} /> @@ -63,7 +63,7 @@ export const DM_Status = () => ( userId='user7' status='busy' statusText='On call' - type={SubscriptionType.DIRECT} + type={RoomType.DIRECT} /> diff --git a/app/views/RoomInfoView/components/RoomInfoViewTitle.tsx b/app/views/RoomInfoView/components/RoomInfoViewTitle.tsx index 612b3268f26..23214f88073 100644 --- a/app/views/RoomInfoView/components/RoomInfoViewTitle.tsx +++ b/app/views/RoomInfoView/components/RoomInfoViewTitle.tsx @@ -2,7 +2,7 @@ import { Text, View } from 'react-native'; import Clipboard from '@react-native-clipboard/clipboard'; import { type ReactElement } from 'react'; -import { type ISubscription, type TUserStatus, SubscriptionType } from '~/definitions'; +import { type ISubscription, type TUserStatus, RoomType } from '~/definitions'; import styles from '../styles'; import { useTheme } from '~/theme'; import RoomTypeIcon from '~/containers/RoomTypeIcon'; @@ -21,7 +21,7 @@ interface IRoomInfoViewTitle { status?: TUserStatus; statusText?: string; statusExpiresAt?: string; - type: SubscriptionType; + type: RoomType; } const RoomInfoViewTitle = ({ @@ -41,7 +41,7 @@ const RoomInfoViewTitle = ({ EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); }; - if (type === SubscriptionType.DIRECT) { + if (type === RoomType.DIRECT) { return ( { const [showEdit, setShowEdit] = useState(false); const roomType = room?.t || t; - const isDirect = roomType === SubscriptionType.DIRECT; - const isLivechat = roomType === SubscriptionType.OMNICHANNEL; + const isDirect = roomType === RoomType.DIRECT; + const isLivechat = roomType === RoomType.OMNICHANNEL; const subscription = useRef(undefined); @@ -192,7 +192,7 @@ const RoomInfoView = (): ReactElement => { const sub = subRoom.observe(); subscription.current = sub.subscribe(changes => { setRoom(changes.asPlain()); - setHeader(roomType === SubscriptionType.DIRECT ? false : canEdit); + setHeader(roomType === RoomType.DIRECT ? false : canEdit); }); } else { try { @@ -205,7 +205,7 @@ const RoomInfoView = (): ReactElement => { } } setShowEdit(canEdit); - setHeader(roomType === SubscriptionType.DIRECT ? false : canEdit); + setHeader(roomType === RoomType.DIRECT ? false : canEdit); }; // Prevents from flashing RoomInfoView on the header title before fetching actual room data diff --git a/app/views/RoomMembersView/components/__tests__/ActionsSection.test.tsx b/app/views/RoomMembersView/components/__tests__/ActionsSection.test.tsx index 74c5636361a..a19812e6e74 100644 --- a/app/views/RoomMembersView/components/__tests__/ActionsSection.test.tsx +++ b/app/views/RoomMembersView/components/__tests__/ActionsSection.test.tsx @@ -3,7 +3,7 @@ import { Provider } from 'react-redux'; import { type ReactNode } from 'react'; import ActionsSection from '../ActionsSection'; -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { mockedStore } from '~/reducers/mockedStore'; import { addUsersToRoom } from '~/lib/services/restApi'; import { showErrorAlertWithEMessage } from '~/lib/methods/helpers'; @@ -46,7 +46,7 @@ describe('ActionsSection add users', () => { it('shows the error and stays on the screen when adding users fails', async () => { const error = new Error('error-user-is-banned'); jest.mocked(addUsersToRoom).mockRejectedValue(error); - const { getByTestId } = render(, { + const { getByTestId } = render(, { wrapper: Wrapper }); diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts index b3a3d940989..7ceacf15b1b 100644 --- a/app/views/RoomMembersView/helpers.ts +++ b/app/views/RoomMembersView/helpers.ts @@ -2,14 +2,7 @@ import { Q } from '@nozbe/watermelondb'; import { Alert } from 'react-native'; import { LISTENER } from '~/containers/Toast'; -import { - type IGetRoomRoles, - type IUser, - type RoomType, - SubscriptionType, - type TSubscriptionModel, - type TUserModel -} from '~/definitions'; +import { type IGetRoomRoles, type IUser, RoomType, type TSubscriptionModel, type TUserModel } from '~/definitions'; import I18n from '~/i18n'; import { getRoomTitle, showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; import EventEmitter from '~/lib/methods/helpers/events'; @@ -105,7 +98,7 @@ export const navToDirectMessage = async (item: IUser, isMasterDetail: boolean): } else { const result = await createDirectMessage(item.username); if (result.success) { - handleGoRoom({ rid: result.room?._id as string, name: item.username, t: SubscriptionType.DIRECT }, isMasterDetail); + handleGoRoom({ rid: result.room?._id as string, name: item.username, t: RoomType.DIRECT }, isMasterDetail); } } } catch (e: any) { @@ -222,7 +215,7 @@ export const handleRemoveUserFromRoom = async ( ): Promise => { try { const userId = selectedUser._id; - await removeUserFromRoom({ roomId: room.rid, t: room.t as RoomType, userId }); + await removeUserFromRoom({ roomId: room.rid, t: room.t, userId }); const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) }); EventEmitter.emit(LISTENER, { message }); callback(); diff --git a/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx b/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx index 7f4a608443c..d1dfe952c23 100644 --- a/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx +++ b/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx @@ -4,7 +4,7 @@ import { useShallow } from 'zustand/react/shallow'; import { getRoomTitle } from '~/lib/methods/helpers'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; import { getInvitationActions, getInvitationText } from '~/lib/methods/getInvitationData'; -import { type IInviteSubscription } from '~/definitions'; +import { type IInviteSubscription, type RoomType } from '~/definitions'; import { isSubscriptionModel } from '~/definitions/TRoom'; import { type IRoomViewProps, type RoomStore } from '~/views/RoomView/definitions'; import RoomScreen from '~/views/RoomView/RoomScreen'; @@ -16,7 +16,7 @@ import { RoomBackground } from '../RoomBackground'; interface IRoomViewContentProps extends IRoomViewProps { rid?: string; - t?: string; + t?: RoomType; tmid?: string; roomStore: RoomStore; ready: boolean; diff --git a/app/views/RoomView/definitions.ts b/app/views/RoomView/definitions.ts index 41b53143d02..ca0b24a93d2 100644 --- a/app/views/RoomView/definitions.ts +++ b/app/views/RoomView/definitions.ts @@ -25,7 +25,7 @@ export type IRoomViewProps = Pick, export interface IRoomScreenInput { rid?: string; - t?: string; + t?: RoomType; tmid?: string; name?: string; initialRoom: TRoomOrPreview; @@ -34,7 +34,7 @@ export interface IRoomScreenInput { export interface IRoomScreenProps extends Pick { rid: string; - t: string; + t: RoomType; tmid?: string; roomStore: RoomStore; ready: boolean; @@ -231,7 +231,7 @@ export type IRoomMessageActionsProps = Pick< export interface IUseRoomMessagingParams { rid?: string; - t?: string; + t?: RoomType; tmid?: string; ready: boolean; roomStore: RoomStore; @@ -249,7 +249,7 @@ export interface IUseSubscriptionUnreadsResult { export interface IUseJumpToMessageParams { rid?: string; tmid?: string; - t?: string; + t?: RoomType; isMasterDetail: boolean; listContainerRef: RefObject; roomUserIdRef: RefObject; @@ -263,6 +263,6 @@ export interface IUseJumpToMessageResult { export interface IUseOmnichannelPermissionsParams { rid?: string; - t?: string; + t?: RoomType; roomStore: RoomStore; } diff --git a/app/views/RoomView/hooks/__tests__/useOmnichannelPermissions.test.tsx b/app/views/RoomView/hooks/__tests__/useOmnichannelPermissions.test.tsx index b0b5809adc4..b4d91442e17 100644 --- a/app/views/RoomView/hooks/__tests__/useOmnichannelPermissions.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useOmnichannelPermissions.test.tsx @@ -1,6 +1,7 @@ import { renderHook } from '@testing-library/react-native'; import { createStore } from 'zustand'; +import { type RoomType } from '~/definitions'; import { usePermissions } from '~/lib/hooks/usePermissions'; import { type RoomState, type RoomStore } from '~/views/RoomView/definitions'; import { useOmnichannelPermissions } from '../useOmnichannelPermissions'; @@ -45,7 +46,7 @@ describe('useOmnichannelPermissions', () => { mockUsePermissions.mockReturnValue([true, true]); const roomStore = makeRoomStore(); - const { rerender } = renderHook(({ t }: { t: string }) => useOmnichannelPermissions({ rid: 'rid-1', t, roomStore }), { + const { rerender } = renderHook(({ t }: { t: RoomType }) => useOmnichannelPermissions({ rid: 'rid-1', t, roomStore }), { initialProps: { t: 'l' } }); expect(roomStore.getState().canForwardGuest).toBe(true); diff --git a/app/views/RoomView/hooks/useGoRoomActionsView.ts b/app/views/RoomView/hooks/useGoRoomActionsView.ts index 2fa50b4a3e8..cfc36b2b74d 100644 --- a/app/views/RoomView/hooks/useGoRoomActionsView.ts +++ b/app/views/RoomView/hooks/useGoRoomActionsView.ts @@ -4,7 +4,7 @@ import { useShallow } from 'zustand/react/shallow'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; -import type { ISubscription, SubscriptionType, TSubscriptionModel } from '~/definitions'; +import type { ISubscription, RoomType, TSubscriptionModel } from '~/definitions'; import { type TNavigation } from '~/stacks/stackType'; import { type ModalStackParamList } from '~/stacks/MasterDetailStack/types'; import { type IRoomViewProps } from '../definitions'; @@ -37,7 +37,7 @@ export const useGoRoomActionsView = (roomStore: RoomStore): ((screen?: keyof Mod const room = roomStore.getState().room; const params = { rid: rid as string, - t: t as SubscriptionType, + t: t as RoomType, member, joined: membership === 'subscribed', omnichannelPermissions diff --git a/app/views/RoomView/hooks/useRoomActionsState.ts b/app/views/RoomView/hooks/useRoomActionsState.ts index 5b9c29b12d6..2e6cf2eb57c 100644 --- a/app/views/RoomView/hooks/useRoomActionsState.ts +++ b/app/views/RoomView/hooks/useRoomActionsState.ts @@ -2,7 +2,7 @@ import { useStore } from 'zustand'; import { useShallow } from 'zustand/react/shallow'; import { useNavigation } from '@react-navigation/native'; -import { type SubscriptionType, type TUserStatus } from '~/definitions'; +import { type RoomType, type TUserStatus } from '~/definitions'; import { isSubscriptionModel } from '~/definitions/TRoom'; import i18n from '~/i18n'; import getRoomAccessibilityLabel from '~/lib/helpers/getRoomAccessibilityLabel'; @@ -45,7 +45,7 @@ export const useRoomActionsState = (rid: string, roomStore: RoomStore) => { useShallow(s => { const room = s.room; return { - t: room.t as SubscriptionType, + t: room.t as RoomType, status: fromSubscription(r => r.status, undefined)(s), roomName: getRoomTitle(room), roomIsGroupChat: isGroupChat(room), diff --git a/app/views/RoomView/hooks/useRoomMessageHandlers.tsx b/app/views/RoomView/hooks/useRoomMessageHandlers.tsx index cbb32c4299d..37e362454cb 100644 --- a/app/views/RoomView/hooks/useRoomMessageHandlers.tsx +++ b/app/views/RoomView/hooks/useRoomMessageHandlers.tsx @@ -14,7 +14,7 @@ import { type IMessage, type IRoomInfoParam, type IUseRoomMessageHandlersResult, - SubscriptionType, + RoomType, type TAnyMessageModel } from '~/definitions'; import { useActionSheet } from '~/containers/ActionSheet'; @@ -52,7 +52,7 @@ export function useRoomMessageHandlers({ }; const navToRoomInfo = (navParam: IRoomInfoParam) => { - logEvent(events[`ROOM_GO_${navParam.t === SubscriptionType.DIRECT ? 'USER' : 'ROOM'}_INFO`]); + logEvent(events[`ROOM_GO_${navParam.t === RoomType.DIRECT ? 'USER' : 'ROOM'}_INFO`]); const params = { ...navParam, fromRid: rid }; if (isMasterDetail) { navigation.navigate('ModalStackNavigator', { screen: 'RoomInfoView', params: { ...params, showCloseModal: true } }); diff --git a/app/views/RoomView/services/__tests__/navigateToScreen.test.ts b/app/views/RoomView/services/__tests__/navigateToScreen.test.ts index a84beb99f5e..49691389758 100644 --- a/app/views/RoomView/services/__tests__/navigateToScreen.test.ts +++ b/app/views/RoomView/services/__tests__/navigateToScreen.test.ts @@ -1,10 +1,10 @@ -import { SubscriptionType } from '~/definitions'; +import { RoomType } from '~/definitions'; import { navigateToScreen, type TRoomStackNavigation, type TRoomStackParamList } from '../navigateToScreen'; describe('navigateToScreen', () => { const navigate = jest.fn(); const navigation = { navigate } as unknown as TRoomStackNavigation; - const threadMessagesParams: TRoomStackParamList['ThreadMessagesView'] = { rid: 'rid-1', t: SubscriptionType.CHANNEL }; + const threadMessagesParams: TRoomStackParamList['ThreadMessagesView'] = { rid: 'rid-1', t: RoomType.CHANNEL }; beforeEach(() => jest.clearAllMocks()); diff --git a/app/views/RoomView/services/openThread.ts b/app/views/RoomView/services/openThread.ts index 3f964520dde..3b1ab36140c 100644 --- a/app/views/RoomView/services/openThread.ts +++ b/app/views/RoomView/services/openThread.ts @@ -2,7 +2,7 @@ import { sendLoadingEvent } from '~/containers/Loading'; import I18n from '~/i18n'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '~/lib/constants/keys'; import { makeThreadName } from '~/lib/methods/helpers/room'; -import { type SubscriptionType, type TAnyMessageModel } from '~/definitions'; +import { type RoomType, type TAnyMessageModel } from '~/definitions'; import { type IRoomViewProps, type TGetMessageInfoResult } from '../definitions'; import { fetchThreadName } from './fetchThreadName'; @@ -13,7 +13,7 @@ export type TOpenThreadTarget = TAnyMessageModel | { tmid: string } | TGetMessag interface IOpenThreadDeps { navigation: IRoomViewProps['navigation']; rid?: string; - t?: string; + t?: RoomType; roomUserId?: string | null; cancelJumpToMessage: () => void; } @@ -25,7 +25,7 @@ export const openThread = async ( message: TOpenThreadTarget, { navigation, rid, t, roomUserId, cancelJumpToMessage }: IOpenThreadDeps ): Promise => { - if (!rid) { + if (!rid || !t) { return; } const { tmid } = message; @@ -35,7 +35,7 @@ export const openThread = async ( rid, tmid: message.id, name: makeThreadName(message), - t: t as SubscriptionType, + t, roomUserId }); } @@ -68,7 +68,7 @@ export const openThread = async ( rid, tmid, name: isUndecryptable(message) ? I18n.t('E2E_Key_Error') : threadName, - t: t as SubscriptionType, + t, roomUserId, jumpToMessageId }); diff --git a/app/views/RoomsListView/utils/__tests__/groupRooms.test.ts b/app/views/RoomsListView/utils/__tests__/groupRooms.test.ts index 12797af4adb..100c628235b 100644 --- a/app/views/RoomsListView/utils/__tests__/groupRooms.test.ts +++ b/app/views/RoomsListView/utils/__tests__/groupRooms.test.ts @@ -1,8 +1,8 @@ -import { SubscriptionType, type TSubscriptionModel } from '~/definitions'; +import { RoomType, type TSubscriptionModel } from '~/definitions'; import { buildRoomList, categoryIdOfHeader, roomsInSection } from '../groupRooms'; import { DEFAULT_GROUP_ORDER, SYSTEM_GROUPS } from '../sidebarGroupOrder'; -const room = (fields: Partial) => ({ t: SubscriptionType.CHANNEL, ...fields }) as TSubscriptionModel; +const room = (fields: Partial) => ({ t: RoomType.CHANNEL, ...fields }) as TSubscriptionModel; const options = { groupOrder: ['work', ...DEFAULT_GROUP_ORDER], @@ -23,7 +23,7 @@ describe('groupRooms', () => { const chats = [ room({ rid: 'general', f: true, category: 'work' }), room({ rid: 'random', f: true }), - room({ rid: 'dm', t: SubscriptionType.DIRECT }) + room({ rid: 'dm', t: RoomType.DIRECT }) ]; expect(layout(buildRoomList(chats, options))).toEqual([ @@ -55,7 +55,7 @@ describe('groupRooms', () => { const chats = [ room({ rid: 'general', category: 'work' }), room({ rid: 'random', f: true, category: 'work' }), - room({ rid: 'dm', t: SubscriptionType.DIRECT }) + room({ rid: 'dm', t: RoomType.DIRECT }) ]; expect(layout(buildRoomList(chats, { ...options, groupByType: false }))).toEqual([ @@ -68,7 +68,7 @@ describe('groupRooms', () => { }); it('lists rooms in a flat list when categories, favorites and unread are off', () => { - const chats = [room({ rid: 'general', category: 'work' }), room({ rid: 'dm', t: SubscriptionType.DIRECT })]; + const chats = [room({ rid: 'general', category: 'work' }), room({ rid: 'dm', t: RoomType.DIRECT })]; expect(layout(buildRoomList(chats, { ...options, groupByType: false, showFavorites: false }))).toEqual(['general', 'dm']); }); @@ -89,9 +89,9 @@ describe('groupRooms', () => { it('places omnichannel rooms before unread rooms and regular groups', () => { const chats = [ - room({ rid: 'open-livechat', t: SubscriptionType.OMNICHANNEL, onHold: false }), + room({ rid: 'open-livechat', t: RoomType.OMNICHANNEL, onHold: false }), room({ rid: 'unread', alert: true }), - room({ rid: 'on-hold-livechat', t: SubscriptionType.OMNICHANNEL, onHold: true }), + room({ rid: 'on-hold-livechat', t: RoomType.OMNICHANNEL, onHold: true }), room({ rid: 'regular' }) ]; @@ -118,7 +118,7 @@ describe('groupRooms', () => { }); it('keeps the header of a collapsed group and hides its rooms', () => { - const chats = [room({ rid: 'general', category: 'work' }), room({ rid: 'dm', t: SubscriptionType.DIRECT })]; + const chats = [room({ rid: 'general', category: 'work' }), room({ rid: 'dm', t: RoomType.DIRECT })]; expect(layout(buildRoomList(chats, { ...options, collapsedGroups: new Set(['work']) }))).toEqual([ '# Work', @@ -234,7 +234,7 @@ describe('groupRooms', () => { }); it('leaves out the sections the admin removed from the sections order', () => { - const chats = [room({ rid: 'unread', unread: 1 }), room({ rid: 'livechat', t: SubscriptionType.OMNICHANNEL })]; + const chats = [room({ rid: 'unread', unread: 1 }), room({ rid: 'livechat', t: RoomType.OMNICHANNEL })]; const sectionsOrder = SYSTEM_GROUPS.filter(key => key !== 'Unread' && key !== 'Open_Livechats'); expect( @@ -255,7 +255,7 @@ describe('roomsInSection', () => { const chats = [ room({ rid: 'general', category: 'work' }), room({ rid: 'random', f: true }), - room({ rid: 'dm', t: SubscriptionType.DIRECT }) + room({ rid: 'dm', t: RoomType.DIRECT }) ]; const roomList = buildRoomList(chats, options); diff --git a/app/views/SearchMessagesView/index.tsx b/app/views/SearchMessagesView/index.tsx index d3615f671f0..8be2a12afea 100644 --- a/app/views/SearchMessagesView/index.tsx +++ b/app/views/SearchMessagesView/index.tsx @@ -37,7 +37,7 @@ import { type IUrl, type IAttachment, type IRoomInfoParam, - type SubscriptionType + type RoomType } from '~/definitions'; import { searchMessages } from '~/lib/services/restApi'; import { type TNavigation } from '~/stacks/stackType'; @@ -72,7 +72,7 @@ class SearchMessagesView extends Component Date: Thu, 29 Oct 2026 15:43:59 -0300 Subject: [PATCH 4/9] refactor: tell leave and delete room actions apart by removal scope --- app/actions/room.ts | 16 +++++++------ app/definitions/ERoomType.ts | 7 ------ app/definitions/index.ts | 1 - app/reducers/room.test.ts | 9 ++++--- app/sagas/room.js | 24 +++++++++---------- app/views/RoomActionsView/index.tsx | 9 ++++--- .../hooks/useRoomDeletionActions.tsx | 7 +++--- app/views/TeamChannelsView.tsx | 5 ++-- 8 files changed, 34 insertions(+), 44 deletions(-) delete mode 100644 app/definitions/ERoomType.ts diff --git a/app/actions/room.ts b/app/actions/room.ts index 57f88675fa2..56824cec1c3 100644 --- a/app/actions/room.ts +++ b/app/actions/room.ts @@ -1,6 +1,6 @@ import { type Action } from 'redux'; -import { type ERoomType, type RoomType } from '../definitions'; +import { type RoomType } from '../definitions'; import { ROOM } from './actionsTypes'; // TYPE RETURN RELATED @@ -22,14 +22,16 @@ type TUnsubscribeRoom = IBaseReturn; type TRoom = Record; +export type TRoomRemovalScope = 'room' | 'team'; + interface ILeaveRoom extends Action { - roomType: ERoomType; + scope: TRoomRemovalScope; room: TRoom; selected?: ISelected; } interface IDeleteRoom extends Action { - roomType: ERoomType; + scope: TRoomRemovalScope; room: TRoom; selected?: ISelected; } @@ -92,20 +94,20 @@ export function unsubscribeRoom(rid: string): TUnsubscribeRoom { }; } -export function leaveRoom(roomType: ERoomType, room: TRoom, selected?: ISelected): ILeaveRoom { +export function leaveRoom(scope: TRoomRemovalScope, room: TRoom, selected?: ISelected): ILeaveRoom { return { type: ROOM.LEAVE, room, - roomType, + scope, selected }; } -export function deleteRoom(roomType: ERoomType, room: TRoom, selected?: ISelected): IDeleteRoom { +export function deleteRoom(scope: TRoomRemovalScope, room: TRoom, selected?: ISelected): IDeleteRoom { return { type: ROOM.DELETE, room, - roomType, + scope, selected }; } diff --git a/app/definitions/ERoomType.ts b/app/definitions/ERoomType.ts deleted file mode 100644 index 010fa78f04c..00000000000 --- a/app/definitions/ERoomType.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum ERoomType { - p = 'group', - c = 'channel', - d = 'direct', - t = 'team', - l = 'omnichannel' -} diff --git a/app/definitions/index.ts b/app/definitions/index.ts index 92106f71922..3875822e1d8 100644 --- a/app/definitions/index.ts +++ b/app/definitions/index.ts @@ -5,7 +5,6 @@ import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { type TNavigation } from '../stacks/stackType'; import { type TColors, type TSupportedThemes } from '../theme'; -export * from './ERoomType'; export * from './IAttachment'; export * from './IBiometricTrustStore'; export * from './ICannedResponse'; diff --git a/app/reducers/room.test.ts b/app/reducers/room.test.ts index ab39c239851..450adaaa86c 100644 --- a/app/reducers/room.test.ts +++ b/app/reducers/room.test.ts @@ -10,7 +10,6 @@ import { subscribeRoom, unsubscribeRoom } from '../actions/room'; -import { ERoomType } from '../definitions/ERoomType'; import { mockedStore } from './mockedStore'; import { initialState } from './room'; @@ -33,16 +32,16 @@ describe('test room reducer', () => { }); it('should return initial state after leaveRoom', () => { - mockedStore.dispatch(leaveRoom(ERoomType.c, { rid: ERoomType.c })); + mockedStore.dispatch(leaveRoom('room', { rid: 'GENERAL' })); const { rid, isDeleting } = mockedStore.getState().room; - expect(rid).toEqual(ERoomType.c); + expect(rid).toEqual('GENERAL'); expect(isDeleting).toEqual(true); }); it('should return initial state after deleteRoom', () => { - mockedStore.dispatch(deleteRoom(ERoomType.l, { rid: ERoomType.l })); + mockedStore.dispatch(deleteRoom('room', { rid: 'GENERAL' })); const { rid, isDeleting } = mockedStore.getState().room; - expect(rid).toEqual(ERoomType.l); + expect(rid).toEqual('GENERAL'); expect(isDeleting).toEqual(true); }); diff --git a/app/sagas/room.js b/app/sagas/room.js index eb6fdf86f57..45ff77a7f9b 100644 --- a/app/sagas/room.js +++ b/app/sagas/room.js @@ -69,18 +69,18 @@ const watchUserTyping = function* watchUserTyping({ rid, status, args }) { } }; -const handleRemovedRoom = function* handleRemovedRoom(roomType, actionType) { +const handleRemovedRoom = function* handleRemovedRoom(scope, actionType) { const isMasterDetail = getIsMasterDetail(); Navigation.popToTop(isMasterDetail); if (actionType === 'leave') { EventEmitter.emit(LISTENER, { - message: roomType === 'team' ? I18n.t('Left_The_Team_Successfully') : I18n.t('Left_The_Room_Successfully') + message: scope === 'team' ? I18n.t('Left_The_Team_Successfully') : I18n.t('Left_The_Room_Successfully') }); } if (actionType === 'delete') { EventEmitter.emit(LISTENER, { - message: roomType === 'team' ? I18n.t('Deleted_The_Team_Successfully') : I18n.t('Deleted_The_Room_Successfully') + message: scope === 'team' ? I18n.t('Deleted_The_Team_Successfully') : I18n.t('Deleted_The_Room_Successfully') }); } @@ -94,19 +94,19 @@ const handleRemovedRoom = function* handleRemovedRoom(roomType, actionType) { } }; -const handleLeaveRoom = function* handleLeaveRoom({ room, roomType, selected }) { +const handleLeaveRoom = function* handleLeaveRoom({ room, scope, selected }) { logEvent(events.RA_LEAVE); try { let result = {}; - if (roomType === 'channel') { + if (scope === 'room') { result = yield leaveRoom(room.rid, room.t); - } else if (roomType === 'team') { + } else if (scope === 'team') { result = yield leaveTeam({ teamId: room.teamId, ...(selected && { rooms: selected }) }); } if (result?.success) { - yield handleRemovedRoom(roomType, 'leave'); + yield handleRemovedRoom(scope, 'leave'); } } catch (e) { logEvent(events.RA_LEAVE_F); @@ -120,26 +120,26 @@ const handleLeaveRoom = function* handleLeaveRoom({ room, roomType, selected }) } }; -const handleDeleteRoom = function* handleDeleteRoom({ room, roomType, selected }) { +const handleDeleteRoom = function* handleDeleteRoom({ room, scope, selected }) { logEvent(events.RI_EDIT_DELETE); try { let result = {}; - if (roomType === 'channel') { + if (scope === 'room') { result = yield deleteRoom(room.rid || room._id, room.t); - } else if (roomType === 'team') { + } else if (scope === 'team') { result = yield deleteTeam({ teamId: room.teamId, ...(selected && { roomsToRemove: selected }) }); } if (result?.success) { - yield handleRemovedRoom(roomType, 'delete'); + yield handleRemovedRoom(scope, 'delete'); } } catch (e) { logEvent(events.RI_EDIT_DELETE_F); Alert.alert( I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { - action: roomType === 'team' ? I18n.t('deleting_team') : I18n.t('deleting_room') + action: scope === 'team' ? I18n.t('deleting_team') : I18n.t('deleting_room') }) ); } diff --git a/app/views/RoomActionsView/index.tsx b/app/views/RoomActionsView/index.tsx index 2417890ec7c..dadc2bfaaae 100644 --- a/app/views/RoomActionsView/index.tsx +++ b/app/views/RoomActionsView/index.tsx @@ -38,7 +38,6 @@ import { showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers/inf import log, { events, logEvent } from '~/lib/methods/helpers/log'; import Touch from '~/containers/Touch'; import styles from './styles'; -import { ERoomType } from '~/definitions/ERoomType'; import { E2E_ROOM_TYPES } from '~/lib/constants/keys'; import { themes } from '~/lib/constants/colors'; import { getPermalinkChannel } from '~/lib/methods/getPermalinks'; @@ -512,7 +511,7 @@ class RoomActionsView extends Component dispatch(leaveRoom(ERoomType.c, room)) + onPress: () => dispatch(leaveRoom('room', room)) }); }; @@ -601,14 +600,14 @@ class RoomActionsView extends Component dispatch(leaveRoom(ERoomType.t, room, data)), + nextAction: data => dispatch(leaveRoom('team', room, data)), showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_leave')) }); } else { showConfirmationAlert({ message: I18n.t('You_are_leaving_the_team', { team: getRoomTitle(room) }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }), - onPress: () => dispatch(leaveRoom(ERoomType.t, room)) + onPress: () => dispatch(leaveRoom('team', room)) }); } } @@ -617,7 +616,7 @@ class RoomActionsView extends Component dispatch(leaveRoom(ERoomType.t, room)) + onPress: () => dispatch(leaveRoom('team', room)) }); } }; diff --git a/app/views/RoomInfoEditView/hooks/useRoomDeletionActions.tsx b/app/views/RoomInfoEditView/hooks/useRoomDeletionActions.tsx index 942c9fe42d2..3d83b9fea84 100644 --- a/app/views/RoomInfoEditView/hooks/useRoomDeletionActions.tsx +++ b/app/views/RoomInfoEditView/hooks/useRoomDeletionActions.tsx @@ -11,7 +11,6 @@ import I18n from '~/i18n'; import { getRoomTitle, showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; import log from '~/lib/methods/helpers/log'; import { deleteRoom as actionDeleteRoom } from '~/actions/room'; -import { ERoomType } from '~/definitions/ERoomType'; import { type ISubscription, type TSubscriptionModel } from '~/definitions'; interface IUseRoomDeletionActions { @@ -56,7 +55,7 @@ const useRoomDeletionActions = ({ navigation, room, deleteCPermission, deletePPe showConfirmationAlert({ message: I18n.t('You_are_deleting_the_team', { team: getRoomTitle(room) }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }), - onPress: () => actionDeleteRoom(ERoomType.t, room, selected) + onPress: () => actionDeleteRoom('team', room, selected) }); } }); @@ -64,7 +63,7 @@ const useRoomDeletionActions = ({ navigation, room, deleteCPermission, deletePPe showConfirmationAlert({ message: I18n.t('You_are_deleting_the_team', { team: getRoomTitle(room) }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }), - onPress: () => dispatch(actionDeleteRoom(ERoomType.t, room)) + onPress: () => dispatch(actionDeleteRoom('team', room)) }); } } catch (e: any) { @@ -88,7 +87,7 @@ const useRoomDeletionActions = ({ navigation, room, deleteCPermission, deletePPe { text: I18n.t('Yes_action_it', { action: I18n.t('delete') }), style: 'destructive', - onPress: () => dispatch(actionDeleteRoom(ERoomType.c, room)) + onPress: () => dispatch(actionDeleteRoom('room', room)) } ], { cancelable: false } diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 828c7fc77cf..fd1fa6fb71d 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -16,7 +16,6 @@ import BackgroundContainer from '../containers/BackgroundContainer'; import RoomHeader from '../containers/RoomHeader'; import SafeAreaView from '../containers/SafeAreaView'; import { type IApplicationState, type IBaseScreen, type TSubscriptionModel } from '../definitions'; -import { ERoomType } from '../definitions/ERoomType'; import { withDimensions } from '../lib/hooks/withDimensions'; import { withMasterDetail } from '../lib/hooks/useMasterDetail'; import { BASE_ROW_HEIGHT, BASE_ROW_HEIGHT_CONDENSED } from '../lib/hooks/useResponsiveLayout/useResponsiveLayout'; @@ -48,7 +47,7 @@ const API_FETCH_COUNT = 25; const keyExtractor = (item: IItem) => item._id; export interface IItem { - _id: ERoomType; + _id: string; fname: string; customFields: object; broadcast: boolean; @@ -451,7 +450,7 @@ class TeamChannelsView extends Component dispatch(deleteRoom(ERoomType.c, item)) + onPress: () => dispatch(deleteRoom('room', item)) } ], { cancelable: false } From 6fcfa08a482490d6d1cd85fe7e14c6d4f32768b5 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 29 Oct 2026 15:45:55 -0300 Subject: [PATCH 5/9] refactor: map room types to permalink path segments in one place --- app/definitions/ISubscription.ts | 6 ------ .../methods/__tests__/roomTypePath.test.ts | 20 +++++++++++++++++++ app/lib/methods/canOpenRoom.ts | 19 +++++++++--------- app/lib/methods/getPermalinks.ts | 11 +++------- app/lib/methods/roomTypePath.ts | 15 ++++++++++++++ app/lib/notifications/index.ts | 11 +++------- app/sagas/deepLinking.js | 10 ++-------- 7 files changed, 52 insertions(+), 40 deletions(-) create mode 100644 app/lib/methods/__tests__/roomTypePath.test.ts create mode 100644 app/lib/methods/roomTypePath.ts diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts index 3ea0a8202db..e52dff801af 100644 --- a/app/definitions/ISubscription.ts +++ b/app/definitions/ISubscription.ts @@ -18,12 +18,6 @@ export interface IVisitor { lastMessageTs?: Date; } -export enum ERoomTypes { - DIRECT = 'direct', - GROUP = 'group', - CHANNEL = 'channel' -} - type RelationModified = { fetch(): Promise } & Relation; type OldKey = { e2eKeyId: string; ts: Date; E2EKey: string }; diff --git a/app/lib/methods/__tests__/roomTypePath.test.ts b/app/lib/methods/__tests__/roomTypePath.test.ts new file mode 100644 index 00000000000..cf95db4f30e --- /dev/null +++ b/app/lib/methods/__tests__/roomTypePath.test.ts @@ -0,0 +1,20 @@ +import { RoomType } from '~/definitions/IRoom'; +import { pathSegmentToRoomType, roomTypeToPathSegment } from '../roomTypePath'; + +describe('roomTypePath', () => { + it.each(Object.values(RoomType))('reads back room type %s from its path segment', roomType => { + expect(pathSegmentToRoomType(roomTypeToPathSegment(roomType))).toBe(roomType); + }); + + it.each([ + ['channel', RoomType.CHANNEL], + ['group', RoomType.GROUP], + ['direct', RoomType.DIRECT] + ])('reads the permalink segment %s as room type %s', (segment, roomType) => { + expect(pathSegmentToRoomType(segment)).toBe(roomType); + }); + + it.each([undefined, '', 'invite', 'c'])('reads %p as no room type', segment => { + expect(pathSegmentToRoomType(segment)).toBeUndefined(); + }); +}); diff --git a/app/lib/methods/canOpenRoom.ts b/app/lib/methods/canOpenRoom.ts index 95ea126922f..3ffa4196633 100644 --- a/app/lib/methods/canOpenRoom.ts +++ b/app/lib/methods/canOpenRoom.ts @@ -1,8 +1,9 @@ -import { ERoomTypes } from '~/definitions'; +import { RoomType } from '~/definitions'; import database from '../database'; import sdk from '../services/sdk'; import { createDirectMessage } from './createDirectMessage'; import { getRoomByTypeAndName } from '../services/restApi'; +import { pathSegmentToRoomType } from './roomTypePath'; async function openGroup(roomId: string) { try { @@ -14,11 +15,11 @@ async function openGroup(roomId: string) { } } -async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: string }) { +async function open({ type, rid, name }: { type?: RoomType; rid: string; name: string }) { try { // if it's a direct link without rid we'll create a new dm // if the dm already exists it'll return the existent - if (type === ERoomTypes.DIRECT && !rid) { + if (type === RoomType.DIRECT && !rid) { const result = await createDirectMessage(name); if (result.success) { const { room } = result; @@ -29,7 +30,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: } } - if (type === ERoomTypes.CHANNEL || type === ERoomTypes.GROUP) { + if (type === RoomType.CHANNEL || type === RoomType.GROUP) { let roomId = rid; let room = null; @@ -37,8 +38,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: // getRoomByTypeAndName resolves both, unlike the REST endpoints, which match // roomName exactly and return "not found" when handed an id. if (!roomId) { - const roomType = type === ERoomTypes.GROUP ? 'p' : 'c'; - room = await getRoomByTypeAndName(roomType, name); + room = await getRoomByTypeAndName(type, name); roomId = room?._id; } @@ -47,7 +47,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: } // a group has to be open before it can be read - if (type === ERoomTypes.GROUP) { + if (type === RoomType.GROUP) { const didOpenGroup = await openGroup(roomId); if (!didOpenGroup) { return false; @@ -94,10 +94,9 @@ export async function canOpenRoom({ rid, path }: { rid: string; path: string }): } } - const [type, name] = path.split('/'); - const t = type as ERoomTypes; + const [pathSegment, name] = path.split('/'); try { - const result = await open({ type: t, rid, name }); + const result = await open({ type: pathSegmentToRoomType(pathSegment), rid, name }); return result; } catch (e) { return false; diff --git a/app/lib/methods/getPermalinks.ts b/app/lib/methods/getPermalinks.ts index 8deaa27eca8..0af30e2206d 100644 --- a/app/lib/methods/getPermalinks.ts +++ b/app/lib/methods/getPermalinks.ts @@ -3,6 +3,7 @@ import { type RoomType, type TAnyMessageModel, type TSubscriptionModel } from '~ import { store } from '../store/auxStore'; import { isGroupChat } from './helpers'; import { getRoom } from './getRoom'; +import { roomTypeToPathSegment } from './roomTypePath'; interface ISubscription { t: RoomType; @@ -10,12 +11,6 @@ interface ISubscription { rid: string; } -const roomTypes = { - p: 'group', - c: 'channel', - d: 'direct' -}; - export async function getPermalinkMessage(message: TAnyMessageModel): Promise { if (!message.subscription) return null; let room: TSubscriptionModel; @@ -35,11 +30,11 @@ export function getPermalinkChannel(channel: TSubscriptionModel): string { } export function buildPermalinkMessage(server: string, isGoupChat: boolean, room: ISubscription, messageId: string) { - const roomType = roomTypes[room.t as Exclude]; + const roomType = roomTypeToPathSegment(room.t); return `${server}/${roomType}/${isGoupChat ? room.rid : encodeURIComponent(room.name)}?msg=${messageId}`; } export function buildPermalinkChannel(server: string, channel: ISubscription) { - const roomType = roomTypes[channel.t as Exclude]; + const roomType = roomTypeToPathSegment(channel.t); return `${server}/${roomType}/${encodeURIComponent(channel.name)}`; } diff --git a/app/lib/methods/roomTypePath.ts b/app/lib/methods/roomTypePath.ts new file mode 100644 index 00000000000..636a8004f0f --- /dev/null +++ b/app/lib/methods/roomTypePath.ts @@ -0,0 +1,15 @@ +import { RoomType } from '~/definitions/IRoom'; + +const pathSegmentByRoomType = { + [RoomType.CHANNEL]: 'channel', + [RoomType.GROUP]: 'group', + [RoomType.DIRECT]: 'direct', + [RoomType.OMNICHANNEL]: 'channels' +} as const satisfies Record; + +const roomTypes = Object.keys(pathSegmentByRoomType) as RoomType[]; + +export const roomTypeToPathSegment = (t: RoomType) => pathSegmentByRoomType[t]; + +export const pathSegmentToRoomType = (segment?: string): RoomType | undefined => + roomTypes.find(t => pathSegmentByRoomType[t] === segment); diff --git a/app/lib/notifications/index.ts b/app/lib/notifications/index.ts index 9ca621c3724..3fe75720122 100644 --- a/app/lib/notifications/index.ts +++ b/app/lib/notifications/index.ts @@ -5,6 +5,7 @@ import { appInit } from '~/actions/app'; import { deepLinkingClickCallPush, deepLinkingOpen } from '~/actions/deepLinking'; import { type INotification, RoomType } from '~/definitions'; import { store } from '../store/auxStore'; +import { roomTypeToPathSegment } from '../methods/roomTypePath'; import { pushNotificationConfigure, removeAllNotifications, setNotificationsBadgeCount } from './push'; export { getDeviceToken } from './deviceToken'; @@ -13,7 +14,7 @@ interface IEjson { rid: string; name: string; sender: { username: string; name: string }; - type: string; + type: RoomType; host: string; messageId: string; } @@ -52,12 +53,6 @@ export const onNotification = (push: INotification): void => { return; } const { rid, name, sender, type, host, messageId }: IEjson = notification; - const types: Record = { - c: 'channel', - d: 'direct', - p: 'group', - l: 'channels' - }; let roomName = name; if (type === RoomType.DIRECT) { roomName = sender?.username ?? name; @@ -69,7 +64,7 @@ export const onNotification = (push: INotification): void => { host, rid, messageId, - path: `${types[type]}/${roomName}` + path: `${roomTypeToPathSegment(type)}/${roomName}` }; store.dispatch(deepLinkingOpen(params)); return; diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index e827f7590d6..003039d2110 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -15,6 +15,7 @@ import database from '../lib/database'; import { getServerById } from '../lib/database/services/Server'; import { canOpenRoom } from '../lib/methods/canOpenRoom'; import { getServerInfo } from '../lib/methods/getServerInfo'; +import { pathSegmentToRoomType } from '../lib/methods/roomTypePath'; import { getUidDirectMessage, normalizeDeepLinkingServerHost } from '../lib/methods/helpers'; import EventEmitter from '../lib/methods/helpers/events'; import { goRoom, navigateToRoom } from '../lib/methods/helpers/goRoom'; @@ -31,13 +32,6 @@ import sdk from '../lib/services/sdk'; import Navigation, { waitForNavigationReady } from '../lib/navigation/appNavigation'; import { resetVoipState } from '../lib/services/voip/resetVoipState'; -const roomTypes = { - channel: 'c', - direct: 'd', - group: 'p', - channels: 'l' -}; - const DEEP_LINK_SIGN_IN_TIMEOUT = 60_000; export const shouldAutoConfirmDeepLinkLogin = (isE2E, params = {}) => isE2E && params.forceLoginPrompt !== 'true'; @@ -97,7 +91,7 @@ const navigate = function* navigate({ params }) { if (room) { const item = { name, - t: roomTypes[type], + t: pathSegmentToRoomType(type), roomUserId: getUidDirectMessage(room), ...room }; From 644c819e6bd664aab537cc11af22b637b2eb6a62 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 29 Oct 2026 15:53:05 -0300 Subject: [PATCH 6/9] refactor: gather room kind predicates in one typed module --- app/lib/apps/__tests__/filters.test.ts | 1 + app/lib/apps/definitions.ts | 1 + app/lib/apps/filters.ts | 10 ++++------ app/lib/methods/helpers/helpers.ts | 5 +---- app/lib/methods/helpers/index.ts | 1 + app/lib/methods/helpers/room.ts | 2 -- app/lib/methods/helpers/roomKind.ts | 8 ++++++++ .../hooks/__tests__/useSubscriptionUnreads.test.ts | 1 - app/views/RoomView/hooks/useRoomActionsState.ts | 2 +- app/views/SelectedUsersView/index.tsx | 10 +++++----- app/views/ShareView/Header.tsx | 4 ++-- 11 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 app/lib/methods/helpers/roomKind.ts diff --git a/app/lib/apps/__tests__/filters.test.ts b/app/lib/apps/__tests__/filters.test.ts index 590b2bc537d..97f2007039f 100644 --- a/app/lib/apps/__tests__/filters.test.ts +++ b/app/lib/apps/__tests__/filters.test.ts @@ -23,6 +23,7 @@ describe('applyRoomFilter', () => { [RoomTypeFilter.PRIVATE_DISCUSSION, { t: 'p', prid: 'parent' }], [RoomTypeFilter.DIRECT, { t: 'd' }], [RoomTypeFilter.DIRECT_MULTIPLE, { t: 'd', uids: ['a', 'b', 'c'] }], + [RoomTypeFilter.DIRECT_MULTIPLE, { t: 'd', usernames: ['a', 'b', 'c'] }], [RoomTypeFilter.LIVE_CHAT, { t: 'l' }] ])('%s matches its room', (roomType, room) => { expect(applyRoomFilter(button({ when: { roomTypes: [roomType] } }), room)).toBe(true); diff --git a/app/lib/apps/definitions.ts b/app/lib/apps/definitions.ts index c61a68f17cf..04b8d7ffb36 100644 --- a/app/lib/apps/definitions.ts +++ b/app/lib/apps/definitions.ts @@ -46,6 +46,7 @@ export interface IAppActionButtonRoom { teamMain?: boolean; prid?: string; uids?: string[]; + usernames?: string[]; } export interface IAppLanguages { diff --git a/app/lib/apps/filters.ts b/app/lib/apps/filters.ts index fb182121e93..2e4561f9415 100644 --- a/app/lib/apps/filters.ts +++ b/app/lib/apps/filters.ts @@ -5,19 +5,17 @@ import { type TRoomTypeFilter, RoomTypeFilter } from './definitions'; - -const isTeamRoom = ({ teamMain }: IAppActionButtonRoom): boolean => !!teamMain; -const isDiscussion = ({ prid }: IAppActionButtonRoom): boolean => !!prid; +import { isDiscussion, isGroupChat, isTeamMain } from '~/lib/methods/helpers/roomKind'; const roomTypeMatchers: { [K in TRoomTypeFilter]: (room: IAppActionButtonRoom) => boolean } = { [RoomTypeFilter.PUBLIC_CHANNEL]: room => room.t === 'c', [RoomTypeFilter.PRIVATE_CHANNEL]: room => room.t === 'p', - [RoomTypeFilter.PUBLIC_TEAM]: room => isTeamRoom(room) && room.t === 'c', - [RoomTypeFilter.PRIVATE_TEAM]: room => isTeamRoom(room) && room.t === 'p', + [RoomTypeFilter.PUBLIC_TEAM]: room => isTeamMain(room) && room.t === 'c', + [RoomTypeFilter.PRIVATE_TEAM]: room => isTeamMain(room) && room.t === 'p', [RoomTypeFilter.PUBLIC_DISCUSSION]: room => isDiscussion(room) && room.t === 'c', [RoomTypeFilter.PRIVATE_DISCUSSION]: room => isDiscussion(room) && room.t === 'p', [RoomTypeFilter.DIRECT]: room => room.t === 'd', - [RoomTypeFilter.DIRECT_MULTIPLE]: room => room.t === 'd' && (room.uids?.length ?? 0) > 2, + [RoomTypeFilter.DIRECT_MULTIPLE]: room => room.t === 'd' && isGroupChat(room), [RoomTypeFilter.LIVE_CHAT]: room => room.t === 'l' }; diff --git a/app/lib/methods/helpers/helpers.ts b/app/lib/methods/helpers/helpers.ts index 9fbe6639b1c..240046991b1 100644 --- a/app/lib/methods/helpers/helpers.ts +++ b/app/lib/methods/helpers/helpers.ts @@ -2,10 +2,7 @@ import log from './log'; import { store as reduxStore } from '~/lib/store/auxStore'; import database from '~/lib/database'; - -export function isGroupChat(room): boolean { - return ((room?.uids && room.uids.length > 2) || (room?.usernames && room.usernames.length > 2)) ?? false; -} +import { isGroupChat } from './roomKind'; export function getRoomAvatar(room) { if (isGroupChat(room) && room.uids && room.usernames) { diff --git a/app/lib/methods/helpers/index.ts b/app/lib/methods/helpers/index.ts index 07133c487fb..413a8038358 100644 --- a/app/lib/methods/helpers/index.ts +++ b/app/lib/methods/helpers/index.ts @@ -12,6 +12,7 @@ export * from './normalizeDeepLinkingServerHost'; export * from './normalizeStatusExpiresAt'; export * from './onAbort'; export * from './room'; +export * from './roomKind'; export * from './server'; export * from './isSsl'; export * from './isValidEmail'; diff --git a/app/lib/methods/helpers/room.ts b/app/lib/methods/helpers/room.ts index 6d21db028ea..4a28577db97 100644 --- a/app/lib/methods/helpers/room.ts +++ b/app/lib/methods/helpers/room.ts @@ -68,5 +68,3 @@ export const getBadgeColor = ({ export const makeThreadName = (messageRecord: { id?: string; msg?: string; attachments?: IAttachment[] }): string | undefined => messageRecord.msg || messageRecord?.attachments?.[0]?.title; - -export const isTeamRoom = ({ teamId, joined }: { teamId?: string; joined?: boolean }): boolean => (!!teamId && joined) ?? false; diff --git a/app/lib/methods/helpers/roomKind.ts b/app/lib/methods/helpers/roomKind.ts new file mode 100644 index 00000000000..48fb20f80d5 --- /dev/null +++ b/app/lib/methods/helpers/roomKind.ts @@ -0,0 +1,8 @@ +export const isGroupChat = (room?: { uids?: string[]; usernames?: string[] } | null): boolean => + (room?.uids?.length ?? 0) > 2 || (room?.usernames?.length ?? 0) > 2; + +export const isDiscussion = ({ prid }: { prid?: string }): boolean => !!prid; + +export const isTeamMain = ({ teamMain }: { teamMain?: boolean }): boolean => !!teamMain; + +export const isTeamRoom = ({ teamId, joined }: { teamId?: string; joined?: boolean }): boolean => (!!teamId && joined) ?? false; diff --git a/app/views/RoomView/hooks/__tests__/useSubscriptionUnreads.test.ts b/app/views/RoomView/hooks/__tests__/useSubscriptionUnreads.test.ts index ba1fe856b58..4e0a08275ee 100644 --- a/app/views/RoomView/hooks/__tests__/useSubscriptionUnreads.test.ts +++ b/app/views/RoomView/hooks/__tests__/useSubscriptionUnreads.test.ts @@ -14,7 +14,6 @@ jest.mock('~/lib/methods/readMessages', () => ({ readMessages: jest.fn() })); jest.mock('~/lib/methods/loadThreadMessages', () => ({ loadThreadMessages: jest.fn() })); jest.mock('~/lib/methods/helpers/helpers', () => ({ getUidDirectMessage: jest.fn(), - isGroupChat: jest.fn(() => false), canAutoTranslate: jest.fn(() => false) })); diff --git a/app/views/RoomView/hooks/useRoomActionsState.ts b/app/views/RoomView/hooks/useRoomActionsState.ts index 2e6cf2eb57c..466d5360d08 100644 --- a/app/views/RoomView/hooks/useRoomActionsState.ts +++ b/app/views/RoomView/hooks/useRoomActionsState.ts @@ -48,7 +48,7 @@ export const useRoomActionsState = (rid: string, roomStore: RoomStore) => { t: room.t as RoomType, status: fromSubscription(r => r.status, undefined)(s), roomName: getRoomTitle(room), - roomIsGroupChat: isGroupChat(room), + roomIsGroupChat: fromSubscription(isGroupChat, false)(s), teamMain: fromSubscription(r => !!r.teamMain, false)(s), encrypted: fromSubscription(r => r.encrypted, undefined)(s), disableNotifications: fromSubscription(r => r.disableNotifications, undefined)(s) diff --git a/app/views/SelectedUsersView/index.tsx b/app/views/SelectedUsersView/index.tsx index 0b330d8e1b3..10c1c908674 100644 --- a/app/views/SelectedUsersView/index.tsx +++ b/app/views/SelectedUsersView/index.tsx @@ -20,6 +20,7 @@ import { stackedSearchBarOptions, nativeHeaderContentInset, translucentHeader } import { headerRightActions } from '~/lib/methods/helpers/navigation/headerActions'; import database from '~/lib/database'; import UserItem from '~/containers/UserItem'; +import { type TSubscriptionModel } from '~/definitions'; import { type ISelectedUser } from '~/reducers/selectedUsers'; import { getUserSelector } from '~/selectors/login'; import { type ChatsStackParamList, type NewMessageStackParamList } from '~/stacks/types'; @@ -39,7 +40,7 @@ type TNavigation = NativeStackNavigationProp< >; const SelectedUsersView = () => { - const [chats, setChats] = useState([]); + const [chats, setChats] = useState([]); const [search, setSearch] = useState([]); // True while the remote (spotlight) request is in flight, after local results are already painted const [searching, setSearching] = useState(false); @@ -136,7 +137,7 @@ const SelectedUsersView = () => { const observable = await db.get('subscriptions').query(Q.where('t', 'd')).observeWithColumns(['room_updated_at']); querySubscription = observable.subscribe(data => { - const chats = orderBy(data, ['roomUpdatedAt'], ['desc']) as ISelectedUser[]; + const chats = orderBy(data, ['roomUpdatedAt'], ['desc']); setChats(chats); }); } catch (e) { @@ -179,9 +180,8 @@ const SelectedUsersView = () => { } }; - const searchOrChats = (search.length > 0 ? search : chats) as ISelectedUser[]; - // filter DM between multiple users - const data = searchOrChats.filter(sub => !isGroupChatMethod(sub)); + const directChats = chats.filter(chat => !isGroupChatMethod(chat)); + const data = (search.length > 0 ? search : directChats) as ISelectedUser[]; return ( diff --git a/app/views/ShareView/Header.tsx b/app/views/ShareView/Header.tsx index b107eb4f843..781218aa73f 100644 --- a/app/views/ShareView/Header.tsx +++ b/app/views/ShareView/Header.tsx @@ -8,7 +8,7 @@ import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; import { makeThreadName } from '~/lib/methods/helpers/room'; import { type TThreadModel } from '~/definitions'; -import { type TRoomOrPreview } from '~/definitions/TRoom'; +import { isSubscriptionModel, type TRoomOrPreview } from '~/definitions/TRoom'; import { getRoomTitle, isGroupChat, isAndroid, isTablet } from '~/lib/methods/helpers'; import { getMessageById } from '~/lib/database/services/Message'; @@ -64,7 +64,7 @@ const Header = memo(({ room, thread }: IHeader) => { } else if (type === 'l') { icon = 'omnichannel'; } else if (type === 'd') { - if (isGroupChat(room)) { + if (isSubscriptionModel(room) && isGroupChat(room)) { icon = 'team'; } else { icon = 'mention'; From e052324e1215c20b910d4fc78f4f2eb09007f9ff Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 30 Oct 2026 10:51:00 -0300 Subject: [PATCH 7/9] refactor: drop the isRoomType guard now that room types are typed - Type IAppActionButtonRoom.t as RoomType and observe usernames for app action buttons - Add uids/usernames to TPreviewRoom so ShareView shows the team icon for group DMs picked from the share sheet - Derive roomTypePath's room types from RoomType and cover the omnichannel segment - Stop exporting the internal room type to API type map - Document in CONTEXT.md that a Thread carries its parent Room's type --- CONTEXT.md | 2 +- app/definitions/TRoom.ts | 2 ++ app/lib/apps/__tests__/filters.test.ts | 10 +++++-- app/lib/apps/definitions.ts | 4 ++- app/lib/apps/useAppActionButtons.ts | 2 +- .../methods/__tests__/roomTypePath.test.ts | 3 +- app/lib/methods/loadMissedMessages.ts | 3 +- app/lib/methods/roomTypePath.ts | 2 +- app/lib/methods/roomTypeToApiType.ts | 4 +-- app/views/ShareView/Header.tsx | 4 +-- app/views/ShareView/__tests__/Header.test.tsx | 29 +++++++++++++++++++ 11 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 app/views/ShareView/__tests__/Header.test.tsx diff --git a/CONTEXT.md b/CONTEXT.md index f39883ab0d0..064f86b15f3 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -175,7 +175,7 @@ Subscription fields: **Unread** (count; _avoid_ Badge count), **User Mentions** ## Flagged ambiguities - **Server**: legacy name for Workspace, still in code (`server`, `serversHistory`, `selectServer`). Use Workspace in prose, new names, and copy; renaming is a migration. "Server" as the remote side (_server response_, _server clock_) stays valid. -- **Room types `'e2e'` and `'thread'`** in the `SubscriptionType` enum are FIXME-marked flags, not room types. +- **Room type** (`RoomType`) is only `'c'`, `'p'`, `'d'` or `'l'`. A Thread is not a room type: a RoomView with `tmid` is a Thread and carries its parent Room's type. - **Account**: means a User, which authenticates on a Workspace. - **Channel** strictly means type `'c'`. - **History**: Workspace History vs Room History. diff --git a/app/definitions/TRoom.ts b/app/definitions/TRoom.ts index 0dc2a5e6bb0..1f405eecf95 100644 --- a/app/definitions/TRoom.ts +++ b/app/definitions/TRoom.ts @@ -7,6 +7,8 @@ export type TPreviewRoom = { name?: string; fname?: string; prid?: string; + uids?: string[]; + usernames?: string[]; visitor?: IVisitor; joinCodeRequired?: boolean; lastMessage?: ILastMessage; diff --git a/app/lib/apps/__tests__/filters.test.ts b/app/lib/apps/__tests__/filters.test.ts index 97f2007039f..856c7449fad 100644 --- a/app/lib/apps/__tests__/filters.test.ts +++ b/app/lib/apps/__tests__/filters.test.ts @@ -1,4 +1,10 @@ -import { type IAppActionButton, RoomTypeFilter, UIActionButtonContext } from '../definitions'; +import { + type IAppActionButton, + type IAppActionButtonRoom, + RoomTypeFilter, + type TRoomTypeFilter, + UIActionButtonContext +} from '../definitions'; import { applyAuthFilter, applyCategoryFilter, applyRoomFilter, collectPermissions } from '../filters'; const button = (overrides: Partial = {}): IAppActionButton => ({ @@ -25,7 +31,7 @@ describe('applyRoomFilter', () => { [RoomTypeFilter.DIRECT_MULTIPLE, { t: 'd', uids: ['a', 'b', 'c'] }], [RoomTypeFilter.DIRECT_MULTIPLE, { t: 'd', usernames: ['a', 'b', 'c'] }], [RoomTypeFilter.LIVE_CHAT, { t: 'l' }] - ])('%s matches its room', (roomType, room) => { + ] satisfies [TRoomTypeFilter, IAppActionButtonRoom][])('%s matches its room', (roomType, room) => { expect(applyRoomFilter(button({ when: { roomTypes: [roomType] } }), room)).toBe(true); }); diff --git a/app/lib/apps/definitions.ts b/app/lib/apps/definitions.ts index 04b8d7ffb36..b2130c211f2 100644 --- a/app/lib/apps/definitions.ts +++ b/app/lib/apps/definitions.ts @@ -1,5 +1,7 @@ // https://github.com/RocketChat/Rocket.Chat/tree/develop/packages/apps-engine/src/definition/ui +import { type RoomType } from '~/definitions/IRoom'; + export const UIActionButtonContext = { ROOM_ACTION: 'roomAction', MESSAGE_BOX_ACTION: 'messageBoxAction' @@ -42,7 +44,7 @@ export interface IAppActionButton { } export interface IAppActionButtonRoom { - t?: string; + t?: RoomType; teamMain?: boolean; prid?: string; uids?: string[]; diff --git a/app/lib/apps/useAppActionButtons.ts b/app/lib/apps/useAppActionButtons.ts index 256ccaf8668..bc55829224a 100644 --- a/app/lib/apps/useAppActionButtons.ts +++ b/app/lib/apps/useAppActionButtons.ts @@ -42,7 +42,7 @@ export const useAppActionButtons = (rid?: string): IAppActionButtonItem[] => { const subscription$ = db .get('subscriptions') .query(Q.where('id', rid)) - .observeWithColumns(['t', 'roles', 'team_main', 'prid', 'uids']); + .observeWithColumns(['t', 'roles', 'team_main', 'prid', 'uids', 'usernames']); const permissions$ = permissionIds.length ? db .get('permissions') diff --git a/app/lib/methods/__tests__/roomTypePath.test.ts b/app/lib/methods/__tests__/roomTypePath.test.ts index cf95db4f30e..3dca892caad 100644 --- a/app/lib/methods/__tests__/roomTypePath.test.ts +++ b/app/lib/methods/__tests__/roomTypePath.test.ts @@ -9,7 +9,8 @@ describe('roomTypePath', () => { it.each([ ['channel', RoomType.CHANNEL], ['group', RoomType.GROUP], - ['direct', RoomType.DIRECT] + ['direct', RoomType.DIRECT], + ['channels', RoomType.OMNICHANNEL] ])('reads the permalink segment %s as room type %s', (segment, roomType) => { expect(pathSegmentToRoomType(segment)).toBe(roomType); }); diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index 9ad7d5dc392..7086d0ae775 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -7,7 +7,6 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; import log from './helpers/log'; import { snapshotServerTimestamps, type TServerTimestamps, updateLastOpen } from './updateLastOpen'; import { loadMessagesForRoom } from './loadMessagesForRoom'; -import { isRoomType } from './roomTypeToApiType'; const count = 50; @@ -84,7 +83,7 @@ export async function loadMissedMessages(args: { // A room whose history load fetched no messages has no cursor to sync from, and syncing is what // would write one. Load its history instead: it fetches the same gap and seeds the cursor. const sub = await getSubscriptionByRoomId(args.rid); - if (sub && !sub.lastOpen && isRoomType(sub.t)) { + if (sub && !sub.lastOpen) { return loadMessagesForRoom({ rid: args.rid, t: sub.t }); } } diff --git a/app/lib/methods/roomTypePath.ts b/app/lib/methods/roomTypePath.ts index 636a8004f0f..965580d38f4 100644 --- a/app/lib/methods/roomTypePath.ts +++ b/app/lib/methods/roomTypePath.ts @@ -7,7 +7,7 @@ const pathSegmentByRoomType = { [RoomType.OMNICHANNEL]: 'channels' } as const satisfies Record; -const roomTypes = Object.keys(pathSegmentByRoomType) as RoomType[]; +const roomTypes = Object.values(RoomType); export const roomTypeToPathSegment = (t: RoomType) => pathSegmentByRoomType[t]; diff --git a/app/lib/methods/roomTypeToApiType.ts b/app/lib/methods/roomTypeToApiType.ts index 5cf6f63cdbb..3ec2a84a149 100644 --- a/app/lib/methods/roomTypeToApiType.ts +++ b/app/lib/methods/roomTypeToApiType.ts @@ -16,7 +16,7 @@ type ApiTypes = T extends 'c' ? ETypes.Channels : never; -export const types: { [K in RoomType]: ApiTypes } = { +const types: { [K in RoomType]: ApiTypes } = { c: ETypes.Channels, d: ETypes.Im, p: ETypes.Groups, @@ -24,5 +24,3 @@ export const types: { [K in RoomType]: ApiTypes } = { }; export const roomTypeToApiType = (t: T) => types[t]; - -export const isRoomType = (t: unknown): t is RoomType => typeof t === 'string' && t in types; diff --git a/app/views/ShareView/Header.tsx b/app/views/ShareView/Header.tsx index 781218aa73f..b107eb4f843 100644 --- a/app/views/ShareView/Header.tsx +++ b/app/views/ShareView/Header.tsx @@ -8,7 +8,7 @@ import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; import { makeThreadName } from '~/lib/methods/helpers/room'; import { type TThreadModel } from '~/definitions'; -import { isSubscriptionModel, type TRoomOrPreview } from '~/definitions/TRoom'; +import { type TRoomOrPreview } from '~/definitions/TRoom'; import { getRoomTitle, isGroupChat, isAndroid, isTablet } from '~/lib/methods/helpers'; import { getMessageById } from '~/lib/database/services/Message'; @@ -64,7 +64,7 @@ const Header = memo(({ room, thread }: IHeader) => { } else if (type === 'l') { icon = 'omnichannel'; } else if (type === 'd') { - if (isSubscriptionModel(room) && isGroupChat(room)) { + if (isGroupChat(room)) { icon = 'team'; } else { icon = 'mention'; diff --git a/app/views/ShareView/__tests__/Header.test.tsx b/app/views/ShareView/__tests__/Header.test.tsx new file mode 100644 index 00000000000..bac7543b71f --- /dev/null +++ b/app/views/ShareView/__tests__/Header.test.tsx @@ -0,0 +1,29 @@ +import { render, screen } from '@testing-library/react-native'; + +import { initStore } from '~/lib/store/auxStore'; +import { mockedStore } from '~/reducers/mockedStore'; +import { type TThreadModel } from '~/definitions'; +import { type TPreviewRoom } from '~/definitions/TRoom'; +import Header from '../Header'; + +jest.mock('~/containers/CustomIcon', () => { + const { Text } = jest.requireActual('react-native'); + return { CustomIcon: ({ name }: { name: string }) => }; +}); + +const noThread = {} as TThreadModel; + +beforeAll(() => { + initStore(mockedStore); +}); + +describe('ShareView Header', () => { + it.each([ + ['a group DM picked from the share sheet gets the team icon', { rid: 'rid', t: 'd', usernames: ['a', 'b', 'c'] }, 'team'], + ['a 1-on-1 DM gets the mention icon', { rid: 'rid', t: 'd', usernames: ['a', 'b'] }, 'mention'] + ] satisfies [string, TPreviewRoom, string][])('%s', (_, room, icon) => { + render(
); + + expect(screen.getByTestId(`icon-${icon}`)).toBeOnTheScreen(); + }); +}); From bddab2f205fb32410498a12981ad358bfaa8635e Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 30 Oct 2026 11:33:28 -0300 Subject: [PATCH 8/9] test: drop the loadMissedMessages case for the removed room type guard --- app/lib/methods/loadMissedMessages.test.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/lib/methods/loadMissedMessages.test.ts b/app/lib/methods/loadMissedMessages.test.ts index d80abede220..4fbfda12259 100644 --- a/app/lib/methods/loadMissedMessages.test.ts +++ b/app/lib/methods/loadMissedMessages.test.ts @@ -81,16 +81,6 @@ describe('loadMissedMessages', () => { expect(mockedUpdateMessages).not.toHaveBeenCalled(); }); - it('falls through to the sync path when the subscription type is not a room type', async () => { - mockedGetSubscriptionByRoomId.mockResolvedValue({ lastOpen: null, t: 'not-a-room-type' } as never); - mockedSdkGet.mockResolvedValue({ result: { updated: [], deleted: [], cursor: { next: null } } } as never); - - await loadMissedMessages({ rid: RID }); - - expect(mockedLoadMessagesForRoom).not.toHaveBeenCalled(); - expect(mockedUpdateMessages).toHaveBeenCalledWith(expect.objectContaining({ rid: RID, update: [], remove: [] })); - }); - it('does nothing when there is no subscription', async () => { mockedGetSubscriptionByRoomId.mockResolvedValue(null as never); From df3a1f839dcc5a6447cf055668fd120ff80bf5ef Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 30 Oct 2026 11:33:31 -0300 Subject: [PATCH 9/9] refactor: type the preview room's t as RoomType - Render EmptyRoomView when RoomView mounts without params, so the room store is only built with a real rid and t - Make t and rid required on the preview room, room screen input, openThread and jump-to-message params - Drop EMPTY_ROOM, the RoomType casts and the no-rid branches in the room header and content --- app/definitions/TRoom.ts | 3 +- .../List/hooks/__tests__/useMessages.test.tsx | 4 +- .../RoomView/__tests__/RoomView.test.tsx | 8 +--- .../RoomView/components/EmptyRoomView.tsx | 9 +++++ app/views/RoomView/components/MessageRow.tsx | 3 +- .../RoomView/components/RoomMessageList.tsx | 3 +- .../RoomViewContent/RoomViewContent.tsx | 9 +---- .../RoomViewHeader/RoomViewHeader.tsx | 9 ++--- .../__tests__/RoomViewHeader.test.tsx | 14 +------ .../__tests__/RoomMessageActions.test.tsx | 2 +- app/views/RoomView/definitions.ts | 12 +++--- .../hooks/__tests__/useE2EEStatus.test.ts | 4 +- .../__tests__/useEmptyRoomHeader.test.tsx | 39 +++++++++++++++++++ .../hooks/__tests__/useJsRoomHeader.test.tsx | 10 ----- .../__tests__/useSubscriptionUnreads.test.ts | 6 +-- .../RoomView/hooks/useEmptyRoomHeader.tsx | 18 +++++++++ .../RoomView/hooks/useGoRoomActionsView.ts | 6 +-- app/views/RoomView/hooks/useJsRoomHeader.tsx | 13 +------ .../RoomView/hooks/useRoomActionsState.ts | 4 +- app/views/RoomView/index.tsx | 18 ++++++--- app/views/RoomView/services/openThread.ts | 7 +--- app/views/RoomView/services/parseRoomRoute.ts | 6 +-- app/views/RoomView/stores/RoomStore.ts | 7 ++-- .../stores/__tests__/RoomStore.test.ts | 17 ++++---- .../__tests__/RoomStoreContext.test.tsx | 2 +- 25 files changed, 127 insertions(+), 106 deletions(-) create mode 100644 app/views/RoomView/components/EmptyRoomView.tsx create mode 100644 app/views/RoomView/hooks/__tests__/useEmptyRoomHeader.test.tsx create mode 100644 app/views/RoomView/hooks/useEmptyRoomHeader.tsx diff --git a/app/definitions/TRoom.ts b/app/definitions/TRoom.ts index 1f405eecf95..2750f5a0081 100644 --- a/app/definitions/TRoom.ts +++ b/app/definitions/TRoom.ts @@ -1,9 +1,10 @@ import { type ILastMessage } from './IMessage'; +import { type RoomType } from './IRoom'; import { type IVisitor, type TSubscriptionModel } from './ISubscription'; export type TPreviewRoom = { rid: string; - t: string; + t: RoomType; name?: string; fname?: string; prid?: string; diff --git a/app/views/RoomView/List/hooks/__tests__/useMessages.test.tsx b/app/views/RoomView/List/hooks/__tests__/useMessages.test.tsx index d362aa2f183..e227577ecea 100644 --- a/app/views/RoomView/List/hooks/__tests__/useMessages.test.tsx +++ b/app/views/RoomView/List/hooks/__tests__/useMessages.test.tsx @@ -4,7 +4,7 @@ import { type ReactNode } from 'react'; import { ROOM } from '~/actions/actionsTypes'; import { type IRoomHistoryRequest } from '~/actions/room'; -import { type RoomType, type TAnyMessageModel } from '~/definitions'; +import { type TAnyMessageModel } from '~/definitions'; import database from '~/lib/database'; import { getMessageById } from '~/lib/database/services/Message'; import { getThreadById } from '~/lib/database/services/Thread'; @@ -72,7 +72,7 @@ const baseArgs = { showMessageInMainThread: true, hideSystemMessages: [] as string[], serverVersion: '3.0.0' as string | null, - t: 'c' as RoomType + t: 'c' as const }; const msg = (overrides: Partial & { id: string }): TAnyMessageModel => diff --git a/app/views/RoomView/__tests__/RoomView.test.tsx b/app/views/RoomView/__tests__/RoomView.test.tsx index db38cd6b187..54ad8767fc5 100644 --- a/app/views/RoomView/__tests__/RoomView.test.tsx +++ b/app/views/RoomView/__tests__/RoomView.test.tsx @@ -30,6 +30,7 @@ jest.mock('../components/EncryptedRoom', () => { return { EncryptedRoom: () => createElement(RNView, { testID: 'encrypted-screen' }) }; }); jest.mock('../components/RoomViewHeader/RoomViewHeader', () => ({ RoomViewHeader: () => null })); +jest.mock('../hooks/useEmptyRoomHeader', () => ({ useEmptyRoomHeader: jest.fn() })); jest.mock('../hooks/useE2EEStatus', () => ({ useE2EEStatus: jest.fn(() => ({ showMissingE2EEKey: false, showE2EEDisabledRoom: false, hasE2EEWarning: false })) })); @@ -87,13 +88,6 @@ describe('RoomView', () => { expect(screen.queryByTestId('room-screen')).toBeNull(); }); - it('renders the empty-room background when the route has a rid but no type', () => { - renderRoomView({ rid: 'rid-1' }); - - expect(screen.getByTestId('room-view-empty')).toBeOnTheScreen(); - expect(screen.queryByTestId('room-screen')).toBeNull(); - }); - it('keeps the room screen unmounted while the room is an invite', () => { room.current = { id: 'sub-1', rid: 'rid-1', t: 'c' } as TRoomOrPreview; jest.mocked(isInviteSubscription).mockReturnValue(true); diff --git a/app/views/RoomView/components/EmptyRoomView.tsx b/app/views/RoomView/components/EmptyRoomView.tsx new file mode 100644 index 00000000000..6bdaa972c80 --- /dev/null +++ b/app/views/RoomView/components/EmptyRoomView.tsx @@ -0,0 +1,9 @@ +import { type ReactElement } from 'react'; + +import { useEmptyRoomHeader } from '../hooks/useEmptyRoomHeader'; +import { RoomBackground } from './RoomBackground'; + +export const EmptyRoomView = (): ReactElement => { + useEmptyRoomHeader(); + return ; +}; diff --git a/app/views/RoomView/components/MessageRow.tsx b/app/views/RoomView/components/MessageRow.tsx index ec795a1afaa..64ca0c0bb5a 100644 --- a/app/views/RoomView/components/MessageRow.tsx +++ b/app/views/RoomView/components/MessageRow.tsx @@ -6,7 +6,6 @@ import Message from '~/containers/message'; import { getMessageSeparators } from '~/containers/message/utils'; import LoadMore from '../LoadMore'; import { MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad } from '~/lib/constants/messageTypeLoad'; -import { type RoomType } from '~/definitions'; import { useThreadBadgeColor } from '../hooks/useThreadBadgeColor'; import { type TMessageRowProps } from '../definitions'; @@ -15,7 +14,7 @@ const useIsIgnored = (authorId?: string): boolean => export const MessageRow = memo(function MessageRow({ item, previousItem, highlightedMessage, onLongPress }: TMessageRowProps) { const rid = useRoomStore(s => s.room.rid); - const t = useRoomStore(s => s.room.t) as RoomType; + const t = useRoomStore(s => s.room.t); const isIgnored = useIsIgnored(item?.u?._id); const threadBadgeColor = useThreadBadgeColor(item.id); const { lastSeen } = useRoomScreen(); diff --git a/app/views/RoomView/components/RoomMessageList.tsx b/app/views/RoomView/components/RoomMessageList.tsx index edd7de2fca1..ef425e33780 100644 --- a/app/views/RoomView/components/RoomMessageList.tsx +++ b/app/views/RoomView/components/RoomMessageList.tsx @@ -2,7 +2,6 @@ import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useSetting } from '~/lib/hooks/useSetting'; import { isRoomFederated } from '~/lib/methods/isRoomFederated'; import { getUserSelector } from '~/selectors/login'; -import { type RoomType } from '~/definitions'; import { A11yGateProvider } from '~/containers/message/stores/A11yGate'; import { type IRoomMessageListProps } from '../definitions'; import { type TRoomOrPreview } from '~/definitions/TRoom'; @@ -38,7 +37,7 @@ export const RoomMessageList = ({ errorActionsShow }: IRoomMessageListProps) => { const rid = useRoomStore(s => s.room.rid); - const t = useRoomStore(s => s.room.t) as RoomType; + const t = useRoomStore(s => s.room.t); const federated = useRoomStore(fromSubscription(room => isRoomFederated(room), false)); const archived = useRoomStore(fromSubscription(room => !!room.archived, false)); const broadcast = useRoomStore(fromSubscription(room => !!room.broadcast, false)); diff --git a/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx b/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx index d1dfe952c23..f7612967ce5 100644 --- a/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx +++ b/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx @@ -12,11 +12,10 @@ import { useE2EEStatus } from '~/views/RoomView/hooks/useE2EEStatus'; import { EncryptedRoom } from '../EncryptedRoom'; import { InvitedRoomScreen } from '../InvitedRoomScreen'; import { MissingRoomE2EEKey } from '../MissingRoomE2EEKey'; -import { RoomBackground } from '../RoomBackground'; interface IRoomViewContentProps extends IRoomViewProps { - rid?: string; - t?: RoomType; + rid: string; + t: RoomType; tmid?: string; roomStore: RoomStore; ready: boolean; @@ -32,10 +31,6 @@ export const RoomViewContent = ({ route, navigation, rid, t, tmid, roomStore, re const { showMissingE2EEKey, showE2EEDisabledRoom } = useE2EEStatus(roomStore); - if (!rid || !t) { - return ; - } - if (invitation) { return ( { +const NativeRoomHeader = ({ rid, tmid, name, roomStore }: IRoomViewHeaderProps) => { const showsAvatar = useMasterDetail() && !tmid; return ( @@ -26,15 +26,12 @@ const NativeRoomHeader = ({ rid, tmid, name, roomStore }: IRoomViewHeaderProps & const JsRoomHeader = ({ rid, tmid, name, roomStore }: IRoomViewHeaderProps) => { useJsRoomHeader({ rid, tmid, name, roomStore }); - return rid ? : null; + return ; }; export const RoomViewHeader = ({ rid, tmid, name, roomStore }: IRoomViewHeaderProps) => { if (!hasNativeHeaderBar) { return ; } - if (!rid) { - return null; - } return ; }; diff --git a/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx b/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx index 17d009c26ed..2b6e45371fb 100644 --- a/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx +++ b/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx @@ -90,12 +90,6 @@ describe('on the JS header', () => { expect(optionsWith('unstable_headerLeftItems')).toHaveLength(0); }); - it('sets no right actions without a rid', () => { - render(); - - expect(optionsWith('headerRight')).toHaveLength(0); - }); - it('renders only the active mode actions in the JS header buttons', () => { render(); @@ -121,12 +115,6 @@ describe('on the native header bar', () => { expect(optionsWith('headerTitle')).toHaveLength(0); }); - it('leaves the header untouched without a rid', () => { - render(); - - expect(mockSetOptions).not.toHaveBeenCalled(); - }); - it('shows the room actions on the right and an unread back button on the left', () => { render(); @@ -142,7 +130,7 @@ describe('on the native header bar', () => { it.each([ ['room', undefined, {}, useRoomActions], ['thread', 'tmid-1', {}, useThreadActions], - ['omnichannel', undefined, { t: 'l' }, useOmnichannelActions] + ['omnichannel', undefined, { t: 'l' as const }, useOmnichannelActions] ])('mounts only the %s action hook', (_mode, tmid, room, activeHook) => { render(); diff --git a/app/views/RoomView/components/__tests__/RoomMessageActions.test.tsx b/app/views/RoomView/components/__tests__/RoomMessageActions.test.tsx index 49d4284cf1a..804b8d0daf6 100644 --- a/app/views/RoomView/components/__tests__/RoomMessageActions.test.tsx +++ b/app/views/RoomView/components/__tests__/RoomMessageActions.test.tsx @@ -12,7 +12,7 @@ import { type RoomState } from '~/views/RoomView/definitions'; import { RoomStoreContext } from '~/views/RoomView/stores/RoomStoreContext'; import { RoomMessageActions } from '../RoomMessageActions'; -const subRoom = { id: 'sub-1', rid: 'rid-1', t: 'c' }; +const subRoom = { id: 'sub-1', rid: 'rid-1', t: 'c' as const }; const makeRoomStore = () => createStore(() => ({ diff --git a/app/views/RoomView/definitions.ts b/app/views/RoomView/definitions.ts index ca0b24a93d2..17ad7fa9bb5 100644 --- a/app/views/RoomView/definitions.ts +++ b/app/views/RoomView/definitions.ts @@ -24,8 +24,8 @@ import { type MessageRoomState } from '~/containers/message/stores/MessageRoomSt export type IRoomViewProps = Pick, 'navigation' | 'route'>; export interface IRoomScreenInput { - rid?: string; - t?: RoomType; + rid: string; + t: RoomType; tmid?: string; name?: string; initialRoom: TRoomOrPreview; @@ -230,8 +230,8 @@ export type IRoomMessageActionsProps = Pick< }; export interface IUseRoomMessagingParams { - rid?: string; - t?: RoomType; + rid: string; + t: RoomType; tmid?: string; ready: boolean; roomStore: RoomStore; @@ -247,9 +247,9 @@ export interface IUseSubscriptionUnreadsResult { } export interface IUseJumpToMessageParams { - rid?: string; + rid: string; tmid?: string; - t?: RoomType; + t: RoomType; isMasterDetail: boolean; listContainerRef: RefObject; roomUserIdRef: RefObject; diff --git a/app/views/RoomView/hooks/__tests__/useE2EEStatus.test.ts b/app/views/RoomView/hooks/__tests__/useE2EEStatus.test.ts index c44759be1bc..9690e781631 100644 --- a/app/views/RoomView/hooks/__tests__/useE2EEStatus.test.ts +++ b/app/views/RoomView/hooks/__tests__/useE2EEStatus.test.ts @@ -42,7 +42,7 @@ describe('useE2EEStatus', () => { 'derives warnings with session encryption %s, Room encryption %s and key %s', (enabled, encrypted, E2EKey, missing, disabled) => { mockState.encryption.enabled = enabled; - const room = { id: 'rid-1', rid: 'rid-1', t: 'c', encrypted, E2EKey }; + const room = { id: 'rid-1', rid: 'rid-1', t: 'c' as const, encrypted, E2EKey }; const store = createRoomStore(room); expect(renderHook(() => useE2EEStatus(store)).result.current).toEqual({ @@ -54,7 +54,7 @@ describe('useE2EEStatus', () => { ); it('clears the missing-key warning when the same Room receives its key', () => { - const room = { id: 'rid-1', rid: 'rid-1', t: 'c', encrypted: true, E2EKey: undefined as string | undefined }; + const room = { id: 'rid-1', rid: 'rid-1', t: 'c' as const, encrypted: true, E2EKey: undefined as string | undefined }; const store = createRoomStore(room); const { result } = renderHook(() => useE2EEStatus(store)); expect(result.current.hasE2EEWarning).toBe(true); diff --git a/app/views/RoomView/hooks/__tests__/useEmptyRoomHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useEmptyRoomHeader.test.tsx new file mode 100644 index 00000000000..4ac1f9def81 --- /dev/null +++ b/app/views/RoomView/hooks/__tests__/useEmptyRoomHeader.test.tsx @@ -0,0 +1,39 @@ +import { renderHook } from '@testing-library/react-native'; + +import { useEmptyRoomHeader } from '../useEmptyRoomHeader'; + +const mockHelpers = { hasNativeHeaderBar: false }; +jest.mock('~/lib/methods/helpers', () => ({ + get hasNativeHeaderBar() { + return mockHelpers.hasNativeHeaderBar; + } +})); + +const mockSetOptions = jest.fn(); +jest.mock('@react-navigation/native', () => ({ + useNavigation: () => ({ setOptions: mockSetOptions }) +})); + +describe('useEmptyRoomHeader', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('sets only the headerLeft spacer on the JS header', () => { + mockHelpers.hasNativeHeaderBar = false; + renderHook(() => useEmptyRoomHeader()); + + expect(mockSetOptions).toHaveBeenCalledTimes(1); + const options = mockSetOptions.mock.calls[0][0]; + expect(typeof options.headerLeft).toBe('function'); + expect(options).not.toHaveProperty('headerTitle'); + expect(options).not.toHaveProperty('headerRight'); + }); + + it('leaves the native header bar untouched', () => { + mockHelpers.hasNativeHeaderBar = true; + renderHook(() => useEmptyRoomHeader()); + + expect(mockSetOptions).not.toHaveBeenCalled(); + }); +}); diff --git a/app/views/RoomView/hooks/__tests__/useJsRoomHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useJsRoomHeader.test.tsx index 6a2eccc8e49..d96688de316 100644 --- a/app/views/RoomView/hooks/__tests__/useJsRoomHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useJsRoomHeader.test.tsx @@ -47,16 +47,6 @@ describe('useJsRoomHeader', () => { mockTestStore = makeRoomStore(); }); - it('sets only the headerLeft spacer and returns when rid is missing', () => { - renderHook(() => useJsRoomHeader({ rid: undefined, tmid: undefined, name: 'general', roomStore: mockTestStore })); - - expect(mockSetOptions).toHaveBeenCalledTimes(1); - const options = mockSetOptions.mock.calls[0][0]; - expect(typeof options.headerLeft).toBe('function'); - expect(options).not.toHaveProperty('headerTitle'); - expect(options).not.toHaveProperty('headerRight'); - }); - it('re-fires the title effect when a selected field changes on a re-emitted room', () => { mockTestStore = makeRoomStore({ room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', topic: 'old' } as RoomState['room'] diff --git a/app/views/RoomView/hooks/__tests__/useSubscriptionUnreads.test.ts b/app/views/RoomView/hooks/__tests__/useSubscriptionUnreads.test.ts index 4e0a08275ee..7a28b450797 100644 --- a/app/views/RoomView/hooks/__tests__/useSubscriptionUnreads.test.ts +++ b/app/views/RoomView/hooks/__tests__/useSubscriptionUnreads.test.ts @@ -21,7 +21,7 @@ const mockGet = database.active.get as jest.Mock; const mockGetUidDirectMessage = getUidDirectMessage as jest.Mock; let observedStore: any; -const stubRoom = { rid: 'rid-1', t: 'c' }; +const stubRoom = { rid: 'rid-1', t: 'c' as const }; // Emits subscription rows through the rid-keyed RoomStore's observer, which is the only source // the hook reads from. @@ -71,8 +71,8 @@ describe('useSubscriptionUnreads', () => { expect(result.current.isSelfDm).toBe(false); }); - it('falls back to empty unreads without a rid', () => { - const { result } = renderHook(() => useSubscriptionUnreads(createRoomStore({ initialRoom: { rid: '', t: '' } }), 'user-1')); + it('falls back to empty unreads for a preview room', () => { + const { result } = renderHook(() => useSubscriptionUnreads(createRoomStore({ initialRoom: stubRoom }), 'user-1')); expect(result.current.tunread).toEqual([]); }); diff --git a/app/views/RoomView/hooks/useEmptyRoomHeader.tsx b/app/views/RoomView/hooks/useEmptyRoomHeader.tsx new file mode 100644 index 00000000000..3f98aca8e66 --- /dev/null +++ b/app/views/RoomView/hooks/useEmptyRoomHeader.tsx @@ -0,0 +1,18 @@ +import { useLayoutEffect } from 'react'; +import { PixelRatio, View } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; + +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; +import { type IRoomViewProps } from '../definitions'; + +export const useEmptyRoomHeader = (): void => { + const navigation = useNavigation(); + + useLayoutEffect(() => { + if (hasNativeHeaderBar) { + return; + } + const height = 37 * PixelRatio.getFontScale(); + navigation.setOptions({ headerLeft: () => }); + }, [navigation]); +}; diff --git a/app/views/RoomView/hooks/useGoRoomActionsView.ts b/app/views/RoomView/hooks/useGoRoomActionsView.ts index cfc36b2b74d..2437087fe9c 100644 --- a/app/views/RoomView/hooks/useGoRoomActionsView.ts +++ b/app/views/RoomView/hooks/useGoRoomActionsView.ts @@ -4,7 +4,7 @@ import { useShallow } from 'zustand/react/shallow'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; -import type { ISubscription, RoomType, TSubscriptionModel } from '~/definitions'; +import type { ISubscription, TSubscriptionModel } from '~/definitions'; import { type TNavigation } from '~/stacks/stackType'; import { type ModalStackParamList } from '~/stacks/MasterDetailStack/types'; import { type IRoomViewProps } from '../definitions'; @@ -36,8 +36,8 @@ export const useGoRoomActionsView = (roomStore: RoomStore): ((screen?: keyof Mod logEvent(events.ROOM_GO_RA); const room = roomStore.getState().room; const params = { - rid: rid as string, - t: t as RoomType, + rid, + t, member, joined: membership === 'subscribed', omnichannelPermissions diff --git a/app/views/RoomView/hooks/useJsRoomHeader.tsx b/app/views/RoomView/hooks/useJsRoomHeader.tsx index 91498e208fc..735a8532090 100644 --- a/app/views/RoomView/hooks/useJsRoomHeader.tsx +++ b/app/views/RoomView/hooks/useJsRoomHeader.tsx @@ -1,5 +1,4 @@ import { useLayoutEffect } from 'react'; -import { PixelRatio, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { useStore } from 'zustand'; @@ -10,7 +9,7 @@ import { useGoRoomActionsView } from './useGoRoomActionsView'; import { useHeaderFields } from './useHeaderFields'; interface IUseJsRoomHeaderParams { - rid?: string; + rid: string; tmid?: string; name?: string; roomStore: RoomStore; @@ -23,20 +22,10 @@ export const useJsRoomHeader = ({ rid, tmid, name: threadName, roomStore }: IUse const goRoomActionsView = useGoRoomActionsView(roomStore); useLayoutEffect(() => { - if (!rid) { - const height = 37 * PixelRatio.getFontScale(); - navigation.setOptions({ headerLeft: () => }); - return; - } - navigation.setOptions({ headerLeft: () => }); }, [rid, tmid, navigation, roomStore]); useLayoutEffect(() => { - if (!rid) { - return; - } - navigation.setOptions({ headerTitle: () => ( { useShallow(s => { const room = s.room; return { - t: room.t as RoomType, + t: room.t, status: fromSubscription(r => r.status, undefined)(s), roomName: getRoomTitle(room), roomIsGroupChat: fromSubscription(isGroupChat, false)(s), diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index a4e6e368a1d..1edbe8fc89e 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -1,15 +1,14 @@ import { useEffect, useState } from 'react'; -import { type IRoomViewProps } from './definitions'; +import { type IRoomScreenInput, type IRoomViewProps, type RoomStore } from './definitions'; +import { EmptyRoomView } from './components/EmptyRoomView'; import { RoomViewContent } from './components/RoomViewContent/RoomViewContent'; import { RoomViewHeader } from './components/RoomViewHeader/RoomViewHeader'; import { parseRoomRoute } from './services/parseRoomRoute'; import { createRoomStore, observeRoom } from './stores/RoomStore'; -import { type RoomStore } from './definitions'; -const RoomView = ({ route, navigation }: IRoomViewProps) => { - const [mountedRouteParams] = useState(() => parseRoomRoute(route.params)); - const { rid, t, tmid, name, initialRoom, roomUserId } = mountedRouteParams; +const OpenRoomView = ({ route, navigation, mountedRoute }: IRoomViewProps & { mountedRoute: IRoomScreenInput }) => { + const { rid, t, tmid, name, initialRoom, roomUserId } = mountedRoute; const [roomStore] = useState(() => createRoomStore({ rid, initialRoom, roomUserId })); const [ready, setReady] = useState(false); @@ -23,4 +22,13 @@ const RoomView = ({ route, navigation }: IRoomViewProps) => { ); }; +const RoomView = ({ route, navigation }: IRoomViewProps) => { + const [mountedRoute] = useState(() => (route.params ? parseRoomRoute(route.params) : null)); + + if (!mountedRoute) { + return ; + } + return ; +}; + export default RoomView; diff --git a/app/views/RoomView/services/openThread.ts b/app/views/RoomView/services/openThread.ts index 3b1ab36140c..3c42946f360 100644 --- a/app/views/RoomView/services/openThread.ts +++ b/app/views/RoomView/services/openThread.ts @@ -12,8 +12,8 @@ export type TOpenThreadTarget = TAnyMessageModel | { tmid: string } | TGetMessag interface IOpenThreadDeps { navigation: IRoomViewProps['navigation']; - rid?: string; - t?: RoomType; + rid: string; + t: RoomType; roomUserId?: string | null; cancelJumpToMessage: () => void; } @@ -25,9 +25,6 @@ export const openThread = async ( message: TOpenThreadTarget, { navigation, rid, t, roomUserId, cancelJumpToMessage }: IOpenThreadDeps ): Promise => { - if (!rid || !t) { - return; - } const { tmid } = message; if (!tmid) { if ('tlm' in message) { diff --git a/app/views/RoomView/services/parseRoomRoute.ts b/app/views/RoomView/services/parseRoomRoute.ts index 2cfddda4f4c..55b356e70dd 100644 --- a/app/views/RoomView/services/parseRoomRoute.ts +++ b/app/views/RoomView/services/parseRoomRoute.ts @@ -2,8 +2,8 @@ import { getUidDirectMessage } from '~/lib/methods/helpers'; import { type IRoomScreenInput, type IRoomViewProps } from '../definitions'; import { type TRoomOrPreview } from '~/definitions/TRoom'; -export const parseRoomRoute = (params: IRoomViewProps['route']['params']): IRoomScreenInput => { - const { rid, t, tmid, name, fname, prid, visitor, joinCodeRequired, roomUserId } = params ?? {}; - const initialRoom: TRoomOrPreview = { rid: rid ?? '', t: t ?? '', name, fname, prid, visitor, joinCodeRequired }; +export const parseRoomRoute = (params: NonNullable): IRoomScreenInput => { + const { rid, t, tmid, name, fname, prid, visitor, joinCodeRequired, roomUserId } = params; + const initialRoom: TRoomOrPreview = { rid, t, name, fname, prid, visitor, joinCodeRequired }; return { rid, t, tmid, name, initialRoom, roomUserId: roomUserId ?? getUidDirectMessage(initialRoom) }; }; diff --git a/app/views/RoomView/stores/RoomStore.ts b/app/views/RoomView/stores/RoomStore.ts index 7c0aab66204..199a3dd520e 100644 --- a/app/views/RoomView/stores/RoomStore.ts +++ b/app/views/RoomView/stores/RoomStore.ts @@ -10,7 +10,7 @@ import { getUserInfo } from '~/lib/services/restApi'; import { isGroupChat, getUidDirectMessage, canAutoTranslate as canAutoTranslateMethod } from '~/lib/methods/helpers'; import log from '~/lib/methods/helpers/log'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; -import { type RoomType, type TSubscriptionModel } from '~/definitions'; +import { type TSubscriptionModel } from '~/definitions'; import { type TRoomOrPreview, isSubscriptionModel } from '~/definitions/TRoom'; import { type IRoomStoreInitParams, @@ -22,7 +22,6 @@ import { import getMessages from '../services/getMessages'; import { joinRoom, resumeRoom } from '../services/joinRoom'; -const EMPTY_ROOM: TRoomOrPreview = { rid: '', t: '' }; const EMPTY_MEMBER: RoomState['member'] = {}; const INIT_MAX_ATTEMPTS = 3; @@ -85,7 +84,7 @@ const loadRoom = async ( } else { await getMessages({ rid: room.rid, - ...(isSubscriptionModel(room) && room.lastOpen ? {} : { t: room.t as RoomType }) + ...(isSubscriptionModel(room) && room.lastOpen ? {} : { t: room.t }) }); if (isAborted()) { return { status: 'skipped' }; @@ -195,7 +194,7 @@ export function observeRoom(rid: string | undefined, store: RoomStore, onReady?: export const createRoomStore = ({ rid, - initialRoom = EMPTY_ROOM, + initialRoom, roomUserId = null }: { rid?: string; diff --git a/app/views/RoomView/stores/__tests__/RoomStore.test.ts b/app/views/RoomView/stores/__tests__/RoomStore.test.ts index 38e81bb91cf..bdd0d245410 100644 --- a/app/views/RoomView/stores/__tests__/RoomStore.test.ts +++ b/app/views/RoomView/stores/__tests__/RoomStore.test.ts @@ -47,8 +47,8 @@ const mockIsGroupChat = isGroupChat as jest.Mock; const mockIsInviteSubscription = isInviteSubscription as unknown as jest.Mock; const mockLog = log as jest.Mock; -const stubRoom = { rid: 'rid-1', t: 'c' }; -const subRoom = { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general' }; +const stubRoom = { rid: 'rid-1', t: 'c' as const }; +const subRoom = { id: 'rid-1', rid: 'rid-1', t: 'c' as const, name: 'general' }; const flush = () => Promise.resolve().then(() => Promise.resolve()); @@ -117,7 +117,7 @@ describe('RoomStore', () => { }); it('leaves a DM room untouched when its record is destroyed', async () => { - const dmRow = { ...subRoom, t: 'd' }; + const dmRow = { ...subRoom, t: 'd' as const }; const { record, emit, destroy } = setupPresentRow(dmRow); const store = createRoomStore({ rid: 'rid-1', initialRoom: { ...stubRoom, t: 'd' } }); observeRoom('rid-1', store); @@ -211,7 +211,7 @@ describe('RoomStore', () => { expect(store.getState().membership).toBe('subscribed'); }); - it.each(['c', 'd'])('preserves record completion after query handoff for room type %s', async t => { + it.each(['c', 'd'] as const)('preserves record completion after query handoff for room type %s', async t => { const { emitRows } = setupAbsentThenPresentRow(); const store = createRoomStore({ rid: 'rid-1', initialRoom: { ...stubRoom, t } }); observeRoom('rid-1', store); @@ -301,7 +301,7 @@ describe('RoomStore', () => { it('fetches the DM member and sets roomUserId on success', async () => { setupPresentRow(); mockGetUserInfo.mockResolvedValue({ success: true, user: { _id: 'uid-1', username: 'alice' } }); - const dmRoom = { ...subRoom, t: 'd' }; + const dmRoom = { ...subRoom, t: 'd' as const }; const store = createRoomStore({ rid: 'rid-1', initialRoom: dmRoom }); await store.getState().init(); @@ -319,7 +319,7 @@ describe('RoomStore', () => { resolveUserInfo = resolve; }) ); - const dmRoom = { ...subRoom, t: 'd' }; + const dmRoom = { ...subRoom, t: 'd' as const }; const store = createRoomStore({ rid: 'rid-1', initialRoom: dmRoom }); const initPromise = store.getState().init(); @@ -410,10 +410,10 @@ describe('RoomStore', () => { expect(mockGetMessages).toHaveBeenCalledTimes(3); }); - it('retries against the room the observer delivered after the first attempt failed on an empty store', async () => { + it('retries against the room the observer delivered after the first attempt failed on a preview room', async () => { const { emit } = setupPresentRow(); mockGetMessages.mockRejectedValueOnce(new Error('boom')); - const store = createRoomStore({ rid: 'rid-1', initialRoom: { rid: '', t: '' } }); + const store = createRoomStore({ rid: 'rid-1', initialRoom: { ...stubRoom, t: 'p' } }); observeRoom('rid-1', store); await flush(); @@ -424,7 +424,6 @@ describe('RoomStore', () => { await expect(initPromise).resolves.toEqual({ status: 'loaded', lastSeen: null }); expect(mockGetMessages).toHaveBeenCalledTimes(2); - // The retry used the room the observer delivered, not the empty snapshot init started with. expect(mockGetMessages).toHaveBeenLastCalledWith({ rid: 'rid-1', t: 'c' }); }); diff --git a/app/views/RoomView/stores/__tests__/RoomStoreContext.test.tsx b/app/views/RoomView/stores/__tests__/RoomStoreContext.test.tsx index 4438d808c78..16a65604ee0 100644 --- a/app/views/RoomView/stores/__tests__/RoomStoreContext.test.tsx +++ b/app/views/RoomView/stores/__tests__/RoomStoreContext.test.tsx @@ -4,7 +4,7 @@ import { createStore } from 'zustand'; import { type RoomState, type RoomStore } from '~/views/RoomView/definitions'; import { RoomStoreContext, useRoomStore } from '../RoomStoreContext'; -const subRoom = { id: 'sub-1', rid: 'rid-1', t: 'c', topic: 'old', name: 'general' }; +const subRoom = { id: 'sub-1', rid: 'rid-1', t: 'c' as const, topic: 'old', name: 'general' }; const makeRoomStore = (): RoomStore => createStore(() => ({