Implement proper shortcut management.

This commit is contained in:
John Preston 2024-02-28 08:58:37 +04:00
parent 23e22de6ec
commit f086203d25
19 changed files with 474 additions and 87 deletions

View File

@ -1560,6 +1560,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
if (const auto local = owner.message(id)) {
if (local->isScheduled()) {
session().data().scheduledMessages().apply(d, local);
} else if (local->isBusinessShortcut()) {
session().data().shortcutMessages().apply(d, local);
} else {
const auto existing = session().data().message(
id.peer,

View File

@ -128,6 +128,94 @@ void ShortcutMessages::clearOldRequests() {
}
}
void ShortcutMessages::updateShortcuts(const QVector<MTPQuickReply> &list) {
auto shortcuts = parseShortcuts(list);
auto changes = std::vector<ShortcutIdChange>();
for (auto &[id, shortcut] : _shortcuts.list) {
if (shortcuts.list.contains(id)) {
continue;
}
auto foundId = BusinessShortcutId();
for (auto &[realId, real] : shortcuts.list) {
if (real.name == shortcut.name) {
foundId = realId;
break;
}
}
if (foundId) {
mergeMessagesFromTo(id, foundId);
changes.push_back({ .oldId = id, .newId = foundId });
} else {
shortcuts.list.emplace(id, shortcut);
}
}
const auto changed = !_shortcutsLoaded
|| (shortcuts != _shortcuts);
if (changed) {
_shortcuts = std::move(shortcuts);
_shortcutsLoaded = true;
for (const auto &change : changes) {
_shortcutIdChanges.fire_copy(change);
}
_shortcutsChanged.fire({});
} else {
Assert(changes.empty());
}
}
void ShortcutMessages::mergeMessagesFromTo(
BusinessShortcutId fromId,
BusinessShortcutId toId) {
auto &to = _data[toId];
const auto i = _data.find(fromId);
if (i == end(_data)) {
return;
}
auto &from = i->second;
auto destroy = base::flat_set<not_null<HistoryItem*>>();
for (auto &item : from.items) {
if (item->isSending() || item->hasFailed()) {
item->setRealShortcutId(toId);
to.items.push_back(std::move(item));
} else {
destroy.emplace(item.get());
}
}
for (const auto &item : destroy) {
item->destroy();
}
_data.remove(fromId);
cancelRequest(fromId);
_updates.fire_copy(toId);
if (!destroy.empty()) {
cancelRequest(toId);
request(toId);
}
}
Shortcuts ShortcutMessages::parseShortcuts(
const QVector<MTPQuickReply> &list) const {
auto result = Shortcuts();
for (const auto &reply : list) {
const auto shortcut = parseShortcut(reply);
result.list.emplace(shortcut.id, shortcut);
}
return result;
}
Shortcut ShortcutMessages::parseShortcut(const MTPQuickReply &reply) const {
const auto &data = reply.data();
return Shortcut{
.id = BusinessShortcutId(data.vshortcut_id().v),
.count = data.vcount().v,
.name = qs(data.vshortcut()),
.topMessageId = localMessageId(data.vtop_message().v),
};
}
MsgId ShortcutMessages::localMessageId(MsgId remoteId) const {
return RemoteToLocalMsgId(remoteId);
}
@ -146,11 +234,60 @@ int ShortcutMessages::count(BusinessShortcutId shortcutId) const {
}
void ShortcutMessages::apply(const MTPDupdateQuickReplies &update) {
updateShortcuts(update.vquick_replies().v);
scheduleShortcutsReload();
}
void ShortcutMessages::scheduleShortcutsReload() {
const auto hasUnknownMessages = [&] {
const auto selfId = _session->userPeerId();
for (const auto &[id, shortcut] : _shortcuts.list) {
if (!_session->data().message({ selfId, shortcut.topMessageId })) {
return true;
}
}
return false;
};
if (hasUnknownMessages()) {
_shortcutsLoaded = false;
const auto cancelledId = base::take(_shortcutsRequestId);
_session->api().request(cancelledId).cancel();
crl::on_main(_session, [=] {
if (cancelledId || hasUnknownMessages()) {
preloadShortcuts();
}
});
}
}
void ShortcutMessages::apply(const MTPDupdateNewQuickReply &update) {
const auto selfId = _session->userPeerId();
const auto &reply = update.vquick_reply();
auto foundId = BusinessShortcutId();
const auto shortcut = parseShortcut(reply);
for (auto &[id, existing] : _shortcuts.list) {
if (id == shortcut.id) {
foundId = id;
break;
} else if (existing.name == shortcut.name) {
foundId = id;
break;
}
}
if (foundId == shortcut.id) {
auto &already = _shortcuts.list[shortcut.id];
if (already != shortcut) {
already = shortcut;
_shortcutsChanged.fire({});
}
return;
} else if (foundId) {
_shortcuts.list.emplace(shortcut.id, shortcut);
mergeMessagesFromTo(foundId, shortcut.id);
_shortcuts.list.remove(foundId);
_shortcutIdChanges.fire({ foundId, shortcut.id });
_shortcutsChanged.fire({});
}
}
void ShortcutMessages::apply(const MTPDupdateQuickReplyMessage &update) {
@ -159,10 +296,30 @@ void ShortcutMessages::apply(const MTPDupdateQuickReplyMessage &update) {
if (!shortcutId) {
return;
}
const auto loaded = _data.contains(shortcutId);
auto &list = _data[shortcutId];
append(shortcutId, list, message);
sort(list);
_updates.fire_copy(shortcutId);
updateCount(shortcutId);
if (!loaded) {
request(shortcutId);
}
}
void ShortcutMessages::updateCount(BusinessShortcutId shortcutId) {
const auto i = _data.find(shortcutId);
const auto j = _shortcuts.list.find(shortcutId);
if (j == end(_shortcuts.list)) {
return;
}
const auto count = (i != end(_data))
? int(i->second.itemById.size())
: 0;
if (j->second.count != count) {
_shortcuts.list[shortcutId].count = count;
_shortcutsChanged.fire({});
}
}
void ShortcutMessages::apply(
@ -187,6 +344,10 @@ void ShortcutMessages::apply(
}
}
_updates.fire_copy(shortcutId);
updateCount(shortcutId);
cancelRequest(shortcutId);
request(shortcutId);
}
void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) {
@ -195,12 +356,17 @@ void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) {
return;
}
auto i = _data.find(shortcutId);
while (i != end(_data)) {
Assert(!i->second.itemById.empty());
while (i != end(_data) && !i->second.itemById.empty()) {
i->second.itemById.back().second->destroy();
i = _data.find(shortcutId);
}
_updates.fire_copy(shortcutId);
if (_data.contains(shortcutId)) {
updateCount(shortcutId);
} else {
_shortcuts.list.remove(shortcutId);
_shortcutIdChanges.fire({ shortcutId, 0 });
}
}
void ShortcutMessages::apply(
@ -283,30 +449,7 @@ void ShortcutMessages::preloadShortcuts() {
owner->processMessages(
data.vmessages(),
NewMessageType::Existing);
auto shortcuts = Shortcuts();
const auto messages = &owner->shortcutMessages();
for (const auto &reply : data.vquick_replies().v) {
const auto &data = reply.data();
const auto id = BusinessShortcutId(data.vshortcut_id().v);
shortcuts.list.emplace(id, Shortcut{
.name = qs(data.vshortcut()),
.topMessageId = messages->localMessageId(
data.vtop_message().v),
.count = data.vcount().v,
});
}
for (auto &[id, shortcut] : _shortcuts.list) {
if (id < 0) {
shortcuts.list.emplace(id, shortcut);
}
}
const auto changed = !_shortcutsLoaded
|| (shortcuts != _shortcuts);
if (changed) {
_shortcuts = std::move(shortcuts);
_shortcutsLoaded = true;
_shortcutsChanged.fire({});
}
updateShortcuts(data.vquick_replies().v);
}, [&](const MTPDmessages_quickRepliesNotModified &) {
if (!_shortcutsLoaded) {
_shortcutsLoaded = true;
@ -328,6 +471,11 @@ rpl::producer<> ShortcutMessages::shortcutsChanged() const {
return _shortcutsChanged.events();
}
auto ShortcutMessages::shortcutIdChanged() const
-> rpl::producer<ShortcutIdChange> {
return _shortcutIdChanges.events();
}
BusinessShortcutId ShortcutMessages::emplaceShortcut(QString name) {
Expects(_shortcutsLoaded);
@ -337,7 +485,7 @@ BusinessShortcutId ShortcutMessages::emplaceShortcut(QString name) {
}
}
const auto result = --_localShortcutId;
_shortcuts.list.emplace(result, Shortcut{ name });
_shortcuts.list.emplace(result, Shortcut{ .id = result, .name = name });
return result;
}
@ -348,6 +496,14 @@ Shortcut ShortcutMessages::lookupShortcut(BusinessShortcutId id) const {
return i->second;
}
void ShortcutMessages::cancelRequest(BusinessShortcutId shortcutId) {
const auto j = _requests.find(shortcutId);
if (j != end(_requests)) {
_session->api().request(j->second.requestId).cancel();
_requests.erase(j);
}
}
void ShortcutMessages::request(BusinessShortcutId shortcutId) {
auto &request = _requests[shortcutId];
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
@ -512,6 +668,7 @@ void ShortcutMessages::remove(not_null<const HistoryItem*> item) {
_data.erase(i);
}
_updates.fire_copy(shortcutId);
updateCount(shortcutId);
}
uint64 ShortcutMessages::countListHash(const List &list) const {
@ -537,11 +694,10 @@ uint64 ShortcutMessages::countListHash(const List &list) const {
MTPInputQuickReplyShortcut ShortcutIdToMTP(
not_null<Main::Session*> session,
BusinessShortcutId id) {
if (id >= 0) {
return MTP_inputQuickReplyShortcutId(MTP_int(id));
}
return MTP_inputQuickReplyShortcut(MTP_string(
session->data().shortcutMessages().lookupShortcut(id).name));
return id
? MTP_inputQuickReplyShortcut(MTP_string(
session->data().shortcutMessages().lookupShortcut(id).name))
: MTPInputQuickReplyShortcut();
}
} // namespace Data

View File

@ -22,15 +22,21 @@ class Session;
struct MessagesSlice;
struct Shortcut {
BusinessShortcutId id = 0;
int count = 0;
QString name;
MsgId topMessageId = 0;
int count = 0;
friend inline bool operator==(
const Shortcut &a,
const Shortcut &b) = default;
};
struct ShortcutIdChange {
BusinessShortcutId oldId = 0;
BusinessShortcutId newId = 0;
};
struct Shortcuts {
base::flat_map<BusinessShortcutId, Shortcut> list;
@ -69,6 +75,7 @@ public:
[[nodiscard]] const Shortcuts &shortcuts() const;
[[nodiscard]] bool shortcutsLoaded() const;
[[nodiscard]] rpl::producer<> shortcutsChanged() const;
[[nodiscard]] rpl::producer<ShortcutIdChange> shortcutIdChanged() const;
[[nodiscard]] BusinessShortcutId emplaceShortcut(QString name);
[[nodiscard]] Shortcut lookupShortcut(BusinessShortcutId id) const;
@ -100,6 +107,17 @@ private:
void remove(not_null<const HistoryItem*> item);
[[nodiscard]] uint64 countListHash(const List &list) const;
void clearOldRequests();
void cancelRequest(BusinessShortcutId shortcutId);
void updateCount(BusinessShortcutId shortcutId);
void scheduleShortcutsReload();
void mergeMessagesFromTo(
BusinessShortcutId fromId,
BusinessShortcutId toId);
void updateShortcuts(const QVector<MTPQuickReply> &list);
[[nodiscard]] Shortcut parseShortcut(const MTPQuickReply &reply) const;
[[nodiscard]] Shortcuts parseShortcuts(
const QVector<MTPQuickReply> &list) const;
const not_null<Main::Session*> _session;
const not_null<History*> _history;
@ -111,6 +129,7 @@ private:
Shortcuts _shortcuts;
rpl::event_stream<> _shortcutsChanged;
rpl::event_stream<ShortcutIdChange> _shortcutIdChanges;
BusinessShortcutId _localShortcutId = 0;
uint64 _shortcutsHash = 0;
mtpRequestId _shortcutsRequestId = 0;

View File

@ -1542,6 +1542,10 @@ bool HistoryItem::isBusinessShortcut() const {
return _shortcutId != 0;
}
void HistoryItem::setRealShortcutId(BusinessShortcutId id) {
_shortcutId = id;
}
void HistoryItem::destroy() {
_history->destroyMessage(this);
}

View File

@ -196,6 +196,7 @@ public:
[[nodiscard]] bool isUserpicSuggestion() const;
[[nodiscard]] BusinessShortcutId shortcutId() const;
[[nodiscard]] bool isBusinessShortcut() const;
void setRealShortcutId(BusinessShortcutId id);
void addLogEntryOriginal(
WebPageId localId,

View File

@ -442,7 +442,7 @@ void BottomInfo::paintReactions(
}
QSize BottomInfo::countCurrentSize(int newWidth) {
if (newWidth >= maxWidth()) {
if (newWidth >= maxWidth() || (_data.flags & Data::Flag::Shortcut)) {
return optimalSize();
}
const auto dateHeight = (_data.flags & Data::Flag::Sponsored)
@ -509,7 +509,8 @@ void BottomInfo::layoutRepliesText() {
if (!_data.replies
|| !*_data.replies
|| (_data.flags & Data::Flag::RepliesContext)
|| (_data.flags & Data::Flag::Sending)) {
|| (_data.flags & Data::Flag::Sending)
|| (_data.flags & Data::Flag::Shortcut)) {
_replies.clear();
return;
}
@ -549,6 +550,9 @@ void BottomInfo::layoutReactionsText() {
}
QSize BottomInfo::countOptimalSize() {
if (_data.flags & Data::Flag::Shortcut) {
return { st::historySendStateSpace / 2, st::msgDateFont->height };
}
auto width = 0;
if (_data.flags & (Data::Flag::OutLayout | Data::Flag::Sending)) {
width += st::historySendStateSpace;
@ -654,6 +658,9 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
if (item->isPinned() && message->context() != Context::Pinned) {
result.flags |= Flag::Pinned;
}
if (message->context() == Context::ShortcutMessages) {
result.flags |= Flag::Shortcut;
}
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
if (!msgsigned->isAnonymousRank) {
result.author = msgsigned->postAuthor;

View File

@ -44,6 +44,7 @@ public:
Sponsored = 0x10,
Pinned = 0x20,
Imported = 0x40,
Shortcut = 0x80,
//Unread, // We don't want to pass and update it in Date for now.
};
friend inline constexpr bool is_flag_type(Flag) { return true; };

View File

@ -1937,6 +1937,9 @@ int ListWidget::resizeGetHeight(int newWidth) {
_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom)
? (_minHeight - _itemsHeight - st::historyPaddingBottom)
: 0;
if (_emptyInfo) {
_emptyInfo->setVisible(isEmpty());
}
return _itemsTop + _itemsHeight + st::historyPaddingBottom;
}
@ -3934,6 +3937,9 @@ void ListWidget::replyNextMessage(FullMsgId fullId, bool next) {
void ListWidget::setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w) {
_emptyInfo = std::move(w);
if (_emptyInfo) {
_emptyInfo->setVisible(isEmpty());
}
}
ListWidget::~ListWidget() {

View File

@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/ui/dialogs_stories_list.h"
#include "lang/lang_keys.h"
#include "lang/lang_numbers_animation.h"
#include "info/info_wrap_widget.h"
#include "info/info_controller.h"
#include "info/profile/info_profile_values.h"
@ -721,25 +720,7 @@ bool TopBar::computeCanToggleStoryPin() const {
}
Ui::StringWithNumbers TopBar::generateSelectedText() const {
using Type = Storage::SharedMediaType;
const auto phrase = [&] {
switch (_selectedItems.type) {
case Type::Photo: return tr::lng_media_selected_photo;
case Type::GIF: return tr::lng_media_selected_gif;
case Type::Video: return tr::lng_media_selected_video;
case Type::File: return tr::lng_media_selected_file;
case Type::MusicFile: return tr::lng_media_selected_song;
case Type::Link: return tr::lng_media_selected_link;
case Type::RoundVoiceFile: return tr::lng_media_selected_audio;
case Type::PhotoVideo: return tr::lng_stories_row_count;
}
Unexpected("Type in TopBar::generateSelectedText()");
}();
return phrase(
tr::now,
lt_count,
_selectedItems.list.size(),
Ui::StringWithNumbers::FromString);
return _selectedItems.title(_selectedItems.list.size());
}
bool TopBar::selectionMode() const {

View File

@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum_topic.h"
#include "mainwidget.h"
#include "lang/lang_keys.h"
#include "lang/lang_numbers_animation.h"
#include "styles/style_chat.h" // popupMenuExpandedSeparator
#include "styles/style_info.h"
#include "styles/style_profile.h"
@ -64,6 +65,26 @@ const style::InfoTopBar &TopBarStyle(Wrap wrap) {
&& section.settingsType()->hasCustomTopBar();
}
[[nodiscard]] Fn<Ui::StringWithNumbers(int)> SelectedTitleForMedia(
Section::MediaType type) {
return [type](int count) {
using Type = Storage::SharedMediaType;
return [&] {
switch (type) {
case Type::Photo: return tr::lng_media_selected_photo;
case Type::GIF: return tr::lng_media_selected_gif;
case Type::Video: return tr::lng_media_selected_video;
case Type::File: return tr::lng_media_selected_file;
case Type::MusicFile: return tr::lng_media_selected_song;
case Type::Link: return tr::lng_media_selected_link;
case Type::RoundVoiceFile: return tr::lng_media_selected_audio;
case Type::PhotoVideo: return tr::lng_stories_row_count;
}
Unexpected("Type in TopBar::generateSelectedText()");
}()(tr::now, lt_count, count, Ui::StringWithNumbers::FromString);
};
}
} // namespace
struct WrapWidget::StackItem {
@ -71,6 +92,10 @@ struct WrapWidget::StackItem {
// std::shared_ptr<ContentMemento> anotherTab;
};
SelectedItems::SelectedItems(Section::MediaType mediaType)
: title(SelectedTitleForMedia(mediaType)) {
}
WrapWidget::WrapWidget(
QWidget *parent,
not_null<Window::SessionController*> window,
@ -609,7 +634,12 @@ void WrapWidget::finishShowContent() {
_desiredShadowVisibilities.fire(_content->desiredShadowVisibility());
_desiredBottomShadowVisibilities.fire(
_content->desiredBottomShadowVisibility());
_selectedLists.fire(_content->selectedListValue());
if (auto selection = _content->selectedListValue()) {
_selectedLists.fire(std::move(selection));
} else {
_selectedLists.fire(rpl::single(
SelectedItems(Storage::SharedMediaType::Photo)));
}
_scrollTillBottomChanges.fire(_content->scrollTillBottomChanges());
_topShadow->raise();
_topShadow->finishAnimating();

View File

@ -20,6 +20,7 @@ class PlainShadow;
class PopupMenu;
class IconButton;
class RoundRect;
struct StringWithNumbers;
} // namespace Ui
namespace Window {
@ -61,11 +62,10 @@ struct SelectedItem {
};
struct SelectedItems {
explicit SelectedItems(Storage::SharedMediaType type)
: type(type) {
}
SelectedItems() = default;
explicit SelectedItems(Storage::SharedMediaType type);
Storage::SharedMediaType type;
Fn<Ui::StringWithNumbers(int)> title;
std::vector<SelectedItem> list;
};

View File

@ -248,6 +248,14 @@ void Widget::enableBackButton() {
_flexibleScroll.backButtonEnables.fire({});
}
rpl::producer<SelectedItems> Widget::selectedListValue() const {
return _inner->selectedListValue();
}
void Widget::selectionAction(SelectionAction action) {
_inner->selectionAction(action);
}
void Widget::saveState(not_null<Memento*> memento) {
memento->setScrollTop(scrollTopSave());
}

View File

@ -80,6 +80,9 @@ public:
void enableBackButton() override;
rpl::producer<SelectedItems> selectedListValue() const override;
void selectionAction(SelectionAction action) override;
private:
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);

View File

@ -38,7 +38,6 @@ public:
~AwayMessage();
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
private:

View File

@ -41,6 +41,7 @@ public:
~Greeting();
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
const Ui::RoundRect *bottomSkipRounding() const {
return &_bottomSkipRounding;
@ -176,6 +177,10 @@ rpl::producer<QString> Greeting::title() {
return tr::lng_greeting_title();
}
rpl::producer<Type> Greeting::sectionShowOther() {
return _showOther.events();
}
void Greeting::setupContent(
not_null<Window::SessionController*> controller) {
using namespace rpl::mappers;

View File

@ -8,16 +8,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/business/settings_quick_replies.h"
#include "core/application.h"
#include "data/business/data_shortcut_messages.h"
#include "data/data_session.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/business/settings_recipients_helper.h"
#include "settings/business/settings_shortcut_messages.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
namespace Settings {
@ -31,12 +37,13 @@ public:
~QuickReplies();
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
private:
void setupContent(not_null<Window::SessionController*> controller);
void save();
rpl::variable<Data::BusinessRecipients> _recipients;
rpl::event_stream<Type> _showOther;
};
@ -57,6 +64,10 @@ rpl::producer<QString> QuickReplies::title() {
return tr::lng_replies_title();
}
rpl::producer<Type> QuickReplies::sectionShowOther() {
return _showOther.events();
}
void QuickReplies::setupContent(
not_null<Window::SessionController*> controller) {
using namespace rpl::mappers;
@ -73,24 +84,82 @@ void QuickReplies::setupContent(
});
Ui::AddSkip(content);
const auto enabled = content->add(object_ptr<Ui::SettingsButton>(
const auto add = content->add(object_ptr<Ui::SettingsButton>(
content,
tr::lng_replies_add(),
st::settingsButtonNoIcon
));
enabled->setClickedCallback([=] {
const auto owner = &controller->session().data();
const auto messages = &owner->shortcutMessages();
add->setClickedCallback([=] {
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_replies_add_title());
box->addRow(object_ptr<Ui::FlatLabel>(
box,
tr::lng_replies_add_shortcut(),
st::settingsAddReplyLabel));
const auto field = box->addRow(object_ptr<Ui::InputField>(
box,
st::settingsAddReplyField,
tr::lng_replies_add_placeholder(),
QString()));
box->setFocusCallback([=] {
field->setFocusFast();
});
const auto submit = [=] {
const auto weak = Ui::MakeWeak(box);
const auto name = field->getLastText().trimmed();
if (name.isEmpty()) {
field->showError();
} else {
const auto id = messages->emplaceShortcut(name);
_showOther.fire(ShortcutMessagesId(id));
}
if (const auto strong = weak.data()) {
strong->closeBox();
}
};
field->submits(
) | rpl::start_with_next(submit, field->lifetime());
box->addButton(tr::lng_settings_save(), submit);
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}));
});
const auto wrap = content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
content,
object_ptr<Ui::VerticalLayout>(content)));
const auto inner = wrap->entity();
Ui::AddSkip(content);
Ui::AddDivider(content);
Ui::AddSkip(content);
Ui::AddSkip(inner);
Ui::AddDivider(inner);
const auto inner = content->add(
object_ptr<Ui::VerticalLayout>(content));
rpl::single(rpl::empty) | rpl::then(
messages->shortcutsChanged()
) | rpl::start_with_next([=] {
while (inner->count()) {
delete inner->widgetAt(0);
}
const auto &shortcuts = messages->shortcuts();
auto i = 0;
for (const auto &shortcut : shortcuts.list) {
const auto name = shortcut.second.name;
AddButtonWithLabel(
inner,
rpl::single('/' + name),
tr::lng_forum_messages(
lt_count,
rpl::single(1. * shortcut.second.count)),
st::settingsButtonNoIcon
)->setClickedCallback([=] {
const auto id = messages->emplaceShortcut(name);
_showOther.fire(ShortcutMessagesId(id));
});
}
}, content->lifetime());
Ui::ResizeFitChild(this, content);
}

View File

@ -30,14 +30,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_sticker_toast.h"
#include "history/history.h"
#include "history/history_item.h"
#include "info/info_wrap_widget.h"
#include "inline_bots/inline_bot_result.h"
#include "lang/lang_keys.h"
#include "lang/lang_numbers_animation.h"
#include "main/main_session.h"
#include "menu/menu_send.h"
#include "settings/business/settings_recipients_helper.h"
#include "storage/localimageloader.h"
#include "storage/storage_account.h"
#include "storage/storage_media_prepare.h"
#include "storage/storage_shared_media.h"
#include "ui/chat/attach/attach_send_files_way.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h"
@ -72,13 +75,16 @@ public:
[[nodiscard]] static Type Id(BusinessShortcutId shortcutId);
[[nodiscard]] Type id() const final override {
return Id(_shortcutId);
return Id(_shortcutId.current());
}
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] rpl::producer<> sectionShowBack() override;
void setInnerFocus() override;
rpl::producer<Info::SelectedItems> selectedListValue() override;
void selectionAction(Info::SelectionAction action) override;
bool paintOuter(
not_null<QWidget*> outer,
int maxVisibleHeight,
@ -238,8 +244,9 @@ private:
const not_null<Window::SessionController*> _controller;
const not_null<Main::Session*> _session;
const not_null<Ui::ScrollArea*> _scroll;
const BusinessShortcutId _shortcutId;
const not_null<History*> _history;
rpl::variable<BusinessShortcutId> _shortcutId;
rpl::variable<QString> _shortcut;
std::shared_ptr<Ui::ChatStyle> _style;
std::shared_ptr<Ui::ChatTheme> _theme;
QPointer<ListWidget> _inner;
@ -248,6 +255,9 @@ private:
rpl::event_stream<> _showBackRequests;
bool _skipScrollEvent = false;
rpl::variable<Info::SelectedItems> _selectedItems
= Info::SelectedItems(Storage::SharedMediaType::kCount);
std::unique_ptr<StickerToast> _stickerToast;
FullMsgId _lastShownAt;
@ -287,12 +297,31 @@ ShortcutMessages::ShortcutMessages(
, _controller(controller)
, _session(&controller->session())
, _scroll(scroll)
, _shortcutId(shortcutId)
, _history(_session->data().history(_session->user()->id))
, _shortcutId(shortcutId)
, _shortcut(
_session->data().shortcutMessages().lookupShortcut(shortcutId).name)
, _cornerButtons(
_scroll,
controller->chatStyle(),
static_cast<HistoryView::CornerButtonsDelegate*>(this)) {
const auto messages = &_session->data().shortcutMessages();
messages->shortcutIdChanged(
) | rpl::start_with_next([=](Data::ShortcutIdChange change) {
if (change.oldId == _shortcutId.current()) {
if (change.newId) {
_shortcutId = change.newId;
} else {
_showBackRequests.fire({});
}
}
}, lifetime());
messages->shortcutsChanged(
) | rpl::start_with_next([=] {
_shortcut = messages->lookupShortcut(_shortcutId.current()).name;
}, lifetime());
controller->chatStyle()->paletteChanged(
) | rpl::start_with_next([=] {
_scroll->updateBars();
@ -351,7 +380,13 @@ Type ShortcutMessages::Id(BusinessShortcutId shortcutId) {
}
rpl::producer<QString> ShortcutMessages::title() {
return rpl::single(u"Editing messages list"_q);
return _shortcut.value() | rpl::map([=](const QString &shortcut) {
return (shortcut == u"away"_q)
? tr::lng_away_title()
: (shortcut == u"hello"_q)
? tr::lng_greeting_title()
: rpl::single('/' + shortcut);
}) | rpl::flatten_latest();
}
void ShortcutMessages::processScroll() {
@ -379,6 +414,18 @@ void ShortcutMessages::setInnerFocus() {
_composeControls->focus();
}
rpl::producer<Info::SelectedItems> ShortcutMessages::selectedListValue() {
return _selectedItems.value();
}
void ShortcutMessages::selectionAction(Info::SelectionAction action) {
switch (action) {
case Info::SelectionAction::Clear: clearSelected(); return;
case Info::SelectionAction::Delete: confirmDeleteSelected(); return;
}
Unexpected("Action in ShortcutMessages::selectionAction.");
}
bool ShortcutMessages::paintOuter(
not_null<QWidget*> outer,
int maxVisibleHeight,
@ -572,7 +619,7 @@ bool ShortcutMessages::listScrollTo(int top, bool syntetic) {
void ShortcutMessages::listCancelRequest() {
if (_inner && !_inner->getSelectedItems().empty()) {
//clearSelected();
clearSelected();
return;
} else if (_composeControls->handleCancelRequest()) {
return;
@ -592,13 +639,15 @@ rpl::producer<Data::MessagesSlice> ShortcutMessages::listSource(
Data::MessagePosition aroundId,
int limitBefore,
int limitAfter) {
const auto data = &_controller->session().data();
return rpl::single(rpl::empty) | rpl::then(
data->shortcutMessages().updates(_shortcutId)
) | rpl::map([=] {
return data->shortcutMessages().list(_shortcutId);
});
return rpl::never<Data::MessagesSlice>();
const auto messages = &_session->data().shortcutMessages();
return _shortcutId.value(
) | rpl::map([=](BusinessShortcutId shortcutId) {
return rpl::single(rpl::empty) | rpl::then(
messages->updates(shortcutId)
) | rpl::map([=] {
return messages->list(shortcutId);
});
}) | rpl::flatten_latest();
}
bool ShortcutMessages::listAllowsMultiSelect() {
@ -617,6 +666,24 @@ bool ShortcutMessages::listIsLessInOrder(
}
void ShortcutMessages::listSelectionChanged(SelectedItems &&items) {
auto value = Info::SelectedItems();
value.title = [](int count) {
return tr::lng_forum_messages(
tr::now,
lt_count,
count,
Ui::StringWithNumbers::FromString);
};
value.list = items | ranges::views::transform([](SelectedItem item) {
auto result = Info::SelectedItem(GlobalMsgId{ item.msgId });
result.canDelete = item.canDelete;
return result;
}) | ranges::to_vector;
_selectedItems = std::move(value);
if (items.empty()) {
doSetInnerFocus();
}
}
void ShortcutMessages::listMarkReadTill(not_null<HistoryItem*> item) {
@ -784,20 +851,21 @@ bool ShortcutMessages::cornerButtonsHas(CornerButtonType type) {
}
void ShortcutMessages::pushReplyReturn(not_null<HistoryItem*> item) {
if (item->shortcutId() == _shortcutId) {
if (item->shortcutId() == _shortcutId.current()) {
_cornerButtons.pushReplyReturn(item);
}
}
void ShortcutMessages::checkReplyReturns() {
const auto currentTop = _scroll->scrollTop();
const auto shortcutId = _shortcutId.current();
while (const auto replyReturn = _cornerButtons.replyReturn()) {
const auto position = replyReturn->position();
const auto scrollTop = _inner->scrollTopForPosition(position);
const auto below = scrollTop
? (currentTop >= std::min(*scrollTop, _scroll->scrollTopMax()))
: _inner->isBelowPosition(position);
if (below) {
if (replyReturn->shortcutId() != shortcutId || below) {
_cornerButtons.calculateNextReplyReturn();
} else {
break;
@ -858,7 +926,7 @@ Api::SendAction ShortcutMessages::prepareSendAction(
Api::SendOptions options) const {
auto result = Api::SendAction(_history, options);
result.replyTo = replyTo();
result.options.shortcutId = _shortcutId;
result.options.shortcutId = _shortcutId.current();
result.options.sendAs = _composeControls->sendAsPeer();
return result;
}

View File

@ -616,3 +616,19 @@ settingsWorkingHoursPicker: 200px;
settingsWorkingHoursPickerItemHeight: 40px;
settingsAwaySchedulePadding: margins(0px, 8px, 0px, 8px);
settingsAddReplyLabel: FlatLabel(defaultFlatLabel) {
minWidth: 256px;
}
settingsAddReplyField: InputField(defaultInputField) {
textBg: transparent;
textMargins: margins(0px, 10px, 0px, 2px);
placeholderFg: placeholderFg;
placeholderFgActive: placeholderFgActive;
placeholderFgError: placeholderFgActive;
placeholderMargins: margins(2px, 0px, 2px, 0px);
placeholderScale: 0.;
heightMin: 36px;
}

View File

@ -17,6 +17,11 @@ namespace anim {
enum class repeat : uchar;
} // namespace anim
namespace Info {
struct SelectedItems;
enum class SelectionAction;
} // namespace Info
namespace Main {
class Session;
} // namespace Main
@ -91,6 +96,13 @@ public:
virtual void setStepDataReference(std::any &data) {
}
[[nodiscard]] virtual auto selectedListValue()
-> rpl::producer<Info::SelectedItems> {
return nullptr;
}
virtual void selectionAction(Info::SelectionAction action) {
}
virtual bool paintOuter(
not_null<QWidget*> outer,
int maxVisibleHeight,