Message / Contact: Support copying number, support non-registered (#1860)

This commit is contained in:
Alexander Zinchuk 2022-05-06 17:55:55 +01:00
parent 1f99bd3d49
commit 39cdd8e8e8
5 changed files with 62 additions and 24 deletions

View File

@ -60,7 +60,9 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
openForwardMenuForSelectedMessages, openForwardMenuForSelectedMessages,
downloadSelectedMessages, downloadSelectedMessages,
copySelectedMessages, copySelectedMessages,
showNotification,
} = getActions(); } = getActions();
const lang = useLang();
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag(); const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
@ -78,8 +80,11 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
const handleCopy = useCallback(() => { const handleCopy = useCallback(() => {
copySelectedMessages(); copySelectedMessages();
showNotification({
message: lang('Share.Link.Copied'),
});
exitMessageSelectMode(); exitMessageSelectMode();
}, [copySelectedMessages, exitMessageSelectMode]); }, [copySelectedMessages, exitMessageSelectMode, lang, showNotification]);
const handleDownload = useCallback(() => { const handleDownload = useCallback(() => {
downloadSelectedMessages(); downloadSelectedMessages();
@ -89,8 +94,6 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
const prevSelectedMessagesCount = usePrevious(selectedMessagesCount || undefined, true); const prevSelectedMessagesCount = usePrevious(selectedMessagesCount || undefined, true);
const renderingSelectedMessagesCount = isActive ? selectedMessagesCount : prevSelectedMessagesCount; const renderingSelectedMessagesCount = isActive ? selectedMessagesCount : prevSelectedMessagesCount;
const lang = useLang();
const formattedMessagesCount = lang('VoiceOver.Chat.MessagesSelected', renderingSelectedMessagesCount, 'i'); const formattedMessagesCount = lang('VoiceOver.Chat.MessagesSelected', renderingSelectedMessagesCount, 'i');
const className = buildClassName( const className = buildClassName(

View File

@ -20,6 +20,8 @@ type StateProps = {
phoneCodeList: ApiCountryCode[]; phoneCodeList: ApiCountryCode[];
}; };
const UNREGISTERED_CONTACT_ID = '0';
const Contact: FC<OwnProps & StateProps> = ({ const Contact: FC<OwnProps & StateProps> = ({
contact, user, phoneCodeList, contact, user, phoneCodeList,
}) => { }) => {
@ -31,6 +33,7 @@ const Contact: FC<OwnProps & StateProps> = ({
phoneNumber, phoneNumber,
userId, userId,
} = contact; } = contact;
const isRegistered = userId !== UNREGISTERED_CONTACT_ID;
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
openChat({ id: userId }); openChat({ id: userId });
@ -38,8 +41,8 @@ const Contact: FC<OwnProps & StateProps> = ({
return ( return (
<div <div
className={buildClassName('Contact', Boolean(userId) && 'interactive')} className={buildClassName('Contact', isRegistered && 'interactive')}
onClick={userId ? handleClick : undefined} onClick={isRegistered ? handleClick : undefined}
> >
<Avatar size="large" user={user} text={firstName || lastName} /> <Avatar size="large" user={user} text={firstName || lastName} />
<div className="contact-info"> <div className="contact-info">

View File

@ -6,6 +6,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
import { MessageListType } from '../../../global/types'; import { MessageListType } from '../../../global/types';
import { ApiAvailableReaction, ApiMessage } from '../../../api/types'; import { ApiAvailableReaction, ApiMessage } from '../../../api/types';
import { IAlbum, IAnchorPosition } from '../../../types'; import { IAlbum, IAnchorPosition } from '../../../types';
import { import {
selectActiveDownloadIds, selectActiveDownloadIds,
selectAllowedMessageActions, selectAllowedMessageActions,
@ -19,17 +20,18 @@ import {
} from '../../../global/helpers'; } from '../../../global/helpers';
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
import { getDayStartAt } from '../../../util/dateFormat'; import { getDayStartAt } from '../../../util/dateFormat';
import buildClassName from '../../../util/buildClassName';
import { REM } from '../../common/helpers/mediaDimensions';
import { copyTextToClipboard } from '../../../util/clipboard'; import { copyTextToClipboard } from '../../../util/clipboard';
import useShowTransition from '../../../hooks/useShowTransition'; import useShowTransition from '../../../hooks/useShowTransition';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import { REM } from '../../common/helpers/mediaDimensions';
import DeleteMessageModal from '../../common/DeleteMessageModal'; import DeleteMessageModal from '../../common/DeleteMessageModal';
import ReportModal from '../../common/ReportModal'; import ReportModal from '../../common/ReportModal';
import PinMessageModal from '../../common/PinMessageModal'; import PinMessageModal from '../../common/PinMessageModal';
import MessageContextMenu from './MessageContextMenu'; import MessageContextMenu from './MessageContextMenu';
import CalendarModal from '../../common/CalendarModal'; import CalendarModal from '../../common/CalendarModal';
import buildClassName from '../../../util/buildClassName';
const START_SIZE = 2 * REM; const START_SIZE = 2 * REM;
@ -300,7 +302,12 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
const handleCopyLink = useCallback(() => { const handleCopyLink = useCallback(() => {
copyTextToClipboard(`https://t.me/${chatUsername || `c/${message.chatId.replace('-', '')}`}/${message.id}`); copyTextToClipboard(`https://t.me/${chatUsername || `c/${message.chatId.replace('-', '')}`}/${message.id}`);
closeMenu(); closeMenu();
}, [chatUsername, closeMenu, message.chatId, message.id]); }, [chatUsername, closeMenu, message]);
const handleCopyNumber = useCallback(() => {
copyTextToClipboard(message.content.contact!.phoneNumber);
closeMenu();
}, [closeMenu, message]);
const handleDownloadClick = useCallback(() => { const handleDownloadClick = useCallback(() => {
(album?.messages || [message]).forEach((msg) => { (album?.messages || [message]).forEach((msg) => {
@ -383,6 +390,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
onClose={closeMenu} onClose={closeMenu}
onCopyLink={handleCopyLink} onCopyLink={handleCopyLink}
onCopyMessages={handleCopyMessages} onCopyMessages={handleCopyMessages}
onCopyNumber={handleCopyNumber}
onDownload={handleDownloadClick} onDownload={handleDownloadClick}
onSaveGif={handleSaveGif} onSaveGif={handleSaveGif}
onShowSeenBy={handleOpenSeenByModal} onShowSeenBy={handleOpenSeenByModal}
@ -464,6 +472,7 @@ export default memo(withGlobal<OwnProps>(
&& !areReactionsEmpty(message.reactions) && message.reactions.canSeeList; && !areReactionsEmpty(message.reactions) && message.reactions.canSeeList;
const canRemoveReaction = isPrivate && message.reactions?.results?.some((l) => l.isChosen); const canRemoveReaction = isPrivate && message.reactions?.results?.some((l) => l.isChosen);
const isProtected = selectIsMessageProtected(global, message); const isProtected = selectIsMessageProtected(global, message);
const canCopyNumber = Boolean(message.content.contact);
return { return {
availableReactions: global.availableReactions, availableReactions: global.availableReactions,
@ -479,7 +488,7 @@ export default memo(withGlobal<OwnProps>(
canForward: !isProtected && !isScheduled && canForward, canForward: !isProtected && !isScheduled && canForward,
canFaveSticker: !isScheduled && canFaveSticker, canFaveSticker: !isScheduled && canFaveSticker,
canUnfaveSticker: !isScheduled && canUnfaveSticker, canUnfaveSticker: !isScheduled && canUnfaveSticker,
canCopy: !isProtected && canCopy, canCopy: canCopyNumber || (!isProtected && canCopy),
canCopyLink: !isProtected && !isScheduled && canCopyLink, canCopyLink: !isProtected && !isScheduled && canCopyLink,
canSelect, canSelect,
canDownload: !isProtected && canDownload, canDownload: !isProtected && canDownload,

View File

@ -1,6 +1,7 @@
import React, { import React, {
FC, memo, useCallback, useEffect, useRef, FC, memo, useCallback, useEffect, useRef,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import { ApiAvailableReaction, ApiMessage, ApiUser } from '../../../api/types'; import { ApiAvailableReaction, ApiMessage, ApiUser } from '../../../api/types';
import { IAnchorPosition } from '../../../types'; import { IAnchorPosition } from '../../../types';
@ -8,11 +9,12 @@ import { IAnchorPosition } from '../../../types';
import { getMessageCopyOptions } from './helpers/copyOptions'; import { getMessageCopyOptions } from './helpers/copyOptions';
import { disableScrolling, enableScrolling } from '../../../util/scrollLock'; import { disableScrolling, enableScrolling } from '../../../util/scrollLock';
import { getUserFullName } from '../../../global/helpers'; import { getUserFullName } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
import useFlag from '../../../hooks/useFlag';
import useContextMenuPosition from '../../../hooks/useContextMenuPosition'; import useContextMenuPosition from '../../../hooks/useContextMenuPosition';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import buildClassName from '../../../util/buildClassName';
import useFlag from '../../../hooks/useFlag';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
import Menu from '../../ui/Menu'; import Menu from '../../ui/Menu';
import MenuItem from '../../ui/MenuItem'; import MenuItem from '../../ui/MenuItem';
@ -66,6 +68,7 @@ type OwnProps = {
onCloseAnimationEnd?: () => void; onCloseAnimationEnd?: () => void;
onCopyLink?: () => void; onCopyLink?: () => void;
onCopyMessages?: (messageIds: number[]) => void; onCopyMessages?: (messageIds: number[]) => void;
onCopyNumber?: () => void;
onDownload?: () => void; onDownload?: () => void;
onSaveGif?: () => void; onSaveGif?: () => void;
onShowSeenBy?: () => void; onShowSeenBy?: () => void;
@ -121,6 +124,7 @@ const MessageContextMenu: FC<OwnProps> = ({
onClose, onClose,
onCloseAnimationEnd, onCloseAnimationEnd,
onCopyLink, onCopyLink,
onCopyNumber,
onDownload, onDownload,
onSaveGif, onSaveGif,
onShowSeenBy, onShowSeenBy,
@ -128,16 +132,28 @@ const MessageContextMenu: FC<OwnProps> = ({
onSendReaction, onSendReaction,
onCopyMessages, onCopyMessages,
}) => { }) => {
const { showNotification } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const menuRef = useRef<HTMLDivElement>(null); const menuRef = useRef<HTMLDivElement>(null);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const scrollableRef = useRef<HTMLDivElement>(null); const scrollableRef = useRef<HTMLDivElement>(null);
const copyOptions = getMessageCopyOptions(message, onClose, canCopyLink ? onCopyLink : undefined, onCopyMessages); const lang = useLang();
const noReactions = !isPrivate && !enabledReactions?.length; const noReactions = !isPrivate && !enabledReactions?.length;
const withReactions = canShowReactionList && !noReactions; const withReactions = canShowReactionList && !noReactions;
const [isReady, markIsReady, unmarkIsReady] = useFlag(); const [isReady, markIsReady, unmarkIsReady] = useFlag();
const handleAfterCopy = useCallback(() => {
showNotification({
message: lang('Share.Link.Copied'),
});
onClose();
}, [lang, onClose, showNotification]);
const copyOptions = getMessageCopyOptions(
message, handleAfterCopy, canCopyLink ? onCopyLink : undefined, onCopyMessages, onCopyNumber,
);
const getTriggerElement = useCallback(() => { const getTriggerElement = useCallback(() => {
return document.querySelector(`.Transition__slide--active > .MessageList div[data-message-id="${message.id}"]`); return document.querySelector(`.Transition__slide--active > .MessageList div[data-message-id="${message.id}"]`);
}, [message.id]); }, [message.id]);
@ -193,8 +209,6 @@ const MessageContextMenu: FC<OwnProps> = ({
return enableScrolling; return enableScrolling;
}, [withScroll]); }, [withScroll]);
const lang = useLang();
return ( return (
<Menu <Menu
ref={menuRef} ref={menuRef}

View File

@ -2,6 +2,7 @@ import { ApiMediaFormat, ApiMessage } from '../../../../api/types';
import * as mediaLoader from '../../../../util/mediaLoader'; import * as mediaLoader from '../../../../util/mediaLoader';
import { import {
getMessageContact,
getMessageMediaHash, getMessageMediaHash,
getMessagePhoto, getMessagePhoto,
getMessageText, getMessageText,
@ -24,11 +25,13 @@ export function getMessageCopyOptions(
afterEffect?: () => void, afterEffect?: () => void,
onCopyLink?: () => void, onCopyLink?: () => void,
onCopyMessages?: (messageIds: number[]) => void, onCopyMessages?: (messageIds: number[]) => void,
onCopyNumber?: () => void,
): ICopyOptions { ): ICopyOptions {
const options: ICopyOptions = []; const options: ICopyOptions = [];
const text = getMessageText(message); const text = getMessageText(message);
const photo = getMessagePhoto(message) const photo = getMessagePhoto(message)
|| (!getMessageWebPageVideo(message) ? getMessageWebPagePhoto(message) : undefined); || (!getMessageWebPageVideo(message) ? getMessageWebPagePhoto(message) : undefined);
const contact = getMessageContact(message);
const mediaHash = getMessageMediaHash(message, 'inline'); const mediaHash = getMessageMediaHash(message, 'inline');
const canImageBeCopied = photo && (mediaHash || hasMessageLocalBlobUrl(message)) && CLIPBOARD_ITEM_SUPPORTED; const canImageBeCopied = photo && (mediaHash || hasMessageLocalBlobUrl(message)) && CLIPBOARD_ITEM_SUPPORTED;
const selection = window.getSelection(); const selection = window.getSelection();
@ -41,9 +44,7 @@ export function getMessageCopyOptions(
Promise.resolve(mediaHash ? mediaLoader.fetch(mediaHash, ApiMediaFormat.BlobUrl) : photo!.blobUrl) Promise.resolve(mediaHash ? mediaLoader.fetch(mediaHash, ApiMediaFormat.BlobUrl) : photo!.blobUrl)
.then(copyImageToClipboard); .then(copyImageToClipboard);
if (afterEffect) { afterEffect?.();
afterEffect();
}
}, },
}); });
} }
@ -68,9 +69,7 @@ export function getMessageCopyOptions(
copyTextToClipboard(clipboardText); copyTextToClipboard(clipboardText);
} }
if (afterEffect) { afterEffect?.();
afterEffect();
}
}, },
}); });
} }
@ -82,9 +81,19 @@ export function getMessageCopyOptions(
handler: () => { handler: () => {
onCopyLink(); onCopyLink();
if (afterEffect) { afterEffect?.();
afterEffect(); },
} });
}
if (contact && onCopyNumber) {
options.push({
label: 'lng_profile_copy_phone',
icon: 'copy',
handler: () => {
onCopyNumber();
afterEffect?.();
}, },
}); });
} }