Implement proper shortcut management.
This commit is contained in:
parent
23e22de6ec
commit
f086203d25
|
@ -1560,6 +1560,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||||
if (const auto local = owner.message(id)) {
|
if (const auto local = owner.message(id)) {
|
||||||
if (local->isScheduled()) {
|
if (local->isScheduled()) {
|
||||||
session().data().scheduledMessages().apply(d, local);
|
session().data().scheduledMessages().apply(d, local);
|
||||||
|
} else if (local->isBusinessShortcut()) {
|
||||||
|
session().data().shortcutMessages().apply(d, local);
|
||||||
} else {
|
} else {
|
||||||
const auto existing = session().data().message(
|
const auto existing = session().data().message(
|
||||||
id.peer,
|
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 {
|
MsgId ShortcutMessages::localMessageId(MsgId remoteId) const {
|
||||||
return RemoteToLocalMsgId(remoteId);
|
return RemoteToLocalMsgId(remoteId);
|
||||||
}
|
}
|
||||||
|
@ -146,11 +234,60 @@ int ShortcutMessages::count(BusinessShortcutId shortcutId) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShortcutMessages::apply(const MTPDupdateQuickReplies &update) {
|
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) {
|
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) {
|
void ShortcutMessages::apply(const MTPDupdateQuickReplyMessage &update) {
|
||||||
|
@ -159,10 +296,30 @@ void ShortcutMessages::apply(const MTPDupdateQuickReplyMessage &update) {
|
||||||
if (!shortcutId) {
|
if (!shortcutId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto loaded = _data.contains(shortcutId);
|
||||||
auto &list = _data[shortcutId];
|
auto &list = _data[shortcutId];
|
||||||
append(shortcutId, list, message);
|
append(shortcutId, list, message);
|
||||||
sort(list);
|
sort(list);
|
||||||
_updates.fire_copy(shortcutId);
|
_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(
|
void ShortcutMessages::apply(
|
||||||
|
@ -187,6 +344,10 @@ void ShortcutMessages::apply(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_updates.fire_copy(shortcutId);
|
_updates.fire_copy(shortcutId);
|
||||||
|
updateCount(shortcutId);
|
||||||
|
|
||||||
|
cancelRequest(shortcutId);
|
||||||
|
request(shortcutId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) {
|
void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) {
|
||||||
|
@ -195,12 +356,17 @@ void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto i = _data.find(shortcutId);
|
auto i = _data.find(shortcutId);
|
||||||
while (i != end(_data)) {
|
while (i != end(_data) && !i->second.itemById.empty()) {
|
||||||
Assert(!i->second.itemById.empty());
|
|
||||||
i->second.itemById.back().second->destroy();
|
i->second.itemById.back().second->destroy();
|
||||||
i = _data.find(shortcutId);
|
i = _data.find(shortcutId);
|
||||||
}
|
}
|
||||||
_updates.fire_copy(shortcutId);
|
_updates.fire_copy(shortcutId);
|
||||||
|
if (_data.contains(shortcutId)) {
|
||||||
|
updateCount(shortcutId);
|
||||||
|
} else {
|
||||||
|
_shortcuts.list.remove(shortcutId);
|
||||||
|
_shortcutIdChanges.fire({ shortcutId, 0 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShortcutMessages::apply(
|
void ShortcutMessages::apply(
|
||||||
|
@ -283,30 +449,7 @@ void ShortcutMessages::preloadShortcuts() {
|
||||||
owner->processMessages(
|
owner->processMessages(
|
||||||
data.vmessages(),
|
data.vmessages(),
|
||||||
NewMessageType::Existing);
|
NewMessageType::Existing);
|
||||||
auto shortcuts = Shortcuts();
|
updateShortcuts(data.vquick_replies().v);
|
||||||
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({});
|
|
||||||
}
|
|
||||||
}, [&](const MTPDmessages_quickRepliesNotModified &) {
|
}, [&](const MTPDmessages_quickRepliesNotModified &) {
|
||||||
if (!_shortcutsLoaded) {
|
if (!_shortcutsLoaded) {
|
||||||
_shortcutsLoaded = true;
|
_shortcutsLoaded = true;
|
||||||
|
@ -328,6 +471,11 @@ rpl::producer<> ShortcutMessages::shortcutsChanged() const {
|
||||||
return _shortcutsChanged.events();
|
return _shortcutsChanged.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto ShortcutMessages::shortcutIdChanged() const
|
||||||
|
-> rpl::producer<ShortcutIdChange> {
|
||||||
|
return _shortcutIdChanges.events();
|
||||||
|
}
|
||||||
|
|
||||||
BusinessShortcutId ShortcutMessages::emplaceShortcut(QString name) {
|
BusinessShortcutId ShortcutMessages::emplaceShortcut(QString name) {
|
||||||
Expects(_shortcutsLoaded);
|
Expects(_shortcutsLoaded);
|
||||||
|
|
||||||
|
@ -337,7 +485,7 @@ BusinessShortcutId ShortcutMessages::emplaceShortcut(QString name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto result = --_localShortcutId;
|
const auto result = --_localShortcutId;
|
||||||
_shortcuts.list.emplace(result, Shortcut{ name });
|
_shortcuts.list.emplace(result, Shortcut{ .id = result, .name = name });
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,6 +496,14 @@ Shortcut ShortcutMessages::lookupShortcut(BusinessShortcutId id) const {
|
||||||
return i->second;
|
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) {
|
void ShortcutMessages::request(BusinessShortcutId shortcutId) {
|
||||||
auto &request = _requests[shortcutId];
|
auto &request = _requests[shortcutId];
|
||||||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||||
|
@ -512,6 +668,7 @@ void ShortcutMessages::remove(not_null<const HistoryItem*> item) {
|
||||||
_data.erase(i);
|
_data.erase(i);
|
||||||
}
|
}
|
||||||
_updates.fire_copy(shortcutId);
|
_updates.fire_copy(shortcutId);
|
||||||
|
updateCount(shortcutId);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 ShortcutMessages::countListHash(const List &list) const {
|
uint64 ShortcutMessages::countListHash(const List &list) const {
|
||||||
|
@ -537,11 +694,10 @@ uint64 ShortcutMessages::countListHash(const List &list) const {
|
||||||
MTPInputQuickReplyShortcut ShortcutIdToMTP(
|
MTPInputQuickReplyShortcut ShortcutIdToMTP(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
BusinessShortcutId id) {
|
BusinessShortcutId id) {
|
||||||
if (id >= 0) {
|
return id
|
||||||
return MTP_inputQuickReplyShortcutId(MTP_int(id));
|
? MTP_inputQuickReplyShortcut(MTP_string(
|
||||||
}
|
session->data().shortcutMessages().lookupShortcut(id).name))
|
||||||
return MTP_inputQuickReplyShortcut(MTP_string(
|
: MTPInputQuickReplyShortcut();
|
||||||
session->data().shortcutMessages().lookupShortcut(id).name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -22,15 +22,21 @@ class Session;
|
||||||
struct MessagesSlice;
|
struct MessagesSlice;
|
||||||
|
|
||||||
struct Shortcut {
|
struct Shortcut {
|
||||||
|
BusinessShortcutId id = 0;
|
||||||
|
int count = 0;
|
||||||
QString name;
|
QString name;
|
||||||
MsgId topMessageId = 0;
|
MsgId topMessageId = 0;
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
friend inline bool operator==(
|
friend inline bool operator==(
|
||||||
const Shortcut &a,
|
const Shortcut &a,
|
||||||
const Shortcut &b) = default;
|
const Shortcut &b) = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ShortcutIdChange {
|
||||||
|
BusinessShortcutId oldId = 0;
|
||||||
|
BusinessShortcutId newId = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct Shortcuts {
|
struct Shortcuts {
|
||||||
base::flat_map<BusinessShortcutId, Shortcut> list;
|
base::flat_map<BusinessShortcutId, Shortcut> list;
|
||||||
|
|
||||||
|
@ -69,6 +75,7 @@ public:
|
||||||
[[nodiscard]] const Shortcuts &shortcuts() const;
|
[[nodiscard]] const Shortcuts &shortcuts() const;
|
||||||
[[nodiscard]] bool shortcutsLoaded() const;
|
[[nodiscard]] bool shortcutsLoaded() const;
|
||||||
[[nodiscard]] rpl::producer<> shortcutsChanged() const;
|
[[nodiscard]] rpl::producer<> shortcutsChanged() const;
|
||||||
|
[[nodiscard]] rpl::producer<ShortcutIdChange> shortcutIdChanged() const;
|
||||||
[[nodiscard]] BusinessShortcutId emplaceShortcut(QString name);
|
[[nodiscard]] BusinessShortcutId emplaceShortcut(QString name);
|
||||||
[[nodiscard]] Shortcut lookupShortcut(BusinessShortcutId id) const;
|
[[nodiscard]] Shortcut lookupShortcut(BusinessShortcutId id) const;
|
||||||
|
|
||||||
|
@ -100,6 +107,17 @@ private:
|
||||||
void remove(not_null<const HistoryItem*> item);
|
void remove(not_null<const HistoryItem*> item);
|
||||||
[[nodiscard]] uint64 countListHash(const List &list) const;
|
[[nodiscard]] uint64 countListHash(const List &list) const;
|
||||||
void clearOldRequests();
|
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<Main::Session*> _session;
|
||||||
const not_null<History*> _history;
|
const not_null<History*> _history;
|
||||||
|
@ -111,6 +129,7 @@ private:
|
||||||
|
|
||||||
Shortcuts _shortcuts;
|
Shortcuts _shortcuts;
|
||||||
rpl::event_stream<> _shortcutsChanged;
|
rpl::event_stream<> _shortcutsChanged;
|
||||||
|
rpl::event_stream<ShortcutIdChange> _shortcutIdChanges;
|
||||||
BusinessShortcutId _localShortcutId = 0;
|
BusinessShortcutId _localShortcutId = 0;
|
||||||
uint64 _shortcutsHash = 0;
|
uint64 _shortcutsHash = 0;
|
||||||
mtpRequestId _shortcutsRequestId = 0;
|
mtpRequestId _shortcutsRequestId = 0;
|
||||||
|
|
|
@ -1542,6 +1542,10 @@ bool HistoryItem::isBusinessShortcut() const {
|
||||||
return _shortcutId != 0;
|
return _shortcutId != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryItem::setRealShortcutId(BusinessShortcutId id) {
|
||||||
|
_shortcutId = id;
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryItem::destroy() {
|
void HistoryItem::destroy() {
|
||||||
_history->destroyMessage(this);
|
_history->destroyMessage(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@ public:
|
||||||
[[nodiscard]] bool isUserpicSuggestion() const;
|
[[nodiscard]] bool isUserpicSuggestion() const;
|
||||||
[[nodiscard]] BusinessShortcutId shortcutId() const;
|
[[nodiscard]] BusinessShortcutId shortcutId() const;
|
||||||
[[nodiscard]] bool isBusinessShortcut() const;
|
[[nodiscard]] bool isBusinessShortcut() const;
|
||||||
|
void setRealShortcutId(BusinessShortcutId id);
|
||||||
|
|
||||||
void addLogEntryOriginal(
|
void addLogEntryOriginal(
|
||||||
WebPageId localId,
|
WebPageId localId,
|
||||||
|
|
|
@ -442,7 +442,7 @@ void BottomInfo::paintReactions(
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize BottomInfo::countCurrentSize(int newWidth) {
|
QSize BottomInfo::countCurrentSize(int newWidth) {
|
||||||
if (newWidth >= maxWidth()) {
|
if (newWidth >= maxWidth() || (_data.flags & Data::Flag::Shortcut)) {
|
||||||
return optimalSize();
|
return optimalSize();
|
||||||
}
|
}
|
||||||
const auto dateHeight = (_data.flags & Data::Flag::Sponsored)
|
const auto dateHeight = (_data.flags & Data::Flag::Sponsored)
|
||||||
|
@ -509,7 +509,8 @@ void BottomInfo::layoutRepliesText() {
|
||||||
if (!_data.replies
|
if (!_data.replies
|
||||||
|| !*_data.replies
|
|| !*_data.replies
|
||||||
|| (_data.flags & Data::Flag::RepliesContext)
|
|| (_data.flags & Data::Flag::RepliesContext)
|
||||||
|| (_data.flags & Data::Flag::Sending)) {
|
|| (_data.flags & Data::Flag::Sending)
|
||||||
|
|| (_data.flags & Data::Flag::Shortcut)) {
|
||||||
_replies.clear();
|
_replies.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -549,6 +550,9 @@ void BottomInfo::layoutReactionsText() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize BottomInfo::countOptimalSize() {
|
QSize BottomInfo::countOptimalSize() {
|
||||||
|
if (_data.flags & Data::Flag::Shortcut) {
|
||||||
|
return { st::historySendStateSpace / 2, st::msgDateFont->height };
|
||||||
|
}
|
||||||
auto width = 0;
|
auto width = 0;
|
||||||
if (_data.flags & (Data::Flag::OutLayout | Data::Flag::Sending)) {
|
if (_data.flags & (Data::Flag::OutLayout | Data::Flag::Sending)) {
|
||||||
width += st::historySendStateSpace;
|
width += st::historySendStateSpace;
|
||||||
|
@ -654,6 +658,9 @@ BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
|
||||||
if (item->isPinned() && message->context() != Context::Pinned) {
|
if (item->isPinned() && message->context() != Context::Pinned) {
|
||||||
result.flags |= Flag::Pinned;
|
result.flags |= Flag::Pinned;
|
||||||
}
|
}
|
||||||
|
if (message->context() == Context::ShortcutMessages) {
|
||||||
|
result.flags |= Flag::Shortcut;
|
||||||
|
}
|
||||||
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
|
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
|
||||||
if (!msgsigned->isAnonymousRank) {
|
if (!msgsigned->isAnonymousRank) {
|
||||||
result.author = msgsigned->postAuthor;
|
result.author = msgsigned->postAuthor;
|
||||||
|
|
|
@ -44,6 +44,7 @@ public:
|
||||||
Sponsored = 0x10,
|
Sponsored = 0x10,
|
||||||
Pinned = 0x20,
|
Pinned = 0x20,
|
||||||
Imported = 0x40,
|
Imported = 0x40,
|
||||||
|
Shortcut = 0x80,
|
||||||
//Unread, // We don't want to pass and update it in Date for now.
|
//Unread, // We don't want to pass and update it in Date for now.
|
||||||
};
|
};
|
||||||
friend inline constexpr bool is_flag_type(Flag) { return true; };
|
friend inline constexpr bool is_flag_type(Flag) { return true; };
|
||||||
|
|
|
@ -1937,6 +1937,9 @@ int ListWidget::resizeGetHeight(int newWidth) {
|
||||||
_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom)
|
_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom)
|
||||||
? (_minHeight - _itemsHeight - st::historyPaddingBottom)
|
? (_minHeight - _itemsHeight - st::historyPaddingBottom)
|
||||||
: 0;
|
: 0;
|
||||||
|
if (_emptyInfo) {
|
||||||
|
_emptyInfo->setVisible(isEmpty());
|
||||||
|
}
|
||||||
return _itemsTop + _itemsHeight + st::historyPaddingBottom;
|
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) {
|
void ListWidget::setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w) {
|
||||||
_emptyInfo = std::move(w);
|
_emptyInfo = std::move(w);
|
||||||
|
if (_emptyInfo) {
|
||||||
|
_emptyInfo->setVisible(isEmpty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListWidget::~ListWidget() {
|
ListWidget::~ListWidget() {
|
||||||
|
|
|
@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "dialogs/ui/dialogs_stories_list.h"
|
#include "dialogs/ui/dialogs_stories_list.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "lang/lang_numbers_animation.h"
|
|
||||||
#include "info/info_wrap_widget.h"
|
#include "info/info_wrap_widget.h"
|
||||||
#include "info/info_controller.h"
|
#include "info/info_controller.h"
|
||||||
#include "info/profile/info_profile_values.h"
|
#include "info/profile/info_profile_values.h"
|
||||||
|
@ -721,25 +720,7 @@ bool TopBar::computeCanToggleStoryPin() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ui::StringWithNumbers TopBar::generateSelectedText() const {
|
Ui::StringWithNumbers TopBar::generateSelectedText() const {
|
||||||
using Type = Storage::SharedMediaType;
|
return _selectedItems.title(_selectedItems.list.size());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TopBar::selectionMode() const {
|
bool TopBar::selectionMode() const {
|
||||||
|
|
|
@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_forum_topic.h"
|
#include "data/data_forum_topic.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
#include "lang/lang_numbers_animation.h"
|
||||||
#include "styles/style_chat.h" // popupMenuExpandedSeparator
|
#include "styles/style_chat.h" // popupMenuExpandedSeparator
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_profile.h"
|
#include "styles/style_profile.h"
|
||||||
|
@ -64,6 +65,26 @@ const style::InfoTopBar &TopBarStyle(Wrap wrap) {
|
||||||
&& section.settingsType()->hasCustomTopBar();
|
&& 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
|
} // namespace
|
||||||
|
|
||||||
struct WrapWidget::StackItem {
|
struct WrapWidget::StackItem {
|
||||||
|
@ -71,6 +92,10 @@ struct WrapWidget::StackItem {
|
||||||
// std::shared_ptr<ContentMemento> anotherTab;
|
// std::shared_ptr<ContentMemento> anotherTab;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SelectedItems::SelectedItems(Section::MediaType mediaType)
|
||||||
|
: title(SelectedTitleForMedia(mediaType)) {
|
||||||
|
}
|
||||||
|
|
||||||
WrapWidget::WrapWidget(
|
WrapWidget::WrapWidget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Window::SessionController*> window,
|
not_null<Window::SessionController*> window,
|
||||||
|
@ -609,7 +634,12 @@ void WrapWidget::finishShowContent() {
|
||||||
_desiredShadowVisibilities.fire(_content->desiredShadowVisibility());
|
_desiredShadowVisibilities.fire(_content->desiredShadowVisibility());
|
||||||
_desiredBottomShadowVisibilities.fire(
|
_desiredBottomShadowVisibilities.fire(
|
||||||
_content->desiredBottomShadowVisibility());
|
_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());
|
_scrollTillBottomChanges.fire(_content->scrollTillBottomChanges());
|
||||||
_topShadow->raise();
|
_topShadow->raise();
|
||||||
_topShadow->finishAnimating();
|
_topShadow->finishAnimating();
|
||||||
|
|
|
@ -20,6 +20,7 @@ class PlainShadow;
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
class IconButton;
|
class IconButton;
|
||||||
class RoundRect;
|
class RoundRect;
|
||||||
|
struct StringWithNumbers;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
|
@ -61,11 +62,10 @@ struct SelectedItem {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SelectedItems {
|
struct SelectedItems {
|
||||||
explicit SelectedItems(Storage::SharedMediaType type)
|
SelectedItems() = default;
|
||||||
: type(type) {
|
explicit SelectedItems(Storage::SharedMediaType type);
|
||||||
}
|
|
||||||
|
|
||||||
Storage::SharedMediaType type;
|
Fn<Ui::StringWithNumbers(int)> title;
|
||||||
std::vector<SelectedItem> list;
|
std::vector<SelectedItem> list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -248,6 +248,14 @@ void Widget::enableBackButton() {
|
||||||
_flexibleScroll.backButtonEnables.fire({});
|
_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) {
|
void Widget::saveState(not_null<Memento*> memento) {
|
||||||
memento->setScrollTop(scrollTopSave());
|
memento->setScrollTop(scrollTopSave());
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,9 @@ public:
|
||||||
|
|
||||||
void enableBackButton() override;
|
void enableBackButton() override;
|
||||||
|
|
||||||
|
rpl::producer<SelectedItems> selectedListValue() const override;
|
||||||
|
void selectionAction(SelectionAction action) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void saveState(not_null<Memento*> memento);
|
void saveState(not_null<Memento*> memento);
|
||||||
void restoreState(not_null<Memento*> memento);
|
void restoreState(not_null<Memento*> memento);
|
||||||
|
|
|
@ -38,7 +38,6 @@ public:
|
||||||
~AwayMessage();
|
~AwayMessage();
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<QString> title() override;
|
[[nodiscard]] rpl::producer<QString> title() override;
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
|
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -41,6 +41,7 @@ public:
|
||||||
~Greeting();
|
~Greeting();
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<QString> title() override;
|
[[nodiscard]] rpl::producer<QString> title() override;
|
||||||
|
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
|
||||||
|
|
||||||
const Ui::RoundRect *bottomSkipRounding() const {
|
const Ui::RoundRect *bottomSkipRounding() const {
|
||||||
return &_bottomSkipRounding;
|
return &_bottomSkipRounding;
|
||||||
|
@ -176,6 +177,10 @@ rpl::producer<QString> Greeting::title() {
|
||||||
return tr::lng_greeting_title();
|
return tr::lng_greeting_title();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<Type> Greeting::sectionShowOther() {
|
||||||
|
return _showOther.events();
|
||||||
|
}
|
||||||
|
|
||||||
void Greeting::setupContent(
|
void Greeting::setupContent(
|
||||||
not_null<Window::SessionController*> controller) {
|
not_null<Window::SessionController*> controller) {
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
|
|
|
@ -8,16 +8,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "settings/business/settings_quick_replies.h"
|
#include "settings/business/settings_quick_replies.h"
|
||||||
|
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
#include "data/business/data_shortcut_messages.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "settings/business/settings_recipients_helper.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/text/text_utilities.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/fields/input_field.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "ui/vertical_list.h"
|
#include "ui/vertical_list.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
|
|
||||||
namespace Settings {
|
namespace Settings {
|
||||||
|
@ -31,12 +37,13 @@ public:
|
||||||
~QuickReplies();
|
~QuickReplies();
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<QString> title() override;
|
[[nodiscard]] rpl::producer<QString> title() override;
|
||||||
|
[[nodiscard]] rpl::producer<Type> sectionShowOther() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupContent(not_null<Window::SessionController*> controller);
|
void setupContent(not_null<Window::SessionController*> controller);
|
||||||
void save();
|
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();
|
return tr::lng_replies_title();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<Type> QuickReplies::sectionShowOther() {
|
||||||
|
return _showOther.events();
|
||||||
|
}
|
||||||
|
|
||||||
void QuickReplies::setupContent(
|
void QuickReplies::setupContent(
|
||||||
not_null<Window::SessionController*> controller) {
|
not_null<Window::SessionController*> controller) {
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
|
@ -73,24 +84,82 @@ void QuickReplies::setupContent(
|
||||||
});
|
});
|
||||||
|
|
||||||
Ui::AddSkip(content);
|
Ui::AddSkip(content);
|
||||||
const auto enabled = content->add(object_ptr<Ui::SettingsButton>(
|
const auto add = content->add(object_ptr<Ui::SettingsButton>(
|
||||||
content,
|
content,
|
||||||
tr::lng_replies_add(),
|
tr::lng_replies_add(),
|
||||||
st::settingsButtonNoIcon
|
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(
|
Ui::AddSkip(content);
|
||||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
Ui::AddDivider(content);
|
||||||
content,
|
Ui::AddSkip(content);
|
||||||
object_ptr<Ui::VerticalLayout>(content)));
|
|
||||||
const auto inner = wrap->entity();
|
|
||||||
|
|
||||||
Ui::AddSkip(inner);
|
const auto inner = content->add(
|
||||||
Ui::AddDivider(inner);
|
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);
|
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/view/history_view_sticker_toast.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "info/info_wrap_widget.h"
|
||||||
#include "inline_bots/inline_bot_result.h"
|
#include "inline_bots/inline_bot_result.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
#include "lang/lang_numbers_animation.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "menu/menu_send.h"
|
#include "menu/menu_send.h"
|
||||||
#include "settings/business/settings_recipients_helper.h"
|
#include "settings/business/settings_recipients_helper.h"
|
||||||
#include "storage/localimageloader.h"
|
#include "storage/localimageloader.h"
|
||||||
#include "storage/storage_account.h"
|
#include "storage/storage_account.h"
|
||||||
#include "storage/storage_media_prepare.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/attach/attach_send_files_way.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
#include "ui/chat/chat_theme.h"
|
#include "ui/chat/chat_theme.h"
|
||||||
|
@ -72,13 +75,16 @@ public:
|
||||||
[[nodiscard]] static Type Id(BusinessShortcutId shortcutId);
|
[[nodiscard]] static Type Id(BusinessShortcutId shortcutId);
|
||||||
|
|
||||||
[[nodiscard]] Type id() const final override {
|
[[nodiscard]] Type id() const final override {
|
||||||
return Id(_shortcutId);
|
return Id(_shortcutId.current());
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<QString> title() override;
|
[[nodiscard]] rpl::producer<QString> title() override;
|
||||||
[[nodiscard]] rpl::producer<> sectionShowBack() override;
|
[[nodiscard]] rpl::producer<> sectionShowBack() override;
|
||||||
void setInnerFocus() override;
|
void setInnerFocus() override;
|
||||||
|
|
||||||
|
rpl::producer<Info::SelectedItems> selectedListValue() override;
|
||||||
|
void selectionAction(Info::SelectionAction action) override;
|
||||||
|
|
||||||
bool paintOuter(
|
bool paintOuter(
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
int maxVisibleHeight,
|
int maxVisibleHeight,
|
||||||
|
@ -238,8 +244,9 @@ private:
|
||||||
const not_null<Window::SessionController*> _controller;
|
const not_null<Window::SessionController*> _controller;
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
const not_null<Ui::ScrollArea*> _scroll;
|
const not_null<Ui::ScrollArea*> _scroll;
|
||||||
const BusinessShortcutId _shortcutId;
|
|
||||||
const not_null<History*> _history;
|
const not_null<History*> _history;
|
||||||
|
rpl::variable<BusinessShortcutId> _shortcutId;
|
||||||
|
rpl::variable<QString> _shortcut;
|
||||||
std::shared_ptr<Ui::ChatStyle> _style;
|
std::shared_ptr<Ui::ChatStyle> _style;
|
||||||
std::shared_ptr<Ui::ChatTheme> _theme;
|
std::shared_ptr<Ui::ChatTheme> _theme;
|
||||||
QPointer<ListWidget> _inner;
|
QPointer<ListWidget> _inner;
|
||||||
|
@ -248,6 +255,9 @@ private:
|
||||||
rpl::event_stream<> _showBackRequests;
|
rpl::event_stream<> _showBackRequests;
|
||||||
bool _skipScrollEvent = false;
|
bool _skipScrollEvent = false;
|
||||||
|
|
||||||
|
rpl::variable<Info::SelectedItems> _selectedItems
|
||||||
|
= Info::SelectedItems(Storage::SharedMediaType::kCount);
|
||||||
|
|
||||||
std::unique_ptr<StickerToast> _stickerToast;
|
std::unique_ptr<StickerToast> _stickerToast;
|
||||||
|
|
||||||
FullMsgId _lastShownAt;
|
FullMsgId _lastShownAt;
|
||||||
|
@ -287,12 +297,31 @@ ShortcutMessages::ShortcutMessages(
|
||||||
, _controller(controller)
|
, _controller(controller)
|
||||||
, _session(&controller->session())
|
, _session(&controller->session())
|
||||||
, _scroll(scroll)
|
, _scroll(scroll)
|
||||||
, _shortcutId(shortcutId)
|
|
||||||
, _history(_session->data().history(_session->user()->id))
|
, _history(_session->data().history(_session->user()->id))
|
||||||
|
, _shortcutId(shortcutId)
|
||||||
|
, _shortcut(
|
||||||
|
_session->data().shortcutMessages().lookupShortcut(shortcutId).name)
|
||||||
, _cornerButtons(
|
, _cornerButtons(
|
||||||
_scroll,
|
_scroll,
|
||||||
controller->chatStyle(),
|
controller->chatStyle(),
|
||||||
static_cast<HistoryView::CornerButtonsDelegate*>(this)) {
|
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(
|
controller->chatStyle()->paletteChanged(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
_scroll->updateBars();
|
_scroll->updateBars();
|
||||||
|
@ -351,7 +380,13 @@ Type ShortcutMessages::Id(BusinessShortcutId shortcutId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<QString> ShortcutMessages::title() {
|
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() {
|
void ShortcutMessages::processScroll() {
|
||||||
|
@ -379,6 +414,18 @@ void ShortcutMessages::setInnerFocus() {
|
||||||
_composeControls->focus();
|
_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(
|
bool ShortcutMessages::paintOuter(
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
int maxVisibleHeight,
|
int maxVisibleHeight,
|
||||||
|
@ -572,7 +619,7 @@ bool ShortcutMessages::listScrollTo(int top, bool syntetic) {
|
||||||
|
|
||||||
void ShortcutMessages::listCancelRequest() {
|
void ShortcutMessages::listCancelRequest() {
|
||||||
if (_inner && !_inner->getSelectedItems().empty()) {
|
if (_inner && !_inner->getSelectedItems().empty()) {
|
||||||
//clearSelected();
|
clearSelected();
|
||||||
return;
|
return;
|
||||||
} else if (_composeControls->handleCancelRequest()) {
|
} else if (_composeControls->handleCancelRequest()) {
|
||||||
return;
|
return;
|
||||||
|
@ -592,13 +639,15 @@ rpl::producer<Data::MessagesSlice> ShortcutMessages::listSource(
|
||||||
Data::MessagePosition aroundId,
|
Data::MessagePosition aroundId,
|
||||||
int limitBefore,
|
int limitBefore,
|
||||||
int limitAfter) {
|
int limitAfter) {
|
||||||
const auto data = &_controller->session().data();
|
const auto messages = &_session->data().shortcutMessages();
|
||||||
return rpl::single(rpl::empty) | rpl::then(
|
return _shortcutId.value(
|
||||||
data->shortcutMessages().updates(_shortcutId)
|
) | rpl::map([=](BusinessShortcutId shortcutId) {
|
||||||
) | rpl::map([=] {
|
return rpl::single(rpl::empty) | rpl::then(
|
||||||
return data->shortcutMessages().list(_shortcutId);
|
messages->updates(shortcutId)
|
||||||
});
|
) | rpl::map([=] {
|
||||||
return rpl::never<Data::MessagesSlice>();
|
return messages->list(shortcutId);
|
||||||
|
});
|
||||||
|
}) | rpl::flatten_latest();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShortcutMessages::listAllowsMultiSelect() {
|
bool ShortcutMessages::listAllowsMultiSelect() {
|
||||||
|
@ -617,6 +666,24 @@ bool ShortcutMessages::listIsLessInOrder(
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShortcutMessages::listSelectionChanged(SelectedItems &&items) {
|
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) {
|
void ShortcutMessages::listMarkReadTill(not_null<HistoryItem*> item) {
|
||||||
|
@ -784,20 +851,21 @@ bool ShortcutMessages::cornerButtonsHas(CornerButtonType type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShortcutMessages::pushReplyReturn(not_null<HistoryItem*> item) {
|
void ShortcutMessages::pushReplyReturn(not_null<HistoryItem*> item) {
|
||||||
if (item->shortcutId() == _shortcutId) {
|
if (item->shortcutId() == _shortcutId.current()) {
|
||||||
_cornerButtons.pushReplyReturn(item);
|
_cornerButtons.pushReplyReturn(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShortcutMessages::checkReplyReturns() {
|
void ShortcutMessages::checkReplyReturns() {
|
||||||
const auto currentTop = _scroll->scrollTop();
|
const auto currentTop = _scroll->scrollTop();
|
||||||
|
const auto shortcutId = _shortcutId.current();
|
||||||
while (const auto replyReturn = _cornerButtons.replyReturn()) {
|
while (const auto replyReturn = _cornerButtons.replyReturn()) {
|
||||||
const auto position = replyReturn->position();
|
const auto position = replyReturn->position();
|
||||||
const auto scrollTop = _inner->scrollTopForPosition(position);
|
const auto scrollTop = _inner->scrollTopForPosition(position);
|
||||||
const auto below = scrollTop
|
const auto below = scrollTop
|
||||||
? (currentTop >= std::min(*scrollTop, _scroll->scrollTopMax()))
|
? (currentTop >= std::min(*scrollTop, _scroll->scrollTopMax()))
|
||||||
: _inner->isBelowPosition(position);
|
: _inner->isBelowPosition(position);
|
||||||
if (below) {
|
if (replyReturn->shortcutId() != shortcutId || below) {
|
||||||
_cornerButtons.calculateNextReplyReturn();
|
_cornerButtons.calculateNextReplyReturn();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
@ -858,7 +926,7 @@ Api::SendAction ShortcutMessages::prepareSendAction(
|
||||||
Api::SendOptions options) const {
|
Api::SendOptions options) const {
|
||||||
auto result = Api::SendAction(_history, options);
|
auto result = Api::SendAction(_history, options);
|
||||||
result.replyTo = replyTo();
|
result.replyTo = replyTo();
|
||||||
result.options.shortcutId = _shortcutId;
|
result.options.shortcutId = _shortcutId.current();
|
||||||
result.options.sendAs = _composeControls->sendAsPeer();
|
result.options.sendAs = _composeControls->sendAsPeer();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -616,3 +616,19 @@ settingsWorkingHoursPicker: 200px;
|
||||||
settingsWorkingHoursPickerItemHeight: 40px;
|
settingsWorkingHoursPickerItemHeight: 40px;
|
||||||
|
|
||||||
settingsAwaySchedulePadding: margins(0px, 8px, 0px, 8px);
|
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;
|
enum class repeat : uchar;
|
||||||
} // namespace anim
|
} // namespace anim
|
||||||
|
|
||||||
|
namespace Info {
|
||||||
|
struct SelectedItems;
|
||||||
|
enum class SelectionAction;
|
||||||
|
} // namespace Info
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
@ -91,6 +96,13 @@ public:
|
||||||
virtual void setStepDataReference(std::any &data) {
|
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(
|
virtual bool paintOuter(
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
int maxVisibleHeight,
|
int maxVisibleHeight,
|
||||||
|
|
Loading…
Reference in New Issue