Middle Header: Introduce Add Contact and Block User buttons (#1735)
This commit is contained in:
parent
531c2de36c
commit
a215aa1083
@ -1,13 +1,13 @@
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
import {
|
||||
ApiBotCommand, ApiUser, ApiUserStatus, ApiUserType,
|
||||
ApiBotCommand, ApiUser, ApiUserSettings, ApiUserStatus, ApiUserType,
|
||||
} from '../../types';
|
||||
import { buildApiPeerId } from './peers';
|
||||
|
||||
export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUser {
|
||||
const {
|
||||
fullUser: {
|
||||
about, commonChatsCount, pinnedMsgId, botInfo, blocked,
|
||||
about, commonChatsCount, pinnedMsgId, botInfo, blocked, settings,
|
||||
},
|
||||
users,
|
||||
} = mtpUserFull;
|
||||
@ -16,6 +16,7 @@ export function buildApiUserFromFull(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
|
||||
return {
|
||||
...user,
|
||||
settings: buildApiUserSettings(settings),
|
||||
fullInfo: {
|
||||
bio: about,
|
||||
commonChatsCount,
|
||||
@ -111,3 +112,17 @@ export function buildApiUsersAndStatuses(mtpUsers: GramJs.TypeUser[]) {
|
||||
|
||||
return { users, userStatusesById };
|
||||
}
|
||||
|
||||
export function buildApiUserSettings({
|
||||
autoarchived,
|
||||
reportSpam,
|
||||
addContact,
|
||||
blockContact,
|
||||
}: GramJs.PeerSettings): ApiUserSettings {
|
||||
return {
|
||||
isAutoArchived: Boolean(autoarchived),
|
||||
canReportSpam: Boolean(reportSpam),
|
||||
canAddContact: Boolean(addContact),
|
||||
canBlockContact: Boolean(blockContact),
|
||||
};
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ export {
|
||||
|
||||
export {
|
||||
fetchFullUser, fetchNearestCountry, fetchTopUsers, fetchContactList, fetchUsers,
|
||||
addContact, updateContact, deleteContact, fetchProfilePhotos, fetchCommonChats,
|
||||
addContact, updateContact, deleteContact, fetchProfilePhotos, fetchCommonChats, reportSpam,
|
||||
} from './users';
|
||||
|
||||
export {
|
||||
|
||||
@ -51,6 +51,7 @@ export async function fetchFullUser({
|
||||
'@type': 'updateUser',
|
||||
id,
|
||||
user: {
|
||||
settings: userWithFullInfo.settings,
|
||||
fullInfo: userWithFullInfo.fullInfo,
|
||||
},
|
||||
});
|
||||
@ -247,6 +248,14 @@ export async function fetchProfilePhotos(user?: ApiUser, chat?: ApiChat) {
|
||||
};
|
||||
}
|
||||
|
||||
export function reportSpam(user: ApiUser) {
|
||||
const { id, accessHash } = user;
|
||||
|
||||
return invokeRequest(new GramJs.messages.ReportSpam({
|
||||
peer: buildInputPeer(id, accessHash),
|
||||
}), true);
|
||||
}
|
||||
|
||||
function updateLocalDb(result: (GramJs.photos.Photos | GramJs.photos.PhotosSlice | GramJs.messages.Chats)) {
|
||||
if ('chats' in result) {
|
||||
addEntitiesWithPhotosToLocalDb(result.chats);
|
||||
|
||||
@ -23,7 +23,7 @@ import {
|
||||
buildApiChatFromPreview,
|
||||
buildApiChatFolder,
|
||||
} from './apiBuilders/chats';
|
||||
import { buildApiUser, buildApiUserStatus } from './apiBuilders/users';
|
||||
import { buildApiUser, buildApiUserSettings, buildApiUserStatus } from './apiBuilders/users';
|
||||
import {
|
||||
buildMessageFromUpdate,
|
||||
isMessageWithMedia,
|
||||
@ -771,11 +771,12 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdatePeerSettings) {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { _entities } = update;
|
||||
const { _entities, settings } = update;
|
||||
if (!_entities) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (_entities?.length) {
|
||||
_entities
|
||||
.filter((e) => e instanceof GramJs.User && !e.contact)
|
||||
@ -797,7 +798,10 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
||||
onUpdate({
|
||||
'@type': 'updateUser',
|
||||
id: user.id,
|
||||
user,
|
||||
user: {
|
||||
...user,
|
||||
...(settings && { settings: buildApiUserSettings(settings) }),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -26,6 +26,14 @@ export interface ApiUser {
|
||||
|
||||
// Obtained from GetFullUser / UserFullInfo
|
||||
fullInfo?: ApiUserFullInfo;
|
||||
settings?: ApiUserSettings;
|
||||
}
|
||||
|
||||
export interface ApiUserSettings {
|
||||
isAutoArchived?: boolean;
|
||||
canReportSpam?: boolean;
|
||||
canAddContact?: boolean;
|
||||
canBlockContact?: boolean;
|
||||
}
|
||||
|
||||
export interface ApiUserFullInfo {
|
||||
|
||||
@ -42,6 +42,7 @@ import {
|
||||
selectIsUserBlocked,
|
||||
selectPinnedIds,
|
||||
selectTheme,
|
||||
selectUser,
|
||||
} from '../../modules/selectors';
|
||||
import {
|
||||
getCanPostInChat, getMessageSendingRestrictionReason, isChatChannel, isChatSuperGroup, isUserId,
|
||||
@ -106,10 +107,12 @@ type StateProps = {
|
||||
currentTransitionKey: number;
|
||||
messageLists?: GlobalMessageList[];
|
||||
isChannel?: boolean;
|
||||
isUserFull?: boolean;
|
||||
canSubscribe?: boolean;
|
||||
canStartBot?: boolean;
|
||||
canRestartBot?: boolean;
|
||||
activeEmojiInteractions?: ActiveEmojiInteraction[];
|
||||
lastSyncTime?: number;
|
||||
};
|
||||
|
||||
const CLOSE_ANIMATION_DURATION = IS_SINGLE_COLUMN_LAYOUT ? 450 + ANIMATION_END_DELAY : undefined;
|
||||
@ -147,15 +150,18 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
shouldSkipHistoryAnimations,
|
||||
currentTransitionKey,
|
||||
isChannel,
|
||||
isUserFull,
|
||||
canSubscribe,
|
||||
canStartBot,
|
||||
canRestartBot,
|
||||
activeEmojiInteractions,
|
||||
lastSyncTime,
|
||||
}) => {
|
||||
const {
|
||||
openChat,
|
||||
unpinAllMessages,
|
||||
loadUser,
|
||||
loadFullUser,
|
||||
closeLocalTextSearch,
|
||||
exitMessageSelectMode,
|
||||
closePaymentModal,
|
||||
@ -251,6 +257,12 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
}
|
||||
}, [chatId, isPrivate, loadUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPrivate && !isUserFull && lastSyncTime) {
|
||||
loadFullUser({ userId: chatId });
|
||||
}
|
||||
}, [chatId, isPrivate, isUserFull, lastSyncTime, loadFullUser]);
|
||||
|
||||
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
if (IS_TOUCH_ENV) {
|
||||
return;
|
||||
@ -540,7 +552,9 @@ export default memo(withGlobal(
|
||||
|
||||
const { messageLists } = global.messages;
|
||||
const currentMessageList = selectCurrentMessageList(global);
|
||||
const { isLeftColumnShown, chats: { listIds }, activeEmojiInteractions } = global;
|
||||
const {
|
||||
isLeftColumnShown, chats: { listIds }, activeEmojiInteractions, lastSyncTime,
|
||||
} = global;
|
||||
|
||||
const state: StateProps = {
|
||||
theme,
|
||||
@ -559,6 +573,7 @@ export default memo(withGlobal(
|
||||
animationLevel: global.settings.byKey.animationLevel,
|
||||
currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1),
|
||||
activeEmojiInteractions,
|
||||
lastSyncTime,
|
||||
};
|
||||
|
||||
if (!currentMessageList || !listIds.active) {
|
||||
@ -566,8 +581,10 @@ export default memo(withGlobal(
|
||||
}
|
||||
|
||||
const { chatId, threadId, type: messageListType } = currentMessageList;
|
||||
const isPrivate = isUserId(chatId);
|
||||
const chat = selectChat(global, chatId);
|
||||
const bot = selectChatBot(global, chatId);
|
||||
const user = isPrivate ? selectUser(global, chatId) : undefined;
|
||||
const pinnedIds = selectPinnedIds(global, chatId);
|
||||
const { chatId: audioChatId, messageId: audioMessageId } = global.audioPlayer;
|
||||
|
||||
@ -588,7 +605,8 @@ export default memo(withGlobal(
|
||||
chatId,
|
||||
threadId,
|
||||
messageListType,
|
||||
isPrivate: isUserId(chatId),
|
||||
isPrivate,
|
||||
isUserFull: Boolean(user?.settings),
|
||||
canPost: !isPinnedMessageList && (!chat || canPost) && !isBotNotStarted,
|
||||
isPinnedMessageList,
|
||||
isScheduledMessageList,
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
} from '../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import {
|
||||
getChatTitle, getMessageKey, getSenderTitle, isUserId,
|
||||
getChatTitle, getMessageKey, getPrivateChatUserId, getSenderTitle, isUserId,
|
||||
} from '../../modules/helpers';
|
||||
import {
|
||||
selectAllowedMessageActions,
|
||||
@ -35,6 +35,7 @@ import {
|
||||
selectScheduledIds,
|
||||
selectThreadInfo,
|
||||
selectThreadTopMessageId,
|
||||
selectUser,
|
||||
} from '../../modules/selectors';
|
||||
import useEnsureMessage from '../../hooks/useEnsureMessage';
|
||||
import useWindowSize from '../../hooks/useWindowSize';
|
||||
@ -53,6 +54,7 @@ import HeaderActions from './HeaderActions';
|
||||
import HeaderPinnedMessage from './HeaderPinnedMessage';
|
||||
import AudioPlayer from './AudioPlayer';
|
||||
import GroupCallTopPane from '../calls/group/GroupCallTopPane';
|
||||
import UserReportPanel from './UserReportPanel';
|
||||
|
||||
import './MiddleHeader.scss';
|
||||
|
||||
@ -81,6 +83,7 @@ type StateProps = {
|
||||
isChatWithSelf?: boolean;
|
||||
isChatWithBot?: boolean;
|
||||
lastSyncTime?: number;
|
||||
shouldShowUserReportPanel?: boolean;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
currentTransitionKey: number;
|
||||
connectionState?: GlobalState['connectionState'];
|
||||
@ -106,6 +109,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
isChatWithSelf,
|
||||
isChatWithBot,
|
||||
lastSyncTime,
|
||||
shouldShowUserReportPanel,
|
||||
shouldSkipHistoryAnimations,
|
||||
currentTransitionKey,
|
||||
connectionState,
|
||||
@ -395,6 +399,9 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
onAllPinnedClick={handleAllPinnedClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
{shouldShowUserReportPanel && <UserReportPanel key={chatId} userId={chatId} />}
|
||||
|
||||
<div className="header-tools">
|
||||
{isAudioPlayerRendered && (
|
||||
<AudioPlayer
|
||||
@ -418,6 +425,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId, threadId, messageListType }): StateProps => {
|
||||
const { isLeftColumnShown, lastSyncTime, shouldSkipHistoryAnimations } = global;
|
||||
const chat = selectChat(global, chatId);
|
||||
const userId = chat && getPrivateChatUserId(chat);
|
||||
const user = userId ? selectUser(global, userId) : undefined;
|
||||
|
||||
const { typingStatus } = chat || {};
|
||||
|
||||
@ -446,6 +455,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
audioMessage,
|
||||
chat,
|
||||
messagesCount,
|
||||
shouldShowUserReportPanel: Boolean(user?.settings?.canAddContact || user?.settings?.canBlockContact),
|
||||
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
||||
isChatWithBot: chat && selectIsChatWithBot(global, chat),
|
||||
lastSyncTime,
|
||||
|
||||
36
src/components/middle/UserReportPanel.scss
Normal file
36
src/components/middle/UserReportPanel.scss
Normal file
@ -0,0 +1,36 @@
|
||||
.UserReportPanel {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
background: var(--color-background);
|
||||
padding: 0.375rem 0.8125rem 0.25rem 1rem;
|
||||
box-shadow: 0 0.125rem 0.125rem var(--color-light-shadow), inset 0 0.125rem 0.125rem var(--color-light-shadow);
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: opacity 0.15s ease, transform var(--layer-transition);
|
||||
|
||||
body.animation-level-1 & {
|
||||
.ripple-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1276px) {
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: opacity 0.15s ease, transform var(--layer-transition);
|
||||
|
||||
#Main.right-column-open & {
|
||||
padding-right: calc(var(--right-column-width) + 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
.UserReportPanel--Button {
|
||||
margin-left: 0.25rem;
|
||||
flex: 1 1 50%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
124
src/components/middle/UserReportPanel.tsx
Normal file
124
src/components/middle/UserReportPanel.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
import React, { FC, memo, useCallback, useState } from '../../lib/teact/teact';
|
||||
import { withGlobal, getDispatch } from '../../lib/teact/teactn';
|
||||
|
||||
import { ApiUser } from '../../api/types';
|
||||
|
||||
import { selectUser } from '../../modules/selectors';
|
||||
import { getUserFirstOrLastName, getUserFullName } from '../../modules/helpers';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import ConfirmDialog from '../ui/ConfirmDialog';
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
|
||||
import './UserReportPanel.scss';
|
||||
|
||||
type OwnProps = {
|
||||
userId: string;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
user?: ApiUser;
|
||||
};
|
||||
|
||||
const UserReportPanel: FC<OwnProps & StateProps> = ({ userId, user }) => {
|
||||
const {
|
||||
addContact,
|
||||
blockContact,
|
||||
reportSpam,
|
||||
deleteChat,
|
||||
toggleChatArchived,
|
||||
} = getDispatch();
|
||||
|
||||
const lang = useLang();
|
||||
const [isBlockUserModalOpen, openBlockUserModal, closeBlockUserModal] = useFlag();
|
||||
const [shouldReportSpam, setShouldReportSpam] = useState<boolean>(true);
|
||||
const [shouldDeleteChat, setShouldDeleteChat] = useState<boolean>(true);
|
||||
const { settings, accessHash } = user || {};
|
||||
const {
|
||||
isAutoArchived, canReportSpam, canAddContact, canBlockContact,
|
||||
} = settings || {};
|
||||
const handleAddContact = useCallback(() => {
|
||||
addContact({ userId });
|
||||
if (isAutoArchived) {
|
||||
toggleChatArchived({ chatId: userId });
|
||||
}
|
||||
}, [addContact, isAutoArchived, toggleChatArchived, userId]);
|
||||
|
||||
const handleConfirmBlock = useCallback(() => {
|
||||
closeBlockUserModal();
|
||||
blockContact({ contactId: userId, accessHash });
|
||||
if (canReportSpam && shouldReportSpam) {
|
||||
reportSpam({ userId });
|
||||
}
|
||||
if (shouldDeleteChat) {
|
||||
deleteChat({ chatId: userId });
|
||||
}
|
||||
}, [
|
||||
accessHash, blockContact, closeBlockUserModal, deleteChat, reportSpam, canReportSpam, shouldDeleteChat,
|
||||
shouldReportSpam, userId,
|
||||
]);
|
||||
|
||||
if (!settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="UserReportPanel">
|
||||
{canAddContact && (
|
||||
<Button
|
||||
isText
|
||||
ripple
|
||||
fluid
|
||||
size="tiny"
|
||||
className="UserReportPanel--Button"
|
||||
onClick={handleAddContact}
|
||||
>
|
||||
{lang('lng_new_contact_add')}
|
||||
</Button>
|
||||
)}
|
||||
{canBlockContact && (
|
||||
<Button
|
||||
color="danger"
|
||||
isText
|
||||
ripple
|
||||
fluid
|
||||
size="tiny"
|
||||
className="UserReportPanel--Button"
|
||||
onClick={openBlockUserModal}
|
||||
>
|
||||
{lang('lng_new_contact_block')}
|
||||
</Button>
|
||||
)}
|
||||
<ConfirmDialog
|
||||
isOpen={isBlockUserModalOpen}
|
||||
onClose={closeBlockUserModal}
|
||||
title={lang('BlockUserTitle', getUserFirstOrLastName(user))}
|
||||
text={lang('UserInfo.BlockConfirmationTitle', getUserFullName(user))}
|
||||
isButtonsInOneRow
|
||||
confirmIsDestructive
|
||||
confirmLabel={lang('Block')}
|
||||
confirmHandler={handleConfirmBlock}
|
||||
>
|
||||
{canReportSpam && (
|
||||
<Checkbox
|
||||
label={lang('DeleteReportSpam')}
|
||||
checked={shouldReportSpam}
|
||||
onCheck={setShouldReportSpam}
|
||||
/>
|
||||
)}
|
||||
<Checkbox
|
||||
label={lang('DeleteThisChat')}
|
||||
checked={shouldDeleteChat}
|
||||
onCheck={setShouldDeleteChat}
|
||||
/>
|
||||
</ConfirmDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { userId }): StateProps => ({ user: selectUser(global, userId) }),
|
||||
)(UserReportPanel));
|
||||
@ -561,7 +561,7 @@ export type ActionTypes = (
|
||||
// users
|
||||
'loadFullUser' | 'loadNearestCountry' | 'loadTopUsers' | 'loadContactList' |
|
||||
'loadCurrentUser' | 'updateProfile' | 'checkUsername' | 'addContact' | 'updateContact' |
|
||||
'deleteContact' | 'loadUser' | 'setUserSearchQuery' | 'loadCommonChats' |
|
||||
'deleteContact' | 'loadUser' | 'setUserSearchQuery' | 'loadCommonChats' | 'reportSpam' |
|
||||
// chat creation
|
||||
'createChannel' | 'createGroupChat' | 'resetChatCreation' |
|
||||
// settings
|
||||
|
||||
@ -1021,6 +1021,7 @@ messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action
|
||||
messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.reportSpam#cf1592db peer:InputPeer = Bool;
|
||||
messages.report#8953ab4e peer:InputPeer id:Vector<int> reason:ReportReason message:string = Bool;
|
||||
messages.getChats#49e9528f id:Vector<long> = messages.Chats;
|
||||
messages.getFullChat#aeb00b34 chat_id:long = messages.ChatFull;
|
||||
|
||||
@ -67,6 +67,7 @@
|
||||
"messages.sendMessage",
|
||||
"messages.sendMedia",
|
||||
"messages.forwardMessages",
|
||||
"messages.reportSpam",
|
||||
"messages.report",
|
||||
"messages.getChats",
|
||||
"messages.getFullChat",
|
||||
|
||||
@ -271,6 +271,16 @@ addReducer('addContact', (global, actions, payload) => {
|
||||
void callApi('addContact', pick(user, ['id', 'accessHash', 'firstName', 'lastName', 'phoneNumber']));
|
||||
});
|
||||
|
||||
addReducer('reportSpam', (global, actions, payload) => {
|
||||
const { userId } = payload!;
|
||||
const user = selectUser(global, userId);
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
void callApi('reportSpam', user);
|
||||
});
|
||||
|
||||
async function searchUsers(query: string) {
|
||||
const result = await callApi('searchChats', { query });
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user