diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx index 60bbfe14f..7d10b2936 100644 --- a/app/containers/Avatar/Avatar.tsx +++ b/app/containers/Avatar/Avatar.tsx @@ -30,7 +30,8 @@ const Avatar = React.memo( borderRadius = 4, type = SubscriptionType.DIRECT, avatarExternalProviderUrl, - roomAvatarExternalProviderUrl + roomAvatarExternalProviderUrl, + cdnPrefix }: IAvatar) => { if ((!text && !avatar && !emoji && !rid) || !server) { return null; @@ -61,7 +62,8 @@ const Avatar = React.memo( rid, blockUnauthenticatedAccess, avatarExternalProviderUrl, - roomAvatarExternalProviderUrl + roomAvatarExternalProviderUrl, + cdnPrefix }); } diff --git a/app/containers/Avatar/AvatarContainer.tsx b/app/containers/Avatar/AvatarContainer.tsx index acfb0978a..e8e6a0754 100644 --- a/app/containers/Avatar/AvatarContainer.tsx +++ b/app/containers/Avatar/AvatarContainer.tsx @@ -32,9 +32,10 @@ const AvatarContainer = ({ shallowEqual ); - const { avatarExternalProviderUrl, roomAvatarExternalProviderUrl } = useSelector((state: IApplicationState) => ({ + const { avatarExternalProviderUrl, roomAvatarExternalProviderUrl, cdnPrefix } = useSelector((state: IApplicationState) => ({ avatarExternalProviderUrl: state.settings.Accounts_AvatarExternalProviderUrl as string, - roomAvatarExternalProviderUrl: state.settings.Accounts_RoomAvatarExternalProviderUrl as string + roomAvatarExternalProviderUrl: state.settings.Accounts_RoomAvatarExternalProviderUrl as string, + cdnPrefix: state.settings.CDN_PREFIX as string })); const blockUnauthenticatedAccess = useSelector( (state: IApplicationState) => @@ -67,6 +68,7 @@ const AvatarContainer = ({ roomAvatarExternalProviderUrl={roomAvatarExternalProviderUrl} avatarETag={avatarETag} serverVersion={serverVersion} + cdnPrefix={cdnPrefix} /> ); }; diff --git a/app/containers/Avatar/interfaces.ts b/app/containers/Avatar/interfaces.ts index 6ae020202..625ae4b87 100644 --- a/app/containers/Avatar/interfaces.ts +++ b/app/containers/Avatar/interfaces.ts @@ -24,4 +24,5 @@ export interface IAvatar { serverVersion?: string | null; avatarExternalProviderUrl?: string; roomAvatarExternalProviderUrl?: string; + cdnPrefix?: string; } diff --git a/app/containers/message/Audio.tsx b/app/containers/message/Audio.tsx index 315a26442..7f6afdf54 100644 --- a/app/containers/message/Audio.tsx +++ b/app/containers/message/Audio.tsx @@ -6,6 +6,7 @@ import moment from 'moment'; import { dequal } from 'dequal'; import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake'; import { Sound } from 'expo-av/build/Audio/Sound'; +import { connect } from 'react-redux'; import Touchable from './Touchable'; import Markdown from '../markdown'; @@ -17,7 +18,7 @@ import MessageContext from './Context'; import ActivityIndicator from '../ActivityIndicator'; import { withDimensions } from '../../dimensions'; import { TGetCustomEmoji } from '../../definitions/IEmoji'; -import { IAttachment, IUserMessage } from '../../definitions'; +import { IApplicationState, IAttachment, IUserMessage } from '../../definitions'; import { TSupportedThemes, useTheme } from '../../theme'; import { downloadMediaFile, getMediaCache } from '../../lib/methods/handleMediaDownload'; import EventEmitter from '../../lib/methods/helpers/events'; @@ -41,6 +42,7 @@ interface IMessageAudioProps { scale?: number; author?: IUserMessage; msg?: string; + cdnPrefix?: string; } interface IMessageAudioState { @@ -208,13 +210,12 @@ class MessageAudio extends React.Component { - const { file } = this.props; + const { file, cdnPrefix } = this.props; // @ts-ignore can't use declare to type this const { baseUrl } = this.context; - let url = file.audio_url; if (url && !url.startsWith('http')) { - url = `${baseUrl}${file.audio_url}`; + url = `${cdnPrefix || baseUrl}${file.audio_url}`; } return url; }; @@ -393,4 +394,8 @@ class MessageAudio extends React.Component ({ + cdnPrefix: state.settings.CDN_PREFIX as string +}); + +export default connect(mapStateToProps)(withDimensions(MessageAudio)); diff --git a/app/lib/constants/defaultSettings.ts b/app/lib/constants/defaultSettings.ts index 164832190..4551f6319 100644 --- a/app/lib/constants/defaultSettings.ts +++ b/app/lib/constants/defaultSettings.ts @@ -246,5 +246,8 @@ export const defaultSettings = { Omnichannel_call_provider: { type: 'valueAsBoolean' }, + CDN_PREFIX: { + type: 'valueAsString' + }, ...deprecatedSettings } as const; diff --git a/app/lib/methods/helpers/formatAttachmentUrl.ts b/app/lib/methods/helpers/formatAttachmentUrl.ts index 55055f4dc..143188594 100644 --- a/app/lib/methods/helpers/formatAttachmentUrl.ts +++ b/app/lib/methods/helpers/formatAttachmentUrl.ts @@ -2,6 +2,7 @@ import { URL } from 'react-native-url-polyfill'; import { LOCAL_DOCUMENT_DIRECTORY } from '../handleMediaDownload'; import { isImageBase64 } from '../isImageBase64'; +import { store } from '../../store/auxStore'; function setParamInUrl({ url, token, userId }: { url: string; token: string; userId: string }) { const urlObj = new URL(url); @@ -23,5 +24,9 @@ export const formatAttachmentUrl = (attachmentUrl: string | undefined, userId: s } return setParamInUrl({ url: attachmentUrl, token, userId }); } + const cdnPrefix = store?.getState().settings.CDN_PREFIX as string; + if (cdnPrefix) { + server = cdnPrefix.trim().replace(/\/+$/, ''); + } return setParamInUrl({ url: `${server}${attachmentUrl}`, token, userId }); }; diff --git a/app/lib/methods/helpers/getAvatarUrl.ts b/app/lib/methods/helpers/getAvatarUrl.ts index 476b54896..1702369ef 100644 --- a/app/lib/methods/helpers/getAvatarUrl.ts +++ b/app/lib/methods/helpers/getAvatarUrl.ts @@ -22,7 +22,8 @@ export const getAvatarURL = ({ blockUnauthenticatedAccess, serverVersion, avatarExternalProviderUrl, - roomAvatarExternalProviderUrl + roomAvatarExternalProviderUrl, + cdnPrefix }: IAvatar): string => { let room; if (type === SubscriptionType.DIRECT) { @@ -48,6 +49,10 @@ export const getAvatarURL = ({ query += `&etag=${avatarETag}`; } + if (cdnPrefix) { + server = cdnPrefix.trim().replace(/\/+$/, ''); + } + if (avatar) { if (avatar.startsWith('http')) { return avatar; diff --git a/e2e/tests/room/02-room.spec.ts b/e2e/tests/room/02-room.spec.ts index 8a863f9de..a7f091106 100644 --- a/e2e/tests/room/02-room.spec.ts +++ b/e2e/tests/room/02-room.spec.ts @@ -478,6 +478,8 @@ describe('Room screen', () => { .toExist() .withTimeout(2000); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); + // Fix android flaky test. Close the action sheet, then re-open again + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('action-sheet')).swipe('up', 'fast', 0.5); await sleep(300); // wait for animation await waitFor(element(by[textMatcher]('Delete'))) diff --git a/e2e/tests/room/05-threads.spec.ts b/e2e/tests/room/05-threads.spec.ts index 9f46eceab..5ef800674 100644 --- a/e2e/tests/room/05-threads.spec.ts +++ b/e2e/tests/room/05-threads.spec.ts @@ -237,6 +237,9 @@ describe('Threads', () => { .withTimeout(5000); await element(by.id(`message-thread-button-${thread}`)).tap(); await tryTapping(element(by[textMatcher]('replied')).atIndex(0), 2000, true); + // Fix android flaky test. Close the action sheet, then re-open again + await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); + await sleep(1000); // wait for animation await element(by.id('action-sheet')).swipe('up', 'fast', 0.5); await sleep(300); // wait for animation await element(by[textMatcher]('Delete')).atIndex(0).tap(); diff --git a/e2e/tests/room/11-autoTranslate.spec.ts b/e2e/tests/room/11-autoTranslate.spec.ts index 44fa2d81d..814ad5f37 100644 --- a/e2e/tests/room/11-autoTranslate.spec.ts +++ b/e2e/tests/room/11-autoTranslate.spec.ts @@ -137,9 +137,10 @@ describe('Auto Translate', () => { // verify default language is checked await waitFor(element(by.id(`auto-translate-view-${languages.default}`))) - .toBeVisible() + .toExist() .whileElement(by.id('auto-translate-view')) .scroll(750, 'down'); + await element(by.id('auto-translate-view')).swipe('up', 'slow', 0.5); await waitForVisible(`auto-translate-view-${languages.default}-check`); // enable translated language @@ -219,6 +220,7 @@ describe('Auto Translate', () => { }); it(`should don't see action to View original when disable auto translate`, async () => { + await searchMessage(oldMessage[languages.default] as string, textMatcher); // will scroll the messages list to the last one await waitForVisibleTextMatcher(oldMessage[languages.default] as string, textMatcher); await tryTapping(element(by[textMatcher](oldMessage[languages.default] as string)).atIndex(0), 2000, true);