Settings / Privacy: Voice Messages (#1968)

This commit is contained in:
Alexander Zinchuk 2022-08-31 15:00:38 +02:00
parent 1eb030b2d8
commit 21a989b165
27 changed files with 205 additions and 54 deletions

View File

@ -71,6 +71,8 @@ export function buildPrivacyKey(key: GramJs.TypePrivacyKey): ApiPrivacyKey | und
return 'phoneP2P'; return 'phoneP2P';
case 'PrivacyKeyForwards': case 'PrivacyKeyForwards':
return 'forwards'; return 'forwards';
case 'PrivacyKeyVoiceMessages':
return 'voiceMessages';
case 'PrivacyKeyChatInvite': case 'PrivacyKeyChatInvite':
return 'chatInvite'; return 'chatInvite';
} }

View File

@ -452,6 +452,9 @@ export function buildInputPrivacyKey(privacyKey: ApiPrivacyKey) {
case 'phoneP2P': case 'phoneP2P':
return new GramJs.InputPrivacyKeyPhoneP2P(); return new GramJs.InputPrivacyKeyPhoneP2P();
case 'voiceMessages':
return new GramJs.InputPrivacyKeyVoiceMessages();
} }
return undefined; return undefined;

View File

@ -36,13 +36,13 @@ export async function fetchFullUser({
}) { }) {
const input = buildInputEntity(id, accessHash); const input = buildInputEntity(id, accessHash);
if (!(input instanceof GramJs.InputUser)) { if (!(input instanceof GramJs.InputUser)) {
return; return undefined;
} }
const fullInfo = await invokeRequest(new GramJs.users.GetFullUser({ id: input })); const fullInfo = await invokeRequest(new GramJs.users.GetFullUser({ id: input }));
if (!fullInfo) { if (!fullInfo) {
return; return undefined;
} }
if (fullInfo.fullUser.profilePhoto instanceof GramJs.Photo) { if (fullInfo.fullUser.profilePhoto instanceof GramJs.Photo) {
@ -66,6 +66,8 @@ export async function fetchFullUser({
fullInfo: userWithFullInfo.fullInfo, fullInfo: userWithFullInfo.fullInfo,
}, },
}); });
return userWithFullInfo;
} }
export async function fetchCommonChats(id: string, accessHash?: string, maxId?: string) { export async function fetchCommonChats(id: string, accessHash?: string, maxId?: string) {

View File

@ -149,14 +149,18 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
function renderMessage() { function renderMessage() {
if (isChannel && chat.isCreator) { if (isChannel && chat.isCreator) {
return <p>{renderText(lang('ChatList.DeleteAndLeaveGroupConfirmation', chatTitle), ['simple_markdown'])}</p>; return (
<p>
{renderText(lang('ChatList.DeleteAndLeaveGroupConfirmation', chatTitle), ['simple_markdown', 'emoji'])}
</p>
);
} }
if ((isChannel && !chat.isCreator) || isBasicGroup || isSuperGroup) { if ((isChannel && !chat.isCreator) || isBasicGroup || isSuperGroup) {
return <p>{renderText(lang('ChannelLeaveAlertWithName', chatTitle), ['simple_markdown'])}</p>; return <p>{renderText(lang('ChannelLeaveAlertWithName', chatTitle), ['simple_markdown', 'emoji'])}</p>;
} }
return <p>{renderText(lang('ChatList.DeleteChatConfirmation', contactName), ['simple_markdown'])}</p>; return <p>{renderText(lang('ChatList.DeleteChatConfirmation', contactName), ['simple_markdown', 'emoji'])}</p>;
} }
function renderActionText() { function renderActionText() {

View File

@ -164,6 +164,7 @@ const LeftColumn: FC<StateProps> = ({
case SettingsScreens.PrivacyPhoneP2P: case SettingsScreens.PrivacyPhoneP2P:
case SettingsScreens.PrivacyForwarding: case SettingsScreens.PrivacyForwarding:
case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyGroupChats:
case SettingsScreens.PrivacyVoiceMessages:
case SettingsScreens.PrivacyBlockedUsers: case SettingsScreens.PrivacyBlockedUsers:
case SettingsScreens.ActiveWebsites: case SettingsScreens.ActiveWebsites:
case SettingsScreens.TwoFaDisabled: case SettingsScreens.TwoFaDisabled:
@ -220,6 +221,10 @@ const LeftColumn: FC<StateProps> = ({
case SettingsScreens.PrivacyForwardingDeniedContacts: case SettingsScreens.PrivacyForwardingDeniedContacts:
setSettingsScreen(SettingsScreens.PrivacyForwarding); setSettingsScreen(SettingsScreens.PrivacyForwarding);
return; return;
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
case SettingsScreens.PrivacyVoiceMessagesDeniedContacts:
setSettingsScreen(SettingsScreens.PrivacyVoiceMessages);
return;
case SettingsScreens.PrivacyGroupChatsAllowedContacts: case SettingsScreens.PrivacyGroupChatsAllowedContacts:
case SettingsScreens.PrivacyGroupChatsDeniedContacts: case SettingsScreens.PrivacyGroupChatsDeniedContacts:
setSettingsScreen(SettingsScreens.PrivacyGroupChats); setSettingsScreen(SettingsScreens.PrivacyGroupChats);

View File

@ -58,6 +58,12 @@
} }
} }
.settings-icon-locked {
align-self: center;
margin-right: 0.25rem !important;
font-size: 1rem !important;
}
#monkey { #monkey {
margin-top: 0.5rem; margin-top: 0.5rem;
margin-bottom: 1rem; margin-bottom: 1rem;
@ -183,6 +189,7 @@
.multiline-menu-item { .multiline-menu-item {
white-space: initial; white-space: initial;
flex-grow: 1;
&.full-size { &.full-size {
width: 100%; width: 100%;

View File

@ -104,6 +104,11 @@ const PRIVACY_FORWARDING_SCREENS = [
SettingsScreens.PrivacyForwardingDeniedContacts, SettingsScreens.PrivacyForwardingDeniedContacts,
]; ];
const PRIVACY_VOICE_MESSAGES_SCREENS = [
SettingsScreens.PrivacyVoiceMessagesAllowedContacts,
SettingsScreens.PrivacyVoiceMessagesDeniedContacts,
];
const PRIVACY_GROUP_CHATS_SCREENS = [ const PRIVACY_GROUP_CHATS_SCREENS = [
SettingsScreens.PrivacyGroupChatsAllowedContacts, SettingsScreens.PrivacyGroupChatsAllowedContacts,
SettingsScreens.PrivacyGroupChatsDeniedContacts, SettingsScreens.PrivacyGroupChatsDeniedContacts,
@ -178,6 +183,7 @@ const Settings: FC<OwnProps> = ({
[SettingsScreens.PrivacyPhoneCall]: PRIVACY_PHONE_CALL_SCREENS.includes(screen), [SettingsScreens.PrivacyPhoneCall]: PRIVACY_PHONE_CALL_SCREENS.includes(screen),
[SettingsScreens.PrivacyPhoneP2P]: PRIVACY_PHONE_P2P_SCREENS.includes(screen), [SettingsScreens.PrivacyPhoneP2P]: PRIVACY_PHONE_P2P_SCREENS.includes(screen),
[SettingsScreens.PrivacyForwarding]: PRIVACY_FORWARDING_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), [SettingsScreens.PrivacyGroupChats]: PRIVACY_GROUP_CHATS_SCREENS.includes(screen),
}; };
@ -284,6 +290,7 @@ const Settings: FC<OwnProps> = ({
case SettingsScreens.PrivacyPhoneCall: case SettingsScreens.PrivacyPhoneCall:
case SettingsScreens.PrivacyPhoneP2P: case SettingsScreens.PrivacyPhoneP2P:
case SettingsScreens.PrivacyForwarding: case SettingsScreens.PrivacyForwarding:
case SettingsScreens.PrivacyVoiceMessages:
case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyGroupChats:
return ( return (
<SettingsPrivacyVisibility <SettingsPrivacyVisibility
@ -300,6 +307,7 @@ const Settings: FC<OwnProps> = ({
case SettingsScreens.PrivacyPhoneCallAllowedContacts: case SettingsScreens.PrivacyPhoneCallAllowedContacts:
case SettingsScreens.PrivacyPhoneP2PAllowedContacts: case SettingsScreens.PrivacyPhoneP2PAllowedContacts:
case SettingsScreens.PrivacyForwardingAllowedContacts: case SettingsScreens.PrivacyForwardingAllowedContacts:
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
case SettingsScreens.PrivacyGroupChatsAllowedContacts: case SettingsScreens.PrivacyGroupChatsAllowedContacts:
return ( return (
<SettingsPrivacyVisibilityExceptionList <SettingsPrivacyVisibilityExceptionList
@ -317,6 +325,7 @@ const Settings: FC<OwnProps> = ({
case SettingsScreens.PrivacyPhoneCallDeniedContacts: case SettingsScreens.PrivacyPhoneCallDeniedContacts:
case SettingsScreens.PrivacyPhoneP2PDeniedContacts: case SettingsScreens.PrivacyPhoneP2PDeniedContacts:
case SettingsScreens.PrivacyForwardingDeniedContacts: case SettingsScreens.PrivacyForwardingDeniedContacts:
case SettingsScreens.PrivacyVoiceMessagesDeniedContacts:
case SettingsScreens.PrivacyGroupChatsDeniedContacts: case SettingsScreens.PrivacyGroupChatsDeniedContacts:
return ( return (
<SettingsPrivacyVisibilityExceptionList <SettingsPrivacyVisibilityExceptionList

View File

@ -110,18 +110,22 @@ const SettingsHeader: FC<OwnProps> = ({
return <h3>{lang('Privacy.ProfilePhoto')}</h3>; return <h3>{lang('Privacy.ProfilePhoto')}</h3>;
case SettingsScreens.PrivacyForwarding: case SettingsScreens.PrivacyForwarding:
return <h3>{lang('PrivacyForwards')}</h3>; return <h3>{lang('PrivacyForwards')}</h3>;
case SettingsScreens.PrivacyVoiceMessages:
return <h3>{lang('PrivacyVoiceMessages')}</h3>;
case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyGroupChats:
return <h3>{lang('AutodownloadGroupChats')}</h3>; return <h3>{lang('AutodownloadGroupChats')}</h3>;
case SettingsScreens.PrivacyPhoneNumberAllowedContacts: case SettingsScreens.PrivacyPhoneNumberAllowedContacts:
case SettingsScreens.PrivacyLastSeenAllowedContacts: case SettingsScreens.PrivacyLastSeenAllowedContacts:
case SettingsScreens.PrivacyProfilePhotoAllowedContacts: case SettingsScreens.PrivacyProfilePhotoAllowedContacts:
case SettingsScreens.PrivacyForwardingAllowedContacts: case SettingsScreens.PrivacyForwardingAllowedContacts:
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
case SettingsScreens.PrivacyGroupChatsAllowedContacts: case SettingsScreens.PrivacyGroupChatsAllowedContacts:
return <h3>{lang('AlwaysShareWith')}</h3>; return <h3>{lang('AlwaysShareWith')}</h3>;
case SettingsScreens.PrivacyPhoneNumberDeniedContacts: case SettingsScreens.PrivacyPhoneNumberDeniedContacts:
case SettingsScreens.PrivacyLastSeenDeniedContacts: case SettingsScreens.PrivacyLastSeenDeniedContacts:
case SettingsScreens.PrivacyProfilePhotoDeniedContacts: case SettingsScreens.PrivacyProfilePhotoDeniedContacts:
case SettingsScreens.PrivacyForwardingDeniedContacts: case SettingsScreens.PrivacyForwardingDeniedContacts:
case SettingsScreens.PrivacyVoiceMessagesDeniedContacts:
case SettingsScreens.PrivacyGroupChatsDeniedContacts: case SettingsScreens.PrivacyGroupChatsDeniedContacts:
return <h3>{lang('NeverShareWith')}</h3>; return <h3>{lang('NeverShareWith')}</h3>;

View File

@ -5,6 +5,8 @@ import { getActions, withGlobal } from '../../../global';
import type { ApiPrivacySettings } from '../../../types'; import type { ApiPrivacySettings } from '../../../types';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { selectIsCurrentUserPremium } from '../../../global/selectors';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
@ -18,6 +20,7 @@ type OwnProps = {
}; };
type StateProps = { type StateProps = {
isCurrentUserPremium?: boolean;
hasPassword?: boolean; hasPassword?: boolean;
hasPasscode?: boolean; hasPasscode?: boolean;
blockedCount: number; blockedCount: number;
@ -29,6 +32,7 @@ type StateProps = {
privacyLastSeen?: ApiPrivacySettings; privacyLastSeen?: ApiPrivacySettings;
privacyProfilePhoto?: ApiPrivacySettings; privacyProfilePhoto?: ApiPrivacySettings;
privacyForwarding?: ApiPrivacySettings; privacyForwarding?: ApiPrivacySettings;
privacyVoiceMessages?: ApiPrivacySettings;
privacyGroupChats?: ApiPrivacySettings; privacyGroupChats?: ApiPrivacySettings;
privacyPhoneCall?: ApiPrivacySettings; privacyPhoneCall?: ApiPrivacySettings;
privacyPhoneP2P?: ApiPrivacySettings; privacyPhoneP2P?: ApiPrivacySettings;
@ -36,6 +40,7 @@ type StateProps = {
const SettingsPrivacy: FC<OwnProps & StateProps> = ({ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
isActive, isActive,
isCurrentUserPremium,
hasPassword, hasPassword,
hasPasscode, hasPasscode,
blockedCount, blockedCount,
@ -47,6 +52,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
privacyLastSeen, privacyLastSeen,
privacyProfilePhoto, privacyProfilePhoto,
privacyForwarding, privacyForwarding,
privacyVoiceMessages,
privacyGroupChats, privacyGroupChats,
privacyPhoneCall, privacyPhoneCall,
privacyPhoneP2P, privacyPhoneP2P,
@ -62,6 +68,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
loadGlobalPrivacySettings, loadGlobalPrivacySettings,
updateGlobalPrivacySettings, updateGlobalPrivacySettings,
loadWebAuthorizations, loadWebAuthorizations,
showNotification,
} = getActions(); } = getActions();
useEffect(() => { useEffect(() => {
@ -91,6 +98,16 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
}); });
}, [updateGlobalPrivacySettings]); }, [updateGlobalPrivacySettings]);
const handleVoiceMessagesClick = useCallback(() => {
if (isCurrentUserPremium) {
onScreenSelect(SettingsScreens.PrivacyVoiceMessages);
} else {
showNotification({
message: lang('PrivacyVoiceMessagesPremiumOnly'),
});
}
}, [isCurrentUserPremium, lang, onScreenSelect, showNotification]);
function getVisibilityValue(setting?: ApiPrivacySettings) { function getVisibilityValue(setting?: ApiPrivacySettings) {
const { visibility } = setting || {}; const { visibility } = setting || {};
const blockCount = setting ? setting.blockChatIds.length + setting.blockUserIds.length : 0; const blockCount = setting ? setting.blockChatIds.length + setting.blockUserIds.length : 0;
@ -256,6 +273,21 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
</span> </span>
</div> </div>
</ListItem> </ListItem>
<ListItem
narrow
disabled={!isCurrentUserPremium}
allowDisabledClick
rightElement={!isCurrentUserPremium && <i className="icon-lock-badge settings-icon-locked" />}
className="no-icon"
onClick={handleVoiceMessagesClick}
>
<div className="multiline-menu-item">
<span className="title">{lang('PrivacyVoiceMessages')}</span>
<span className="subtitle" dir="auto">
{getVisibilityValue(privacyVoiceMessages)}
</span>
</div>
</ListItem>
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
@ -317,6 +349,7 @@ export default memo(withGlobal<OwnProps>(
} = global; } = global;
return { return {
isCurrentUserPremium: selectIsCurrentUserPremium(global),
hasPassword, hasPassword,
hasPasscode: Boolean(hasPasscode), hasPasscode: Boolean(hasPasscode),
blockedCount: blocked.totalCount, blockedCount: blocked.totalCount,
@ -328,6 +361,7 @@ export default memo(withGlobal<OwnProps>(
privacyLastSeen: privacy.lastSeen, privacyLastSeen: privacy.lastSeen,
privacyProfilePhoto: privacy.profilePhoto, privacyProfilePhoto: privacy.profilePhoto,
privacyForwarding: privacy.forwards, privacyForwarding: privacy.forwards,
privacyVoiceMessages: privacy.voiceMessages,
privacyGroupChats: privacy.chatInvite, privacyGroupChats: privacy.chatInvite,
privacyPhoneCall: privacy.phoneCall, privacyPhoneCall: privacy.phoneCall,
privacyPhoneP2P: privacy.phoneP2P, privacyPhoneP2P: privacy.phoneP2P,

View File

@ -77,6 +77,8 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
return lang('PrivacyProfilePhotoTitle'); return lang('PrivacyProfilePhotoTitle');
case SettingsScreens.PrivacyForwarding: case SettingsScreens.PrivacyForwarding:
return lang('PrivacyForwardsTitle'); return lang('PrivacyForwardsTitle');
case SettingsScreens.PrivacyVoiceMessages:
return lang('PrivacyVoiceMessagesTitle');
case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyGroupChats:
return lang('WhoCanAddMe'); return lang('WhoCanAddMe');
case SettingsScreens.PrivacyPhoneCall: case SettingsScreens.PrivacyPhoneCall:
@ -116,6 +118,8 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
return SettingsScreens.PrivacyPhoneCallAllowedContacts; return SettingsScreens.PrivacyPhoneCallAllowedContacts;
case SettingsScreens.PrivacyPhoneP2P: case SettingsScreens.PrivacyPhoneP2P:
return SettingsScreens.PrivacyPhoneP2PAllowedContacts; return SettingsScreens.PrivacyPhoneP2PAllowedContacts;
case SettingsScreens.PrivacyVoiceMessages:
return SettingsScreens.PrivacyVoiceMessagesAllowedContacts;
default: default:
return SettingsScreens.PrivacyGroupChatsAllowedContacts; return SettingsScreens.PrivacyGroupChatsAllowedContacts;
} }
@ -135,6 +139,8 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
return SettingsScreens.PrivacyPhoneCallDeniedContacts; return SettingsScreens.PrivacyPhoneCallDeniedContacts;
case SettingsScreens.PrivacyPhoneP2P: case SettingsScreens.PrivacyPhoneP2P:
return SettingsScreens.PrivacyPhoneP2PDeniedContacts; return SettingsScreens.PrivacyPhoneP2PDeniedContacts;
case SettingsScreens.PrivacyVoiceMessages:
return SettingsScreens.PrivacyVoiceMessagesDeniedContacts;
default: default:
return SettingsScreens.PrivacyGroupChatsDeniedContacts; return SettingsScreens.PrivacyGroupChatsDeniedContacts;
} }
@ -258,6 +264,10 @@ export default memo(withGlobal<OwnProps>(
privacySettings = privacy.forwards; privacySettings = privacy.forwards;
break; break;
case SettingsScreens.PrivacyVoiceMessages:
privacySettings = privacy.voiceMessages;
break;
case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyGroupChats:
privacySettings = privacy.chatInvite; privacySettings = privacy.chatInvite;
break; break;

View File

@ -143,6 +143,9 @@ function getCurrentPrivacySettings(global: GlobalState, screen: SettingsScreens)
case SettingsScreens.PrivacyForwardingAllowedContacts: case SettingsScreens.PrivacyForwardingAllowedContacts:
case SettingsScreens.PrivacyForwardingDeniedContacts: case SettingsScreens.PrivacyForwardingDeniedContacts:
return privacy.forwards; return privacy.forwards;
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
case SettingsScreens.PrivacyVoiceMessagesDeniedContacts:
return privacy.voiceMessages;
case SettingsScreens.PrivacyGroupChatsDeniedContacts: case SettingsScreens.PrivacyGroupChatsDeniedContacts:
case SettingsScreens.PrivacyGroupChatsAllowedContacts: case SettingsScreens.PrivacyGroupChatsAllowedContacts:
return privacy.chatInvite; return privacy.chatInvite;

View File

@ -19,6 +19,10 @@ export function getPrivacyKey(screen: SettingsScreens): ApiPrivacyKey | undefine
case SettingsScreens.PrivacyForwardingAllowedContacts: case SettingsScreens.PrivacyForwardingAllowedContacts:
case SettingsScreens.PrivacyForwardingDeniedContacts: case SettingsScreens.PrivacyForwardingDeniedContacts:
return 'forwards'; return 'forwards';
case SettingsScreens.PrivacyVoiceMessages:
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
case SettingsScreens.PrivacyVoiceMessagesDeniedContacts:
return 'voiceMessages';
case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyGroupChats:
case SettingsScreens.PrivacyGroupChatsAllowedContacts: case SettingsScreens.PrivacyGroupChatsAllowedContacts:
case SettingsScreens.PrivacyGroupChatsDeniedContacts: case SettingsScreens.PrivacyGroupChatsDeniedContacts:

View File

@ -40,7 +40,7 @@ const BotTrustModal: FC<OwnProps> = ({ bot, type }) => {
onClose={cancelBotTrustRequest} onClose={cancelBotTrustRequest}
confirmHandler={handleBotTrustAccept} confirmHandler={handleBotTrustAccept}
title={title} title={title}
textParts={renderText(text, ['br', 'simple_markdown'])} textParts={renderText(text, ['br', 'simple_markdown', 'emoji'])}
/> />
); );
}; };

View File

@ -153,7 +153,8 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
className="error" className="error"
title={getErrorHeader(error)} title={getErrorHeader(error)}
> >
{error.hasErrorKey ? getReadableErrorText(error) : renderText(error.message!, ['emoji', 'br'])} {error.hasErrorKey ? getReadableErrorText(error)
: renderText(error.message!, ['simple_markdown', 'emoji', 'br'])}
<div> <div>
<Button isText onClick={closeModal}>{lang('OK')}</Button> <Button isText onClick={closeModal}>{lang('OK')}</Button>
</div> </div>

View File

@ -26,11 +26,11 @@ const Notifications: FC<StateProps> = ({ notifications }) => {
message, className, localId, action, actionText, title, message, className, localId, action, actionText, title,
}) => ( }) => (
<Notification <Notification
title={title ? renderText(title, ['emoji', 'br', 'links', 'simple_markdown']) : undefined} title={title ? renderText(title, ['simple_markdown', 'emoji', 'br', 'links']) : undefined}
action={action} action={action}
actionText={actionText} actionText={actionText}
className={className} className={className}
message={renderText(message, ['emoji', 'br', 'links', 'simple_markdown'])} message={renderText(message, ['simple_markdown', 'emoji', 'br', 'links'])}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onDismiss={() => dismissNotification({ localId })} onDismiss={() => dismissNotification({ localId })}
/> />

View File

@ -85,7 +85,6 @@ type StateProps = {
audioMessage?: ApiMessage; audioMessage?: ApiMessage;
messagesCount?: number; messagesCount?: number;
isChatWithSelf?: boolean; isChatWithSelf?: boolean;
isChatWithBot?: boolean;
lastSyncTime?: number; lastSyncTime?: number;
hasButtonInHeader?: boolean; hasButtonInHeader?: boolean;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
@ -111,7 +110,6 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
chat, chat,
messagesCount, messagesCount,
isChatWithSelf, isChatWithSelf,
isChatWithBot,
lastSyncTime, lastSyncTime,
hasButtonInHeader, hasButtonInHeader,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
@ -338,7 +336,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
typingStatus={typingStatus} typingStatus={typingStatus}
status={connectionStatusText} status={connectionStatusText}
withDots={Boolean(connectionStatusText)} withDots={Boolean(connectionStatusText)}
withFullInfo={isChatWithBot} withFullInfo
withMediaViewer withMediaViewer
withUpdatingStatus withUpdatingStatus
withVideoAvatar withVideoAvatar
@ -483,7 +481,6 @@ export default memo(withGlobal<OwnProps>(
chat, chat,
messagesCount, messagesCount,
isChatWithSelf: selectIsChatWithSelf(global, chatId), isChatWithSelf: selectIsChatWithSelf(global, chatId),
isChatWithBot,
lastSyncTime, lastSyncTime,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1), currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1),

View File

@ -175,6 +175,7 @@ type StateProps =
fileSizeLimit: number; fileSizeLimit: number;
captionLimit: number; captionLimit: number;
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
canSendVoiceByPrivacy?: boolean;
} }
& Pick<GlobalState, 'connectionState'>; & Pick<GlobalState, 'connectionState'>;
@ -214,6 +215,7 @@ const Composer: FC<OwnProps & StateProps> = ({
chat, chat,
isForCurrentMessageList, isForCurrentMessageList,
isCurrentUserPremium, isCurrentUserPremium,
canSendVoiceByPrivacy,
connectionState, connectionState,
isChatWithBot, isChatWithBot,
isChatWithSelf, isChatWithSelf,
@ -269,6 +271,7 @@ const Composer: FC<OwnProps & StateProps> = ({
resetOpenChatWithText, resetOpenChatWithText,
callAttachMenuBot, callAttachMenuBot,
openLimitReachedModal, openLimitReachedModal,
showNotification,
} = getActions(); } = getActions();
const lang = useLang(); const lang = useLang();
@ -903,14 +906,26 @@ const Composer: FC<OwnProps & StateProps> = ({
} }
}, [isSelectModeActive, enableHover, disableHover, isReady]); }, [isSelectModeActive, enableHover, disableHover, isReady]);
const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record
&& (!canAttachMedia || !canSendVoiceByPrivacy);
const mainButtonHandler = useCallback(() => { const mainButtonHandler = useCallback(() => {
switch (mainButtonState) { switch (mainButtonState) {
case MainButtonState.Send: case MainButtonState.Send:
handleSend(); handleSend();
break; break;
case MainButtonState.Record: case MainButtonState.Record: {
void startRecordingVoice(); if (areVoiceMessagesNotAllowed) {
if (!canSendVoiceByPrivacy) {
showNotification({
message: lang('VoiceMessagesRestrictedByPrivacy', chat?.title),
});
}
} else {
startRecordingVoice();
}
break; break;
}
case MainButtonState.Edit: case MainButtonState.Edit:
handleEditComplete(); handleEditComplete();
break; break;
@ -926,12 +941,11 @@ const Composer: FC<OwnProps & StateProps> = ({
break; break;
} }
}, [ }, [
mainButtonState, handleSend, startRecordingVoice, handleEditComplete, activeVoiceRecording, requestCalendar, mainButtonState, handleSend, handleEditComplete, activeVoiceRecording, requestCalendar, areVoiceMessagesNotAllowed,
pauseRecordingVoice, handleMessageSchedule, canSendVoiceByPrivacy, showNotification, lang, chat?.title, startRecordingVoice, pauseRecordingVoice,
handleMessageSchedule,
]); ]);
const areVoiceMessagesNotAllowed = mainButtonState === MainButtonState.Record && !canAttachMedia;
const prevEditedMessage = usePrevious(editingMessage, true); const prevEditedMessage = usePrevious(editingMessage, true);
const renderedEditedMessage = editingMessage || prevEditedMessage; const renderedEditedMessage = editingMessage || prevEditedMessage;
@ -948,7 +962,7 @@ const Composer: FC<OwnProps & StateProps> = ({
sendButtonAriaLabel = 'Save edited message'; sendButtonAriaLabel = 'Save edited message';
break; break;
case MainButtonState.Record: case MainButtonState.Record:
sendButtonAriaLabel = areVoiceMessagesNotAllowed sendButtonAriaLabel = !canAttachMedia
? 'Conversation.DefaultRestrictedMedia' ? 'Conversation.DefaultRestrictedMedia'
: 'AccDescrVoiceMessage'; : 'AccDescrVoiceMessage';
} }
@ -1254,6 +1268,7 @@ const Composer: FC<OwnProps & StateProps> = ({
color="secondary" color="secondary"
className={buildClassName(mainButtonState, !isReady && 'not-ready', activeVoiceRecording && 'recording')} className={buildClassName(mainButtonState, !isReady && 'not-ready', activeVoiceRecording && 'recording')}
disabled={areVoiceMessagesNotAllowed} disabled={areVoiceMessagesNotAllowed}
allowDisabledClick
ariaLabel={lang(sendButtonAriaLabel)} ariaLabel={lang(sendButtonAriaLabel)}
onClick={mainButtonHandler} onClick={mainButtonHandler}
onContextMenu={ onContextMenu={
@ -1305,6 +1320,8 @@ export default memo(withGlobal<OwnProps>(
const isForCurrentMessageList = chatId === currentMessageList?.chatId const isForCurrentMessageList = chatId === currentMessageList?.chatId
&& threadId === currentMessageList?.threadId && threadId === currentMessageList?.threadId
&& messageListType === currentMessageList?.type; && messageListType === currentMessageList?.type;
const user = selectUser(global, chatId);
const canSendVoiceByPrivacy = (user && !user.fullInfo?.noVoiceMessages) ?? true;
const editingDraft = messageListType === 'scheduled' const editingDraft = messageListType === 'scheduled'
? selectEditingScheduledDraft(global, chatId) ? selectEditingScheduledDraft(global, chatId)
@ -1358,6 +1375,7 @@ export default memo(withGlobal<OwnProps>(
fileSizeLimit: selectCurrentLimit(global, 'uploadMaxFileparts') * MAX_UPLOAD_FILEPART_SIZE, fileSizeLimit: selectCurrentLimit(global, 'uploadMaxFileparts') * MAX_UPLOAD_FILEPART_SIZE,
captionLimit: selectCurrentLimit(global, 'captionLength'), captionLimit: selectCurrentLimit(global, 'captionLength'),
isCurrentUserPremium: selectIsCurrentUserPremium(global), isCurrentUserPremium: selectIsCurrentUserPremium(global),
canSendVoiceByPrivacy,
}; };
}, },
)(Composer)); )(Composer));

View File

@ -55,7 +55,9 @@
&.disabled { &.disabled {
opacity: 0.5 !important; opacity: 0.5 !important;
cursor: default; cursor: default;
pointer-events: none; &:not(.click-allowed) {
pointer-events: none;
}
} }
&.round { &.round {

View File

@ -33,6 +33,7 @@ export type OwnProps = {
href?: string; href?: string;
download?: string; download?: string;
disabled?: boolean; disabled?: boolean;
allowDisabledClick?: boolean;
ripple?: boolean; ripple?: boolean;
faded?: boolean; faded?: boolean;
tabIndex?: number; tabIndex?: number;
@ -79,6 +80,7 @@ const Button: FC<OwnProps> = ({
href, href,
download, download,
disabled, disabled,
allowDisabledClick,
ripple, ripple,
faded, faded,
tabIndex, tabIndex,
@ -104,6 +106,7 @@ const Button: FC<OwnProps> = ({
pill && 'pill', pill && 'pill',
fluid && 'fluid', fluid && 'fluid',
disabled && 'disabled', disabled && 'disabled',
allowDisabledClick && 'click-allowed',
isText && 'text', isText && 'text',
isLoading && 'loading', isLoading && 'loading',
ripple && 'has-ripple', ripple && 'has-ripple',
@ -114,7 +117,7 @@ const Button: FC<OwnProps> = ({
); );
const handleClick = useCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => { const handleClick = useCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
if (!disabled && onClick) { if ((allowDisabledClick || !disabled) && onClick) {
onClick(e); onClick(e);
} }
@ -124,15 +127,15 @@ const Button: FC<OwnProps> = ({
setTimeout(() => { setTimeout(() => {
setIsClicked(false); setIsClicked(false);
}, CLICKED_TIMEOUT); }, CLICKED_TIMEOUT);
}, [disabled, onClick, shouldStopPropagation]); }, [allowDisabledClick, disabled, onClick, shouldStopPropagation]);
const handleMouseDown = useCallback((e: ReactMouseEvent<HTMLButtonElement>) => { const handleMouseDown = useCallback((e: ReactMouseEvent<HTMLButtonElement>) => {
if (!noPreventDefault) e.preventDefault(); if (!noPreventDefault) e.preventDefault();
if (!disabled && onMouseDown) { if ((allowDisabledClick || !disabled) && onMouseDown) {
onMouseDown(e); onMouseDown(e);
} }
}, [disabled, noPreventDefault, onMouseDown]); }, [allowDisabledClick, disabled, noPreventDefault, onMouseDown]);
if (href) { if (href) {
return ( return (

View File

@ -69,12 +69,12 @@
} }
} }
&.disabled { &.disabled:not(.click-allowed) {
pointer-events: none; pointer-events: none;
}
.ListItem-button { &.disabled .ListItem-button {
opacity: 0.5; opacity: 0.5;
}
} }
&:not(.disabled):not(.is-static) { &:not(.disabled):not(.is-static) {
@ -86,6 +86,7 @@
} }
@media (hover: hover) { @media (hover: hover) {
&:hover, &:hover,
&:focus { &:focus {
--background-color: var(--color-chat-hover); --background-color: var(--color-chat-hover);
@ -202,7 +203,8 @@
margin-left: 0.5rem; margin-left: 0.5rem;
} }
.PremiumIcon, .VerifiedIcon { .PremiumIcon,
.VerifiedIcon {
margin-left: 0.25rem; margin-left: 0.25rem;
} }

View File

@ -30,11 +30,13 @@ interface OwnProps {
icon?: string; icon?: string;
leftElement?: TeactNode; leftElement?: TeactNode;
secondaryIcon?: string; secondaryIcon?: string;
rightElement?: TeactNode;
buttonClassName?: string; buttonClassName?: string;
className?: string; className?: string;
style?: string; style?: string;
children: React.ReactNode; children: React.ReactNode;
disabled?: boolean; disabled?: boolean;
allowDisabledClick?: boolean;
ripple?: boolean; ripple?: boolean;
narrow?: boolean; narrow?: boolean;
inactive?: boolean; inactive?: boolean;
@ -56,10 +58,12 @@ const ListItem: FC<OwnProps> = ({
leftElement, leftElement,
buttonClassName, buttonClassName,
secondaryIcon, secondaryIcon,
rightElement,
className, className,
style, style,
children, children,
disabled, disabled,
allowDisabledClick,
ripple, ripple,
narrow, narrow,
inactive, inactive,
@ -108,7 +112,7 @@ const ListItem: FC<OwnProps> = ({
); );
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (disabled || !onClick) { if ((disabled && !allowDisabledClick) || !onClick) {
return; return;
} }
onClick(e); onClick(e);
@ -117,10 +121,10 @@ const ListItem: FC<OwnProps> = ({
markIsTouched(); markIsTouched();
fastRaf(unmarkIsTouched); fastRaf(unmarkIsTouched);
} }
}, [disabled, markIsTouched, onClick, ripple, unmarkIsTouched]); }, [allowDisabledClick, disabled, markIsTouched, onClick, ripple, unmarkIsTouched]);
const handleSecondaryIconClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { const handleSecondaryIconClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (disabled || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return; if ((disabled && !allowDisabledClick) || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return;
e.stopPropagation(); e.stopPropagation();
if (onSecondaryIconClick) { if (onSecondaryIconClick) {
onSecondaryIconClick(e); onSecondaryIconClick(e);
@ -154,6 +158,7 @@ const ListItem: FC<OwnProps> = ({
ripple && 'has-ripple', ripple && 'has-ripple',
narrow && 'narrow', narrow && 'narrow',
disabled && 'disabled', disabled && 'disabled',
allowDisabledClick && 'click-allowed',
inactive && 'inactive', inactive && 'inactive',
contextMenuPosition && 'has-menu-open', contextMenuPosition && 'has-menu-open',
focus && 'focus', focus && 'focus',
@ -201,6 +206,7 @@ const ListItem: FC<OwnProps> = ({
<i className={`icon-${secondaryIcon}`} /> <i className={`icon-${secondaryIcon}`} />
</Button> </Button>
)} )}
{rightElement}
</div> </div>
{contextActions && contextMenuPosition !== undefined && ( {contextActions && contextMenuPosition !== undefined && (
<Menu <Menu

View File

@ -40,7 +40,6 @@ const Notification: FC<OwnProps> = ({
const [isOpen, setIsOpen] = useState(true); const [isOpen, setIsOpen] = useState(true);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const timerRef = useRef<number | undefined>(null); const timerRef = useRef<number | undefined>(null);
const { transitionClassNames } = useShowTransition(isOpen); const { transitionClassNames } = useShowTransition(isOpen);
const closeAndDismiss = useCallback(() => { const closeAndDismiss = useCallback(() => {

View File

@ -69,9 +69,12 @@ import {
selectUser, selectUser,
selectSendAs, selectSendAs,
selectSponsoredMessage, selectSponsoredMessage,
selectForwardsContainVoiceMessages,
} from '../../selectors'; } from '../../selectors';
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers'; import {
import { getMessageOriginalId, isServiceNotificationMessage } from '../../helpers'; debounce, onTickEnd, rafPromise,
} from '../../../util/schedulers';
import { getMessageOriginalId, getUserFullName, isServiceNotificationMessage } from '../../helpers';
import { getTranslation } from '../../../util/langProvider'; import { getTranslation } from '../../../util/langProvider';
import { ensureProtocol } from '../../../util/ensureProtocol'; 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) { function countSortedIds(ids: number[], from: number, to: number) {
let count = 0; let count = 0;

View File

@ -318,6 +318,7 @@ addActionHandler('loadPrivacySettings', async (global) => {
chatInviteSettings, chatInviteSettings,
phoneCallSettings, phoneCallSettings,
phoneP2PSettings, phoneP2PSettings,
voiceMessagesSettings,
] = await Promise.all([ ] = await Promise.all([
callApi('fetchPrivacySettings', 'phoneNumber'), callApi('fetchPrivacySettings', 'phoneNumber'),
callApi('fetchPrivacySettings', 'lastSeen'), callApi('fetchPrivacySettings', 'lastSeen'),
@ -326,6 +327,7 @@ addActionHandler('loadPrivacySettings', async (global) => {
callApi('fetchPrivacySettings', 'chatInvite'), callApi('fetchPrivacySettings', 'chatInvite'),
callApi('fetchPrivacySettings', 'phoneCall'), callApi('fetchPrivacySettings', 'phoneCall'),
callApi('fetchPrivacySettings', 'phoneP2P'), callApi('fetchPrivacySettings', 'phoneP2P'),
callApi('fetchPrivacySettings', 'voiceMessages'),
]); ]);
if ( if (
@ -336,6 +338,7 @@ addActionHandler('loadPrivacySettings', async (global) => {
|| !chatInviteSettings || !chatInviteSettings
|| !phoneCallSettings || !phoneCallSettings
|| !phoneP2PSettings || !phoneP2PSettings
|| !voiceMessagesSettings
) { ) {
return; return;
} }
@ -354,6 +357,7 @@ addActionHandler('loadPrivacySettings', async (global) => {
chatInvite: chatInviteSettings, chatInvite: chatInviteSettings,
phoneCall: phoneCallSettings, phoneCall: phoneCallSettings,
phoneP2P: phoneP2PSettings, phoneP2P: phoneP2PSettings,
voiceMessages: voiceMessagesSettings,
}, },
}, },
}); });

View File

@ -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) => { addActionHandler('openForwardMenuForSelectedMessages', (global, actions) => {
if (!global.selectedMessages) { if (!global.selectedMessages) {
return; return;

View File

@ -975,3 +975,13 @@ export function selectCanScheduleUntilOnline(global: GlobalState, id: string) {
!isChatWithSelf && !chatBot && isUserId(id) && selectUserStatus(global, id)?.wasOnline, !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;
});
}

View File

@ -174,6 +174,7 @@ export enum SettingsScreens {
PrivacyPhoneCall, PrivacyPhoneCall,
PrivacyPhoneP2P, PrivacyPhoneP2P,
PrivacyForwarding, PrivacyForwarding,
PrivacyVoiceMessages,
PrivacyGroupChats, PrivacyGroupChats,
PrivacyPhoneNumberAllowedContacts, PrivacyPhoneNumberAllowedContacts,
PrivacyPhoneNumberDeniedContacts, PrivacyPhoneNumberDeniedContacts,
@ -187,6 +188,8 @@ export enum SettingsScreens {
PrivacyPhoneP2PDeniedContacts, PrivacyPhoneP2PDeniedContacts,
PrivacyForwardingAllowedContacts, PrivacyForwardingAllowedContacts,
PrivacyForwardingDeniedContacts, PrivacyForwardingDeniedContacts,
PrivacyVoiceMessagesAllowedContacts,
PrivacyVoiceMessagesDeniedContacts,
PrivacyGroupChatsAllowedContacts, PrivacyGroupChatsAllowedContacts,
PrivacyGroupChatsDeniedContacts, PrivacyGroupChatsDeniedContacts,
PrivacyBlockedUsers, PrivacyBlockedUsers,
@ -327,7 +330,7 @@ export enum NewChatMembersProgress {
export type ProfileTabType = 'members' | 'commonChats' | 'media' | 'documents' | 'links' | 'audio' | 'voice'; export type ProfileTabType = 'members' | 'commonChats' | 'media' | 'documents' | 'links' | 'audio' | 'voice';
export type SharedMediaType = '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'; 'forwards' | 'chatInvite' | 'phoneCall' | 'phoneP2P';
export type PrivacyVisibility = 'everybody' | 'contacts' | 'nonContacts' | 'nobody'; export type PrivacyVisibility = 'everybody' | 'contacts' | 'nonContacts' | 'nobody';