mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 02:01:40 -05:00
Add edit / view of user information for support.
This commit is contained in:
parent
5e1b8212b2
commit
9a8ab84ecb
13 changed files with 407 additions and 43 deletions
|
@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "chat_helpers/stickers.h"
|
#include "chat_helpers/stickers.h"
|
||||||
#include "ui/text_options.h"
|
#include "ui/text_options.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
|
#include "support/support_helper.h"
|
||||||
#include "storage/localimageloader.h"
|
#include "storage/localimageloader.h"
|
||||||
#include "storage/file_download.h"
|
#include "storage/file_download.h"
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
|
@ -814,18 +815,25 @@ void ApiWrap::requestFullPeer(PeerData *peer) {
|
||||||
auto failHandler = [this, peer](const RPCError &error) {
|
auto failHandler = [this, peer](const RPCError &error) {
|
||||||
_fullPeerRequests.remove(peer);
|
_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(
|
return request(MTPusers_GetFullUser(
|
||||||
user->inputUser
|
user->inputUser
|
||||||
)).done([this, user](const MTPUserFull &result, mtpRequestId requestId) {
|
)).done([=](const MTPUserFull &result, mtpRequestId requestId) {
|
||||||
gotUserFull(user, result, requestId);
|
gotUserFull(user, result, requestId);
|
||||||
}).fail(failHandler).send();
|
}).fail(failHandler).send();
|
||||||
} else if (auto chat = peer->asChat()) {
|
} else if (const auto chat = peer->asChat()) {
|
||||||
return request(MTPmessages_GetFullChat(chat->inputChat)).done([this, peer](const MTPmessages_ChatFull &result, mtpRequestId requestId) {
|
return request(MTPmessages_GetFullChat(
|
||||||
|
chat->inputChat
|
||||||
|
)).done([=](const MTPmessages_ChatFull &result, mtpRequestId requestId) {
|
||||||
gotChatFull(peer, result, requestId);
|
gotChatFull(peer, result, requestId);
|
||||||
}).fail(failHandler).send();
|
}).fail(failHandler).send();
|
||||||
} else if (auto channel = peer->asChannel()) {
|
} else if (const auto channel = peer->asChannel()) {
|
||||||
return request(MTPchannels_GetFullChannel(channel->inputChannel)).done([this, peer](const MTPmessages_ChatFull &result, mtpRequestId requestId) {
|
return request(MTPchannels_GetFullChannel(
|
||||||
|
channel->inputChannel
|
||||||
|
)).done([=](const MTPmessages_ChatFull &result, mtpRequestId requestId) {
|
||||||
gotChatFull(peer, result, requestId);
|
gotChatFull(peer, result, requestId);
|
||||||
}).fail(failHandler).send();
|
}).fail(failHandler).send();
|
||||||
}
|
}
|
||||||
|
|
|
@ -542,6 +542,10 @@ confirmBg: windowBgOver;
|
||||||
confirmMaxHeight: 245px;
|
confirmMaxHeight: 245px;
|
||||||
confirmCompressedSkip: 10px;
|
confirmCompressedSkip: 10px;
|
||||||
|
|
||||||
|
supportInfoField: InputField(defaultInputField) {
|
||||||
|
heightMax: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
connectionHostInputField: InputField(defaultInputField) {
|
connectionHostInputField: InputField(defaultInputField) {
|
||||||
width: 160px;
|
width: 160px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,15 @@ dialogsTextPaletteDraftOver: TextPalette(defaultTextPalette) {
|
||||||
dialogsTextPaletteDraftActive: TextPalette(defaultTextPalette) {
|
dialogsTextPaletteDraftActive: TextPalette(defaultTextPalette) {
|
||||||
linkFg: dialogsDraftFgActive;
|
linkFg: dialogsDraftFgActive;
|
||||||
}
|
}
|
||||||
|
dialogsTextPaletteTaken: TextPalette(defaultTextPalette) {
|
||||||
|
linkFg: boxTextFgGood;
|
||||||
|
}
|
||||||
|
dialogsTextPaletteTakenOver: TextPalette(defaultTextPalette) {
|
||||||
|
linkFg: boxTextFgGood;
|
||||||
|
}
|
||||||
|
dialogsTextPaletteTakenActive: TextPalette(defaultTextPalette) {
|
||||||
|
linkFg: dialogsDraftFgActive;
|
||||||
|
}
|
||||||
|
|
||||||
dialogsMenuToggle: IconButton {
|
dialogsMenuToggle: IconButton {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
|
|
|
@ -126,7 +126,7 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
|
||||||
| UpdateFlag::NameChanged
|
| UpdateFlag::NameChanged
|
||||||
| UpdateFlag::PhotoChanged
|
| UpdateFlag::PhotoChanged
|
||||||
| UpdateFlag::UserIsContact
|
| UpdateFlag::UserIsContact
|
||||||
| UpdateFlag::OccupiedChanged;
|
| UpdateFlag::UserOccupiedChanged;
|
||||||
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) {
|
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) {
|
||||||
if (update.flags & UpdateFlag::ChatPinnedChanged) {
|
if (update.flags & UpdateFlag::ChatPinnedChanged) {
|
||||||
stopReorderPinned();
|
stopReorderPinned();
|
||||||
|
@ -134,7 +134,7 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
|
||||||
if (update.flags & UpdateFlag::NameChanged) {
|
if (update.flags & UpdateFlag::NameChanged) {
|
||||||
handlePeerNameChange(update.peer, update.oldNameFirstLetters);
|
handlePeerNameChange(update.peer, update.oldNameFirstLetters);
|
||||||
}
|
}
|
||||||
if (update.flags & (UpdateFlag::PhotoChanged | UpdateFlag::OccupiedChanged)) {
|
if (update.flags & (UpdateFlag::PhotoChanged | UpdateFlag::UserOccupiedChanged)) {
|
||||||
this->update();
|
this->update();
|
||||||
emit App::main()->dialogsUpdated();
|
emit App::main()->dialogsUpdated();
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,7 +281,11 @@ void paintRow(
|
||||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, Ui::DialogTextOptions());
|
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, Ui::DialogTextOptions());
|
||||||
}
|
}
|
||||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||||
|
if (supportMode) {
|
||||||
|
p.setTextPalette(active ? st::dialogsTextPaletteTakenActive : (selected ? st::dialogsTextPaletteTakenOver : st::dialogsTextPaletteTaken));
|
||||||
|
} else {
|
||||||
p.setTextPalette(active ? st::dialogsTextPaletteDraftActive : (selected ? st::dialogsTextPaletteDraftOver : st::dialogsTextPaletteDraft));
|
p.setTextPalette(active ? st::dialogsTextPaletteDraftActive : (selected ? st::dialogsTextPaletteDraftOver : st::dialogsTextPaletteDraft));
|
||||||
|
}
|
||||||
history->cloudDraftTextCache.drawElided(p, nameleft, texttop, availableWidth, 1);
|
history->cloudDraftTextCache.drawElided(p, nameleft, texttop, availableWidth, 1);
|
||||||
p.restoreTextPalette();
|
p.restoreTextPalette();
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,8 @@ TopBarWidget::TopBarWidget(
|
||||||
using UpdateFlag = Notify::PeerUpdate::Flag;
|
using UpdateFlag = Notify::PeerUpdate::Flag;
|
||||||
auto flags = UpdateFlag::UserHasCalls
|
auto flags = UpdateFlag::UserHasCalls
|
||||||
| UpdateFlag::UserOnlineChanged
|
| UpdateFlag::UserOnlineChanged
|
||||||
| UpdateFlag::MembersChanged;
|
| UpdateFlag::MembersChanged
|
||||||
|
| UpdateFlag::UserSupportInfoChanged;
|
||||||
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(flags, [this](const Notify::PeerUpdate &update) {
|
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(flags, [this](const Notify::PeerUpdate &update) {
|
||||||
if (update.flags & UpdateFlag::UserHasCalls) {
|
if (update.flags & UpdateFlag::UserHasCalls) {
|
||||||
if (update.peer->isUser()) {
|
if (update.peer->isUser()) {
|
||||||
|
@ -739,8 +740,14 @@ void TopBarWidget::updateOnlineDisplay() {
|
||||||
const auto now = unixtime();
|
const auto now = unixtime();
|
||||||
bool titlePeerTextOnline = false;
|
bool titlePeerTextOnline = false;
|
||||||
if (const auto user = _activeChat.peer()->asUser()) {
|
if (const auto user = _activeChat.peer()->asUser()) {
|
||||||
|
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);
|
text = Data::OnlineText(user, now);
|
||||||
titlePeerTextOnline = Data::OnlineTextActive(user, now);
|
titlePeerTextOnline = Data::OnlineTextActive(user, now);
|
||||||
|
}
|
||||||
} else if (const auto chat = _activeChat.peer()->asChat()) {
|
} else if (const auto chat = _activeChat.peer()->asChat()) {
|
||||||
if (!chat->amIn()) {
|
if (!chat->amIn()) {
|
||||||
text = lang(lng_chat_status_unaccessible);
|
text = lang(lng_chat_status_unaccessible);
|
||||||
|
|
|
@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "info/profile/info_profile_values.h"
|
#include "info/profile/info_profile_values.h"
|
||||||
#include "info/profile/info_profile_button.h"
|
#include "info/profile/info_profile_button.h"
|
||||||
#include "info/profile/info_profile_text.h"
|
#include "info/profile/info_profile_text.h"
|
||||||
|
#include "support/support_helper.h"
|
||||||
#include "window/window_controller.h"
|
#include "window/window_controller.h"
|
||||||
#include "window/window_peer_menu.h"
|
#include "window/window_peer_menu.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
@ -210,19 +211,28 @@ DetailsFiller::DetailsFiller(
|
||||||
object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||||
auto result = object_ptr<Ui::VerticalLayout>(_wrap);
|
auto result = object_ptr<Ui::VerticalLayout>(_wrap);
|
||||||
auto tracker = Ui::MultiSlideTracker();
|
auto tracker = Ui::MultiSlideTracker();
|
||||||
auto addInfoLine = [&](
|
auto addInfoLineGeneric = [&](
|
||||||
LangKey label,
|
rpl::producer<QString> label,
|
||||||
rpl::producer<TextWithEntities> &&text,
|
rpl::producer<TextWithEntities> &&text,
|
||||||
const style::FlatLabel &textSt = st::infoLabeled) {
|
const style::FlatLabel &textSt = st::infoLabeled) {
|
||||||
auto line = CreateTextWithLabel(
|
auto line = CreateTextWithLabel(
|
||||||
result,
|
result,
|
||||||
Lang::Viewer(label) | WithEmptyEntities(),
|
std::move(label) | WithEmptyEntities(),
|
||||||
std::move(text),
|
std::move(text),
|
||||||
textSt,
|
textSt,
|
||||||
st::infoProfileLabeledPadding);
|
st::infoProfileLabeledPadding);
|
||||||
tracker.track(result->add(std::move(line.wrap)));
|
tracker.track(result->add(std::move(line.wrap)));
|
||||||
return line.text;
|
return line.text;
|
||||||
};
|
};
|
||||||
|
auto addInfoLine = [&](
|
||||||
|
LangKey label,
|
||||||
|
rpl::producer<TextWithEntities> &&text,
|
||||||
|
const style::FlatLabel &textSt = st::infoLabeled) {
|
||||||
|
return addInfoLineGeneric(
|
||||||
|
Lang::Viewer(label),
|
||||||
|
std::move(text),
|
||||||
|
textSt);
|
||||||
|
};
|
||||||
auto addInfoOneLine = [&](
|
auto addInfoOneLine = [&](
|
||||||
LangKey label,
|
LangKey label,
|
||||||
rpl::producer<TextWithEntities> &&text,
|
rpl::producer<TextWithEntities> &&text,
|
||||||
|
@ -236,6 +246,12 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
if (auto user = _peer->asUser()) {
|
if (auto user = _peer->asUser()) {
|
||||||
|
if (Auth().supportMode()) {
|
||||||
|
addInfoLineGeneric(
|
||||||
|
Auth().supportHelper().infoLabelValue(user),
|
||||||
|
Auth().supportHelper().infoTextValue(user));
|
||||||
|
}
|
||||||
|
|
||||||
addInfoOneLine(
|
addInfoOneLine(
|
||||||
lng_info_mobile_label,
|
lng_info_mobile_label,
|
||||||
PhoneValue(user),
|
PhoneValue(user),
|
||||||
|
|
|
@ -53,7 +53,11 @@ TextWithLabel CreateTextWithLabel(
|
||||||
layout->add(Ui::CreateSkipWidget(layout, st::infoLabelSkip));
|
layout->add(Ui::CreateSkipWidget(layout, st::infoLabelSkip));
|
||||||
layout->add(object_ptr<Ui::FlatLabel>(
|
layout->add(object_ptr<Ui::FlatLabel>(
|
||||||
layout,
|
layout,
|
||||||
std::move(label),
|
std::move(
|
||||||
|
label
|
||||||
|
) | rpl::after_next([=] {
|
||||||
|
layout->resizeToWidth(layout->widthNoMargins());
|
||||||
|
}),
|
||||||
st::infoLabel));
|
st::infoLabel));
|
||||||
result->finishAnimating();
|
result->finishAnimating();
|
||||||
return { std::move(result), labeled };
|
return { std::move(result), labeled };
|
||||||
|
|
|
@ -39,34 +39,35 @@ struct PeerUpdate {
|
||||||
RestrictionReasonChanged = (1 << 8),
|
RestrictionReasonChanged = (1 << 8),
|
||||||
UnreadViewChanged = (1 << 9),
|
UnreadViewChanged = (1 << 9),
|
||||||
PinnedMessageChanged = (1 << 10),
|
PinnedMessageChanged = (1 << 10),
|
||||||
OccupiedChanged = (1 << 11),
|
|
||||||
|
|
||||||
// For chats and channels
|
// For chats and channels
|
||||||
InviteLinkChanged = (1 << 12),
|
InviteLinkChanged = (1 << 11),
|
||||||
MembersChanged = (1 << 13),
|
MembersChanged = (1 << 12),
|
||||||
AdminsChanged = (1 << 14),
|
AdminsChanged = (1 << 13),
|
||||||
BannedUsersChanged = (1 << 15),
|
BannedUsersChanged = (1 << 14),
|
||||||
UnreadMentionsChanged = (1 << 16),
|
UnreadMentionsChanged = (1 << 15),
|
||||||
|
|
||||||
// For users
|
// For users
|
||||||
UserCanShareContact = (1 << 17),
|
UserCanShareContact = (1 << 16),
|
||||||
UserIsContact = (1 << 18),
|
UserIsContact = (1 << 17),
|
||||||
UserPhoneChanged = (1 << 19),
|
UserPhoneChanged = (1 << 18),
|
||||||
UserIsBlocked = (1 << 20),
|
UserIsBlocked = (1 << 19),
|
||||||
BotCommandsChanged = (1 << 21),
|
BotCommandsChanged = (1 << 20),
|
||||||
UserOnlineChanged = (1 << 22),
|
UserOnlineChanged = (1 << 21),
|
||||||
BotCanAddToGroups = (1 << 23),
|
BotCanAddToGroups = (1 << 22),
|
||||||
UserCommonChatsChanged = (1 << 24),
|
UserCommonChatsChanged = (1 << 23),
|
||||||
UserHasCalls = (1 << 25),
|
UserHasCalls = (1 << 24),
|
||||||
|
UserOccupiedChanged = (1 << 25),
|
||||||
|
UserSupportInfoChanged = (1 << 26),
|
||||||
|
|
||||||
// For chats
|
// For chats
|
||||||
ChatCanEdit = (1 << 17),
|
ChatCanEdit = (1 << 16),
|
||||||
|
|
||||||
// For channels
|
// For channels
|
||||||
ChannelAmIn = (1 << 17),
|
ChannelAmIn = (1 << 16),
|
||||||
ChannelRightsChanged = (1 << 18),
|
ChannelRightsChanged = (1 << 17),
|
||||||
ChannelStickersChanged = (1 << 19),
|
ChannelStickersChanged = (1 << 18),
|
||||||
ChannelPromotedChanged = (1 << 20),
|
ChannelPromotedChanged = (1 << 19),
|
||||||
};
|
};
|
||||||
using Flags = base::flags<Flag>;
|
using Flags = base::flags<Flag>;
|
||||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||||
|
|
|
@ -10,10 +10,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "dialogs/dialogs_key.h"
|
#include "dialogs/dialogs_key.h"
|
||||||
#include "data/data_drafts.h"
|
#include "data/data_drafts.h"
|
||||||
#include "history/history.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 "window/window_controller.h"
|
||||||
#include "auth_session.h"
|
#include "auth_session.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "styles/style_boxes.h"
|
||||||
|
|
||||||
namespace Support {
|
namespace Support {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -21,6 +29,104 @@ namespace {
|
||||||
constexpr auto kOccupyFor = TimeId(60);
|
constexpr auto kOccupyFor = TimeId(60);
|
||||||
constexpr auto kReoccupyEach = 30 * TimeMs(1000);
|
constexpr auto kReoccupyEach = 30 * TimeMs(1000);
|
||||||
|
|
||||||
|
class EditInfoBox : public BoxContent {
|
||||||
|
public:
|
||||||
|
EditInfoBox(
|
||||||
|
QWidget*,
|
||||||
|
const TextWithTags &text,
|
||||||
|
Fn<void(TextWithTags, Fn<void(bool success)>)> submit);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void prepare() override;
|
||||||
|
void setInnerFocus() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
object_ptr<Ui::InputField> _field = { nullptr };
|
||||||
|
Fn<void(TextWithTags, Fn<void(bool success)>)> _submit;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EditInfoBox::EditInfoBox(
|
||||||
|
QWidget*,
|
||||||
|
const TextWithTags &text,
|
||||||
|
Fn<void(TextWithTags, Fn<void(bool success)>)> 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() {
|
uint32 OccupationTag() {
|
||||||
return uint32(Sandbox::UserTag() & 0xFFFFFFFFU);
|
return uint32(Sandbox::UserTag() & 0xFFFFFFFFU);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +151,7 @@ Data::Draft OccupiedDraft(const QString &normalizedName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 ParseOccupationTag(History *history) {
|
uint32 ParseOccupationTag(History *history) {
|
||||||
if (!history) {
|
if (!history || !history->peer->isUser()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const auto draft = history->cloudDraft();
|
const auto draft = history->cloudDraft();
|
||||||
|
@ -75,7 +181,7 @@ uint32 ParseOccupationTag(History *history) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ParseOccupationName(History *history) {
|
QString ParseOccupationName(History *history) {
|
||||||
if (!history) {
|
if (!history || !history->peer->isUser()) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
const auto draft = history->cloudDraft();
|
const auto draft = history->cloudDraft();
|
||||||
|
@ -105,7 +211,7 @@ QString ParseOccupationName(History *history) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeId OccupiedBySomeoneTill(History *history) {
|
TimeId OccupiedBySomeoneTill(History *history) {
|
||||||
if (!history) {
|
if (!history || !history->peer->isUser()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const auto draft = history->cloudDraft();
|
const auto draft = history->cloudDraft();
|
||||||
|
@ -159,7 +265,8 @@ Helper::Helper(not_null<AuthSession*> session)
|
||||||
void Helper::registerWindow(not_null<Window::Controller*> controller) {
|
void Helper::registerWindow(not_null<Window::Controller*> controller) {
|
||||||
controller->activeChatValue(
|
controller->activeChatValue(
|
||||||
) | rpl::map([](Dialogs::Key key) {
|
) | 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::distinct_until_changed(
|
||||||
) | rpl::start_with_next([=](History *history) {
|
) | rpl::start_with_next([=](History *history) {
|
||||||
updateOccupiedHistory(controller, history);
|
updateOccupiedHistory(controller, history);
|
||||||
|
@ -179,12 +286,12 @@ void Helper::chatOccupiedUpdated(not_null<History*> history) {
|
||||||
_occupiedChats[history] = till + 2;
|
_occupiedChats[history] = till + 2;
|
||||||
Notify::peerUpdatedDelayed(
|
Notify::peerUpdatedDelayed(
|
||||||
history->peer,
|
history->peer,
|
||||||
Notify::PeerUpdate::Flag::OccupiedChanged);
|
Notify::PeerUpdate::Flag::UserOccupiedChanged);
|
||||||
checkOccupiedChats();
|
checkOccupiedChats();
|
||||||
} else if (_occupiedChats.take(history)) {
|
} else if (_occupiedChats.take(history)) {
|
||||||
Notify::peerUpdatedDelayed(
|
Notify::peerUpdatedDelayed(
|
||||||
history->peer,
|
history->peer,
|
||||||
Notify::PeerUpdate::Flag::OccupiedChanged);
|
Notify::PeerUpdate::Flag::UserOccupiedChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +307,7 @@ void Helper::checkOccupiedChats() {
|
||||||
_occupiedChats.erase(nearest);
|
_occupiedChats.erase(nearest);
|
||||||
Notify::peerUpdatedDelayed(
|
Notify::peerUpdatedDelayed(
|
||||||
history->peer,
|
history->peer,
|
||||||
Notify::PeerUpdate::Flag::OccupiedChanged);
|
Notify::PeerUpdate::Flag::UserOccupiedChanged);
|
||||||
} else {
|
} else {
|
||||||
_checkOccupiedTimer.callOnce(
|
_checkOccupiedTimer.callOnce(
|
||||||
(nearest->second - now) * TimeMs(1000));
|
(nearest->second - now) * TimeMs(1000));
|
||||||
|
@ -266,6 +373,140 @@ bool Helper::isOccupiedBySomeone(History *history) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Helper::refreshInfo(not_null<UserData*> 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<UserData*> 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<UserInfo> Helper::infoValue(not_null<UserData*> user) const {
|
||||||
|
return Notify::PeerUpdateValue(
|
||||||
|
user,
|
||||||
|
Notify::PeerUpdate::Flag::UserSupportInfoChanged
|
||||||
|
) | rpl::map([=] {
|
||||||
|
return infoCurrent(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> Helper::infoLabelValue(
|
||||||
|
not_null<UserData*> user) const {
|
||||||
|
return infoValue(
|
||||||
|
user
|
||||||
|
) | rpl::map([](const Support::UserInfo &info) {
|
||||||
|
return info.author + ", " + FormatDateTime(info.date);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<TextWithEntities> Helper::infoTextValue(
|
||||||
|
not_null<UserData*> user) const {
|
||||||
|
return infoValue(
|
||||||
|
user
|
||||||
|
) | rpl::map([](const Support::UserInfo &info) {
|
||||||
|
return info.text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
UserInfo Helper::infoCurrent(not_null<UserData*> user) const {
|
||||||
|
const auto i = _userInformation.find(user);
|
||||||
|
return (i != end(_userInformation)) ? i->second : UserInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helper::editInfo(not_null<UserData*> user) {
|
||||||
|
if (!_userInfoEditPending.contains(user)) {
|
||||||
|
_userInfoEditPending.emplace(user);
|
||||||
|
refreshInfo(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helper::showEditInfoBox(not_null<UserData*> user) {
|
||||||
|
const auto info = infoCurrent(user);
|
||||||
|
const auto editData = TextWithTags{
|
||||||
|
info.text.text,
|
||||||
|
ConvertEntitiesToTextTags(info.text.entities)
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto save = [=](TextWithTags result, Fn<void(bool)> done) {
|
||||||
|
saveInfo(user, TextWithEntities{
|
||||||
|
result.text,
|
||||||
|
ConvertTextTagsToEntities(result.tags)
|
||||||
|
}, done);
|
||||||
|
};
|
||||||
|
Ui::show(Box<EditInfoBox>(editData, save), LayerOption::KeepOther);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helper::saveInfo(
|
||||||
|
not_null<UserData*> user,
|
||||||
|
TextWithEntities text,
|
||||||
|
Fn<void(bool success)> 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() {
|
Templates &Helper::templates() {
|
||||||
return _templates;
|
return _templates;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,22 @@ class Controller;
|
||||||
|
|
||||||
namespace Support {
|
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 {
|
class Helper : private MTP::Sender {
|
||||||
public:
|
public:
|
||||||
explicit Helper(not_null<AuthSession*> session);
|
explicit Helper(not_null<AuthSession*> session);
|
||||||
|
@ -31,9 +47,21 @@ public:
|
||||||
bool isOccupiedByMe(History *history) const;
|
bool isOccupiedByMe(History *history) const;
|
||||||
bool isOccupiedBySomeone(History *history) const;
|
bool isOccupiedBySomeone(History *history) const;
|
||||||
|
|
||||||
|
void refreshInfo(not_null<UserData*> user);
|
||||||
|
rpl::producer<UserInfo> infoValue(not_null<UserData*> user) const;
|
||||||
|
rpl::producer<QString> infoLabelValue(not_null<UserData*> user) const;
|
||||||
|
rpl::producer<TextWithEntities> infoTextValue(
|
||||||
|
not_null<UserData*> user) const;
|
||||||
|
UserInfo infoCurrent(not_null<UserData*> user) const;
|
||||||
|
void editInfo(not_null<UserData*> user);
|
||||||
|
|
||||||
Templates &templates();
|
Templates &templates();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct SavingInfo {
|
||||||
|
TextWithEntities data;
|
||||||
|
mtpRequestId requestId = 0;
|
||||||
|
};
|
||||||
void checkOccupiedChats();
|
void checkOccupiedChats();
|
||||||
void updateOccupiedHistory(
|
void updateOccupiedHistory(
|
||||||
not_null<Window::Controller*> controller,
|
not_null<Window::Controller*> controller,
|
||||||
|
@ -43,6 +71,15 @@ private:
|
||||||
void occupyInDraft();
|
void occupyInDraft();
|
||||||
void reoccupy();
|
void reoccupy();
|
||||||
|
|
||||||
|
void applyInfo(
|
||||||
|
not_null<UserData*> user,
|
||||||
|
const MTPhelp_UserInfo &result);
|
||||||
|
void showEditInfoBox(not_null<UserData*> user);
|
||||||
|
void saveInfo(
|
||||||
|
not_null<UserData*> user,
|
||||||
|
TextWithEntities text,
|
||||||
|
Fn<void(bool success)> done);
|
||||||
|
|
||||||
not_null<AuthSession*> _session;
|
not_null<AuthSession*> _session;
|
||||||
Templates _templates;
|
Templates _templates;
|
||||||
QString _supportName;
|
QString _supportName;
|
||||||
|
@ -53,6 +90,10 @@ private:
|
||||||
base::Timer _checkOccupiedTimer;
|
base::Timer _checkOccupiedTimer;
|
||||||
base::flat_map<not_null<History*>, TimeId> _occupiedChats;
|
base::flat_map<not_null<History*>, TimeId> _occupiedChats;
|
||||||
|
|
||||||
|
base::flat_map<not_null<UserData*>, UserInfo> _userInformation;
|
||||||
|
base::flat_set<not_null<UserData*>> _userInfoEditPending;
|
||||||
|
base::flat_map<not_null<UserData*>, SavingInfo> _userInfoSaving;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {
|
struct TextWithEntities {
|
||||||
QString text;
|
QString text;
|
||||||
EntitiesInText entities;
|
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 {
|
enum {
|
||||||
TextParseMultiline = 0x001,
|
TextParseMultiline = 0x001,
|
||||||
TextParseLinks = 0x002,
|
TextParseLinks = 0x002,
|
||||||
|
|
|
@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "window/window_controller.h"
|
#include "window/window_controller.h"
|
||||||
|
#include "support/support_helper.h"
|
||||||
#include "info/info_memento.h"
|
#include "info/info_memento.h"
|
||||||
#include "info/info_controller.h"
|
#include "info/info_controller.h"
|
||||||
#include "info/feed/info_feed_channels_controllers.h"
|
#include "info/feed/info_feed_channels_controllers.h"
|
||||||
|
@ -316,6 +317,11 @@ void Filler::addBlockUser(not_null<UserData*> user) {
|
||||||
|
|
||||||
void Filler::addUserActions(not_null<UserData*> user) {
|
void Filler::addUserActions(not_null<UserData*> user) {
|
||||||
if (_source != PeerMenuSource::ChatsList) {
|
if (_source != PeerMenuSource::ChatsList) {
|
||||||
|
if (Auth().supportMode()) {
|
||||||
|
_addAction("Edit support info", [=] {
|
||||||
|
Auth().supportHelper().editInfo(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (user->isContact()) {
|
if (user->isContact()) {
|
||||||
if (!user->isSelf()) {
|
if (!user->isSelf()) {
|
||||||
_addAction(
|
_addAction(
|
||||||
|
|
Loading…
Add table
Reference in a new issue