From b244f91d869072286e3c86a7728b103f9c310cf3 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Oct 2024 13:21:06 -0300 Subject: [PATCH 1/9] fix: draft message not being updated (#5927) --- .../components/ComposerInput.tsx | 4 +++- e2e/tests/room/02-room.spec.ts | 23 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/containers/MessageComposer/components/ComposerInput.tsx b/app/containers/MessageComposer/components/ComposerInput.tsx index 378b1729bc..d1a3db21e4 100644 --- a/app/containers/MessageComposer/components/ComposerInput.tsx +++ b/app/containers/MessageComposer/components/ComposerInput.tsx @@ -6,7 +6,7 @@ import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/native'; import I18n from '../../../i18n'; import { IAutocompleteItemProps, IComposerInput, IComposerInputProps, IInputSelection, TSetInput } from '../interfaces'; -import { useAutocompleteParams, useFocused, useMessageComposerApi } from '../context'; +import { useAutocompleteParams, useFocused, useMessageComposerApi, useMicOrSend } from '../context'; import { fetchIsAllOrHere, getMentionRegexp } from '../helpers'; import { useSubscription, useAutoSaveDraft } from '../hooks'; import sharedStyles from '../../../views/Styles'; @@ -58,6 +58,8 @@ export const ComposerInput = memo( const usedCannedResponse = route.params?.usedCannedResponse; const prevAction = usePrevious(action); + // subscribe to changes on mic state to update draft after a message is sent + useMicOrSend(); const { saveMessageDraft } = useAutoSaveDraft(textRef.current); // Draft/Canned Responses diff --git a/e2e/tests/room/02-room.spec.ts b/e2e/tests/room/02-room.spec.ts index 7a38a3c960..20b6ddbec1 100644 --- a/e2e/tests/room/02-room.spec.ts +++ b/e2e/tests/room/02-room.spec.ts @@ -10,7 +10,8 @@ import { TTextMatcher, mockMessage, navigateToRoom, - navigateToRecentRoom + navigateToRecentRoom, + checkMessage } from '../../helpers/app'; import { createRandomRoom, createRandomUser, deleteCreatedUsers, ITestUser, sendMessage } from '../../helpers/data_setup'; import data from '../../data'; @@ -413,6 +414,26 @@ describe('Room screen', () => { await tapBack(); }); + it('should save draft, check it, send it and clear it', async () => { + await navigateToRoom(room); + const draftMessage = 'draft'; + await element(by.id('message-composer-input')).typeText(draftMessage); + await tapBack(); + await navigateToRecentRoom(room); + await sleep(500); // wait for animation + await expect(element(by.id('message-composer-input'))).toHaveText(draftMessage); + await waitFor(element(by.id('message-composer-send'))) + .toExist() + .withTimeout(5000); + await element(by.id('message-composer-send')).tap(); + await checkMessage(draftMessage); + await tapBack(); + await navigateToRecentRoom(room); + await sleep(500); // wait for animation + await expect(element(by.id('message-composer-input'))).toHaveText(''); + await tapBack(); + }); + it('should save message and quote draft correctly', async () => { const newUser = await createRandomUser(); const { name: draftRoom } = await createRandomRoom(newUser, 'c'); From 39f54a03ccec1f71034fc482f2c761d15e714440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Stasiak?= <91474186+OtavioStasiak@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:39:48 -0300 Subject: [PATCH 2/9] fix: you are in preview mode german translation (#5907) --- app/i18n/locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/i18n/locales/de.json b/app/i18n/locales/de.json index e899495fe9..81d03d2d10 100644 --- a/app/i18n/locales/de.json +++ b/app/i18n/locales/de.json @@ -740,7 +740,7 @@ "you": "Sie", "You_are_converting_the_team": "Sie wandeln dieses Team in einen Room um", "You_are_deleting_the_team": "Sie sind dabei dieses Team zu löschen.", - "You_are_in_preview_mode": "Sie befinden dich im Vorschaumodus", + "You_are_in_preview_mode": "Sie befinden sich im Vorschaumodus", "You_are_leaving_the_team": "Sie verlassen das Team '{{team}}'", "You_can_search_using_RegExp_eg": "Sie können mit RegExp suchen. z.B. `/ ^ text $ / i`", "You_colon": "Sie: ", From 85f6099b91777fb89f6f230cd450466c0da94b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Stasiak?= <91474186+OtavioStasiak@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:35:40 -0300 Subject: [PATCH 3/9] regression: Big emoji cutted (#5905) Co-authored-by: Diego Mello --- app/containers/markdown/index.tsx | 5 ++++- app/containers/markdown/styles.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/containers/markdown/index.tsx b/app/containers/markdown/index.tsx index a2b663f415..6b33eb12a8 100644 --- a/app/containers/markdown/index.tsx +++ b/app/containers/markdown/index.tsx @@ -191,10 +191,13 @@ class Markdown extends PureComponent { }; renderParagraph = ({ children }: any) => { - const { numberOfLines, style = [], theme } = this.props; + const { numberOfLines, style = [], theme, msg } = this.props; if (!children || children.length === 0) { return null; } + if (msg && this.isMessageContainsOnlyEmoji) { + return {children}; + } return ( {children} diff --git a/app/containers/markdown/styles.ts b/app/containers/markdown/styles.ts index 232d60d7f1..c03fd66f76 100644 --- a/app/containers/markdown/styles.ts +++ b/app/containers/markdown/styles.ts @@ -46,6 +46,7 @@ export default StyleSheet.create({ ...sharedStyles.textRegular }, textBig: { + lineHeight: 43, fontSize: 30, ...sharedStyles.textRegular }, From 2668165f3619ad6c11c94cb3cf88a11912adfb01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Stasiak?= <91474186+OtavioStasiak@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:21:41 -0300 Subject: [PATCH 4/9] fix: update thumb message layout (#5872) Co-authored-by: Otavio Stasiak --- .../message/Components/Attachments/Reply.tsx | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/app/containers/message/Components/Attachments/Reply.tsx b/app/containers/message/Components/Attachments/Reply.tsx index eb374a5beb..0ba0e88d0e 100644 --- a/app/containers/message/Components/Attachments/Reply.tsx +++ b/app/containers/message/Components/Attachments/Reply.tsx @@ -30,7 +30,7 @@ const styles = StyleSheet.create({ attachmentContainer: { flex: 1, borderRadius: 4, - flexDirection: 'column', + flexDirection: 'row', paddingVertical: 4, paddingLeft: 8 }, @@ -43,6 +43,11 @@ const styles = StyleSheet.create({ alignItems: 'center', marginBottom: 8 }, + titleAndDescriptionContainer: { + flexDirection: 'column', + flex: 1, + width: 200 + }, author: { fontSize: 16, ...sharedStyles.textMedium, @@ -72,11 +77,12 @@ const styles = StyleSheet.create({ marginBottom: 4 }, image: { - height: 200, - flex: 1, + height: 80, + width: 80, borderTopLeftRadius: 4, borderTopRightRadius: 4, - marginBottom: 1 + marginBottom: 1, + marginLeft: 20 }, title: { flex: 1, @@ -245,28 +251,30 @@ const Reply = React.memo( background={Touchable.Ripple(themes[theme].surfaceNeutral)} disabled={!!(loading || attachment.message_link)}> - - <Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} /> + <View style={styles.titleAndDescriptionContainer}> + <Title attachment={attachment} timeFormat={timeFormat} theme={theme} /> + <Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} /> + <Attachments + attachments={attachment.attachments} + getCustomEmoji={getCustomEmoji} + timeFormat={timeFormat} + style={[{ color: themes[theme].fontHint, fontSize: 14, marginBottom: 8 }]} + isReply + showAttachment={showAttachment} + /> + <Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} /> + {loading ? ( + <View style={[styles.backdrop]}> + <View + style={[ + styles.backdrop, + { backgroundColor: themes[theme].surfaceNeutral, opacity: themes[theme].attachmentLoadingOpacity } + ]}></View> + <RCActivityIndicator /> + </View> + ) : null} + </View> <UrlImage image={attachment.thumb_url} /> - <Attachments - attachments={attachment.attachments} - getCustomEmoji={getCustomEmoji} - timeFormat={timeFormat} - style={[{ color: themes[theme].fontHint, fontSize: 14, marginBottom: 8 }]} - isReply - showAttachment={showAttachment} - /> - <Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} /> - {loading ? ( - <View style={[styles.backdrop]}> - <View - style={[ - styles.backdrop, - { backgroundColor: themes[theme].surfaceNeutral, opacity: themes[theme].attachmentLoadingOpacity } - ]}></View> - <RCActivityIndicator /> - </View> - ) : null} </View> </Touchable> <Markdown msg={msg} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} /> From c34a1101cd41f20f55788f8e22eef30c9f157cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Stasiak?= <91474186+OtavioStasiak@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:45:12 -0300 Subject: [PATCH 5/9] feat: created thumbnail using expo-video-thumbnail (#5893) --- .../message/Components/Attachments/Video.tsx | 119 ++++++++++++------ 1 file changed, 79 insertions(+), 40 deletions(-) diff --git a/app/containers/message/Components/Attachments/Video.tsx b/app/containers/message/Components/Attachments/Video.tsx index 23b72e6e46..201c9b617a 100644 --- a/app/containers/message/Components/Attachments/Video.tsx +++ b/app/containers/message/Components/Attachments/Video.tsx @@ -1,5 +1,7 @@ -import React, { useContext } from 'react'; -import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native'; +import React, { useContext, useEffect, useState } from 'react'; +import { StyleProp, StyleSheet, TextStyle, View } from 'react-native'; +import FastImage from 'react-native-fast-image'; +import { getThumbnailAsync } from 'expo-video-thumbnails'; import { IUserMessage } from '../../../../definitions'; import { IAttachment } from '../../../../definitions/IAttachment'; @@ -8,70 +10,107 @@ import I18n from '../../../../i18n'; import { fileDownload, isIOS } from '../../../../lib/methods/helpers'; import EventEmitter from '../../../../lib/methods/helpers/events'; import { useTheme } from '../../../../theme'; -import sharedStyles from '../../../../views/Styles'; -import { TIconsName } from '../../../CustomIcon'; import { LISTENER } from '../../../Toast'; import Markdown from '../../../markdown'; import MessageContext from '../../Context'; import Touchable from '../../Touchable'; import { useMediaAutoDownload } from '../../hooks/useMediaAutoDownload'; -import BlurComponent from '../OverlayComponent'; -import { TDownloadState } from '../../../../lib/methods/handleMediaDownload'; import messageStyles from '../../styles'; +import OverlayComponent from '../OverlayComponent'; +import { CustomIcon, TIconsName } from '../../../CustomIcon'; +import { themes } from '../../../../lib/constants'; +import { TDownloadState } from '../../../../lib/methods/handleMediaDownload'; const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1; const styles = StyleSheet.create({ - cancelContainer: { - position: 'absolute', - top: 8, - right: 8 + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center' + }, + overlay: { + flex: 1 + }, + image: { + width: '100%', + height: '100%' }, - text: { - ...sharedStyles.textRegular, - fontSize: 12 + playerIcon: { + position: 'absolute', + textShadowRadius: 3, + textShadowOffset: { + width: 0.5, + height: 0.5 + } } }); -interface IMessageVideo { - file: IAttachment; - showAttachment?: (file: IAttachment) => void; - getCustomEmoji: TGetCustomEmoji; - author?: IUserMessage; - style?: StyleProp<TextStyle>[]; - isReply?: boolean; - msg?: string; -} +type TThumbnailImage = string | null; -const CancelIndicator = () => { - const { colors } = useTheme(); - return ( - <View style={styles.cancelContainer}> - <Text style={[styles.text, { color: colors.fontSecondaryInfo }]}>{I18n.t('Cancel')}</Text> - </View> - ); +type ThumbnailProps = { + url: string; + status: TDownloadState; + encrypted?: boolean; }; -const Thumbnail = ({ status, encrypted = false }: { status: TDownloadState; encrypted: boolean }) => { - const { colors } = useTheme(); +const Thumbnail = ({ url, status, encrypted = false }: ThumbnailProps) => { + const { theme } = useTheme(); + let icon: TIconsName = status === 'downloaded' ? 'play-filled' : 'arrow-down-circle'; if (encrypted && status === 'downloaded') { icon = 'encrypted'; } + const [image, setImage] = useState<TThumbnailImage>(null); + + const generateThumbnail = async () => { + try { + if (!url) return; + + const { uri } = await getThumbnailAsync(url, { + time: 1 + }); + setImage(uri); + } catch (e) { + console.warn(e); + } + }; + + useEffect(() => { + generateThumbnail(); + }, [url]); + return ( - <> - <BlurComponent - iconName={icon} - loading={status === 'loading'} - style={[messageStyles.image, { borderColor: colors.strokeLight, borderWidth: 1 }]} - /> - {status === 'loading' ? <CancelIndicator /> : null} - </> + <View style={styles.container}> + {status === 'loading' || !image || encrypted ? ( + <OverlayComponent style={styles.overlay} loading={status === 'loading'} iconName={icon} /> + ) : ( + <> + <FastImage style={styles.image} resizeMode='cover' source={{ uri: image }} /> + <CustomIcon + name={icon} + size={54} + color={themes[theme].fontPureWhite} + style={[styles.playerIcon, { textShadowColor: themes[theme].backdropColor }]} + /> + </> + )} + </View> ); }; +interface IMessageVideo { + file: IAttachment; + showAttachment?: (file: IAttachment) => void; + getCustomEmoji: TGetCustomEmoji; + author?: IUserMessage; + style?: StyleProp<TextStyle>[]; + isReply?: boolean; + msg?: string; +} + const Video = ({ file, showAttachment, @@ -112,7 +151,7 @@ const Video = ({ <> <Markdown msg={msg} username={user.username} getCustomEmoji={getCustomEmoji} style={[isReply && style]} theme={theme} /> <Touchable onPress={_onPress} style={messageStyles.image} background={Touchable.Ripple(colors.surfaceNeutral)}> - <Thumbnail status={status} encrypted={isEncrypted} /> + <Thumbnail status={status} url={url} encrypted={isEncrypted} /> </Touchable> </> ); From 14e08579299a3f4a5f2b04812d87ee4e84734c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Stasiak?= <91474186+OtavioStasiak@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:54:49 -0300 Subject: [PATCH 6/9] fix: pinned menu returns a blank screen for certain rooms. (#5934) --- app/containers/message/Urls.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/containers/message/Urls.tsx b/app/containers/message/Urls.tsx index 7e1ecaf5cd..025b885fdc 100644 --- a/app/containers/message/Urls.tsx +++ b/app/containers/message/Urls.tsx @@ -126,8 +126,14 @@ type TImageLoadedState = 'loading' | 'done' | 'error'; const Url = ({ url }: { url: IUrl }) => { const { colors, theme } = useTheme(); const { baseUrl, user } = useContext(MessageContext); - let image = url.image || url.url; - image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; + const getImageUrl = () => { + const imageUrl = url.image || url.url; + + if (!imageUrl) return null; + if (imageUrl.includes('http')) return imageUrl; + return `${baseUrl}/${imageUrl}?rc_uid=${user.id}&rc_token=${user.token}`; + }; + const image = getImageUrl(); const onPress = () => openLink(url.url, theme); From cb1868cb99e5536f292bd1b8b2cf4040d6a45b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Stasiak?= <91474186+OtavioStasiak@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:41:21 -0300 Subject: [PATCH 7/9] chore: Update the query parameters for the selected routes (#5941) --- app/lib/methods/getServerInfo.ts | 7 +++- app/lib/methods/getSettings.ts | 24 ++++++------ app/lib/services/restApi.ts | 65 +++++++++++++++++++++++++++----- app/sagas/selectServer.ts | 2 +- app/views/MessagesView/index.tsx | 6 +-- 5 files changed, 78 insertions(+), 26 deletions(-) diff --git a/app/lib/methods/getServerInfo.ts b/app/lib/methods/getServerInfo.ts index 6b7cc0f7d8..87a0d08082 100644 --- a/app/lib/methods/getServerInfo.ts +++ b/app/lib/methods/getServerInfo.ts @@ -9,6 +9,7 @@ import { store } from '../store/auxStore'; import I18n from '../../i18n'; import { SIGNED_SUPPORTED_VERSIONS_PUBLIC_KEY } from '../constants'; import { getServerById } from '../database/services/Server'; +import { compareServerVersion } from './helpers'; import log from './helpers/log'; interface IServerInfoFailure { @@ -121,7 +122,11 @@ export async function getServerInfo(server: string): Promise<TServerInfoResult> } const getUniqueId = async (server: string): Promise<string> => { - const response = await fetch(`${server}/api/v1/settings.public?query={"_id": "uniqueID"}`); + const serverVersion = store.getState().server.version; + const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') + ? `${server}/api/v1/settings.public?_id=uniqueID` + : `${server}/api/v1/settings.public?query={"_id": "uniqueID"}`; + const response = await fetch(url); const result = await response.json(); return result?.settings?.[0]?.value; }; diff --git a/app/lib/methods/getSettings.ts b/app/lib/methods/getSettings.ts index a0db493857..954c8362a2 100644 --- a/app/lib/methods/getSettings.ts +++ b/app/lib/methods/getSettings.ts @@ -12,6 +12,7 @@ import sdk from '../services/sdk'; import protectedFunction from './helpers/protectedFunction'; import { parseSettings, _prepareSettings } from './parseSettings'; import { setPresenceCap } from './getUsersPresence'; +import { compareServerVersion } from './helpers'; const serverInfoKeys = [ 'Site_Name', @@ -108,12 +109,14 @@ const serverInfoUpdate = async (serverInfo: IPreparedSettings[], iconSetting: IS }); }; -export async function getLoginSettings({ server }: { server: string }): Promise<void> { +export async function getLoginSettings({ server, serverVersion }: { server: string; serverVersion: string }): Promise<void> { + const settingsParams = JSON.stringify(loginSettings); + + const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') + ? `${server}/api/v1/settings.public?_id=${loginSettings.join(',')}` + : `${server}/api/v1/settings.public?query={"_id":{"$in":${settingsParams}}}`; try { - const settingsParams = JSON.stringify(loginSettings); - const result = await fetch(`${server}/api/v1/settings.public?query={"_id":{"$in":${settingsParams}}}`).then(response => - response.json() - ); + const result = await fetch(url).then(response => response.json()); if (result.success && result.settings.length) { reduxStore.dispatch(clearSettings()); @@ -153,18 +156,17 @@ export async function getSettings(): Promise<void> { let offset = 0; let remaining; let settings: IData[] = []; - + const serverVersion = reduxStore.getState().server.version; + const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') + ? `${sdk.current.client.host}/api/v1/settings.public?_id=${settingsParams.join(',')}` + : `${sdk.current.client.host}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}`; // Iterate over paginated results to retrieve all settings do { // TODO: why is no-await-in-loop enforced in the first place? /* eslint-disable no-await-in-loop */ - const response = await fetch( - `${sdk.current.client.host}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}} - &offset=${offset}` - ); + const response = await fetch(`${url}&offset=${offset}`); const result = await response.json(); - if (!result.success) { return; } diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 48e13005e1..c78ea16298 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -681,20 +681,65 @@ export const getFiles = (roomId: string, type: SubscriptionType, offset: number) }); }; -export const getMessages = ( - roomId: string, - type: SubscriptionType, - query: { 'mentions._id': { $in: string[] } } | { 'starred._id': { $in: string[] } } | { pinned: boolean }, - offset: number -) => { +export const getMessages = ({ + roomId, + type, + offset, + starredIds, + mentionIds, + pinned +}: { + roomId: string; + type: SubscriptionType; + offset: number; + mentionIds?: string[]; + starredIds?: string[]; + pinned?: boolean; +}) => { const t = type as SubscriptionType.DIRECT | SubscriptionType.CHANNEL | SubscriptionType.GROUP; - // RC 0.59.0 - return sdk.get(`${roomTypeToApiType(t)}.messages`, { + const serverVersion = reduxStore.getState().server.version; + + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0')) { + const params: any = { + roomId, + offset, + sort: { ts: -1 } + }; + + if (mentionIds && mentionIds.length > 0) { + params.mentionIds = mentionIds.join(','); + } + + if (starredIds && starredIds.length > 0) { + params.starredIds = starredIds.join(','); + } + + if (pinned) { + params.pinned = pinned; + } + + return sdk.get(`${roomTypeToApiType(t)}.messages`, params); + } + const params: any = { roomId, - query, offset, sort: { ts: -1 } - }); + }; + + if (mentionIds && mentionIds.length > 0) { + params.query = { ...params.query, 'mentions._id': { $in: mentionIds } }; + } + + if (starredIds && starredIds.length > 0) { + params.query = { ...params.query, 'starred._id': { $in: starredIds } }; + } + + if (pinned) { + params.query = { ...params.query, pinned: true }; + } + + // RC 0.59.0 + return sdk.get(`${roomTypeToApiType(t)}.messages`, params); }; export const getReadReceipts = (messageId: string) => diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts index 84daa85196..d2a2461658 100644 --- a/app/sagas/selectServer.ts +++ b/app/sagas/selectServer.ts @@ -236,7 +236,7 @@ const handleServerRequest = function* handleServerRequest({ server, username, fr if (serverInfo) { yield Services.getLoginServices(server); - yield getLoginSettings({ server }); + yield getLoginSettings({ server, serverVersion: serverInfo.version }); Navigation.navigate('WorkspaceView'); const Accounts_iframe_enabled = yield* appSelector(state => state.settings.Accounts_iframe_enabled); diff --git a/app/views/MessagesView/index.tsx b/app/views/MessagesView/index.tsx index 6a94605f03..5a9d3c3658 100644 --- a/app/views/MessagesView/index.tsx +++ b/app/views/MessagesView/index.tsx @@ -220,7 +220,7 @@ class MessagesView extends React.Component<IMessagesViewProps, IMessagesViewStat name: I18n.t('Mentions'), fetchFunc: () => { const { messages } = this.state; - return Services.getMessages(this.rid, this.t, { 'mentions._id': { $in: [user.id] } }, messages.length); + return Services.getMessages({ roomId: this.rid, type: this.t, offset: messages.length, mentionIds: [user.id] }); }, noDataMsg: I18n.t('No_mentioned_messages'), testID: 'mentioned-messages-view', @@ -231,7 +231,7 @@ class MessagesView extends React.Component<IMessagesViewProps, IMessagesViewStat name: I18n.t('Starred'), fetchFunc: () => { const { messages } = this.state; - return Services.getMessages(this.rid, this.t, { 'starred._id': { $in: [user.id] } }, messages.length); + return Services.getMessages({ roomId: this.rid, type: this.t, offset: messages.length, starredIds: [user.id] }); }, noDataMsg: I18n.t('No_starred_messages'), testID: 'starred-messages-view', @@ -250,7 +250,7 @@ class MessagesView extends React.Component<IMessagesViewProps, IMessagesViewStat name: I18n.t('Pinned'), fetchFunc: () => { const { messages } = this.state; - return Services.getMessages(this.rid, this.t, { pinned: true }, messages.length); + return Services.getMessages({ roomId: this.rid, type: this.t, offset: messages.length, pinned: true }); }, noDataMsg: I18n.t('No_pinned_messages'), testID: 'pinned-messages-view', From b8f9873b3012aa7752a64d475cf56c71f0c7b752 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Wed, 30 Oct 2024 18:03:26 -0300 Subject: [PATCH 8/9] feat: Add new permissions to teams (#5943) --- app/lib/methods/getPermissions.ts | 7 +- app/stacks/types.ts | 2 + app/views/AddChannelTeamView.tsx | 90 +++++++++++++++------- app/views/AddExistingChannelView/index.tsx | 24 ++++-- app/views/RoomActionsView/index.tsx | 28 ++++--- app/views/TeamChannelsView.tsx | 66 +++++++++++++--- 6 files changed, 158 insertions(+), 59 deletions(-) diff --git a/app/lib/methods/getPermissions.ts b/app/lib/methods/getPermissions.ts index f79de0121d..7bbd870950 100644 --- a/app/lib/methods/getPermissions.ts +++ b/app/lib/methods/getPermissions.ts @@ -61,7 +61,12 @@ export const SUPPORTED_PERMISSIONS = [ 'mobile-upload-file', 'delete-own-message', 'call-management', - 'test-push-notifications' + 'test-push-notifications', + 'move-room-to-team', + 'create-team-channel', + 'create-team-group', + 'delete-team-channel', + 'delete-team-group' ] as const; export async function setPermissions(): Promise<void> { diff --git a/app/stacks/types.ts b/app/stacks/types.ts index 27fbdba913..c3c9142216 100644 --- a/app/stacks/types.ts +++ b/app/stacks/types.ts @@ -148,6 +148,8 @@ export type ChatsStackParamList = { }; AddChannelTeamView: { teamId: string; + rid: string; + t: 'c' | 'p'; }; AddExistingChannelView: { teamId: string; diff --git a/app/views/AddChannelTeamView.tsx b/app/views/AddChannelTeamView.tsx index c21c50778d..64f2f14fef 100644 --- a/app/views/AddChannelTeamView.tsx +++ b/app/views/AddChannelTeamView.tsx @@ -10,6 +10,9 @@ import SafeAreaView from '../containers/SafeAreaView'; import I18n from '../i18n'; import { ChatsStackParamList, DrawerParamList, NewMessageStackParamList } from '../stacks/types'; import { IApplicationState } from '../definitions'; +import { usePermissions } from '../lib/hooks'; +import { compareServerVersion } from '../lib/methods/helpers'; +import { TSupportedPermissions } from '../reducers/permissions'; type TRoute = RouteProp<ChatsStackParamList, 'AddChannelTeamView'>; @@ -18,13 +21,40 @@ type TNavigation = CompositeNavigationProp< CompositeNavigationProp<NativeStackNavigationProp<NewMessageStackParamList>, NativeStackNavigationProp<DrawerParamList>> >; +const useCreateNewPermission = (rid: string, t: 'c' | 'p') => { + const permissions: TSupportedPermissions[] = t === 'c' ? ['create-c'] : ['create-p']; + + const serverVersion = useSelector((state: IApplicationState) => state.server.version); + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0')) { + permissions.push(t === 'c' ? 'create-team-channel' : 'create-team-group'); + } + + const result = usePermissions(permissions, rid); + return result.some(Boolean); +}; + +const useAddExistingPermission = (rid: string) => { + let permissions: TSupportedPermissions[] = ['add-team-channel']; + + const serverVersion = useSelector((state: IApplicationState) => state.server.version); + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0')) { + permissions = ['move-room-to-team']; + } + + const result = usePermissions(permissions, rid); + return result[0]; +}; + const AddChannelTeamView = () => { const navigation = useNavigation<TNavigation>(); const isMasterDetail = useSelector((state: IApplicationState) => state.app.isMasterDetail); const { - params: { teamId } + params: { teamId, rid, t } } = useRoute<TRoute>(); + const canCreateNew = useCreateNewPermission(rid, t); + const canAddExisting = useAddExistingPermission(rid); + useLayoutEffect(() => { navigation.setOptions({ title: I18n.t('Add_Channel_to_Team') }); }, [navigation]); @@ -34,31 +64,39 @@ const AddChannelTeamView = () => { <StatusBar /> <List.Container> <List.Separator /> - <List.Item - title='Create_New' - onPress={() => - isMasterDetail - ? navigation.navigate('SelectedUsersViewCreateChannel', { - nextAction: () => navigation.navigate('CreateChannelView', { teamId }) - }) - : navigation.navigate('SelectedUsersView', { - nextAction: () => - navigation.navigate('ChatsStackNavigator', { screen: 'CreateChannelView', params: { teamId } }) - }) - } - testID='add-channel-team-view-create-channel' - left={() => <List.Icon name='team' />} - right={() => <List.Icon name='chevron-right' />} - /> - <List.Separator /> - <List.Item - title='Add_Existing' - onPress={() => navigation.navigate('AddExistingChannelView', { teamId })} - testID='add-channel-team-view-add-existing' - left={() => <List.Icon name='channel-public' />} - right={() => <List.Icon name='chevron-right' />} - /> - <List.Separator /> + {canCreateNew ? ( + <> + <List.Item + title='Create_New' + onPress={() => + isMasterDetail + ? navigation.navigate('SelectedUsersViewCreateChannel', { + nextAction: () => navigation.navigate('CreateChannelView', { teamId }) + }) + : navigation.navigate('SelectedUsersView', { + nextAction: () => + navigation.navigate('ChatsStackNavigator', { screen: 'CreateChannelView', params: { teamId } }) + }) + } + testID='add-channel-team-view-create-channel' + left={() => <List.Icon name='team' />} + right={() => <List.Icon name='chevron-right' />} + /> + <List.Separator /> + </> + ) : null} + {canAddExisting ? ( + <> + <List.Item + title='Add_Existing' + onPress={() => navigation.navigate('AddExistingChannelView', { teamId })} + testID='add-channel-team-view-add-existing' + left={() => <List.Icon name='channel-public' />} + right={() => <List.Icon name='chevron-right' />} + /> + <List.Separator /> + </> + ) : null} </List.Container> </SafeAreaView> ); diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index 09367a9b08..f04d0d2407 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -18,7 +18,7 @@ import { animateNextTransition } from '../../lib/methods/helpers/layoutAnimation import { showErrorAlert } from '../../lib/methods/helpers/info'; import { ChatsStackParamList } from '../../stacks/types'; import { TSubscriptionModel, SubscriptionType } from '../../definitions'; -import { getRoomTitle, hasPermission, useDebounce } from '../../lib/methods/helpers'; +import { compareServerVersion, getRoomTitle, hasPermission, useDebounce } from '../../lib/methods/helpers'; import { Services } from '../../lib/services'; import { useAppSelector } from '../../lib/hooks'; @@ -38,9 +38,11 @@ const AddExistingChannelView = () => { params: { teamId } } = useRoute<TRoute>(); - const { addTeamChannelPermission, isMasterDetail } = useAppSelector(state => ({ + const { serverVersion, addTeamChannelPermission, isMasterDetail, moveRoomToTeamPermission } = useAppSelector(state => ({ + serverVersion: state.server.version, isMasterDetail: state.app.isMasterDetail, - addTeamChannelPermission: state.permissions['add-team-channel'] + addTeamChannelPermission: state.permissions['add-team-channel'], + moveRoomToTeamPermission: state.permissions['move-room-to-team'] })); useLayoutEffect(() => { @@ -70,6 +72,15 @@ const AddExistingChannelView = () => { navigation.setOptions(options); }; + const hasCreatePermission = async (id: string) => { + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0')) { + const result = await hasPermission([moveRoomToTeamPermission], id); + return result[0]; + } + const result = await hasPermission([addTeamChannelPermission], id); + return result[0]; + }; + const query = async (stringToSearch = '') => { try { const db = database.active; @@ -90,11 +101,8 @@ const AddExistingChannelView = () => { if (channel.prid) { return false; } - const permissions = await hasPermission([addTeamChannelPermission], channel.rid); - if (!permissions[0]) { - return false; - } - return true; + const result = await hasCreatePermission(channel.rid); + return result; }) ); diff --git a/app/views/RoomActionsView/index.tsx b/app/views/RoomActionsView/index.tsx index 80806de3fc..1f4fc76d0c 100644 --- a/app/views/RoomActionsView/index.tsx +++ b/app/views/RoomActionsView/index.tsx @@ -75,6 +75,7 @@ interface IRoomActionsViewProps extends IActionSheetProvider, IBaseScreen<StackT viewBroadcastMemberListPermission?: string[]; createTeamPermission?: string[]; addTeamChannelPermission?: string[]; + moveRoomToTeamPermission?: string[]; convertTeamPermission?: string[]; viewCannedResponsesPermission?: string[]; livechatAllowManualOnHold?: boolean; @@ -222,7 +223,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction const canToggleEncryption = await this.canToggleEncryption(); const canViewMembers = await this.canViewMembers(); const canCreateTeam = await this.canCreateTeam(); - const canAddChannelToTeam = await this.canAddChannelToTeam(); + const canAddChannelToTeam = await this.hasMoveToTeamPermission(room.rid); const canConvertTeam = await this.canConvertTeam(); const hasE2EEWarning = EncryptionUtils.hasE2EEWarning({ encryptionEnabled, @@ -313,14 +314,14 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction return canCreateTeam; }; - canAddChannelToTeam = async () => { - const { room } = this.state; - const { addTeamChannelPermission } = this.props; - const { rid } = room; - const permissions = await hasPermission([addTeamChannelPermission], rid); - - const canAddChannelToTeam = permissions[0]; - return canAddChannelToTeam; + hasMoveToTeamPermission = async (rid: string) => { + const { addTeamChannelPermission, moveRoomToTeamPermission, serverVersion } = this.props; + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0')) { + const result = await hasPermission([moveRoomToTeamPermission], rid); + return result[0]; + } + const result = await hasPermission([addTeamChannelPermission], rid); + return result[0]; }; canConvertTeam = async () => { @@ -693,7 +694,6 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction searchTeam = async (onChangeText: string) => { logEvent(events.RA_SEARCH_TEAM); try { - const { addTeamChannelPermission, createTeamPermission } = this.props; const QUERY_SIZE = 50; const db = database.active; const teams = await db @@ -709,11 +709,8 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction const asyncFilter = async (teamArray: TSubscriptionModel[]) => { const results = await Promise.all( teamArray.map(async team => { - const permissions = await hasPermission([addTeamChannelPermission, createTeamPermission], team.rid); - if (!permissions[0]) { - return false; - } - return true; + const result = await this.hasMoveToTeamPermission(team.rid); + return result; }) ); @@ -1290,6 +1287,7 @@ const mapStateToProps = (state: IApplicationState) => ({ viewBroadcastMemberListPermission: state.permissions['view-broadcast-member-list'], createTeamPermission: state.permissions['create-team'], addTeamChannelPermission: state.permissions['add-team-channel'], + moveRoomToTeamPermission: state.permissions['move-room-to-team'], convertTeamPermission: state.permissions['convert-team'], viewCannedResponsesPermission: state.permissions['view-canned-responses'], livechatAllowManualOnHold: state.settings.Livechat_allow_manual_on_hold as boolean, diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index ef9c81b1ec..bbaa25a2cd 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -21,13 +21,12 @@ import I18n from '../i18n'; import database from '../lib/database'; import { CustomIcon } from '../containers/CustomIcon'; import RoomItem, { ROW_HEIGHT } from '../containers/RoomItem'; -import { getUserSelector } from '../selectors/login'; import { ChatsStackParamList } from '../stacks/types'; import { withTheme } from '../theme'; import { goRoom } from '../lib/methods/helpers/goRoom'; import { showErrorAlert } from '../lib/methods/helpers/info'; import log, { events, logEvent } from '../lib/methods/helpers/log'; -import { getRoomAvatar, getRoomTitle, hasPermission, debounce, isIOS } from '../lib/methods/helpers'; +import { getRoomAvatar, getRoomTitle, hasPermission, debounce, isIOS, compareServerVersion } from '../lib/methods/helpers'; import { Services } from '../lib/services'; const API_FETCH_COUNT = 25; @@ -71,14 +70,22 @@ interface ITeamChannelsViewState { } interface ITeamChannelsViewProps extends IBaseScreen<ChatsStackParamList, 'TeamChannelsView'> { + serverVersion: string; useRealName: boolean; width: number; StoreLastMessage: boolean; addTeamChannelPermission: string[]; + moveRoomToTeamPermission: string[]; editTeamChannelPermission: string[]; removeTeamChannelPermission: string[]; + createCPermission: string[]; + createTeamChannelPermission: string[]; + createPPermission: string[]; + createTeamGroupPermission: string[]; deleteCPermission: string[]; deletePPermission: string[]; + deleteTeamChannelPermission: string[]; + deleteTeamGroupPermission: string[]; showActionSheet: (options: TActionSheetOptions) => void; showAvatar: boolean; displayMode: DisplayMode; @@ -113,8 +120,28 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan this.load(); } + hasCreatePermission = async () => { + const { + addTeamChannelPermission, + moveRoomToTeamPermission, + serverVersion, + createCPermission, + createPPermission, + createTeamChannelPermission, + createTeamGroupPermission + } = this.props; + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0')) { + const createPermissions = + this.team.t === 'c' ? [createCPermission, createTeamChannelPermission] : [createPPermission, createTeamGroupPermission]; + const result = await hasPermission([moveRoomToTeamPermission, ...createPermissions], this.team.rid); + return result.some(Boolean); + } + const createPermissions = this.team.t === 'c' ? [createCPermission] : [createPPermission]; + const result = await hasPermission([addTeamChannelPermission, ...createPermissions], this.team.rid); + return result.some(Boolean); + }; + loadTeam = async () => { - const { addTeamChannelPermission } = this.props; const { loading, data } = this.state; const db = database.active; @@ -128,8 +155,8 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan throw new Error(); } - const permissions = await hasPermission([addTeamChannelPermission], this.team.rid); - if (permissions[0]) { + const hasCreatePermission = await this.hasCreatePermission(); + if (hasCreatePermission) { this.setState({ showCreate: true }, () => this.setHeader()); } @@ -219,7 +246,9 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan <HeaderButton.Item iconName='create' testID='team-channels-view-create' - onPress={() => navigation.navigate('AddChannelTeamView', { teamId: this.teamId })} + onPress={() => + navigation.navigate('AddChannelTeamView', { teamId: this.teamId, rid: this.team.rid, t: this.team.t as any }) + } /> ) : null} <HeaderButton.Item iconName='search' testID='team-channels-view-search' onPress={this.onSearchPress} /> @@ -405,6 +434,20 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan ); }; + hasDeletePermission = async (rid: string, t: 'c' | 'p') => { + const { serverVersion, deleteCPermission, deletePPermission, deleteTeamChannelPermission, deleteTeamGroupPermission } = + this.props; + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0')) { + const permissions = + t === 'c' ? [deleteTeamChannelPermission, deleteTeamGroupPermission] : [deletePPermission, deletePPermission]; + const result = await hasPermission(permissions, rid); + return result[0] && result[1]; + } + + const result = await hasPermission([t === 'c' ? deleteCPermission : deletePPermission], rid); + return result[0]; + }; + showChannelActions = async (item: IItem) => { logEvent(events.ROOM_SHOW_BOX_ACTIONS); const { @@ -535,16 +578,21 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan } const mapStateToProps = (state: IApplicationState) => ({ - baseUrl: state.server.server, - user: getUserSelector(state), + serverVersion: state.server.version, useRealName: state.settings.UI_Use_Real_Name, isMasterDetail: state.app.isMasterDetail, StoreLastMessage: state.settings.Store_Last_Message, addTeamChannelPermission: state.permissions['add-team-channel'], + moveRoomToTeamPermission: state.permissions['move-room-to-team'], editTeamChannelPermission: state.permissions['edit-team-channel'], removeTeamChannelPermission: state.permissions['remove-team-channel'], - deleteCPermission: state.permissions['delete-c'], + createCPermission: state.permissions['create-c'], + createTeamChannelPermission: state.permissions['create-team-channel'], + createPPermission: state.permissions['create-p'], + createTeamGroupPermission: state.permissions['create-team-group'], + deleteTeamChannelPermission: state.permissions['delete-team-channel'], deletePPermission: state.permissions['delete-p'], + deleteTeamGroupPermission: state.permissions['delete-team-group'], showAvatar: state.sortPreferences.showAvatar, displayMode: state.sortPreferences.displayMode }); From 84a65106592c7e61b9e1f22b85b6b312c218038b Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> Date: Mon, 4 Nov 2024 15:55:27 -0300 Subject: [PATCH 9/9] chore: Bump version to 4.55.0 (#5949) --- android/app/build.gradle | 2 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 4 ++-- ios/RocketChatRN/Info.plist | 2 +- ios/ShareRocketChatRN/Info.plist | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 11cbd8bd9d..31c7158397 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -93,7 +93,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode VERSIONCODE as Integer - versionName "4.54.0" + versionName "4.55.0" vectorDrawables.useSupportLibrary = true if (!isFoss) { manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 6c44bf39d2..009f6c0939 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -2975,7 +2975,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.54.0; + MARKETING_VERSION = 4.55.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -3019,7 +3019,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.54.0; + MARKETING_VERSION = 4.55.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index b3498b4c54..8be4512a87 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -28,7 +28,7 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>4.54.0</string> + <string>4.55.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleURLTypes</key> diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist index 22ea62b83d..171d1ab932 100644 --- a/ios/ShareRocketChatRN/Info.plist +++ b/ios/ShareRocketChatRN/Info.plist @@ -26,7 +26,7 @@ <key>CFBundlePackageType</key> <string>XPC!</string> <key>CFBundleShortVersionString</key> - <string>4.54.0</string> + <string>4.55.0</string> <key>CFBundleVersion</key> <string>1</string> <key>KeychainGroup</key> diff --git a/package.json b/package.json index f890700fe7..17335516b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rocket-chat-reactnative", - "version": "4.54.0", + "version": "4.55.0", "private": true, "scripts": { "start": "react-native start",