mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 15:17:07 +02:00
Implement proper shortcut management.
This commit is contained in:
parent
23e22de6ec
commit
f086203d25
19 changed files with 474 additions and 87 deletions
Telegram/SourceFiles
api
data/business
history
info
settings
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1542,6 +1542,10 @@ bool HistoryItem::isBusinessShortcut() const {
|
|||
return _shortcutId != 0;
|
||||
}
|
||||
|
||||
void HistoryItem::setRealShortcutId(BusinessShortcutId id) {
|
||||
_shortcutId = id;
|
||||
}
|
||||
|
||||
void HistoryItem::destroy() {
|
||||
_history->destroyMessage(this);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; };
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -38,7 +38,6 @@ public:
|
|||
~AwayMessage();
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
|
||||
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue