Composer / Send As: Display lock icon for non-premium users (#2076)

This commit is contained in:
Alexander Zinchuk 2022-10-17 17:35:07 +02:00
parent 04dee2ce7b
commit d0735fcc0e
9 changed files with 69 additions and 34 deletions

View File

@ -11,6 +11,7 @@ import type {
ApiExportedInvite, ApiExportedInvite,
ApiChatInviteImporter, ApiChatInviteImporter,
ApiChatSettings, ApiChatSettings,
ApiSendAsPeerId,
} from '../../types'; } from '../../types';
import { pick, pickTruthy } from '../../../util/iteratees'; import { pick, pickTruthy } from '../../../util/iteratees';
import { import {
@ -467,3 +468,10 @@ export function buildApiChatReactions(availableReactions?: GramJs.TypeChatReacti
return undefined; return undefined;
} }
export function buildApiSendAsPeerId(sendAs: GramJs.SendAsPeer): ApiSendAsPeerId {
return {
id: getApiChatIdFromMtpPeer(sendAs.peer),
isPremium: sendAs.premiumRequired,
};
}

View File

@ -55,7 +55,7 @@ import {
buildInputPollFromExisting, buildInputPollFromExisting,
} from '../gramjsBuilders'; } from '../gramjsBuilders';
import localDb from '../localDb'; import localDb from '../localDb';
import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiChatFromPreview, buildApiSendAsPeerId } from '../apiBuilders/chats';
import { fetchFile } from '../../../util/files'; import { fetchFile } from '../../../util/files';
import { import {
addEntitiesWithPhotosToLocalDb, addEntitiesWithPhotosToLocalDb,
@ -65,7 +65,6 @@ import {
} from '../helpers'; } from '../helpers';
import { interpolateArray } from '../../../util/waveform'; import { interpolateArray } from '../../../util/waveform';
import { requestChatUpdate } from './chats'; import { requestChatUpdate } from './chats';
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
const FAST_SEND_TIMEOUT = 1000; const FAST_SEND_TIMEOUT = 1000;
const INPUT_WAVEFORM_LENGTH = 63; const INPUT_WAVEFORM_LENGTH = 63;
@ -1311,13 +1310,13 @@ export async function fetchSendAs({
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesWithPhotosToLocalDb(result.users);
addEntitiesWithPhotosToLocalDb(result.chats); addEntitiesWithPhotosToLocalDb(result.chats);
const users = result.users.map(buildApiUser).filter<ApiUser>(Boolean as any); const users = result.users.map(buildApiUser).filter(Boolean);
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter<ApiChat>(Boolean as any); const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
return { return {
users, users,
chats, chats,
ids: result.peers.map((sendAsPeer) => getApiChatIdFromMtpPeer(sendAsPeer.peer)), sendAs: result.peers.map(buildApiSendAsPeerId),
}; };
} }

View File

@ -68,7 +68,7 @@ export interface ApiChat {
joinRequests?: ApiChatInviteImporter[]; joinRequests?: ApiChatInviteImporter[];
isJoinToSend?: boolean; isJoinToSend?: boolean;
isJoinRequest?: boolean; isJoinRequest?: boolean;
sendAsIds?: string[]; sendAsPeerIds?: ApiSendAsPeerId[];
unreadReactions?: number[]; unreadReactions?: number[];
unreadMentions?: number[]; unreadMentions?: number[];
@ -182,3 +182,8 @@ export interface ApiChatSettings {
canAddContact?: boolean; canAddContact?: boolean;
canBlockContact?: boolean; canBlockContact?: boolean;
} }
export interface ApiSendAsPeerId {
id: string;
isPremium?: boolean;
}

View File

@ -282,8 +282,9 @@ const Composer: FC<OwnProps & StateProps> = ({
const lastMessageSendTimeSeconds = useRef<number>(); const lastMessageSendTimeSeconds = useRef<number>();
const prevDropAreaState = usePrevious(dropAreaState); const prevDropAreaState = usePrevious(dropAreaState);
const { width: windowWidth } = windowSize.get(); const { width: windowWidth } = windowSize.get();
const sendAsIds = chat?.sendAsIds; const sendAsPeerIds = chat?.sendAsPeerIds;
const canShowSendAs = sendAsIds && (sendAsIds.length > 1 || !sendAsIds.includes(currentUserId!)); const canShowSendAs = sendAsPeerIds
&& (sendAsPeerIds.length > 1 || !sendAsPeerIds.some((peer) => peer.id === currentUserId!));
// Prevent Symbol Menu from closing when calendar is open // Prevent Symbol Menu from closing when calendar is open
const [isSymbolMenuForced, forceShowSymbolMenu, cancelForceShowSymbolMenu] = useFlag(); const [isSymbolMenuForced, forceShowSymbolMenu, cancelForceShowSymbolMenu] = useFlag();
const sendMessageAction = useSendMessageAction(chatId, threadId); const sendMessageAction = useSendMessageAction(chatId, threadId);
@ -304,10 +305,10 @@ const Composer: FC<OwnProps & StateProps> = ({
}, [isReady, chatId, loadScheduledHistory, lastSyncTime, threadId]); }, [isReady, chatId, loadScheduledHistory, lastSyncTime, threadId]);
useEffect(() => { useEffect(() => {
if (chatId && chat && lastSyncTime && !sendAsIds && isReady && isChatSuperGroup(chat)) { if (chatId && chat && lastSyncTime && !sendAsPeerIds && isReady && isChatSuperGroup(chat)) {
loadSendAs({ chatId }); loadSendAs({ chatId });
} }
}, [chat, chatId, isReady, lastSyncTime, loadSendAs, sendAsIds]); }, [chat, chatId, isReady, lastSyncTime, loadSendAs, sendAsPeerIds]);
useEffect(() => { useEffect(() => {
if (chatId && chat && lastSyncTime && !chat.fullInfo && isReady && isChatSuperGroup(chat)) { if (chatId && chat && lastSyncTime && !chat.fullInfo && isReady && isChatSuperGroup(chat)) {
@ -316,10 +317,10 @@ const Composer: FC<OwnProps & StateProps> = ({
}, [chat, chatId, isReady, lastSyncTime, loadFullChat]); }, [chat, chatId, isReady, lastSyncTime, loadFullChat]);
const shouldAnimateSendAsButtonRef = useRef(false); const shouldAnimateSendAsButtonRef = useRef(false);
useOnChange(([prevChatId, prevSendAsIds]) => { useOnChange(([prevChatId, prevSendAsPeerIds]) => {
// We only animate send-as button if `sendAsIds` was missing when opening the chat // We only animate send-as button if `sendAsPeerIds` was missing when opening the chat
shouldAnimateSendAsButtonRef.current = Boolean(chatId === prevChatId && sendAsIds && !prevSendAsIds); shouldAnimateSendAsButtonRef.current = Boolean(chatId === prevChatId && sendAsPeerIds && !prevSendAsPeerIds);
}, [chatId, sendAsIds]); }, [chatId, sendAsPeerIds]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (!appendixRef.current) return; if (!appendixRef.current) return;
@ -1056,7 +1057,8 @@ const Composer: FC<OwnProps & StateProps> = ({
onClose={closeSendAsMenu} onClose={closeSendAsMenu}
chatId={chatId} chatId={chatId}
selectedSendAsId={sendAsId} selectedSendAsId={sendAsId}
sendAsIds={sendAsIds} sendAsPeerIds={sendAsPeerIds}
isCurrentUserPremium={isCurrentUserPremium}
/> />
<MentionTooltip <MentionTooltip
isOpen={isMentionTooltipOpen} isOpen={isMentionTooltipOpen}
@ -1318,8 +1320,8 @@ export default memo(withGlobal<OwnProps>(
const keyboardMessage = botKeyboardMessageId ? selectChatMessage(global, chatId, botKeyboardMessageId) : undefined; const keyboardMessage = botKeyboardMessageId ? selectChatMessage(global, chatId, botKeyboardMessageId) : undefined;
const { currentUserId } = global; const { currentUserId } = global;
const defaultSendAsId = chat?.fullInfo ? chat?.fullInfo?.sendAsId || currentUserId : undefined; const defaultSendAsId = chat?.fullInfo ? chat?.fullInfo?.sendAsId || currentUserId : undefined;
const sendAsId = chat?.sendAsIds && defaultSendAsId && chat.sendAsIds.includes(defaultSendAsId) const sendAsId = chat?.sendAsPeerIds && defaultSendAsId
? defaultSendAsId && chat.sendAsPeerIds.some((peer) => peer.id === defaultSendAsId) ? defaultSendAsId
: (chat?.adminRights?.anonymous ? chat?.id : undefined); : (chat?.adminRights?.anonymous ? chat?.id : undefined);
const sendAsUser = sendAsId ? selectUser(global, sendAsId) : undefined; const sendAsUser = sendAsId ? selectUser(global, sendAsId) : undefined;
const sendAsChat = !sendAsUser && sendAsId ? selectChat(global, sendAsId) : undefined; const sendAsChat = !sendAsUser && sendAsId ? selectChat(global, sendAsId) : undefined;

View File

@ -59,6 +59,10 @@
padding: 0.5625rem 1rem !important; padding: 0.5625rem 1rem !important;
border-radius: 0; border-radius: 0;
align-items: center; align-items: center;
& > .send-as-icon-locked {
margin-right: 0.5rem;
}
} }
.info { .info {

View File

@ -1,8 +1,10 @@
import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
useCallback, useEffect, useRef, memo, useCallback, useEffect, useRef, memo,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import type { FC } from '../../../lib/teact/teact';
import type { ApiSendAsPeerId } from '../../../api/types';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation'; import { useKeyboardNavigation } from './hooks/useKeyboardNavigation';
import { IS_TOUCH_ENV } from '../../../util/environment'; import { IS_TOUCH_ENV } from '../../../util/environment';
@ -21,20 +23,22 @@ import './SendAsMenu.scss';
export type OwnProps = { export type OwnProps = {
isOpen: boolean; isOpen: boolean;
onClose: () => void;
chatId?: string; chatId?: string;
selectedSendAsId?: string; selectedSendAsId?: string;
sendAsIds?: string[]; sendAsPeerIds?: ApiSendAsPeerId[];
isCurrentUserPremium?: boolean;
onClose: () => void;
}; };
const SendAsMenu: FC<OwnProps> = ({ const SendAsMenu: FC<OwnProps> = ({
isOpen, isOpen,
onClose,
chatId, chatId,
selectedSendAsId, selectedSendAsId,
sendAsIds, sendAsPeerIds,
isCurrentUserPremium,
onClose,
}) => { }) => {
const { saveDefaultSendAs } = getActions(); const { saveDefaultSendAs, showNotification, openPremiumModal } = getActions();
// No need for expensive global updates on users and chats, so we avoid them // No need for expensive global updates on users and chats, so we avoid them
const usersById = getGlobal().users.byId; const usersById = getGlobal().users.byId;
@ -59,7 +63,7 @@ const SendAsMenu: FC<OwnProps> = ({
const selectedSendAsIndex = useKeyboardNavigation({ const selectedSendAsIndex = useKeyboardNavigation({
isActive: isOpen, isActive: isOpen,
items: sendAsIds, items: sendAsPeerIds,
onSelect: handleUserSelect, onSelect: handleUserSelect,
shouldSelectOnTab: true, shouldSelectOnTab: true,
shouldSaveSelectionOnUpdateItems: true, shouldSaveSelectionOnUpdateItems: true,
@ -71,10 +75,10 @@ const SendAsMenu: FC<OwnProps> = ({
}, [selectedSendAsIndex]); }, [selectedSendAsIndex]);
useEffect(() => { useEffect(() => {
if (sendAsIds && !sendAsIds.length) { if (sendAsPeerIds && !sendAsPeerIds.length) {
onClose(); onClose();
} }
}, [sendAsIds, onClose]); }, [sendAsPeerIds, onClose]);
return ( return (
<Menu <Menu
@ -90,18 +94,31 @@ const SendAsMenu: FC<OwnProps> = ({
noCompact noCompact
> >
<div className="send-as-title" dir="auto">{lang('SendMessageAsTitle')}</div> <div className="send-as-title" dir="auto">{lang('SendMessageAsTitle')}</div>
{usersById && chatsById && sendAsIds?.map((id, index) => { {usersById && chatsById && sendAsPeerIds?.map(({ id, isPremium }, index) => {
const user = isUserId(id) ? usersById[id] : undefined; const user = isUserId(id) ? usersById[id] : undefined;
const chat = !user ? chatsById[id] : undefined; const chat = !user ? chatsById[id] : undefined;
const userOrChat = user || chat; const userOrChat = user || chat;
const handleClick = () => {
if (!isPremium || isCurrentUserPremium) {
handleUserSelect(id);
} else {
showNotification({
message: lang('SelectSendAsPeerPremiumHint'),
actionText: lang('Open'),
action: () => openPremiumModal(),
});
}
};
return ( return (
<ListItem <ListItem
key={id} key={id}
className="SendAsItem chat-item-clickable scroll-item with-avatar" className="SendAsItem chat-item-clickable scroll-item with-avatar"
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => handleUserSelect(id)} onClick={handleClick}
focus={selectedSendAsIndex === index} focus={selectedSendAsIndex === index}
rightElement={!isCurrentUserPremium && isPremium && <i className="icon-lock-badge send-as-icon-locked" />}
> >
<Avatar <Avatar
size="small" size="small"

View File

@ -1,6 +1,6 @@
.Notification-container { .Notification-container {
position: relative; position: relative;
width: 24rem; width: 27rem;
max-width: 100vw; max-width: 100vw;
margin: 4.25rem auto 0.25rem; margin: 4.25rem auto 0.25rem;
z-index: var(--z-notification); z-index: var(--z-notification);
@ -61,8 +61,8 @@
color: var(--color-primary); color: var(--color-primary);
font-weight: 500; font-weight: 500;
text-transform: none; text-transform: none;
flex: 1; flex-shrink: 1;
padding: 0 2rem; padding: 0.25rem;
height: 2rem; height: 2rem;
} }
} }

View File

@ -42,7 +42,7 @@ export const MEDIA_PROGRESSIVE_CACHE_DISABLED = false;
export const MEDIA_PROGRESSIVE_CACHE_NAME = 'tt-media-progressive'; export const MEDIA_PROGRESSIVE_CACHE_NAME = 'tt-media-progressive';
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg'; export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
export const LANG_CACHE_NAME = 'tt-lang-packs-v13'; export const LANG_CACHE_NAME = 'tt-lang-packs-v14';
export const ASSET_CACHE_NAME = 'tt-assets'; export const ASSET_CACHE_NAME = 'tt-assets';
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500]; export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];

View File

@ -1091,7 +1091,7 @@ addActionHandler('loadSendAs', async (global, actions, payload) => {
const result = await callApi('fetchSendAs', { chat }); const result = await callApi('fetchSendAs', { chat });
if (!result) { if (!result) {
setGlobal(updateChat(getGlobal(), chatId, { setGlobal(updateChat(getGlobal(), chatId, {
sendAsIds: [], sendAsPeerIds: [],
})); }));
return; return;
@ -1100,7 +1100,7 @@ addActionHandler('loadSendAs', async (global, actions, payload) => {
global = getGlobal(); global = getGlobal();
global = addUsers(global, buildCollectionByKey(result.users, 'id')); global = addUsers(global, buildCollectionByKey(result.users, 'id'));
global = addChats(global, buildCollectionByKey(result.chats, 'id')); global = addChats(global, buildCollectionByKey(result.chats, 'id'));
global = updateChat(global, chatId, { sendAsIds: result.ids }); global = updateChat(global, chatId, { sendAsPeerIds: result.sendAs });
setGlobal(global); setGlobal(global);
}); });