From 21a989b165739d6d6ed745cbcd18fa1f52611ac2 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 31 Aug 2022 15:00:38 +0200 Subject: [PATCH] Settings / Privacy: Voice Messages (#1968) --- src/api/gramjs/apiBuilders/misc.ts | 2 + src/api/gramjs/gramjsBuilders/index.ts | 3 ++ src/api/gramjs/methods/users.ts | 6 ++- src/components/common/DeleteChatModal.tsx | 10 +++-- src/components/left/LeftColumn.tsx | 5 +++ src/components/left/settings/Settings.scss | 7 ++++ src/components/left/settings/Settings.tsx | 9 +++++ .../left/settings/SettingsHeader.tsx | 4 ++ .../left/settings/SettingsPrivacy.tsx | 34 ++++++++++++++++ .../settings/SettingsPrivacyVisibility.tsx | 10 +++++ ...SettingsPrivacyVisibilityExceptionList.tsx | 3 ++ .../left/settings/helpers/privacy.ts | 4 ++ src/components/main/BotTrustModal.tsx | 2 +- src/components/main/Dialogs.tsx | 3 +- src/components/main/Notifications.tsx | 4 +- src/components/middle/MiddleHeader.tsx | 5 +-- src/components/middle/composer/Composer.tsx | 32 +++++++++++---- src/components/ui/Button.scss | 4 +- src/components/ui/Button.tsx | 11 +++-- src/components/ui/ListItem.scss | 12 +++--- src/components/ui/ListItem.tsx | 12 ++++-- src/components/ui/Notification.tsx | 1 - src/global/actions/api/messages.ts | 40 ++++++++++++++++++- src/global/actions/api/settings.ts | 4 ++ src/global/actions/ui/messages.ts | 17 -------- src/global/selectors/messages.ts | 10 +++++ src/types/index.ts | 5 ++- 27 files changed, 205 insertions(+), 54 deletions(-) diff --git a/src/api/gramjs/apiBuilders/misc.ts b/src/api/gramjs/apiBuilders/misc.ts index 2f7eac6d6..a314b47a9 100644 --- a/src/api/gramjs/apiBuilders/misc.ts +++ b/src/api/gramjs/apiBuilders/misc.ts @@ -71,6 +71,8 @@ export function buildPrivacyKey(key: GramJs.TypePrivacyKey): ApiPrivacyKey | und return 'phoneP2P'; case 'PrivacyKeyForwards': return 'forwards'; + case 'PrivacyKeyVoiceMessages': + return 'voiceMessages'; case 'PrivacyKeyChatInvite': return 'chatInvite'; } diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts index 81a091d12..3e675be58 100644 --- a/src/api/gramjs/gramjsBuilders/index.ts +++ b/src/api/gramjs/gramjsBuilders/index.ts @@ -452,6 +452,9 @@ export function buildInputPrivacyKey(privacyKey: ApiPrivacyKey) { case 'phoneP2P': return new GramJs.InputPrivacyKeyPhoneP2P(); + + case 'voiceMessages': + return new GramJs.InputPrivacyKeyVoiceMessages(); } return undefined; diff --git a/src/api/gramjs/methods/users.ts b/src/api/gramjs/methods/users.ts index 76f12156c..f63d0faed 100644 --- a/src/api/gramjs/methods/users.ts +++ b/src/api/gramjs/methods/users.ts @@ -36,13 +36,13 @@ export async function fetchFullUser({ }) { const input = buildInputEntity(id, accessHash); if (!(input instanceof GramJs.InputUser)) { - return; + return undefined; } const fullInfo = await invokeRequest(new GramJs.users.GetFullUser({ id: input })); if (!fullInfo) { - return; + return undefined; } if (fullInfo.fullUser.profilePhoto instanceof GramJs.Photo) { @@ -66,6 +66,8 @@ export async function fetchFullUser({ fullInfo: userWithFullInfo.fullInfo, }, }); + + return userWithFullInfo; } export async function fetchCommonChats(id: string, accessHash?: string, maxId?: string) { diff --git a/src/components/common/DeleteChatModal.tsx b/src/components/common/DeleteChatModal.tsx index c248ac172..7f3a01ed3 100644 --- a/src/components/common/DeleteChatModal.tsx +++ b/src/components/common/DeleteChatModal.tsx @@ -149,14 +149,18 @@ const DeleteChatModal: FC = ({ function renderMessage() { if (isChannel && chat.isCreator) { - return

{renderText(lang('ChatList.DeleteAndLeaveGroupConfirmation', chatTitle), ['simple_markdown'])}

; + return ( +

+ {renderText(lang('ChatList.DeleteAndLeaveGroupConfirmation', chatTitle), ['simple_markdown', 'emoji'])} +

+ ); } if ((isChannel && !chat.isCreator) || isBasicGroup || isSuperGroup) { - return

{renderText(lang('ChannelLeaveAlertWithName', chatTitle), ['simple_markdown'])}

; + return

{renderText(lang('ChannelLeaveAlertWithName', chatTitle), ['simple_markdown', 'emoji'])}

; } - return

{renderText(lang('ChatList.DeleteChatConfirmation', contactName), ['simple_markdown'])}

; + return

{renderText(lang('ChatList.DeleteChatConfirmation', contactName), ['simple_markdown', 'emoji'])}

; } function renderActionText() { diff --git a/src/components/left/LeftColumn.tsx b/src/components/left/LeftColumn.tsx index f1a1f3b91..9ba0885ec 100644 --- a/src/components/left/LeftColumn.tsx +++ b/src/components/left/LeftColumn.tsx @@ -164,6 +164,7 @@ const LeftColumn: FC = ({ case SettingsScreens.PrivacyPhoneP2P: case SettingsScreens.PrivacyForwarding: case SettingsScreens.PrivacyGroupChats: + case SettingsScreens.PrivacyVoiceMessages: case SettingsScreens.PrivacyBlockedUsers: case SettingsScreens.ActiveWebsites: case SettingsScreens.TwoFaDisabled: @@ -220,6 +221,10 @@ const LeftColumn: FC = ({ case SettingsScreens.PrivacyForwardingDeniedContacts: setSettingsScreen(SettingsScreens.PrivacyForwarding); return; + case SettingsScreens.PrivacyVoiceMessagesAllowedContacts: + case SettingsScreens.PrivacyVoiceMessagesDeniedContacts: + setSettingsScreen(SettingsScreens.PrivacyVoiceMessages); + return; case SettingsScreens.PrivacyGroupChatsAllowedContacts: case SettingsScreens.PrivacyGroupChatsDeniedContacts: setSettingsScreen(SettingsScreens.PrivacyGroupChats); diff --git a/src/components/left/settings/Settings.scss b/src/components/left/settings/Settings.scss index fa033fbd5..3003e53ff 100644 --- a/src/components/left/settings/Settings.scss +++ b/src/components/left/settings/Settings.scss @@ -58,6 +58,12 @@ } } + .settings-icon-locked { + align-self: center; + margin-right: 0.25rem !important; + font-size: 1rem !important; + } + #monkey { margin-top: 0.5rem; margin-bottom: 1rem; @@ -183,6 +189,7 @@ .multiline-menu-item { white-space: initial; + flex-grow: 1; &.full-size { width: 100%; diff --git a/src/components/left/settings/Settings.tsx b/src/components/left/settings/Settings.tsx index 0484f9de1..f8dc68c7f 100644 --- a/src/components/left/settings/Settings.tsx +++ b/src/components/left/settings/Settings.tsx @@ -104,6 +104,11 @@ const PRIVACY_FORWARDING_SCREENS = [ SettingsScreens.PrivacyForwardingDeniedContacts, ]; +const PRIVACY_VOICE_MESSAGES_SCREENS = [ + SettingsScreens.PrivacyVoiceMessagesAllowedContacts, + SettingsScreens.PrivacyVoiceMessagesDeniedContacts, +]; + const PRIVACY_GROUP_CHATS_SCREENS = [ SettingsScreens.PrivacyGroupChatsAllowedContacts, SettingsScreens.PrivacyGroupChatsDeniedContacts, @@ -178,6 +183,7 @@ const Settings: FC = ({ [SettingsScreens.PrivacyPhoneCall]: PRIVACY_PHONE_CALL_SCREENS.includes(screen), [SettingsScreens.PrivacyPhoneP2P]: PRIVACY_PHONE_P2P_SCREENS.includes(screen), [SettingsScreens.PrivacyForwarding]: PRIVACY_FORWARDING_SCREENS.includes(screen), + [SettingsScreens.PrivacyVoiceMessages]: PRIVACY_VOICE_MESSAGES_SCREENS.includes(screen), [SettingsScreens.PrivacyGroupChats]: PRIVACY_GROUP_CHATS_SCREENS.includes(screen), }; @@ -284,6 +290,7 @@ const Settings: FC = ({ case SettingsScreens.PrivacyPhoneCall: case SettingsScreens.PrivacyPhoneP2P: case SettingsScreens.PrivacyForwarding: + case SettingsScreens.PrivacyVoiceMessages: case SettingsScreens.PrivacyGroupChats: return ( = ({ case SettingsScreens.PrivacyPhoneCallAllowedContacts: case SettingsScreens.PrivacyPhoneP2PAllowedContacts: case SettingsScreens.PrivacyForwardingAllowedContacts: + case SettingsScreens.PrivacyVoiceMessagesAllowedContacts: case SettingsScreens.PrivacyGroupChatsAllowedContacts: return ( = ({ case SettingsScreens.PrivacyPhoneCallDeniedContacts: case SettingsScreens.PrivacyPhoneP2PDeniedContacts: case SettingsScreens.PrivacyForwardingDeniedContacts: + case SettingsScreens.PrivacyVoiceMessagesDeniedContacts: case SettingsScreens.PrivacyGroupChatsDeniedContacts: return ( = ({ return

{lang('Privacy.ProfilePhoto')}

; case SettingsScreens.PrivacyForwarding: return

{lang('PrivacyForwards')}

; + case SettingsScreens.PrivacyVoiceMessages: + return

{lang('PrivacyVoiceMessages')}

; case SettingsScreens.PrivacyGroupChats: return

{lang('AutodownloadGroupChats')}

; case SettingsScreens.PrivacyPhoneNumberAllowedContacts: case SettingsScreens.PrivacyLastSeenAllowedContacts: case SettingsScreens.PrivacyProfilePhotoAllowedContacts: case SettingsScreens.PrivacyForwardingAllowedContacts: + case SettingsScreens.PrivacyVoiceMessagesAllowedContacts: case SettingsScreens.PrivacyGroupChatsAllowedContacts: return

{lang('AlwaysShareWith')}

; case SettingsScreens.PrivacyPhoneNumberDeniedContacts: case SettingsScreens.PrivacyLastSeenDeniedContacts: case SettingsScreens.PrivacyProfilePhotoDeniedContacts: case SettingsScreens.PrivacyForwardingDeniedContacts: + case SettingsScreens.PrivacyVoiceMessagesDeniedContacts: case SettingsScreens.PrivacyGroupChatsDeniedContacts: return

{lang('NeverShareWith')}

; diff --git a/src/components/left/settings/SettingsPrivacy.tsx b/src/components/left/settings/SettingsPrivacy.tsx index 1b7e56374..4c5d32eba 100644 --- a/src/components/left/settings/SettingsPrivacy.tsx +++ b/src/components/left/settings/SettingsPrivacy.tsx @@ -5,6 +5,8 @@ import { getActions, withGlobal } from '../../../global'; import type { ApiPrivacySettings } from '../../../types'; import { SettingsScreens } from '../../../types'; +import { selectIsCurrentUserPremium } from '../../../global/selectors'; + import useLang from '../../../hooks/useLang'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -18,6 +20,7 @@ type OwnProps = { }; type StateProps = { + isCurrentUserPremium?: boolean; hasPassword?: boolean; hasPasscode?: boolean; blockedCount: number; @@ -29,6 +32,7 @@ type StateProps = { privacyLastSeen?: ApiPrivacySettings; privacyProfilePhoto?: ApiPrivacySettings; privacyForwarding?: ApiPrivacySettings; + privacyVoiceMessages?: ApiPrivacySettings; privacyGroupChats?: ApiPrivacySettings; privacyPhoneCall?: ApiPrivacySettings; privacyPhoneP2P?: ApiPrivacySettings; @@ -36,6 +40,7 @@ type StateProps = { const SettingsPrivacy: FC = ({ isActive, + isCurrentUserPremium, hasPassword, hasPasscode, blockedCount, @@ -47,6 +52,7 @@ const SettingsPrivacy: FC = ({ privacyLastSeen, privacyProfilePhoto, privacyForwarding, + privacyVoiceMessages, privacyGroupChats, privacyPhoneCall, privacyPhoneP2P, @@ -62,6 +68,7 @@ const SettingsPrivacy: FC = ({ loadGlobalPrivacySettings, updateGlobalPrivacySettings, loadWebAuthorizations, + showNotification, } = getActions(); useEffect(() => { @@ -91,6 +98,16 @@ const SettingsPrivacy: FC = ({ }); }, [updateGlobalPrivacySettings]); + const handleVoiceMessagesClick = useCallback(() => { + if (isCurrentUserPremium) { + onScreenSelect(SettingsScreens.PrivacyVoiceMessages); + } else { + showNotification({ + message: lang('PrivacyVoiceMessagesPremiumOnly'), + }); + } + }, [isCurrentUserPremium, lang, onScreenSelect, showNotification]); + function getVisibilityValue(setting?: ApiPrivacySettings) { const { visibility } = setting || {}; const blockCount = setting ? setting.blockChatIds.length + setting.blockUserIds.length : 0; @@ -256,6 +273,21 @@ const SettingsPrivacy: FC = ({ + } + className="no-icon" + onClick={handleVoiceMessagesClick} + > +
+ {lang('PrivacyVoiceMessages')} + + {getVisibilityValue(privacyVoiceMessages)} + +
+
( } = global; return { + isCurrentUserPremium: selectIsCurrentUserPremium(global), hasPassword, hasPasscode: Boolean(hasPasscode), blockedCount: blocked.totalCount, @@ -328,6 +361,7 @@ export default memo(withGlobal( privacyLastSeen: privacy.lastSeen, privacyProfilePhoto: privacy.profilePhoto, privacyForwarding: privacy.forwards, + privacyVoiceMessages: privacy.voiceMessages, privacyGroupChats: privacy.chatInvite, privacyPhoneCall: privacy.phoneCall, privacyPhoneP2P: privacy.phoneP2P, diff --git a/src/components/left/settings/SettingsPrivacyVisibility.tsx b/src/components/left/settings/SettingsPrivacyVisibility.tsx index 2f7d558bb..6eb564ae2 100644 --- a/src/components/left/settings/SettingsPrivacyVisibility.tsx +++ b/src/components/left/settings/SettingsPrivacyVisibility.tsx @@ -77,6 +77,8 @@ const SettingsPrivacyVisibility: FC = ({ return lang('PrivacyProfilePhotoTitle'); case SettingsScreens.PrivacyForwarding: return lang('PrivacyForwardsTitle'); + case SettingsScreens.PrivacyVoiceMessages: + return lang('PrivacyVoiceMessagesTitle'); case SettingsScreens.PrivacyGroupChats: return lang('WhoCanAddMe'); case SettingsScreens.PrivacyPhoneCall: @@ -116,6 +118,8 @@ const SettingsPrivacyVisibility: FC = ({ return SettingsScreens.PrivacyPhoneCallAllowedContacts; case SettingsScreens.PrivacyPhoneP2P: return SettingsScreens.PrivacyPhoneP2PAllowedContacts; + case SettingsScreens.PrivacyVoiceMessages: + return SettingsScreens.PrivacyVoiceMessagesAllowedContacts; default: return SettingsScreens.PrivacyGroupChatsAllowedContacts; } @@ -135,6 +139,8 @@ const SettingsPrivacyVisibility: FC = ({ return SettingsScreens.PrivacyPhoneCallDeniedContacts; case SettingsScreens.PrivacyPhoneP2P: return SettingsScreens.PrivacyPhoneP2PDeniedContacts; + case SettingsScreens.PrivacyVoiceMessages: + return SettingsScreens.PrivacyVoiceMessagesDeniedContacts; default: return SettingsScreens.PrivacyGroupChatsDeniedContacts; } @@ -258,6 +264,10 @@ export default memo(withGlobal( privacySettings = privacy.forwards; break; + case SettingsScreens.PrivacyVoiceMessages: + privacySettings = privacy.voiceMessages; + break; + case SettingsScreens.PrivacyGroupChats: privacySettings = privacy.chatInvite; break; diff --git a/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx b/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx index 50d5d1425..5c943cba4 100644 --- a/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx +++ b/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx @@ -143,6 +143,9 @@ function getCurrentPrivacySettings(global: GlobalState, screen: SettingsScreens) case SettingsScreens.PrivacyForwardingAllowedContacts: case SettingsScreens.PrivacyForwardingDeniedContacts: return privacy.forwards; + case SettingsScreens.PrivacyVoiceMessagesAllowedContacts: + case SettingsScreens.PrivacyVoiceMessagesDeniedContacts: + return privacy.voiceMessages; case SettingsScreens.PrivacyGroupChatsDeniedContacts: case SettingsScreens.PrivacyGroupChatsAllowedContacts: return privacy.chatInvite; diff --git a/src/components/left/settings/helpers/privacy.ts b/src/components/left/settings/helpers/privacy.ts index e97647ee4..12bc6e2e6 100644 --- a/src/components/left/settings/helpers/privacy.ts +++ b/src/components/left/settings/helpers/privacy.ts @@ -19,6 +19,10 @@ export function getPrivacyKey(screen: SettingsScreens): ApiPrivacyKey | undefine case SettingsScreens.PrivacyForwardingAllowedContacts: case SettingsScreens.PrivacyForwardingDeniedContacts: return 'forwards'; + case SettingsScreens.PrivacyVoiceMessages: + case SettingsScreens.PrivacyVoiceMessagesAllowedContacts: + case SettingsScreens.PrivacyVoiceMessagesDeniedContacts: + return 'voiceMessages'; case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyGroupChatsAllowedContacts: case SettingsScreens.PrivacyGroupChatsDeniedContacts: diff --git a/src/components/main/BotTrustModal.tsx b/src/components/main/BotTrustModal.tsx index bbc93d62d..951a8dfe4 100644 --- a/src/components/main/BotTrustModal.tsx +++ b/src/components/main/BotTrustModal.tsx @@ -40,7 +40,7 @@ const BotTrustModal: FC = ({ bot, type }) => { onClose={cancelBotTrustRequest} confirmHandler={handleBotTrustAccept} title={title} - textParts={renderText(text, ['br', 'simple_markdown'])} + textParts={renderText(text, ['br', 'simple_markdown', 'emoji'])} /> ); }; diff --git a/src/components/main/Dialogs.tsx b/src/components/main/Dialogs.tsx index 1a2d17081..db395454d 100644 --- a/src/components/main/Dialogs.tsx +++ b/src/components/main/Dialogs.tsx @@ -153,7 +153,8 @@ const Dialogs: FC = ({ dialogs }) => { className="error" title={getErrorHeader(error)} > - {error.hasErrorKey ? getReadableErrorText(error) : renderText(error.message!, ['emoji', 'br'])} + {error.hasErrorKey ? getReadableErrorText(error) + : renderText(error.message!, ['simple_markdown', 'emoji', 'br'])}
diff --git a/src/components/main/Notifications.tsx b/src/components/main/Notifications.tsx index f525480c2..a24bf364a 100644 --- a/src/components/main/Notifications.tsx +++ b/src/components/main/Notifications.tsx @@ -26,11 +26,11 @@ const Notifications: FC = ({ notifications }) => { message, className, localId, action, actionText, title, }) => ( dismissNotification({ localId })} /> diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index e718e29a6..b50ead720 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -85,7 +85,6 @@ type StateProps = { audioMessage?: ApiMessage; messagesCount?: number; isChatWithSelf?: boolean; - isChatWithBot?: boolean; lastSyncTime?: number; hasButtonInHeader?: boolean; shouldSkipHistoryAnimations?: boolean; @@ -111,7 +110,6 @@ const MiddleHeader: FC = ({ chat, messagesCount, isChatWithSelf, - isChatWithBot, lastSyncTime, hasButtonInHeader, shouldSkipHistoryAnimations, @@ -338,7 +336,7 @@ const MiddleHeader: FC = ({ typingStatus={typingStatus} status={connectionStatusText} withDots={Boolean(connectionStatusText)} - withFullInfo={isChatWithBot} + withFullInfo withMediaViewer withUpdatingStatus withVideoAvatar @@ -483,7 +481,6 @@ export default memo(withGlobal( chat, messagesCount, isChatWithSelf: selectIsChatWithSelf(global, chatId), - isChatWithBot, lastSyncTime, shouldSkipHistoryAnimations, currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1), diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index 78f3a6386..eb969e2cc 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -175,6 +175,7 @@ type StateProps = fileSizeLimit: number; captionLimit: number; isCurrentUserPremium?: boolean; + canSendVoiceByPrivacy?: boolean; } & Pick; @@ -214,6 +215,7 @@ const Composer: FC = ({ chat, isForCurrentMessageList, isCurrentUserPremium, + canSendVoiceByPrivacy, connectionState, isChatWithBot, isChatWithSelf, @@ -269,6 +271,7 @@ const Composer: FC = ({ resetOpenChatWithText, callAttachMenuBot, openLimitReachedModal, + showNotification, } = getActions(); const lang = useLang(); @@ -903,14 +906,26 @@ const Composer: FC = ({ } }, [isSelectModeActive, enableHover, disableHover, isReady]); + const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record + && (!canAttachMedia || !canSendVoiceByPrivacy); + const mainButtonHandler = useCallback(() => { switch (mainButtonState) { case MainButtonState.Send: handleSend(); break; - case MainButtonState.Record: - void startRecordingVoice(); + case MainButtonState.Record: { + if (areVoiceMessagesNotAllowed) { + if (!canSendVoiceByPrivacy) { + showNotification({ + message: lang('VoiceMessagesRestrictedByPrivacy', chat?.title), + }); + } + } else { + startRecordingVoice(); + } break; + } case MainButtonState.Edit: handleEditComplete(); break; @@ -926,12 +941,11 @@ const Composer: FC = ({ break; } }, [ - mainButtonState, handleSend, startRecordingVoice, handleEditComplete, activeVoiceRecording, requestCalendar, - pauseRecordingVoice, handleMessageSchedule, + mainButtonState, handleSend, handleEditComplete, activeVoiceRecording, requestCalendar, areVoiceMessagesNotAllowed, + canSendVoiceByPrivacy, showNotification, lang, chat?.title, startRecordingVoice, pauseRecordingVoice, + handleMessageSchedule, ]); - const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record && !canAttachMedia; - const prevEditedMessage = usePrevious(editingMessage, true); const renderedEditedMessage = editingMessage || prevEditedMessage; @@ -948,7 +962,7 @@ const Composer: FC = ({ sendButtonAriaLabel = 'Save edited message'; break; case MainButtonState.Record: - sendButtonAriaLabel = areVoiceMessagesNotAllowed + sendButtonAriaLabel = !canAttachMedia ? 'Conversation.DefaultRestrictedMedia' : 'AccDescrVoiceMessage'; } @@ -1254,6 +1268,7 @@ const Composer: FC = ({ color="secondary" className={buildClassName(mainButtonState, !isReady && 'not-ready', activeVoiceRecording && 'recording')} disabled={areVoiceMessagesNotAllowed} + allowDisabledClick ariaLabel={lang(sendButtonAriaLabel)} onClick={mainButtonHandler} onContextMenu={ @@ -1305,6 +1320,8 @@ export default memo(withGlobal( const isForCurrentMessageList = chatId === currentMessageList?.chatId && threadId === currentMessageList?.threadId && messageListType === currentMessageList?.type; + const user = selectUser(global, chatId); + const canSendVoiceByPrivacy = (user && !user.fullInfo?.noVoiceMessages) ?? true; const editingDraft = messageListType === 'scheduled' ? selectEditingScheduledDraft(global, chatId) @@ -1358,6 +1375,7 @@ export default memo(withGlobal( fileSizeLimit: selectCurrentLimit(global, 'uploadMaxFileparts') * MAX_UPLOAD_FILEPART_SIZE, captionLimit: selectCurrentLimit(global, 'captionLength'), isCurrentUserPremium: selectIsCurrentUserPremium(global), + canSendVoiceByPrivacy, }; }, )(Composer)); diff --git a/src/components/ui/Button.scss b/src/components/ui/Button.scss index 6a9246138..d979bb512 100644 --- a/src/components/ui/Button.scss +++ b/src/components/ui/Button.scss @@ -55,7 +55,9 @@ &.disabled { opacity: 0.5 !important; cursor: default; - pointer-events: none; + &:not(.click-allowed) { + pointer-events: none; + } } &.round { diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index a4830c1ca..3f82f6f96 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -33,6 +33,7 @@ export type OwnProps = { href?: string; download?: string; disabled?: boolean; + allowDisabledClick?: boolean; ripple?: boolean; faded?: boolean; tabIndex?: number; @@ -79,6 +80,7 @@ const Button: FC = ({ href, download, disabled, + allowDisabledClick, ripple, faded, tabIndex, @@ -104,6 +106,7 @@ const Button: FC = ({ pill && 'pill', fluid && 'fluid', disabled && 'disabled', + allowDisabledClick && 'click-allowed', isText && 'text', isLoading && 'loading', ripple && 'has-ripple', @@ -114,7 +117,7 @@ const Button: FC = ({ ); const handleClick = useCallback((e: ReactMouseEvent) => { - if (!disabled && onClick) { + if ((allowDisabledClick || !disabled) && onClick) { onClick(e); } @@ -124,15 +127,15 @@ const Button: FC = ({ setTimeout(() => { setIsClicked(false); }, CLICKED_TIMEOUT); - }, [disabled, onClick, shouldStopPropagation]); + }, [allowDisabledClick, disabled, onClick, shouldStopPropagation]); const handleMouseDown = useCallback((e: ReactMouseEvent) => { if (!noPreventDefault) e.preventDefault(); - if (!disabled && onMouseDown) { + if ((allowDisabledClick || !disabled) && onMouseDown) { onMouseDown(e); } - }, [disabled, noPreventDefault, onMouseDown]); + }, [allowDisabledClick, disabled, noPreventDefault, onMouseDown]); if (href) { return ( diff --git a/src/components/ui/ListItem.scss b/src/components/ui/ListItem.scss index a514de820..cd9b238ec 100644 --- a/src/components/ui/ListItem.scss +++ b/src/components/ui/ListItem.scss @@ -69,12 +69,12 @@ } } - &.disabled { + &.disabled:not(.click-allowed) { pointer-events: none; + } - .ListItem-button { - opacity: 0.5; - } + &.disabled .ListItem-button { + opacity: 0.5; } &:not(.disabled):not(.is-static) { @@ -86,6 +86,7 @@ } @media (hover: hover) { + &:hover, &:focus { --background-color: var(--color-chat-hover); @@ -202,7 +203,8 @@ margin-left: 0.5rem; } - .PremiumIcon, .VerifiedIcon { + .PremiumIcon, + .VerifiedIcon { margin-left: 0.25rem; } diff --git a/src/components/ui/ListItem.tsx b/src/components/ui/ListItem.tsx index 73b00895c..478d08347 100644 --- a/src/components/ui/ListItem.tsx +++ b/src/components/ui/ListItem.tsx @@ -30,11 +30,13 @@ interface OwnProps { icon?: string; leftElement?: TeactNode; secondaryIcon?: string; + rightElement?: TeactNode; buttonClassName?: string; className?: string; style?: string; children: React.ReactNode; disabled?: boolean; + allowDisabledClick?: boolean; ripple?: boolean; narrow?: boolean; inactive?: boolean; @@ -56,10 +58,12 @@ const ListItem: FC = ({ leftElement, buttonClassName, secondaryIcon, + rightElement, className, style, children, disabled, + allowDisabledClick, ripple, narrow, inactive, @@ -108,7 +112,7 @@ const ListItem: FC = ({ ); const handleClick = useCallback((e: React.MouseEvent) => { - if (disabled || !onClick) { + if ((disabled && !allowDisabledClick) || !onClick) { return; } onClick(e); @@ -117,10 +121,10 @@ const ListItem: FC = ({ markIsTouched(); fastRaf(unmarkIsTouched); } - }, [disabled, markIsTouched, onClick, ripple, unmarkIsTouched]); + }, [allowDisabledClick, disabled, markIsTouched, onClick, ripple, unmarkIsTouched]); const handleSecondaryIconClick = (e: React.MouseEvent) => { - if (disabled || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return; + if ((disabled && !allowDisabledClick) || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return; e.stopPropagation(); if (onSecondaryIconClick) { onSecondaryIconClick(e); @@ -154,6 +158,7 @@ const ListItem: FC = ({ ripple && 'has-ripple', narrow && 'narrow', disabled && 'disabled', + allowDisabledClick && 'click-allowed', inactive && 'inactive', contextMenuPosition && 'has-menu-open', focus && 'focus', @@ -201,6 +206,7 @@ const ListItem: FC = ({ )} + {rightElement} {contextActions && contextMenuPosition !== undefined && ( = ({ const [isOpen, setIsOpen] = useState(true); // eslint-disable-next-line no-null/no-null const timerRef = useRef(null); - const { transitionClassNames } = useShowTransition(isOpen); const closeAndDismiss = useCallback(() => { diff --git a/src/global/actions/api/messages.ts b/src/global/actions/api/messages.ts index 4f2fb66cd..44ecb1084 100644 --- a/src/global/actions/api/messages.ts +++ b/src/global/actions/api/messages.ts @@ -69,9 +69,12 @@ import { selectUser, selectSendAs, selectSponsoredMessage, + selectForwardsContainVoiceMessages, } from '../../selectors'; -import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers'; -import { getMessageOriginalId, isServiceNotificationMessage } from '../../helpers'; +import { + debounce, onTickEnd, rafPromise, +} from '../../../util/schedulers'; +import { getMessageOriginalId, getUserFullName, isServiceNotificationMessage } from '../../helpers'; import { getTranslation } from '../../../util/langProvider'; import { ensureProtocol } from '../../../util/ensureProtocol'; @@ -1245,6 +1248,39 @@ addActionHandler('openUrl', (global, actions, payload) => { } }); +addActionHandler('setForwardChatId', async (global, actions, payload) => { + const { id } = payload; + let user = selectUser(global, id); + if (user && selectForwardsContainVoiceMessages(global)) { + if (!user.fullInfo) { + const { accessHash } = user; + user = await callApi('fetchFullUser', { id, accessHash }); + } + + if (user?.fullInfo!.noVoiceMessages) { + actions.showDialog({ + data: { + message: getTranslation('VoiceMessagesRestrictedByPrivacy', getUserFullName(user)), + }, + }); + return; + } + } + + setGlobal({ + ...global, + forwardMessages: { + ...global.forwardMessages, + toChatId: id, + isModalShown: false, + }, + }); + + actions.openChat({ id }); + actions.closeMediaViewer(); + actions.exitMessageSelectMode(); +}); + function countSortedIds(ids: number[], from: number, to: number) { let count = 0; diff --git a/src/global/actions/api/settings.ts b/src/global/actions/api/settings.ts index 7136ddc68..a917deed6 100644 --- a/src/global/actions/api/settings.ts +++ b/src/global/actions/api/settings.ts @@ -318,6 +318,7 @@ addActionHandler('loadPrivacySettings', async (global) => { chatInviteSettings, phoneCallSettings, phoneP2PSettings, + voiceMessagesSettings, ] = await Promise.all([ callApi('fetchPrivacySettings', 'phoneNumber'), callApi('fetchPrivacySettings', 'lastSeen'), @@ -326,6 +327,7 @@ addActionHandler('loadPrivacySettings', async (global) => { callApi('fetchPrivacySettings', 'chatInvite'), callApi('fetchPrivacySettings', 'phoneCall'), callApi('fetchPrivacySettings', 'phoneP2P'), + callApi('fetchPrivacySettings', 'voiceMessages'), ]); if ( @@ -336,6 +338,7 @@ addActionHandler('loadPrivacySettings', async (global) => { || !chatInviteSettings || !phoneCallSettings || !phoneP2PSettings + || !voiceMessagesSettings ) { return; } @@ -354,6 +357,7 @@ addActionHandler('loadPrivacySettings', async (global) => { chatInvite: chatInviteSettings, phoneCall: phoneCallSettings, phoneP2P: phoneP2PSettings, + voiceMessages: voiceMessagesSettings, }, }, }); diff --git a/src/global/actions/ui/messages.ts b/src/global/actions/ui/messages.ts index 9e9170d99..38ef793f5 100644 --- a/src/global/actions/ui/messages.ts +++ b/src/global/actions/ui/messages.ts @@ -471,23 +471,6 @@ addActionHandler('exitForwardMode', (global) => { }); }); -addActionHandler('setForwardChatId', (global, actions, payload) => { - const { id } = payload; - - setGlobal({ - ...global, - forwardMessages: { - ...global.forwardMessages, - toChatId: id, - isModalShown: false, - }, - }); - - actions.openChat({ id }); - actions.closeMediaViewer(); - actions.exitMessageSelectMode(); -}); - addActionHandler('openForwardMenuForSelectedMessages', (global, actions) => { if (!global.selectedMessages) { return; diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index 0fb868cce..a90b9e8fa 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -975,3 +975,13 @@ export function selectCanScheduleUntilOnline(global: GlobalState, id: string) { !isChatWithSelf && !chatBot && isUserId(id) && selectUserStatus(global, id)?.wasOnline, ); } + +export function selectForwardsContainVoiceMessages(global: GlobalState) { + const { messageIds, fromChatId } = global.forwardMessages; + if (!messageIds) return false; + const chatMessages = selectChatMessages(global, fromChatId!); + return messageIds.some((messageId) => { + const message = chatMessages[messageId]; + return Boolean(message.content.voice) || message.content.video?.isRound; + }); +} diff --git a/src/types/index.ts b/src/types/index.ts index 57c6f5838..8f36fb480 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -174,6 +174,7 @@ export enum SettingsScreens { PrivacyPhoneCall, PrivacyPhoneP2P, PrivacyForwarding, + PrivacyVoiceMessages, PrivacyGroupChats, PrivacyPhoneNumberAllowedContacts, PrivacyPhoneNumberDeniedContacts, @@ -187,6 +188,8 @@ export enum SettingsScreens { PrivacyPhoneP2PDeniedContacts, PrivacyForwardingAllowedContacts, PrivacyForwardingDeniedContacts, + PrivacyVoiceMessagesAllowedContacts, + PrivacyVoiceMessagesDeniedContacts, PrivacyGroupChatsAllowedContacts, PrivacyGroupChatsDeniedContacts, PrivacyBlockedUsers, @@ -327,7 +330,7 @@ export enum NewChatMembersProgress { export type ProfileTabType = 'members' | 'commonChats' | 'media' | 'documents' | 'links' | 'audio' | 'voice'; export type SharedMediaType = 'media' | 'documents' | 'links' | 'audio' | 'voice'; -export type ApiPrivacyKey = 'phoneNumber' | 'lastSeen' | 'profilePhoto' | +export type ApiPrivacyKey = 'phoneNumber' | 'lastSeen' | 'profilePhoto' | 'voiceMessages' | 'forwards' | 'chatInvite' | 'phoneCall' | 'phoneP2P'; export type PrivacyVisibility = 'everybody' | 'contacts' | 'nonContacts' | 'nobody';