From 9a8ab84ecbde4040233dab75b717540675fb6cd4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 20 Nov 2018 19:36:36 +0400 Subject: [PATCH] Add edit / view of user information for support. --- Telegram/SourceFiles/apiwrap.cpp | 20 +- Telegram/SourceFiles/boxes/boxes.style | 4 + Telegram/SourceFiles/dialogs/dialogs.style | 9 + .../dialogs/dialogs_inner_widget.cpp | 4 +- .../SourceFiles/dialogs/dialogs_layout.cpp | 6 +- .../view/history_view_top_bar_widget.cpp | 13 +- .../info/profile/info_profile_actions.cpp | 22 +- .../info/profile/info_profile_text.cpp | 6 +- Telegram/SourceFiles/observer_peer.h | 41 +-- .../SourceFiles/support/support_helper.cpp | 255 +++++++++++++++++- Telegram/SourceFiles/support/support_helper.h | 41 +++ Telegram/SourceFiles/ui/text/text_entity.h | 23 ++ .../SourceFiles/window/window_peer_menu.cpp | 6 + 13 files changed, 407 insertions(+), 43 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 9a095ea92..a2e566b3a 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/stickers.h" #include "ui/text_options.h" #include "ui/emoji_config.h" +#include "support/support_helper.h" #include "storage/localimageloader.h" #include "storage/file_download.h" #include "storage/file_upload.h" @@ -814,18 +815,25 @@ void ApiWrap::requestFullPeer(PeerData *peer) { auto failHandler = [this, peer](const RPCError &error) { _fullPeerRequests.remove(peer); }; - if (auto user = peer->asUser()) { + if (const auto user = peer->asUser()) { + if (_session->supportMode()) { + _session->supportHelper().refreshInfo(user); + } return request(MTPusers_GetFullUser( user->inputUser - )).done([this, user](const MTPUserFull &result, mtpRequestId requestId) { + )).done([=](const MTPUserFull &result, mtpRequestId requestId) { gotUserFull(user, result, requestId); }).fail(failHandler).send(); - } else if (auto chat = peer->asChat()) { - return request(MTPmessages_GetFullChat(chat->inputChat)).done([this, peer](const MTPmessages_ChatFull &result, mtpRequestId requestId) { + } else if (const auto chat = peer->asChat()) { + return request(MTPmessages_GetFullChat( + chat->inputChat + )).done([=](const MTPmessages_ChatFull &result, mtpRequestId requestId) { gotChatFull(peer, result, requestId); }).fail(failHandler).send(); - } else if (auto channel = peer->asChannel()) { - return request(MTPchannels_GetFullChannel(channel->inputChannel)).done([this, peer](const MTPmessages_ChatFull &result, mtpRequestId requestId) { + } else if (const auto channel = peer->asChannel()) { + return request(MTPchannels_GetFullChannel( + channel->inputChannel + )).done([=](const MTPmessages_ChatFull &result, mtpRequestId requestId) { gotChatFull(peer, result, requestId); }).fail(failHandler).send(); } diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index e71804f69..99c4f7823 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -542,6 +542,10 @@ confirmBg: windowBgOver; confirmMaxHeight: 245px; confirmCompressedSkip: 10px; +supportInfoField: InputField(defaultInputField) { + heightMax: 256px; +} + connectionHostInputField: InputField(defaultInputField) { width: 160px; } diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 4071006da..d57ef8c94 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -63,6 +63,15 @@ dialogsTextPaletteDraftOver: TextPalette(defaultTextPalette) { dialogsTextPaletteDraftActive: TextPalette(defaultTextPalette) { linkFg: dialogsDraftFgActive; } +dialogsTextPaletteTaken: TextPalette(defaultTextPalette) { + linkFg: boxTextFgGood; +} +dialogsTextPaletteTakenOver: TextPalette(defaultTextPalette) { + linkFg: boxTextFgGood; +} +dialogsTextPaletteTakenActive: TextPalette(defaultTextPalette) { + linkFg: dialogsDraftFgActive; +} dialogsMenuToggle: IconButton { width: 40px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f80bcaffc..88f5b7f05 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -126,7 +126,7 @@ DialogsInner::DialogsInner(QWidget *parent, not_null contro | UpdateFlag::NameChanged | UpdateFlag::PhotoChanged | UpdateFlag::UserIsContact - | UpdateFlag::OccupiedChanged; + | UpdateFlag::UserOccupiedChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) { if (update.flags & UpdateFlag::ChatPinnedChanged) { stopReorderPinned(); @@ -134,7 +134,7 @@ DialogsInner::DialogsInner(QWidget *parent, not_null contro if (update.flags & UpdateFlag::NameChanged) { handlePeerNameChange(update.peer, update.oldNameFirstLetters); } - if (update.flags & (UpdateFlag::PhotoChanged | UpdateFlag::OccupiedChanged)) { + if (update.flags & (UpdateFlag::PhotoChanged | UpdateFlag::UserOccupiedChanged)) { this->update(); emit App::main()->dialogsUpdated(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index c1ac9b489..34d31ac78 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -281,7 +281,11 @@ void paintRow( history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, Ui::DialogTextOptions()); } p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg)); - p.setTextPalette(active ? st::dialogsTextPaletteDraftActive : (selected ? st::dialogsTextPaletteDraftOver : st::dialogsTextPaletteDraft)); + if (supportMode) { + p.setTextPalette(active ? st::dialogsTextPaletteTakenActive : (selected ? st::dialogsTextPaletteTakenOver : st::dialogsTextPaletteTaken)); + } else { + p.setTextPalette(active ? st::dialogsTextPaletteDraftActive : (selected ? st::dialogsTextPaletteDraftOver : st::dialogsTextPaletteDraft)); + } history->cloudDraftTextCache.drawElided(p, nameleft, texttop, availableWidth, 1); p.restoreTextPalette(); } diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 372664422..6bbebf432 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -106,7 +106,8 @@ TopBarWidget::TopBarWidget( using UpdateFlag = Notify::PeerUpdate::Flag; auto flags = UpdateFlag::UserHasCalls | UpdateFlag::UserOnlineChanged - | UpdateFlag::MembersChanged; + | UpdateFlag::MembersChanged + | UpdateFlag::UserSupportInfoChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(flags, [this](const Notify::PeerUpdate &update) { if (update.flags & UpdateFlag::UserHasCalls) { if (update.peer->isUser()) { @@ -739,8 +740,14 @@ void TopBarWidget::updateOnlineDisplay() { const auto now = unixtime(); bool titlePeerTextOnline = false; if (const auto user = _activeChat.peer()->asUser()) { - text = Data::OnlineText(user, now); - titlePeerTextOnline = Data::OnlineTextActive(user, now); + if (Auth().supportMode() + && !Auth().supportHelper().infoCurrent(user).text.empty()) { + text = QString::fromUtf8("\xe2\x9a\xa0\xef\xb8\x8f check info"); + titlePeerTextOnline = false; + } else { + text = Data::OnlineText(user, now); + titlePeerTextOnline = Data::OnlineTextActive(user, now); + } } else if (const auto chat = _activeChat.peer()->asChat()) { if (!chat->amIn()) { text = lang(lng_chat_status_unaccessible); diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 339cf24b6..b6a44082e 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_values.h" #include "info/profile/info_profile_button.h" #include "info/profile/info_profile_text.h" +#include "support/support_helper.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" #include "mainwidget.h" @@ -210,19 +211,28 @@ DetailsFiller::DetailsFiller( object_ptr DetailsFiller::setupInfo() { auto result = object_ptr(_wrap); auto tracker = Ui::MultiSlideTracker(); - auto addInfoLine = [&]( - LangKey label, + auto addInfoLineGeneric = [&]( + rpl::producer label, rpl::producer &&text, const style::FlatLabel &textSt = st::infoLabeled) { auto line = CreateTextWithLabel( result, - Lang::Viewer(label) | WithEmptyEntities(), + std::move(label) | WithEmptyEntities(), std::move(text), textSt, st::infoProfileLabeledPadding); tracker.track(result->add(std::move(line.wrap))); return line.text; }; + auto addInfoLine = [&]( + LangKey label, + rpl::producer &&text, + const style::FlatLabel &textSt = st::infoLabeled) { + return addInfoLineGeneric( + Lang::Viewer(label), + std::move(text), + textSt); + }; auto addInfoOneLine = [&]( LangKey label, rpl::producer &&text, @@ -236,6 +246,12 @@ object_ptr DetailsFiller::setupInfo() { return result; }; if (auto user = _peer->asUser()) { + if (Auth().supportMode()) { + addInfoLineGeneric( + Auth().supportHelper().infoLabelValue(user), + Auth().supportHelper().infoTextValue(user)); + } + addInfoOneLine( lng_info_mobile_label, PhoneValue(user), diff --git a/Telegram/SourceFiles/info/profile/info_profile_text.cpp b/Telegram/SourceFiles/info/profile/info_profile_text.cpp index a8064b069..d34773f05 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_text.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_text.cpp @@ -53,7 +53,11 @@ TextWithLabel CreateTextWithLabel( layout->add(Ui::CreateSkipWidget(layout, st::infoLabelSkip)); layout->add(object_ptr( layout, - std::move(label), + std::move( + label + ) | rpl::after_next([=] { + layout->resizeToWidth(layout->widthNoMargins()); + }), st::infoLabel)); result->finishAnimating(); return { std::move(result), labeled }; diff --git a/Telegram/SourceFiles/observer_peer.h b/Telegram/SourceFiles/observer_peer.h index f416c2c39..6e889c361 100644 --- a/Telegram/SourceFiles/observer_peer.h +++ b/Telegram/SourceFiles/observer_peer.h @@ -39,34 +39,35 @@ struct PeerUpdate { RestrictionReasonChanged = (1 << 8), UnreadViewChanged = (1 << 9), PinnedMessageChanged = (1 << 10), - OccupiedChanged = (1 << 11), // For chats and channels - InviteLinkChanged = (1 << 12), - MembersChanged = (1 << 13), - AdminsChanged = (1 << 14), - BannedUsersChanged = (1 << 15), - UnreadMentionsChanged = (1 << 16), + InviteLinkChanged = (1 << 11), + MembersChanged = (1 << 12), + AdminsChanged = (1 << 13), + BannedUsersChanged = (1 << 14), + UnreadMentionsChanged = (1 << 15), // For users - UserCanShareContact = (1 << 17), - UserIsContact = (1 << 18), - UserPhoneChanged = (1 << 19), - UserIsBlocked = (1 << 20), - BotCommandsChanged = (1 << 21), - UserOnlineChanged = (1 << 22), - BotCanAddToGroups = (1 << 23), - UserCommonChatsChanged = (1 << 24), - UserHasCalls = (1 << 25), + UserCanShareContact = (1 << 16), + UserIsContact = (1 << 17), + UserPhoneChanged = (1 << 18), + UserIsBlocked = (1 << 19), + BotCommandsChanged = (1 << 20), + UserOnlineChanged = (1 << 21), + BotCanAddToGroups = (1 << 22), + UserCommonChatsChanged = (1 << 23), + UserHasCalls = (1 << 24), + UserOccupiedChanged = (1 << 25), + UserSupportInfoChanged = (1 << 26), // For chats - ChatCanEdit = (1 << 17), + ChatCanEdit = (1 << 16), // For channels - ChannelAmIn = (1 << 17), - ChannelRightsChanged = (1 << 18), - ChannelStickersChanged = (1 << 19), - ChannelPromotedChanged = (1 << 20), + ChannelAmIn = (1 << 16), + ChannelRightsChanged = (1 << 17), + ChannelStickersChanged = (1 << 18), + ChannelPromotedChanged = (1 << 19), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index dca95c4aa..b7d5160cb 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -10,10 +10,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_key.h" #include "data/data_drafts.h" #include "history/history.h" +#include "boxes/abstract_box.h" +#include "ui/toast/toast.h" +#include "ui/widgets/input_fields.h" +#include "ui/text/text_entity.h" +#include "ui/text_options.h" +#include "chat_helpers/message_field.h" +#include "lang/lang_keys.h" #include "window/window_controller.h" #include "auth_session.h" #include "observer_peer.h" #include "apiwrap.h" +#include "styles/style_boxes.h" namespace Support { namespace { @@ -21,6 +29,104 @@ namespace { constexpr auto kOccupyFor = TimeId(60); constexpr auto kReoccupyEach = 30 * TimeMs(1000); +class EditInfoBox : public BoxContent { +public: + EditInfoBox( + QWidget*, + const TextWithTags &text, + Fn)> submit); + +protected: + void prepare() override; + void setInnerFocus() override; + +private: + object_ptr _field = { nullptr }; + Fn)> _submit; + +}; + +EditInfoBox::EditInfoBox( + QWidget*, + const TextWithTags &text, + Fn)> submit) +: _field( + this, + st::supportInfoField, + Ui::InputField::Mode::MultiLine, + [] { return QString("Support information"); }, + text) +, _submit(std::move(submit)) { + _field->setMaxLength(Global::CaptionLengthMax()); + _field->setSubmitSettings(Ui::InputField::SubmitSettings::Both); + _field->setInstantReplaces(Ui::InstantReplaces::Default()); + _field->setInstantReplacesEnabled(Global::ReplaceEmojiValue()); + _field->setMarkdownReplacesEnabled(rpl::single(true)); + _field->setEditLinkCallback(DefaultEditLinkCallback(_field)); +} + +void EditInfoBox::prepare() { + setTitle([] { return QString("Edit support information"); }); + + const auto save = [=] { + const auto done = crl::guard(this, [=](bool success) { + if (success) { + closeBox(); + } else { + _field->showError(); + } + }); + _submit(_field->getTextWithAppliedMarkdown(), done); + }; + addButton(langFactory(lng_settings_save), save); + addButton(langFactory(lng_cancel), [=] { closeBox(); }); + + connect(_field, &Ui::InputField::submitted, save); + connect(_field, &Ui::InputField::cancelled, [=] { closeBox(); }); + + auto cursor = _field->textCursor(); + cursor.movePosition(QTextCursor::End); + _field->setTextCursor(cursor); + + widthValue( + ) | rpl::start_with_next([=](int width) { + _field->resizeToWidth( + width - st::boxPadding.left() - st::boxPadding.right()); + _field->moveToLeft(st::boxPadding.left(), st::boxPadding.bottom()); + }, _field->lifetime()); + + _field->heightValue( + ) | rpl::start_with_next([=](int height) { + setDimensions( + st::boxWideWidth, + st::boxPadding.bottom() + height + st::boxPadding.bottom()); + }, _field->lifetime()); +} + +void EditInfoBox::setInnerFocus() { + _field->setFocusFast(); +} + +QString FormatDateTime(TimeId value) { + const auto now = QDateTime::currentDateTime(); + const auto date = ParseDateTime(value); + if (date.date() == now.date()) { + return lng_mediaview_today( + lt_time, + date.time().toString(cTimeFormat())); + } else if (date.date().addDays(1) == now.date()) { + return lng_mediaview_yesterday( + lt_time, + date.time().toString(cTimeFormat())); + } else { + return lng_mediaview_date_time( + lt_date, + date.date().toString(qsl("dd.MM.yy")), + lt_time, + date.time().toString(cTimeFormat())); + } +} + uint32 OccupationTag() { return uint32(Sandbox::UserTag() & 0xFFFFFFFFU); } @@ -45,7 +151,7 @@ Data::Draft OccupiedDraft(const QString &normalizedName) { } uint32 ParseOccupationTag(History *history) { - if (!history) { + if (!history || !history->peer->isUser()) { return 0; } const auto draft = history->cloudDraft(); @@ -75,7 +181,7 @@ uint32 ParseOccupationTag(History *history) { } QString ParseOccupationName(History *history) { - if (!history) { + if (!history || !history->peer->isUser()) { return QString(); } const auto draft = history->cloudDraft(); @@ -105,7 +211,7 @@ QString ParseOccupationName(History *history) { } TimeId OccupiedBySomeoneTill(History *history) { - if (!history) { + if (!history || !history->peer->isUser()) { return 0; } const auto draft = history->cloudDraft(); @@ -159,7 +265,8 @@ Helper::Helper(not_null session) void Helper::registerWindow(not_null controller) { controller->activeChatValue( ) | rpl::map([](Dialogs::Key key) { - return key.history(); + const auto history = key.history(); + return (history && history->peer->isUser()) ? history : nullptr; }) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](History *history) { updateOccupiedHistory(controller, history); @@ -179,12 +286,12 @@ void Helper::chatOccupiedUpdated(not_null history) { _occupiedChats[history] = till + 2; Notify::peerUpdatedDelayed( history->peer, - Notify::PeerUpdate::Flag::OccupiedChanged); + Notify::PeerUpdate::Flag::UserOccupiedChanged); checkOccupiedChats(); } else if (_occupiedChats.take(history)) { Notify::peerUpdatedDelayed( history->peer, - Notify::PeerUpdate::Flag::OccupiedChanged); + Notify::PeerUpdate::Flag::UserOccupiedChanged); } } @@ -200,7 +307,7 @@ void Helper::checkOccupiedChats() { _occupiedChats.erase(nearest); Notify::peerUpdatedDelayed( history->peer, - Notify::PeerUpdate::Flag::OccupiedChanged); + Notify::PeerUpdate::Flag::UserOccupiedChanged); } else { _checkOccupiedTimer.callOnce( (nearest->second - now) * TimeMs(1000)); @@ -266,6 +373,140 @@ bool Helper::isOccupiedBySomeone(History *history) const { return false; } +void Helper::refreshInfo(not_null user) { + request(MTPhelp_GetUserInfo( + user->inputUser + )).done([=](const MTPhelp_UserInfo &result) { + applyInfo(user, result); + if (_userInfoEditPending.contains(user)) { + _userInfoEditPending.erase(user); + showEditInfoBox(user); + } + }).send(); +} + +void Helper::applyInfo( + not_null user, + const MTPhelp_UserInfo &result) { + const auto notify = [&] { + Notify::peerUpdatedDelayed( + user, + Notify::PeerUpdate::Flag::UserSupportInfoChanged); + }; + const auto remove = [&] { + if (_userInformation.take(user)) { + notify(); + } + }; + result.match([&](const MTPDhelp_userInfo &data) { + auto info = UserInfo(); + info.author = qs(data.vauthor); + info.date = data.vdate.v; + info.text = TextWithEntities{ + qs(data.vmessage), + TextUtilities::EntitiesFromMTP(data.ventities.v) }; + if (info.text.empty()) { + remove(); + } else if (_userInformation[user] != info) { + _userInformation[user] = info; + notify(); + } + }, [&](const MTPDhelp_userInfoEmpty &) { + remove(); + }); +} + +rpl::producer Helper::infoValue(not_null user) const { + return Notify::PeerUpdateValue( + user, + Notify::PeerUpdate::Flag::UserSupportInfoChanged + ) | rpl::map([=] { + return infoCurrent(user); + }); +} + +rpl::producer Helper::infoLabelValue( + not_null user) const { + return infoValue( + user + ) | rpl::map([](const Support::UserInfo &info) { + return info.author + ", " + FormatDateTime(info.date); + }); +} + +rpl::producer Helper::infoTextValue( + not_null user) const { + return infoValue( + user + ) | rpl::map([](const Support::UserInfo &info) { + return info.text; + }); +} + +UserInfo Helper::infoCurrent(not_null user) const { + const auto i = _userInformation.find(user); + return (i != end(_userInformation)) ? i->second : UserInfo(); +} + +void Helper::editInfo(not_null user) { + if (!_userInfoEditPending.contains(user)) { + _userInfoEditPending.emplace(user); + refreshInfo(user); + } +} + +void Helper::showEditInfoBox(not_null user) { + const auto info = infoCurrent(user); + const auto editData = TextWithTags{ + info.text.text, + ConvertEntitiesToTextTags(info.text.entities) + }; + + const auto save = [=](TextWithTags result, Fn done) { + saveInfo(user, TextWithEntities{ + result.text, + ConvertTextTagsToEntities(result.tags) + }, done); + }; + Ui::show(Box(editData, save), LayerOption::KeepOther); +} + +void Helper::saveInfo( + not_null user, + TextWithEntities text, + Fn done) { + const auto i = _userInfoSaving.find(user); + if (i != end(_userInfoSaving)) { + if (i->second.data == text) { + return; + } else { + i->second.data = text; + request(base::take(i->second.requestId)).cancel(); + } + } else { + _userInfoSaving.emplace(user, SavingInfo{ text }); + } + + TextUtilities::PrepareForSending( + text, + Ui::ItemTextDefaultOptions().flags); + TextUtilities::Trim(text); + + const auto entities = TextUtilities::EntitiesToMTP( + text.entities, + TextUtilities::ConvertOption::SkipLocal); + _userInfoSaving[user].requestId = request(MTPhelp_EditUserInfo( + user->inputUser, + MTP_string(text.text), + entities + )).done([=](const MTPhelp_UserInfo &result) { + applyInfo(user, result); + done(true); + }).fail([=](const RPCError &error) { + done(false); + }).send(); +} + Templates &Helper::templates() { return _templates; } diff --git a/Telegram/SourceFiles/support/support_helper.h b/Telegram/SourceFiles/support/support_helper.h index 5f09c6e85..bd7685852 100644 --- a/Telegram/SourceFiles/support/support_helper.h +++ b/Telegram/SourceFiles/support/support_helper.h @@ -19,6 +19,22 @@ class Controller; namespace Support { +struct UserInfo { + QString author; + TimeId date = 0; + TextWithEntities text; +}; + +inline bool operator==(const UserInfo &a, const UserInfo &b) { + return (a.author == b.author) + && (a.date == b.date) + && (a.text == b.text); +} + +inline bool operator!=(const UserInfo &a, const UserInfo &b) { + return !(a == b); +} + class Helper : private MTP::Sender { public: explicit Helper(not_null session); @@ -31,9 +47,21 @@ public: bool isOccupiedByMe(History *history) const; bool isOccupiedBySomeone(History *history) const; + void refreshInfo(not_null user); + rpl::producer infoValue(not_null user) const; + rpl::producer infoLabelValue(not_null user) const; + rpl::producer infoTextValue( + not_null user) const; + UserInfo infoCurrent(not_null user) const; + void editInfo(not_null user); + Templates &templates(); private: + struct SavingInfo { + TextWithEntities data; + mtpRequestId requestId = 0; + }; void checkOccupiedChats(); void updateOccupiedHistory( not_null controller, @@ -43,6 +71,15 @@ private: void occupyInDraft(); void reoccupy(); + void applyInfo( + not_null user, + const MTPhelp_UserInfo &result); + void showEditInfoBox(not_null user); + void saveInfo( + not_null user, + TextWithEntities text, + Fn done); + not_null _session; Templates _templates; QString _supportName; @@ -53,6 +90,10 @@ private: base::Timer _checkOccupiedTimer; base::flat_map, TimeId> _occupiedChats; + base::flat_map, UserInfo> _userInformation; + base::flat_set> _userInfoEditPending; + base::flat_map, SavingInfo> _userInfoSaving; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/ui/text/text_entity.h b/Telegram/SourceFiles/ui/text/text_entity.h index 0365362fd..6e7de2269 100644 --- a/Telegram/SourceFiles/ui/text/text_entity.h +++ b/Telegram/SourceFiles/ui/text/text_entity.h @@ -100,6 +100,17 @@ private: }; +inline bool operator==(const EntityInText &a, const EntityInText &b) { + return (a.type() == b.type()) + && (a.offset() == b.offset()) + && (a.length() == b.length()) + && (a.data() == b.data()); +} + +inline bool operator!=(const EntityInText &a, const EntityInText &b) { + return !(a == b); +} + struct TextWithEntities { QString text; EntitiesInText entities; @@ -109,6 +120,18 @@ struct TextWithEntities { } }; +inline bool operator==( + const TextWithEntities &a, + const TextWithEntities &b) { + return (a.text == b.text) && (a.entities == b.entities); +} + +inline bool operator!=( + const TextWithEntities &a, + const TextWithEntities &b) { + return !(a == b); +} + enum { TextParseMultiline = 0x001, TextParseLinks = 0x002, diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 9700fb6b9..be7f842c4 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" #include "history/history.h" #include "window/window_controller.h" +#include "support/support_helper.h" #include "info/info_memento.h" #include "info/info_controller.h" #include "info/feed/info_feed_channels_controllers.h" @@ -316,6 +317,11 @@ void Filler::addBlockUser(not_null user) { void Filler::addUserActions(not_null user) { if (_source != PeerMenuSource::ChatsList) { + if (Auth().supportMode()) { + _addAction("Edit support info", [=] { + Auth().supportHelper().editInfo(user); + }); + } if (user->isContact()) { if (!user->isSelf()) { _addAction(