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';
case 'PrivacyKeyForwards':
return 'forwards';
case 'PrivacyKeyVoiceMessages':
return 'voiceMessages';
case 'PrivacyKeyChatInvite':
return 'chatInvite';
}

View File

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

View File

@ -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) {

View File

@ -149,14 +149,18 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
function renderMessage() {
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) {
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() {

View File

@ -164,6 +164,7 @@ const LeftColumn: FC<StateProps> = ({
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<StateProps> = ({
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);

View File

@ -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%;

View File

@ -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<OwnProps> = ({
[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<OwnProps> = ({
case SettingsScreens.PrivacyPhoneCall:
case SettingsScreens.PrivacyPhoneP2P:
case SettingsScreens.PrivacyForwarding:
case SettingsScreens.PrivacyVoiceMessages:
case SettingsScreens.PrivacyGroupChats:
return (
<SettingsPrivacyVisibility
@ -300,6 +307,7 @@ const Settings: FC<OwnProps> = ({
case SettingsScreens.PrivacyPhoneCallAllowedContacts:
case SettingsScreens.PrivacyPhoneP2PAllowedContacts:
case SettingsScreens.PrivacyForwardingAllowedContacts:
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
case SettingsScreens.PrivacyGroupChatsAllowedContacts:
return (
<SettingsPrivacyVisibilityExceptionList
@ -317,6 +325,7 @@ const Settings: FC<OwnProps> = ({
case SettingsScreens.PrivacyPhoneCallDeniedContacts:
case SettingsScreens.PrivacyPhoneP2PDeniedContacts:
case SettingsScreens.PrivacyForwardingDeniedContacts:
case SettingsScreens.PrivacyVoiceMessagesDeniedContacts:
case SettingsScreens.PrivacyGroupChatsDeniedContacts:
return (
<SettingsPrivacyVisibilityExceptionList

View File

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

View File

@ -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<OwnProps & StateProps> = ({
isActive,
isCurrentUserPremium,
hasPassword,
hasPasscode,
blockedCount,
@ -47,6 +52,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
privacyLastSeen,
privacyProfilePhoto,
privacyForwarding,
privacyVoiceMessages,
privacyGroupChats,
privacyPhoneCall,
privacyPhoneP2P,
@ -62,6 +68,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
loadGlobalPrivacySettings,
updateGlobalPrivacySettings,
loadWebAuthorizations,
showNotification,
} = getActions();
useEffect(() => {
@ -91,6 +98,16 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
});
}, [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<OwnProps & StateProps> = ({
</span>
</div>
</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
narrow
className="no-icon"
@ -317,6 +349,7 @@ export default memo(withGlobal<OwnProps>(
} = global;
return {
isCurrentUserPremium: selectIsCurrentUserPremium(global),
hasPassword,
hasPasscode: Boolean(hasPasscode),
blockedCount: blocked.totalCount,
@ -328,6 +361,7 @@ export default memo(withGlobal<OwnProps>(
privacyLastSeen: privacy.lastSeen,
privacyProfilePhoto: privacy.profilePhoto,
privacyForwarding: privacy.forwards,
privacyVoiceMessages: privacy.voiceMessages,
privacyGroupChats: privacy.chatInvite,
privacyPhoneCall: privacy.phoneCall,
privacyPhoneP2P: privacy.phoneP2P,

View File

@ -77,6 +77,8 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
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<OwnProps>(
privacySettings = privacy.forwards;
break;
case SettingsScreens.PrivacyVoiceMessages:
privacySettings = privacy.voiceMessages;
break;
case SettingsScreens.PrivacyGroupChats:
privacySettings = privacy.chatInvite;
break;

View File

@ -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;

View File

@ -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:

View File

@ -40,7 +40,7 @@ const BotTrustModal: FC<OwnProps> = ({ bot, type }) => {
onClose={cancelBotTrustRequest}
confirmHandler={handleBotTrustAccept}
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"
title={getErrorHeader(error)}
>
{error.hasErrorKey ? getReadableErrorText(error) : renderText(error.message!, ['emoji', 'br'])}
{error.hasErrorKey ? getReadableErrorText(error)
: renderText(error.message!, ['simple_markdown', 'emoji', 'br'])}
<div>
<Button isText onClick={closeModal}>{lang('OK')}</Button>
</div>

View File

@ -26,11 +26,11 @@ const Notifications: FC<StateProps> = ({ notifications }) => {
message, className, localId, action, actionText, title,
}) => (
<Notification
title={title ? renderText(title, ['emoji', 'br', 'links', 'simple_markdown']) : undefined}
title={title ? renderText(title, ['simple_markdown', 'emoji', 'br', 'links']) : undefined}
action={action}
actionText={actionText}
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
onDismiss={() => dismissNotification({ localId })}
/>

View File

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

View File

@ -175,6 +175,7 @@ type StateProps =
fileSizeLimit: number;
captionLimit: number;
isCurrentUserPremium?: boolean;
canSendVoiceByPrivacy?: boolean;
}
& Pick<GlobalState, 'connectionState'>;
@ -214,6 +215,7 @@ const Composer: FC<OwnProps & StateProps> = ({
chat,
isForCurrentMessageList,
isCurrentUserPremium,
canSendVoiceByPrivacy,
connectionState,
isChatWithBot,
isChatWithSelf,
@ -269,6 +271,7 @@ const Composer: FC<OwnProps & StateProps> = ({
resetOpenChatWithText,
callAttachMenuBot,
openLimitReachedModal,
showNotification,
} = getActions();
const lang = useLang();
@ -903,14 +906,26 @@ const Composer: FC<OwnProps & StateProps> = ({
}
}, [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<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
sendButtonAriaLabel = 'Save edited message';
break;
case MainButtonState.Record:
sendButtonAriaLabel = areVoiceMessagesNotAllowed
sendButtonAriaLabel = !canAttachMedia
? 'Conversation.DefaultRestrictedMedia'
: 'AccDescrVoiceMessage';
}
@ -1254,6 +1268,7 @@ const Composer: FC<OwnProps & StateProps> = ({
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<OwnProps>(
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<OwnProps>(
fileSizeLimit: selectCurrentLimit(global, 'uploadMaxFileparts') * MAX_UPLOAD_FILEPART_SIZE,
captionLimit: selectCurrentLimit(global, 'captionLength'),
isCurrentUserPremium: selectIsCurrentUserPremium(global),
canSendVoiceByPrivacy,
};
},
)(Composer));

View File

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

View File

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

View File

@ -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;
}

View File

@ -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<OwnProps> = ({
leftElement,
buttonClassName,
secondaryIcon,
rightElement,
className,
style,
children,
disabled,
allowDisabledClick,
ripple,
narrow,
inactive,
@ -108,7 +112,7 @@ const ListItem: FC<OwnProps> = ({
);
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (disabled || !onClick) {
if ((disabled && !allowDisabledClick) || !onClick) {
return;
}
onClick(e);
@ -117,10 +121,10 @@ const ListItem: FC<OwnProps> = ({
markIsTouched();
fastRaf(unmarkIsTouched);
}
}, [disabled, markIsTouched, onClick, ripple, unmarkIsTouched]);
}, [allowDisabledClick, disabled, markIsTouched, onClick, ripple, unmarkIsTouched]);
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();
if (onSecondaryIconClick) {
onSecondaryIconClick(e);
@ -154,6 +158,7 @@ const ListItem: FC<OwnProps> = ({
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<OwnProps> = ({
<i className={`icon-${secondaryIcon}`} />
</Button>
)}
{rightElement}
</div>
{contextActions && contextMenuPosition !== undefined && (
<Menu

View File

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

View File

@ -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;

View File

@ -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,
},
},
});

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) => {
if (!global.selectedMessages) {
return;

View File

@ -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;
});
}

View File

@ -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';