From 41823b80bd868595b9fa19444f789fc8f58f8f4e Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:04:13 +0530 Subject: [PATCH 01/63] fix: pass tmid to show typing status in threads (#6894) --- app/actions/room.ts | 10 ++++++++-- .../MessageComposer/components/ComposerInput.tsx | 2 +- app/lib/services/restApi.ts | 4 ++-- app/sagas/room.js | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/actions/room.ts b/app/actions/room.ts index 1ac3b0f4907..a4811411344 100644 --- a/app/actions/room.ts +++ b/app/actions/room.ts @@ -39,9 +39,14 @@ interface IForwardRoom extends Action { rid: string; } +type IUserTypingArgs = { + tmid?: string; +}; + interface IUserTyping extends Action { rid: string; status: boolean; + args?: IUserTypingArgs; } export interface IRoomHistoryRequest extends Action { @@ -109,11 +114,12 @@ export function removedRoom(): Action { }; } -export function userTyping(rid: string, status = true): IUserTyping { +export function userTyping(rid: string, status = true, args?: IUserTypingArgs): IUserTyping { return { type: ROOM.USER_TYPING, rid, - status + status, + args }; } diff --git a/app/containers/MessageComposer/components/ComposerInput.tsx b/app/containers/MessageComposer/components/ComposerInput.tsx index 178ca3b58b1..d1835ef4abd 100644 --- a/app/containers/MessageComposer/components/ComposerInput.tsx +++ b/app/containers/MessageComposer/components/ComposerInput.tsx @@ -361,7 +361,7 @@ export const ComposerInput = memo( const handleTyping = (isTyping: boolean) => { if (sharing || !rid) return; - dispatch(userTyping(rid, isTyping)); + dispatch(userTyping(rid, isTyping, tmid ? { tmid } : {})); }; return ( diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index f38c096c315..95ad3f86c7e 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -950,14 +950,14 @@ export const addUsersToRoom = (rid: string): Promise => { return sdk.methodCallWrapper('addUsersToRoom', { rid, users }); }; -export const emitTyping = (room: IRoom, typing = true) => { +export const emitTyping = (room: IRoom, typing = true, args: { tmid?: string } = {}) => { const { login, settings, server } = reduxStore.getState(); const { UI_Use_Real_Name } = settings; const { version: serverVersion } = server; const { user } = login; const name = UI_Use_Real_Name ? user.name : user.username; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.0.0')) { - return sdk.methodCall('stream-notify-room', `${room}/user-activity`, name, typing ? ['user-typing'] : []); + return sdk.methodCall('stream-notify-room', `${room}/user-activity`, name, typing ? ['user-typing'] : [], args); } return sdk.methodCall('stream-notify-room', `${room}/typing`, name, typing); }; diff --git a/app/sagas/room.js b/app/sagas/room.js index 71bd73c717a..9fb160695f0 100644 --- a/app/sagas/room.js +++ b/app/sagas/room.js @@ -51,10 +51,10 @@ const clearInactiveTyping = function* clearInactiveTyping({ rid }) { yield clearUserTyping({ rid, status: false }); }; -const watchUserTyping = function* watchUserTyping({ rid, status }) { +const watchUserTyping = function* watchUserTyping({ rid, status, args }) { try { if (status) { - yield emitTyping(rid, status); + yield emitTyping(rid, status, args); if (inactiveTypingTask) { yield cancel(inactiveTypingTask); } From ad951f9555e9418e8fc12b11cd5557226cd9e148 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:09:52 +0530 Subject: [PATCH 02/63] fix: timestamp bug fixes (#6910) --- .../markdown/components/Timestamp.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/containers/markdown/components/Timestamp.tsx b/app/containers/markdown/components/Timestamp.tsx index 31731c322a3..da760661b3c 100644 --- a/app/containers/markdown/components/Timestamp.tsx +++ b/app/containers/markdown/components/Timestamp.tsx @@ -14,44 +14,44 @@ interface ITimestampProps { const Timestamp = ({ value }: ITimestampProps): React.ReactElement => { const { colors } = useTheme(); - const formatDate = React.useMemo(() => { - const timestamp = parseInt(value.timestamp) * 1000; + const timestampMs = React.useMemo(() => parseInt(value.timestamp, 10) * 1000, [value.timestamp]); + const formatDate = React.useMemo(() => { if (value.format === 't') { - return dayjs(timestamp).format('hh:mm A'); + return dayjs(timestampMs).format('hh:mm A'); } if (value.format === 'T') { - return dayjs(timestamp).format('hh:mm:ss A'); + return dayjs(timestampMs).format('hh:mm:ss A'); } if (value.format === 'd') { - return dayjs(timestamp).format('MM/DD/YYYY'); + return dayjs(timestampMs).format('MM/DD/YYYY'); } if (value.format === 'D') { - return dayjs(timestamp).format('dddd, MMM DD, YYYY'); + return dayjs(timestampMs).format('dddd, MMM DD, YYYY'); } if (value.format === 'f') { - return dayjs(timestamp).format('dddd, MMM DD, YYYY hh:mm A'); + return dayjs(timestampMs).format('dddd, MMM DD, YYYY hh:mm A'); } if (value.format === 'F') { - return dayjs(timestamp).format('dddd, MMM DD, YYYY hh:mm:ss A'); + return dayjs(timestampMs).format('dddd, MMM DD, YYYY hh:mm:ss A'); } if (value.format === 'R') { - return dayjs(timestamp).fromNow(); + return dayjs(timestampMs).fromNow(); } return 'Invalid Date'; - }, [value]); + }, [timestampMs, value.format]); const handlePress = React.useCallback(() => { - const message = dayjs(parseInt(value.timestamp) * 1000).format('dddd, MMM DD, YYYY hh:mm A'); + const message = dayjs(timestampMs).format('dddd, MMM DD, YYYY hh:mm A'); EventEmitter.emit(LISTENER, { message }); - }, [value.timestamp]); + }, [timestampMs]); return ( Date: Fri, 16 Jan 2026 13:13:41 +0530 Subject: [PATCH 03/63] fix: chat fails to load older messages when scrolling to the top (#6861) --- app/lib/methods/loadMessagesForRoom.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/lib/methods/loadMessagesForRoom.ts b/app/lib/methods/loadMessagesForRoom.ts index 747b8b2aad2..10aa131ec5e 100644 --- a/app/lib/methods/loadMessagesForRoom.ts +++ b/app/lib/methods/loadMessagesForRoom.ts @@ -9,7 +9,6 @@ import updateMessages from './updateMessages'; import { generateLoadMoreId } from './helpers/generateLoadMoreId'; const COUNT = 50; -const COUNT_LIMIT = COUNT * 10; async function load({ rid: roomId, latest, t }: { rid: string; latest?: Date; t: RoomTypes }): Promise { const apiType = roomTypeToApiType(t); @@ -21,11 +20,11 @@ async function load({ rid: roomId, latest, t }: { rid: string; latest?: Date; t: let mainMessagesCount = 0; async function fetchBatch(lastTs?: string): Promise { - if (allMessages.length >= COUNT_LIMIT) { + if (allMessages.length >= COUNT) { return; } - const params = { roomId, count: COUNT, ...(lastTs && { latest: lastTs }) }; + const params = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) }; let data; switch (apiType) { @@ -77,7 +76,7 @@ export function loadMessagesForRoom(args: { if (data?.length) { const lastMessage = data[data.length - 1]; const lastMessageRecord = await getMessageById(lastMessage._id as string); - if (!lastMessageRecord && (data.length === COUNT || data.length >= COUNT_LIMIT)) { + if (!lastMessageRecord && data.length === COUNT) { const loadMoreMessage = { _id: generateLoadMoreId(lastMessage._id as string), rid: lastMessage.rid, From c52af0481d8dcbf3071789b023c75df27d2472b7 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:25:35 +0530 Subject: [PATCH 04/63] fix: Keep connecting label on header while logging in (#6920) --- app/views/RoomsListView/components/Header.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/RoomsListView/components/Header.tsx b/app/views/RoomsListView/components/Header.tsx index 6d56a9582f0..3f6ff738f90 100644 --- a/app/views/RoomsListView/components/Header.tsx +++ b/app/views/RoomsListView/components/Header.tsx @@ -36,6 +36,7 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string) const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading); const connected = useAppSelector(state => state.meteor.connected); + const isLoggingIn = useAppSelector(state => state.login.isFetching); const isFetching = useAppSelector(state => state.rooms.isFetching); const serverName = useAppSelector(state => state.settings.Site_Name as string); const server = useAppSelector(state => state.server.server); @@ -55,7 +56,7 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string) let subtitle; if (supportedVersionsStatus === 'expired') { subtitle = 'Cannot connect'; - } else if (connecting) { + } else if (connecting || isLoggingIn) { subtitle = I18n.t('Connecting'); } else if (isFetching) { subtitle = I18n.t('Updating'); From 7b483e6296eb8019fe156d8022d20cb9b27eaafc Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:56:18 +0530 Subject: [PATCH 05/63] chore: changelog in beta release (#6899) --- .github/actions/upload-android/action.yml | 28 ++++++++++++++- .github/actions/upload-ios/action.yml | 8 ++++- .github/workflows/build-develop.yml | 13 ++++--- .github/workflows/build-official-android.yml | 4 +-- .github/workflows/generate-changelog.yml | 38 ++++++++++++++++++++ android/fastlane/Fastfile | 6 +++- ios/fastlane/Fastfile | 28 ++++++++++++--- 7 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/generate-changelog.yml diff --git a/.github/actions/upload-android/action.yml b/.github/actions/upload-android/action.yml index 7f1fb418089..0452a2832b7 100644 --- a/.github/actions/upload-android/action.yml +++ b/.github/actions/upload-android/action.yml @@ -53,6 +53,33 @@ runs: echo "${{ inputs.FASTLANE_GOOGLE_SERVICE_ACCOUNT }}" | base64 --decode > service_account.json shell: bash + - uses: actions/download-artifact@v4 + if: ${{ inputs.trigger == 'develop' }} + with: + name: release-changelog + path: . + + - name: Prepare Play Store changelog metadata + if: ${{ inputs.trigger == 'develop' }} + run: | + mkdir -p android/fastlane/metadata/android/en-US/changelogs + + if [ -f changelog.txt ]; then + char_count=$(wc -m < changelog.txt) + + if [ "$char_count" -gt 500 ]; then + cut -c1-497 changelog.txt > "android/fastlane/metadata/android/en-US/changelogs/${BUILD_VERSION}.txt" + printf "..." >> "android/fastlane/metadata/android/en-US/changelogs/${BUILD_VERSION}.txt" + else + cat changelog.txt > "android/fastlane/metadata/android/en-US/changelogs/${BUILD_VERSION}.txt" + fi + else + printf "Internal improvements and bug fixes" > "android/fastlane/metadata/android/en-US/changelogs/${BUILD_VERSION}.txt" + fi + shell: bash + env: + BUILD_VERSION: ${{ inputs.BUILD_VERSION }} + - name: Fastlane Play Store Upload working-directory: android run: | @@ -65,7 +92,6 @@ runs: if [[ ${{ inputs.trigger }} == "develop" ]] && [[ ${{ inputs.type }} == 'official' ]]; then bundle exec fastlane android official_open_testing fi - shell: bash - name: Leave a comment on PR diff --git a/.github/actions/upload-ios/action.yml b/.github/actions/upload-ios/action.yml index 68f5d4539d4..4deefc781a2 100644 --- a/.github/actions/upload-ios/action.yml +++ b/.github/actions/upload-ios/action.yml @@ -109,6 +109,12 @@ runs: yarn pod-install shell: bash + - uses: actions/download-artifact@v4 + if: ${{ inputs.type == 'official' && inputs.trigger == 'develop' }} + with: + name: release-changelog + path: . + - name: Fastlane Submit to TestFlight working-directory: ios run: | @@ -157,4 +163,4 @@ runs: message="**iOS Build Available**"$'\n\n'"$app_name $VERSION_NAME.$BUILD_VERSION" gh pr comment "$PR_NUMBER" --body "$message" - shell: bash \ No newline at end of file + shell: bash diff --git a/.github/workflows/build-develop.yml b/.github/workflows/build-develop.yml index 48ffc62e564..b8be5d7ef33 100644 --- a/.github/workflows/build-develop.yml +++ b/.github/workflows/build-develop.yml @@ -14,11 +14,16 @@ jobs: if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }} uses: ./.github/workflows/eslint.yml + generate-changelog: + name: Generate Release Changelog + needs: [run-eslint-and-test] + uses: ./.github/workflows/generate-changelog.yml + android-build-experimental-store: name: Build Android Experimental if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }} uses: ./.github/workflows/build-android.yml - needs: [run-eslint-and-test] + needs: [run-eslint-and-test, generate-changelog] secrets: inherit with: type: experimental @@ -28,7 +33,7 @@ jobs: name: Build Android Official if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }} uses: ./.github/workflows/build-official-android.yml - needs: [run-eslint-and-test] + needs: [run-eslint-and-test, generate-changelog] secrets: inherit with: type: official @@ -38,7 +43,7 @@ jobs: name: Build iOS Experimental if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }} uses: ./.github/workflows/build-ios.yml - needs: [run-eslint-and-test] + needs: [run-eslint-and-test, generate-changelog] secrets: inherit with: type: experimental @@ -48,7 +53,7 @@ jobs: name: Build iOS Official if: ${{ github.repository == 'RocketChat/Rocket.Chat.ReactNative' }} uses: ./.github/workflows/build-official-ios.yml - needs: [run-eslint-and-test] + needs: [run-eslint-and-test, generate-changelog] secrets: inherit with: type: official diff --git a/.github/workflows/build-official-android.yml b/.github/workflows/build-official-android.yml index 76d195085b0..226c50ff26a 100644 --- a/.github/workflows/build-official-android.yml +++ b/.github/workflows/build-official-android.yml @@ -72,7 +72,7 @@ jobs: upload-android: name: Upload runs-on: ubuntu-latest - needs: [upload-hold] + needs: [build-android, upload-hold] if: ${{ inputs.type == 'official' && (always() && (needs.upload-hold.result == 'success' || needs.upload-hold.result == 'skipped')) }} steps: - name: Checkout Repository @@ -85,7 +85,7 @@ jobs: trigger: ${{ inputs.trigger }} FASTLANE_GOOGLE_SERVICE_ACCOUNT: ${{ secrets.FASTLANE_GOOGLE_SERVICE_ACCOUNT }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_VERSION: ${{ needs.upload-hold.outputs.BUILD_VERSION }} + BUILD_VERSION: ${{ needs.build-android.outputs.BUILD_VERSION }} upload-internal: name: Internal Sharing diff --git a/.github/workflows/generate-changelog.yml b/.github/workflows/generate-changelog.yml new file mode 100644 index 00000000000..af729951373 --- /dev/null +++ b/.github/workflows/generate-changelog.yml @@ -0,0 +1,38 @@ +name: Generate Release Changelog + +on: + workflow_call: + +jobs: + generate-changelog: + name: Generate changelog + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate changelog + shell: bash + run: | + LATEST_RELEASE_TAG=$(git tag --sort=-creatordate | head -n 1) + + if [ -z "$LATEST_RELEASE_TAG" ]; then + echo "- Improvements and bug fixes" > changelog.txt + exit 0 + fi + + git log "$LATEST_RELEASE_TAG"..HEAD --pretty=format:"- %s" --no-merges > changelog.txt + + if [ ! -s changelog.txt ]; then + echo "- Improvements and bug fixes" > changelog.txt + fi + + - name: Upload changelog artifact + uses: actions/upload-artifact@v4 + with: + name: release-changelog + path: changelog.txt + retention-days: 15 diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index b89435389ce..1448a1173f0 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -78,7 +78,11 @@ platform :android do upload_to_play_store( package_name: 'chat.rocket.android', track: 'beta', - aab: 'app/build/outputs/bundle/officialRelease/app-official-release.aab' + aab: 'app/build/outputs/bundle/officialRelease/app-official-release.aab', + skip_upload_metadata: true, + skip_upload_changelogs: false, + skip_upload_images: true, + skip_upload_screenshots: true ) end end diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 80a43c7f587..6f52b0e2f6b 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -29,17 +29,35 @@ platform :ios do desc "Submit a new Beta Build to Apple TestFlight" lane :beta do |options| + changelog_path = File.expand_path('../../changelog.txt', __dir__) + + changelog = if File.exist?(changelog_path) + content = File.read(changelog_path) + content.length > 4000 ? content[0, 3997] + "..." : content + end + api_key = app_store_connect_api_key( key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"], issuer_id: ENV["APP_STORE_CONNECT_API_KEY_ISSUER_ID"], key_filepath: 'fastlane/app_store_connect_api_key.p8', in_house: false ) - pilot( - ipa: 'Rocket.Chat.ipa', - app_identifier: options[:official] ? 'chat.rocket.ios' : 'chat.rocket.reactnative', - skip_waiting_for_build_processing: true - ) + + pilot_options = { + ipa: 'Rocket.Chat.ipa', + app_identifier: options[:official] ? 'chat.rocket.ios' : 'chat.rocket.reactnative', + skip_waiting_for_build_processing: !(options[:official] && changelog), + reject_build_waiting_for_review: true, + } + + if options[:official] && changelog + pilot_options[:changelog] = changelog + pilot_options[:distribute_external] = true + pilot_options[:notify_external_testers] = true + pilot_options[:groups] = ["External Testers"] + end + + pilot(pilot_options) upload_symbols_to_crashlytics(dsym_path: "Rocket.Chat.app.dSYM.zip") upload_symbols_to_bugsnag( config_file: "RocketChatRN/Info.plist", From ad2f552c8f6ed4e8d424833a8f9c70b726b61112 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:29:33 +0530 Subject: [PATCH 06/63] chore: added concurrency on develop action (#6921) --- .github/workflows/build-develop.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-develop.yml b/.github/workflows/build-develop.yml index b8be5d7ef33..5e9969daf74 100644 --- a/.github/workflows/build-develop.yml +++ b/.github/workflows/build-develop.yml @@ -8,6 +8,10 @@ on: branches: - 'develop' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: run-eslint-and-test: name: ESLint and Test From c079de04f151b677dee1fb5c2ae992e09c65ec3f Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:27:49 +0530 Subject: [PATCH 07/63] chore(Android): fix CI generated changelog (#6922) --- .github/actions/upload-android/action.yml | 9 +-------- .github/scripts/prepare-changelog.js | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 .github/scripts/prepare-changelog.js diff --git a/.github/actions/upload-android/action.yml b/.github/actions/upload-android/action.yml index 0452a2832b7..aafeccf437f 100644 --- a/.github/actions/upload-android/action.yml +++ b/.github/actions/upload-android/action.yml @@ -65,14 +65,7 @@ runs: mkdir -p android/fastlane/metadata/android/en-US/changelogs if [ -f changelog.txt ]; then - char_count=$(wc -m < changelog.txt) - - if [ "$char_count" -gt 500 ]; then - cut -c1-497 changelog.txt > "android/fastlane/metadata/android/en-US/changelogs/${BUILD_VERSION}.txt" - printf "..." >> "android/fastlane/metadata/android/en-US/changelogs/${BUILD_VERSION}.txt" - else - cat changelog.txt > "android/fastlane/metadata/android/en-US/changelogs/${BUILD_VERSION}.txt" - fi + node .github/scripts/prepare-changelog.js else printf "Internal improvements and bug fixes" > "android/fastlane/metadata/android/en-US/changelogs/${BUILD_VERSION}.txt" fi diff --git a/.github/scripts/prepare-changelog.js b/.github/scripts/prepare-changelog.js new file mode 100644 index 00000000000..9b31db56352 --- /dev/null +++ b/.github/scripts/prepare-changelog.js @@ -0,0 +1,20 @@ +const fs = require("fs"); + +const buildVersion = process.env.BUILD_VERSION; +const input = fs.readFileSync("changelog.txt", "utf8"); + +const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" }); +const chars = Array.from(segmenter.segment(input), s => s.segment); + +let output; +if (chars.length > 500) { + output = chars.slice(0, 497).join("") + "..."; +} else { + output = input; +} + +fs.writeFileSync( + `android/fastlane/metadata/android/en-US/changelogs/${buildVersion}.txt`, + output, + "utf8" +); From 12fb458abd592e5ac2fb5c9fa54def7b67c9f9b5 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 18 Jan 2026 20:11:01 +0530 Subject: [PATCH 08/63] fix (android): unable to send files in E2EE channel using action menu (#6919) --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5f334cbad37..4a19e871830 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5024,8 +5024,8 @@ tldts "~5.7.112" "@rocket.chat/mobile-crypto@RocketChat/rocket.chat-mobile-crypto": - version "0.2.0" - resolved "https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/06bab0cb2e329822911c80d1b937219c15154faa" + version "0.2.1" + resolved "https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/b75e261282bc0c25a3b8fde3230d1c1e01809e00" "@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile": version "1.3.3-mobile" From 2afa9ee51312b12a60f377a5d878003d55218f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Stasiak?= <91474186+OtavioStasiak@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:29:40 -0300 Subject: [PATCH 09/63] fix(iOS): app crashing on render image (#6908) * fix: formatting base64 images wrong * chore: add base64 avatar storybook case * chore: improve unit tests of getAvatarUrl * chore: format code and fix lint issues [skip ci] * fix: unit test * cleanup --------- Co-authored-by: OtavioStasiak --- app/containers/Avatar/Avatar.stories.tsx | 4 + .../Avatar/__snapshots__/Avatar.test.tsx.snap | 55 ++++++ app/lib/methods/helpers/getAvatarUrl.test.ts | 182 +++++++++++++++++- app/lib/methods/helpers/getAvatarUrl.ts | 3 + 4 files changed, 243 insertions(+), 1 deletion(-) diff --git a/app/containers/Avatar/Avatar.stories.tsx b/app/containers/Avatar/Avatar.stories.tsx index ed07c9d408c..0a2503e3c7d 100644 --- a/app/containers/Avatar/Avatar.stories.tsx +++ b/app/containers/Avatar/Avatar.stories.tsx @@ -12,6 +12,8 @@ const styles = StyleSheet.create({ }); const server = 'https://open.rocket.chat'; +const base64Image = + 'data:image/png;base64,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'; export default { title: 'Avatar' @@ -31,6 +33,8 @@ export const AvatarUrl = () => ( export const AvatarPath = () => ; +export const AvatarBase64 = () => ; + export const WithETag = () => ( ); diff --git a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap index 97d9de5ea35..0a0d7df4ae6 100644 --- a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap +++ b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap @@ -1,5 +1,60 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Story Snapshots: AvatarBase64 should match snapshot 1`] = ` + + + +`; + exports[`Story Snapshots: AvatarExternalProviderUrl should match snapshot 1`] = ` ({ PixelRatio: { get: () => 1 } })); +jest.mock('./compareServerVersion', () => ({ + compareServerVersion: jest.fn() +})); + +const mockCompareServerVersion = compareServerVersion as jest.MockedFunction; describe('formatUrl function', () => { test('formats the default URL to get the user avatar', () => { @@ -30,3 +37,176 @@ describe('formatUrl function', () => { expect(result).toEqual(expected); }); }); + +describe('getAvatarURL function', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('returns the avatar unchanged when it is a base64 data URI', () => { + const avatar = 'data:image/png;base64,ABC123'; + + const expected = avatar; + const result = getAvatarURL({ avatar }); + expect(result).toEqual(expected); + }); + + test('returns the avatar unchanged when it starts with http', () => { + const avatar = 'https://example.com/avatar.png'; + const server = 'https://mobile.qa.rocket.chat'; + + const expected = avatar; + const result = getAvatarURL({ avatar, server }); + expect(result).toEqual(expected); + }); + + test('formats avatar URL with server when avatar does not start with http', () => { + const avatar = '/avatar/user123'; + const server = 'https://mobile.qa.rocket.chat'; + const size = 30; + + const expected = 'https://mobile.qa.rocket.chat/avatar/user123?format=png&size=30'; + const result = getAvatarURL({ avatar, server, size }); + expect(result).toEqual(expected); + }); + + test('uses external provider URL for direct messages', () => { + const type = SubscriptionType.DIRECT; + const text = 'username123'; + const avatarExternalProviderUrl = 'https://external.provider.com/avatar/{username}'; + const size = 30; + + const expected = 'https://external.provider.com/avatar/username123?format=png&size=30'; + const result = getAvatarURL({ type, text, avatarExternalProviderUrl, size }); + expect(result).toEqual(expected); + }); + + test('uses room avatar external provider URL when serverVersion >= 3.8.0', () => { + const rid = 'room123'; + const serverVersion = '3.8.0'; + const roomAvatarExternalProviderUrl = 'https://external.provider.com/room/{roomId}'; + const size = 30; + + mockCompareServerVersion.mockReturnValue(true); + + const expected = 'https://external.provider.com/room/room123?format=png&size=30'; + const result = getAvatarURL({ rid, serverVersion, roomAvatarExternalProviderUrl, size }); + expect(result).toEqual(expected); + expect(mockCompareServerVersion).toHaveBeenCalledWith('3.8.0', 'greaterThanOrEqualTo', '3.8.0'); + }); + + test('uses room/{rid} format when serverVersion >= 3.6.0', () => { + const rid = 'room123'; + const serverVersion = '3.6.0'; + const server = 'https://mobile.qa.rocket.chat'; + const size = 30; + const text = 'roomname'; + + // compareServerVersion returns false for 'lowerThan' when version >= 3.6.0 + // The condition is !compareServerVersion(..., 'lowerThan', '3.6.0') + // So we need to return false to make !false = true + mockCompareServerVersion.mockReturnValue(false); + + const expected = 'https://mobile.qa.rocket.chat/avatar/room/room123?format=png&size=30'; + const result = getAvatarURL({ rid, serverVersion, server, size, text }); + expect(result).toEqual(expected); + expect(mockCompareServerVersion).toHaveBeenCalledWith('3.6.0', 'lowerThan', '3.6.0'); + }); + + test('uses @{text} format when serverVersion < 3.6.0 or no rid', () => { + const text = 'username123'; + const serverVersion = '3.5.0'; + const server = 'https://mobile.qa.rocket.chat'; + const size = 30; + + mockCompareServerVersion.mockReturnValue(false); + + const expected = 'https://mobile.qa.rocket.chat/avatar/@username123?format=png&size=30'; + const result = getAvatarURL({ text, serverVersion, server, size }); + expect(result).toEqual(expected); + }); + + test('adds authentication query parameters when userId, token, and blockUnauthenticatedAccess are provided', () => { + const avatar = '/avatar/user123'; + const server = 'https://mobile.qa.rocket.chat'; + const userId = 'user123'; + const token = 'token123'; + const blockUnauthenticatedAccess = true; + const size = 30; + + const expected = 'https://mobile.qa.rocket.chat/avatar/user123?format=png&size=30&rc_token=token123&rc_uid=user123'; + const result = getAvatarURL({ avatar, server, userId, token, blockUnauthenticatedAccess, size }); + expect(result).toEqual(expected); + }); + + test('adds avatarETag query parameter when provided', () => { + const avatar = '/avatar/user123'; + const server = 'https://mobile.qa.rocket.chat'; + const avatarETag = 'etag123'; + const size = 30; + + const expected = 'https://mobile.qa.rocket.chat/avatar/user123?format=png&size=30&etag=etag123'; + const result = getAvatarURL({ avatar, server, avatarETag, size }); + expect(result).toEqual(expected); + }); + + test('adds both authentication and etag query parameters when all are provided', () => { + const avatar = '/avatar/user123'; + const server = 'https://mobile.qa.rocket.chat'; + const userId = 'user123'; + const token = 'token123'; + const blockUnauthenticatedAccess = true; + const avatarETag = 'etag123'; + const size = 30; + + const expected = + 'https://mobile.qa.rocket.chat/avatar/user123?format=png&size=30&rc_token=token123&rc_uid=user123&etag=etag123'; + const result = getAvatarURL({ avatar, server, userId, token, blockUnauthenticatedAccess, avatarETag, size }); + expect(result).toEqual(expected); + }); + + test('uses cdnPrefix when provided and starts with http', () => { + const avatar = '/avatar/user123'; + const server = 'https://mobile.qa.rocket.chat'; + const cdnPrefix = 'https://cdn.example.com'; + const size = 30; + + const expected = 'https://cdn.example.com/avatar/user123?format=png&size=30'; + const result = getAvatarURL({ avatar, server, cdnPrefix, size }); + expect(result).toEqual(expected); + }); + + test('returns default avatar URL when no avatar is provided', () => { + const text = 'username123'; + const server = 'https://mobile.qa.rocket.chat'; + const size = 30; + + mockCompareServerVersion.mockReturnValue(false); + + const expected = 'https://mobile.qa.rocket.chat/avatar/@username123?format=png&size=30'; + const result = getAvatarURL({ text, server, size }); + expect(result).toEqual(expected); + }); + + test('trims trailing slashes from external provider URLs', () => { + const type = SubscriptionType.DIRECT; + const text = 'username123'; + const avatarExternalProviderUrl = 'https://external.provider.com/avatar/{username}//'; + const size = 30; + + const expected = 'https://external.provider.com/avatar/username123?format=png&size=30'; + const result = getAvatarURL({ type, text, avatarExternalProviderUrl, size }); + expect(result).toEqual(expected); + }); + + test('trims trailing slashes from cdnPrefix', () => { + const avatar = '/avatar/user123'; + const server = 'https://mobile.qa.rocket.chat'; + const cdnPrefix = 'https://cdn.example.com///'; + const size = 30; + + const expected = 'https://cdn.example.com/avatar/user123?format=png&size=30'; + const result = getAvatarURL({ avatar, server, cdnPrefix, size }); + expect(result).toEqual(expected); + }); +}); diff --git a/app/lib/methods/helpers/getAvatarUrl.ts b/app/lib/methods/helpers/getAvatarUrl.ts index b59880472fa..d76f0ccdc65 100644 --- a/app/lib/methods/helpers/getAvatarUrl.ts +++ b/app/lib/methods/helpers/getAvatarUrl.ts @@ -25,6 +25,9 @@ export const getAvatarURL = ({ roomAvatarExternalProviderUrl, cdnPrefix }: IAvatar): string => { + if (!!avatar && avatar?.startsWith('data:')) { + return avatar; + } let room; if (type === SubscriptionType.DIRECT) { room = text; From 88eb8b3d770668c3ad7d0b3cd1dfe7e779da38a1 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:15:44 +0530 Subject: [PATCH 10/63] fix: review button visibility issue on voice recording screen (#6941) --- .../MessageComposer/__snapshots__/MessageComposer.test.tsx.snap | 2 +- .../MessageComposer/components/RecordAudio/ReviewButton.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap index ca392c9b2f3..1251c3e9af8 100644 --- a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap +++ b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap @@ -220,7 +220,7 @@ exports[`MessageComposer Audio tap record 1`] = ` style={ [ { - "color": "#FFFFFF", + "color": "#2F343D", "fontSize": 24, }, [ diff --git a/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx b/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx index 1cf9415a3d5..dd9bdfa8672 100644 --- a/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx +++ b/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx @@ -22,7 +22,7 @@ export const ReviewButton = ({ onPress }: { onPress: Function }): ReactElement = onPress={() => onPress()} hitSlop={hitSlop}> - + ); From ef136906032c4d90fe1984b7a62eb849ab59fd9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Stasiak?= <91474186+OtavioStasiak@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:59:02 -0300 Subject: [PATCH 11/63] fix: Click on Show more on ModalActions breaks the app (#6942) * fix: hide elements instead of conditional render * chore: update snapshot test * fix: remove useMemo * chore: add case on storybook * fix: snapshot test * chore: format code and fix lint issues [skip ci] --------- Co-authored-by: OtavioStasiak --- app/containers/UIKit/Actions.tsx | 36 +- app/containers/UIKit/UiKitModal.stories.tsx | 110 ++ .../__snapshots__/UiKitMessage.test.tsx.snap | 899 +++++++++------- .../__snapshots__/UiKitModal.test.tsx.snap | 962 ++++++++++++++++++ 4 files changed, 1630 insertions(+), 377 deletions(-) diff --git a/app/containers/UIKit/Actions.tsx b/app/containers/UIKit/Actions.tsx index 15f01af1c64..a103ca2bbe5 100644 --- a/app/containers/UIKit/Actions.tsx +++ b/app/containers/UIKit/Actions.tsx @@ -1,22 +1,44 @@ import React, { useState } from 'react'; +import { View, StyleSheet } from 'react-native'; import { BlockContext } from '@rocket.chat/ui-kit'; import Button from '../Button'; import I18n from '../../i18n'; import { type IActions } from './interfaces'; +const styles = StyleSheet.create({ + hidden: { + overflow: 'hidden', + height: 0 + } +}); + export const Actions = ({ blockId, appId, elements, parser }: IActions) => { const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5); - const renderedElements = showMoreVisible ? elements?.slice(0, 5) : elements; + const shouldShowMore = elements && elements.length > 5; + const maxVisible = 5; + + if (!elements || !parser) { + return null; + } + + // Always render all elements to maintain consistent hook calls + // This ensures hooks are always called in the same order + // Use View wrapper to conditionally hide elements instead of conditionally rendering return ( <> - <> - {renderedElements - ? renderedElements?.map(element => parser?.renderActions({ blockId, appId, ...element }, BlockContext.ACTION, parser)) - : null} - - {showMoreVisible &&